1 /* 2 * Copyright (C) 2006 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 android.content.res; 18 19 import android.annotation.Nullable; 20 import android.compat.annotation.UnsupportedAppUsage; 21 import android.content.pm.ApplicationInfo; 22 import android.graphics.Canvas; 23 import android.graphics.Insets; 24 import android.graphics.PointF; 25 import android.graphics.Rect; 26 import android.graphics.Region; 27 import android.os.Build; 28 import android.os.Build.VERSION_CODES; 29 import android.os.Parcel; 30 import android.os.Parcelable; 31 import android.util.DisplayMetrics; 32 import android.util.MergedConfiguration; 33 import android.view.InsetsSourceControl; 34 import android.view.InsetsState; 35 import android.view.MotionEvent; 36 import android.view.WindowManager; 37 import android.view.WindowManager.LayoutParams; 38 39 /** 40 * CompatibilityInfo class keeps the information about the screen compatibility mode that the 41 * application is running under. 42 * 43 * {@hide} 44 */ 45 public class CompatibilityInfo implements Parcelable { 46 /** default compatibility info object for compatible applications */ 47 @UnsupportedAppUsage 48 public static final CompatibilityInfo DEFAULT_COMPATIBILITY_INFO = new CompatibilityInfo() { 49 }; 50 51 /** 52 * This is the number of pixels we would like to have along the 53 * short axis of an app that needs to run on a normal size screen. 54 */ 55 public static final int DEFAULT_NORMAL_SHORT_DIMENSION = 320; 56 57 /** 58 * This is the maximum aspect ratio we will allow while keeping 59 * applications in a compatible screen size. 60 */ 61 public static final float MAXIMUM_ASPECT_RATIO = (854f/480f); 62 63 /** 64 * A compatibility flags 65 */ 66 private final int mCompatibilityFlags; 67 68 /** 69 * A flag mask to tell if the application needs scaling (when mApplicationScale != 1.0f) 70 * {@see compatibilityFlag} 71 */ 72 private static final int SCALING_REQUIRED = 1; 73 74 /** 75 * Application must always run in compatibility mode? 76 */ 77 private static final int ALWAYS_NEEDS_COMPAT = 2; 78 79 /** 80 * Application never should run in compatibility mode? 81 */ 82 private static final int NEVER_NEEDS_COMPAT = 4; 83 84 /** 85 * Set if the application needs to run in screen size compatibility mode. 86 */ 87 private static final int NEEDS_SCREEN_COMPAT = 8; 88 89 /** 90 * Set if the application needs to run in with compat resources. 91 */ 92 private static final int NEEDS_COMPAT_RES = 16; 93 94 /** 95 * Set if the application needs to be forcibly downscaled 96 */ 97 private static final int HAS_OVERRIDE_SCALING = 32; 98 99 /** 100 * The effective screen density we have selected for this application. 101 */ 102 public final int applicationDensity; 103 104 /** 105 * Application's scale. 106 */ 107 @UnsupportedAppUsage 108 public final float applicationScale; 109 110 /** 111 * Application's inverted scale. 112 */ 113 public final float applicationInvertedScale; 114 115 /** 116 * Application's density scale. 117 * 118 * <p>In most cases this is equal to {@link #applicationScale}, but in some cases e.g. 119 * Automotive the requirement is to just scale the density and keep the resolution the same. 120 * This is used for artificially making apps look zoomed in to compensate for the user distance 121 * from the screen. 122 */ 123 public final float applicationDensityScale; 124 125 /** 126 * Application's density inverted scale. 127 */ 128 public final float applicationDensityInvertedScale; 129 130 /** The process level override inverted scale. See {@link #HAS_OVERRIDE_SCALING}. */ 131 private static float sOverrideInvertedScale = 1f; 132 133 /** The process level override inverted density scale. See {@link #HAS_OVERRIDE_SCALING}. */ 134 private static float sOverrideDensityInvertScale = 1f; 135 136 @UnsupportedAppUsage 137 @Deprecated CompatibilityInfo(ApplicationInfo appInfo, int screenLayout, int sw, boolean forceCompat)138 public CompatibilityInfo(ApplicationInfo appInfo, int screenLayout, int sw, 139 boolean forceCompat) { 140 this(appInfo, screenLayout, sw, forceCompat, 1f); 141 } 142 CompatibilityInfo(ApplicationInfo appInfo, int screenLayout, int sw, boolean forceCompat, float scaleFactor)143 public CompatibilityInfo(ApplicationInfo appInfo, int screenLayout, int sw, 144 boolean forceCompat, float scaleFactor) { 145 this(appInfo, screenLayout, sw, forceCompat, scaleFactor, scaleFactor); 146 } 147 CompatibilityInfo(ApplicationInfo appInfo, int screenLayout, int sw, boolean forceCompat, float scaleFactor, float densityScaleFactor)148 public CompatibilityInfo(ApplicationInfo appInfo, int screenLayout, int sw, 149 boolean forceCompat, float scaleFactor, float densityScaleFactor) { 150 int compatFlags = 0; 151 152 if (appInfo.targetSdkVersion < VERSION_CODES.O) { 153 compatFlags |= NEEDS_COMPAT_RES; 154 } 155 if (scaleFactor != 1f || densityScaleFactor != 1f) { 156 applicationScale = scaleFactor; 157 applicationInvertedScale = 1f / scaleFactor; 158 applicationDensityScale = densityScaleFactor; 159 applicationDensityInvertedScale = 1f / densityScaleFactor; 160 applicationDensity = (int) ((DisplayMetrics.DENSITY_DEVICE_STABLE 161 * applicationDensityInvertedScale) + .5f); 162 mCompatibilityFlags = NEVER_NEEDS_COMPAT | HAS_OVERRIDE_SCALING; 163 // Override scale has the highest priority. So ignore other compatibility attributes. 164 return; 165 } 166 if (appInfo.requiresSmallestWidthDp != 0 || appInfo.compatibleWidthLimitDp != 0 167 || appInfo.largestWidthLimitDp != 0) { 168 // New style screen requirements spec. 169 int required = appInfo.requiresSmallestWidthDp != 0 170 ? appInfo.requiresSmallestWidthDp 171 : appInfo.compatibleWidthLimitDp; 172 if (required == 0) { 173 required = appInfo.largestWidthLimitDp; 174 } 175 int compat = appInfo.compatibleWidthLimitDp != 0 176 ? appInfo.compatibleWidthLimitDp : required; 177 if (compat < required) { 178 compat = required; 179 } 180 int largest = appInfo.largestWidthLimitDp; 181 182 if (required > DEFAULT_NORMAL_SHORT_DIMENSION) { 183 // For now -- if they require a size larger than the only 184 // size we can do in compatibility mode, then don't ever 185 // allow the app to go in to compat mode. Trying to run 186 // it at a smaller size it can handle will make it far more 187 // broken than running at a larger size than it wants or 188 // thinks it can handle. 189 compatFlags |= NEVER_NEEDS_COMPAT; 190 } else if (largest != 0 && sw > largest) { 191 // If the screen size is larger than the largest size the 192 // app thinks it can work with, then always force it in to 193 // compatibility mode. 194 compatFlags |= NEEDS_SCREEN_COMPAT | ALWAYS_NEEDS_COMPAT; 195 } else if (compat >= sw) { 196 // The screen size is something the app says it was designed 197 // for, so never do compatibility mode. 198 compatFlags |= NEVER_NEEDS_COMPAT; 199 } else if (forceCompat) { 200 // The app may work better with or without compatibility mode. 201 // Let the user decide. 202 compatFlags |= NEEDS_SCREEN_COMPAT; 203 } 204 205 // Modern apps always support densities. 206 applicationDensity = DisplayMetrics.DENSITY_DEVICE; 207 applicationScale = 1.0f; 208 applicationInvertedScale = 1.0f; 209 applicationDensityScale = 1.0f; 210 applicationDensityInvertedScale = 1.0f; 211 } else { 212 /** 213 * Has the application said that its UI is expandable? Based on the 214 * <supports-screen> android:expandible in the manifest. 215 */ 216 final int EXPANDABLE = 2; 217 218 /** 219 * Has the application said that its UI supports large screens? Based on the 220 * <supports-screen> android:largeScreens in the manifest. 221 */ 222 final int LARGE_SCREENS = 8; 223 224 /** 225 * Has the application said that its UI supports xlarge screens? Based on the 226 * <supports-screen> android:xlargeScreens in the manifest. 227 */ 228 final int XLARGE_SCREENS = 32; 229 230 int sizeInfo = 0; 231 232 // We can't rely on the application always setting 233 // FLAG_RESIZEABLE_FOR_SCREENS so will compute it based on various input. 234 boolean anyResizeable = false; 235 236 if ((appInfo.flags & ApplicationInfo.FLAG_SUPPORTS_LARGE_SCREENS) != 0) { 237 sizeInfo |= LARGE_SCREENS; 238 anyResizeable = true; 239 if (!forceCompat) { 240 // If we aren't forcing the app into compatibility mode, then 241 // assume if it supports large screens that we should allow it 242 // to use the full space of an xlarge screen as well. 243 sizeInfo |= XLARGE_SCREENS | EXPANDABLE; 244 } 245 } 246 if ((appInfo.flags & ApplicationInfo.FLAG_SUPPORTS_XLARGE_SCREENS) != 0) { 247 anyResizeable = true; 248 if (!forceCompat) { 249 sizeInfo |= XLARGE_SCREENS | EXPANDABLE; 250 } 251 } 252 if ((appInfo.flags & ApplicationInfo.FLAG_RESIZEABLE_FOR_SCREENS) != 0) { 253 anyResizeable = true; 254 sizeInfo |= EXPANDABLE; 255 } 256 257 if (forceCompat) { 258 // If we are forcing compatibility mode, then ignore an app that 259 // just says it is resizable for screens. We'll only have it fill 260 // the screen if it explicitly says it supports the screen size we 261 // are running in. 262 sizeInfo &= ~EXPANDABLE; 263 } 264 265 compatFlags |= NEEDS_SCREEN_COMPAT; 266 switch (screenLayout&Configuration.SCREENLAYOUT_SIZE_MASK) { 267 case Configuration.SCREENLAYOUT_SIZE_XLARGE: 268 if ((sizeInfo&XLARGE_SCREENS) != 0) { 269 compatFlags &= ~NEEDS_SCREEN_COMPAT; 270 } 271 if ((appInfo.flags & ApplicationInfo.FLAG_SUPPORTS_XLARGE_SCREENS) != 0) { 272 compatFlags |= NEVER_NEEDS_COMPAT; 273 } 274 break; 275 case Configuration.SCREENLAYOUT_SIZE_LARGE: 276 if ((sizeInfo&LARGE_SCREENS) != 0) { 277 compatFlags &= ~NEEDS_SCREEN_COMPAT; 278 } 279 if ((appInfo.flags & ApplicationInfo.FLAG_SUPPORTS_LARGE_SCREENS) != 0) { 280 compatFlags |= NEVER_NEEDS_COMPAT; 281 } 282 break; 283 } 284 285 if ((screenLayout&Configuration.SCREENLAYOUT_COMPAT_NEEDED) != 0) { 286 if ((sizeInfo&EXPANDABLE) != 0) { 287 compatFlags &= ~NEEDS_SCREEN_COMPAT; 288 } else if (!anyResizeable) { 289 compatFlags |= ALWAYS_NEEDS_COMPAT; 290 } 291 } else { 292 compatFlags &= ~NEEDS_SCREEN_COMPAT; 293 compatFlags |= NEVER_NEEDS_COMPAT; 294 } 295 296 if ((appInfo.flags & ApplicationInfo.FLAG_SUPPORTS_SCREEN_DENSITIES) != 0) { 297 applicationDensity = DisplayMetrics.DENSITY_DEVICE; 298 applicationScale = 1.0f; 299 applicationInvertedScale = 1.0f; 300 applicationDensityScale = 1.0f; 301 applicationDensityInvertedScale = 1.0f; 302 } else { 303 applicationDensity = DisplayMetrics.DENSITY_DEFAULT; 304 applicationScale = DisplayMetrics.DENSITY_DEVICE 305 / (float) DisplayMetrics.DENSITY_DEFAULT; 306 applicationInvertedScale = 1.0f / applicationScale; 307 applicationDensityScale = DisplayMetrics.DENSITY_DEVICE 308 / (float) DisplayMetrics.DENSITY_DEFAULT; 309 applicationDensityInvertedScale = 1f / applicationDensityScale; 310 compatFlags |= SCALING_REQUIRED; 311 } 312 } 313 314 mCompatibilityFlags = compatFlags; 315 } 316 CompatibilityInfo(int compFlags, int dens, float scale, float invertedScale)317 private CompatibilityInfo(int compFlags, 318 int dens, float scale, float invertedScale) { 319 mCompatibilityFlags = compFlags; 320 applicationDensity = dens; 321 applicationScale = scale; 322 applicationInvertedScale = invertedScale; 323 applicationDensityScale = (float) DisplayMetrics.DENSITY_DEVICE_STABLE / dens; 324 applicationDensityInvertedScale = 1f / applicationDensityScale; 325 } 326 327 @UnsupportedAppUsage CompatibilityInfo()328 private CompatibilityInfo() { 329 this(NEVER_NEEDS_COMPAT, DisplayMetrics.DENSITY_DEVICE, 330 1.0f, 331 1.0f); 332 } 333 334 /** 335 * @return true if the scaling is required 336 */ 337 @UnsupportedAppUsage isScalingRequired()338 public boolean isScalingRequired() { 339 return (mCompatibilityFlags & SCALING_REQUIRED) != 0; 340 } 341 342 /** Returns {@code true} if {@link #sOverrideInvertedScale} should be set. */ hasOverrideScaling()343 public boolean hasOverrideScaling() { 344 return (mCompatibilityFlags & HAS_OVERRIDE_SCALING) != 0; 345 } 346 347 @UnsupportedAppUsage supportsScreen()348 public boolean supportsScreen() { 349 return (mCompatibilityFlags&NEEDS_SCREEN_COMPAT) == 0; 350 } 351 neverSupportsScreen()352 public boolean neverSupportsScreen() { 353 return (mCompatibilityFlags&ALWAYS_NEEDS_COMPAT) != 0; 354 } 355 alwaysSupportsScreen()356 public boolean alwaysSupportsScreen() { 357 return (mCompatibilityFlags&NEVER_NEEDS_COMPAT) != 0; 358 } 359 needsCompatResources()360 public boolean needsCompatResources() { 361 return (mCompatibilityFlags&NEEDS_COMPAT_RES) != 0; 362 } 363 364 /** 365 * Returns the translator which translates the coordinates in compatibility mode. 366 * @param params the window's parameter 367 */ 368 @UnsupportedAppUsage getTranslator()369 public Translator getTranslator() { 370 return (mCompatibilityFlags & SCALING_REQUIRED) != 0 ? new Translator() : null; 371 } 372 373 /** 374 * A helper object to translate the screen and window coordinates back and forth. 375 * @hide 376 */ 377 public class Translator { 378 @UnsupportedAppUsage 379 final public float applicationScale; 380 @UnsupportedAppUsage 381 final public float applicationInvertedScale; 382 383 private Rect mContentInsetsBuffer = null; 384 private Rect mVisibleInsetsBuffer = null; 385 private Region mTouchableAreaBuffer = null; 386 Translator(float applicationScale, float applicationInvertedScale)387 Translator(float applicationScale, float applicationInvertedScale) { 388 this.applicationScale = applicationScale; 389 this.applicationInvertedScale = applicationInvertedScale; 390 } 391 Translator()392 Translator() { 393 this(CompatibilityInfo.this.applicationScale, 394 CompatibilityInfo.this.applicationInvertedScale); 395 } 396 397 /** 398 * Translate the region in window to screen. 399 */ 400 @UnsupportedAppUsage translateRegionInWindowToScreen(Region transparentRegion)401 public void translateRegionInWindowToScreen(Region transparentRegion) { 402 transparentRegion.scale(applicationScale); 403 } 404 405 /** 406 * Apply translation to the canvas that is necessary to draw the content. 407 */ 408 @UnsupportedAppUsage translateCanvas(Canvas canvas)409 public void translateCanvas(Canvas canvas) { 410 if (applicationScale == 1.5f) { 411 /* When we scale for compatibility, we can put our stretched 412 bitmaps and ninepatches on exacty 1/2 pixel boundaries, 413 which can give us inconsistent drawing due to imperfect 414 float precision in the graphics engine's inverse matrix. 415 416 As a work-around, we translate by a tiny amount to avoid 417 landing on exact pixel centers and boundaries, giving us 418 the slop we need to draw consistently. 419 420 This constant is meant to resolve to 1/255 after it is 421 scaled by 1.5 (applicationScale). Note, this is just a guess 422 as to what is small enough not to create its own artifacts, 423 and big enough to avoid the precision problems. Feel free 424 to experiment with smaller values as you choose. 425 */ 426 final float tinyOffset = 2.0f / (3 * 255); 427 canvas.translate(tinyOffset, tinyOffset); 428 } 429 canvas.scale(applicationScale, applicationScale); 430 } 431 432 /** 433 * Translate the motion event captured on screen to the application's window. 434 */ 435 @UnsupportedAppUsage translateEventInScreenToAppWindow(MotionEvent event)436 public void translateEventInScreenToAppWindow(MotionEvent event) { 437 event.scale(applicationInvertedScale); 438 } 439 440 /** 441 * Translate the window's layout parameter, from application's view to 442 * Screen's view. 443 */ 444 @UnsupportedAppUsage translateWindowLayout(WindowManager.LayoutParams params)445 public void translateWindowLayout(WindowManager.LayoutParams params) { 446 params.scale(applicationScale); 447 } 448 449 /** 450 * Translate a length in application's window to screen. 451 */ translateLengthInAppWindowToScreen(float length)452 public float translateLengthInAppWindowToScreen(float length) { 453 return length * applicationScale; 454 } 455 456 /** 457 * Translate a Rect in application's window to screen. 458 */ 459 @UnsupportedAppUsage translateRectInAppWindowToScreen(Rect rect)460 public void translateRectInAppWindowToScreen(Rect rect) { 461 rect.scale(applicationScale); 462 } 463 464 /** 465 * Translate a Rect in screen coordinates into the app window's coordinates. 466 */ 467 @UnsupportedAppUsage translateRectInScreenToAppWindow(@ullable Rect rect)468 public void translateRectInScreenToAppWindow(@Nullable Rect rect) { 469 if (rect == null) { 470 return; 471 } 472 rect.scale(applicationInvertedScale); 473 } 474 475 /** 476 * Translate an {@link InsetsState} in screen coordinates into the app window's coordinates. 477 */ translateInsetsStateInScreenToAppWindow(InsetsState state)478 public void translateInsetsStateInScreenToAppWindow(InsetsState state) { 479 state.scale(applicationInvertedScale); 480 } 481 482 /** 483 * Translate {@link InsetsSourceControl}s in screen coordinates into the app window's 484 * coordinates. 485 */ translateSourceControlsInScreenToAppWindow(InsetsSourceControl[] controls)486 public void translateSourceControlsInScreenToAppWindow(InsetsSourceControl[] controls) { 487 if (controls == null) { 488 return; 489 } 490 final float scale = applicationInvertedScale; 491 if (scale == 1f) { 492 return; 493 } 494 for (InsetsSourceControl control : controls) { 495 if (control == null) { 496 continue; 497 } 498 final Insets hint = control.getInsetsHint(); 499 control.setInsetsHint( 500 (int) (scale * hint.left), 501 (int) (scale * hint.top), 502 (int) (scale * hint.right), 503 (int) (scale * hint.bottom)); 504 } 505 } 506 507 /** 508 * Translate a Point in screen coordinates into the app window's coordinates. 509 */ translatePointInScreenToAppWindow(PointF point)510 public void translatePointInScreenToAppWindow(PointF point) { 511 final float scale = applicationInvertedScale; 512 if (scale != 1.0f) { 513 point.x *= scale; 514 point.y *= scale; 515 } 516 } 517 518 /** 519 * Translate the location of the sub window. 520 * @param params 521 */ translateLayoutParamsInAppWindowToScreen(LayoutParams params)522 public void translateLayoutParamsInAppWindowToScreen(LayoutParams params) { 523 params.scale(applicationScale); 524 } 525 526 /** 527 * Translate the content insets in application window to Screen. This uses 528 * the internal buffer for content insets to avoid extra object allocation. 529 */ 530 @UnsupportedAppUsage getTranslatedContentInsets(Rect contentInsets)531 public Rect getTranslatedContentInsets(Rect contentInsets) { 532 if (mContentInsetsBuffer == null) mContentInsetsBuffer = new Rect(); 533 mContentInsetsBuffer.set(contentInsets); 534 translateRectInAppWindowToScreen(mContentInsetsBuffer); 535 return mContentInsetsBuffer; 536 } 537 538 /** 539 * Translate the visible insets in application window to Screen. This uses 540 * the internal buffer for visible insets to avoid extra object allocation. 541 */ getTranslatedVisibleInsets(Rect visibleInsets)542 public Rect getTranslatedVisibleInsets(Rect visibleInsets) { 543 if (mVisibleInsetsBuffer == null) mVisibleInsetsBuffer = new Rect(); 544 mVisibleInsetsBuffer.set(visibleInsets); 545 translateRectInAppWindowToScreen(mVisibleInsetsBuffer); 546 return mVisibleInsetsBuffer; 547 } 548 549 /** 550 * Translate the touchable area in application window to Screen. This uses 551 * the internal buffer for touchable area to avoid extra object allocation. 552 */ getTranslatedTouchableArea(Region touchableArea)553 public Region getTranslatedTouchableArea(Region touchableArea) { 554 if (mTouchableAreaBuffer == null) mTouchableAreaBuffer = new Region(); 555 mTouchableAreaBuffer.set(touchableArea); 556 mTouchableAreaBuffer.scale(applicationScale); 557 return mTouchableAreaBuffer; 558 } 559 } 560 561 /** Applies the compatibility adjustment to the display metrics. */ applyDisplayMetricsIfNeeded(DisplayMetrics inoutDm, boolean applyToSize)562 public void applyDisplayMetricsIfNeeded(DisplayMetrics inoutDm, boolean applyToSize) { 563 if (hasOverrideScale()) { 564 scaleDisplayMetrics(sOverrideInvertedScale, sOverrideDensityInvertScale, inoutDm, 565 applyToSize); 566 return; 567 } 568 if (!equals(DEFAULT_COMPATIBILITY_INFO)) { 569 applyToDisplayMetrics(inoutDm); 570 } 571 } 572 applyToDisplayMetrics(DisplayMetrics inoutDm)573 public void applyToDisplayMetrics(DisplayMetrics inoutDm) { 574 if (hasOverrideScale()) return; 575 if (!supportsScreen()) { 576 // This is a larger screen device and the app is not 577 // compatible with large screens, so diddle it. 578 CompatibilityInfo.computeCompatibleScaling(inoutDm, inoutDm); 579 } else { 580 inoutDm.widthPixels = inoutDm.noncompatWidthPixels; 581 inoutDm.heightPixels = inoutDm.noncompatHeightPixels; 582 } 583 584 if (isScalingRequired()) { 585 scaleDisplayMetrics(applicationInvertedScale, applicationDensityInvertedScale, inoutDm, 586 true /* applyToSize */); 587 } 588 } 589 590 /** Scales the density of the given display metrics. */ scaleDisplayMetrics(float invertScale, float densityInvertScale, DisplayMetrics inoutDm, boolean applyToSize)591 private static void scaleDisplayMetrics(float invertScale, float densityInvertScale, 592 DisplayMetrics inoutDm, boolean applyToSize) { 593 inoutDm.density = inoutDm.noncompatDensity * densityInvertScale; 594 inoutDm.densityDpi = (int) ((inoutDm.noncompatDensityDpi 595 * densityInvertScale) + .5f); 596 // Note: since this is changing the scaledDensity, you might think we also need to change 597 // inoutDm.fontScaleConverter to accurately calculate non-linear font scaling. But we're not 598 // going to do that, for a couple of reasons (see b/265695259 for details): 599 // 1. The first case is only for apps targeting SDK < 4. These ancient apps will just have 600 // to live with linear font scaling. We don't want to make anything more unpredictable. 601 // 2. The second case where this is called is for scaling down games. But it is called in 602 // two situations: 603 // a. When from ResourcesImpl.updateConfiguration(), we will set the fontScaleConverter 604 // *after* this method is called. That's the only place where the app will actually 605 // use the DisplayMetrics for scaling fonts in its resources. 606 // b. Sometime later by WindowManager in onResume or other windowing events. In this case 607 // the DisplayMetrics object is never used by the app/resources, so it's ok if 608 // fontScaleConverter is null because it's not being used to scale fonts anyway. 609 inoutDm.scaledDensity = inoutDm.noncompatScaledDensity * densityInvertScale; 610 inoutDm.xdpi = inoutDm.noncompatXdpi * densityInvertScale; 611 inoutDm.ydpi = inoutDm.noncompatYdpi * densityInvertScale; 612 if (applyToSize) { 613 inoutDm.widthPixels = (int) (inoutDm.widthPixels * invertScale + 0.5f); 614 inoutDm.heightPixels = (int) (inoutDm.heightPixels * invertScale + 0.5f); 615 } 616 } 617 applyToConfiguration(int displayDensity, Configuration inoutConfig)618 public void applyToConfiguration(int displayDensity, Configuration inoutConfig) { 619 if (hasOverrideScale()) return; 620 if (!supportsScreen()) { 621 // This is a larger screen device and the app is not 622 // compatible with large screens, so we are forcing it to 623 // run as if the screen is normal size. 624 inoutConfig.screenLayout = 625 (inoutConfig.screenLayout&~Configuration.SCREENLAYOUT_SIZE_MASK) 626 | Configuration.SCREENLAYOUT_SIZE_NORMAL; 627 inoutConfig.screenWidthDp = inoutConfig.compatScreenWidthDp; 628 inoutConfig.screenHeightDp = inoutConfig.compatScreenHeightDp; 629 inoutConfig.smallestScreenWidthDp = inoutConfig.compatSmallestScreenWidthDp; 630 } 631 inoutConfig.densityDpi = displayDensity; 632 if (isScalingRequired()) { 633 scaleConfiguration(applicationInvertedScale, applicationDensityInvertedScale, 634 inoutConfig); 635 } 636 } 637 638 /** Scales the density and bounds of the given configuration. */ scaleConfiguration(float invertScale, Configuration inoutConfig)639 public static void scaleConfiguration(float invertScale, Configuration inoutConfig) { 640 scaleConfiguration(invertScale, invertScale, inoutConfig); 641 } 642 643 /** Scales the density and bounds of the given configuration. */ scaleConfiguration(float invertScale, float densityInvertScale, Configuration inoutConfig)644 public static void scaleConfiguration(float invertScale, float densityInvertScale, 645 Configuration inoutConfig) { 646 inoutConfig.densityDpi = (int) ((inoutConfig.densityDpi 647 * densityInvertScale) + .5f); 648 inoutConfig.windowConfiguration.scale(invertScale); 649 } 650 651 /** @see #sOverrideInvertedScale */ applyOverrideScaleIfNeeded(Configuration config)652 public static void applyOverrideScaleIfNeeded(Configuration config) { 653 if (!hasOverrideScale()) return; 654 scaleConfiguration(sOverrideInvertedScale, sOverrideDensityInvertScale, config); 655 } 656 657 /** @see #sOverrideInvertedScale */ applyOverrideScaleIfNeeded(MergedConfiguration mergedConfig)658 public static void applyOverrideScaleIfNeeded(MergedConfiguration mergedConfig) { 659 if (!hasOverrideScale()) return; 660 scaleConfiguration(sOverrideInvertedScale, sOverrideDensityInvertScale, 661 mergedConfig.getGlobalConfiguration()); 662 scaleConfiguration(sOverrideInvertedScale, sOverrideDensityInvertScale, 663 mergedConfig.getOverrideConfiguration()); 664 scaleConfiguration(sOverrideInvertedScale, sOverrideDensityInvertScale, 665 mergedConfig.getMergedConfiguration()); 666 } 667 668 /** Returns {@code true} if this process is in a environment with override scale. */ hasOverrideScale()669 private static boolean hasOverrideScale() { 670 return sOverrideInvertedScale != 1f || sOverrideDensityInvertScale != 1f; 671 } 672 673 /** @see #sOverrideInvertedScale */ setOverrideInvertedScale(float invertScale)674 public static void setOverrideInvertedScale(float invertScale) { 675 setOverrideInvertedScale(invertScale, invertScale); 676 } 677 678 /** @see #sOverrideInvertedScale */ setOverrideInvertedScale(float invertScale, float densityInvertScale)679 public static void setOverrideInvertedScale(float invertScale, float densityInvertScale) { 680 sOverrideInvertedScale = invertScale; 681 sOverrideDensityInvertScale = densityInvertScale; 682 } 683 684 /** @see #sOverrideInvertedScale */ getOverrideInvertedScale()685 public static float getOverrideInvertedScale() { 686 return sOverrideInvertedScale; 687 } 688 689 /** @see #sOverrideDensityInvertScale */ getOverrideDensityInvertedScale()690 public static float getOverrideDensityInvertedScale() { 691 return sOverrideDensityInvertScale; 692 } 693 694 /** 695 * Compute the frame Rect for applications runs under compatibility mode. 696 * 697 * @param dm the display metrics used to compute the frame size. 698 * @param outDm If non-null the width and height will be set to their scaled values. 699 * @return Returns the scaling factor for the window. 700 */ 701 @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553) computeCompatibleScaling(DisplayMetrics dm, DisplayMetrics outDm)702 public static float computeCompatibleScaling(DisplayMetrics dm, DisplayMetrics outDm) { 703 final int width = dm.noncompatWidthPixels; 704 final int height = dm.noncompatHeightPixels; 705 int shortSize, longSize; 706 if (width < height) { 707 shortSize = width; 708 longSize = height; 709 } else { 710 shortSize = height; 711 longSize = width; 712 } 713 int newShortSize = (int)(DEFAULT_NORMAL_SHORT_DIMENSION * dm.density + 0.5f); 714 float aspect = ((float)longSize) / shortSize; 715 if (aspect > MAXIMUM_ASPECT_RATIO) { 716 aspect = MAXIMUM_ASPECT_RATIO; 717 } 718 int newLongSize = (int)(newShortSize * aspect + 0.5f); 719 int newWidth, newHeight; 720 if (width < height) { 721 newWidth = newShortSize; 722 newHeight = newLongSize; 723 } else { 724 newWidth = newLongSize; 725 newHeight = newShortSize; 726 } 727 728 float sw = width/(float)newWidth; 729 float sh = height/(float)newHeight; 730 float scale = sw < sh ? sw : sh; 731 if (scale < 1) { 732 scale = 1; 733 } 734 735 if (outDm != null) { 736 outDm.widthPixels = newWidth; 737 outDm.heightPixels = newHeight; 738 } 739 740 return scale; 741 } 742 743 @Override 744 public boolean equals(@Nullable Object o) { 745 if (this == o) { 746 return true; 747 } 748 try { 749 CompatibilityInfo oc = (CompatibilityInfo)o; 750 if (mCompatibilityFlags != oc.mCompatibilityFlags) return false; 751 if (applicationDensity != oc.applicationDensity) return false; 752 if (applicationScale != oc.applicationScale) return false; 753 if (applicationInvertedScale != oc.applicationInvertedScale) return false; 754 if (applicationDensityScale != oc.applicationDensityScale) return false; 755 if (applicationDensityInvertedScale != oc.applicationDensityInvertedScale) return false; 756 return true; 757 } catch (ClassCastException e) { 758 return false; 759 } 760 } 761 762 @Override 763 public String toString() { 764 StringBuilder sb = new StringBuilder(128); 765 sb.append("{"); 766 sb.append(applicationDensity); 767 sb.append("dpi"); 768 if (isScalingRequired()) { 769 sb.append(" "); 770 sb.append(applicationScale); 771 sb.append("x"); 772 } 773 if (hasOverrideScaling()) { 774 sb.append(" overrideInvScale="); 775 sb.append(applicationInvertedScale); 776 sb.append(" overrideDensityInvScale="); 777 sb.append(applicationDensityInvertedScale); 778 } 779 if (!supportsScreen()) { 780 sb.append(" resizing"); 781 } 782 if (neverSupportsScreen()) { 783 sb.append(" never-compat"); 784 } 785 if (alwaysSupportsScreen()) { 786 sb.append(" always-compat"); 787 } 788 sb.append("}"); 789 return sb.toString(); 790 } 791 792 @Override 793 public int hashCode() { 794 int result = 17; 795 result = 31 * result + mCompatibilityFlags; 796 result = 31 * result + applicationDensity; 797 result = 31 * result + Float.floatToIntBits(applicationScale); 798 result = 31 * result + Float.floatToIntBits(applicationInvertedScale); 799 result = 31 * result + Float.floatToIntBits(applicationDensityScale); 800 result = 31 * result + Float.floatToIntBits(applicationDensityInvertedScale); 801 return result; 802 } 803 804 @Override 805 public int describeContents() { 806 return 0; 807 } 808 809 @Override 810 public void writeToParcel(Parcel dest, int flags) { 811 dest.writeInt(mCompatibilityFlags); 812 dest.writeInt(applicationDensity); 813 dest.writeFloat(applicationScale); 814 dest.writeFloat(applicationInvertedScale); 815 dest.writeFloat(applicationDensityScale); 816 dest.writeFloat(applicationDensityInvertedScale); 817 } 818 819 @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P, trackingBug = 115609023) 820 public static final @android.annotation.NonNull Parcelable.Creator<CompatibilityInfo> CREATOR 821 = new Parcelable.Creator<CompatibilityInfo>() { 822 @Override 823 public CompatibilityInfo createFromParcel(Parcel source) { 824 return new CompatibilityInfo(source); 825 } 826 827 @Override 828 public CompatibilityInfo[] newArray(int size) { 829 return new CompatibilityInfo[size]; 830 } 831 }; 832 833 private CompatibilityInfo(Parcel source) { 834 mCompatibilityFlags = source.readInt(); 835 applicationDensity = source.readInt(); 836 applicationScale = source.readFloat(); 837 applicationInvertedScale = source.readFloat(); 838 applicationDensityScale = source.readFloat(); 839 applicationDensityInvertedScale = source.readFloat(); 840 } 841 842 /** 843 * A data class for holding scale factor for width, height, and density. 844 */ 845 public static final class CompatScale { 846 847 public final float mScaleFactor; 848 public final float mDensityScaleFactor; 849 850 public CompatScale(float scaleFactor) { 851 this(scaleFactor, scaleFactor); 852 } 853 854 public CompatScale(float scaleFactor, float densityScaleFactor) { 855 mScaleFactor = scaleFactor; 856 mDensityScaleFactor = densityScaleFactor; 857 } 858 859 @Override 860 public boolean equals(@Nullable Object o) { 861 if (this == o) { 862 return true; 863 } 864 if (!(o instanceof CompatScale)) { 865 return false; 866 } 867 try { 868 CompatScale oc = (CompatScale) o; 869 if (mScaleFactor != oc.mScaleFactor) return false; 870 if (mDensityScaleFactor != oc.mDensityScaleFactor) return false; 871 return true; 872 } catch (ClassCastException e) { 873 return false; 874 } 875 } 876 877 @Override 878 public String toString() { 879 StringBuilder sb = new StringBuilder(128); 880 sb.append("mScaleFactor= "); 881 sb.append(mScaleFactor); 882 sb.append(" mDensityScaleFactor= "); 883 sb.append(mDensityScaleFactor); 884 return sb.toString(); 885 } 886 887 @Override 888 public int hashCode() { 889 int result = 17; 890 result = 31 * result + Float.floatToIntBits(mScaleFactor); 891 result = 31 * result + Float.floatToIntBits(mDensityScaleFactor); 892 return result; 893 } 894 } 895 } 896