blob: fea0f0578463c02bfaef0de510086199d7568091 [file] [log] [blame]
Adam Cohen2e6da152015-05-06 11:42:25 -07001/*
2 * Copyright (C) 2015 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
17package com.android.launcher3;
18
Sunny Goyal19ff7282021-04-22 10:12:54 -070019import static com.android.launcher3.Utilities.dpiFromPx;
Andras Kloczl8e57cce2021-02-11 23:51:19 +010020import static com.android.launcher3.config.FeatureFlags.ENABLE_TWO_PANEL_HOME;
Sunny Goyal35c7b192021-04-20 16:51:10 -070021import static com.android.launcher3.util.DisplayController.CHANGE_DENSITY;
Sunny Goyal19ff7282021-04-22 10:12:54 -070022import static com.android.launcher3.util.DisplayController.CHANGE_SUPPORTED_BOUNDS;
Sunny Goyal9c2b9602020-01-07 13:07:55 -080023import static com.android.launcher3.util.Executors.MAIN_EXECUTOR;
Sunny Goyal87dc48b2018-10-12 11:42:33 -070024
Sunny Goyalc6205602015-05-21 20:46:33 -070025import android.annotation.TargetApi;
Sunny Goyal58fa4b62019-03-22 16:23:25 -070026import android.appwidget.AppWidgetHostView;
Sunny Goyal58fa4b62019-03-22 16:23:25 -070027import android.content.ComponentName;
Adam Cohen2e6da152015-05-06 11:42:25 -070028import android.content.Context;
Sunny Goyal27835952017-01-13 12:15:53 -080029import android.content.res.Configuration;
Hyunyoung Songc55a3502018-12-04 15:43:16 -080030import android.content.res.Resources;
Sunny Goyal819e1932016-07-07 16:43:58 -070031import android.content.res.TypedArray;
32import android.content.res.XmlResourceParser;
Adam Cohen2e6da152015-05-06 11:42:25 -070033import android.graphics.Point;
Sunny Goyal58fa4b62019-03-22 16:23:25 -070034import android.graphics.Rect;
Sunny Goyal415f1732018-11-29 10:33:47 -080035import android.text.TextUtils;
36import android.util.AttributeSet;
Adam Cohen2e6da152015-05-06 11:42:25 -070037import android.util.DisplayMetrics;
Thales Lima7ec83822021-08-05 13:32:10 +010038import android.util.Log;
Sunny Goyal5bc18462019-01-07 15:13:39 -080039import android.util.SparseArray;
40import android.util.TypedValue;
Sunny Goyal819e1932016-07-07 16:43:58 -070041import android.util.Xml;
Sunny Goyal9c2b9602020-01-07 13:07:55 -080042import android.view.Display;
Hyunyoung Song35c3c7f2015-05-28 15:33:40 -070043
Sunny Goyal6fe3eec2019-08-15 14:53:41 -070044import androidx.annotation.Nullable;
45import androidx.annotation.VisibleForTesting;
46
Sunny Goyal68031ca2021-08-02 12:23:44 -070047import com.android.launcher3.model.DeviceGridState;
Sunny Goyalfd58da62020-08-11 12:06:49 -070048import com.android.launcher3.util.DisplayController;
49import com.android.launcher3.util.DisplayController.Info;
Sunny Goyal5bc18462019-01-07 15:13:39 -080050import com.android.launcher3.util.IntArray;
Sunny Goyald0e360a2018-06-29 14:40:18 -070051import com.android.launcher3.util.MainThreadInitializedObject;
Sunny Goyal5bc18462019-01-07 15:13:39 -080052import com.android.launcher3.util.Themes;
Sunny Goyal19ff7282021-04-22 10:12:54 -070053import com.android.launcher3.util.WindowBounds;
Hyunyoung Song35c3c7f2015-05-28 15:33:40 -070054
Sunny Goyal819e1932016-07-07 16:43:58 -070055import org.xmlpull.v1.XmlPullParser;
56import org.xmlpull.v1.XmlPullParserException;
57
58import java.io.IOException;
Adam Cohen2e6da152015-05-06 11:42:25 -070059import java.util.ArrayList;
Sunny Goyal6e6f7992021-08-24 16:23:29 -070060import java.util.Arrays;
Sunny Goyal6d55f662019-01-02 12:13:43 -080061import java.util.Collections;
Sunny Goyal19ff7282021-04-22 10:12:54 -070062import java.util.List;
Adam Cohen2e6da152015-05-06 11:42:25 -070063
64public class InvariantDeviceProfile {
Adam Cohen2e6da152015-05-06 11:42:25 -070065
Hyunyoung Songc55a3502018-12-04 15:43:16 -080066 public static final String TAG = "IDP";
Sunny Goyald0e360a2018-06-29 14:40:18 -070067 // We do not need any synchronization for this variable as its only written on UI thread.
68 public static final MainThreadInitializedObject<InvariantDeviceProfile> INSTANCE =
Sunny Goyal87dc48b2018-10-12 11:42:33 -070069 new MainThreadInitializedObject<>(InvariantDeviceProfile::new);
Adam Cohen2e6da152015-05-06 11:42:25 -070070
Sunny Goyal19ff7282021-04-22 10:12:54 -070071 private static final int DEFAULT_TRUE = -1;
72 private static final int DEFAULT_SPLIT_DISPLAY = 2;
Thales Lima7ec83822021-08-05 13:32:10 +010073 private static final int GRID_ENABLED_ALL_DISPLAYS = 0;
74 private static final int GRID_ENABLED_SINGLE_DISPLAY = 1;
Sunny Goyal19ff7282021-04-22 10:12:54 -070075
Hyunyoung Songc55a3502018-12-04 15:43:16 -080076 private static final String KEY_IDP_GRID_NAME = "idp_grid_name";
Sunny Goyal415f1732018-11-29 10:33:47 -080077
Sunny Goyal53d7ee42015-05-22 12:25:45 -070078 private static final float ICON_SIZE_DEFINED_IN_APP_DP = 48;
79
Hyunyoung Song35c3c7f2015-05-28 15:33:40 -070080 // Constants that affects the interpolation curve between statically defined device profile
81 // buckets.
Hyunyoung Songc55a3502018-12-04 15:43:16 -080082 private static final float KNEARESTNEIGHBOR = 3;
83 private static final float WEIGHT_POWER = 5;
Adam Cohen2e6da152015-05-06 11:42:25 -070084
Hyunyoung Song35c3c7f2015-05-28 15:33:40 -070085 // used to offset float not being able to express extremely small weights in extreme cases.
Hyunyoung Songc55a3502018-12-04 15:43:16 -080086 private static final float WEIGHT_EFFICIENT = 100000f;
87
Hyunyoung Song35c3c7f2015-05-28 15:33:40 -070088 /**
89 * Number of icons per row and column in the workspace.
90 */
Adam Cohen2e6da152015-05-06 11:42:25 -070091 public int numRows;
92 public int numColumns;
Hyunyoung Song35c3c7f2015-05-28 15:33:40 -070093
94 /**
95 * Number of icons per row and column in the folder.
96 */
Adam Cohen2e6da152015-05-06 11:42:25 -070097 public int numFolderRows;
98 public int numFolderColumns;
Sunny Goyalfc218302015-09-17 14:59:10 -070099 public float iconSize;
Jon Mirandab28c4fc2017-06-20 10:58:36 -0700100 public float landscapeIconSize;
Andras Kloczl30fe9152021-08-05 18:02:29 +0200101 public float twoPanelPortraitIconSize;
102 public float twoPanelLandscapeIconSize;
Andras Kloczl6057cb92021-04-07 13:45:17 +0200103 public float landscapeIconTextSize;
Andras Kloczl30fe9152021-08-05 18:02:29 +0200104 public float twoPanelPortraitIconTextSize;
105 public float twoPanelLandscapeIconTextSize;
Sunny Goyalfc218302015-09-17 14:59:10 -0700106 public int iconBitmapSize;
107 public int fillResIconDpi;
108 public float iconTextSize;
Sunny Goyalae190ff2020-04-14 00:19:01 +0000109 public float allAppsIconSize;
110 public float allAppsIconTextSize;
sfufa@google.comde013292021-09-21 18:22:44 -0700111 public float allAppsCellSpacing;
Thales Lima7ec83822021-08-05 13:32:10 +0100112 public boolean isSplitDisplay;
Hyunyoung Song35c3c7f2015-05-28 15:33:40 -0700113
Jon Mirandae126d722021-02-25 10:45:20 -0500114 public float minCellHeight;
115 public float minCellWidth;
Thales Limad1df5fc2021-09-03 18:37:19 +0100116 public float twoPanelPortraitMinCellHeightDps;
117 public float twoPanelPortraitMinCellWidthDps;
118 public float twoPanelLandscapeMinCellHeightDps;
119 public float twoPanelLandscapeMinCellWidthDps;
Jon Mirandae126d722021-02-25 10:45:20 -0500120 public float borderSpacing;
121
Sunny Goyal5bc18462019-01-07 15:13:39 -0800122 private SparseArray<TypedValue> mExtraAttrs;
123
Hyunyoung Song35c3c7f2015-05-28 15:33:40 -0700124 /**
125 * Number of icons inside the hotseat area.
126 */
Tony Wickhamb87f3cd2021-04-07 15:02:37 -0700127 protected int numShownHotseatIcons;
128
129 /**
130 * Number of icons inside the hotseat area that is stored in the database. This is greater than
131 * or equal to numnShownHotseatIcons, allowing for a seamless transition between two hotseat
132 * sizes that share the same DB.
133 */
134 public int numDatabaseHotseatIcons;
Adam Cohen27824492017-09-22 17:10:55 -0700135
Jon Miranda6f7e9702019-09-16 14:44:14 -0700136 /**
137 * Number of columns in the all apps list.
138 */
139 public int numAllAppsColumns;
Sunny Goyal19ff7282021-04-22 10:12:54 -0700140 public int numDatabaseAllAppsColumns;
Jon Miranda6f7e9702019-09-16 14:44:14 -0700141
Jon Mirandae126d722021-02-25 10:45:20 -0500142 /**
143 * Do not query directly. see {@link DeviceProfile#isScalableGrid}.
144 */
145 protected boolean isScalable;
Jon Mirandac9e69fa2021-03-22 17:13:34 -0400146 public int devicePaddingId;
Jon Mirandae126d722021-02-25 10:45:20 -0500147
Tracy Zhou7df93d22020-01-27 13:44:06 -0800148 public String dbFile;
Sunny Goyal415f1732018-11-29 10:33:47 -0800149 public int defaultLayoutId;
Adam Cohen27824492017-09-22 17:10:55 -0700150 int demoModeLayoutId;
Adam Cohen2e6da152015-05-06 11:42:25 -0700151
Pinyao Ting9cd63c92021-06-16 17:51:39 +0000152 /**
153 * An immutable list of supported profiles.
154 */
155 public List<DeviceProfile> supportedProfiles = Collections.EMPTY_LIST;
Sunny Goyalc6205602015-05-21 20:46:33 -0700156
sfufa@google.comde013292021-09-21 18:22:44 -0700157 @Nullable
158 public DevicePaddings devicePaddings;
Jon Miranda228877d2021-02-09 11:05:00 -0500159
Sunny Goyal6f866092016-03-17 17:04:15 -0700160 public Point defaultWallpaperSize;
Sunny Goyal58fa4b62019-03-22 16:23:25 -0700161 public Rect defaultWidgetPadding;
Sunny Goyal6f866092016-03-17 17:04:15 -0700162
Sunny Goyal87dc48b2018-10-12 11:42:33 -0700163 private final ArrayList<OnIDPChangeListener> mChangeListeners = new ArrayList<>();
Sunny Goyal87dc48b2018-10-12 11:42:33 -0700164
Sunny Goyalf633ef52018-03-13 09:57:05 -0700165 @VisibleForTesting
sfufa@google.comde013292021-09-21 18:22:44 -0700166 public InvariantDeviceProfile() {
167 }
Adam Cohen2e6da152015-05-06 11:42:25 -0700168
Sunny Goyalbbf01842015-10-08 07:41:15 -0700169 @TargetApi(23)
Sunny Goyald0e360a2018-06-29 14:40:18 -0700170 private InvariantDeviceProfile(Context context) {
Sunny Goyal0e7a3382020-04-13 17:15:05 -0700171 String gridName = getCurrentGridName(context);
172 String newGridName = initGrid(context, gridName);
173 if (!newGridName.equals(gridName)) {
174 Utilities.getPrefs(context).edit().putString(KEY_IDP_GRID_NAME, newGridName).apply();
175 }
Sunny Goyal68031ca2021-08-02 12:23:44 -0700176 new DeviceGridState(this).writeToPrefs(context);
Sunny Goyal0e7a3382020-04-13 17:15:05 -0700177
Sunny Goyal35c7b192021-04-20 16:51:10 -0700178 DisplayController.INSTANCE.get(context).addChangeListener(
Alex Chaufd6d9422021-04-22 19:10:21 +0100179 (displayContext, info, flags) -> {
Sunny Goyal19ff7282021-04-22 10:12:54 -0700180 if ((flags & (CHANGE_DENSITY | CHANGE_SUPPORTED_BOUNDS)) != 0) {
Alex Chaufd6d9422021-04-22 19:10:21 +0100181 onConfigChanged(displayContext);
Sunny Goyal35c7b192021-04-20 16:51:10 -0700182 }
183 });
Sunny Goyal87dc48b2018-10-12 11:42:33 -0700184 }
185
Hyunyoung Songe11eb472019-03-19 15:05:21 -0700186 /**
187 * This constructor should NOT have any monitors by design.
188 */
Sunny Goyaleff44f32019-01-09 17:29:49 -0800189 public InvariantDeviceProfile(Context context, String gridName) {
190 String newName = initGrid(context, gridName);
191 if (newName == null || !newName.equals(gridName)) {
192 throw new IllegalArgumentException("Unknown grid name");
193 }
194 }
195
Sunny Goyal0e7a3382020-04-13 17:15:05 -0700196 /**
Sunny Goyal9c2b9602020-01-07 13:07:55 -0800197 * This constructor should NOT have any monitors by design.
198 */
199 public InvariantDeviceProfile(Context context, Display display) {
Sunny Goyal0e7a3382020-04-13 17:15:05 -0700200 // Ensure that the main device profile is initialized
Alex Chaufd6d9422021-04-22 19:10:21 +0100201 INSTANCE.get(context);
Sunny Goyal0e7a3382020-04-13 17:15:05 -0700202 String gridName = getCurrentGridName(context);
203
204 // Get the display info based on default display and interpolate it to existing display
205 DisplayOption defaultDisplayOption = invDistWeightedInterpolate(
Sunny Goyal35c7b192021-04-20 16:51:10 -0700206 DisplayController.INSTANCE.get(context).getInfo(),
Sunny Goyal19ff7282021-04-22 10:12:54 -0700207 getPredefinedDeviceProfiles(context, gridName, false), false);
Sunny Goyal0e7a3382020-04-13 17:15:05 -0700208
Alex Chau417bd722021-01-26 15:22:18 +0000209 Info myInfo = new Info(context, display);
Sunny Goyal0e7a3382020-04-13 17:15:05 -0700210 DisplayOption myDisplayOption = invDistWeightedInterpolate(
Sunny Goyal19ff7282021-04-22 10:12:54 -0700211 myInfo, getPredefinedDeviceProfiles(context, gridName, false), false);
Sunny Goyal0e7a3382020-04-13 17:15:05 -0700212
213 DisplayOption result = new DisplayOption(defaultDisplayOption.grid)
214 .add(myDisplayOption);
Sunny Goyal6e6f7992021-08-24 16:23:29 -0700215 result.iconSizes[DisplayOption.INDEX_DEFAULT] =
216 defaultDisplayOption.iconSizes[DisplayOption.INDEX_DEFAULT];
217 for (int i = 1; i < DisplayOption.COUNT_TOTAL; i++) {
218 result.iconSizes[i] = Math.min(
219 defaultDisplayOption.iconSizes[i], myDisplayOption.iconSizes[i]);
Alex Chau7c439722021-03-24 11:32:44 +0000220 }
Sunny Goyal6e6f7992021-08-24 16:23:29 -0700221
Jon Mirandae126d722021-02-25 10:45:20 -0500222 result.minCellHeight = defaultDisplayOption.minCellHeight;
223 result.minCellWidth = defaultDisplayOption.minCellWidth;
224 result.borderSpacing = defaultDisplayOption.borderSpacing;
sfufa@google.comde013292021-09-21 18:22:44 -0700225 result.allAppsCellSpacing = defaultDisplayOption.allAppsCellSpacing;
Jon Miranda228877d2021-02-09 11:05:00 -0500226
Sunny Goyal19ff7282021-04-22 10:12:54 -0700227 initGrid(context, myInfo, result, false);
Sunny Goyal9c2b9602020-01-07 13:07:55 -0800228 }
229
Tracy Zhou42255d22020-03-13 00:38:11 -0700230 public static String getCurrentGridName(Context context) {
Tracy Zhouc6060e62020-04-27 13:05:34 -0700231 return Utilities.isGridOptionsEnabled(context)
232 ? Utilities.getPrefs(context).getString(KEY_IDP_GRID_NAME, null) : null;
Tracy Zhou42255d22020-03-13 00:38:11 -0700233 }
234
Sunny Goyaleff44f32019-01-09 17:29:49 -0800235 private String initGrid(Context context, String gridName) {
Sunny Goyal35c7b192021-04-20 16:51:10 -0700236 Info displayInfo = DisplayController.INSTANCE.get(context).getInfo();
Pat Manning67094bf2021-05-26 12:38:13 +0000237 // Each screen has two profiles (portrait/landscape), so devices with four or more
238 // supported profiles implies two or more internal displays.
239 boolean isSplitDisplay =
240 displayInfo.supportedBounds.size() >= 4 && ENABLE_TWO_PANEL_HOME.get();
Sunny Goyal19ff7282021-04-22 10:12:54 -0700241
242 ArrayList<DisplayOption> allOptions =
243 getPredefinedDeviceProfiles(context, gridName, isSplitDisplay);
244 DisplayOption displayOption =
245 invDistWeightedInterpolate(displayInfo, allOptions, isSplitDisplay);
246 initGrid(context, displayInfo, displayOption, isSplitDisplay);
Sunny Goyal0e7a3382020-04-13 17:15:05 -0700247 return displayOption.grid.name;
Sunny Goyal9c2b9602020-01-07 13:07:55 -0800248 }
Adam Cohen2e6da152015-05-06 11:42:25 -0700249
Sunny Goyal0e7a3382020-04-13 17:15:05 -0700250 private void initGrid(
Sunny Goyal19ff7282021-04-22 10:12:54 -0700251 Context context, Info displayInfo, DisplayOption displayOption,
252 boolean isSplitDisplay) {
Sunny Goyal35c7b192021-04-20 16:51:10 -0700253 DisplayMetrics metrics = context.getResources().getDisplayMetrics();
Sunny Goyal0e7a3382020-04-13 17:15:05 -0700254 GridOption closestProfile = displayOption.grid;
Sunny Goyalae190ff2020-04-14 00:19:01 +0000255 numRows = closestProfile.numRows;
256 numColumns = closestProfile.numColumns;
Sunny Goyalae190ff2020-04-14 00:19:01 +0000257 dbFile = closestProfile.dbFile;
258 defaultLayoutId = closestProfile.defaultLayoutId;
259 demoModeLayoutId = closestProfile.demoModeLayoutId;
260 numFolderRows = closestProfile.numFolderRows;
261 numFolderColumns = closestProfile.numFolderColumns;
Jon Mirandae126d722021-02-25 10:45:20 -0500262 isScalable = closestProfile.isScalable;
Jon Mirandac9e69fa2021-03-22 17:13:34 -0400263 devicePaddingId = closestProfile.devicePaddingId;
Thales Lima7ec83822021-08-05 13:32:10 +0100264 this.isSplitDisplay = isSplitDisplay;
Sunny Goyalae190ff2020-04-14 00:19:01 +0000265
266 mExtraAttrs = closestProfile.extraAttrs;
267
Sunny Goyal6e6f7992021-08-24 16:23:29 -0700268 iconSize = displayOption.iconSizes[DisplayOption.INDEX_DEFAULT];
269 landscapeIconSize = displayOption.iconSizes[DisplayOption.INDEX_LANDSCAPE];
270 twoPanelPortraitIconSize = displayOption.iconSizes[DisplayOption.INDEX_TWO_PANEL_PORTRAIT];
271 twoPanelLandscapeIconSize =
272 displayOption.iconSizes[DisplayOption.INDEX_TWO_PANEL_LANDSCAPE];
Sunny Goyal35c7b192021-04-20 16:51:10 -0700273 iconBitmapSize = ResourceUtils.pxFromDp(iconSize, metrics);
Sunny Goyalae190ff2020-04-14 00:19:01 +0000274 fillResIconDpi = getLauncherIconDensity(iconBitmapSize);
275
Sunny Goyal6e6f7992021-08-24 16:23:29 -0700276 iconTextSize = displayOption.textSizes[DisplayOption.INDEX_DEFAULT];
277 landscapeIconTextSize = displayOption.textSizes[DisplayOption.INDEX_LANDSCAPE];
278 twoPanelPortraitIconTextSize =
279 displayOption.textSizes[DisplayOption.INDEX_TWO_PANEL_PORTRAIT];
280 twoPanelLandscapeIconTextSize =
281 displayOption.textSizes[DisplayOption.INDEX_TWO_PANEL_LANDSCAPE];
282
Jon Mirandae126d722021-02-25 10:45:20 -0500283 minCellHeight = displayOption.minCellHeight;
284 minCellWidth = displayOption.minCellWidth;
Thales Limad1df5fc2021-09-03 18:37:19 +0100285 twoPanelPortraitMinCellHeightDps = displayOption.twoPanelPortraitMinCellHeightDps;
286 twoPanelPortraitMinCellWidthDps = displayOption.twoPanelPortraitMinCellWidthDps;
287 twoPanelLandscapeMinCellHeightDps = displayOption.twoPanelLandscapeMinCellHeightDps;
288 twoPanelLandscapeMinCellWidthDps = displayOption.twoPanelLandscapeMinCellWidthDps;
Jon Mirandae126d722021-02-25 10:45:20 -0500289 borderSpacing = displayOption.borderSpacing;
sfufa@google.comde013292021-09-21 18:22:44 -0700290 allAppsCellSpacing = displayOption.allAppsCellSpacing;
Sunny Goyal19ff7282021-04-22 10:12:54 -0700291
292 numShownHotseatIcons = closestProfile.numHotseatIcons;
293 numDatabaseHotseatIcons = isSplitDisplay
294 ? closestProfile.numDatabaseHotseatIcons : closestProfile.numHotseatIcons;
295
296 numAllAppsColumns = closestProfile.numAllAppsColumns;
297 numDatabaseAllAppsColumns = isSplitDisplay
298 ? closestProfile.numDatabaseAllAppsColumns : closestProfile.numAllAppsColumns;
Jon Mirandae126d722021-02-25 10:45:20 -0500299
Tracy Zhouc6060e62020-04-27 13:05:34 -0700300 if (Utilities.isGridOptionsEnabled(context)) {
Sunny Goyal6e6f7992021-08-24 16:23:29 -0700301 allAppsIconSize = displayOption.iconSizes[DisplayOption.INDEX_ALL_APPS];
302 allAppsIconTextSize = displayOption.textSizes[DisplayOption.INDEX_ALL_APPS];
Sunny Goyalae190ff2020-04-14 00:19:01 +0000303 } else {
304 allAppsIconSize = iconSize;
305 allAppsIconTextSize = iconTextSize;
306 }
307
Jon Mirandac9e69fa2021-03-22 17:13:34 -0400308 if (devicePaddingId != 0) {
309 devicePaddings = new DevicePaddings(context, devicePaddingId);
310 }
311
Sunny Goyalae190ff2020-04-14 00:19:01 +0000312 // If the partner customization apk contains any grid overrides, apply them
313 // Supported overrides: numRows, numColumns, iconSize
Sunny Goyal35c7b192021-04-20 16:51:10 -0700314 applyPartnerDeviceProfileOverrides(context, metrics);
Sunny Goyalc6205602015-05-21 20:46:33 -0700315
Pinyao Ting9cd63c92021-06-16 17:51:39 +0000316 final List<DeviceProfile> localSupportedProfiles = new ArrayList<>();
Sunny Goyal19ff7282021-04-22 10:12:54 -0700317 defaultWallpaperSize = new Point(displayInfo.currentSize);
318 for (WindowBounds bounds : displayInfo.supportedBounds) {
Pinyao Ting9cd63c92021-06-16 17:51:39 +0000319 localSupportedProfiles.add(new DeviceProfile.Builder(context, this, displayInfo)
Sunny Goyal19ff7282021-04-22 10:12:54 -0700320 .setUseTwoPanels(isSplitDisplay)
321 .setWindowBounds(bounds).build());
Sunny Goyalc6205602015-05-21 20:46:33 -0700322
Sunny Goyal19ff7282021-04-22 10:12:54 -0700323 // Wallpaper size should be the maximum of the all possible sizes Launcher expects
324 int displayWidth = bounds.bounds.width();
325 int displayHeight = bounds.bounds.height();
326 defaultWallpaperSize.y = Math.max(defaultWallpaperSize.y, displayHeight);
Sunny Goyal0e7a3382020-04-13 17:15:05 -0700327
Sunny Goyal19ff7282021-04-22 10:12:54 -0700328 // We need to ensure that there is enough extra space in the wallpaper
329 // for the intended parallax effects
330 float parallaxFactor =
331 dpiFromPx(Math.min(displayWidth, displayHeight), displayInfo.densityDpi) < 720
332 ? 2
333 : wallpaperTravelToScreenWidthRatio(displayWidth, displayHeight);
334 defaultWallpaperSize.x =
335 Math.max(defaultWallpaperSize.x, Math.round(parallaxFactor * displayWidth));
Sunny Goyal6f866092016-03-17 17:04:15 -0700336 }
Pinyao Ting9cd63c92021-06-16 17:51:39 +0000337 supportedProfiles = Collections.unmodifiableList(localSupportedProfiles);
Sunny Goyal58fa4b62019-03-22 16:23:25 -0700338
339 ComponentName cn = new ComponentName(context.getPackageName(), getClass().getName());
340 defaultWidgetPadding = AppWidgetHostView.getDefaultPaddingForWidget(context, cn, null);
Adam Cohen2e6da152015-05-06 11:42:25 -0700341 }
342
Sunny Goyal5bc18462019-01-07 15:13:39 -0800343 @Nullable
344 public TypedValue getAttrValue(int attr) {
345 return mExtraAttrs == null ? null : mExtraAttrs.get(attr);
346 }
347
Sunny Goyal87dc48b2018-10-12 11:42:33 -0700348 public void addOnChangeListener(OnIDPChangeListener listener) {
349 mChangeListeners.add(listener);
350 }
351
Hyunyoung Songb4d1ca42019-01-08 17:15:16 -0800352 public void removeOnChangeListener(OnIDPChangeListener listener) {
353 mChangeListeners.remove(listener);
354 }
355
Hyunyoung Songc55a3502018-12-04 15:43:16 -0800356
Sunny Goyal7d892ff2019-01-11 15:08:44 -0800357 public void setCurrentGrid(Context context, String gridName) {
358 Context appContext = context.getApplicationContext();
359 Utilities.getPrefs(appContext).edit().putString(KEY_IDP_GRID_NAME, gridName).apply();
Sunny Goyal6fe3eec2019-08-15 14:53:41 -0700360 MAIN_EXECUTOR.execute(() -> onConfigChanged(appContext));
Sunny Goyal7d892ff2019-01-11 15:08:44 -0800361 }
362
Sunny Goyal6e6f7992021-08-24 16:23:29 -0700363 private Object[] toModelState() {
sfufa@google.comde013292021-09-21 18:22:44 -0700364 return new Object[]{
Sunny Goyal6e6f7992021-08-24 16:23:29 -0700365 numColumns, numRows, numDatabaseHotseatIcons, iconBitmapSize, fillResIconDpi,
366 numDatabaseAllAppsColumns, dbFile};
367 }
368
Sunny Goyal87dc48b2018-10-12 11:42:33 -0700369 private void onConfigChanged(Context context) {
Sunny Goyal6e6f7992021-08-24 16:23:29 -0700370 Object[] oldState = toModelState();
371
Sunny Goyal87dc48b2018-10-12 11:42:33 -0700372 // Re-init grid
Tracy Zhouc6060e62020-04-27 13:05:34 -0700373 String gridName = getCurrentGridName(context);
Jon Miranda6f7e9702019-09-16 14:44:14 -0700374 initGrid(context, gridName);
Sunny Goyal87dc48b2018-10-12 11:42:33 -0700375
Sunny Goyal6e6f7992021-08-24 16:23:29 -0700376 boolean modelPropsChanged = !Arrays.equals(oldState, toModelState());
Sunny Goyal87dc48b2018-10-12 11:42:33 -0700377 for (OnIDPChangeListener listener : mChangeListeners) {
Sunny Goyal6e6f7992021-08-24 16:23:29 -0700378 listener.onIdpChanged(modelPropsChanged);
Sunny Goyal87dc48b2018-10-12 11:42:33 -0700379 }
380 }
381
Sunny Goyal19ff7282021-04-22 10:12:54 -0700382 private static ArrayList<DisplayOption> getPredefinedDeviceProfiles(
383 Context context, String gridName, boolean isSplitDisplay) {
Sunny Goyal415f1732018-11-29 10:33:47 -0800384 ArrayList<DisplayOption> profiles = new ArrayList<>();
Sunny Goyal819e1932016-07-07 16:43:58 -0700385 try (XmlResourceParser parser = context.getResources().getXml(R.xml.device_profiles)) {
386 final int depth = parser.getDepth();
387 int type;
Sunny Goyal819e1932016-07-07 16:43:58 -0700388 while (((type = parser.next()) != XmlPullParser.END_TAG ||
389 parser.getDepth() > depth) && type != XmlPullParser.END_DOCUMENT) {
Sunny Goyal7d892ff2019-01-11 15:08:44 -0800390 if ((type == XmlPullParser.START_TAG)
391 && GridOption.TAG_NAME.equals(parser.getName())) {
Sunny Goyal415f1732018-11-29 10:33:47 -0800392
Thales Lima7ec83822021-08-05 13:32:10 +0100393 GridOption gridOption =
394 new GridOption(context, Xml.asAttributeSet(parser), isSplitDisplay);
395 if (gridOption.isEnabled) {
396 final int displayDepth = parser.getDepth();
397 while (((type = parser.next()) != XmlPullParser.END_TAG
398 || parser.getDepth() > displayDepth)
399 && type != XmlPullParser.END_DOCUMENT) {
400 if ((type == XmlPullParser.START_TAG) && "display-option".equals(
401 parser.getName())) {
402 profiles.add(new DisplayOption(gridOption, context,
403 Xml.asAttributeSet(parser),
404 isSplitDisplay ? DEFAULT_SPLIT_DISPLAY : DEFAULT_TRUE));
405 }
Sunny Goyal415f1732018-11-29 10:33:47 -0800406 }
407 }
Sunny Goyal819e1932016-07-07 16:43:58 -0700408 }
409 }
sfufa@google.comde013292021-09-21 18:22:44 -0700410 } catch (IOException | XmlPullParserException e) {
Sunny Goyal819e1932016-07-07 16:43:58 -0700411 throw new RuntimeException(e);
412 }
Sunny Goyal415f1732018-11-29 10:33:47 -0800413
Sunny Goyalae190ff2020-04-14 00:19:01 +0000414 ArrayList<DisplayOption> filteredProfiles = new ArrayList<>();
Sunny Goyal415f1732018-11-29 10:33:47 -0800415 if (!TextUtils.isEmpty(gridName)) {
416 for (DisplayOption option : profiles) {
Thales Lima7ec83822021-08-05 13:32:10 +0100417 if (gridName.equals(option.grid.name) && option.grid.isEnabled) {
Sunny Goyalae190ff2020-04-14 00:19:01 +0000418 filteredProfiles.add(option);
Sunny Goyal415f1732018-11-29 10:33:47 -0800419 }
420 }
421 }
Sunny Goyalae190ff2020-04-14 00:19:01 +0000422 if (filteredProfiles.isEmpty()) {
423 // No grid found, use the default options
424 for (DisplayOption option : profiles) {
425 if (option.canBeDefault) {
426 filteredProfiles.add(option);
427 }
Sunny Goyal415f1732018-11-29 10:33:47 -0800428 }
429 }
Sunny Goyalae190ff2020-04-14 00:19:01 +0000430 if (filteredProfiles.isEmpty()) {
431 throw new RuntimeException("No display option with canBeDefault=true");
432 }
433 return filteredProfiles;
Hyunyoung Song35c3c7f2015-05-28 15:33:40 -0700434 }
435
Thales Lima7ec83822021-08-05 13:32:10 +0100436 /**
437 * @return all the grid options that can be shown on the device
438 */
439 public List<GridOption> parseAllGridOptions(Context context) {
440 List<GridOption> result = new ArrayList<>();
441 try (XmlResourceParser parser = context.getResources().getXml(R.xml.device_profiles)) {
442 final int depth = parser.getDepth();
443 int type;
444 while (((type = parser.next()) != XmlPullParser.END_TAG
445 || parser.getDepth() > depth) && type != XmlPullParser.END_DOCUMENT) {
446 if ((type == XmlPullParser.START_TAG)
447 && GridOption.TAG_NAME.equals(parser.getName())) {
448 GridOption option =
449 new GridOption(context, Xml.asAttributeSet(parser), isSplitDisplay);
450 if (option.isEnabled) {
451 result.add(option);
452 }
453 }
454 }
455 } catch (IOException | XmlPullParserException e) {
456 Log.e(TAG, "Error parsing device profile", e);
457 return Collections.emptyList();
458 }
459 return result;
460 }
461
Sunny Goyal53d7ee42015-05-22 12:25:45 -0700462 private int getLauncherIconDensity(int requiredSize) {
463 // Densities typically defined by an app.
sfufa@google.comde013292021-09-21 18:22:44 -0700464 int[] densityBuckets = new int[]{
Sunny Goyal53d7ee42015-05-22 12:25:45 -0700465 DisplayMetrics.DENSITY_LOW,
466 DisplayMetrics.DENSITY_MEDIUM,
467 DisplayMetrics.DENSITY_TV,
468 DisplayMetrics.DENSITY_HIGH,
469 DisplayMetrics.DENSITY_XHIGH,
470 DisplayMetrics.DENSITY_XXHIGH,
471 DisplayMetrics.DENSITY_XXXHIGH
472 };
473
474 int density = DisplayMetrics.DENSITY_XXXHIGH;
475 for (int i = densityBuckets.length - 1; i >= 0; i--) {
476 float expectedSize = ICON_SIZE_DEFINED_IN_APP_DP * densityBuckets[i]
477 / DisplayMetrics.DENSITY_DEFAULT;
478 if (expectedSize >= requiredSize) {
479 density = densityBuckets[i];
480 }
481 }
482
483 return density;
484 }
Hyunyoung Song35c3c7f2015-05-28 15:33:40 -0700485
Adam Cohen2e6da152015-05-06 11:42:25 -0700486 /**
487 * Apply any Partner customization grid overrides.
488 *
489 * Currently we support: all apps row / column count.
490 */
Hyunyoung Song35c3c7f2015-05-28 15:33:40 -0700491 private void applyPartnerDeviceProfileOverrides(Context context, DisplayMetrics dm) {
492 Partner p = Partner.get(context.getPackageManager());
Adam Cohen2e6da152015-05-06 11:42:25 -0700493 if (p != null) {
494 p.applyInvariantDeviceProfileOverrides(this, dm);
495 }
496 }
497
Sunny Goyal415f1732018-11-29 10:33:47 -0800498 private static float dist(float x0, float y0, float x1, float y1) {
Hyunyoung Song35c3c7f2015-05-28 15:33:40 -0700499 return (float) Math.hypot(x1 - x0, y1 - y0);
Adam Cohen2e6da152015-05-06 11:42:25 -0700500 }
501
Sunny Goyal19ff7282021-04-22 10:12:54 -0700502 private static DisplayOption invDistWeightedInterpolate(
503 Info displayInfo, ArrayList<DisplayOption> points, boolean isSplitDisplay) {
504 int minWidthPx = Integer.MAX_VALUE;
505 int minHeightPx = Integer.MAX_VALUE;
506 for (WindowBounds bounds : displayInfo.supportedBounds) {
507 boolean isTablet = displayInfo.isTablet(bounds);
508 if (isTablet && isSplitDisplay) {
509 // For split displays, take half width per page
510 minWidthPx = Math.min(minWidthPx, bounds.availableSize.x / 2);
511 minHeightPx = Math.min(minHeightPx, bounds.availableSize.y);
Sunny Goyal0e7a3382020-04-13 17:15:05 -0700512
Sunny Goyal19ff7282021-04-22 10:12:54 -0700513 } else if (!isTablet && bounds.isLandscape()) {
514 // We will use transposed layout in this case
515 minWidthPx = Math.min(minWidthPx, bounds.availableSize.y);
516 minHeightPx = Math.min(minHeightPx, bounds.availableSize.x);
517 } else {
518 minWidthPx = Math.min(minWidthPx, bounds.availableSize.x);
519 minHeightPx = Math.min(minHeightPx, bounds.availableSize.y);
520 }
521 }
522
523 float width = dpiFromPx(minWidthPx, displayInfo.densityDpi);
524 float height = dpiFromPx(minHeightPx, displayInfo.densityDpi);
Sunny Goyal0e7a3382020-04-13 17:15:05 -0700525
526 // Sort the profiles based on the closeness to the device size
527 Collections.sort(points, (a, b) ->
528 Float.compare(dist(width, height, a.minWidthDps, a.minHeightDps),
529 dist(width, height, b.minWidthDps, b.minHeightDps)));
530
Sunny Goyal6e6f7992021-08-24 16:23:29 -0700531 DisplayOption closestPoint = points.get(0);
532 GridOption closestOption = closestPoint.grid;
Sunny Goyal0e7a3382020-04-13 17:15:05 -0700533 float weights = 0;
534
Sunny Goyal6e6f7992021-08-24 16:23:29 -0700535 if (dist(width, height, closestPoint.minWidthDps, closestPoint.minHeightDps) == 0) {
536 return closestPoint;
Sunny Goyal0e7a3382020-04-13 17:15:05 -0700537 }
538
539 DisplayOption out = new DisplayOption(closestOption);
540 for (int i = 0; i < points.size() && i < KNEARESTNEIGHBOR; ++i) {
Sunny Goyal6e6f7992021-08-24 16:23:29 -0700541 DisplayOption p = points.get(i);
Sunny Goyal0e7a3382020-04-13 17:15:05 -0700542 float w = weight(width, height, p.minWidthDps, p.minHeightDps, WEIGHT_POWER);
543 weights += w;
544 out.add(new DisplayOption().add(p).multiply(w));
545 }
Sunny Goyal6e6f7992021-08-24 16:23:29 -0700546 out.multiply(1.0f / weights);
547
548 // Since the bitmaps are persisted, ensure that the default bitmap size is same as
549 // predefined size to avoid cache invalidation
550 out.iconSizes[DisplayOption.INDEX_DEFAULT] =
551 closestPoint.iconSizes[DisplayOption.INDEX_DEFAULT];
552 for (int i = DisplayOption.INDEX_DEFAULT + 1; i < DisplayOption.COUNT_TOTAL; i++) {
553 out.iconSizes[i] = Math.min(out.iconSizes[i],
554 out.iconSizes[DisplayOption.INDEX_DEFAULT]);
555 }
556
557 return out;
Sunny Goyal0e7a3382020-04-13 17:15:05 -0700558 }
559
Sunny Goyal27835952017-01-13 12:15:53 -0800560 public DeviceProfile getDeviceProfile(Context context) {
Sunny Goyal19ff7282021-04-22 10:12:54 -0700561 Resources res = context.getResources();
562 Configuration config = context.getResources().getConfiguration();
563
Sunny Goyal3e9a29c2021-09-07 15:53:09 -0400564 float screenWidth = config.screenWidthDp * res.getDisplayMetrics().density;
565 float screenHeight = config.screenHeightDp * res.getDisplayMetrics().density;
566 return getBestMatch(screenWidth, screenHeight);
567 }
Sunny Goyal19ff7282021-04-22 10:12:54 -0700568
Sunny Goyal3e9a29c2021-09-07 15:53:09 -0400569 public DeviceProfile getBestMatch(float screenWidth, float screenHeight) {
Sunny Goyal19ff7282021-04-22 10:12:54 -0700570 DeviceProfile bestMatch = supportedProfiles.get(0);
571 float minDiff = Float.MAX_VALUE;
572
573 for (DeviceProfile profile : supportedProfiles) {
Sunny Goyal3e9a29c2021-09-07 15:53:09 -0400574 float diff = Math.abs(profile.widthPx - screenWidth)
575 + Math.abs(profile.heightPx - screenHeight);
Sunny Goyal19ff7282021-04-22 10:12:54 -0700576 if (diff < minDiff) {
577 minDiff = diff;
578 bestMatch = profile;
579 }
580 }
581 return bestMatch;
Sunny Goyal27835952017-01-13 12:15:53 -0800582 }
583
Sunny Goyal415f1732018-11-29 10:33:47 -0800584 private static float weight(float x0, float y0, float x1, float y1, float pow) {
Hyunyoung Song35c3c7f2015-05-28 15:33:40 -0700585 float d = dist(x0, y0, x1, y1);
586 if (Float.compare(d, 0f) == 0) {
587 return Float.POSITIVE_INFINITY;
588 }
589 return (float) (WEIGHT_EFFICIENT / Math.pow(d, pow));
590 }
Sunny Goyal6f866092016-03-17 17:04:15 -0700591
592 /**
593 * As a ratio of screen height, the total distance we want the parallax effect to span
594 * horizontally
595 */
596 private static float wallpaperTravelToScreenWidthRatio(int width, int height) {
597 float aspectRatio = width / (float) height;
598
599 // At an aspect ratio of 16/10, the wallpaper parallax effect should span 1.5 * screen width
600 // At an aspect ratio of 10/16, the wallpaper parallax effect should span 1.2 * screen width
601 // We will use these two data points to extrapolate how much the wallpaper parallax effect
602 // to span (ie travel) at any aspect ratio:
603
sfufa@google.comde013292021-09-21 18:22:44 -0700604 final float ASPECT_RATIO_LANDSCAPE = 16 / 10f;
605 final float ASPECT_RATIO_PORTRAIT = 10 / 16f;
Sunny Goyal6f866092016-03-17 17:04:15 -0700606 final float WALLPAPER_WIDTH_TO_SCREEN_RATIO_LANDSCAPE = 1.5f;
607 final float WALLPAPER_WIDTH_TO_SCREEN_RATIO_PORTRAIT = 1.2f;
608
609 // To find out the desired width at different aspect ratios, we use the following two
610 // formulas, where the coefficient on x is the aspect ratio (width/height):
611 // (16/10)x + y = 1.5
612 // (10/16)x + y = 1.2
613 // We solve for x and y and end up with a final formula:
614 final float x =
sfufa@google.comde013292021-09-21 18:22:44 -0700615 (WALLPAPER_WIDTH_TO_SCREEN_RATIO_LANDSCAPE
616 - WALLPAPER_WIDTH_TO_SCREEN_RATIO_PORTRAIT) /
Sunny Goyal6f866092016-03-17 17:04:15 -0700617 (ASPECT_RATIO_LANDSCAPE - ASPECT_RATIO_PORTRAIT);
618 final float y = WALLPAPER_WIDTH_TO_SCREEN_RATIO_PORTRAIT - x * ASPECT_RATIO_PORTRAIT;
619 return x * aspectRatio + y;
620 }
621
Sunny Goyal87dc48b2018-10-12 11:42:33 -0700622 public interface OnIDPChangeListener {
623
Sunny Goyalb47172b2021-05-03 19:59:51 -0700624 /**
625 * Called when the device provide changes
626 */
Sunny Goyal6e6f7992021-08-24 16:23:29 -0700627 void onIdpChanged(boolean modelPropertiesChanged);
Sunny Goyal87dc48b2018-10-12 11:42:33 -0700628 }
Sunny Goyal415f1732018-11-29 10:33:47 -0800629
630
Sunny Goyaleff44f32019-01-09 17:29:49 -0800631 public static final class GridOption {
Sunny Goyal415f1732018-11-29 10:33:47 -0800632
Sunny Goyal7d892ff2019-01-11 15:08:44 -0800633 public static final String TAG_NAME = "grid-option";
634
Sunny Goyaleff44f32019-01-09 17:29:49 -0800635 public final String name;
636 public final int numRows;
637 public final int numColumns;
Thales Lima7ec83822021-08-05 13:32:10 +0100638 public final boolean isEnabled;
Sunny Goyal415f1732018-11-29 10:33:47 -0800639
640 private final int numFolderRows;
641 private final int numFolderColumns;
642
Sunny Goyal19ff7282021-04-22 10:12:54 -0700643 private final int numAllAppsColumns;
644 private final int numDatabaseAllAppsColumns;
645 private final int numHotseatIcons;
Tony Wickhamb87f3cd2021-04-07 15:02:37 -0700646 private final int numDatabaseHotseatIcons;
Sunny Goyal415f1732018-11-29 10:33:47 -0800647
Tracy Zhou7df93d22020-01-27 13:44:06 -0800648 private final String dbFile;
Sunny Goyalae190ff2020-04-14 00:19:01 +0000649
Sunny Goyal415f1732018-11-29 10:33:47 -0800650 private final int defaultLayoutId;
651 private final int demoModeLayoutId;
652
Jon Mirandae126d722021-02-25 10:45:20 -0500653 private final boolean isScalable;
Jon Mirandac9e69fa2021-03-22 17:13:34 -0400654 private final int devicePaddingId;
Jon Mirandae126d722021-02-25 10:45:20 -0500655
Sunny Goyal5bc18462019-01-07 15:13:39 -0800656 private final SparseArray<TypedValue> extraAttrs;
657
Thales Lima7ec83822021-08-05 13:32:10 +0100658 public GridOption(Context context, AttributeSet attrs, boolean isSplitDisplay) {
Sunny Goyal415f1732018-11-29 10:33:47 -0800659 TypedArray a = context.obtainStyledAttributes(
660 attrs, R.styleable.GridDisplayOption);
661 name = a.getString(R.styleable.GridDisplayOption_name);
662 numRows = a.getInt(R.styleable.GridDisplayOption_numRows, 0);
663 numColumns = a.getInt(R.styleable.GridDisplayOption_numColumns, 0);
664
Tracy Zhou7df93d22020-01-27 13:44:06 -0800665 dbFile = a.getString(R.styleable.GridDisplayOption_dbFile);
Alex Chau1e448462021-08-13 21:48:35 +0100666 defaultLayoutId = a.getResourceId(isSplitDisplay && a.hasValue(
667 R.styleable.GridDisplayOption_defaultSplitDisplayLayoutId)
668 ? R.styleable.GridDisplayOption_defaultSplitDisplayLayoutId
669 : R.styleable.GridDisplayOption_defaultLayoutId, 0);
Sunny Goyal415f1732018-11-29 10:33:47 -0800670 demoModeLayoutId = a.getResourceId(
671 R.styleable.GridDisplayOption_demoModeLayoutId, defaultLayoutId);
Sunny Goyal19ff7282021-04-22 10:12:54 -0700672
673 numAllAppsColumns = a.getInt(
674 R.styleable.GridDisplayOption_numAllAppsColumns, numColumns);
675 numDatabaseAllAppsColumns = a.getInt(
676 R.styleable.GridDisplayOption_numExtendedAllAppsColumns, 2 * numAllAppsColumns);
677
678 numHotseatIcons = a.getInt(
Sunny Goyal415f1732018-11-29 10:33:47 -0800679 R.styleable.GridDisplayOption_numHotseatIcons, numColumns);
Sunny Goyal19ff7282021-04-22 10:12:54 -0700680 numDatabaseHotseatIcons = a.getInt(
681 R.styleable.GridDisplayOption_numExtendedHotseatIcons, 2 * numHotseatIcons);
682
Sunny Goyal415f1732018-11-29 10:33:47 -0800683 numFolderRows = a.getInt(
684 R.styleable.GridDisplayOption_numFolderRows, numRows);
685 numFolderColumns = a.getInt(
686 R.styleable.GridDisplayOption_numFolderColumns, numColumns);
Jon Miranda6f7e9702019-09-16 14:44:14 -0700687
Jon Mirandae126d722021-02-25 10:45:20 -0500688 isScalable = a.getBoolean(
689 R.styleable.GridDisplayOption_isScalable, false);
Jon Mirandac9e69fa2021-03-22 17:13:34 -0400690 devicePaddingId = a.getResourceId(
691 R.styleable.GridDisplayOption_devicePaddingId, 0);
Jon Mirandae126d722021-02-25 10:45:20 -0500692
Thales Lima7ec83822021-08-05 13:32:10 +0100693 final int enabledInt =
694 a.getInteger(R.styleable.GridDisplayOption_gridEnabled,
695 GRID_ENABLED_ALL_DISPLAYS);
696 isEnabled = enabledInt == GRID_ENABLED_ALL_DISPLAYS
697 || enabledInt == GRID_ENABLED_SINGLE_DISPLAY && !isSplitDisplay;
698
Sunny Goyal415f1732018-11-29 10:33:47 -0800699 a.recycle();
Sunny Goyal5bc18462019-01-07 15:13:39 -0800700 extraAttrs = Themes.createValueMap(context, attrs,
701 IntArray.wrap(R.styleable.GridDisplayOption));
Sunny Goyal415f1732018-11-29 10:33:47 -0800702 }
703 }
704
Sunny Goyal19ff7282021-04-22 10:12:54 -0700705 @VisibleForTesting
706 static final class DisplayOption {
707
Sunny Goyal6e6f7992021-08-24 16:23:29 -0700708 static final int INDEX_DEFAULT = 0;
709 static final int INDEX_LANDSCAPE = 1;
710 static final int INDEX_ALL_APPS = 2;
711 static final int INDEX_TWO_PANEL_PORTRAIT = 3;
712 static final int INDEX_TWO_PANEL_LANDSCAPE = 4;
713
714 static final int COUNT_TOTAL = 5;
715
Sunny Goyal19ff7282021-04-22 10:12:54 -0700716 public final GridOption grid;
Sunny Goyal415f1732018-11-29 10:33:47 -0800717
Sunny Goyal415f1732018-11-29 10:33:47 -0800718 private final float minWidthDps;
719 private final float minHeightDps;
720 private final boolean canBeDefault;
721
Jon Mirandae126d722021-02-25 10:45:20 -0500722 private float minCellHeight;
723 private float minCellWidth;
Thales Limad1df5fc2021-09-03 18:37:19 +0100724 private float twoPanelPortraitMinCellHeightDps;
725 private float twoPanelPortraitMinCellWidthDps;
726 private float twoPanelLandscapeMinCellHeightDps;
727 private float twoPanelLandscapeMinCellWidthDps;
sfufa@google.comde013292021-09-21 18:22:44 -0700728 private float allAppsCellSpacing;
Jon Mirandae126d722021-02-25 10:45:20 -0500729 private float borderSpacing;
730
Sunny Goyal6e6f7992021-08-24 16:23:29 -0700731 private final float[] iconSizes = new float[COUNT_TOTAL];
732 private final float[] textSizes = new float[COUNT_TOTAL];
Sunny Goyal415f1732018-11-29 10:33:47 -0800733
Sunny Goyal19ff7282021-04-22 10:12:54 -0700734 DisplayOption(GridOption grid, Context context, AttributeSet attrs, int defaultFlagValue) {
Sunny Goyal415f1732018-11-29 10:33:47 -0800735 this.grid = grid;
736
737 TypedArray a = context.obtainStyledAttributes(
738 attrs, R.styleable.ProfileDisplayOption);
739
Sunny Goyal415f1732018-11-29 10:33:47 -0800740 minWidthDps = a.getFloat(R.styleable.ProfileDisplayOption_minWidthDps, 0);
741 minHeightDps = a.getFloat(R.styleable.ProfileDisplayOption_minHeightDps, 0);
Sunny Goyal19ff7282021-04-22 10:12:54 -0700742
743 canBeDefault = a.getInt(R.styleable.ProfileDisplayOption_canBeDefault, 0)
744 == defaultFlagValue;
Sunny Goyal415f1732018-11-29 10:33:47 -0800745
Jon Mirandae126d722021-02-25 10:45:20 -0500746 minCellHeight = a.getFloat(R.styleable.ProfileDisplayOption_minCellHeightDps, 0);
747 minCellWidth = a.getFloat(R.styleable.ProfileDisplayOption_minCellWidthDps, 0);
Thales Limad1df5fc2021-09-03 18:37:19 +0100748 twoPanelPortraitMinCellHeightDps = a.getFloat(
749 R.styleable.ProfileDisplayOption_twoPanelPortraitMinCellHeightDps,
750 minCellHeight);
751 twoPanelPortraitMinCellWidthDps = a.getFloat(
752 R.styleable.ProfileDisplayOption_twoPanelPortraitMinCellWidthDps, minCellWidth);
753 twoPanelLandscapeMinCellHeightDps = a.getFloat(
754 R.styleable.ProfileDisplayOption_twoPanelLandscapeMinCellHeightDps,
755 twoPanelPortraitMinCellHeightDps);
756 twoPanelLandscapeMinCellWidthDps = a.getFloat(
757 R.styleable.ProfileDisplayOption_twoPanelLandscapeMinCellWidthDps,
758 twoPanelPortraitMinCellWidthDps);
Jon Mirandae126d722021-02-25 10:45:20 -0500759 borderSpacing = a.getFloat(R.styleable.ProfileDisplayOption_borderSpacingDps, 0);
sfufa@google.comde013292021-09-21 18:22:44 -0700760 allAppsCellSpacing = a.getFloat(R.styleable.ProfileDisplayOption_allAppsCellSpacingDps,
761 borderSpacing);
Jon Mirandae126d722021-02-25 10:45:20 -0500762
Sunny Goyal6e6f7992021-08-24 16:23:29 -0700763 iconSizes[INDEX_DEFAULT] =
764 a.getFloat(R.styleable.ProfileDisplayOption_iconImageSize, 0);
765 iconSizes[INDEX_LANDSCAPE] =
766 a.getFloat(R.styleable.ProfileDisplayOption_landscapeIconSize,
767 iconSizes[INDEX_DEFAULT]);
768 iconSizes[INDEX_ALL_APPS] =
769 a.getFloat(R.styleable.ProfileDisplayOption_allAppsIconSize,
770 iconSizes[INDEX_DEFAULT]);
771 iconSizes[INDEX_TWO_PANEL_PORTRAIT] =
772 a.getFloat(R.styleable.ProfileDisplayOption_twoPanelPortraitIconSize,
773 iconSizes[INDEX_DEFAULT]);
774 iconSizes[INDEX_TWO_PANEL_LANDSCAPE] =
775 a.getFloat(R.styleable.ProfileDisplayOption_twoPanelLandscapeIconSize,
776 iconSizes[INDEX_LANDSCAPE]);
Jon Miranda6f7e9702019-09-16 14:44:14 -0700777
Sunny Goyal6e6f7992021-08-24 16:23:29 -0700778 textSizes[INDEX_DEFAULT] =
779 a.getFloat(R.styleable.ProfileDisplayOption_iconTextSize, 0);
780 textSizes[INDEX_LANDSCAPE] =
781 a.getFloat(R.styleable.ProfileDisplayOption_landscapeIconTextSize,
782 textSizes[INDEX_DEFAULT]);
783 textSizes[INDEX_ALL_APPS] =
784 a.getFloat(R.styleable.ProfileDisplayOption_allAppsIconTextSize,
785 textSizes[INDEX_DEFAULT]);
786 textSizes[INDEX_TWO_PANEL_PORTRAIT] =
787 a.getFloat(R.styleable.ProfileDisplayOption_twoPanelPortraitIconTextSize,
788 textSizes[INDEX_DEFAULT]);
789 textSizes[INDEX_TWO_PANEL_LANDSCAPE] =
790 a.getFloat(R.styleable.ProfileDisplayOption_twoPanelLandscapeIconTextSize,
791 textSizes[INDEX_LANDSCAPE]);
792
Sunny Goyal415f1732018-11-29 10:33:47 -0800793 a.recycle();
794 }
795
796 DisplayOption() {
Sunny Goyal0e7a3382020-04-13 17:15:05 -0700797 this(null);
798 }
799
800 DisplayOption(GridOption grid) {
801 this.grid = grid;
Sunny Goyal415f1732018-11-29 10:33:47 -0800802 minWidthDps = 0;
803 minHeightDps = 0;
804 canBeDefault = false;
Jon Mirandae126d722021-02-25 10:45:20 -0500805 minCellHeight = 0;
806 minCellWidth = 0;
807 borderSpacing = 0;
Sunny Goyal415f1732018-11-29 10:33:47 -0800808 }
809
810 private DisplayOption multiply(float w) {
Sunny Goyal6e6f7992021-08-24 16:23:29 -0700811 for (int i = 0; i < COUNT_TOTAL; i++) {
812 iconSizes[i] *= w;
813 textSizes[i] *= w;
814 }
Jon Mirandae126d722021-02-25 10:45:20 -0500815 minCellHeight *= w;
816 minCellWidth *= w;
Thales Limad1df5fc2021-09-03 18:37:19 +0100817 twoPanelPortraitMinCellHeightDps *= w;
818 twoPanelPortraitMinCellWidthDps *= w;
819 twoPanelLandscapeMinCellHeightDps *= w;
820 twoPanelLandscapeMinCellWidthDps *= w;
Jon Mirandae126d722021-02-25 10:45:20 -0500821 borderSpacing *= w;
sfufa@google.comde013292021-09-21 18:22:44 -0700822 allAppsCellSpacing *= w;
Sunny Goyal415f1732018-11-29 10:33:47 -0800823 return this;
824 }
825
826 private DisplayOption add(DisplayOption p) {
Sunny Goyal6e6f7992021-08-24 16:23:29 -0700827 for (int i = 0; i < COUNT_TOTAL; i++) {
828 iconSizes[i] += p.iconSizes[i];
829 textSizes[i] += p.textSizes[i];
830 }
Jon Mirandae126d722021-02-25 10:45:20 -0500831 minCellHeight += p.minCellHeight;
832 minCellWidth += p.minCellWidth;
Thales Limad1df5fc2021-09-03 18:37:19 +0100833 twoPanelPortraitMinCellHeightDps += p.twoPanelPortraitMinCellHeightDps;
834 twoPanelPortraitMinCellWidthDps += p.twoPanelPortraitMinCellWidthDps;
835 twoPanelLandscapeMinCellHeightDps += p.twoPanelLandscapeMinCellHeightDps;
836 twoPanelLandscapeMinCellWidthDps += p.twoPanelLandscapeMinCellWidthDps;
Jon Mirandae126d722021-02-25 10:45:20 -0500837 borderSpacing += p.borderSpacing;
sfufa@google.comde013292021-09-21 18:22:44 -0700838 allAppsCellSpacing += p.allAppsCellSpacing;
Sunny Goyal415f1732018-11-29 10:33:47 -0800839 return this;
840 }
841 }
Sunny Goyalae190ff2020-04-14 00:19:01 +0000842}