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