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