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