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