Home | History | Annotate | Download | only in res
      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