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.density*DisplayMetrics.DENSITY_DEFAULT)+.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(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 } 464 465 /** 466 * Compute the frame Rect for applications runs under compatibility mode. 467 * 468 * @param dm the display metrics used to compute the frame size. 469 * @param orientation the orientation of the screen. 470 * @param outRect the output parameter which will contain the result. 471 * @return Returns the scaling factor for the window. 472 */ 473 public static float computeCompatibleScaling(DisplayMetrics dm, DisplayMetrics outDm) { 474 final int width = dm.noncompatWidthPixels; 475 final int height = dm.noncompatHeightPixels; 476 int shortSize, longSize; 477 if (width < height) { 478 shortSize = width; 479 longSize = height; 480 } else { 481 shortSize = height; 482 longSize = width; 483 } 484 int newShortSize = (int)(DEFAULT_NORMAL_SHORT_DIMENSION * dm.density + 0.5f); 485 float aspect = ((float)longSize) / shortSize; 486 if (aspect > MAXIMUM_ASPECT_RATIO) { 487 aspect = MAXIMUM_ASPECT_RATIO; 488 } 489 int newLongSize = (int)(newShortSize * aspect + 0.5f); 490 int newWidth, newHeight; 491 if (width < height) { 492 newWidth = newShortSize; 493 newHeight = newLongSize; 494 } else { 495 newWidth = newLongSize; 496 newHeight = newShortSize; 497 } 498 499 float sw = width/(float)newWidth; 500 float sh = height/(float)newHeight; 501 float scale = sw < sh ? sw : sh; 502 if (scale < 1) { 503 scale = 1; 504 } 505 506 if (outDm != null) { 507 outDm.widthPixels = newWidth; 508 outDm.heightPixels = newHeight; 509 } 510 511 return scale; 512 } 513 514 @Override 515 public boolean equals(Object o) { 516 try { 517 CompatibilityInfo oc = (CompatibilityInfo)o; 518 if (mCompatibilityFlags != oc.mCompatibilityFlags) return false; 519 if (applicationDensity != oc.applicationDensity) return false; 520 if (applicationScale != oc.applicationScale) return false; 521 if (applicationInvertedScale != oc.applicationInvertedScale) return false; 522 return true; 523 } catch (ClassCastException e) { 524 return false; 525 } 526 } 527 528 @Override 529 public String toString() { 530 StringBuilder sb = new StringBuilder(128); 531 sb.append("{"); 532 sb.append(applicationDensity); 533 sb.append("dpi"); 534 if (isScalingRequired()) { 535 sb.append(" "); 536 sb.append(applicationScale); 537 sb.append("x"); 538 } 539 if (!supportsScreen()) { 540 sb.append(" resizing"); 541 } 542 if (neverSupportsScreen()) { 543 sb.append(" never-compat"); 544 } 545 if (alwaysSupportsScreen()) { 546 sb.append(" always-compat"); 547 } 548 sb.append("}"); 549 return sb.toString(); 550 } 551 552 @Override 553 public int hashCode() { 554 int result = 17; 555 result = 31 * result + mCompatibilityFlags; 556 result = 31 * result + applicationDensity; 557 result = 31 * result + Float.floatToIntBits(applicationScale); 558 result = 31 * result + Float.floatToIntBits(applicationInvertedScale); 559 return result; 560 } 561 562 @Override 563 public int describeContents() { 564 return 0; 565 } 566 567 @Override 568 public void writeToParcel(Parcel dest, int flags) { 569 dest.writeInt(mCompatibilityFlags); 570 dest.writeInt(applicationDensity); 571 dest.writeFloat(applicationScale); 572 dest.writeFloat(applicationInvertedScale); 573 } 574 575 public static final Parcelable.Creator<CompatibilityInfo> CREATOR 576 = new Parcelable.Creator<CompatibilityInfo>() { 577 public CompatibilityInfo createFromParcel(Parcel source) { 578 return new CompatibilityInfo(source); 579 } 580 581 public CompatibilityInfo[] newArray(int size) { 582 return new CompatibilityInfo[size]; 583 } 584 }; 585 586 private CompatibilityInfo(Parcel source) { 587 mCompatibilityFlags = source.readInt(); 588 applicationDensity = source.readInt(); 589 applicationScale = source.readFloat(); 590 applicationInvertedScale = source.readFloat(); 591 } 592 } 593