/* * 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 static com.android.launcher3.Flags.enableTieredWidgetsByDefaultInPicker; import static com.android.launcher3.UtilitiesKt.CLIP_CHILDREN_FALSE_MODIFIER; import static com.android.launcher3.UtilitiesKt.CLIP_TO_PADDING_FALSE_MODIFIER; import static com.android.launcher3.UtilitiesKt.modifyAttributesOnViewTree; import static com.android.launcher3.UtilitiesKt.restoreAttributesOnViewTree; import static com.android.launcher3.icons.cache.CacheLookupFlag.DEFAULT_LOOKUP_FLAG; import static com.android.launcher3.widget.picker.WidgetsListItemAnimator.WIDGET_LIST_ITEM_APPEARANCE_DELAY; import static com.android.launcher3.widget.picker.model.data.WidgetPickerDataUtils.findContentEntryForPackageUser; import android.content.Context; import android.graphics.Rect; import android.os.Process; import android.os.UserHandle; import android.util.AttributeSet; import android.view.Gravity; import android.view.LayoutInflater; import android.view.MenuItem; import android.view.MotionEvent; import android.view.View; import android.view.ViewGroup; import android.view.ViewParent; import android.view.accessibility.AccessibilityNodeInfo; import android.widget.FrameLayout; import android.widget.LinearLayout; import android.widget.PopupMenu; import android.widget.ScrollView; import android.widget.TextView; import androidx.annotation.NonNull; import androidx.annotation.Nullable; import androidx.annotation.Px; import com.android.launcher3.DeviceProfile; import com.android.launcher3.R; import com.android.launcher3.Utilities; import com.android.launcher3.icons.cache.CacheLookupFlag; import com.android.launcher3.model.WidgetItem; import com.android.launcher3.model.data.PackageItemInfo; import com.android.launcher3.recyclerview.ViewHolderBinder; import com.android.launcher3.util.PackageUserKey; import com.android.launcher3.widget.WidgetCell; 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 MINIMUM_WIDTH_LEFT_PANE_FOLDABLE_DP = 268; private static final int MAXIMUM_WIDTH_LEFT_PANE_FOLDABLE_DP = 395; private static final String SUGGESTIONS_PACKAGE_NAME = "widgets_list_suggestions_entry"; // This ratio defines the max percentage of content area that the recommendations can display // with respect to the bottom sheet's height. private static final float RECOMMENDATION_SECTION_HEIGHT_RATIO_TWO_PANE = 0.70f; private FrameLayout mSuggestedWidgetsContainer; private WidgetsListHeader mSuggestedWidgetsHeader; private PackageUserKey mSuggestedWidgetsPackageUserKey; private View mPrimaryWidgetListView; private LinearLayout mRightPane; private ScrollView mRightPaneScrollView; private WidgetsListTableViewHolderBinder mWidgetsListTableViewHolderBinder; private boolean mOldIsSwipeToDismissInProgress; private int mActivePage = -1; @Nullable private PackageUserKey mSelectedHeader; private TextView mHeaderDescription; /** * A menu displayed for options (e.g. "show all widgets" filter) around widget lists in the * picker. */ protected View mWidgetOptionsMenu; /** * State of the options in the menu (if displayed to the user). */ @Nullable protected WidgetOptionsMenuState mWidgetOptionsMenuState = null; 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_two_pane_sheet_paged_view : R.layout.widgets_two_pane_sheet_recyclerview; layoutInflater.inflate(contentLayoutRes, findViewById(R.id.recycler_view_container), true); setupViews(); mWidgetsListTableViewHolderBinder = new WidgetsListTableViewHolderBinder(mActivityContext, layoutInflater, this, this); mWidgetRecommendationsContainer = mContent.findViewById( R.id.widget_recommendations_container); mWidgetRecommendationsView = mContent.findViewById( R.id.widget_recommendations_view); mWidgetRecommendationsView.initParentViews(mWidgetRecommendationsContainer); mWidgetRecommendationsView.setWidgetCellLongClickListener(this); mWidgetRecommendationsView.setWidgetCellOnClickListener(this); if (!mDeviceProfile.isTwoPanels) { mWidgetRecommendationsView.enableFullPageViewIfLowDensity(); } // To save the currently displayed page, so that, it can be requested when rebinding // recommendations with different size constraints. mWidgetRecommendationsView.addPageSwitchListener( newPage -> mRecommendationsCurrentPage = newPage); mHeaderTitle = mContent.findViewById(R.id.title); mHeaderDescription = mContent.findViewById(R.id.widget_picker_description); mWidgetOptionsMenu = mContent.findViewById(R.id.widget_picker_widget_options_menu); if (!enableTieredWidgetsByDefaultInPicker()) { setupWidgetOptionsMenu(); } mRightPane = mContent.findViewById(R.id.right_pane); mRightPaneScrollView = mContent.findViewById(R.id.right_pane_scroll_view); mRightPaneScrollView.setOverScrollMode(View.OVER_SCROLL_NEVER); mPrimaryWidgetListView = findViewById(R.id.primary_widgets_list_view); mPrimaryWidgetListView.setOutlineProvider(mViewOutlineProvider); mPrimaryWidgetListView.setClipToOutline(true); onWidgetsBound(); // Set the fast scroller as not visible for two pane layout. mFastScroller.setVisibility(GONE); } @Override public void mayUpdateTitleAndDescription(@Nullable String title, @Nullable String description) { if (title != null) { mHeaderTitle.setText(title); } if (description != null) { mHeaderDescription.setText(description); mHeaderDescription.setVisibility(VISIBLE); } } protected void setupWidgetOptionsMenu() { mWidgetOptionsMenu.setOnClickListener(new OnClickListener() { @Override public void onClick(View v) { if (mWidgetOptionsMenuState != null) { PopupMenu popupMenu = new PopupMenu(mActivityContext, /*anchor=*/ v, Gravity.END); MenuItem menuItem = popupMenu.getMenu().add( R.string.widget_picker_show_all_widgets_menu_item_title); menuItem.setCheckable(true); menuItem.setChecked(mWidgetOptionsMenuState.showAllWidgets); menuItem.setOnMenuItemClickListener( item -> onShowAllWidgetsMenuItemClick(item)); popupMenu.show(); } } }); } private boolean onShowAllWidgetsMenuItemClick(MenuItem menuItem) { mWidgetOptionsMenuState.showAllWidgets = !mWidgetOptionsMenuState.showAllWidgets; menuItem.setChecked(mWidgetOptionsMenuState.showAllWidgets); // Refresh widgets onWidgetsBound(); if (mIsInSearchMode) { mSearchBar.reset(); } else if (!mSuggestedWidgetsPackageUserKey.equals(mSelectedHeader)) { mAdapters.get(mActivePage).mWidgetsListAdapter.selectFirstHeaderEntry(); mAdapters.get(mActivePage).mWidgetsRecyclerView.scrollToTop(); } return true; } @Override protected int getTabletHorizontalMargin(DeviceProfile deviceProfile) { // two pane picker is full width for fold as well as tablet. return getResources().getDimensionPixelSize( R.dimen.widget_picker_two_panels_left_right_margin); } @Override protected void onUserSwipeToDismissProgressChanged() { super.onUserSwipeToDismissProgressChanged(); boolean isSwipeToDismissInProgress = mSwipeToDismissProgress.value > 0; if (isSwipeToDismissInProgress == mOldIsSwipeToDismissInProgress) { return; } mOldIsSwipeToDismissInProgress = isSwipeToDismissInProgress; if (isSwipeToDismissInProgress) { modifyAttributesOnViewTree(mPrimaryWidgetListView, (ViewParent) mContent, CLIP_CHILDREN_FALSE_MODIFIER); modifyAttributesOnViewTree(mRightPaneScrollView, (ViewParent) mContent, CLIP_CHILDREN_FALSE_MODIFIER, CLIP_TO_PADDING_FALSE_MODIFIER); } else { restoreAttributesOnViewTree(mPrimaryWidgetListView, mContent, CLIP_CHILDREN_FALSE_MODIFIER); restoreAttributesOnViewTree(mRightPaneScrollView, mContent, CLIP_CHILDREN_FALSE_MODIFIER, CLIP_TO_PADDING_FALSE_MODIFIER); } } @Override protected void onLayout(boolean changed, int l, int t, int r, int b) { super.onLayout(changed, l, t, r, b); if (changed && mDeviceProfile.isTwoPanels) { LinearLayout layout = mContent.findViewById(R.id.linear_layout_container); FrameLayout leftPane = layout.findViewById(R.id.recycler_view_container); LinearLayout.LayoutParams layoutParams = (LayoutParams) leftPane.getLayoutParams(); // Width is 1/3 of the sheet unless it's less than min width or max width int leftPaneWidth = layout.getMeasuredWidth() / 3; @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) { layoutParams.width = minLeftPaneWidthPx; } else if (leftPaneWidth > maxLeftPaneWidthPx) { layoutParams.width = maxLeftPaneWidthPx; } else { layoutParams.width = 0; } layoutParams.weight = layoutParams.width == 0 ? 0.33F : 0; post(() -> { // The following calls all trigger requestLayout, so we post them to avoid // calling requestLayout during a layout pass. This also fixes the related warnings // in logcat. leftPane.setLayoutParams(layoutParams); requestApplyInsets(); if (mSelectedHeader != null) { if (mSelectedHeader.equals(mSuggestedWidgetsPackageUserKey)) { mSuggestedWidgetsHeader.callOnClick(); } else { getHeaderChangeListener().onHeaderChanged(mSelectedHeader); } } }); } } // Used by the two pane sheet to show 3-dot menu to toggle between default lists and all lists // when enableTieredWidgetsByDefaultInPicker is OFF. This code path and the 3-dot menu can be // safely deleted when it's alternative "enableTieredWidgetsByDefaultInPicker" flag is inlined. @Override protected List getWidgetsToDisplay() { List allWidgets = mActivityContext.getWidgetPickerDataProvider().get().getAllWidgets(); List defaultWidgets = mActivityContext.getWidgetPickerDataProvider().get().getDefaultWidgets(); if (allWidgets.isEmpty() || defaultWidgets.isEmpty()) { // no menu if there are no default widgets to show mWidgetOptionsMenuState = null; mWidgetOptionsMenu.setVisibility(GONE); } else { if (mWidgetOptionsMenuState == null) { mWidgetOptionsMenuState = new WidgetOptionsMenuState(); } mWidgetOptionsMenu.setVisibility(VISIBLE); return mWidgetOptionsMenuState.showAllWidgets ? allWidgets : defaultWidgets; } return allWidgets; } @Override public void onWidgetsBound() { super.onWidgetsBound(); if (mRecommendedWidgetsCount == 0 && mSelectedHeader == null) { mAdapters.get(mActivePage).mWidgetsListAdapter.selectFirstHeaderEntry(); mAdapters.get(mActivePage).mWidgetsRecyclerView.scrollToTop(); } } @Override public void onWidgetsListExpandButtonClick(View v) { super.onWidgetsListExpandButtonClick(v); // Refresh right pane with updated data for the selected header. if (mSelectedHeader != null && mSelectedHeader != mSuggestedWidgetsPackageUserKey) { getHeaderChangeListener().onHeaderChanged(mSelectedHeader); } } @Override public void onRecommendedWidgetsBound() { super.onRecommendedWidgetsBound(); if (mSuggestedWidgetsContainer == null && mRecommendedWidgetsCount > 0) { setupSuggestedWidgets(LayoutInflater.from(getContext())); mSuggestedWidgetsHeader.callOnClick(); } else if (mSelectedHeader != null && mSelectedHeader.equals(mSuggestedWidgetsPackageUserKey)) { // Reselect widget if we are reloading recommendations while it is currently showing. selectWidgetCell(mWidgetRecommendationsContainer, getLastSelectedWidgetItem()); } } 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 HighresPackageItemInfo( /* packageName= */ SUGGESTIONS_PACKAGE_NAME, Process.myUserHandle()); String suggestionsHeaderTitle = getContext().getString( R.string.suggested_widgets_header_title); String suggestionsRightPaneTitle = getContext().getString( R.string.widget_picker_right_pane_accessibility_title, suggestionsHeaderTitle); packageItemInfo.title = suggestionsHeaderTitle; // Suggestions may update at run time. The widgets count on suggestions doesn't add any // value, so, we don't show the count. WidgetsListHeaderEntry widgetsListHeaderEntry = WidgetsListHeaderEntry.create( packageItemInfo, /*titleSectionName=*/ suggestionsHeaderTitle, /*items=*/ List.of(), // not necessary /*visibleWidgetsCount=*/ 0) .withWidgetListShown(); mSuggestedWidgetsHeader.applyFromItemInfoWithIcon(widgetsListHeaderEntry); mSuggestedWidgetsHeader.setIcon( getContext().getDrawable(R.drawable.widget_suggestions_icon)); mSuggestedWidgetsHeader.setOnClickListener(view -> { mSuggestedWidgetsHeader.setExpanded(true); resetExpandedHeaders(); mRightPane.removeAllViews(); mRightPane.addView(mWidgetRecommendationsContainer); mRightPaneScrollView.setScrollY(0); mSuggestedWidgetsPackageUserKey = PackageUserKey.fromPackageItemInfo(packageItemInfo); final boolean isChangingHeaders = mSelectedHeader == null || !mSelectedHeader.equals(mSuggestedWidgetsPackageUserKey); if (isChangingHeaders) { // If the initial focus view is still focused or widget picker is still opening, it // is likely a programmatic header click. if (mSelectedHeader != null && !mOpenCloseAnimation.getAnimationPlayer().isRunning() && !getAccessibilityInitialFocusView().isAccessibilityFocused()) { mRightPaneScrollView.setAccessibilityPaneTitle(suggestionsRightPaneTitle); focusOnFirstWidgetCell(mWidgetRecommendationsView); } // If switching from another header, unselect any WidgetCells. This is necessary // because we do not clear/recycle the WidgetCells in the recommendations container // when the header is clicked, only when onRecommendationsBound is called. That // means a WidgetCell in the recommendations container may still be selected from // the last time the recommendations were shown. unselectWidgetCell(mWidgetRecommendationsContainer, getLastSelectedWidgetItem()); } mSelectedHeader = mSuggestedWidgetsPackageUserKey; }); mSuggestedWidgetsContainer.addView(mSuggestedWidgetsHeader); } @Override @Px 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 @Px protected int getAvailableWidthForSuggestions(int pickerAvailableWidth) { int rightPaneWidth = (int) Math.ceil(0.67 * pickerAvailableWidth); if (mDeviceProfile.isTwoPanels) { // 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); // If active page didn't change then we don't want to update the header. if (mActivePage == currentActivePage) { return; } mActivePage = currentActivePage; // When using talkback, swiping left while on right pane, should navigate to the widgets // list on left. mAdapters.get(mActivePage).mWidgetsRecyclerView.setAccessibilityTraversalBefore( mRightPaneScrollView.getId()); // On page change, select the first item in the list to show in the right pane. mAdapters.get(currentActivePage).mWidgetsListAdapter.selectFirstHeaderEntry(); mAdapters.get(currentActivePage).mWidgetsRecyclerView.scrollToTop(); } @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(); if (!isWidgetAvailable) { mRightPane.removeAllViews(); mRightPane.addView(mNoWidgetsView); // with no widgets message, no header is selected on left if (mSuggestedWidgetsPackageUserKey != null && mSuggestedWidgetsPackageUserKey.equals(mSelectedHeader) && mSuggestedWidgetsHeader != null) { mSuggestedWidgetsHeader.setExpanded(false); } mSelectedHeader = null; } super.updateRecyclerViewVisibility(adapterHolder); } @Override public void onSearchResults(List entries) { super.onSearchResults(entries); mAdapters.get(AdapterHolder.SEARCH).mWidgetsListAdapter.selectFirstHeaderEntry(); mAdapters.get(AdapterHolder.SEARCH).mWidgetsRecyclerView.scrollToTop(); } @Override protected boolean shouldScroll(MotionEvent ev) { return getPopupContainer().isEventOverView(mRightPaneScrollView, ev) ? mRightPaneScrollView.canScrollVertically(-1) : super.shouldScroll(ev); } @Override protected void setViewVisibilityBasedOnSearch(boolean isInSearchMode) { super.setViewVisibilityBasedOnSearch(isInSearchMode); if (mSuggestedWidgetsHeader != null && mSuggestedWidgetsContainer != null) { if (!isInSearchMode) { mSuggestedWidgetsContainer.setVisibility(VISIBLE); mSuggestedWidgetsHeader.callOnClick(); } else { mSuggestedWidgetsContainer.setVisibility(GONE); } } else if (!isInSearchMode) { mAdapters.get(mActivePage).mWidgetsListAdapter.selectFirstHeaderEntry(); } } private HeaderChangeListener getHeaderChangeListener() { return new HeaderChangeListener() { @Override public void onHeaderChanged(@NonNull PackageUserKey selectedHeader) { final boolean isSameHeader = mSelectedHeader != null && mSelectedHeader.equals(selectedHeader); // If the initial focus view is still focused or widget picker is still opening, it // is likely a programmatic header click. final boolean isUserClick = mSelectedHeader != null && !mOpenCloseAnimation.getAnimationPlayer().isRunning() && !getAccessibilityInitialFocusView().isAccessibilityFocused(); mSelectedHeader = selectedHeader; WidgetsListContentEntry contentEntry; if (enableTieredWidgetsByDefaultInPicker()) { contentEntry = mAdapters.get( getCurrentAdapterHolderType()).mWidgetsListAdapter.getContentEntry( selectedHeader); } else { // Can be deleted when inlining the "enableTieredWidgetsByDefaultInPicker" // flag final boolean showDefaultWidgets = mWidgetOptionsMenuState != null && !mWidgetOptionsMenuState.showAllWidgets; contentEntry = findContentEntryForPackageUser( mActivityContext.getWidgetPickerDataProvider().get(), selectedHeader, showDefaultWidgets); } if (contentEntry == null || mRightPane == null) { return; } if (mSuggestedWidgetsHeader != null) { mSuggestedWidgetsHeader.setExpanded(false); } WidgetsListContentEntry contentEntryToBind; // Setting max span size enables row to understand how to fit more than one item // in a row. contentEntryToBind = contentEntry.withMaxSpanSize(mMaxSpanPerRow); WidgetsRowViewHolder widgetsRowViewHolder = mWidgetsListTableViewHolderBinder.newViewHolder(mRightPane); mWidgetsListTableViewHolderBinder.bindViewHolder(widgetsRowViewHolder, contentEntryToBind, ViewHolderBinder.POSITION_FIRST | ViewHolderBinder.POSITION_LAST, Collections.EMPTY_LIST); if (isSameHeader) { // Reselect the last selected widget if we are reloading the same header. selectWidgetCell(widgetsRowViewHolder.tableContainer, getLastSelectedWidgetItem()); } widgetsRowViewHolder.mDataCallback = data -> { mWidgetsListTableViewHolderBinder.bindViewHolder(widgetsRowViewHolder, contentEntryToBind, ViewHolderBinder.POSITION_FIRST | ViewHolderBinder.POSITION_LAST, Collections.singletonList(data)); if (isSameHeader) { selectWidgetCell(widgetsRowViewHolder.tableContainer, getLastSelectedWidgetItem()); } }; mRightPane.removeAllViews(); mRightPane.addView(widgetsRowViewHolder.itemView); if (isUserClick) { mRightPaneScrollView.setAccessibilityPaneTitle(getContext().getString( R.string.widget_picker_right_pane_accessibility_title, contentEntry.mPkgItem.title)); postDelayed(() -> focusOnFirstWidgetCell(widgetsRowViewHolder.tableContainer), WIDGET_LIST_ITEM_APPEARANCE_DELAY); } mRightPaneScrollView.setScrollY(0); } }; } private static void selectWidgetCell(ViewGroup parent, WidgetItem item) { if (parent == null || item == null) return; WidgetCell cell = Utilities.findViewByPredicate(parent, v -> v instanceof WidgetCell wc && wc.matchesItem(item)); if (cell != null && !cell.isShowingAddButton()) { cell.callOnClick(); } } /** * Requests focus on the first widget cell in the given widget section. */ private static void focusOnFirstWidgetCell(ViewGroup parent) { if (parent == null) return; WidgetCell cell = Utilities.findViewByPredicate(parent, v -> v instanceof WidgetCell); if (cell != null) { cell.performAccessibilityAction( AccessibilityNodeInfo.ACTION_ACCESSIBILITY_FOCUS, null); } } private static void unselectWidgetCell(ViewGroup parent, WidgetItem item) { if (parent == null || item == null) return; WidgetCell cell = Utilities.findViewByPredicate(parent, v -> v instanceof WidgetCell wc && wc.matchesItem(item)); if (cell != null && cell.isShowingAddButton()) { cell.hideAddButton(/* animate= */ false); } } @Override public void setInsets(Rect insets) { super.setInsets(insets); FrameLayout rightPaneContainer = mContent.findViewById(R.id.right_pane_container); rightPaneContainer.setPadding( rightPaneContainer.getPaddingLeft(), rightPaneContainer.getPaddingTop(), rightPaneContainer.getPaddingRight(), mBottomPadding); requestLayout(); } @Override protected int getWidgetListHorizontalMargin() { return getResources().getDimensionPixelSize( R.dimen.widget_list_left_pane_horizontal_margin); } @Override protected boolean isTwoPane() { return true; } @Override protected int getHeaderTopClip(@NonNull WidgetCell cell) { return 0; } @Override protected void scrollCellContainerByY(WidgetCell wc, int scrollByY) { for (ViewParent parent = wc.getParent(); parent != null; parent = parent.getParent()) { if (parent instanceof ScrollView scrollView) { scrollView.smoothScrollBy(0, scrollByY); return; } else if (parent == this) { return; } } } /** * 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); } /** * Holds the selection state of the options menu (if presented to the user). */ protected static class WidgetOptionsMenuState { /** * UI state indicating whether to show default or all widgets. *

If true, shows all widgets; else shows the default widgets.

*/ public boolean showAllWidgets = false; } private static class HighresPackageItemInfo extends PackageItemInfo { HighresPackageItemInfo(String packageName, UserHandle user) { super(packageName, user); } @Override public CacheLookupFlag getMatchingLookupFlag() { return DEFAULT_LOOKUP_FLAG; } } }