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