• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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