CompatibilityInfo.java revision cf4550c3198d6b3d92cdc52707fe70d7cc0caa9f
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.Rect; 22import android.graphics.Region; 23import android.util.DisplayMetrics; 24import android.util.Log; 25import android.view.Gravity; 26import android.view.MotionEvent; 27import android.view.WindowManager; 28import android.view.WindowManager.LayoutParams; 29 30/** 31 * CompatibilityInfo class keeps the information about compatibility mode that the application is 32 * running under. 33 * 34 * {@hide} 35 */ 36public class CompatibilityInfo { 37 private static final boolean DBG = false; 38 private static final String TAG = "CompatibilityInfo"; 39 40 /** default compatibility info object for compatible applications */ 41 public static final CompatibilityInfo DEFAULT_COMPATIBILITY_INFO = new CompatibilityInfo(); 42 43 /** 44 * The default width of the screen in portrait mode. 45 */ 46 public static final int DEFAULT_PORTRAIT_WIDTH = 320; 47 48 /** 49 * The default height of the screen in portrait mode. 50 */ 51 public static final int DEFAULT_PORTRAIT_HEIGHT = 480; 52 53 /** 54 * The x-shift mode that controls the position of the content or the window under 55 * compatibility mode. 56 * {@see getTranslator} 57 * {@see Translator#mShiftMode} 58 */ 59 private static final int X_SHIFT_NONE = 0; 60 private static final int X_SHIFT_CONTENT = 1; 61 private static final int X_SHIFT_AND_CLIP_CONTENT = 2; 62 private static final int X_SHIFT_WINDOW = 3; 63 64 65 /** 66 * A compatibility flags 67 */ 68 private int mCompatibilityFlags; 69 70 /** 71 * A flag mask to tell if the application needs scaling (when mApplicationScale != 1.0f) 72 * {@see compatibilityFlag} 73 */ 74 private static final int SCALING_REQUIRED = 1; 75 76 /** 77 * A flag mask to indicates that the application can expand over the original size. 78 * The flag is set to true if 79 * 1) Application declares its expandable in manifest file using <expandable /> or 80 * 2) The screen size is same as (320 x 480) * density. 81 * {@see compatibilityFlag} 82 */ 83 private static final int EXPANDABLE = 2; 84 85 /** 86 * A flag mask to tell if the application is configured to be expandable. This differs 87 * from EXPANDABLE in that the application that is not expandable will be 88 * marked as expandable if it runs in (320x 480) * density screen size. 89 */ 90 private static final int CONFIGURED_EXPANDABLE = 4; 91 92 private static final int SCALING_EXPANDABLE_MASK = SCALING_REQUIRED | EXPANDABLE; 93 94 /** 95 * Application's scale. 96 */ 97 public final float applicationScale; 98 99 /** 100 * Application's inverted scale. 101 */ 102 public final float applicationInvertedScale; 103 104 /** 105 * The flags from ApplicationInfo. 106 */ 107 public final int appFlags; 108 109 /** 110 * Window size in Compatibility Mode, in real pixels. This is updated by 111 * {@link DisplayMetrics#updateMetrics}. 112 */ 113 private int mWidth; 114 private int mHeight; 115 116 /** 117 * The x offset to center the window content. In X_SHIFT_WINDOW mode, the offset is added 118 * to the window's layout. In X_SHIFT_CONTENT/X_SHIFT_AND_CLIP_CONTENT mode, the offset 119 * is used to translate the Canvas. 120 */ 121 private int mXOffset; 122 123 public CompatibilityInfo(ApplicationInfo appInfo) { 124 appFlags = appInfo.flags; 125 126 if ((appInfo.flags & ApplicationInfo.FLAG_SUPPORTS_LARGE_SCREENS) != 0) { 127 mCompatibilityFlags = EXPANDABLE | CONFIGURED_EXPANDABLE; 128 } 129 130 float packageDensityScale = -1.0f; 131 if (appInfo.supportsDensities != null) { 132 int minDiff = Integer.MAX_VALUE; 133 for (int density : appInfo.supportsDensities) { 134 if (density == ApplicationInfo.ANY_DENSITY) { 135 packageDensityScale = 1.0f; 136 break; 137 } 138 int tmpDiff = Math.abs(DisplayMetrics.DEVICE_DENSITY - density); 139 if (tmpDiff == 0) { 140 packageDensityScale = 1.0f; 141 break; 142 } 143 // prefer higher density (appScale>1.0), unless that's only option. 144 if (tmpDiff < minDiff && packageDensityScale < 1.0f) { 145 packageDensityScale = DisplayMetrics.DEVICE_DENSITY / (float) density; 146 minDiff = tmpDiff; 147 } 148 } 149 } 150 if (packageDensityScale > 0.0f) { 151 applicationScale = packageDensityScale; 152 } else { 153 applicationScale = 154 DisplayMetrics.DEVICE_DENSITY / (float) DisplayMetrics.DEFAULT_DENSITY; 155 } 156 applicationInvertedScale = 1.0f / applicationScale; 157 if (applicationScale != 1.0f) { 158 mCompatibilityFlags |= SCALING_REQUIRED; 159 } 160 } 161 162 private CompatibilityInfo(int appFlags, int compFlags, float scale, float invertedScale) { 163 this.appFlags = appFlags; 164 mCompatibilityFlags = compFlags; 165 applicationScale = scale; 166 applicationInvertedScale = invertedScale; 167 } 168 169 private CompatibilityInfo() { 170 this(ApplicationInfo.FLAG_SUPPORTS_SMALL_SCREENS 171 | ApplicationInfo.FLAG_SUPPORTS_NORMAL_SCREENS 172 | ApplicationInfo.FLAG_SUPPORTS_LARGE_SCREENS, 173 EXPANDABLE | CONFIGURED_EXPANDABLE, 174 1.0f, 175 1.0f); 176 } 177 178 /** 179 * Returns the copy of this instance. 180 */ 181 public CompatibilityInfo copy() { 182 CompatibilityInfo info = new CompatibilityInfo(appFlags, mCompatibilityFlags, 183 applicationScale, applicationInvertedScale); 184 info.setVisibleRect(mXOffset, mWidth, mHeight); 185 return info; 186 } 187 188 /** 189 * Sets the application's visible rect in compatibility mode. 190 * @param xOffset the application's x offset that is added to center the content. 191 * @param widthPixels the application's width in real pixels on the screen. 192 * @param heightPixels the application's height in real pixels on the screen. 193 */ 194 public void setVisibleRect(int xOffset, int widthPixels, int heightPixels) { 195 this.mXOffset = xOffset; 196 mWidth = widthPixels; 197 mHeight = heightPixels; 198 } 199 200 /** 201 * Sets expandable bit in the compatibility flag. 202 */ 203 public void setExpandable(boolean expandable) { 204 if (expandable) { 205 mCompatibilityFlags |= CompatibilityInfo.EXPANDABLE; 206 } else { 207 mCompatibilityFlags &= ~CompatibilityInfo.EXPANDABLE; 208 } 209 } 210 211 /** 212 * @return true if the application is configured to be expandable. 213 */ 214 public boolean isConfiguredExpandable() { 215 return (mCompatibilityFlags & CompatibilityInfo.CONFIGURED_EXPANDABLE) != 0; 216 } 217 218 /** 219 * @return true if the scaling is required 220 */ 221 public boolean isScalingRequired() { 222 return (mCompatibilityFlags & SCALING_REQUIRED) != 0; 223 } 224 225 @Override 226 public String toString() { 227 return "CompatibilityInfo{scale=" + applicationScale + 228 ", compatibility flag=" + mCompatibilityFlags + "}"; 229 } 230 231 /** 232 * Returns the translator which can translate the coordinates of the window. 233 * There are five different types of Translator. 234 * 235 * 1) {@link CompatibilityInfo#X_SHIFT_AND_CLIP_CONTENT} 236 * Shift and clip the content of the window at drawing time. Used for activities' 237 * main window (with no gravity). 238 * 2) {@link CompatibilityInfo#X_SHIFT_CONTENT} 239 * Shift the content of the window at drawing time. Used for windows that is created by 240 * an application and expected to be aligned with the application window. 241 * 3) {@link CompatibilityInfo#X_SHIFT_WINDOW} 242 * Create the window with adjusted x- coordinates. This is typically used 243 * in popup window, where it has to be placed relative to main window. 244 * 4) {@link CompatibilityInfo#X_SHIFT_NONE} 245 * No adjustment required, such as dialog. 246 * 5) Same as X_SHIFT_WINDOW, but no scaling. This is used by {@link SurfaceView}, which 247 * does not require scaling, but its window's location has to be adjusted. 248 * 249 * @param params the window's parameter 250 */ 251 public Translator getTranslator(WindowManager.LayoutParams params) { 252 if ( (mCompatibilityFlags & CompatibilityInfo.SCALING_EXPANDABLE_MASK) 253 == CompatibilityInfo.EXPANDABLE) { 254 if (DBG) Log.d(TAG, "no translation required"); 255 return null; 256 } 257 258 if ((mCompatibilityFlags & CompatibilityInfo.EXPANDABLE) == 0) { 259 if ((params.flags & WindowManager.LayoutParams.FLAG_NO_COMPATIBILITY_SCALING) != 0) { 260 if (DBG) Log.d(TAG, "translation for surface view selected"); 261 return new Translator(X_SHIFT_WINDOW, false, 1.0f, 1.0f); 262 } else { 263 int shiftMode; 264 if (params.gravity == Gravity.NO_GRAVITY) { 265 // For Regular Application window 266 shiftMode = X_SHIFT_AND_CLIP_CONTENT; 267 if (DBG) Log.d(TAG, "shift and clip translator"); 268 } else if (params.width == WindowManager.LayoutParams.FILL_PARENT) { 269 // For Regular Application window 270 shiftMode = X_SHIFT_CONTENT; 271 if (DBG) Log.d(TAG, "shift content translator"); 272 } else if ((params.gravity & Gravity.LEFT) != 0 && params.x > 0) { 273 shiftMode = X_SHIFT_WINDOW; 274 if (DBG) Log.d(TAG, "shift window translator"); 275 } else { 276 shiftMode = X_SHIFT_NONE; 277 if (DBG) Log.d(TAG, "no content/window translator"); 278 } 279 return new Translator(shiftMode); 280 } 281 } else if (isScalingRequired()) { 282 return new Translator(); 283 } else { 284 return null; 285 } 286 } 287 288 /** 289 * A helper object to translate the screen and window coordinates back and forth. 290 * @hide 291 */ 292 public class Translator { 293 final private int mShiftMode; 294 final public boolean scalingRequired; 295 final public float applicationScale; 296 final public float applicationInvertedScale; 297 298 private Rect mContentInsetsBuffer = null; 299 private Rect mVisibleInsets = null; 300 301 Translator(int shiftMode, boolean scalingRequired, float applicationScale, 302 float applicationInvertedScale) { 303 mShiftMode = shiftMode; 304 this.scalingRequired = scalingRequired; 305 this.applicationScale = applicationScale; 306 this.applicationInvertedScale = applicationInvertedScale; 307 } 308 309 Translator(int shiftMode) { 310 this(shiftMode, 311 isScalingRequired(), 312 CompatibilityInfo.this.applicationScale, 313 CompatibilityInfo.this.applicationInvertedScale); 314 } 315 316 Translator() { 317 this(X_SHIFT_NONE); 318 } 319 320 /** 321 * Translate the screen rect to the application frame. 322 */ 323 public void translateRectInScreenToAppWinFrame(Rect rect) { 324 if (rect.isEmpty()) return; // skip if the window size is empty. 325 switch (mShiftMode) { 326 case X_SHIFT_AND_CLIP_CONTENT: 327 rect.intersect(0, 0, mWidth, mHeight); 328 break; 329 case X_SHIFT_CONTENT: 330 rect.intersect(0, 0, mWidth + mXOffset, mHeight); 331 break; 332 case X_SHIFT_WINDOW: 333 case X_SHIFT_NONE: 334 break; 335 } 336 if (scalingRequired) { 337 rect.scale(applicationInvertedScale); 338 } 339 } 340 341 /** 342 * Translate the region in window to screen. 343 */ 344 public void translateRegionInWindowToScreen(Region transparentRegion) { 345 switch (mShiftMode) { 346 case X_SHIFT_AND_CLIP_CONTENT: 347 case X_SHIFT_CONTENT: 348 transparentRegion.scale(applicationScale); 349 transparentRegion.translate(mXOffset, 0); 350 break; 351 case X_SHIFT_WINDOW: 352 case X_SHIFT_NONE: 353 transparentRegion.scale(applicationScale); 354 } 355 } 356 357 /** 358 * Apply translation to the canvas that is necessary to draw the content. 359 */ 360 public void translateCanvas(Canvas canvas) { 361 if (mShiftMode == X_SHIFT_CONTENT || 362 mShiftMode == X_SHIFT_AND_CLIP_CONTENT) { 363 // TODO: clear outside when rotation is changed. 364 365 // Translate x-offset only when the content is shifted. 366 canvas.translate(mXOffset, 0); 367 } 368 if (scalingRequired) { 369 canvas.scale(applicationScale, applicationScale); 370 } 371 } 372 373 /** 374 * Translate the motion event captured on screen to the application's window. 375 */ 376 public void translateEventInScreenToAppWindow(MotionEvent event) { 377 if (mShiftMode == X_SHIFT_CONTENT || 378 mShiftMode == X_SHIFT_AND_CLIP_CONTENT) { 379 event.translate(-mXOffset, 0); 380 } 381 if (scalingRequired) { 382 event.scale(applicationInvertedScale); 383 } 384 } 385 386 /** 387 * Translate the window's layout parameter, from application's view to 388 * Screen's view. 389 */ 390 public void translateWindowLayout(WindowManager.LayoutParams params) { 391 switch (mShiftMode) { 392 case X_SHIFT_NONE: 393 case X_SHIFT_AND_CLIP_CONTENT: 394 case X_SHIFT_CONTENT: 395 params.scale(applicationScale); 396 break; 397 case X_SHIFT_WINDOW: 398 params.scale(applicationScale); 399 params.x += mXOffset; 400 break; 401 } 402 } 403 404 /** 405 * Translate a Rect in application's window to screen. 406 */ 407 public void translateRectInAppWindowToScreen(Rect rect) { 408 // TODO Auto-generated method stub 409 if (scalingRequired) { 410 rect.scale(applicationScale); 411 } 412 switch(mShiftMode) { 413 case X_SHIFT_NONE: 414 case X_SHIFT_WINDOW: 415 break; 416 case X_SHIFT_CONTENT: 417 case X_SHIFT_AND_CLIP_CONTENT: 418 rect.offset(mXOffset, 0); 419 break; 420 } 421 } 422 423 /** 424 * Translate a Rect in screen coordinates into the app window's coordinates. 425 */ 426 public void translateRectInScreenToAppWindow(Rect rect) { 427 switch (mShiftMode) { 428 case X_SHIFT_NONE: 429 case X_SHIFT_WINDOW: 430 break; 431 case X_SHIFT_CONTENT: { 432 rect.intersects(mXOffset, 0, rect.right, rect.bottom); 433 int dx = Math.min(mXOffset, rect.left); 434 rect.offset(-dx, 0); 435 break; 436 } 437 case X_SHIFT_AND_CLIP_CONTENT: { 438 rect.intersects(mXOffset, 0, mWidth + mXOffset, mHeight); 439 int dx = Math.min(mXOffset, rect.left); 440 rect.offset(-dx, 0); 441 break; 442 } 443 } 444 if (scalingRequired) { 445 rect.scale(applicationInvertedScale); 446 } 447 } 448 449 /** 450 * Translate the location of the sub window. 451 * @param params 452 */ 453 public void translateLayoutParamsInAppWindowToScreen(LayoutParams params) { 454 if (scalingRequired) { 455 params.scale(applicationScale); 456 } 457 switch (mShiftMode) { 458 // the window location on these mode does not require adjustmenet. 459 case X_SHIFT_NONE: 460 case X_SHIFT_WINDOW: 461 break; 462 case X_SHIFT_CONTENT: 463 case X_SHIFT_AND_CLIP_CONTENT: 464 params.x += mXOffset; 465 break; 466 } 467 } 468 469 /** 470 * Translate the content insets in application window to Screen. This uses 471 * the internal buffer for content insets to avoid extra object allocation. 472 */ 473 public Rect getTranslatedContentInsets(Rect contentInsets) { 474 if (mContentInsetsBuffer == null) mContentInsetsBuffer = new Rect(); 475 mContentInsetsBuffer.set(contentInsets); 476 translateRectInAppWindowToScreen(mContentInsetsBuffer); 477 return mContentInsetsBuffer; 478 } 479 480 /** 481 * Translate the visible insets in application window to Screen. This uses 482 * the internal buffer for content insets to avoid extra object allocation. 483 */ 484 public Rect getTranslatedVisbileInsets(Rect visibleInsets) { 485 if (mVisibleInsets == null) mVisibleInsets = new Rect(); 486 mVisibleInsets.set(visibleInsets); 487 translateRectInAppWindowToScreen(mVisibleInsets); 488 return mVisibleInsets; 489 } 490 } 491} 492