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.LauncherSettings.Favorites.CONTAINER_WIDGETS_PREDICTION; 19 import static com.android.launcher3.widget.util.WidgetSizes.getWidgetSizePx; 20 import static com.android.launcher3.widget.util.WidgetsTableUtils.WIDGETS_TABLE_ROW_COUNT_COMPARATOR; 21 22 import static java.lang.Math.max; 23 24 import android.content.Context; 25 import android.util.AttributeSet; 26 import android.view.Gravity; 27 import android.view.LayoutInflater; 28 import android.view.View; 29 import android.widget.TableLayout; 30 31 import androidx.annotation.Nullable; 32 import androidx.annotation.Px; 33 34 import com.android.launcher3.DeviceProfile; 35 import com.android.launcher3.R; 36 import com.android.launcher3.model.WidgetItem; 37 import com.android.launcher3.widget.WidgetCell; 38 import com.android.launcher3.widget.WidgetTableRow; 39 import com.android.launcher3.widget.picker.util.WidgetPreviewContainerSize; 40 41 import java.util.ArrayList; 42 import java.util.List; 43 import java.util.stream.Collectors; 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 widgetCell.showDescription(false); 115 widgetCell.showDimensions(false); 116 } 117 addView(tableRow); 118 } 119 setVisibility(VISIBLE); 120 } 121 addItemCell(WidgetTableRow parent)122 private WidgetCell addItemCell(WidgetTableRow parent) { 123 WidgetCell widget = (WidgetCell) LayoutInflater.from( 124 getContext()).inflate(R.layout.widget_cell, parent, false); 125 widget.addPreviewReadyListener(parent); 126 widget.setOnClickListener(mWidgetCellOnClickListener); 127 128 View previewContainer = widget.findViewById(R.id.widget_preview_container); 129 previewContainer.setOnClickListener(mWidgetCellOnClickListener); 130 previewContainer.setOnLongClickListener(mWidgetCellOnLongClickListener); 131 widget.setAnimatePreview(false); 132 widget.setSourceContainer(CONTAINER_WIDGETS_PREDICTION); 133 134 parent.addView(widget); 135 return widget; 136 } 137 selectRowsThatFitInAvailableHeight( List<ArrayList<WidgetItem>> recommendedWidgets, @Px float recommendationTableMaxHeight, DeviceProfile deviceProfile)138 private List<ArrayList<WidgetItem>> selectRowsThatFitInAvailableHeight( 139 List<ArrayList<WidgetItem>> recommendedWidgets, @Px float recommendationTableMaxHeight, 140 DeviceProfile deviceProfile) { 141 List<ArrayList<WidgetItem>> filteredRows = new ArrayList<>(); 142 // A naive estimation of the widgets recommendation table height without inflation. 143 float totalHeight = mWidgetsRecommendationTableVerticalPadding; 144 145 for (int i = 0; i < recommendedWidgets.size(); i++) { 146 List<WidgetItem> widgetItems = recommendedWidgets.get(i); 147 float rowHeight = 0; 148 for (int j = 0; j < widgetItems.size(); j++) { 149 WidgetItem widgetItem = widgetItems.get(j); 150 WidgetPreviewContainerSize previewContainerSize = 151 WidgetPreviewContainerSize.Companion.forItem(widgetItem, deviceProfile); 152 float widgetItemHeight = getWidgetSizePx(deviceProfile, previewContainerSize.spanX, 153 previewContainerSize.spanY).getHeight(); 154 rowHeight = max(rowHeight, 155 widgetItemHeight + mWidgetCellTextViewsHeight + mWidgetCellVerticalPadding); 156 } 157 if (totalHeight + rowHeight <= recommendationTableMaxHeight) { 158 totalHeight += rowHeight; 159 filteredRows.add(new ArrayList<>(widgetItems)); 160 } 161 } 162 163 // Perform re-ordering once we have filtered out recommendations that fit. 164 return filteredRows.stream().sorted(WIDGETS_TABLE_ROW_COUNT_COMPARATOR) 165 .collect(Collectors.toList()); 166 } 167 } 168