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