• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2023 The Android Open Source Project
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *      http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 package com.android.launcher3.widget.picker;
17 
18 import android.content.Context;
19 import android.graphics.Outline;
20 import android.os.Process;
21 import android.util.AttributeSet;
22 import android.view.LayoutInflater;
23 import android.view.MotionEvent;
24 import android.view.View;
25 import android.view.ViewOutlineProvider;
26 import android.widget.LinearLayout;
27 import android.widget.ScrollView;
28 
29 import androidx.annotation.NonNull;
30 
31 import com.android.launcher3.R;
32 import com.android.launcher3.model.data.PackageItemInfo;
33 import com.android.launcher3.recyclerview.ViewHolderBinder;
34 import com.android.launcher3.util.PackageUserKey;
35 import com.android.launcher3.widget.model.WidgetsListBaseEntry;
36 import com.android.launcher3.widget.model.WidgetsListContentEntry;
37 import com.android.launcher3.widget.model.WidgetsListHeaderEntry;
38 
39 import java.util.Collections;
40 import java.util.List;
41 
42 /**
43  * Popup for showing the full list of available widgets with a two-pane layout.
44  */
45 public class WidgetsTwoPaneSheet extends WidgetsFullSheet {
46 
47     private static final int PERSONAL_TAB = 0;
48     private static final int WORK_TAB = 1;
49     private static final String SUGGESTIONS_PACKAGE_NAME = "widgets_list_suggestions_entry";
50 
51     private LinearLayout mSuggestedWidgetsContainer;
52     private WidgetsListHeader mSuggestedWidgetsHeader;
53     private LinearLayout mRightPane;
54 
55     private ScrollView mRightPaneScrollView;
56     private WidgetsListTableViewHolderBinder mWidgetsListTableViewHolderBinder;
57     private int mActivePage = -1;
58 
59     private final ViewOutlineProvider mViewOutlineProviderRightPane = new ViewOutlineProvider() {
60         @Override
61         public void getOutline(View view, Outline outline) {
62             outline.setRoundRect(
63                     0,
64                     0,
65                     view.getMeasuredWidth(),
66                     view.getMeasuredHeight() - getResources().getDimensionPixelSize(
67                             R.dimen.widget_list_horizontal_margin_two_pane),
68                     view.getResources().getDimensionPixelSize(
69                             R.dimen.widget_list_top_bottom_corner_radius)
70             );
71         }
72     };
73 
WidgetsTwoPaneSheet(Context context, AttributeSet attrs, int defStyleAttr)74     public WidgetsTwoPaneSheet(Context context, AttributeSet attrs, int defStyleAttr) {
75         super(context, attrs, defStyleAttr);
76     }
77 
WidgetsTwoPaneSheet(Context context, AttributeSet attrs)78     public WidgetsTwoPaneSheet(Context context, AttributeSet attrs) {
79         super(context, attrs);
80     }
81 
82     @Override
setupSheet()83     protected void setupSheet() {
84         // Set the header change listener in the adapter
85         mAdapters.get(AdapterHolder.PRIMARY)
86                 .mWidgetsListAdapter.setHeaderChangeListener(getHeaderChangeListener());
87         mAdapters.get(AdapterHolder.WORK)
88                 .mWidgetsListAdapter.setHeaderChangeListener(getHeaderChangeListener());
89         mAdapters.get(AdapterHolder.SEARCH)
90                 .mWidgetsListAdapter.setHeaderChangeListener(getHeaderChangeListener());
91 
92         LayoutInflater layoutInflater = LayoutInflater.from(getContext());
93 
94         int contentLayoutRes = mHasWorkProfile ? R.layout.widgets_two_pane_sheet_paged_view
95                 : R.layout.widgets_two_pane_sheet_recyclerview;
96         layoutInflater.inflate(contentLayoutRes, findViewById(R.id.recycler_view_container), true);
97 
98         setupViews();
99 
100         mWidgetsListTableViewHolderBinder =
101                 new WidgetsListTableViewHolderBinder(mActivityContext, layoutInflater, this, this);
102         mRecommendedWidgetsTable = mContent.findViewById(R.id.recommended_widget_table);
103         mRecommendedWidgetsTable.setWidgetCellLongClickListener(this);
104         mRecommendedWidgetsTable.setWidgetCellOnClickListener(this);
105         mHeaderTitle = mContent.findViewById(R.id.title);
106         mRightPane = mContent.findViewById(R.id.right_pane);
107         mRightPane.setOutlineProvider(mViewOutlineProviderRightPane);
108         mRightPaneScrollView = mContent.findViewById(R.id.right_pane_scroll_view);
109         mRightPaneScrollView.setOverScrollMode(View.OVER_SCROLL_NEVER);
110 
111         onRecommendedWidgetsBound();
112         onWidgetsBound();
113         setUpEducationViewsIfNeeded();
114 
115         // Set the fast scroller as not visible for two pane layout.
116         mFastScroller.setVisibility(GONE);
117     }
118 
119     @Override
onRecommendedWidgetsBound()120     public void onRecommendedWidgetsBound() {
121         super.onRecommendedWidgetsBound();
122 
123         if (mSuggestedWidgetsContainer == null && mHasRecommendedWidgets) {
124             setupSuggestedWidgets(LayoutInflater.from(getContext()));
125             mSuggestedWidgetsHeader.callOnClick();
126         }
127     }
128 
setupSuggestedWidgets(LayoutInflater layoutInflater)129     private void setupSuggestedWidgets(LayoutInflater layoutInflater) {
130         // Add suggested widgets.
131         mSuggestedWidgetsContainer = mSearchScrollView.findViewById(R.id.suggestions_header);
132 
133         // Inflate the suggestions header.
134         mSuggestedWidgetsHeader = (WidgetsListHeader) layoutInflater.inflate(
135                 R.layout.widgets_list_row_header_two_pane,
136                 mSuggestedWidgetsContainer,
137                 false);
138         mSuggestedWidgetsHeader.setExpanded(true);
139 
140         PackageItemInfo packageItemInfo = new PackageItemInfo(
141                 /* packageName= */ SUGGESTIONS_PACKAGE_NAME,
142                 Process.myUserHandle()) {
143             @Override
144             public boolean usingLowResIcon() {
145                 return false;
146             }
147         };
148         packageItemInfo.title = getContext().getString(R.string.suggested_widgets_header_title);
149         WidgetsListHeaderEntry widgetsListHeaderEntry = WidgetsListHeaderEntry.create(
150                         packageItemInfo,
151                         getContext().getString(R.string.suggested_widgets_header_title),
152                         mActivityContext.getPopupDataProvider().getRecommendedWidgets())
153                 .withWidgetListShown();
154 
155         mSuggestedWidgetsHeader.applyFromItemInfoWithIcon(widgetsListHeaderEntry);
156         mSuggestedWidgetsHeader.setIcon(
157                 getContext().getDrawable(R.drawable.widget_suggestions_icon));
158         mSuggestedWidgetsHeader.setOnClickListener(view -> {
159             mSuggestedWidgetsHeader.setExpanded(true);
160             resetExpandedHeaders();
161             mRightPane.removeAllViews();
162             mRightPane.addView(mRecommendedWidgetsTable);
163             mRightPaneScrollView.setScrollY(0);
164         });
165         mSuggestedWidgetsContainer.addView(mSuggestedWidgetsHeader);
166     }
167 
168     @Override
getMaxTableHeight(float noWidgetsViewHeight)169     protected float getMaxTableHeight(float noWidgetsViewHeight) {
170         return Float.MAX_VALUE;
171     }
172 
173     @Override
onActivePageChanged(int currentActivePage)174     public void onActivePageChanged(int currentActivePage) {
175         super.onActivePageChanged(currentActivePage);
176 
177         // If active page didn't change then we don't want to update the header.
178         if (mActivePage == currentActivePage) {
179             return;
180         }
181 
182         mActivePage = currentActivePage;
183 
184         if (mSuggestedWidgetsHeader == null) {
185             mAdapters.get(currentActivePage).mWidgetsListAdapter.selectFirstHeaderEntry();
186             mAdapters.get(currentActivePage).mWidgetsRecyclerView.scrollToTop();
187         } else if (currentActivePage == PERSONAL_TAB || currentActivePage == WORK_TAB) {
188             mSuggestedWidgetsHeader.callOnClick();
189         }
190     }
191 
192     @Override
updateRecyclerViewVisibility(AdapterHolder adapterHolder)193     protected void updateRecyclerViewVisibility(AdapterHolder adapterHolder) {
194         // The first item is always an empty space entry. Look for any more items.
195         boolean isWidgetAvailable = adapterHolder.mWidgetsListAdapter.hasVisibleEntries();
196 
197         mRightPane.setVisibility(isWidgetAvailable ? VISIBLE : GONE);
198 
199         super.updateRecyclerViewVisibility(adapterHolder);
200     }
201 
202     @Override
onSearchResults(List<WidgetsListBaseEntry> entries)203     public void onSearchResults(List<WidgetsListBaseEntry> entries) {
204         super.onSearchResults(entries);
205         mAdapters.get(AdapterHolder.SEARCH).mWidgetsListAdapter.selectFirstHeaderEntry();
206         mAdapters.get(AdapterHolder.SEARCH).mWidgetsRecyclerView.scrollToTop();
207     }
208 
209     @Override
shouldScroll(MotionEvent ev)210     protected boolean shouldScroll(MotionEvent ev) {
211         return getPopupContainer().isEventOverView(mRightPaneScrollView, ev)
212                 ? mRightPaneScrollView.canScrollVertically(-1)
213                 : super.shouldScroll(ev);
214     }
215 
216     @Override
setViewVisibilityBasedOnSearch(boolean isInSearchMode)217     protected void setViewVisibilityBasedOnSearch(boolean isInSearchMode) {
218         super.setViewVisibilityBasedOnSearch(isInSearchMode);
219 
220         if (mSuggestedWidgetsHeader != null && mSuggestedWidgetsContainer != null) {
221             if (!isInSearchMode) {
222                 mSuggestedWidgetsContainer.setVisibility(VISIBLE);
223                 mSuggestedWidgetsHeader.callOnClick();
224             } else {
225                 mSuggestedWidgetsContainer.setVisibility(GONE);
226             }
227         } else if (!isInSearchMode) {
228             mAdapters.get(mActivePage).mWidgetsListAdapter.selectFirstHeaderEntry();
229         }
230 
231     }
232 
233     @Override
getContentView()234     protected View getContentView() {
235         return mRightPane;
236     }
237 
getHeaderChangeListener()238     private HeaderChangeListener getHeaderChangeListener() {
239         return new HeaderChangeListener() {
240             @Override
241             public void onHeaderChanged(@NonNull PackageUserKey selectedHeader) {
242                 WidgetsListContentEntry contentEntry = mActivityContext.getPopupDataProvider()
243                         .getSelectedAppWidgets(selectedHeader);
244 
245                 if (contentEntry == null || mRightPane == null) {
246                     return;
247                 }
248 
249                 if (mSuggestedWidgetsHeader != null) {
250                     mSuggestedWidgetsHeader.setExpanded(false);
251                 }
252                 WidgetsRowViewHolder widgetsRowViewHolder =
253                         mWidgetsListTableViewHolderBinder.newViewHolder(mRightPane);
254                 mWidgetsListTableViewHolderBinder.bindViewHolder(widgetsRowViewHolder,
255                         contentEntry,
256                         ViewHolderBinder.POSITION_FIRST | ViewHolderBinder.POSITION_LAST,
257                         Collections.EMPTY_LIST);
258                 widgetsRowViewHolder.mDataCallback = data -> {
259                     mWidgetsListTableViewHolderBinder.bindViewHolder(widgetsRowViewHolder,
260                             contentEntry,
261                             ViewHolderBinder.POSITION_FIRST | ViewHolderBinder.POSITION_LAST,
262                             Collections.singletonList(data));
263                 };
264                 mRightPane.removeAllViews();
265                 mRightPane.addView(widgetsRowViewHolder.itemView);
266                 mRightPaneScrollView.setScrollY(0);
267             }
268         };
269     }
270 
271     @Override
272     protected boolean isTwoPane() {
273         return true;
274     }
275 
276     /**
277      * This is a listener for when the selected header gets changed in the left pane.
278      */
279     public interface HeaderChangeListener {
280         /**
281          * Sets the right pane to have the widgets for the currently selected header from
282          * the left pane.
283          */
284         void onHeaderChanged(@NonNull PackageUserKey selectedHeader);
285     }
286 }
287