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.content.pm.ApplicationInfo; 20 import android.graphics.Canvas; 21 import android.graphics.PointF; 22 import android.graphics.Rect; 23 import android.graphics.Region; 24 import android.os.Build; 25 import android.os.Build.VERSION_CODES; 26 import android.os.Parcel; 27 import android.os.Parcelable; 28 import android.util.DisplayMetrics; 29 import android.view.MotionEvent; 30 import android.view.WindowManager; 31 import android.view.WindowManager.LayoutParams; 32 33 /** 34 * CompatibilityInfo class keeps the information about compatibility mode that the application is 35 * running under. 36 * 37 * {@hide} 38 */ 39 public class CompatibilityInfo implements Parcelable { 40 /** default compatibility info object for compatible applications */ 41 public static final CompatibilityInfo DEFAULT_COMPATIBILITY_INFO = new CompatibilityInfo() { 42 }; 43 44 /** 45 * This is the number of pixels we would like to have along the 46 * short axis of an app that needs to run on a normal size screen. 47 */ 48 public static final int DEFAULT_NORMAL_SHORT_DIMENSION = 320; 49 50 /** 51 * This is the maximum aspect ratio we will allow while keeping 52 * applications in a compatible screen size. 53 */ 54 public static final float MAXIMUM_ASPECT_RATIO = (854f/480f); 55 56 /** 57 * A compatibility flags 58 */ 59 private final int mCompatibilityFlags; 60 61 /** 62 * A flag mask to tell if the application needs scaling (when mApplicationScale != 1.0f) 63 * {@see compatibilityFlag} 64 */ 65 private static final int SCALING_REQUIRED = 1; 66 67 /** 68 * Application must always run in compatibility mode? 69 */ 70 private static final int ALWAYS_NEEDS_COMPAT = 2; 71 72 /** 73 * Application never should run in compatibility mode? 74 */ 75 private static final int NEVER_NEEDS_COMPAT = 4; 76 77 /** 78 * Set if the application needs to run in screen size compatibility mode. 79 */ 80 private static final int NEEDS_SCREEN_COMPAT = 8; 81 82 /** 83 * Set if the application needs to run in with compat resources. 84 */ 85 private static final int NEEDS_COMPAT_RES = 16; 86 87 /** 88 * The effective screen density we have selected for this application. 89 */ 90 public final int applicationDensity; 91 92 /** 93 * Application's scale. 94 */ 95 public final float applicationScale; 96 97 /** 98 * Application's inverted scale. 99 */ 100 public final float applicationInvertedScale; 101 CompatibilityInfo(ApplicationInfo appInfo, int screenLayout, int sw, boolean forceCompat)102 public CompatibilityInfo(ApplicationInfo appInfo, int screenLayout, int sw, 103 boolean forceCompat) { 104 int compatFlags = 0; 105 106 if (appInfo.targetSdkVersion < VERSION_CODES.O) { 107 compatFlags |= NEEDS_COMPAT_RES; 108 } 109 if (appInfo.requiresSmallestWidthDp != 0 || appInfo.compatibleWidthLimitDp != 0 110 || appInfo.largestWidthLimitDp != 0) { 111 // New style screen requirements spec. 112 int required = appInfo.requiresSmallestWidthDp != 0 113 ? appInfo.requiresSmallestWidthDp 114 : appInfo.compatibleWidthLimitDp; 115 if (required == 0) { 116 required = appInfo.largestWidthLimitDp; 117 } 118 int compat = appInfo.compatibleWidthLimitDp != 0 119 ? appInfo.compatibleWidthLimitDp : required; 120 if (compat < required) { 121 compat = required; 122 } 123 int largest = appInfo.largestWidthLimitDp; 124 125 if (required > DEFAULT_NORMAL_SHORT_DIMENSION) { 126 // For now -- if they require a size larger than the only 127 // size we can do in compatibility mode, then don't ever 128 // allow the app to go in to compat mode. Trying to run 129 // it at a smaller size it can handle will make it far more 130 // broken than running at a larger size than it wants or 131 // thinks it can handle. 132 compatFlags |= NEVER_NEEDS_COMPAT; 133 } else if (largest != 0 && sw > largest) { 134 // If the screen size is larger than the largest size the 135 // app thinks it can work with, then always force it in to 136 // compatibility mode. 137 compatFlags |= NEEDS_SCREEN_COMPAT | ALWAYS_NEEDS_COMPAT; 138 } else if (compat >= sw) { 139 // The screen size is something the app says it was designed 140 // for, so never do compatibility mode. 141 compatFlags |= NEVER_NEEDS_COMPAT; 142 } else if (forceCompat) { 143 // The app may work better with or without compatibility mode. 144 // Let the user decide. 145 compatFlags |= NEEDS_SCREEN_COMPAT; 146 } 147 148 // Modern apps always support densities. 149 applicationDensity = DisplayMetrics.DENSITY_DEVICE; 150 applicationScale = 1.0f; 151 applicationInvertedScale = 1.0f; 152 153 } else { 154 /** 155 * Has the application said that its UI is expandable? Based on the 156 * <supports-screen> android:expandible in the manifest. 157 */ 158 final int EXPANDABLE = 2; 159 160 /** 161 * Has the application said that its UI supports large screens? Based on the 162 * <supports-screen> android:largeScreens in the manifest. 163 */ 164 final int LARGE_SCREENS = 8; 165 166 /** 167 * Has the application said that its UI supports xlarge screens? Based on the 168 * <supports-screen> android:xlargeScreens in the manifest. 169 */ 170 final int XLARGE_SCREENS = 32; 171 172 int sizeInfo = 0; 173 174 // We can't rely on the application always setting 175 // FLAG_RESIZEABLE_FOR_SCREENS so will compute it based on various input. 176 boolean anyResizeable = false; 177 178 if ((appInfo.flags & ApplicationInfo.FLAG_SUPPORTS_LARGE_SCREENS) != 0) { 179 sizeInfo |= LARGE_SCREENS; 180 anyResizeable = true; 181 if (!forceCompat) { 182 // If we aren't forcing the app into compatibility mode, then 183 // assume if it supports large screens that we should allow it 184 // to use the full space of an xlarge screen as well. 185 sizeInfo |= XLARGE_SCREENS | EXPANDABLE; 186 } 187 } 188 if ((appInfo.flags & ApplicationInfo.FLAG_SUPPORTS_XLARGE_SCREENS) != 0) { 189 anyResizeable = true; 190 if (!forceCompat) { 191 sizeInfo |= XLARGE_SCREENS | EXPANDABLE; 192 } 193 } 194 if ((appInfo.flags & ApplicationInfo.FLAG_RESIZEABLE_FOR_SCREENS) != 0) { 195 anyResizeable = true; 196 sizeInfo |= EXPANDABLE; 197 } 198 199 if (forceCompat) { 200 // If we are forcing compatibility mode, then ignore an app that 201 // just says it is resizable for screens. We'll only have it fill 202 // the screen if it explicitly says it supports the screen size we 203 // are running in. 204 sizeInfo &= ~EXPANDABLE; 205 } 206 207 compatFlags |= NEEDS_SCREEN_COMPAT; 208 switch (screenLayout&Configuration.SCREENLAYOUT_SIZE_MASK) { 209 case Configuration.SCREENLAYOUT_SIZE_XLARGE: 210 if ((sizeInfo&XLARGE_SCREENS) != 0) { 211 compatFlags &= ~NEEDS_SCREEN_COMPAT; 212 } 213 if ((appInfo.flags & ApplicationInfo.FLAG_SUPPORTS_XLARGE_SCREENS) != 0) { 214 compatFlags |= NEVER_NEEDS_COMPAT; 215 } 216 break; 217 case Configuration.SCREENLAYOUT_SIZE_LARGE: 218 if ((sizeInfo&LARGE_SCREENS) != 0) { 219 compatFlags &= ~NEEDS_SCREEN_COMPAT; 220 } 221 if ((appInfo.flags & ApplicationInfo.FLAG_SUPPORTS_LARGE_SCREENS) != 0) { 222 compatFlags |= NEVER_NEEDS_COMPAT; 223 } 224 break; 225 } 226 227 if ((screenLayout&Configuration.SCREENLAYOUT_COMPAT_NEEDED) != 0) { 228 if ((sizeInfo&EXPANDABLE) != 0) { 229 compatFlags &= ~NEEDS_SCREEN_COMPAT; 230 } else if (!anyResizeable) { 231 compatFlags |= ALWAYS_NEEDS_COMPAT; 232 } 233 } else { 234 compatFlags &= ~NEEDS_SCREEN_COMPAT; 235 compatFlags |= NEVER_NEEDS_COMPAT; 236 } 237 238 if ((appInfo.flags & ApplicationInfo.FLAG_SUPPORTS_SCREEN_DENSITIES) != 0) { 239 applicationDensity = DisplayMetrics.DENSITY_DEVICE; 240 applicationScale = 1.0f; 241 applicationInvertedScale = 1.0f; 242 } else { 243 applicationDensity = DisplayMetrics.DENSITY_DEFAULT; 244 applicationScale = DisplayMetrics.DENSITY_DEVICE 245 / (float) DisplayMetrics.DENSITY_DEFAULT; 246 applicationInvertedScale = 1.0f / applicationScale; 247 compatFlags |= SCALING_REQUIRED; 248 } 249 } 250 251 mCompatibilityFlags = compatFlags; 252 } 253 CompatibilityInfo(int compFlags, int dens, float scale, float invertedScale)254 private CompatibilityInfo(int compFlags, 255 int dens, float scale, float invertedScale) { 256 mCompatibilityFlags = compFlags; 257 applicationDensity = dens; 258 applicationScale = scale; 259 applicationInvertedScale = invertedScale; 260 } 261 CompatibilityInfo()262 private CompatibilityInfo() { 263 this(NEVER_NEEDS_COMPAT, DisplayMetrics.DENSITY_DEVICE, 264 1.0f, 265 1.0f); 266 } 267 268 /** 269 * @return true if the scaling is required 270 */ isScalingRequired()271 public boolean isScalingRequired() { 272 return (mCompatibilityFlags&SCALING_REQUIRED) != 0; 273 } 274 supportsScreen()275 public boolean supportsScreen() { 276 return (mCompatibilityFlags&NEEDS_SCREEN_COMPAT) == 0; 277 } 278 neverSupportsScreen()279 public boolean neverSupportsScreen() { 280 return (mCompatibilityFlags&ALWAYS_NEEDS_COMPAT) != 0; 281 } 282 alwaysSupportsScreen()283 public boolean alwaysSupportsScreen() { 284 return (mCompatibilityFlags&NEVER_NEEDS_COMPAT) != 0; 285 } 286 needsCompatResources()287 public boolean needsCompatResources() { 288 return (mCompatibilityFlags&NEEDS_COMPAT_RES) != 0; 289 } 290 291 /** 292 * Returns the translator which translates the coordinates in compatibility mode. 293 * @param params the window's parameter 294 */ getTranslator()295 public Translator getTranslator() { 296 return isScalingRequired() ? new Translator() : null; 297 } 298 299 /** 300 * A helper object to translate the screen and window coordinates back and forth. 301 * @hide 302 */ 303 public class Translator { 304 final public float applicationScale; 305 final public float applicationInvertedScale; 306 307 private Rect mContentInsetsBuffer = null; 308 private Rect mVisibleInsetsBuffer = null; 309 private Region mTouchableAreaBuffer = null; 310 Translator(float applicationScale, float applicationInvertedScale)311 Translator(float applicationScale, float applicationInvertedScale) { 312 this.applicationScale = applicationScale; 313 this.applicationInvertedScale = applicationInvertedScale; 314 } 315 Translator()316 Translator() { 317 this(CompatibilityInfo.this.applicationScale, 318 CompatibilityInfo.this.applicationInvertedScale); 319 } 320 321 /** 322 * Translate the screen rect to the application frame. 323 */ translateRectInScreenToAppWinFrame(Rect rect)324 public void translateRectInScreenToAppWinFrame(Rect rect) { 325 rect.scale(applicationInvertedScale); 326 } 327 328 /** 329 * Translate the region in window to screen. 330 */ translateRegionInWindowToScreen(Region transparentRegion)331 public void translateRegionInWindowToScreen(Region transparentRegion) { 332 transparentRegion.scale(applicationScale); 333 } 334 335 /** 336 * Apply translation to the canvas that is necessary to draw the content. 337 */ translateCanvas(Canvas canvas)338 public void translateCanvas(Canvas canvas) { 339 if (applicationScale == 1.5f) { 340 /* When we scale for compatibility, we can put our stretched 341 bitmaps and ninepatches on exacty 1/2 pixel boundaries, 342 which can give us inconsistent drawing due to imperfect 343 float precision in the graphics engine's inverse matrix. 344 345 As a work-around, we translate by a tiny amount to avoid 346 landing on exact pixel centers and boundaries, giving us 347 the slop we need to draw consistently. 348 349 This constant is meant to resolve to 1/255 after it is 350 scaled by 1.5 (applicationScale). Note, this is just a guess 351 as to what is small enough not to create its own artifacts, 352 and big enough to avoid the precision problems. Feel free 353 to experiment with smaller values as you choose. 354 */ 355 final float tinyOffset = 2.0f / (3 * 255); 356 canvas.translate(tinyOffset, tinyOffset); 357 } 358 canvas.scale(applicationScale, applicationScale); 359 } 360 361 /** 362 * Translate the motion event captured on screen to the application's window. 363 */ translateEventInScreenToAppWindow(MotionEvent event)364 public void translateEventInScreenToAppWindow(MotionEvent event) { 365 event.scale(applicationInvertedScale); 366 } 367 368 /** 369 * Translate the window's layout parameter, from application's view to 370 * Screen's view. 371 */ translateWindowLayout(WindowManager.LayoutParams params)372 public void translateWindowLayout(WindowManager.LayoutParams params) { 373 params.scale(applicationScale); 374 } 375 376 /** 377 * Translate a Rect in application's window to screen. 378 */ translateRectInAppWindowToScreen(Rect rect)379 public void translateRectInAppWindowToScreen(Rect rect) { 380 rect.scale(applicationScale); 381 } 382 383 /** 384 * Translate a Rect in screen coordinates into the app window's coordinates. 385 */ translateRectInScreenToAppWindow(Rect rect)386 public void translateRectInScreenToAppWindow(Rect rect) { 387 rect.scale(applicationInvertedScale); 388 } 389 390 /** 391 * Translate a Point in screen coordinates into the app window's coordinates. 392 */ translatePointInScreenToAppWindow(PointF point)393 public void translatePointInScreenToAppWindow(PointF point) { 394 final float scale = applicationInvertedScale; 395 if (scale != 1.0f) { 396 point.x *= scale; 397 point.y *= scale; 398 } 399 } 400 401 /** 402 * Translate the location of the sub window. 403 * @param params 404 */ translateLayoutParamsInAppWindowToScreen(LayoutParams params)405 public void translateLayoutParamsInAppWindowToScreen(LayoutParams params) { 406 params.scale(applicationScale); 407 } 408 409 /** 410 * Translate the content insets in application window to Screen. This uses 411 * the internal buffer for content insets to avoid extra object allocation. 412 */ getTranslatedContentInsets(Rect contentInsets)413 public Rect getTranslatedContentInsets(Rect contentInsets) { 414 if (mContentInsetsBuffer == null) mContentInsetsBuffer = new Rect(); 415 mContentInsetsBuffer.set(contentInsets); 416 translateRectInAppWindowToScreen(mContentInsetsBuffer); 417 return mContentInsetsBuffer; 418 } 419 420 /** 421 * Translate the visible insets in application window to Screen. This uses 422 * the internal buffer for visible insets to avoid extra object allocation. 423 */ getTranslatedVisibleInsets(Rect visibleInsets)424 public Rect getTranslatedVisibleInsets(Rect visibleInsets) { 425 if (mVisibleInsetsBuffer == null) mVisibleInsetsBuffer = new Rect(); 426 mVisibleInsetsBuffer.set(visibleInsets); 427 translateRectInAppWindowToScreen(mVisibleInsetsBuffer); 428 return mVisibleInsetsBuffer; 429 } 430 431 /** 432 * Translate the touchable area in application window to Screen. This uses 433 * the internal buffer for touchable area to avoid extra object allocation. 434 */ getTranslatedTouchableArea(Region touchableArea)435 public Region getTranslatedTouchableArea(Region touchableArea) { 436 if (mTouchableAreaBuffer == null) mTouchableAreaBuffer = new Region(); 437 mTouchableAreaBuffer.set(touchableArea); 438 mTouchableAreaBuffer.scale(applicationScale); 439 return mTouchableAreaBuffer; 440 } 441 } 442 applyToDisplayMetrics(DisplayMetrics inoutDm)443 public void applyToDisplayMetrics(DisplayMetrics inoutDm) { 444 if (!supportsScreen()) { 445 // This is a larger screen device and the app is not 446 // compatible with large screens, so diddle it. 447 CompatibilityInfo.computeCompatibleScaling(inoutDm, inoutDm); 448 } else { 449 inoutDm.widthPixels = inoutDm.noncompatWidthPixels; 450 inoutDm.heightPixels = inoutDm.noncompatHeightPixels; 451 } 452 453 if (isScalingRequired()) { 454 float invertedRatio = applicationInvertedScale; 455 inoutDm.density = inoutDm.noncompatDensity * invertedRatio; 456 inoutDm.densityDpi = (int)((inoutDm.noncompatDensityDpi * invertedRatio) + .5f); 457 inoutDm.scaledDensity = inoutDm.noncompatScaledDensity * invertedRatio; 458 inoutDm.xdpi = inoutDm.noncompatXdpi * invertedRatio; 459 inoutDm.ydpi = inoutDm.noncompatYdpi * invertedRatio; 460 inoutDm.widthPixels = (int) (inoutDm.widthPixels * invertedRatio + 0.5f); 461 inoutDm.heightPixels = (int) (inoutDm.heightPixels * invertedRatio + 0.5f); 462 } 463 } 464 applyToConfiguration(int displayDensity, Configuration inoutConfig)465 public void applyToConfiguration(int displayDensity, Configuration inoutConfig) { 466 if (!supportsScreen()) { 467 // This is a larger screen device and the app is not 468 // compatible with large screens, so we are forcing it to 469 // run as if the screen is normal size. 470 inoutConfig.screenLayout = 471 (inoutConfig.screenLayout&~Configuration.SCREENLAYOUT_SIZE_MASK) 472 | Configuration.SCREENLAYOUT_SIZE_NORMAL; 473 inoutConfig.screenWidthDp = inoutConfig.compatScreenWidthDp; 474 inoutConfig.screenHeightDp = inoutConfig.compatScreenHeightDp; 475 inoutConfig.smallestScreenWidthDp = inoutConfig.compatSmallestScreenWidthDp; 476 } 477 inoutConfig.densityDpi = displayDensity; 478 if (isScalingRequired()) { 479 float invertedRatio = applicationInvertedScale; 480 inoutConfig.densityDpi = (int)((inoutConfig.densityDpi * invertedRatio) + .5f); 481 } 482 } 483 484 /** 485 * Compute the frame Rect for applications runs under compatibility mode. 486 * 487 * @param dm the display metrics used to compute the frame size. 488 * @param outDm If non-null the width and height will be set to their scaled values. 489 * @return Returns the scaling factor for the window. 490 */ computeCompatibleScaling(DisplayMetrics dm, DisplayMetrics outDm)491 public static float computeCompatibleScaling(DisplayMetrics dm, DisplayMetrics outDm) { 492 final int width = dm.noncompatWidthPixels; 493 final int height = dm.noncompatHeightPixels; 494 int shortSize, longSize; 495 if (width < height) { 496 shortSize = width; 497 longSize = height; 498 } else { 499 shortSize = height; 500 longSize = width; 501 } 502 int newShortSize = (int)(DEFAULT_NORMAL_SHORT_DIMENSION * dm.density + 0.5f); 503 float aspect = ((float)longSize) / shortSize; 504 if (aspect > MAXIMUM_ASPECT_RATIO) { 505 aspect = MAXIMUM_ASPECT_RATIO; 506 } 507 int newLongSize = (int)(newShortSize * aspect + 0.5f); 508 int newWidth, newHeight; 509 if (width < height) { 510 newWidth = newShortSize; 511 newHeight = newLongSize; 512 } else { 513 newWidth = newLongSize; 514 newHeight = newShortSize; 515 } 516 517 float sw = width/(float)newWidth; 518 float sh = height/(float)newHeight; 519 float scale = sw < sh ? sw : sh; 520 if (scale < 1) { 521 scale = 1; 522 } 523 524 if (outDm != null) { 525 outDm.widthPixels = newWidth; 526 outDm.heightPixels = newHeight; 527 } 528 529 return scale; 530 } 531 532 @Override 533 public boolean equals(Object o) { 534 if (this == o) { 535 return true; 536 } 537 try { 538 CompatibilityInfo oc = (CompatibilityInfo)o; 539 if (mCompatibilityFlags != oc.mCompatibilityFlags) return false; 540 if (applicationDensity != oc.applicationDensity) return false; 541 if (applicationScale != oc.applicationScale) return false; 542 if (applicationInvertedScale != oc.applicationInvertedScale) return false; 543 return true; 544 } catch (ClassCastException e) { 545 return false; 546 } 547 } 548 549 @Override 550 public String toString() { 551 StringBuilder sb = new StringBuilder(128); 552 sb.append("{"); 553 sb.append(applicationDensity); 554 sb.append("dpi"); 555 if (isScalingRequired()) { 556 sb.append(" "); 557 sb.append(applicationScale); 558 sb.append("x"); 559 } 560 if (!supportsScreen()) { 561 sb.append(" resizing"); 562 } 563 if (neverSupportsScreen()) { 564 sb.append(" never-compat"); 565 } 566 if (alwaysSupportsScreen()) { 567 sb.append(" always-compat"); 568 } 569 sb.append("}"); 570 return sb.toString(); 571 } 572 573 @Override 574 public int hashCode() { 575 int result = 17; 576 result = 31 * result + mCompatibilityFlags; 577 result = 31 * result + applicationDensity; 578 result = 31 * result + Float.floatToIntBits(applicationScale); 579 result = 31 * result + Float.floatToIntBits(applicationInvertedScale); 580 return result; 581 } 582 583 @Override 584 public int describeContents() { 585 return 0; 586 } 587 588 @Override 589 public void writeToParcel(Parcel dest, int flags) { 590 dest.writeInt(mCompatibilityFlags); 591 dest.writeInt(applicationDensity); 592 dest.writeFloat(applicationScale); 593 dest.writeFloat(applicationInvertedScale); 594 } 595 596 public static final Parcelable.Creator<CompatibilityInfo> CREATOR 597 = new Parcelable.Creator<CompatibilityInfo>() { 598 @Override 599 public CompatibilityInfo createFromParcel(Parcel source) { 600 return new CompatibilityInfo(source); 601 } 602 603 @Override 604 public CompatibilityInfo[] newArray(int size) { 605 return new CompatibilityInfo[size]; 606 } 607 }; 608 609 private CompatibilityInfo(Parcel source) { 610 mCompatibilityFlags = source.readInt(); 611 applicationDensity = source.readInt(); 612 applicationScale = source.readFloat(); 613 applicationInvertedScale = source.readFloat(); 614 } 615 } 616