• 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 
20 import android.content.Context;
21 import android.util.AttributeSet;
22 import android.util.Log;
23 import android.util.Size;
24 import android.view.Gravity;
25 import android.view.LayoutInflater;
26 import android.view.View;
27 import android.view.ViewGroup;
28 import android.widget.TableLayout;
29 import android.widget.TableRow;
30 
31 import androidx.annotation.Nullable;
32 
33 import com.android.launcher3.DeviceProfile;
34 import com.android.launcher3.Launcher;
35 import com.android.launcher3.LauncherAppState;
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.util.WidgetSizes;
40 
41 import java.util.ArrayList;
42 import java.util.List;
43 
44 /** A {@link TableLayout} for showing recommended widgets. */
45 public final class WidgetsRecommendationTableLayout extends TableLayout {
46     private static final String TAG = "WidgetsRecommendationTableLayout";
47     private static final float DOWN_SCALE_RATIO = 0.9f;
48     private static final float MAX_DOWN_SCALE_RATIO = 0.5f;
49     private final float mWidgetsRecommendationTableVerticalPadding;
50     private final float mWidgetCellVerticalPadding;
51     private final float mWidgetCellTextViewsHeight;
52 
53     private float mRecommendationTableMaxHeight = Float.MAX_VALUE;
54     @Nullable private OnLongClickListener mWidgetCellOnLongClickListener;
55     @Nullable private OnClickListener mWidgetCellOnClickListener;
56     @Nullable private OnTouchListener mWidgetCellOnTouchListener;
57 
WidgetsRecommendationTableLayout(Context context)58     public WidgetsRecommendationTableLayout(Context context) {
59         this(context, /* attrs= */ null);
60     }
61 
WidgetsRecommendationTableLayout(Context context, AttributeSet attrs)62     public WidgetsRecommendationTableLayout(Context context, AttributeSet attrs) {
63         super(context, attrs);
64         // There are 1 row for title, 1 row for dimension and 2 rows for description.
65         mWidgetsRecommendationTableVerticalPadding = 2 * getResources()
66                 .getDimensionPixelSize(R.dimen.recommended_widgets_table_vertical_padding);
67         mWidgetCellVerticalPadding = 2 * getResources()
68                 .getDimensionPixelSize(R.dimen.widget_cell_vertical_padding);
69         mWidgetCellTextViewsHeight = 4 * getResources().getDimension(R.dimen.widget_cell_font_size);
70     }
71 
72     /** Sets a {@link android.view.View.OnLongClickListener} for all widget cells in this table. */
setWidgetCellLongClickListener(OnLongClickListener onLongClickListener)73     public void setWidgetCellLongClickListener(OnLongClickListener onLongClickListener) {
74         mWidgetCellOnLongClickListener = onLongClickListener;
75     }
76 
77     /** Sets a {@link android.view.View.OnClickListener} for all widget cells in this table. */
setWidgetCellOnClickListener(OnClickListener widgetCellOnClickListener)78     public void setWidgetCellOnClickListener(OnClickListener widgetCellOnClickListener) {
79         mWidgetCellOnClickListener = widgetCellOnClickListener;
80     }
81 
82     /** Sets a {@link android.view.View.OnTouchListener} for all widget cells in this table. */
setWidgetCellOnTouchListener(OnTouchListener widgetCellOnTouchListener)83     public void setWidgetCellOnTouchListener(OnTouchListener widgetCellOnTouchListener) {
84         mWidgetCellOnTouchListener = widgetCellOnTouchListener;
85     }
86 
87     /**
88      * Sets a list of recommended widgets that would like to be displayed in this table within the
89      * desired {@code recommendationTableMaxHeight}.
90      *
91      * <p>If the content can't fit {@code recommendationTableMaxHeight}, this view will remove a
92      * last row from the {@code recommendedWidgets} until it fits or only one row left. If the only
93      * row still doesn't fit, we scale down the preview image.
94      */
setRecommendedWidgets(List<ArrayList<WidgetItem>> recommendedWidgets, float recommendationTableMaxHeight)95     public void setRecommendedWidgets(List<ArrayList<WidgetItem>> recommendedWidgets,
96             float recommendationTableMaxHeight) {
97         mRecommendationTableMaxHeight = recommendationTableMaxHeight;
98         RecommendationTableData data = fitRecommendedWidgetsToTableSpace(/* previewScale= */ 1f,
99                 recommendedWidgets);
100         bindData(data);
101     }
102 
bindData(RecommendationTableData data)103     private void bindData(RecommendationTableData data) {
104         if (data.mRecommendationTable.size() == 0) {
105             setVisibility(GONE);
106             return;
107         }
108 
109         removeAllViews();
110 
111         for (int i = 0; i < data.mRecommendationTable.size(); i++) {
112             List<WidgetItem> widgetItems = data.mRecommendationTable.get(i);
113             TableRow tableRow = new TableRow(getContext());
114             tableRow.setGravity(Gravity.TOP);
115 
116             for (WidgetItem widgetItem : widgetItems) {
117                 WidgetCell widgetCell = addItemCell(tableRow);
118                 widgetCell.setPreviewSize(widgetItem, data.mPreviewScale);
119                 widgetCell.applyFromCellItem(widgetItem,
120                         LauncherAppState.getInstance(getContext()).getWidgetCache());
121                 widgetCell.ensurePreview();
122             }
123             addView(tableRow);
124         }
125         setVisibility(VISIBLE);
126     }
127 
addItemCell(ViewGroup parent)128     private WidgetCell addItemCell(ViewGroup parent) {
129         WidgetCell widget = (WidgetCell) LayoutInflater.from(
130                 getContext()).inflate(R.layout.widget_cell, parent, false);
131 
132         widget.setOnTouchListener(mWidgetCellOnTouchListener);
133         View previewContainer = widget.findViewById(R.id.widget_preview_container);
134         previewContainer.setOnClickListener(mWidgetCellOnClickListener);
135         previewContainer.setOnLongClickListener(mWidgetCellOnLongClickListener);
136         widget.setAnimatePreview(false);
137         widget.setSourceContainer(CONTAINER_WIDGETS_PREDICTION);
138 
139         parent.addView(widget);
140         return widget;
141     }
142 
fitRecommendedWidgetsToTableSpace( float previewScale, List<ArrayList<WidgetItem>> recommendedWidgetsInTable)143     private RecommendationTableData fitRecommendedWidgetsToTableSpace(
144             float previewScale,
145             List<ArrayList<WidgetItem>> recommendedWidgetsInTable) {
146         if (previewScale < MAX_DOWN_SCALE_RATIO) {
147             Log.w(TAG, "Hide recommended widgets. Can't down scale previews to " + previewScale);
148             return new RecommendationTableData(List.of(), previewScale);
149         }
150         // A naive estimation of the widgets recommendation table height without inflation.
151         float totalHeight = mWidgetsRecommendationTableVerticalPadding;
152         DeviceProfile deviceProfile = Launcher.getLauncher(getContext()).getDeviceProfile();
153         for (int i = 0; i < recommendedWidgetsInTable.size(); i++) {
154             List<WidgetItem> widgetItems = recommendedWidgetsInTable.get(i);
155             float rowHeight = 0;
156             for (int j = 0; j < widgetItems.size(); j++) {
157                 WidgetItem widgetItem = widgetItems.get(j);
158                 Size widgetSize = WidgetSizes.getWidgetItemSizePx(getContext(), deviceProfile,
159                         widgetItem);
160                 float previewHeight = widgetSize.getHeight() * previewScale;
161                 rowHeight = Math.max(rowHeight,
162                         previewHeight + mWidgetCellTextViewsHeight + mWidgetCellVerticalPadding);
163             }
164             totalHeight += rowHeight;
165         }
166 
167         if (totalHeight < mRecommendationTableMaxHeight) {
168             return new RecommendationTableData(recommendedWidgetsInTable, previewScale);
169         }
170 
171         if (recommendedWidgetsInTable.size() > 1) {
172             // We don't want to scale down widgets preview unless we really need to. Reduce the
173             // num of row by 1 to see if it fits.
174             return fitRecommendedWidgetsToTableSpace(
175                     previewScale,
176                     recommendedWidgetsInTable.subList(/* fromIndex= */0,
177                             /* toIndex= */recommendedWidgetsInTable.size() - 1));
178         }
179 
180         float nextPreviewScale = previewScale * DOWN_SCALE_RATIO;
181         return fitRecommendedWidgetsToTableSpace(nextPreviewScale, recommendedWidgetsInTable);
182     }
183 
184     /** Data class for the widgets recommendation table and widgets preview scaling. */
185     private class RecommendationTableData {
186         private final List<ArrayList<WidgetItem>> mRecommendationTable;
187         private final float mPreviewScale;
188 
RecommendationTableData(List<ArrayList<WidgetItem>> recommendationTable, float previewScale)189         RecommendationTableData(List<ArrayList<WidgetItem>> recommendationTable,
190                 float previewScale) {
191             mRecommendationTable = recommendationTable;
192             mPreviewScale = previewScale;
193         }
194     }
195 }
196