1 /* 2 * Copyright (C) 2021 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 static com.android.launcher3.Flags.enableCategorizedWidgetSuggestions; 19 import static com.android.launcher3.LauncherSettings.Favorites.CONTAINER_WIDGETS_PREDICTION; 20 import static com.android.launcher3.widget.util.WidgetSizes.getWidgetSizePx; 21 import static com.android.launcher3.widget.util.WidgetsTableUtils.WIDGETS_TABLE_ROW_SIZE_COMPARATOR; 22 23 import static java.lang.Math.max; 24 25 import android.content.Context; 26 import android.util.AttributeSet; 27 import android.view.Gravity; 28 import android.view.LayoutInflater; 29 import android.view.View; 30 import android.widget.TableLayout; 31 32 import androidx.annotation.Nullable; 33 import androidx.annotation.Px; 34 35 import com.android.launcher3.DeviceProfile; 36 import com.android.launcher3.R; 37 import com.android.launcher3.model.WidgetItem; 38 import com.android.launcher3.widget.WidgetCell; 39 import com.android.launcher3.widget.WidgetTableRow; 40 import com.android.launcher3.widget.picker.util.WidgetPreviewContainerSize; 41 42 import java.util.ArrayList; 43 import java.util.List; 44 45 /** A {@link TableLayout} for showing recommended widgets. */ 46 public final class WidgetsRecommendationTableLayout extends TableLayout { 47 private final float mWidgetsRecommendationTableVerticalPadding; 48 private final float mWidgetCellVerticalPadding; 49 private final float mWidgetCellTextViewsHeight; 50 51 @Nullable private OnLongClickListener mWidgetCellOnLongClickListener; 52 @Nullable private OnClickListener mWidgetCellOnClickListener; 53 WidgetsRecommendationTableLayout(Context context)54 public WidgetsRecommendationTableLayout(Context context) { 55 this(context, /* attrs= */ null); 56 } 57 WidgetsRecommendationTableLayout(Context context, AttributeSet attrs)58 public WidgetsRecommendationTableLayout(Context context, AttributeSet attrs) { 59 super(context, attrs); 60 // There are 1 row for title, 1 row for dimension and max 3 rows for description. 61 mWidgetsRecommendationTableVerticalPadding = 2 * getResources() 62 .getDimensionPixelSize(R.dimen.widget_recommendations_table_vertical_padding); 63 mWidgetCellVerticalPadding = 2 * getResources() 64 .getDimensionPixelSize(R.dimen.widget_cell_vertical_padding); 65 mWidgetCellTextViewsHeight = 66 getResources().getDimension(R.dimen.widget_cell_title_line_height); 67 } 68 69 /** Sets a {@link android.view.View.OnLongClickListener} for all widget cells in this table. */ setWidgetCellLongClickListener(OnLongClickListener onLongClickListener)70 public void setWidgetCellLongClickListener(OnLongClickListener onLongClickListener) { 71 mWidgetCellOnLongClickListener = onLongClickListener; 72 } 73 74 /** Sets a {@link android.view.View.OnClickListener} for all widget cells in this table. */ setWidgetCellOnClickListener(OnClickListener widgetCellOnClickListener)75 public void setWidgetCellOnClickListener(OnClickListener widgetCellOnClickListener) { 76 mWidgetCellOnClickListener = widgetCellOnClickListener; 77 } 78 79 /** 80 * Sets a list of recommended widgets that would like to be displayed in this table within the 81 * desired {@code recommendationTableMaxHeight}. 82 * 83 * <p>If the content can't fit {@code recommendationTableMaxHeight}, this view will remove a 84 * last row from the {@code recommendedWidgets} until it fits or only one row left. 85 * 86 * <p>Returns the list of widgets that could fit</p> 87 */ setRecommendedWidgets( List<ArrayList<WidgetItem>> recommendedWidgets, DeviceProfile deviceProfile, float recommendationTableMaxHeight)88 public List<ArrayList<WidgetItem>> setRecommendedWidgets( 89 List<ArrayList<WidgetItem>> recommendedWidgets, 90 DeviceProfile deviceProfile, float recommendationTableMaxHeight) { 91 List<ArrayList<WidgetItem>> rows = selectRowsThatFitInAvailableHeight(recommendedWidgets, 92 recommendationTableMaxHeight, deviceProfile); 93 bindData(rows); 94 return rows; 95 } 96 bindData(List<ArrayList<WidgetItem>> recommendationTable)97 private void bindData(List<ArrayList<WidgetItem>> recommendationTable) { 98 if (recommendationTable.isEmpty()) { 99 setVisibility(GONE); 100 return; 101 } 102 103 removeAllViews(); 104 105 for (int i = 0; i < recommendationTable.size(); i++) { 106 List<WidgetItem> widgetItems = recommendationTable.get(i); 107 WidgetTableRow tableRow = new WidgetTableRow(getContext()); 108 tableRow.setupRow(widgetItems.size(), /*resizeDelayMs=*/ 0); 109 tableRow.setGravity(Gravity.TOP); 110 for (WidgetItem widgetItem : widgetItems) { 111 WidgetCell widgetCell = addItemCell(tableRow); 112 widgetCell.applyFromCellItem(widgetItem); 113 widgetCell.showAppIconInWidgetTitle(true); 114 if (enableCategorizedWidgetSuggestions()) { 115 widgetCell.showDescription(false); 116 widgetCell.showDimensions(false); 117 } 118 } 119 addView(tableRow); 120 } 121 setVisibility(VISIBLE); 122 } 123 addItemCell(WidgetTableRow parent)124 private WidgetCell addItemCell(WidgetTableRow parent) { 125 WidgetCell widget = (WidgetCell) LayoutInflater.from( 126 getContext()).inflate(R.layout.widget_cell, parent, false); 127 widget.addPreviewReadyListener(parent); 128 widget.setOnClickListener(mWidgetCellOnClickListener); 129 130 View previewContainer = widget.findViewById(R.id.widget_preview_container); 131 previewContainer.setOnClickListener(mWidgetCellOnClickListener); 132 previewContainer.setOnLongClickListener(mWidgetCellOnLongClickListener); 133 widget.setAnimatePreview(false); 134 widget.setSourceContainer(CONTAINER_WIDGETS_PREDICTION); 135 136 parent.addView(widget); 137 return widget; 138 } 139 selectRowsThatFitInAvailableHeight( List<ArrayList<WidgetItem>> recommendedWidgets, @Px float recommendationTableMaxHeight, DeviceProfile deviceProfile)140 private List<ArrayList<WidgetItem>> selectRowsThatFitInAvailableHeight( 141 List<ArrayList<WidgetItem>> recommendedWidgets, @Px float recommendationTableMaxHeight, 142 DeviceProfile deviceProfile) { 143 List<ArrayList<WidgetItem>> filteredRows = new ArrayList<>(); 144 // A naive estimation of the widgets recommendation table height without inflation. 145 float totalHeight = mWidgetsRecommendationTableVerticalPadding; 146 147 for (int i = 0; i < recommendedWidgets.size(); i++) { 148 List<WidgetItem> widgetItems = recommendedWidgets.get(i); 149 float rowHeight = 0; 150 for (int j = 0; j < widgetItems.size(); j++) { 151 WidgetItem widgetItem = widgetItems.get(j); 152 WidgetPreviewContainerSize previewContainerSize = 153 WidgetPreviewContainerSize.Companion.forItem(widgetItem, deviceProfile); 154 float widgetItemHeight = getWidgetSizePx(deviceProfile, previewContainerSize.spanX, 155 previewContainerSize.spanY).getHeight(); 156 rowHeight = max(rowHeight, 157 widgetItemHeight + mWidgetCellTextViewsHeight + mWidgetCellVerticalPadding); 158 } 159 if (totalHeight + rowHeight <= recommendationTableMaxHeight) { 160 totalHeight += rowHeight; 161 filteredRows.add(new ArrayList<>(widgetItems)); 162 } 163 } 164 165 // Perform re-ordering once we have filtered out recommendations that fit. 166 return filteredRows.stream().sorted(WIDGETS_TABLE_ROW_SIZE_COMPARATOR).toList(); 167 } 168 } 169