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