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