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