Merge "Add subclass for two pane widget picker" into udc-dev

This commit is contained in:
Federico Baron
2023-03-24 21:43:24 +00:00
committed by Android (Google) Code Review
5 changed files with 389 additions and 261 deletions
@@ -12,8 +12,8 @@
See the License for the specific language governing permissions and
limitations under the License.
-->
<com.android.launcher3.widget.picker.WidgetsFullSheet xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
<com.android.launcher3.widget.picker.WidgetsTwoPaneSheet
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
@@ -125,4 +125,4 @@
</FrameLayout>
</LinearLayout>
</com.android.launcher3.views.SpringRelativeLayout>
</com.android.launcher3.widget.picker.WidgetsFullSheet>
</com.android.launcher3.widget.picker.WidgetsTwoPaneSheet>
@@ -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<Launcher>
* 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;
@@ -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<AdapterHolder> mAdapters = new SparseArray();
private final UserHandle mCurrentUser = Process.myUserHandle();
private final Predicate<WidgetsListBaseEntry> mPrimaryWidgetsFilter =
entry -> mCurrentUser.equals(entry.mPkgItem.user);
private final Predicate<WidgetsListBaseEntry> mWorkWidgetsFilter =
entry -> !mCurrentUser.equals(entry.mPkgItem.user)
&& !mUserManagerState.isUserQuiet(entry.mPkgItem.user);
protected final boolean mHasWorkProfile;
protected final SparseArray<AdapterHolder> 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<WidgetsListBaseEntry> 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<ArrayList<WidgetItem>> 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);
}
}
@@ -89,7 +89,7 @@ public class WidgetsListAdapter extends Adapter<ViewHolder> implements OnHeaderC
private final SparseArray<ViewHolderBinder> mViewHolderBinders = new SparseArray<>();
private final WidgetListBaseRowEntryComparator mRowComparator =
new WidgetListBaseRowEntryComparator();
@Nullable private final WidgetsFullSheet.HeaderChangeListener mHeaderChangeListener;
@Nullable private WidgetsTwoPaneSheet.HeaderChangeListener mHeaderChangeListener;
private final List<WidgetsListBaseEntry> mAllEntries = new ArrayList<>();
private ArrayList<WidgetsListBaseEntry> mVisibleEntries = new ArrayList<>();
@@ -107,8 +107,7 @@ public class WidgetsListAdapter extends Adapter<ViewHolder> 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<ViewHolder> 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;
@@ -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<WidgetsListBaseEntry> 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);
}
}