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