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