blob: 92cc9e82b17377dda3f4189f24f77466c74896b0 [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;
Thales Limad90faab2021-09-21 17:18:47 +0100121 public float horizontalMargin;
122 public float twoPanelLandscapeHorizontalMargin;
123 public float twoPanelPortraitHorizontalMargin;
Jon Mirandae126d722021-02-25 10:45:20 -0500124
Sunny Goyal5bc18462019-01-07 15:13:39 -0800125 private SparseArray<TypedValue> mExtraAttrs;
126
Hyunyoung Song35c3c7f2015-05-28 15:33:40 -0700127 /**
128 * Number of icons inside the hotseat area.
129 */
Tony Wickhamb87f3cd2021-04-07 15:02:37 -0700130 protected int numShownHotseatIcons;
131
132 /**
133 * Number of icons inside the hotseat area that is stored in the database. This is greater than
134 * or equal to numnShownHotseatIcons, allowing for a seamless transition between two hotseat
135 * sizes that share the same DB.
136 */
137 public int numDatabaseHotseatIcons;
Adam Cohen27824492017-09-22 17:10:55 -0700138
Jon Miranda6f7e9702019-09-16 14:44:14 -0700139 /**
140 * Number of columns in the all apps list.
141 */
142 public int numAllAppsColumns;
Sunny Goyal19ff7282021-04-22 10:12:54 -0700143 public int numDatabaseAllAppsColumns;
Jon Miranda6f7e9702019-09-16 14:44:14 -0700144
Jon Mirandae126d722021-02-25 10:45:20 -0500145 /**
146 * Do not query directly. see {@link DeviceProfile#isScalableGrid}.
147 */
148 protected boolean isScalable;
Jon Mirandac9e69fa2021-03-22 17:13:34 -0400149 public int devicePaddingId;
Jon Mirandae126d722021-02-25 10:45:20 -0500150
Tracy Zhou7df93d22020-01-27 13:44:06 -0800151 public String dbFile;
Sunny Goyal415f1732018-11-29 10:33:47 -0800152 public int defaultLayoutId;
Adam Cohen27824492017-09-22 17:10:55 -0700153 int demoModeLayoutId;
Adam Cohen2e6da152015-05-06 11:42:25 -0700154
Pinyao Ting9cd63c92021-06-16 17:51:39 +0000155 /**
156 * An immutable list of supported profiles.
157 */
158 public List<DeviceProfile> supportedProfiles = Collections.EMPTY_LIST;
Sunny Goyalc6205602015-05-21 20:46:33 -0700159
sfufa@google.comde013292021-09-21 18:22:44 -0700160 @Nullable
161 public DevicePaddings devicePaddings;
Jon Miranda228877d2021-02-09 11:05:00 -0500162
Sunny Goyal6f866092016-03-17 17:04:15 -0700163 public Point defaultWallpaperSize;
Sunny Goyal58fa4b62019-03-22 16:23:25 -0700164 public Rect defaultWidgetPadding;
Sunny Goyal6f866092016-03-17 17:04:15 -0700165
Sunny Goyal87dc48b2018-10-12 11:42:33 -0700166 private final ArrayList<OnIDPChangeListener> mChangeListeners = new ArrayList<>();
Sunny Goyal87dc48b2018-10-12 11:42:33 -0700167
Sunny Goyalf633ef52018-03-13 09:57:05 -0700168 @VisibleForTesting
sfufa@google.comde013292021-09-21 18:22:44 -0700169 public InvariantDeviceProfile() {
170 }
Adam Cohen2e6da152015-05-06 11:42:25 -0700171
Sunny Goyalbbf01842015-10-08 07:41:15 -0700172 @TargetApi(23)
Sunny Goyald0e360a2018-06-29 14:40:18 -0700173 private InvariantDeviceProfile(Context context) {
Sunny Goyal0e7a3382020-04-13 17:15:05 -0700174 String gridName = getCurrentGridName(context);
175 String newGridName = initGrid(context, gridName);
176 if (!newGridName.equals(gridName)) {
177 Utilities.getPrefs(context).edit().putString(KEY_IDP_GRID_NAME, newGridName).apply();
178 }
Sunny Goyal68031ca2021-08-02 12:23:44 -0700179 new DeviceGridState(this).writeToPrefs(context);
Sunny Goyal0e7a3382020-04-13 17:15:05 -0700180
Tracy Zhouc8beebf2021-09-23 10:18:11 -0700181 DisplayController.INSTANCE.get(context).setPriorityListener(
Alex Chaufd6d9422021-04-22 19:10:21 +0100182 (displayContext, info, flags) -> {
Sunny Goyal19ff7282021-04-22 10:12:54 -0700183 if ((flags & (CHANGE_DENSITY | CHANGE_SUPPORTED_BOUNDS)) != 0) {
Alex Chaufd6d9422021-04-22 19:10:21 +0100184 onConfigChanged(displayContext);
Sunny Goyal35c7b192021-04-20 16:51:10 -0700185 }
186 });
Sunny Goyal87dc48b2018-10-12 11:42:33 -0700187 }
188
Hyunyoung Songe11eb472019-03-19 15:05:21 -0700189 /**
190 * This constructor should NOT have any monitors by design.
191 */
Sunny Goyaleff44f32019-01-09 17:29:49 -0800192 public InvariantDeviceProfile(Context context, String gridName) {
193 String newName = initGrid(context, gridName);
194 if (newName == null || !newName.equals(gridName)) {
195 throw new IllegalArgumentException("Unknown grid name");
196 }
197 }
198
Sunny Goyal0e7a3382020-04-13 17:15:05 -0700199 /**
Sunny Goyal9c2b9602020-01-07 13:07:55 -0800200 * This constructor should NOT have any monitors by design.
201 */
202 public InvariantDeviceProfile(Context context, Display display) {
Sunny Goyal0e7a3382020-04-13 17:15:05 -0700203 // Ensure that the main device profile is initialized
Alex Chaufd6d9422021-04-22 19:10:21 +0100204 INSTANCE.get(context);
Sunny Goyal0e7a3382020-04-13 17:15:05 -0700205 String gridName = getCurrentGridName(context);
206
207 // Get the display info based on default display and interpolate it to existing display
208 DisplayOption defaultDisplayOption = invDistWeightedInterpolate(
Sunny Goyal35c7b192021-04-20 16:51:10 -0700209 DisplayController.INSTANCE.get(context).getInfo(),
Sunny Goyal19ff7282021-04-22 10:12:54 -0700210 getPredefinedDeviceProfiles(context, gridName, false), false);
Sunny Goyal0e7a3382020-04-13 17:15:05 -0700211
Alex Chau417bd722021-01-26 15:22:18 +0000212 Info myInfo = new Info(context, display);
Sunny Goyal0e7a3382020-04-13 17:15:05 -0700213 DisplayOption myDisplayOption = invDistWeightedInterpolate(
Sunny Goyal19ff7282021-04-22 10:12:54 -0700214 myInfo, getPredefinedDeviceProfiles(context, gridName, false), false);
Sunny Goyal0e7a3382020-04-13 17:15:05 -0700215
216 DisplayOption result = new DisplayOption(defaultDisplayOption.grid)
217 .add(myDisplayOption);
Sunny Goyal6e6f7992021-08-24 16:23:29 -0700218 result.iconSizes[DisplayOption.INDEX_DEFAULT] =
219 defaultDisplayOption.iconSizes[DisplayOption.INDEX_DEFAULT];
220 for (int i = 1; i < DisplayOption.COUNT_TOTAL; i++) {
221 result.iconSizes[i] = Math.min(
222 defaultDisplayOption.iconSizes[i], myDisplayOption.iconSizes[i]);
Alex Chau7c439722021-03-24 11:32:44 +0000223 }
Sunny Goyal6e6f7992021-08-24 16:23:29 -0700224
Jon Mirandae126d722021-02-25 10:45:20 -0500225 result.minCellHeight = defaultDisplayOption.minCellHeight;
226 result.minCellWidth = defaultDisplayOption.minCellWidth;
227 result.borderSpacing = defaultDisplayOption.borderSpacing;
sfufa@google.comde013292021-09-21 18:22:44 -0700228 result.allAppsCellSpacing = defaultDisplayOption.allAppsCellSpacing;
Jon Miranda228877d2021-02-09 11:05:00 -0500229
Sunny Goyal19ff7282021-04-22 10:12:54 -0700230 initGrid(context, myInfo, result, false);
Sunny Goyal9c2b9602020-01-07 13:07:55 -0800231 }
232
Tracy Zhou42255d22020-03-13 00:38:11 -0700233 public static String getCurrentGridName(Context context) {
Tracy Zhouc6060e62020-04-27 13:05:34 -0700234 return Utilities.isGridOptionsEnabled(context)
235 ? Utilities.getPrefs(context).getString(KEY_IDP_GRID_NAME, null) : null;
Tracy Zhou42255d22020-03-13 00:38:11 -0700236 }
237
Sunny Goyaleff44f32019-01-09 17:29:49 -0800238 private String initGrid(Context context, String gridName) {
Sunny Goyal35c7b192021-04-20 16:51:10 -0700239 Info displayInfo = DisplayController.INSTANCE.get(context).getInfo();
Pat Manning67094bf2021-05-26 12:38:13 +0000240 // Each screen has two profiles (portrait/landscape), so devices with four or more
241 // supported profiles implies two or more internal displays.
242 boolean isSplitDisplay =
243 displayInfo.supportedBounds.size() >= 4 && ENABLE_TWO_PANEL_HOME.get();
Sunny Goyal19ff7282021-04-22 10:12:54 -0700244
245 ArrayList<DisplayOption> allOptions =
246 getPredefinedDeviceProfiles(context, gridName, isSplitDisplay);
247 DisplayOption displayOption =
248 invDistWeightedInterpolate(displayInfo, allOptions, isSplitDisplay);
249 initGrid(context, displayInfo, displayOption, isSplitDisplay);
Sunny Goyal0e7a3382020-04-13 17:15:05 -0700250 return displayOption.grid.name;
Sunny Goyal9c2b9602020-01-07 13:07:55 -0800251 }
Adam Cohen2e6da152015-05-06 11:42:25 -0700252
Sunny Goyal0e7a3382020-04-13 17:15:05 -0700253 private void initGrid(
Sunny Goyal19ff7282021-04-22 10:12:54 -0700254 Context context, Info displayInfo, DisplayOption displayOption,
255 boolean isSplitDisplay) {
Sunny Goyal35c7b192021-04-20 16:51:10 -0700256 DisplayMetrics metrics = context.getResources().getDisplayMetrics();
Sunny Goyal0e7a3382020-04-13 17:15:05 -0700257 GridOption closestProfile = displayOption.grid;
Sunny Goyalae190ff2020-04-14 00:19:01 +0000258 numRows = closestProfile.numRows;
259 numColumns = closestProfile.numColumns;
Sunny Goyalae190ff2020-04-14 00:19:01 +0000260 dbFile = closestProfile.dbFile;
261 defaultLayoutId = closestProfile.defaultLayoutId;
262 demoModeLayoutId = closestProfile.demoModeLayoutId;
263 numFolderRows = closestProfile.numFolderRows;
264 numFolderColumns = closestProfile.numFolderColumns;
Jon Mirandae126d722021-02-25 10:45:20 -0500265 isScalable = closestProfile.isScalable;
Jon Mirandac9e69fa2021-03-22 17:13:34 -0400266 devicePaddingId = closestProfile.devicePaddingId;
Thales Lima7ec83822021-08-05 13:32:10 +0100267 this.isSplitDisplay = isSplitDisplay;
Sunny Goyalae190ff2020-04-14 00:19:01 +0000268
269 mExtraAttrs = closestProfile.extraAttrs;
270
Sunny Goyal6e6f7992021-08-24 16:23:29 -0700271 iconSize = displayOption.iconSizes[DisplayOption.INDEX_DEFAULT];
272 landscapeIconSize = displayOption.iconSizes[DisplayOption.INDEX_LANDSCAPE];
273 twoPanelPortraitIconSize = displayOption.iconSizes[DisplayOption.INDEX_TWO_PANEL_PORTRAIT];
274 twoPanelLandscapeIconSize =
275 displayOption.iconSizes[DisplayOption.INDEX_TWO_PANEL_LANDSCAPE];
Sunny Goyal35c7b192021-04-20 16:51:10 -0700276 iconBitmapSize = ResourceUtils.pxFromDp(iconSize, metrics);
Sunny Goyalae190ff2020-04-14 00:19:01 +0000277 fillResIconDpi = getLauncherIconDensity(iconBitmapSize);
278
Sunny Goyal6e6f7992021-08-24 16:23:29 -0700279 iconTextSize = displayOption.textSizes[DisplayOption.INDEX_DEFAULT];
280 landscapeIconTextSize = displayOption.textSizes[DisplayOption.INDEX_LANDSCAPE];
281 twoPanelPortraitIconTextSize =
282 displayOption.textSizes[DisplayOption.INDEX_TWO_PANEL_PORTRAIT];
283 twoPanelLandscapeIconTextSize =
284 displayOption.textSizes[DisplayOption.INDEX_TWO_PANEL_LANDSCAPE];
285
Jon Mirandae126d722021-02-25 10:45:20 -0500286 minCellHeight = displayOption.minCellHeight;
287 minCellWidth = displayOption.minCellWidth;
Thales Limad1df5fc2021-09-03 18:37:19 +0100288 twoPanelPortraitMinCellHeightDps = displayOption.twoPanelPortraitMinCellHeightDps;
289 twoPanelPortraitMinCellWidthDps = displayOption.twoPanelPortraitMinCellWidthDps;
290 twoPanelLandscapeMinCellHeightDps = displayOption.twoPanelLandscapeMinCellHeightDps;
291 twoPanelLandscapeMinCellWidthDps = displayOption.twoPanelLandscapeMinCellWidthDps;
Jon Mirandae126d722021-02-25 10:45:20 -0500292 borderSpacing = displayOption.borderSpacing;
sfufa@google.comde013292021-09-21 18:22:44 -0700293 allAppsCellSpacing = displayOption.allAppsCellSpacing;
Sunny Goyal19ff7282021-04-22 10:12:54 -0700294
Thales Limad90faab2021-09-21 17:18:47 +0100295 horizontalMargin = displayOption.horizontalMargin;
296 twoPanelLandscapeHorizontalMargin = displayOption.twoPanelLandscapeHorizontalMargin;
297 twoPanelPortraitHorizontalMargin = displayOption.twoPanelPortraitHorizontalMargin;
298
Sunny Goyal19ff7282021-04-22 10:12:54 -0700299 numShownHotseatIcons = closestProfile.numHotseatIcons;
300 numDatabaseHotseatIcons = isSplitDisplay
301 ? closestProfile.numDatabaseHotseatIcons : closestProfile.numHotseatIcons;
302
303 numAllAppsColumns = closestProfile.numAllAppsColumns;
304 numDatabaseAllAppsColumns = isSplitDisplay
305 ? closestProfile.numDatabaseAllAppsColumns : closestProfile.numAllAppsColumns;
Jon Mirandae126d722021-02-25 10:45:20 -0500306
Tracy Zhouc6060e62020-04-27 13:05:34 -0700307 if (Utilities.isGridOptionsEnabled(context)) {
Sunny Goyal6e6f7992021-08-24 16:23:29 -0700308 allAppsIconSize = displayOption.iconSizes[DisplayOption.INDEX_ALL_APPS];
309 allAppsIconTextSize = displayOption.textSizes[DisplayOption.INDEX_ALL_APPS];
Sunny Goyalae190ff2020-04-14 00:19:01 +0000310 } else {
311 allAppsIconSize = iconSize;
312 allAppsIconTextSize = iconTextSize;
313 }
314
Jon Mirandac9e69fa2021-03-22 17:13:34 -0400315 if (devicePaddingId != 0) {
316 devicePaddings = new DevicePaddings(context, devicePaddingId);
317 }
318
Sunny Goyalae190ff2020-04-14 00:19:01 +0000319 // If the partner customization apk contains any grid overrides, apply them
320 // Supported overrides: numRows, numColumns, iconSize
Sunny Goyal35c7b192021-04-20 16:51:10 -0700321 applyPartnerDeviceProfileOverrides(context, metrics);
Sunny Goyalc6205602015-05-21 20:46:33 -0700322
Pinyao Ting9cd63c92021-06-16 17:51:39 +0000323 final List<DeviceProfile> localSupportedProfiles = new ArrayList<>();
Sunny Goyal19ff7282021-04-22 10:12:54 -0700324 defaultWallpaperSize = new Point(displayInfo.currentSize);
325 for (WindowBounds bounds : displayInfo.supportedBounds) {
Pinyao Ting9cd63c92021-06-16 17:51:39 +0000326 localSupportedProfiles.add(new DeviceProfile.Builder(context, this, displayInfo)
Sunny Goyal19ff7282021-04-22 10:12:54 -0700327 .setUseTwoPanels(isSplitDisplay)
328 .setWindowBounds(bounds).build());
Sunny Goyalc6205602015-05-21 20:46:33 -0700329
Sunny Goyal19ff7282021-04-22 10:12:54 -0700330 // Wallpaper size should be the maximum of the all possible sizes Launcher expects
331 int displayWidth = bounds.bounds.width();
332 int displayHeight = bounds.bounds.height();
333 defaultWallpaperSize.y = Math.max(defaultWallpaperSize.y, displayHeight);
Sunny Goyal0e7a3382020-04-13 17:15:05 -0700334
Sunny Goyal19ff7282021-04-22 10:12:54 -0700335 // We need to ensure that there is enough extra space in the wallpaper
336 // for the intended parallax effects
337 float parallaxFactor =
338 dpiFromPx(Math.min(displayWidth, displayHeight), displayInfo.densityDpi) < 720
339 ? 2
340 : wallpaperTravelToScreenWidthRatio(displayWidth, displayHeight);
341 defaultWallpaperSize.x =
342 Math.max(defaultWallpaperSize.x, Math.round(parallaxFactor * displayWidth));
Sunny Goyal6f866092016-03-17 17:04:15 -0700343 }
Pinyao Ting9cd63c92021-06-16 17:51:39 +0000344 supportedProfiles = Collections.unmodifiableList(localSupportedProfiles);
Sunny Goyal58fa4b62019-03-22 16:23:25 -0700345
346 ComponentName cn = new ComponentName(context.getPackageName(), getClass().getName());
347 defaultWidgetPadding = AppWidgetHostView.getDefaultPaddingForWidget(context, cn, null);
Adam Cohen2e6da152015-05-06 11:42:25 -0700348 }
349
Sunny Goyal5bc18462019-01-07 15:13:39 -0800350 @Nullable
351 public TypedValue getAttrValue(int attr) {
352 return mExtraAttrs == null ? null : mExtraAttrs.get(attr);
353 }
354
Sunny Goyal87dc48b2018-10-12 11:42:33 -0700355 public void addOnChangeListener(OnIDPChangeListener listener) {
356 mChangeListeners.add(listener);
357 }
358
Hyunyoung Songb4d1ca42019-01-08 17:15:16 -0800359 public void removeOnChangeListener(OnIDPChangeListener listener) {
360 mChangeListeners.remove(listener);
361 }
362
Hyunyoung Songc55a3502018-12-04 15:43:16 -0800363
Sunny Goyal7d892ff2019-01-11 15:08:44 -0800364 public void setCurrentGrid(Context context, String gridName) {
365 Context appContext = context.getApplicationContext();
366 Utilities.getPrefs(appContext).edit().putString(KEY_IDP_GRID_NAME, gridName).apply();
Sunny Goyal6fe3eec2019-08-15 14:53:41 -0700367 MAIN_EXECUTOR.execute(() -> onConfigChanged(appContext));
Sunny Goyal7d892ff2019-01-11 15:08:44 -0800368 }
369
Sunny Goyal6e6f7992021-08-24 16:23:29 -0700370 private Object[] toModelState() {
sfufa@google.comde013292021-09-21 18:22:44 -0700371 return new Object[]{
Sunny Goyal6e6f7992021-08-24 16:23:29 -0700372 numColumns, numRows, numDatabaseHotseatIcons, iconBitmapSize, fillResIconDpi,
373 numDatabaseAllAppsColumns, dbFile};
374 }
375
Sunny Goyal87dc48b2018-10-12 11:42:33 -0700376 private void onConfigChanged(Context context) {
Sunny Goyal6e6f7992021-08-24 16:23:29 -0700377 Object[] oldState = toModelState();
378
Sunny Goyal87dc48b2018-10-12 11:42:33 -0700379 // Re-init grid
Tracy Zhouc6060e62020-04-27 13:05:34 -0700380 String gridName = getCurrentGridName(context);
Jon Miranda6f7e9702019-09-16 14:44:14 -0700381 initGrid(context, gridName);
Sunny Goyal87dc48b2018-10-12 11:42:33 -0700382
Sunny Goyal6e6f7992021-08-24 16:23:29 -0700383 boolean modelPropsChanged = !Arrays.equals(oldState, toModelState());
Sunny Goyal87dc48b2018-10-12 11:42:33 -0700384 for (OnIDPChangeListener listener : mChangeListeners) {
Sunny Goyal6e6f7992021-08-24 16:23:29 -0700385 listener.onIdpChanged(modelPropsChanged);
Sunny Goyal87dc48b2018-10-12 11:42:33 -0700386 }
387 }
388
Sunny Goyal19ff7282021-04-22 10:12:54 -0700389 private static ArrayList<DisplayOption> getPredefinedDeviceProfiles(
390 Context context, String gridName, boolean isSplitDisplay) {
Sunny Goyal415f1732018-11-29 10:33:47 -0800391 ArrayList<DisplayOption> profiles = new ArrayList<>();
Sunny Goyal819e1932016-07-07 16:43:58 -0700392 try (XmlResourceParser parser = context.getResources().getXml(R.xml.device_profiles)) {
393 final int depth = parser.getDepth();
394 int type;
Sunny Goyal819e1932016-07-07 16:43:58 -0700395 while (((type = parser.next()) != XmlPullParser.END_TAG ||
396 parser.getDepth() > depth) && type != XmlPullParser.END_DOCUMENT) {
Sunny Goyal7d892ff2019-01-11 15:08:44 -0800397 if ((type == XmlPullParser.START_TAG)
398 && GridOption.TAG_NAME.equals(parser.getName())) {
Sunny Goyal415f1732018-11-29 10:33:47 -0800399
Thales Lima7ec83822021-08-05 13:32:10 +0100400 GridOption gridOption =
401 new GridOption(context, Xml.asAttributeSet(parser), isSplitDisplay);
402 if (gridOption.isEnabled) {
403 final int displayDepth = parser.getDepth();
404 while (((type = parser.next()) != XmlPullParser.END_TAG
405 || parser.getDepth() > displayDepth)
406 && type != XmlPullParser.END_DOCUMENT) {
407 if ((type == XmlPullParser.START_TAG) && "display-option".equals(
408 parser.getName())) {
409 profiles.add(new DisplayOption(gridOption, context,
410 Xml.asAttributeSet(parser),
411 isSplitDisplay ? DEFAULT_SPLIT_DISPLAY : DEFAULT_TRUE));
412 }
Sunny Goyal415f1732018-11-29 10:33:47 -0800413 }
414 }
Sunny Goyal819e1932016-07-07 16:43:58 -0700415 }
416 }
sfufa@google.comde013292021-09-21 18:22:44 -0700417 } catch (IOException | XmlPullParserException e) {
Sunny Goyal819e1932016-07-07 16:43:58 -0700418 throw new RuntimeException(e);
419 }
Sunny Goyal415f1732018-11-29 10:33:47 -0800420
Sunny Goyalae190ff2020-04-14 00:19:01 +0000421 ArrayList<DisplayOption> filteredProfiles = new ArrayList<>();
Sunny Goyal415f1732018-11-29 10:33:47 -0800422 if (!TextUtils.isEmpty(gridName)) {
423 for (DisplayOption option : profiles) {
Thales Lima7ec83822021-08-05 13:32:10 +0100424 if (gridName.equals(option.grid.name) && option.grid.isEnabled) {
Sunny Goyalae190ff2020-04-14 00:19:01 +0000425 filteredProfiles.add(option);
Sunny Goyal415f1732018-11-29 10:33:47 -0800426 }
427 }
428 }
Sunny Goyalae190ff2020-04-14 00:19:01 +0000429 if (filteredProfiles.isEmpty()) {
430 // No grid found, use the default options
431 for (DisplayOption option : profiles) {
432 if (option.canBeDefault) {
433 filteredProfiles.add(option);
434 }
Sunny Goyal415f1732018-11-29 10:33:47 -0800435 }
436 }
Sunny Goyalae190ff2020-04-14 00:19:01 +0000437 if (filteredProfiles.isEmpty()) {
438 throw new RuntimeException("No display option with canBeDefault=true");
439 }
440 return filteredProfiles;
Hyunyoung Song35c3c7f2015-05-28 15:33:40 -0700441 }
442
Thales Lima7ec83822021-08-05 13:32:10 +0100443 /**
444 * @return all the grid options that can be shown on the device
445 */
446 public List<GridOption> parseAllGridOptions(Context context) {
447 List<GridOption> result = new ArrayList<>();
448 try (XmlResourceParser parser = context.getResources().getXml(R.xml.device_profiles)) {
449 final int depth = parser.getDepth();
450 int type;
451 while (((type = parser.next()) != XmlPullParser.END_TAG
452 || parser.getDepth() > depth) && type != XmlPullParser.END_DOCUMENT) {
453 if ((type == XmlPullParser.START_TAG)
454 && GridOption.TAG_NAME.equals(parser.getName())) {
455 GridOption option =
456 new GridOption(context, Xml.asAttributeSet(parser), isSplitDisplay);
457 if (option.isEnabled) {
458 result.add(option);
459 }
460 }
461 }
462 } catch (IOException | XmlPullParserException e) {
463 Log.e(TAG, "Error parsing device profile", e);
464 return Collections.emptyList();
465 }
466 return result;
467 }
468
Sunny Goyal53d7ee42015-05-22 12:25:45 -0700469 private int getLauncherIconDensity(int requiredSize) {
470 // Densities typically defined by an app.
sfufa@google.comde013292021-09-21 18:22:44 -0700471 int[] densityBuckets = new int[]{
Sunny Goyal53d7ee42015-05-22 12:25:45 -0700472 DisplayMetrics.DENSITY_LOW,
473 DisplayMetrics.DENSITY_MEDIUM,
474 DisplayMetrics.DENSITY_TV,
475 DisplayMetrics.DENSITY_HIGH,
476 DisplayMetrics.DENSITY_XHIGH,
477 DisplayMetrics.DENSITY_XXHIGH,
478 DisplayMetrics.DENSITY_XXXHIGH
479 };
480
481 int density = DisplayMetrics.DENSITY_XXXHIGH;
482 for (int i = densityBuckets.length - 1; i >= 0; i--) {
483 float expectedSize = ICON_SIZE_DEFINED_IN_APP_DP * densityBuckets[i]
484 / DisplayMetrics.DENSITY_DEFAULT;
485 if (expectedSize >= requiredSize) {
486 density = densityBuckets[i];
487 }
488 }
489
490 return density;
491 }
Hyunyoung Song35c3c7f2015-05-28 15:33:40 -0700492
Adam Cohen2e6da152015-05-06 11:42:25 -0700493 /**
494 * Apply any Partner customization grid overrides.
495 *
496 * Currently we support: all apps row / column count.
497 */
Hyunyoung Song35c3c7f2015-05-28 15:33:40 -0700498 private void applyPartnerDeviceProfileOverrides(Context context, DisplayMetrics dm) {
499 Partner p = Partner.get(context.getPackageManager());
Adam Cohen2e6da152015-05-06 11:42:25 -0700500 if (p != null) {
501 p.applyInvariantDeviceProfileOverrides(this, dm);
502 }
503 }
504
Sunny Goyal415f1732018-11-29 10:33:47 -0800505 private static float dist(float x0, float y0, float x1, float y1) {
Hyunyoung Song35c3c7f2015-05-28 15:33:40 -0700506 return (float) Math.hypot(x1 - x0, y1 - y0);
Adam Cohen2e6da152015-05-06 11:42:25 -0700507 }
508
Sunny Goyal19ff7282021-04-22 10:12:54 -0700509 private static DisplayOption invDistWeightedInterpolate(
510 Info displayInfo, ArrayList<DisplayOption> points, boolean isSplitDisplay) {
511 int minWidthPx = Integer.MAX_VALUE;
512 int minHeightPx = Integer.MAX_VALUE;
513 for (WindowBounds bounds : displayInfo.supportedBounds) {
514 boolean isTablet = displayInfo.isTablet(bounds);
515 if (isTablet && isSplitDisplay) {
516 // For split displays, take half width per page
517 minWidthPx = Math.min(minWidthPx, bounds.availableSize.x / 2);
518 minHeightPx = Math.min(minHeightPx, bounds.availableSize.y);
Sunny Goyal0e7a3382020-04-13 17:15:05 -0700519
Sunny Goyal19ff7282021-04-22 10:12:54 -0700520 } else if (!isTablet && bounds.isLandscape()) {
521 // We will use transposed layout in this case
522 minWidthPx = Math.min(minWidthPx, bounds.availableSize.y);
523 minHeightPx = Math.min(minHeightPx, bounds.availableSize.x);
524 } else {
525 minWidthPx = Math.min(minWidthPx, bounds.availableSize.x);
526 minHeightPx = Math.min(minHeightPx, bounds.availableSize.y);
527 }
528 }
529
530 float width = dpiFromPx(minWidthPx, displayInfo.densityDpi);
531 float height = dpiFromPx(minHeightPx, displayInfo.densityDpi);
Sunny Goyal0e7a3382020-04-13 17:15:05 -0700532
533 // Sort the profiles based on the closeness to the device size
534 Collections.sort(points, (a, b) ->
535 Float.compare(dist(width, height, a.minWidthDps, a.minHeightDps),
536 dist(width, height, b.minWidthDps, b.minHeightDps)));
537
Sunny Goyal6e6f7992021-08-24 16:23:29 -0700538 DisplayOption closestPoint = points.get(0);
539 GridOption closestOption = closestPoint.grid;
Sunny Goyal0e7a3382020-04-13 17:15:05 -0700540 float weights = 0;
541
Sunny Goyal6e6f7992021-08-24 16:23:29 -0700542 if (dist(width, height, closestPoint.minWidthDps, closestPoint.minHeightDps) == 0) {
543 return closestPoint;
Sunny Goyal0e7a3382020-04-13 17:15:05 -0700544 }
545
546 DisplayOption out = new DisplayOption(closestOption);
547 for (int i = 0; i < points.size() && i < KNEARESTNEIGHBOR; ++i) {
Sunny Goyal6e6f7992021-08-24 16:23:29 -0700548 DisplayOption p = points.get(i);
Sunny Goyal0e7a3382020-04-13 17:15:05 -0700549 float w = weight(width, height, p.minWidthDps, p.minHeightDps, WEIGHT_POWER);
550 weights += w;
551 out.add(new DisplayOption().add(p).multiply(w));
552 }
Sunny Goyal6e6f7992021-08-24 16:23:29 -0700553 out.multiply(1.0f / weights);
554
555 // Since the bitmaps are persisted, ensure that the default bitmap size is same as
556 // predefined size to avoid cache invalidation
557 out.iconSizes[DisplayOption.INDEX_DEFAULT] =
558 closestPoint.iconSizes[DisplayOption.INDEX_DEFAULT];
559 for (int i = DisplayOption.INDEX_DEFAULT + 1; i < DisplayOption.COUNT_TOTAL; i++) {
560 out.iconSizes[i] = Math.min(out.iconSizes[i],
561 out.iconSizes[DisplayOption.INDEX_DEFAULT]);
562 }
563
564 return out;
Sunny Goyal0e7a3382020-04-13 17:15:05 -0700565 }
566
Sunny Goyal27835952017-01-13 12:15:53 -0800567 public DeviceProfile getDeviceProfile(Context context) {
Sunny Goyal19ff7282021-04-22 10:12:54 -0700568 Resources res = context.getResources();
569 Configuration config = context.getResources().getConfiguration();
570
Sunny Goyal3e9a29c2021-09-07 15:53:09 -0400571 float screenWidth = config.screenWidthDp * res.getDisplayMetrics().density;
572 float screenHeight = config.screenHeightDp * res.getDisplayMetrics().density;
573 return getBestMatch(screenWidth, screenHeight);
574 }
Sunny Goyal19ff7282021-04-22 10:12:54 -0700575
Sunny Goyal3e9a29c2021-09-07 15:53:09 -0400576 public DeviceProfile getBestMatch(float screenWidth, float screenHeight) {
Sunny Goyal19ff7282021-04-22 10:12:54 -0700577 DeviceProfile bestMatch = supportedProfiles.get(0);
578 float minDiff = Float.MAX_VALUE;
579
580 for (DeviceProfile profile : supportedProfiles) {
Sunny Goyal3e9a29c2021-09-07 15:53:09 -0400581 float diff = Math.abs(profile.widthPx - screenWidth)
582 + Math.abs(profile.heightPx - screenHeight);
Sunny Goyal19ff7282021-04-22 10:12:54 -0700583 if (diff < minDiff) {
584 minDiff = diff;
585 bestMatch = profile;
586 }
587 }
588 return bestMatch;
Sunny Goyal27835952017-01-13 12:15:53 -0800589 }
590
Sunny Goyal415f1732018-11-29 10:33:47 -0800591 private static float weight(float x0, float y0, float x1, float y1, float pow) {
Hyunyoung Song35c3c7f2015-05-28 15:33:40 -0700592 float d = dist(x0, y0, x1, y1);
593 if (Float.compare(d, 0f) == 0) {
594 return Float.POSITIVE_INFINITY;
595 }
596 return (float) (WEIGHT_EFFICIENT / Math.pow(d, pow));
597 }
Sunny Goyal6f866092016-03-17 17:04:15 -0700598
599 /**
600 * As a ratio of screen height, the total distance we want the parallax effect to span
601 * horizontally
602 */
603 private static float wallpaperTravelToScreenWidthRatio(int width, int height) {
604 float aspectRatio = width / (float) height;
605
606 // At an aspect ratio of 16/10, the wallpaper parallax effect should span 1.5 * screen width
607 // At an aspect ratio of 10/16, the wallpaper parallax effect should span 1.2 * screen width
608 // We will use these two data points to extrapolate how much the wallpaper parallax effect
609 // to span (ie travel) at any aspect ratio:
610
sfufa@google.comde013292021-09-21 18:22:44 -0700611 final float ASPECT_RATIO_LANDSCAPE = 16 / 10f;
612 final float ASPECT_RATIO_PORTRAIT = 10 / 16f;
Sunny Goyal6f866092016-03-17 17:04:15 -0700613 final float WALLPAPER_WIDTH_TO_SCREEN_RATIO_LANDSCAPE = 1.5f;
614 final float WALLPAPER_WIDTH_TO_SCREEN_RATIO_PORTRAIT = 1.2f;
615
616 // To find out the desired width at different aspect ratios, we use the following two
617 // formulas, where the coefficient on x is the aspect ratio (width/height):
618 // (16/10)x + y = 1.5
619 // (10/16)x + y = 1.2
620 // We solve for x and y and end up with a final formula:
621 final float x =
sfufa@google.comde013292021-09-21 18:22:44 -0700622 (WALLPAPER_WIDTH_TO_SCREEN_RATIO_LANDSCAPE
623 - WALLPAPER_WIDTH_TO_SCREEN_RATIO_PORTRAIT) /
Sunny Goyal6f866092016-03-17 17:04:15 -0700624 (ASPECT_RATIO_LANDSCAPE - ASPECT_RATIO_PORTRAIT);
625 final float y = WALLPAPER_WIDTH_TO_SCREEN_RATIO_PORTRAIT - x * ASPECT_RATIO_PORTRAIT;
626 return x * aspectRatio + y;
627 }
628
Sunny Goyal87dc48b2018-10-12 11:42:33 -0700629 public interface OnIDPChangeListener {
630
Sunny Goyalb47172b2021-05-03 19:59:51 -0700631 /**
632 * Called when the device provide changes
633 */
Sunny Goyal6e6f7992021-08-24 16:23:29 -0700634 void onIdpChanged(boolean modelPropertiesChanged);
Sunny Goyal87dc48b2018-10-12 11:42:33 -0700635 }
Sunny Goyal415f1732018-11-29 10:33:47 -0800636
637
Sunny Goyaleff44f32019-01-09 17:29:49 -0800638 public static final class GridOption {
Sunny Goyal415f1732018-11-29 10:33:47 -0800639
Sunny Goyal7d892ff2019-01-11 15:08:44 -0800640 public static final String TAG_NAME = "grid-option";
641
Sunny Goyaleff44f32019-01-09 17:29:49 -0800642 public final String name;
643 public final int numRows;
644 public final int numColumns;
Thales Lima7ec83822021-08-05 13:32:10 +0100645 public final boolean isEnabled;
Sunny Goyal415f1732018-11-29 10:33:47 -0800646
647 private final int numFolderRows;
648 private final int numFolderColumns;
649
Sunny Goyal19ff7282021-04-22 10:12:54 -0700650 private final int numAllAppsColumns;
651 private final int numDatabaseAllAppsColumns;
652 private final int numHotseatIcons;
Tony Wickhamb87f3cd2021-04-07 15:02:37 -0700653 private final int numDatabaseHotseatIcons;
Sunny Goyal415f1732018-11-29 10:33:47 -0800654
Tracy Zhou7df93d22020-01-27 13:44:06 -0800655 private final String dbFile;
Sunny Goyalae190ff2020-04-14 00:19:01 +0000656
Sunny Goyal415f1732018-11-29 10:33:47 -0800657 private final int defaultLayoutId;
658 private final int demoModeLayoutId;
659
Jon Mirandae126d722021-02-25 10:45:20 -0500660 private final boolean isScalable;
Jon Mirandac9e69fa2021-03-22 17:13:34 -0400661 private final int devicePaddingId;
Jon Mirandae126d722021-02-25 10:45:20 -0500662
Sunny Goyal5bc18462019-01-07 15:13:39 -0800663 private final SparseArray<TypedValue> extraAttrs;
664
Thales Lima7ec83822021-08-05 13:32:10 +0100665 public GridOption(Context context, AttributeSet attrs, boolean isSplitDisplay) {
Sunny Goyal415f1732018-11-29 10:33:47 -0800666 TypedArray a = context.obtainStyledAttributes(
667 attrs, R.styleable.GridDisplayOption);
668 name = a.getString(R.styleable.GridDisplayOption_name);
669 numRows = a.getInt(R.styleable.GridDisplayOption_numRows, 0);
670 numColumns = a.getInt(R.styleable.GridDisplayOption_numColumns, 0);
671
Tracy Zhou7df93d22020-01-27 13:44:06 -0800672 dbFile = a.getString(R.styleable.GridDisplayOption_dbFile);
Alex Chau1e448462021-08-13 21:48:35 +0100673 defaultLayoutId = a.getResourceId(isSplitDisplay && a.hasValue(
674 R.styleable.GridDisplayOption_defaultSplitDisplayLayoutId)
675 ? R.styleable.GridDisplayOption_defaultSplitDisplayLayoutId
676 : R.styleable.GridDisplayOption_defaultLayoutId, 0);
Sunny Goyal415f1732018-11-29 10:33:47 -0800677 demoModeLayoutId = a.getResourceId(
678 R.styleable.GridDisplayOption_demoModeLayoutId, defaultLayoutId);
Sunny Goyal19ff7282021-04-22 10:12:54 -0700679
680 numAllAppsColumns = a.getInt(
681 R.styleable.GridDisplayOption_numAllAppsColumns, numColumns);
682 numDatabaseAllAppsColumns = a.getInt(
683 R.styleable.GridDisplayOption_numExtendedAllAppsColumns, 2 * numAllAppsColumns);
684
685 numHotseatIcons = a.getInt(
Sunny Goyal415f1732018-11-29 10:33:47 -0800686 R.styleable.GridDisplayOption_numHotseatIcons, numColumns);
Sunny Goyal19ff7282021-04-22 10:12:54 -0700687 numDatabaseHotseatIcons = a.getInt(
688 R.styleable.GridDisplayOption_numExtendedHotseatIcons, 2 * numHotseatIcons);
689
Sunny Goyal415f1732018-11-29 10:33:47 -0800690 numFolderRows = a.getInt(
691 R.styleable.GridDisplayOption_numFolderRows, numRows);
692 numFolderColumns = a.getInt(
693 R.styleable.GridDisplayOption_numFolderColumns, numColumns);
Jon Miranda6f7e9702019-09-16 14:44:14 -0700694
Jon Mirandae126d722021-02-25 10:45:20 -0500695 isScalable = a.getBoolean(
696 R.styleable.GridDisplayOption_isScalable, false);
Jon Mirandac9e69fa2021-03-22 17:13:34 -0400697 devicePaddingId = a.getResourceId(
698 R.styleable.GridDisplayOption_devicePaddingId, 0);
Jon Mirandae126d722021-02-25 10:45:20 -0500699
Thales Lima7ec83822021-08-05 13:32:10 +0100700 final int enabledInt =
701 a.getInteger(R.styleable.GridDisplayOption_gridEnabled,
702 GRID_ENABLED_ALL_DISPLAYS);
703 isEnabled = enabledInt == GRID_ENABLED_ALL_DISPLAYS
704 || enabledInt == GRID_ENABLED_SINGLE_DISPLAY && !isSplitDisplay;
705
Sunny Goyal415f1732018-11-29 10:33:47 -0800706 a.recycle();
Sunny Goyal5bc18462019-01-07 15:13:39 -0800707 extraAttrs = Themes.createValueMap(context, attrs,
708 IntArray.wrap(R.styleable.GridDisplayOption));
Sunny Goyal415f1732018-11-29 10:33:47 -0800709 }
710 }
711
Sunny Goyal19ff7282021-04-22 10:12:54 -0700712 @VisibleForTesting
713 static final class DisplayOption {
714
Sunny Goyal6e6f7992021-08-24 16:23:29 -0700715 static final int INDEX_DEFAULT = 0;
716 static final int INDEX_LANDSCAPE = 1;
717 static final int INDEX_ALL_APPS = 2;
718 static final int INDEX_TWO_PANEL_PORTRAIT = 3;
719 static final int INDEX_TWO_PANEL_LANDSCAPE = 4;
720
721 static final int COUNT_TOTAL = 5;
722
Sunny Goyal19ff7282021-04-22 10:12:54 -0700723 public final GridOption grid;
Sunny Goyal415f1732018-11-29 10:33:47 -0800724
Sunny Goyal415f1732018-11-29 10:33:47 -0800725 private final float minWidthDps;
726 private final float minHeightDps;
727 private final boolean canBeDefault;
728
Jon Mirandae126d722021-02-25 10:45:20 -0500729 private float minCellHeight;
730 private float minCellWidth;
Thales Limad1df5fc2021-09-03 18:37:19 +0100731 private float twoPanelPortraitMinCellHeightDps;
732 private float twoPanelPortraitMinCellWidthDps;
733 private float twoPanelLandscapeMinCellHeightDps;
734 private float twoPanelLandscapeMinCellWidthDps;
sfufa@google.comde013292021-09-21 18:22:44 -0700735 private float allAppsCellSpacing;
Jon Mirandae126d722021-02-25 10:45:20 -0500736 private float borderSpacing;
737
Thales Limad90faab2021-09-21 17:18:47 +0100738 private float horizontalMargin;
739 private float twoPanelLandscapeHorizontalMargin;
740 private float twoPanelPortraitHorizontalMargin;
741
Sunny Goyal6e6f7992021-08-24 16:23:29 -0700742 private final float[] iconSizes = new float[COUNT_TOTAL];
743 private final float[] textSizes = new float[COUNT_TOTAL];
Sunny Goyal415f1732018-11-29 10:33:47 -0800744
Sunny Goyal19ff7282021-04-22 10:12:54 -0700745 DisplayOption(GridOption grid, Context context, AttributeSet attrs, int defaultFlagValue) {
Sunny Goyal415f1732018-11-29 10:33:47 -0800746 this.grid = grid;
747
748 TypedArray a = context.obtainStyledAttributes(
749 attrs, R.styleable.ProfileDisplayOption);
750
Sunny Goyal415f1732018-11-29 10:33:47 -0800751 minWidthDps = a.getFloat(R.styleable.ProfileDisplayOption_minWidthDps, 0);
752 minHeightDps = a.getFloat(R.styleable.ProfileDisplayOption_minHeightDps, 0);
Sunny Goyal19ff7282021-04-22 10:12:54 -0700753
754 canBeDefault = a.getInt(R.styleable.ProfileDisplayOption_canBeDefault, 0)
755 == defaultFlagValue;
Sunny Goyal415f1732018-11-29 10:33:47 -0800756
Jon Mirandae126d722021-02-25 10:45:20 -0500757 minCellHeight = a.getFloat(R.styleable.ProfileDisplayOption_minCellHeightDps, 0);
758 minCellWidth = a.getFloat(R.styleable.ProfileDisplayOption_minCellWidthDps, 0);
Thales Limad1df5fc2021-09-03 18:37:19 +0100759 twoPanelPortraitMinCellHeightDps = a.getFloat(
760 R.styleable.ProfileDisplayOption_twoPanelPortraitMinCellHeightDps,
761 minCellHeight);
762 twoPanelPortraitMinCellWidthDps = a.getFloat(
763 R.styleable.ProfileDisplayOption_twoPanelPortraitMinCellWidthDps, minCellWidth);
764 twoPanelLandscapeMinCellHeightDps = a.getFloat(
765 R.styleable.ProfileDisplayOption_twoPanelLandscapeMinCellHeightDps,
766 twoPanelPortraitMinCellHeightDps);
767 twoPanelLandscapeMinCellWidthDps = a.getFloat(
768 R.styleable.ProfileDisplayOption_twoPanelLandscapeMinCellWidthDps,
769 twoPanelPortraitMinCellWidthDps);
Jon Mirandae126d722021-02-25 10:45:20 -0500770 borderSpacing = a.getFloat(R.styleable.ProfileDisplayOption_borderSpacingDps, 0);
sfufa@google.comde013292021-09-21 18:22:44 -0700771 allAppsCellSpacing = a.getFloat(R.styleable.ProfileDisplayOption_allAppsCellSpacingDps,
772 borderSpacing);
Jon Mirandae126d722021-02-25 10:45:20 -0500773
Sunny Goyal6e6f7992021-08-24 16:23:29 -0700774 iconSizes[INDEX_DEFAULT] =
775 a.getFloat(R.styleable.ProfileDisplayOption_iconImageSize, 0);
776 iconSizes[INDEX_LANDSCAPE] =
777 a.getFloat(R.styleable.ProfileDisplayOption_landscapeIconSize,
778 iconSizes[INDEX_DEFAULT]);
779 iconSizes[INDEX_ALL_APPS] =
780 a.getFloat(R.styleable.ProfileDisplayOption_allAppsIconSize,
781 iconSizes[INDEX_DEFAULT]);
782 iconSizes[INDEX_TWO_PANEL_PORTRAIT] =
783 a.getFloat(R.styleable.ProfileDisplayOption_twoPanelPortraitIconSize,
784 iconSizes[INDEX_DEFAULT]);
785 iconSizes[INDEX_TWO_PANEL_LANDSCAPE] =
786 a.getFloat(R.styleable.ProfileDisplayOption_twoPanelLandscapeIconSize,
787 iconSizes[INDEX_LANDSCAPE]);
Jon Miranda6f7e9702019-09-16 14:44:14 -0700788
Sunny Goyal6e6f7992021-08-24 16:23:29 -0700789 textSizes[INDEX_DEFAULT] =
790 a.getFloat(R.styleable.ProfileDisplayOption_iconTextSize, 0);
791 textSizes[INDEX_LANDSCAPE] =
792 a.getFloat(R.styleable.ProfileDisplayOption_landscapeIconTextSize,
793 textSizes[INDEX_DEFAULT]);
794 textSizes[INDEX_ALL_APPS] =
795 a.getFloat(R.styleable.ProfileDisplayOption_allAppsIconTextSize,
796 textSizes[INDEX_DEFAULT]);
797 textSizes[INDEX_TWO_PANEL_PORTRAIT] =
798 a.getFloat(R.styleable.ProfileDisplayOption_twoPanelPortraitIconTextSize,
799 textSizes[INDEX_DEFAULT]);
800 textSizes[INDEX_TWO_PANEL_LANDSCAPE] =
801 a.getFloat(R.styleable.ProfileDisplayOption_twoPanelLandscapeIconTextSize,
802 textSizes[INDEX_LANDSCAPE]);
803
Thales Limad90faab2021-09-21 17:18:47 +0100804 horizontalMargin = a.getFloat(R.styleable.ProfileDisplayOption_horizontalMargin, 0);
805 twoPanelLandscapeHorizontalMargin = a.getFloat(
806 R.styleable.ProfileDisplayOption_twoPanelLandscapeHorizontalMargin,
807 horizontalMargin);
808 twoPanelPortraitHorizontalMargin = a.getFloat(
809 R.styleable.ProfileDisplayOption_twoPanelPortraitHorizontalMargin,
810 horizontalMargin);
811
Sunny Goyal415f1732018-11-29 10:33:47 -0800812 a.recycle();
813 }
814
815 DisplayOption() {
Sunny Goyal0e7a3382020-04-13 17:15:05 -0700816 this(null);
817 }
818
819 DisplayOption(GridOption grid) {
820 this.grid = grid;
Sunny Goyal415f1732018-11-29 10:33:47 -0800821 minWidthDps = 0;
822 minHeightDps = 0;
823 canBeDefault = false;
Jon Mirandae126d722021-02-25 10:45:20 -0500824 minCellHeight = 0;
825 minCellWidth = 0;
826 borderSpacing = 0;
Sunny Goyal415f1732018-11-29 10:33:47 -0800827 }
828
829 private DisplayOption multiply(float w) {
Sunny Goyal6e6f7992021-08-24 16:23:29 -0700830 for (int i = 0; i < COUNT_TOTAL; i++) {
831 iconSizes[i] *= w;
832 textSizes[i] *= w;
833 }
Jon Mirandae126d722021-02-25 10:45:20 -0500834 minCellHeight *= w;
835 minCellWidth *= w;
Thales Limad1df5fc2021-09-03 18:37:19 +0100836 twoPanelPortraitMinCellHeightDps *= w;
837 twoPanelPortraitMinCellWidthDps *= w;
838 twoPanelLandscapeMinCellHeightDps *= w;
839 twoPanelLandscapeMinCellWidthDps *= w;
Jon Mirandae126d722021-02-25 10:45:20 -0500840 borderSpacing *= w;
sfufa@google.comde013292021-09-21 18:22:44 -0700841 allAppsCellSpacing *= w;
Thales Limad90faab2021-09-21 17:18:47 +0100842 horizontalMargin *= w;
843 twoPanelLandscapeHorizontalMargin *= w;
844 twoPanelPortraitHorizontalMargin *= w;
Sunny Goyal415f1732018-11-29 10:33:47 -0800845 return this;
846 }
847
848 private DisplayOption add(DisplayOption p) {
Sunny Goyal6e6f7992021-08-24 16:23:29 -0700849 for (int i = 0; i < COUNT_TOTAL; i++) {
850 iconSizes[i] += p.iconSizes[i];
851 textSizes[i] += p.textSizes[i];
852 }
Jon Mirandae126d722021-02-25 10:45:20 -0500853 minCellHeight += p.minCellHeight;
854 minCellWidth += p.minCellWidth;
Thales Limad1df5fc2021-09-03 18:37:19 +0100855 twoPanelPortraitMinCellHeightDps += p.twoPanelPortraitMinCellHeightDps;
856 twoPanelPortraitMinCellWidthDps += p.twoPanelPortraitMinCellWidthDps;
857 twoPanelLandscapeMinCellHeightDps += p.twoPanelLandscapeMinCellHeightDps;
858 twoPanelLandscapeMinCellWidthDps += p.twoPanelLandscapeMinCellWidthDps;
Jon Mirandae126d722021-02-25 10:45:20 -0500859 borderSpacing += p.borderSpacing;
sfufa@google.comde013292021-09-21 18:22:44 -0700860 allAppsCellSpacing += p.allAppsCellSpacing;
Thales Limad90faab2021-09-21 17:18:47 +0100861 horizontalMargin += p.horizontalMargin;
862 twoPanelLandscapeHorizontalMargin += p.twoPanelLandscapeHorizontalMargin;
863 twoPanelPortraitHorizontalMargin += p.twoPanelPortraitHorizontalMargin;
Sunny Goyal415f1732018-11-29 10:33:47 -0800864 return this;
865 }
866 }
Sunny Goyalae190ff2020-04-14 00:19:01 +0000867}