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