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