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.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