b3153f0bee
Bug: 358041878 Flag: EXEMPT bugfix Test: on fold device with test dpc Change-Id: I4b6d758cd4d98af941e2222db21a0307e1088867
668 lines
29 KiB
Java
668 lines
29 KiB
Java
/*
|
|
* 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.enableCategorizedWidgetSuggestions;
|
|
import static com.android.launcher3.Flags.enableUnfoldedTwoPanePicker;
|
|
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.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.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.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);
|
|
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) {
|
|
if (enableCategorizedWidgetSuggestions()) {
|
|
// two pane picker is full width for fold as well as tablet.
|
|
return getResources().getDimensionPixelSize(
|
|
R.dimen.widget_picker_two_panels_left_right_margin);
|
|
}
|
|
if (deviceProfile.isTwoPanels && enableUnfoldedTwoPanePicker()) {
|
|
// enableUnfoldedTwoPanePicker made two pane picker full-width for fold only.
|
|
return getResources().getDimensionPixelSize(
|
|
R.dimen.widget_picker_two_panels_left_right_margin);
|
|
}
|
|
if (deviceProfile.isLandscape && !deviceProfile.isTwoPanels) {
|
|
// non-fold tablet landscape margins (ag/22163531)
|
|
return getResources().getDimensionPixelSize(
|
|
R.dimen.widget_picker_landscape_tablet_left_right_margin);
|
|
}
|
|
return deviceProfile.allAppsLeftRightMargin;
|
|
}
|
|
|
|
@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 && enableUnfoldedTwoPanePicker()) {
|
|
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);
|
|
}
|
|
}
|
|
});
|
|
}
|
|
}
|
|
|
|
@Override
|
|
protected List<WidgetsListBaseEntry> getWidgetsToDisplay() {
|
|
List<WidgetsListBaseEntry> allWidgets =
|
|
mActivityContext.getWidgetPickerDataProvider().get().getAllWidgets();
|
|
List<WidgetsListBaseEntry> 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 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 PackageItemInfo(
|
|
/* packageName= */ SUGGESTIONS_PACKAGE_NAME,
|
|
Process.myUserHandle()) {
|
|
@Override
|
|
public boolean usingLowResIcon() {
|
|
return false;
|
|
}
|
|
};
|
|
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 && 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);
|
|
|
|
// 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<WidgetsListBaseEntry> 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;
|
|
final boolean showDefaultWidgets = mWidgetOptionsMenuState != null
|
|
&& !mWidgetOptionsMenuState.showAllWidgets;
|
|
WidgetsListContentEntry contentEntry = findContentEntryForPackageUser(
|
|
mActivityContext.getWidgetPickerDataProvider().get(),
|
|
selectedHeader, showDefaultWidgets);
|
|
|
|
if (contentEntry == null || mRightPane == null) {
|
|
return;
|
|
}
|
|
|
|
if (mSuggestedWidgetsHeader != null) {
|
|
mSuggestedWidgetsHeader.setExpanded(false);
|
|
}
|
|
|
|
WidgetsListContentEntry contentEntryToBind;
|
|
if (enableCategorizedWidgetSuggestions()) {
|
|
// Setting max span size enables row to understand how to fit more than one item
|
|
// in a row.
|
|
contentEntryToBind = contentEntry.withMaxSpanSize(mMaxSpanPerRow);
|
|
} else {
|
|
contentEntryToBind = contentEntry;
|
|
}
|
|
|
|
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.
|
|
* <p>If true, shows all widgets; else shows the default widgets.</p>
|
|
*/
|
|
public boolean showAllWidgets = false;
|
|
}
|
|
}
|