• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2021 The Android Open Source Project
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *      http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 
17 package com.android.launcher3;
18 
19 import static com.android.launcher3.GridType.GRID_TYPE_ANY;
20 import static com.android.launcher3.GridType.GRID_TYPE_NON_ONE_GRID;
21 import static com.android.launcher3.GridType.GRID_TYPE_ONE_GRID;
22 import static com.android.launcher3.LauncherPrefs.DB_FILE;
23 import static com.android.launcher3.LauncherPrefs.ENABLE_TWOLINE_ALLAPPS_TOGGLE;
24 import static com.android.launcher3.LauncherPrefs.FIXED_LANDSCAPE_MODE;
25 import static com.android.launcher3.LauncherPrefs.GRID_NAME;
26 import static com.android.launcher3.LauncherPrefs.NON_FIXED_LANDSCAPE_GRID_NAME;
27 import static com.android.launcher3.Utilities.dpiFromPx;
28 import static com.android.launcher3.testing.shared.ResourceUtils.INVALID_RESOURCE_HANDLE;
29 import static com.android.launcher3.util.DisplayController.CHANGE_DENSITY;
30 import static com.android.launcher3.util.DisplayController.CHANGE_DESKTOP_MODE;
31 import static com.android.launcher3.util.DisplayController.CHANGE_NAVIGATION_MODE;
32 import static com.android.launcher3.util.DisplayController.CHANGE_SUPPORTED_BOUNDS;
33 import static com.android.launcher3.util.DisplayController.CHANGE_TASKBAR_PINNING;
34 import static com.android.launcher3.util.Executors.MAIN_EXECUTOR;
35 
36 import android.content.Context;
37 import android.content.Intent;
38 import android.content.res.Resources;
39 import android.content.res.TypedArray;
40 import android.content.res.XmlResourceParser;
41 import android.graphics.Point;
42 import android.graphics.PointF;
43 import android.graphics.Rect;
44 import android.os.Trace;
45 import android.text.TextUtils;
46 import android.util.AttributeSet;
47 import android.util.DisplayMetrics;
48 import android.util.Log;
49 import android.util.SparseArray;
50 import android.util.Xml;
51 
52 import androidx.annotation.DimenRes;
53 import androidx.annotation.IntDef;
54 import androidx.annotation.StyleRes;
55 import androidx.annotation.VisibleForTesting;
56 import androidx.annotation.XmlRes;
57 import androidx.core.content.res.ResourcesCompat;
58 
59 import com.android.launcher3.config.FeatureFlags;
60 import com.android.launcher3.dagger.ApplicationContext;
61 import com.android.launcher3.dagger.LauncherAppComponent;
62 import com.android.launcher3.dagger.LauncherAppSingleton;
63 import com.android.launcher3.graphics.ThemeManager;
64 import com.android.launcher3.icons.DotRenderer;
65 import com.android.launcher3.logging.FileLog;
66 import com.android.launcher3.model.DeviceGridState;
67 import com.android.launcher3.provider.RestoreDbTask;
68 import com.android.launcher3.testing.shared.ResourceUtils;
69 import com.android.launcher3.util.DaggerSingletonObject;
70 import com.android.launcher3.util.DaggerSingletonTracker;
71 import com.android.launcher3.util.DisplayController;
72 import com.android.launcher3.util.DisplayController.Info;
73 import com.android.launcher3.util.Partner;
74 import com.android.launcher3.util.ResourceHelper;
75 import com.android.launcher3.util.SimpleBroadcastReceiver;
76 import com.android.launcher3.util.WindowBounds;
77 import com.android.launcher3.util.window.CachedDisplayInfo;
78 import com.android.launcher3.util.window.WindowManagerProxy;
79 
80 import org.xmlpull.v1.XmlPullParser;
81 import org.xmlpull.v1.XmlPullParserException;
82 
83 import java.io.IOException;
84 import java.lang.annotation.Retention;
85 import java.lang.annotation.RetentionPolicy;
86 import java.util.ArrayList;
87 import java.util.Arrays;
88 import java.util.Collections;
89 import java.util.List;
90 import java.util.Objects;
91 import java.util.concurrent.CopyOnWriteArrayList;
92 import java.util.stream.Collectors;
93 
94 import javax.inject.Inject;
95 
96 @LauncherAppSingleton
97 public class InvariantDeviceProfile {
98 
99     public static final String TAG = "IDP";
100     // We do not need any synchronization for this variable as its only written on UI thread.
101     public static final DaggerSingletonObject<InvariantDeviceProfile> INSTANCE =
102             new DaggerSingletonObject<>(LauncherAppComponent::getIDP);
103 
104     public static final String GRID_NAME_PREFS_KEY = "idp_grid_name";
105     public static final String NON_FIXED_LANDSCAPE_GRID_NAME_PREFS_KEY =
106             "idp_non_fixed_landscape_grid_name";
107 
108     @Retention(RetentionPolicy.SOURCE)
109     @IntDef({TYPE_PHONE, TYPE_MULTI_DISPLAY, TYPE_TABLET})
110     public @interface DeviceType {
111     }
112 
113     public static final int TYPE_PHONE = 0;
114     public static final int TYPE_MULTI_DISPLAY = 1;
115     public static final int TYPE_TABLET = 2;
116 
117     private static final float ICON_SIZE_DEFINED_IN_APP_DP = 48;
118 
119     // Constants that affects the interpolation curve between statically defined device profile
120     // buckets.
121     private static final float KNEARESTNEIGHBOR = 3;
122     private static final float WEIGHT_POWER = 5;
123 
124     // used to offset float not being able to express extremely small weights in extreme cases.
125     private static final float WEIGHT_EFFICIENT = 100000f;
126 
127     // Used for arrays to specify different sizes (e.g. border spaces, width/height) in different
128     // constraints
129     static final int COUNT_SIZES = 4;
130     static final int INDEX_DEFAULT = 0;
131     static final int INDEX_LANDSCAPE = 1;
132     static final int INDEX_TWO_PANEL_PORTRAIT = 2;
133     static final int INDEX_TWO_PANEL_LANDSCAPE = 3;
134 
135     /** These resources are used to override the device profile */
136     private static final String RES_GRID_NUM_ROWS = "grid_num_rows";
137     private static final String RES_GRID_NUM_COLUMNS = "grid_num_columns";
138     private static final String RES_GRID_ICON_SIZE_DP = "grid_icon_size_dp";
139 
140     private final DisplayController mDisplayController;
141     private final WindowManagerProxy mWMProxy;
142     private final LauncherPrefs mPrefs;
143     private final ThemeManager mThemeManager;
144 
145     /**
146      * Number of icons per row and column in the workspace.
147      */
148     public int numRows;
149     public int numColumns;
150     public int numSearchContainerColumns;
151 
152     /**
153      * Number of icons per row and column in the folder.
154      */
155     public int[] numFolderRows;
156     public int[] numFolderColumns;
157     public float[] iconSize;
158     public float[] iconTextSize;
159     public int iconBitmapSize;
160     public int fillResIconDpi;
161     public @DeviceType int deviceType;
162     public Info displayInfo;
163 
164     public PointF[] minCellSize;
165 
166     public PointF[] borderSpaces;
167     public @DimenRes int inlineNavButtonsEndSpacing;
168 
169     public @StyleRes int folderStyle;
170 
171     public @StyleRes int cellStyle;
172 
173     public float[] horizontalMargin;
174 
175     public PointF[] allAppsCellSize;
176     public float[] allAppsIconSize;
177     public float[] allAppsIconTextSize;
178     public PointF[] allAppsBorderSpaces;
179 
180     public float[] transientTaskbarIconSize;
181 
182     public boolean[] startAlignTaskbar;
183 
184     /**
185      * Number of icons inside the hotseat area.
186      */
187     public int numShownHotseatIcons;
188 
189     /**
190      * Number of icons inside the hotseat area that is stored in the database. This is greater than
191      * or equal to numnShownHotseatIcons, allowing for a seamless transition between two hotseat
192      * sizes that share the same DB.
193      */
194     public int numDatabaseHotseatIcons;
195 
196     public float[] hotseatBarBottomSpace;
197     public float[] hotseatQsbSpace;
198 
199     /**
200      * Number of columns in the all apps list.
201      */
202     public int numAllAppsColumns;
203     public int numAllAppsRowsForCellHeightCalculation;
204     public int numDatabaseAllAppsColumns;
205     public @StyleRes int allAppsStyle;
206 
207     /**
208      * Do not query directly. see {@link DeviceProfile#isScalableGrid}.
209      */
210     protected boolean isScalable;
211     @XmlRes
212     public int devicePaddingId = INVALID_RESOURCE_HANDLE;
213     @XmlRes
214     public int workspaceSpecsId = INVALID_RESOURCE_HANDLE;
215     @XmlRes
216     public int gridSizeSpecsId = INVALID_RESOURCE_HANDLE;;
217     @XmlRes
218     public int workspaceSpecsTwoPanelId = INVALID_RESOURCE_HANDLE;
219     @XmlRes
220     public int allAppsSpecsId = INVALID_RESOURCE_HANDLE;
221     @XmlRes
222     public int allAppsSpecsTwoPanelId = INVALID_RESOURCE_HANDLE;
223     @XmlRes
224     public int folderSpecsId = INVALID_RESOURCE_HANDLE;
225     @XmlRes
226     public int folderSpecsTwoPanelId = INVALID_RESOURCE_HANDLE;
227     @XmlRes
228     public int hotseatSpecsId = INVALID_RESOURCE_HANDLE;
229     @XmlRes
230     public int hotseatSpecsTwoPanelId = INVALID_RESOURCE_HANDLE;
231     @XmlRes
232     public int workspaceCellSpecsId = INVALID_RESOURCE_HANDLE;
233     @XmlRes
234     public int workspaceCellSpecsTwoPanelId = INVALID_RESOURCE_HANDLE;
235     @XmlRes
236     public int allAppsCellSpecsId = INVALID_RESOURCE_HANDLE;
237     @XmlRes
238     public int allAppsCellSpecsTwoPanelId = INVALID_RESOURCE_HANDLE;
239 
240     private String mLocale = "";
241     public boolean enableTwoLinesInAllApps = false;
242     /**
243      * Fixed landscape mode is the landscape on the phones.
244      */
245     public boolean isFixedLandscape = false;
246 
247     @GridType
248     public int gridType;
249     public String dbFile;
250     public int defaultLayoutId;
251     public int demoModeLayoutId;
252     public boolean[] inlineQsb = new boolean[COUNT_SIZES];
253 
254     /**
255      * An immutable list of supported profiles.
256      */
257     public List<DeviceProfile> supportedProfiles = Collections.EMPTY_LIST;
258 
259     public Point defaultWallpaperSize;
260 
261     private final List<OnIDPChangeListener> mChangeListeners = new CopyOnWriteArrayList<>();
262 
263     @Inject
InvariantDeviceProfile( @pplicationContext Context context, LauncherPrefs prefs, DisplayController dc, WindowManagerProxy wmProxy, ThemeManager themeManager, DaggerSingletonTracker lifeCycle)264     InvariantDeviceProfile(
265             @ApplicationContext Context context,
266             LauncherPrefs prefs,
267             DisplayController dc,
268             WindowManagerProxy wmProxy,
269             ThemeManager themeManager,
270             DaggerSingletonTracker lifeCycle) {
271         mDisplayController = dc;
272         mWMProxy = wmProxy;
273         mPrefs = prefs;
274         mThemeManager = themeManager;
275 
276         String gridName = prefs.get(GRID_NAME);
277         initGrid(context, gridName);
278 
279         dc.setPriorityListener(
280                 (displayContext, info, flags) -> {
281                     if ((flags & (CHANGE_DENSITY | CHANGE_SUPPORTED_BOUNDS
282                             | CHANGE_NAVIGATION_MODE | CHANGE_TASKBAR_PINNING
283                             | CHANGE_DESKTOP_MODE)) != 0) {
284                         onConfigChanged(displayContext);
285                     }
286                 });
287         lifeCycle.addCloseable(() -> dc.setPriorityListener(null));
288 
289         LauncherPrefChangeListener prefListener = key -> {
290             if (FIXED_LANDSCAPE_MODE.getSharedPrefKey().equals(key)
291                     && isFixedLandscape != prefs.get(FIXED_LANDSCAPE_MODE)) {
292                 Trace.beginSection("InvariantDeviceProfile#setFixedLandscape");
293                 if (isFixedLandscape) {
294                     setCurrentGrid(context, prefs.get(NON_FIXED_LANDSCAPE_GRID_NAME));
295                 } else {
296                     prefs.put(NON_FIXED_LANDSCAPE_GRID_NAME, mPrefs.get(GRID_NAME));
297                     onConfigChanged(context);
298                 }
299                 Trace.endSection();
300             } else if (ENABLE_TWOLINE_ALLAPPS_TOGGLE.getSharedPrefKey().equals(key)
301                     && enableTwoLinesInAllApps != prefs.get(ENABLE_TWOLINE_ALLAPPS_TOGGLE)) {
302                 onConfigChanged(context);
303             }
304         };
305         prefs.addListener(prefListener, FIXED_LANDSCAPE_MODE, ENABLE_TWOLINE_ALLAPPS_TOGGLE);
306         lifeCycle.addCloseable(() -> prefs.removeListener(prefListener,
307                 FIXED_LANDSCAPE_MODE, ENABLE_TWOLINE_ALLAPPS_TOGGLE));
308 
309         SimpleBroadcastReceiver localeReceiver = new SimpleBroadcastReceiver(context,
310                 MAIN_EXECUTOR, i -> onConfigChanged(context));
311         localeReceiver.register(Intent.ACTION_LOCALE_CHANGED);
312         lifeCycle.addCloseable(() -> localeReceiver.unregisterReceiverSafely());
313     }
314 
initGrid(Context context, String gridName)315     private String initGrid(Context context, String gridName) {
316         Info displayInfo = mDisplayController.getInfo();
317         List<DisplayOption> allOptions = getPredefinedDeviceProfiles(
318                 context,
319                 gridName,
320                 displayInfo,
321                 (RestoreDbTask.isPending(mPrefs) && !Flags.oneGridSpecs()),
322                 mPrefs.get(FIXED_LANDSCAPE_MODE)
323         );
324 
325         // Filter out options that don't have the same number of columns as the grid
326         DeviceGridState deviceGridState = new DeviceGridState(mPrefs);
327         List<DisplayOption> allOptionsFilteredByColCount =
328                 filterByColumnCount(allOptions, deviceGridState.getColumns());
329 
330         DisplayOption displayOption =
331                 invDistWeightedInterpolate(displayInfo, allOptionsFilteredByColCount.isEmpty()
332                                 ? new ArrayList<>(allOptions)
333                                 : new ArrayList<>(allOptionsFilteredByColCount),
334                         displayInfo.getDeviceType());
335 
336         if (!displayOption.grid.name.equals(gridName)) {
337             mPrefs.put(GRID_NAME, displayOption.grid.name);
338         }
339 
340         initGrid(context, displayInfo, displayOption);
341         FileLog.d(TAG, "After initGrid:"
342                 + "gridName:" + gridName
343                 + ", dbFile:" + dbFile
344                 + ", LauncherPrefs GRID_NAME:" + mPrefs.get(GRID_NAME)
345                 + ", LauncherPrefs DB_FILE:" + mPrefs.get(DB_FILE));
346         return displayOption.grid.name;
347     }
348 
filterByColumnCount( List<DisplayOption> allOptions, int numColumns)349     private List<DisplayOption> filterByColumnCount(
350             List<DisplayOption> allOptions, int numColumns) {
351         return allOptions.stream()
352                 .filter(option -> option.grid.numColumns == numColumns)
353                 .collect(Collectors.toList());
354     }
355 
356     /**
357      * @deprecated This is a temporary solution because on the backup and restore case we modify the
358      * IDP, this resets it. b/332974074
359      */
360     @Deprecated
reset(Context context)361     public void reset(Context context) {
362         initGrid(context, mPrefs.get(GRID_NAME));
363     }
364 
initGrid(Context context, Info displayInfo, DisplayOption displayOption)365     private void initGrid(Context context, Info displayInfo, DisplayOption displayOption) {
366         enableTwoLinesInAllApps = Flags.enableTwolineToggle()
367                 && Utilities.isEnglishLanguage(context)
368                 && mPrefs.get(ENABLE_TWOLINE_ALLAPPS_TOGGLE);
369         mLocale = context.getResources().getConfiguration().locale.toString();
370 
371         DisplayMetrics metrics = context.getResources().getDisplayMetrics();
372         GridOption closestProfile = displayOption.grid;
373         numRows = closestProfile.numRows;
374         numColumns = closestProfile.numColumns;
375         numSearchContainerColumns = closestProfile.numSearchContainerColumns;
376         dbFile = closestProfile.dbFile;
377         gridType = closestProfile.gridType;
378         defaultLayoutId = closestProfile.defaultLayoutId;
379         demoModeLayoutId = closestProfile.demoModeLayoutId;
380 
381         numFolderRows = closestProfile.numFolderRows;
382         numFolderColumns = closestProfile.numFolderColumns;
383         folderStyle = closestProfile.folderStyle;
384 
385         cellStyle = closestProfile.cellStyle;
386 
387         isScalable = closestProfile.isScalable;
388         devicePaddingId = closestProfile.devicePaddingId;
389         workspaceSpecsId = closestProfile.mWorkspaceSpecsId;
390         gridSizeSpecsId = closestProfile.mGridSizeSpecsId;
391         workspaceSpecsTwoPanelId = closestProfile.mWorkspaceSpecsTwoPanelId;
392         allAppsSpecsId = closestProfile.mAllAppsSpecsId;
393         allAppsSpecsTwoPanelId = closestProfile.mAllAppsSpecsTwoPanelId;
394         folderSpecsId = closestProfile.mFolderSpecsId;
395         folderSpecsTwoPanelId = closestProfile.mFolderSpecsTwoPanelId;
396         hotseatSpecsId = closestProfile.mHotseatSpecsId;
397         hotseatSpecsTwoPanelId = closestProfile.mHotseatSpecsTwoPanelId;
398         workspaceCellSpecsId = closestProfile.mWorkspaceCellSpecsId;
399         workspaceCellSpecsTwoPanelId = closestProfile.mWorkspaceCellSpecsTwoPanelId;
400         allAppsCellSpecsId = closestProfile.mAllAppsCellSpecsId;
401         allAppsCellSpecsTwoPanelId = closestProfile.mAllAppsCellSpecsTwoPanelId;
402         numAllAppsRowsForCellHeightCalculation =
403                 closestProfile.mNumAllAppsRowsForCellHeightCalculation;
404         this.deviceType = displayInfo.getDeviceType();
405         this.displayInfo = displayInfo;
406 
407         inlineNavButtonsEndSpacing = closestProfile.inlineNavButtonsEndSpacing;
408 
409         iconSize = displayOption.iconSizes;
410         float maxIconSize = iconSize[0];
411         for (int i = 1; i < iconSize.length; i++) {
412             maxIconSize = Math.max(maxIconSize, iconSize[i]);
413         }
414         iconBitmapSize = ResourceUtils.pxFromDp(maxIconSize, metrics);
415         fillResIconDpi = getLauncherIconDensity(iconBitmapSize);
416 
417         iconTextSize = displayOption.textSizes;
418 
419         minCellSize = displayOption.minCellSize;
420 
421         borderSpaces = displayOption.borderSpaces;
422 
423         horizontalMargin = displayOption.horizontalMargin;
424 
425         numShownHotseatIcons = closestProfile.numHotseatIcons;
426         numDatabaseHotseatIcons = deviceType == TYPE_MULTI_DISPLAY
427                 ? closestProfile.numDatabaseHotseatIcons : closestProfile.numHotseatIcons;
428         hotseatBarBottomSpace = displayOption.hotseatBarBottomSpace;
429         hotseatQsbSpace = displayOption.hotseatQsbSpace;
430 
431         allAppsStyle = closestProfile.allAppsStyle;
432 
433         numAllAppsColumns = closestProfile.numAllAppsColumns;
434 
435         numDatabaseAllAppsColumns = deviceType == TYPE_MULTI_DISPLAY
436                 ? closestProfile.numDatabaseAllAppsColumns : closestProfile.numAllAppsColumns;
437 
438         allAppsCellSize = displayOption.allAppsCellSize;
439         allAppsBorderSpaces = displayOption.allAppsBorderSpaces;
440         allAppsIconSize = displayOption.allAppsIconSizes;
441         allAppsIconTextSize = displayOption.allAppsIconTextSizes;
442 
443         inlineQsb = closestProfile.inlineQsb;
444 
445         transientTaskbarIconSize = displayOption.transientTaskbarIconSize;
446 
447         startAlignTaskbar = displayOption.startAlignTaskbar;
448 
449         // Fixed Landscape mode
450         isFixedLandscape = closestProfile.mIsFixedLandscape;
451 
452         // If the partner customization apk contains any grid overrides, apply them
453         // Supported overrides: numRows, numColumns, iconSize
454         applyPartnerDeviceProfileOverrides(context, metrics);
455 
456         final List<DeviceProfile> localSupportedProfiles = new ArrayList<>();
457         defaultWallpaperSize = new Point(displayInfo.currentSize);
458         SparseArray<DotRenderer> dotRendererCache = new SparseArray<>();
459         for (WindowBounds bounds : displayInfo.supportedBounds) {
460             localSupportedProfiles.add(newDPBuilder(context, displayInfo)
461                     .setIsMultiDisplay(deviceType == TYPE_MULTI_DISPLAY)
462                     .setWindowBounds(bounds)
463                     .setDotRendererCache(dotRendererCache)
464                     .build());
465 
466             // Wallpaper size should be the maximum of the all possible sizes Launcher expects
467             int displayWidth = bounds.bounds.width();
468             int displayHeight = bounds.bounds.height();
469             defaultWallpaperSize.y = Math.max(defaultWallpaperSize.y, displayHeight);
470 
471             // We need to ensure that there is enough extra space in the wallpaper
472             // for the intended parallax effects
473             float parallaxFactor =
474                     dpiFromPx(Math.min(displayWidth, displayHeight), displayInfo.getDensityDpi())
475                             < 720
476                             ? 2
477                             : wallpaperTravelToScreenWidthRatio(displayWidth, displayHeight);
478             defaultWallpaperSize.x =
479                     Math.max(defaultWallpaperSize.x, Math.round(parallaxFactor * displayWidth));
480         }
481         supportedProfiles = Collections.unmodifiableList(localSupportedProfiles);
482 
483         int numMinShownHotseatIconsForTablet = supportedProfiles
484                 .stream()
485                 .filter(deviceProfile -> deviceProfile.isTablet)
486                 .mapToInt(deviceProfile -> deviceProfile.numShownHotseatIcons)
487                 .min()
488                 .orElse(0);
489 
490         supportedProfiles
491                 .stream()
492                 .filter(deviceProfile -> deviceProfile.isTablet)
493                 .forEach(deviceProfile -> {
494                     deviceProfile.numShownHotseatIcons = numMinShownHotseatIconsForTablet;
495                     deviceProfile.recalculateHotseatWidthAndBorderSpace();
496                 });
497     }
498 
newDPBuilder(Context context, Info info)499     DeviceProfile.Builder newDPBuilder(Context context, Info info) {
500         return new DeviceProfile.Builder(context, this, info, mWMProxy, mThemeManager);
501     }
502 
addOnChangeListener(OnIDPChangeListener listener)503     public void addOnChangeListener(OnIDPChangeListener listener) {
504         mChangeListeners.add(listener);
505     }
506 
removeOnChangeListener(OnIDPChangeListener listener)507     public void removeOnChangeListener(OnIDPChangeListener listener) {
508         mChangeListeners.remove(listener);
509     }
510 
511     /**
512      * Updates the current grid, this triggers a new IDP, reloads the database and triggers a grid
513      * migration.
514      */
515     @VisibleForTesting
setCurrentGrid(Context context, String newGridName)516     public void setCurrentGrid(Context context, String newGridName) {
517         mPrefs.put(GRID_NAME, newGridName);
518         MAIN_EXECUTOR.execute(() -> {
519             Trace.beginSection("InvariantDeviceProfile#setCurrentGrid");
520             onConfigChanged(context.getApplicationContext());
521             Trace.endSection();
522         });
523     }
524 
toModelState()525     private Object[] toModelState() {
526         return new Object[]{
527                 numColumns, numRows, numSearchContainerColumns, numDatabaseHotseatIcons,
528                 iconBitmapSize, fillResIconDpi, numDatabaseAllAppsColumns, dbFile, mLocale};
529     }
530 
531     /** Updates IDP using the provided context. Notifies listeners of change. */
532     @VisibleForTesting
onConfigChanged(Context context)533     public void onConfigChanged(Context context) {
534         Object[] oldState = toModelState();
535 
536         // Re-init grid
537         initGrid(context, mPrefs.get(GRID_NAME));
538 
539         boolean modelPropsChanged = !Arrays.equals(oldState, toModelState());
540         for (OnIDPChangeListener listener : mChangeListeners) {
541             listener.onIdpChanged(modelPropsChanged);
542         }
543     }
544 
firstGridFilter(GridOption gridOption, int deviceType, boolean allowDisabledGrid, boolean isFixedLandscapeMode)545     private static boolean firstGridFilter(GridOption gridOption, int deviceType,
546             boolean allowDisabledGrid, boolean isFixedLandscapeMode) {
547         return (gridOption.isEnabled(deviceType) || allowDisabledGrid)
548                 && gridOption.filterByFlag(deviceType, isFixedLandscapeMode);
549     }
550 
getPredefinedDeviceProfiles( Context context, String gridName, Info displayInfo, boolean allowDisabledGrid, boolean isFixedLandscapeMode )551     private static List<DisplayOption> getPredefinedDeviceProfiles(
552             Context context,
553             String gridName,
554             Info displayInfo,
555             boolean allowDisabledGrid,
556             boolean isFixedLandscapeMode
557     ) {
558         ArrayList<DisplayOption> profiles = new ArrayList<>();
559 
560         try (XmlResourceParser parser = context.getResources().getXml(R.xml.device_profiles)) {
561             final int depth = parser.getDepth();
562             int type;
563             while (((type = parser.next()) != XmlPullParser.END_TAG ||
564                     parser.getDepth() > depth) && type != XmlPullParser.END_DOCUMENT) {
565                 if ((type == XmlPullParser.START_TAG)
566                         && GridOption.TAG_NAME.equals(parser.getName())) {
567                     GridOption gridOption = new GridOption(
568                             context, Xml.asAttributeSet(parser), displayInfo);
569                     if (firstGridFilter(gridOption, displayInfo.getDeviceType(), allowDisabledGrid,
570                             isFixedLandscapeMode)) {
571                         final int displayDepth = parser.getDepth();
572                         while (((type = parser.next()) != XmlPullParser.END_TAG
573                                 || parser.getDepth() > displayDepth)
574                                 && type != XmlPullParser.END_DOCUMENT) {
575                             if ((type == XmlPullParser.START_TAG) && "display-option".equals(
576                                     parser.getName())) {
577                                 profiles.add(new DisplayOption(gridOption, context,
578                                         Xml.asAttributeSet(parser)));
579                             }
580                         }
581                     }
582                 }
583             }
584         } catch (IOException | XmlPullParserException e) {
585             throw new RuntimeException(e);
586         }
587         ArrayList<DisplayOption> filteredProfiles = new ArrayList<>();
588         if (!TextUtils.isEmpty(gridName)) {
589             for (DisplayOption option : profiles) {
590                 if (gridName.equals(option.grid.name) && (option.grid.isEnabled(
591                         displayInfo.getDeviceType()) || allowDisabledGrid)) {
592                     filteredProfiles.add(option);
593                 }
594             }
595         }
596         if (filteredProfiles.isEmpty() && TextUtils.isEmpty(gridName)) {
597             // Use the default options since gridName is empty and there's no valid grids.
598             for (DisplayOption option : profiles) {
599                 if (option.canBeDefault) {
600                     filteredProfiles.add(option);
601                 }
602             }
603         } else if (filteredProfiles.isEmpty()) {
604             // In this case we had a grid selected but we couldn't find it.
605             filteredProfiles.addAll(profiles);
606         }
607         if (filteredProfiles.isEmpty()) {
608             throw new RuntimeException("No display option with canBeDefault=true");
609         }
610         return filteredProfiles;
611     }
612 
613     /**
614      * Parses through the xml to find GridSize specs. Then calls findBestGridSize to get the
615      * correct grid size for this GridOption.
616      *
617      * @return the result of {@link #findBestGridSize(List, int, int)}.
618      */
getGridSize(ResourceHelper resourceHelper, Context context, Info displayInfo)619     private static GridSize getGridSize(ResourceHelper resourceHelper, Context context,
620             Info displayInfo) {
621         ArrayList<GridSize> gridSizes = new ArrayList<>();
622 
623         try (XmlResourceParser parser = resourceHelper.getXml()) {
624             final int depth = parser.getDepth();
625             int type;
626             while (((type = parser.next()) != XmlPullParser.END_TAG
627                     || parser.getDepth() > depth) && type != XmlPullParser.END_DOCUMENT) {
628                 if ((type == XmlPullParser.START_TAG)
629                         && "GridSize".equals(parser.getName())) {
630                     gridSizes.add(new GridSize(context, Xml.asAttributeSet(parser)));
631                 }
632             }
633         } catch (IOException | XmlPullParserException e) {
634             throw new RuntimeException(e);
635         }
636 
637         // Finds the min width and height in dp for all displays.
638         int[] dimens = findMinWidthAndHeightPxForDevice(displayInfo);
639 
640         return findBestGridSize(gridSizes, dimens[0], dimens[1]);
641     }
642 
643     /**
644      * @return the biggest grid size that fits the display dimensions.
645      * If no best grid size is found, return null.
646      */
findBestGridSize(List<GridSize> list, int minWidthPx, int minHeightPx)647     private static GridSize findBestGridSize(List<GridSize> list, int minWidthPx,
648             int minHeightPx) {
649         GridSize selectedGridSize = null;
650         for (GridSize item: list) {
651             if (minWidthPx >= item.mMinDeviceWidthPx && minHeightPx >= item.mMinDeviceHeightPx) {
652                 if (selectedGridSize == null
653                         || (selectedGridSize.mNumColumns <= item.mNumColumns
654                         && selectedGridSize.mNumRows <= item.mNumRows)) {
655                     selectedGridSize = item;
656                 }
657             }
658         }
659         return selectedGridSize;
660     }
661 
findMinWidthAndHeightPxForDevice(Info displayInfo)662     private static int[] findMinWidthAndHeightPxForDevice(Info displayInfo) {
663         int minDisplayWidthPx = Integer.MAX_VALUE;
664         int minDisplayHeightPx = Integer.MAX_VALUE;
665         for (CachedDisplayInfo display: displayInfo.getAllDisplays()) {
666             minDisplayWidthPx = Math.min(minDisplayWidthPx, display.size.x);
667             minDisplayHeightPx = Math.min(minDisplayHeightPx, display.size.y);
668         }
669         return new int[]{minDisplayWidthPx, minDisplayHeightPx};
670     }
671 
672     /**
673      * Returns the GridOption associated to the given file name or null if the fileName is not
674      * supported.
675      * Ej, launcher.db -> "normal grid", launcher_4_by_4.db -> "practical grid"
676      */
getGridOptionFromFileName(Context context, String fileName)677     public GridOption getGridOptionFromFileName(Context context, String fileName) {
678         return parseAllGridOptions(context).stream()
679                 .filter(gridOption -> Objects.equals(gridOption.dbFile, fileName))
680                 .findFirst()
681                 .orElse(null);
682     }
683 
684     /**
685      * Returns the name of the given size on the current device or empty string if the size is not
686      * supported. Ej. 4x4 -> normal, 5x4 -> practical, etc.
687      * (Note: the name of the grid can be different for the same grid size depending of
688      * the values of the InvariantDeviceProfile)
689      */
getGridNameFromSize(Context context, Point size)690     public String getGridNameFromSize(Context context, Point size) {
691         return parseAllGridOptions(context).stream()
692                 .filter(gridOption -> gridOption.numColumns == size.x
693                         && gridOption.numRows == size.y)
694                 .map(gridOption -> gridOption.name)
695                 .findFirst()
696                 .orElse("");
697     }
698 
699     /**
700      * Returns the grid option for the given gridName on the current device (Note: the gridOption
701      * be different for the same gridName depending on the values of the InvariantDeviceProfile).
702      */
getGridOptionFromName(Context context, String gridName)703     public GridOption getGridOptionFromName(Context context, String gridName) {
704         return parseAllGridOptions(context).stream()
705                 .filter(gridOption -> Objects.equals(gridOption.name, gridName))
706                 .findFirst()
707                 .orElse(null);
708     }
709 
710     /**
711      * @return all the grid options that can be shown on the device
712      */
parseAllGridOptions(Context context)713     public List<GridOption> parseAllGridOptions(Context context) {
714         return parseAllDefinedGridOptions(context, displayInfo)
715                 .stream()
716                 .filter(go -> go.isEnabled(deviceType))
717                 .filter(go -> go.filterByFlag(deviceType, isFixedLandscape))
718                 .collect(Collectors.toList());
719     }
720 
721     /**
722      * @return all the grid options that can be shown on the device
723      */
parseAllDefinedGridOptions(Context context, Info displayInfo)724     public static List<GridOption> parseAllDefinedGridOptions(Context context, Info displayInfo) {
725         List<GridOption> result = new ArrayList<>();
726         try (XmlResourceParser parser = context.getResources().getXml(R.xml.device_profiles)) {
727             final int depth = parser.getDepth();
728             int type;
729             while (((type = parser.next()) != XmlPullParser.END_TAG
730                     || parser.getDepth() > depth) && type != XmlPullParser.END_DOCUMENT) {
731                 if ((type == XmlPullParser.START_TAG)
732                         && GridOption.TAG_NAME.equals(parser.getName())) {
733                     result.add(new GridOption(context, Xml.asAttributeSet(parser), displayInfo));
734                 }
735             }
736         } catch (IOException | XmlPullParserException e) {
737             Log.e(TAG, "Error parsing device profile", e);
738             return Collections.emptyList();
739         }
740         return result;
741     }
742 
getLauncherIconDensity(int requiredSize)743     private int getLauncherIconDensity(int requiredSize) {
744         // Densities typically defined by an app.
745         int[] densityBuckets = new int[]{
746                 DisplayMetrics.DENSITY_LOW,
747                 DisplayMetrics.DENSITY_MEDIUM,
748                 DisplayMetrics.DENSITY_TV,
749                 DisplayMetrics.DENSITY_HIGH,
750                 DisplayMetrics.DENSITY_XHIGH,
751                 DisplayMetrics.DENSITY_XXHIGH,
752                 DisplayMetrics.DENSITY_XXXHIGH
753         };
754 
755         int density = DisplayMetrics.DENSITY_XXXHIGH;
756         for (int i = densityBuckets.length - 1; i >= 0; i--) {
757             float expectedSize = ICON_SIZE_DEFINED_IN_APP_DP * densityBuckets[i]
758                     / DisplayMetrics.DENSITY_DEFAULT;
759             if (expectedSize >= requiredSize) {
760                 density = densityBuckets[i];
761             }
762         }
763 
764         return density;
765     }
766 
767     /**
768      * Apply any Partner customization grid overrides.
769      *
770      * Currently we support: all apps row / column count.
771      */
applyPartnerDeviceProfileOverrides(Context context, DisplayMetrics dm)772     private void applyPartnerDeviceProfileOverrides(Context context, DisplayMetrics dm) {
773         Partner p = Partner.get(context.getPackageManager());
774         if (p == null) {
775             return;
776         }
777         try {
778             int numRows = p.getIntValue(RES_GRID_NUM_ROWS, -1);
779             int numColumns = p.getIntValue(RES_GRID_NUM_COLUMNS, -1);
780             float iconSizePx = p.getDimenValue(RES_GRID_ICON_SIZE_DP, -1);
781 
782             if (numRows > 0 && numColumns > 0) {
783                 this.numRows = numRows;
784                 this.numColumns = numColumns;
785             }
786             if (iconSizePx > 0) {
787                 this.iconSize[InvariantDeviceProfile.INDEX_DEFAULT] =
788                         Utilities.dpiFromPx(iconSizePx, dm.densityDpi);
789             }
790         } catch (Resources.NotFoundException ex) {
791             Log.e(TAG, "Invalid Partner grid resource!", ex);
792         }
793     }
794 
dist(float x0, float y0, float x1, float y1)795     private static float dist(float x0, float y0, float x1, float y1) {
796         return (float) Math.hypot(x1 - x0, y1 - y0);
797     }
798 
invDistWeightedInterpolate( Info displayInfo, List<DisplayOption> points, @DeviceType int deviceType)799     private static DisplayOption invDistWeightedInterpolate(
800             Info displayInfo, List<DisplayOption> points, @DeviceType int deviceType) {
801         int minWidthPx = Integer.MAX_VALUE;
802         int minHeightPx = Integer.MAX_VALUE;
803         for (WindowBounds bounds : displayInfo.supportedBounds) {
804             boolean isTablet = displayInfo.isTablet(bounds);
805             if (isTablet && deviceType == TYPE_MULTI_DISPLAY) {
806                 // For split displays, take half width per page
807                 minWidthPx = Math.min(minWidthPx, bounds.availableSize.x / 2);
808                 minHeightPx = Math.min(minHeightPx, bounds.availableSize.y);
809 
810             } else if (!isTablet && bounds.isLandscape()) {
811                 // We will use transposed layout in this case
812                 minWidthPx = Math.min(minWidthPx, bounds.availableSize.y);
813                 minHeightPx = Math.min(minHeightPx, bounds.availableSize.x);
814             } else {
815                 minWidthPx = Math.min(minWidthPx, bounds.availableSize.x);
816                 minHeightPx = Math.min(minHeightPx, bounds.availableSize.y);
817             }
818         }
819 
820         float width = dpiFromPx(minWidthPx, displayInfo.getDensityDpi());
821         float height = dpiFromPx(minHeightPx, displayInfo.getDensityDpi());
822 
823         // Sort the profiles based on the closeness to the device size
824         points.sort((a, b) ->
825                 Float.compare(dist(width, height, a.minWidthDps, a.minHeightDps),
826                         dist(width, height, b.minWidthDps, b.minHeightDps)));
827 
828         DisplayOption closestPoint = points.get(0);
829         GridOption closestOption = closestPoint.grid;
830         float weights = 0;
831 
832         if (dist(width, height, closestPoint.minWidthDps, closestPoint.minHeightDps) == 0) {
833             return closestPoint;
834         }
835 
836         DisplayOption out = new DisplayOption(closestOption);
837         for (int i = 0; i < points.size() && i < KNEARESTNEIGHBOR; ++i) {
838             DisplayOption p = points.get(i);
839             float w = weight(width, height, p.minWidthDps, p.minHeightDps, WEIGHT_POWER);
840             weights += w;
841             out.add(new DisplayOption().add(p).multiply(w));
842         }
843         out.multiply(1.0f / weights);
844 
845         // Since the bitmaps are persisted, ensure that all bitmap sizes are not larger than
846         // predefined size to avoid cache invalidation
847         for (int i = INDEX_DEFAULT; i < COUNT_SIZES; i++) {
848             out.iconSizes[i] = Math.min(out.iconSizes[i], closestPoint.iconSizes[i]);
849         }
850 
851         return out;
852     }
853 
createDeviceProfileForSecondaryDisplay(Context displayContext)854     public DeviceProfile createDeviceProfileForSecondaryDisplay(Context displayContext) {
855         // Disable transpose layout and use multi-window mode so that the icons are scaled properly
856         return newDPBuilder(displayContext, new Info(displayContext))
857                 .setIsMultiDisplay(false)
858                 .setMultiWindowMode(true)
859                 .setWindowBounds(mWMProxy.getRealBounds(
860                         displayContext, mWMProxy.getDisplayInfo(displayContext)))
861                 .setTransposeLayoutWithOrientation(false)
862                 .build();
863     }
864 
getDeviceProfile(Context context)865     public DeviceProfile getDeviceProfile(Context context) {
866         Rect bounds = mWMProxy.getCurrentBounds(context);
867         int rotation = mWMProxy.getRotation(context);
868         return getBestMatch(bounds.width(), bounds.height(), rotation);
869     }
870 
871     /**
872      * Returns the device profile matching the provided screen configuration
873      */
getBestMatch(float screenWidth, float screenHeight, int rotation)874     public DeviceProfile getBestMatch(float screenWidth, float screenHeight, int rotation) {
875         DeviceProfile bestMatch = supportedProfiles.get(0);
876         float minDiff = Float.MAX_VALUE;
877 
878         for (DeviceProfile profile : supportedProfiles) {
879             float diff = Math.abs(profile.widthPx - screenWidth)
880                     + Math.abs(profile.heightPx - screenHeight);
881             if (diff < minDiff) {
882                 minDiff = diff;
883                 bestMatch = profile;
884             } else if (diff == minDiff && profile.rotationHint == rotation) {
885                 bestMatch = profile;
886             }
887         }
888         return bestMatch;
889     }
890 
weight(float x0, float y0, float x1, float y1, float pow)891     private static float weight(float x0, float y0, float x1, float y1, float pow) {
892         float d = dist(x0, y0, x1, y1);
893         if (Float.compare(d, 0f) == 0) {
894             return Float.POSITIVE_INFINITY;
895         }
896         return (float) (WEIGHT_EFFICIENT / Math.pow(d, pow));
897     }
898 
899     /**
900      * As a ratio of screen height, the total distance we want the parallax effect to span
901      * horizontally
902      */
wallpaperTravelToScreenWidthRatio(int width, int height)903     private static float wallpaperTravelToScreenWidthRatio(int width, int height) {
904         float aspectRatio = width / (float) height;
905 
906         // At an aspect ratio of 16/10, the wallpaper parallax effect should span 1.5 * screen width
907         // At an aspect ratio of 10/16, the wallpaper parallax effect should span 1.2 * screen width
908         // We will use these two data points to extrapolate how much the wallpaper parallax effect
909         // to span (ie travel) at any aspect ratio:
910 
911         final float ASPECT_RATIO_LANDSCAPE = 16 / 10f;
912         final float ASPECT_RATIO_PORTRAIT = 10 / 16f;
913         final float WALLPAPER_WIDTH_TO_SCREEN_RATIO_LANDSCAPE = 1.5f;
914         final float WALLPAPER_WIDTH_TO_SCREEN_RATIO_PORTRAIT = 1.2f;
915 
916         // To find out the desired width at different aspect ratios, we use the following two
917         // formulas, where the coefficient on x is the aspect ratio (width/height):
918         //   (16/10)x + y = 1.5
919         //   (10/16)x + y = 1.2
920         // We solve for x and y and end up with a final formula:
921         final float x =
922                 (WALLPAPER_WIDTH_TO_SCREEN_RATIO_LANDSCAPE
923                         - WALLPAPER_WIDTH_TO_SCREEN_RATIO_PORTRAIT) /
924                         (ASPECT_RATIO_LANDSCAPE - ASPECT_RATIO_PORTRAIT);
925         final float y = WALLPAPER_WIDTH_TO_SCREEN_RATIO_PORTRAIT - x * ASPECT_RATIO_PORTRAIT;
926         return x * aspectRatio + y;
927     }
928 
929     public interface OnIDPChangeListener {
930 
931         /**
932          * Called when the device provide changes
933          */
934         void onIdpChanged(boolean modelPropertiesChanged);
935     }
936 
937 
938     public static final class GridOption {
939 
940         public static final String TAG_NAME = "grid-option";
941 
942         private static final int DEVICE_CATEGORY_PHONE = 1 << 0;
943         private static final int DEVICE_CATEGORY_TABLET = 1 << 1;
944         private static final int DEVICE_CATEGORY_MULTI_DISPLAY = 1 << 2;
945         private static final int DEVICE_CATEGORY_ANY =
946                 DEVICE_CATEGORY_PHONE | DEVICE_CATEGORY_TABLET | DEVICE_CATEGORY_MULTI_DISPLAY;
947 
948         private static final int INLINE_QSB_FOR_PORTRAIT = 1 << 0;
949         private static final int INLINE_QSB_FOR_LANDSCAPE = 1 << 1;
950         private static final int INLINE_QSB_FOR_TWO_PANEL_PORTRAIT = 1 << 2;
951         private static final int INLINE_QSB_FOR_TWO_PANEL_LANDSCAPE = 1 << 3;
952         private static final int DONT_INLINE_QSB = 0;
953 
954         public final String name;
955         public final String gridTitle;
956         public final int gridIconId;
957         public final int numRows;
958         public final int numColumns;
959         public final int numSearchContainerColumns;
960         public final int deviceCategory;
961         @GridType
962         public final int gridType;
963 
964         private final int[] numFolderRows = new int[COUNT_SIZES];
965         private final int[] numFolderColumns = new int[COUNT_SIZES];
966         private final @StyleRes int folderStyle;
967         private final @StyleRes int cellStyle;
968 
969         private final @StyleRes int allAppsStyle;
970         private final int numAllAppsColumns;
971         private final int mNumAllAppsRowsForCellHeightCalculation;
972         private final int numDatabaseAllAppsColumns;
973         private final int numHotseatIcons;
974         private final int numDatabaseHotseatIcons;
975 
976         private final boolean[] inlineQsb = new boolean[COUNT_SIZES];
977 
978         private @DimenRes int inlineNavButtonsEndSpacing;
979         private final String dbFile;
980 
981         private final int defaultLayoutId;
982         private final int demoModeLayoutId;
983 
984         private final boolean isScalable;
985         private final boolean mIsDualGrid;
986         private final int devicePaddingId;
987         private final int mWorkspaceSpecsId;
988         private final int mWorkspaceSpecsTwoPanelId;
989         private final int mAllAppsSpecsId;
990         private final int mAllAppsSpecsTwoPanelId;
991         private final int mFolderSpecsId;
992         private final int mFolderSpecsTwoPanelId;
993         private final int mHotseatSpecsId;
994         private final int mHotseatSpecsTwoPanelId;
995         private final int mWorkspaceCellSpecsId;
996         private final int mWorkspaceCellSpecsTwoPanelId;
997         private final int mAllAppsCellSpecsId;
998         private final int mAllAppsCellSpecsTwoPanelId;
999         private final int mGridSizeSpecsId;
1000         private final boolean mIsFixedLandscape;
1001 
GridOption(Context context, AttributeSet attrs, Info displayInfo)1002         public GridOption(Context context, AttributeSet attrs, Info displayInfo) {
1003             TypedArray a = context.obtainStyledAttributes(
1004                     attrs, R.styleable.GridDisplayOption);
1005             name = a.getString(R.styleable.GridDisplayOption_name);
1006             gridTitle = a.getString(R.styleable.GridDisplayOption_gridTitle);
1007             gridIconId = a.getResourceId(
1008                     R.styleable.GridDisplayOption_gridIconId, INVALID_RESOURCE_HANDLE);
1009             deviceCategory = a.getInt(R.styleable.GridDisplayOption_deviceCategory,
1010                     DEVICE_CATEGORY_ANY);
1011             mGridSizeSpecsId = a.getResourceId(
1012                     R.styleable.GridDisplayOption_gridSizeSpecsId, INVALID_RESOURCE_HANDLE);
1013             mIsDualGrid = a.getBoolean(R.styleable.GridDisplayOption_isDualGrid, false);
1014             if (mGridSizeSpecsId != INVALID_RESOURCE_HANDLE) {
1015                 ResourceHelper resourceHelper = new ResourceHelper(context, mGridSizeSpecsId);
1016                 GridSize gridSize = getGridSize(resourceHelper, context, displayInfo);
1017                 numColumns = gridSize.mNumColumns;
1018                 numRows = gridSize.mNumRows;
1019                 dbFile = gridSize.mDbFile;
1020                 defaultLayoutId = gridSize.mDefaultLayoutId;
1021                 demoModeLayoutId = gridSize.mDemoModeLayoutId;
1022             } else {
1023                 numRows = a.getInt(R.styleable.GridDisplayOption_numRows, 0);
1024                 numColumns = a.getInt(R.styleable.GridDisplayOption_numColumns, 0);
1025                 dbFile = a.getString(R.styleable.GridDisplayOption_dbFile);
1026                 defaultLayoutId = a.getResourceId(
1027                         R.styleable.GridDisplayOption_defaultLayoutId, 0);
1028                 demoModeLayoutId = a.getResourceId(
1029                         R.styleable.GridDisplayOption_demoModeLayoutId, defaultLayoutId);
1030             }
1031 
1032             numSearchContainerColumns = a.getInt(
1033                     R.styleable.GridDisplayOption_numSearchContainerColumns, numColumns);
1034 
1035             allAppsStyle = a.getResourceId(R.styleable.GridDisplayOption_allAppsStyle,
1036                     R.style.AllAppsStyleDefault);
1037             numAllAppsColumns = a.getInt(
1038                     R.styleable.GridDisplayOption_numAllAppsColumns, numColumns);
1039             numDatabaseAllAppsColumns = a.getInt(
1040                     R.styleable.GridDisplayOption_numExtendedAllAppsColumns, 2 * numAllAppsColumns);
1041 
1042             numHotseatIcons = a.getInt(
1043                     R.styleable.GridDisplayOption_numHotseatIcons, numColumns);
1044             numDatabaseHotseatIcons = a.getInt(
1045                     R.styleable.GridDisplayOption_numExtendedHotseatIcons, 2 * numHotseatIcons);
1046 
1047             inlineNavButtonsEndSpacing =
1048                     a.getResourceId(R.styleable.GridDisplayOption_inlineNavButtonsEndSpacing,
1049                             R.dimen.taskbar_button_margin_default);
1050 
1051             numFolderRows[INDEX_DEFAULT] = a.getInt(
1052                     R.styleable.GridDisplayOption_numFolderRows, numRows);
1053             numFolderColumns[INDEX_DEFAULT] = a.getInt(
1054                     R.styleable.GridDisplayOption_numFolderColumns, numColumns);
1055 
1056             if (FeatureFlags.enableResponsiveWorkspace()) {
1057                 numFolderRows[INDEX_LANDSCAPE] = a.getInt(
1058                         R.styleable.GridDisplayOption_numFolderRowsLandscape,
1059                         numFolderRows[INDEX_DEFAULT]);
1060                 numFolderColumns[INDEX_LANDSCAPE] = a.getInt(
1061                         R.styleable.GridDisplayOption_numFolderColumnsLandscape,
1062                         numFolderColumns[INDEX_DEFAULT]);
1063                 numFolderRows[INDEX_TWO_PANEL_PORTRAIT] = a.getInt(
1064                         R.styleable.GridDisplayOption_numFolderRowsTwoPanelPortrait,
1065                         numFolderRows[INDEX_DEFAULT]);
1066                 numFolderColumns[INDEX_TWO_PANEL_PORTRAIT] = a.getInt(
1067                         R.styleable.GridDisplayOption_numFolderColumnsTwoPanelPortrait,
1068                         numFolderColumns[INDEX_DEFAULT]);
1069                 numFolderRows[INDEX_TWO_PANEL_LANDSCAPE] = a.getInt(
1070                         R.styleable.GridDisplayOption_numFolderRowsTwoPanelLandscape,
1071                         numFolderRows[INDEX_DEFAULT]);
1072                 numFolderColumns[INDEX_TWO_PANEL_LANDSCAPE] = a.getInt(
1073                         R.styleable.GridDisplayOption_numFolderColumnsTwoPanelLandscape,
1074                         numFolderColumns[INDEX_DEFAULT]);
1075             } else {
1076                 numFolderRows[INDEX_LANDSCAPE] = numFolderRows[INDEX_DEFAULT];
1077                 numFolderColumns[INDEX_LANDSCAPE] = numFolderColumns[INDEX_DEFAULT];
1078                 numFolderRows[INDEX_TWO_PANEL_PORTRAIT] = numFolderRows[INDEX_DEFAULT];
1079                 numFolderColumns[INDEX_TWO_PANEL_PORTRAIT] = numFolderColumns[INDEX_DEFAULT];
1080                 numFolderRows[INDEX_TWO_PANEL_LANDSCAPE] = numFolderRows[INDEX_DEFAULT];
1081                 numFolderColumns[INDEX_TWO_PANEL_LANDSCAPE] = numFolderColumns[INDEX_DEFAULT];
1082             }
1083 
1084             folderStyle = a.getResourceId(R.styleable.GridDisplayOption_folderStyle,
1085                     INVALID_RESOURCE_HANDLE);
1086 
1087             cellStyle = a.getResourceId(R.styleable.GridDisplayOption_cellStyle,
1088                     R.style.CellStyleDefault);
1089 
1090             isScalable = a.getBoolean(
1091                     R.styleable.GridDisplayOption_isScalable, false);
1092             devicePaddingId = a.getResourceId(
1093                     R.styleable.GridDisplayOption_devicePaddingId, INVALID_RESOURCE_HANDLE);
1094 
1095             if (FeatureFlags.enableResponsiveWorkspace()) {
1096                 mWorkspaceSpecsId = a.getResourceId(
1097                         R.styleable.GridDisplayOption_workspaceSpecsId, INVALID_RESOURCE_HANDLE);
1098                 mWorkspaceSpecsTwoPanelId = a.getResourceId(
1099                         R.styleable.GridDisplayOption_workspaceSpecsTwoPanelId,
1100                         mWorkspaceSpecsId);
1101                 mAllAppsSpecsId = a.getResourceId(
1102                         R.styleable.GridDisplayOption_allAppsSpecsId, INVALID_RESOURCE_HANDLE);
1103                 mAllAppsSpecsTwoPanelId = a.getResourceId(
1104                         R.styleable.GridDisplayOption_allAppsSpecsTwoPanelId,
1105                         mAllAppsSpecsId);
1106                 mFolderSpecsId = a.getResourceId(
1107                         R.styleable.GridDisplayOption_folderSpecsId, INVALID_RESOURCE_HANDLE);
1108                 mFolderSpecsTwoPanelId = a.getResourceId(
1109                         R.styleable.GridDisplayOption_folderSpecsTwoPanelId,
1110                         mFolderSpecsId);
1111                 mHotseatSpecsId = a.getResourceId(
1112                         R.styleable.GridDisplayOption_hotseatSpecsId, INVALID_RESOURCE_HANDLE);
1113                 mHotseatSpecsTwoPanelId = a.getResourceId(
1114                         R.styleable.GridDisplayOption_hotseatSpecsTwoPanelId,
1115                         mHotseatSpecsId);
1116                 mWorkspaceCellSpecsId = a.getResourceId(
1117                         R.styleable.GridDisplayOption_workspaceCellSpecsId,
1118                         INVALID_RESOURCE_HANDLE);
1119                 mWorkspaceCellSpecsTwoPanelId = a.getResourceId(
1120                         R.styleable.GridDisplayOption_workspaceCellSpecsTwoPanelId,
1121                         mWorkspaceCellSpecsId);
1122                 mAllAppsCellSpecsId = a.getResourceId(
1123                         R.styleable.GridDisplayOption_allAppsCellSpecsId,
1124                         INVALID_RESOURCE_HANDLE);
1125                 mAllAppsCellSpecsTwoPanelId = a.getResourceId(
1126                         R.styleable.GridDisplayOption_allAppsCellSpecsTwoPanelId,
1127                         mAllAppsCellSpecsId);
1128                 mNumAllAppsRowsForCellHeightCalculation = a.getInt(
1129                         R.styleable.GridDisplayOption_numAllAppsRowsForCellHeightCalculation,
1130                         numRows);
1131             } else {
1132                 mWorkspaceSpecsId = INVALID_RESOURCE_HANDLE;
1133                 mWorkspaceSpecsTwoPanelId = INVALID_RESOURCE_HANDLE;
1134                 mAllAppsSpecsId = INVALID_RESOURCE_HANDLE;
1135                 mAllAppsSpecsTwoPanelId = INVALID_RESOURCE_HANDLE;
1136                 mFolderSpecsId = INVALID_RESOURCE_HANDLE;
1137                 mFolderSpecsTwoPanelId = INVALID_RESOURCE_HANDLE;
1138                 mHotseatSpecsId = INVALID_RESOURCE_HANDLE;
1139                 mHotseatSpecsTwoPanelId = INVALID_RESOURCE_HANDLE;
1140                 mWorkspaceCellSpecsId = INVALID_RESOURCE_HANDLE;
1141                 mWorkspaceCellSpecsTwoPanelId = INVALID_RESOURCE_HANDLE;
1142                 mAllAppsCellSpecsId = INVALID_RESOURCE_HANDLE;
1143                 mAllAppsCellSpecsTwoPanelId = INVALID_RESOURCE_HANDLE;
1144                 mNumAllAppsRowsForCellHeightCalculation = numRows;
1145             }
1146 
1147             mIsFixedLandscape = a.getBoolean(R.styleable.GridDisplayOption_isFixedLandscape, false);
1148             gridType = a.getInt(R.styleable.GridDisplayOption_gridType, GRID_TYPE_ANY);
1149 
1150             int inlineForRotation = a.getInt(R.styleable.GridDisplayOption_inlineQsb,
1151                     DONT_INLINE_QSB);
1152             inlineQsb[INDEX_DEFAULT] =
1153                     (inlineForRotation & INLINE_QSB_FOR_PORTRAIT) == INLINE_QSB_FOR_PORTRAIT;
1154             inlineQsb[INDEX_LANDSCAPE] =
1155                     (inlineForRotation & INLINE_QSB_FOR_LANDSCAPE) == INLINE_QSB_FOR_LANDSCAPE;
1156             inlineQsb[INDEX_TWO_PANEL_PORTRAIT] =
1157                     (inlineForRotation & INLINE_QSB_FOR_TWO_PANEL_PORTRAIT)
1158                             == INLINE_QSB_FOR_TWO_PANEL_PORTRAIT;
1159             inlineQsb[INDEX_TWO_PANEL_LANDSCAPE] =
1160                     (inlineForRotation & INLINE_QSB_FOR_TWO_PANEL_LANDSCAPE)
1161                             == INLINE_QSB_FOR_TWO_PANEL_LANDSCAPE;
1162 
1163             a.recycle();
1164         }
1165 
isEnabled(@eviceType int deviceType)1166         public boolean isEnabled(@DeviceType int deviceType) {
1167             switch (deviceType) {
1168                 case TYPE_PHONE:
1169                     return (deviceCategory & DEVICE_CATEGORY_PHONE) == DEVICE_CATEGORY_PHONE;
1170                 case TYPE_TABLET:
1171                     return (deviceCategory & DEVICE_CATEGORY_TABLET) == DEVICE_CATEGORY_TABLET;
1172                 case TYPE_MULTI_DISPLAY:
1173                     return (deviceCategory & DEVICE_CATEGORY_MULTI_DISPLAY)
1174                             == DEVICE_CATEGORY_MULTI_DISPLAY;
1175                 default:
1176                     return false;
1177             }
1178         }
1179 
1180         /**
1181          * Returns true if the grid option should be used given the flags that are toggled on/off.
1182          */
filterByFlag(int deviceType, boolean isFixedLandscape)1183         public boolean filterByFlag(int deviceType, boolean isFixedLandscape) {
1184             if (deviceType == TYPE_TABLET) {
1185                 return Flags.oneGridRotationHandling() == mIsDualGrid;
1186             }
1187 
1188             // Here we return true if fixed landscape mode should be on.
1189             if (mIsFixedLandscape || isFixedLandscape) {
1190                 return mIsFixedLandscape && isFixedLandscape && Flags.oneGridSpecs();
1191             }
1192 
1193             // If the grid type is one grid we return true when the flag is on, if the grid type
1194             // is non-one grid we return true when the flag is off. Otherwise, we return true.
1195             if (gridType == GRID_TYPE_ONE_GRID) {
1196                 return Flags.oneGridSpecs();
1197             } else if (gridType == GRID_TYPE_NON_ONE_GRID) {
1198                 return !Flags.oneGridSpecs();
1199             }
1200 
1201             return true;
1202         }
1203     }
1204 
1205     public static final class GridSize {
1206         final int mNumRows;
1207         final int mNumColumns;
1208         final float mMinDeviceWidthPx;
1209         final float mMinDeviceHeightPx;
1210         final String mDbFile;
1211         final int mDefaultLayoutId;
1212         final int mDemoModeLayoutId;
1213 
1214 
GridSize(Context context, AttributeSet attrs)1215         GridSize(Context context, AttributeSet attrs) {
1216             TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.GridSize);
1217 
1218             mNumRows = (int) a.getFloat(R.styleable.GridSize_numGridRows, 0);
1219             mNumColumns = (int) a.getFloat(R.styleable.GridSize_numGridColumns, 0);
1220             mMinDeviceWidthPx = a.getFloat(R.styleable.GridSize_minDeviceWidthPx, 0);
1221             mMinDeviceHeightPx = a.getFloat(R.styleable.GridSize_minDeviceHeightPx, 0);
1222             mDbFile = a.getString(R.styleable.GridSize_dbFile);
1223             mDefaultLayoutId = a.getResourceId(
1224                     R.styleable.GridSize_defaultLayoutId, 0);
1225             mDemoModeLayoutId = a.getResourceId(
1226                     R.styleable.GridSize_demoModeLayoutId, mDefaultLayoutId);
1227 
1228             a.recycle();
1229         }
1230     }
1231 
1232     @VisibleForTesting
1233     static final class DisplayOption {
1234         public final GridOption grid;
1235 
1236         private final float minWidthDps;
1237         private final float minHeightDps;
1238         private final boolean canBeDefault;
1239 
1240         private final PointF[] minCellSize = new PointF[COUNT_SIZES];
1241 
1242         private final PointF[] borderSpaces = new PointF[COUNT_SIZES];
1243         private final float[] horizontalMargin = new float[COUNT_SIZES];
1244         private final float[] hotseatBarBottomSpace = new float[COUNT_SIZES];
1245         private final float[] hotseatQsbSpace = new float[COUNT_SIZES];
1246 
1247         private final float[] iconSizes = new float[COUNT_SIZES];
1248         private final float[] textSizes = new float[COUNT_SIZES];
1249 
1250         private final PointF[] allAppsCellSize = new PointF[COUNT_SIZES];
1251         private final float[] allAppsIconSizes = new float[COUNT_SIZES];
1252         private final float[] allAppsIconTextSizes = new float[COUNT_SIZES];
1253         private final PointF[] allAppsBorderSpaces = new PointF[COUNT_SIZES];
1254 
1255         private final float[] transientTaskbarIconSize = new float[COUNT_SIZES];
1256 
1257         private final boolean[] startAlignTaskbar = new boolean[COUNT_SIZES];
1258 
DisplayOption(GridOption grid, Context context, AttributeSet attrs)1259         DisplayOption(GridOption grid, Context context, AttributeSet attrs) {
1260             this.grid = grid;
1261 
1262             Resources res = context.getResources();
1263 
1264             TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.ProfileDisplayOption);
1265 
1266             minWidthDps = a.getFloat(R.styleable.ProfileDisplayOption_minWidthDps, 0);
1267             minHeightDps = a.getFloat(R.styleable.ProfileDisplayOption_minHeightDps, 0);
1268 
1269             canBeDefault = a.getBoolean(R.styleable.ProfileDisplayOption_canBeDefault, false);
1270 
1271             float x;
1272             float y;
1273 
1274             x = a.getFloat(R.styleable.ProfileDisplayOption_minCellWidth, 0);
1275             y = a.getFloat(R.styleable.ProfileDisplayOption_minCellHeight, 0);
1276             minCellSize[INDEX_DEFAULT] = new PointF(x, y);
1277 
1278             x = a.getFloat(R.styleable.ProfileDisplayOption_minCellWidthLandscape,
1279                     minCellSize[INDEX_DEFAULT].x);
1280             y = a.getFloat(R.styleable.ProfileDisplayOption_minCellHeightLandscape,
1281                     minCellSize[INDEX_DEFAULT].y);
1282             minCellSize[INDEX_LANDSCAPE] = new PointF(x, y);
1283 
1284             x = a.getFloat(R.styleable.ProfileDisplayOption_minCellWidthTwoPanelPortrait,
1285                     minCellSize[INDEX_DEFAULT].x);
1286             y = a.getFloat(R.styleable.ProfileDisplayOption_minCellHeightTwoPanelPortrait,
1287                     minCellSize[INDEX_DEFAULT].y);
1288             minCellSize[INDEX_TWO_PANEL_PORTRAIT] = new PointF(x, y);
1289 
1290             x = a.getFloat(R.styleable.ProfileDisplayOption_minCellWidthTwoPanelLandscape,
1291                     minCellSize[INDEX_DEFAULT].x);
1292             y = a.getFloat(R.styleable.ProfileDisplayOption_minCellHeightTwoPanelLandscape,
1293                     minCellSize[INDEX_DEFAULT].y);
1294             minCellSize[INDEX_TWO_PANEL_LANDSCAPE] = new PointF(x, y);
1295 
1296             float borderSpace = a.getFloat(R.styleable.ProfileDisplayOption_borderSpace, 0);
1297             float borderSpaceLandscape = a.getFloat(
1298                     R.styleable.ProfileDisplayOption_borderSpaceLandscape, borderSpace);
1299             float borderSpaceTwoPanelPortrait = a.getFloat(
1300                     R.styleable.ProfileDisplayOption_borderSpaceTwoPanelPortrait, borderSpace);
1301             float borderSpaceTwoPanelLandscape = a.getFloat(
1302                     R.styleable.ProfileDisplayOption_borderSpaceTwoPanelLandscape, borderSpace);
1303 
1304             x = a.getFloat(R.styleable.ProfileDisplayOption_borderSpaceHorizontal, borderSpace);
1305             y = a.getFloat(R.styleable.ProfileDisplayOption_borderSpaceVertical, borderSpace);
1306             borderSpaces[INDEX_DEFAULT] = new PointF(x, y);
1307 
1308             x = a.getFloat(R.styleable.ProfileDisplayOption_borderSpaceLandscapeHorizontal,
1309                     borderSpaceLandscape);
1310             y = a.getFloat(R.styleable.ProfileDisplayOption_borderSpaceLandscapeVertical,
1311                     borderSpaceLandscape);
1312             borderSpaces[INDEX_LANDSCAPE] = new PointF(x, y);
1313 
1314             x = a.getFloat(
1315                     R.styleable.ProfileDisplayOption_borderSpaceTwoPanelPortraitHorizontal,
1316                     borderSpaceTwoPanelPortrait);
1317             y = a.getFloat(
1318                     R.styleable.ProfileDisplayOption_borderSpaceTwoPanelPortraitVertical,
1319                     borderSpaceTwoPanelPortrait);
1320             borderSpaces[INDEX_TWO_PANEL_PORTRAIT] = new PointF(x, y);
1321 
1322             x = a.getFloat(
1323                     R.styleable.ProfileDisplayOption_borderSpaceTwoPanelLandscapeHorizontal,
1324                     borderSpaceTwoPanelLandscape);
1325             y = a.getFloat(
1326                     R.styleable.ProfileDisplayOption_borderSpaceTwoPanelLandscapeVertical,
1327                     borderSpaceTwoPanelLandscape);
1328             borderSpaces[INDEX_TWO_PANEL_LANDSCAPE] = new PointF(x, y);
1329 
1330             x = a.getFloat(R.styleable.ProfileDisplayOption_allAppsCellWidth,
1331                     minCellSize[INDEX_DEFAULT].x);
1332             y = a.getFloat(R.styleable.ProfileDisplayOption_allAppsCellHeight,
1333                     minCellSize[INDEX_DEFAULT].y);
1334             allAppsCellSize[INDEX_DEFAULT] = new PointF(x, y);
1335 
1336             x = a.getFloat(R.styleable.ProfileDisplayOption_allAppsCellWidthLandscape,
1337                     allAppsCellSize[INDEX_DEFAULT].x);
1338             y = a.getFloat(R.styleable.ProfileDisplayOption_allAppsCellHeightLandscape,
1339                     allAppsCellSize[INDEX_DEFAULT].y);
1340             allAppsCellSize[INDEX_LANDSCAPE] = new PointF(x, y);
1341 
1342             x = a.getFloat(R.styleable.ProfileDisplayOption_allAppsCellWidthTwoPanelPortrait,
1343                     allAppsCellSize[INDEX_DEFAULT].x);
1344             y = a.getFloat(R.styleable.ProfileDisplayOption_allAppsCellHeightTwoPanelPortrait,
1345                     allAppsCellSize[INDEX_DEFAULT].y);
1346             allAppsCellSize[INDEX_TWO_PANEL_PORTRAIT] = new PointF(x, y);
1347 
1348             x = a.getFloat(R.styleable.ProfileDisplayOption_allAppsCellWidthTwoPanelLandscape,
1349                     allAppsCellSize[INDEX_DEFAULT].x);
1350             y = a.getFloat(R.styleable.ProfileDisplayOption_allAppsCellHeightTwoPanelLandscape,
1351                     allAppsCellSize[INDEX_DEFAULT].y);
1352             allAppsCellSize[INDEX_TWO_PANEL_LANDSCAPE] = new PointF(x, y);
1353 
1354             float allAppsBorderSpace = a.getFloat(
1355                     R.styleable.ProfileDisplayOption_allAppsBorderSpace, borderSpace);
1356             float allAppsBorderSpaceLandscape = a.getFloat(
1357                     R.styleable.ProfileDisplayOption_allAppsBorderSpaceLandscape,
1358                     allAppsBorderSpace);
1359             float allAppsBorderSpaceTwoPanelPortrait = a.getFloat(
1360                     R.styleable.ProfileDisplayOption_allAppsBorderSpaceTwoPanelPortrait,
1361                     allAppsBorderSpace);
1362             float allAppsBorderSpaceTwoPanelLandscape = a.getFloat(
1363                     R.styleable.ProfileDisplayOption_allAppsBorderSpaceTwoPanelLandscape,
1364                     allAppsBorderSpace);
1365 
1366             x = a.getFloat(R.styleable.ProfileDisplayOption_allAppsBorderSpaceHorizontal,
1367                     allAppsBorderSpace);
1368             y = a.getFloat(R.styleable.ProfileDisplayOption_allAppsBorderSpaceVertical,
1369                     allAppsBorderSpace);
1370             allAppsBorderSpaces[INDEX_DEFAULT] = new PointF(x, y);
1371 
1372             x = a.getFloat(R.styleable.ProfileDisplayOption_allAppsBorderSpaceLandscapeHorizontal,
1373                     allAppsBorderSpaceLandscape);
1374             y = a.getFloat(R.styleable.ProfileDisplayOption_allAppsBorderSpaceLandscapeVertical,
1375                     allAppsBorderSpaceLandscape);
1376             allAppsBorderSpaces[INDEX_LANDSCAPE] = new PointF(x, y);
1377 
1378             x = a.getFloat(
1379                     R.styleable.ProfileDisplayOption_allAppsBorderSpaceTwoPanelPortraitHorizontal,
1380                     allAppsBorderSpaceTwoPanelPortrait);
1381             y = a.getFloat(
1382                     R.styleable.ProfileDisplayOption_allAppsBorderSpaceTwoPanelPortraitVertical,
1383                     allAppsBorderSpaceTwoPanelPortrait);
1384             allAppsBorderSpaces[INDEX_TWO_PANEL_PORTRAIT] = new PointF(x, y);
1385 
1386             x = a.getFloat(
1387                     R.styleable.ProfileDisplayOption_allAppsBorderSpaceTwoPanelLandscapeHorizontal,
1388                     allAppsBorderSpaceTwoPanelLandscape);
1389             y = a.getFloat(
1390                     R.styleable.ProfileDisplayOption_allAppsBorderSpaceTwoPanelLandscapeVertical,
1391                     allAppsBorderSpaceTwoPanelLandscape);
1392             allAppsBorderSpaces[INDEX_TWO_PANEL_LANDSCAPE] = new PointF(x, y);
1393 
1394             iconSizes[INDEX_DEFAULT] =
1395                     a.getFloat(R.styleable.ProfileDisplayOption_iconImageSize, 0);
1396             iconSizes[INDEX_LANDSCAPE] =
1397                     a.getFloat(R.styleable.ProfileDisplayOption_iconSizeLandscape,
1398                             iconSizes[INDEX_DEFAULT]);
1399             iconSizes[INDEX_TWO_PANEL_PORTRAIT] =
1400                     a.getFloat(R.styleable.ProfileDisplayOption_iconSizeTwoPanelPortrait,
1401                             iconSizes[INDEX_DEFAULT]);
1402             iconSizes[INDEX_TWO_PANEL_LANDSCAPE] =
1403                     a.getFloat(R.styleable.ProfileDisplayOption_iconSizeTwoPanelLandscape,
1404                             iconSizes[INDEX_DEFAULT]);
1405 
1406             allAppsIconSizes[INDEX_DEFAULT] = a.getFloat(
1407                     R.styleable.ProfileDisplayOption_allAppsIconSize, iconSizes[INDEX_DEFAULT]);
1408             allAppsIconSizes[INDEX_LANDSCAPE] = a.getFloat(
1409                     R.styleable.ProfileDisplayOption_allAppsIconSizeLandscape,
1410                     allAppsIconSizes[INDEX_DEFAULT]);
1411             allAppsIconSizes[INDEX_TWO_PANEL_PORTRAIT] = a.getFloat(
1412                     R.styleable.ProfileDisplayOption_allAppsIconSizeTwoPanelPortrait,
1413                     allAppsIconSizes[INDEX_DEFAULT]);
1414             allAppsIconSizes[INDEX_TWO_PANEL_LANDSCAPE] = a.getFloat(
1415                     R.styleable.ProfileDisplayOption_allAppsIconSizeTwoPanelLandscape,
1416                     allAppsIconSizes[INDEX_DEFAULT]);
1417 
1418             textSizes[INDEX_DEFAULT] =
1419                     a.getFloat(R.styleable.ProfileDisplayOption_iconTextSize, 0);
1420             textSizes[INDEX_LANDSCAPE] =
1421                     a.getFloat(R.styleable.ProfileDisplayOption_iconTextSizeLandscape,
1422                             textSizes[INDEX_DEFAULT]);
1423             textSizes[INDEX_TWO_PANEL_PORTRAIT] =
1424                     a.getFloat(R.styleable.ProfileDisplayOption_iconTextSizeTwoPanelPortrait,
1425                             textSizes[INDEX_DEFAULT]);
1426             textSizes[INDEX_TWO_PANEL_LANDSCAPE] =
1427                     a.getFloat(R.styleable.ProfileDisplayOption_iconTextSizeTwoPanelLandscape,
1428                             textSizes[INDEX_DEFAULT]);
1429 
1430             allAppsIconTextSizes[INDEX_DEFAULT] = a.getFloat(
1431                     R.styleable.ProfileDisplayOption_allAppsIconTextSize, textSizes[INDEX_DEFAULT]);
1432             allAppsIconTextSizes[INDEX_LANDSCAPE] = allAppsIconTextSizes[INDEX_DEFAULT];
1433             allAppsIconTextSizes[INDEX_TWO_PANEL_PORTRAIT] = a.getFloat(
1434                     R.styleable.ProfileDisplayOption_allAppsIconTextSizeTwoPanelPortrait,
1435                     allAppsIconTextSizes[INDEX_DEFAULT]);
1436             allAppsIconTextSizes[INDEX_TWO_PANEL_LANDSCAPE] = a.getFloat(
1437                     R.styleable.ProfileDisplayOption_allAppsIconTextSizeTwoPanelLandscape,
1438                     allAppsIconTextSizes[INDEX_DEFAULT]);
1439 
1440             horizontalMargin[INDEX_DEFAULT] = a.getFloat(
1441                     R.styleable.ProfileDisplayOption_horizontalMargin, 0);
1442             horizontalMargin[INDEX_LANDSCAPE] = a.getFloat(
1443                     R.styleable.ProfileDisplayOption_horizontalMarginLandscape,
1444                     horizontalMargin[INDEX_DEFAULT]);
1445             horizontalMargin[INDEX_TWO_PANEL_LANDSCAPE] = a.getFloat(
1446                     R.styleable.ProfileDisplayOption_horizontalMarginTwoPanelLandscape,
1447                     horizontalMargin[INDEX_DEFAULT]);
1448             horizontalMargin[INDEX_TWO_PANEL_PORTRAIT] = a.getFloat(
1449                     R.styleable.ProfileDisplayOption_horizontalMarginTwoPanelPortrait,
1450                     horizontalMargin[INDEX_DEFAULT]);
1451 
1452             hotseatBarBottomSpace[INDEX_DEFAULT] = a.getFloat(
1453                     R.styleable.ProfileDisplayOption_hotseatBarBottomSpace,
1454                     ResourcesCompat.getFloat(res, R.dimen.hotseat_bar_bottom_space_default));
1455             hotseatBarBottomSpace[INDEX_LANDSCAPE] = a.getFloat(
1456                     R.styleable.ProfileDisplayOption_hotseatBarBottomSpaceLandscape,
1457                     hotseatBarBottomSpace[INDEX_DEFAULT]);
1458             hotseatBarBottomSpace[INDEX_TWO_PANEL_LANDSCAPE] = a.getFloat(
1459                     R.styleable.ProfileDisplayOption_hotseatBarBottomSpaceTwoPanelLandscape,
1460                     hotseatBarBottomSpace[INDEX_DEFAULT]);
1461             hotseatBarBottomSpace[INDEX_TWO_PANEL_PORTRAIT] = a.getFloat(
1462                     R.styleable.ProfileDisplayOption_hotseatBarBottomSpaceTwoPanelPortrait,
1463                     hotseatBarBottomSpace[INDEX_DEFAULT]);
1464 
1465             hotseatQsbSpace[INDEX_DEFAULT] = a.getFloat(
1466                     R.styleable.ProfileDisplayOption_hotseatQsbSpace,
1467                     ResourcesCompat.getFloat(res, R.dimen.hotseat_qsb_space_default));
1468             hotseatQsbSpace[INDEX_LANDSCAPE] = a.getFloat(
1469                     R.styleable.ProfileDisplayOption_hotseatQsbSpaceLandscape,
1470                     hotseatQsbSpace[INDEX_DEFAULT]);
1471             hotseatQsbSpace[INDEX_TWO_PANEL_LANDSCAPE] = a.getFloat(
1472                     R.styleable.ProfileDisplayOption_hotseatQsbSpaceTwoPanelLandscape,
1473                     hotseatQsbSpace[INDEX_DEFAULT]);
1474             hotseatQsbSpace[INDEX_TWO_PANEL_PORTRAIT] = a.getFloat(
1475                     R.styleable.ProfileDisplayOption_hotseatQsbSpaceTwoPanelPortrait,
1476                     hotseatQsbSpace[INDEX_DEFAULT]);
1477 
1478             transientTaskbarIconSize[INDEX_DEFAULT] = a.getFloat(
1479                     R.styleable.ProfileDisplayOption_transientTaskbarIconSize,
1480                     ResourcesCompat.getFloat(res, R.dimen.taskbar_icon_size));
1481             transientTaskbarIconSize[INDEX_LANDSCAPE] = a.getFloat(
1482                     R.styleable.ProfileDisplayOption_transientTaskbarIconSizeLandscape,
1483                     transientTaskbarIconSize[INDEX_DEFAULT]);
1484             transientTaskbarIconSize[INDEX_TWO_PANEL_LANDSCAPE] = a.getFloat(
1485                     R.styleable.ProfileDisplayOption_transientTaskbarIconSizeTwoPanelLandscape,
1486                     transientTaskbarIconSize[INDEX_DEFAULT]);
1487             transientTaskbarIconSize[INDEX_TWO_PANEL_PORTRAIT] = a.getFloat(
1488                     R.styleable.ProfileDisplayOption_transientTaskbarIconSizeTwoPanelPortrait,
1489                     transientTaskbarIconSize[INDEX_DEFAULT]);
1490 
1491             startAlignTaskbar[INDEX_DEFAULT] = a.getBoolean(
1492                     R.styleable.ProfileDisplayOption_startAlignTaskbar, false);
1493             startAlignTaskbar[INDEX_LANDSCAPE] = a.getBoolean(
1494                     R.styleable.ProfileDisplayOption_startAlignTaskbarLandscape,
1495                     startAlignTaskbar[INDEX_DEFAULT]);
1496             startAlignTaskbar[INDEX_TWO_PANEL_LANDSCAPE] = a.getBoolean(
1497                     R.styleable.ProfileDisplayOption_startAlignTaskbarTwoPanelLandscape,
1498                     startAlignTaskbar[INDEX_LANDSCAPE]);
1499             startAlignTaskbar[INDEX_TWO_PANEL_PORTRAIT] = a.getBoolean(
1500                     R.styleable.ProfileDisplayOption_startAlignTaskbarTwoPanelPortrait,
1501                     startAlignTaskbar[INDEX_DEFAULT]);
1502 
1503             a.recycle();
1504         }
1505 
DisplayOption()1506         DisplayOption() {
1507             this(null);
1508         }
1509 
DisplayOption(GridOption grid)1510         DisplayOption(GridOption grid) {
1511             this.grid = grid;
1512             minWidthDps = 0;
1513             minHeightDps = 0;
1514             canBeDefault = false;
1515             for (int i = 0; i < COUNT_SIZES; i++) {
1516                 iconSizes[i] = 0;
1517                 textSizes[i] = 0;
1518                 borderSpaces[i] = new PointF();
1519                 minCellSize[i] = new PointF();
1520                 allAppsCellSize[i] = new PointF();
1521                 allAppsIconSizes[i] = 0;
1522                 allAppsIconTextSizes[i] = 0;
1523                 allAppsBorderSpaces[i] = new PointF();
1524                 transientTaskbarIconSize[i] = 0;
1525                 startAlignTaskbar[i] = false;
1526             }
1527         }
1528 
multiply(float w)1529         private DisplayOption multiply(float w) {
1530             for (int i = 0; i < COUNT_SIZES; i++) {
1531                 iconSizes[i] *= w;
1532                 textSizes[i] *= w;
1533                 borderSpaces[i].x *= w;
1534                 borderSpaces[i].y *= w;
1535                 minCellSize[i].x *= w;
1536                 minCellSize[i].y *= w;
1537                 horizontalMargin[i] *= w;
1538                 hotseatBarBottomSpace[i] *= w;
1539                 hotseatQsbSpace[i] *= w;
1540                 allAppsCellSize[i].x *= w;
1541                 allAppsCellSize[i].y *= w;
1542                 allAppsIconSizes[i] *= w;
1543                 allAppsIconTextSizes[i] *= w;
1544                 allAppsBorderSpaces[i].x *= w;
1545                 allAppsBorderSpaces[i].y *= w;
1546                 transientTaskbarIconSize[i] *= w;
1547             }
1548 
1549             return this;
1550         }
1551 
add(DisplayOption p)1552         private DisplayOption add(DisplayOption p) {
1553             for (int i = 0; i < COUNT_SIZES; i++) {
1554                 iconSizes[i] += p.iconSizes[i];
1555                 textSizes[i] += p.textSizes[i];
1556                 borderSpaces[i].x += p.borderSpaces[i].x;
1557                 borderSpaces[i].y += p.borderSpaces[i].y;
1558                 minCellSize[i].x += p.minCellSize[i].x;
1559                 minCellSize[i].y += p.minCellSize[i].y;
1560                 horizontalMargin[i] += p.horizontalMargin[i];
1561                 hotseatBarBottomSpace[i] += p.hotseatBarBottomSpace[i];
1562                 hotseatQsbSpace[i] += p.hotseatQsbSpace[i];
1563                 allAppsCellSize[i].x += p.allAppsCellSize[i].x;
1564                 allAppsCellSize[i].y += p.allAppsCellSize[i].y;
1565                 allAppsIconSizes[i] += p.allAppsIconSizes[i];
1566                 allAppsIconTextSizes[i] += p.allAppsIconTextSizes[i];
1567                 allAppsBorderSpaces[i].x += p.allAppsBorderSpaces[i].x;
1568                 allAppsBorderSpaces[i].y += p.allAppsBorderSpaces[i].y;
1569                 transientTaskbarIconSize[i] += p.transientTaskbarIconSize[i];
1570                 startAlignTaskbar[i] |= p.startAlignTaskbar[i];
1571             }
1572 
1573             return this;
1574         }
1575     }
1576 }
1577