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