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.Build;
     25 import android.os.Build.VERSION_CODES;
     26 import android.os.Parcel;
     27 import android.os.Parcelable;
     28 import android.util.DisplayMetrics;
     29 import android.view.MotionEvent;
     30 import android.view.WindowManager;
     31 import android.view.WindowManager.LayoutParams;
     32 
     33 /**
     34  * CompatibilityInfo class keeps the information about compatibility mode that the application is
     35  * running under.
     36  *
     37  *  {@hide}
     38  */
     39 public class CompatibilityInfo implements Parcelable {
     40     /** default compatibility info object for compatible applications */
     41     public static final CompatibilityInfo DEFAULT_COMPATIBILITY_INFO = new CompatibilityInfo() {
     42     };
     43 
     44     /**
     45      * This is the number of pixels we would like to have along the
     46      * short axis of an app that needs to run on a normal size screen.
     47      */
     48     public static final int DEFAULT_NORMAL_SHORT_DIMENSION = 320;
     49 
     50     /**
     51      * This is the maximum aspect ratio we will allow while keeping
     52      * applications in a compatible screen size.
     53      */
     54     public static final float MAXIMUM_ASPECT_RATIO = (854f/480f);
     55 
     56     /**
     57      *  A compatibility flags
     58      */
     59     private final int mCompatibilityFlags;
     60 
     61     /**
     62      * A flag mask to tell if the application needs scaling (when mApplicationScale != 1.0f)
     63      * {@see compatibilityFlag}
     64      */
     65     private static final int SCALING_REQUIRED = 1;
     66 
     67     /**
     68      * Application must always run in compatibility mode?
     69      */
     70     private static final int ALWAYS_NEEDS_COMPAT = 2;
     71 
     72     /**
     73      * Application never should run in compatibility mode?
     74      */
     75     private static final int NEVER_NEEDS_COMPAT = 4;
     76 
     77     /**
     78      * Set if the application needs to run in screen size compatibility mode.
     79      */
     80     private static final int NEEDS_SCREEN_COMPAT = 8;
     81 
     82     /**
     83      * Set if the application needs to run in with compat resources.
     84      */
     85     private static final int NEEDS_COMPAT_RES = 16;
     86 
     87     /**
     88      * The effective screen density we have selected for this application.
     89      */
     90     public final int applicationDensity;
     91 
     92     /**
     93      * Application's scale.
     94      */
     95     public final float applicationScale;
     96 
     97     /**
     98      * Application's inverted scale.
     99      */
    100     public final float applicationInvertedScale;
    101 
    102     public CompatibilityInfo(ApplicationInfo appInfo, int screenLayout, int sw,
    103             boolean forceCompat) {
    104         int compatFlags = 0;
    105 
    106         if (appInfo.targetSdkVersion < VERSION_CODES.O) {
    107             compatFlags |= NEEDS_COMPAT_RES;
    108         }
    109         if (appInfo.requiresSmallestWidthDp != 0 || appInfo.compatibleWidthLimitDp != 0
    110                 || appInfo.largestWidthLimitDp != 0) {
    111             // New style screen requirements spec.
    112             int required = appInfo.requiresSmallestWidthDp != 0
    113                     ? appInfo.requiresSmallestWidthDp
    114                     : appInfo.compatibleWidthLimitDp;
    115             if (required == 0) {
    116                 required = appInfo.largestWidthLimitDp;
    117             }
    118             int compat = appInfo.compatibleWidthLimitDp != 0
    119                     ? appInfo.compatibleWidthLimitDp : required;
    120             if (compat < required)  {
    121                 compat = required;
    122             }
    123             int largest = appInfo.largestWidthLimitDp;
    124 
    125             if (required > DEFAULT_NORMAL_SHORT_DIMENSION) {
    126                 // For now -- if they require a size larger than the only
    127                 // size we can do in compatibility mode, then don't ever
    128                 // allow the app to go in to compat mode.  Trying to run
    129                 // it at a smaller size it can handle will make it far more
    130                 // broken than running at a larger size than it wants or
    131                 // thinks it can handle.
    132                 compatFlags |= NEVER_NEEDS_COMPAT;
    133             } else if (largest != 0 && sw > largest) {
    134                 // If the screen size is larger than the largest size the
    135                 // app thinks it can work with, then always force it in to
    136                 // compatibility mode.
    137                 compatFlags |= NEEDS_SCREEN_COMPAT | ALWAYS_NEEDS_COMPAT;
    138             } else if (compat >= sw) {
    139                 // The screen size is something the app says it was designed
    140                 // for, so never do compatibility mode.
    141                 compatFlags |= NEVER_NEEDS_COMPAT;
    142             } else if (forceCompat) {
    143                 // The app may work better with or without compatibility mode.
    144                 // Let the user decide.
    145                 compatFlags |= NEEDS_SCREEN_COMPAT;
    146             }
    147 
    148             // Modern apps always support densities.
    149             applicationDensity = DisplayMetrics.DENSITY_DEVICE;
    150             applicationScale = 1.0f;
    151             applicationInvertedScale = 1.0f;
    152 
    153         } else {
    154             /**
    155              * Has the application said that its UI is expandable?  Based on the
    156              * <supports-screen> android:expandible in the manifest.
    157              */
    158             final int EXPANDABLE = 2;
    159 
    160             /**
    161              * Has the application said that its UI supports large screens?  Based on the
    162              * <supports-screen> android:largeScreens in the manifest.
    163              */
    164             final int LARGE_SCREENS = 8;
    165 
    166             /**
    167              * Has the application said that its UI supports xlarge screens?  Based on the
    168              * <supports-screen> android:xlargeScreens in the manifest.
    169              */
    170             final int XLARGE_SCREENS = 32;
    171 
    172             int sizeInfo = 0;
    173 
    174             // We can't rely on the application always setting
    175             // FLAG_RESIZEABLE_FOR_SCREENS so will compute it based on various input.
    176             boolean anyResizeable = false;
    177 
    178             if ((appInfo.flags & ApplicationInfo.FLAG_SUPPORTS_LARGE_SCREENS) != 0) {
    179                 sizeInfo |= LARGE_SCREENS;
    180                 anyResizeable = true;
    181                 if (!forceCompat) {
    182                     // If we aren't forcing the app into compatibility mode, then
    183                     // assume if it supports large screens that we should allow it
    184                     // to use the full space of an xlarge screen as well.
    185                     sizeInfo |= XLARGE_SCREENS | EXPANDABLE;
    186                 }
    187             }
    188             if ((appInfo.flags & ApplicationInfo.FLAG_SUPPORTS_XLARGE_SCREENS) != 0) {
    189                 anyResizeable = true;
    190                 if (!forceCompat) {
    191                     sizeInfo |= XLARGE_SCREENS | EXPANDABLE;
    192                 }
    193             }
    194             if ((appInfo.flags & ApplicationInfo.FLAG_RESIZEABLE_FOR_SCREENS) != 0) {
    195                 anyResizeable = true;
    196                 sizeInfo |= EXPANDABLE;
    197             }
    198 
    199             if (forceCompat) {
    200                 // If we are forcing compatibility mode, then ignore an app that
    201                 // just says it is resizable for screens.  We'll only have it fill
    202                 // the screen if it explicitly says it supports the screen size we
    203                 // are running in.
    204                 sizeInfo &= ~EXPANDABLE;
    205             }
    206 
    207             compatFlags |= NEEDS_SCREEN_COMPAT;
    208             switch (screenLayout&Configuration.SCREENLAYOUT_SIZE_MASK) {
    209                 case Configuration.SCREENLAYOUT_SIZE_XLARGE:
    210                     if ((sizeInfo&XLARGE_SCREENS) != 0) {
    211                         compatFlags &= ~NEEDS_SCREEN_COMPAT;
    212                     }
    213                     if ((appInfo.flags & ApplicationInfo.FLAG_SUPPORTS_XLARGE_SCREENS) != 0) {
    214                         compatFlags |= NEVER_NEEDS_COMPAT;
    215                     }
    216                     break;
    217                 case Configuration.SCREENLAYOUT_SIZE_LARGE:
    218                     if ((sizeInfo&LARGE_SCREENS) != 0) {
    219                         compatFlags &= ~NEEDS_SCREEN_COMPAT;
    220                     }
    221                     if ((appInfo.flags & ApplicationInfo.FLAG_SUPPORTS_LARGE_SCREENS) != 0) {
    222                         compatFlags |= NEVER_NEEDS_COMPAT;
    223                     }
    224                     break;
    225             }
    226 
    227             if ((screenLayout&Configuration.SCREENLAYOUT_COMPAT_NEEDED) != 0) {
    228                 if ((sizeInfo&EXPANDABLE) != 0) {
    229                     compatFlags &= ~NEEDS_SCREEN_COMPAT;
    230                 } else if (!anyResizeable) {
    231                     compatFlags |= ALWAYS_NEEDS_COMPAT;
    232                 }
    233             } else {
    234                 compatFlags &= ~NEEDS_SCREEN_COMPAT;
    235                 compatFlags |= NEVER_NEEDS_COMPAT;
    236             }
    237 
    238             if ((appInfo.flags & ApplicationInfo.FLAG_SUPPORTS_SCREEN_DENSITIES) != 0) {
    239                 applicationDensity = DisplayMetrics.DENSITY_DEVICE;
    240                 applicationScale = 1.0f;
    241                 applicationInvertedScale = 1.0f;
    242             } else {
    243                 applicationDensity = DisplayMetrics.DENSITY_DEFAULT;
    244                 applicationScale = DisplayMetrics.DENSITY_DEVICE
    245                         / (float) DisplayMetrics.DENSITY_DEFAULT;
    246                 applicationInvertedScale = 1.0f / applicationScale;
    247                 compatFlags |= SCALING_REQUIRED;
    248             }
    249         }
    250 
    251         mCompatibilityFlags = compatFlags;
    252     }
    253 
    254     private CompatibilityInfo(int compFlags,
    255             int dens, float scale, float invertedScale) {
    256         mCompatibilityFlags = compFlags;
    257         applicationDensity = dens;
    258         applicationScale = scale;
    259         applicationInvertedScale = invertedScale;
    260     }
    261 
    262     private CompatibilityInfo() {
    263         this(NEVER_NEEDS_COMPAT, DisplayMetrics.DENSITY_DEVICE,
    264                 1.0f,
    265                 1.0f);
    266     }
    267 
    268     /**
    269      * @return true if the scaling is required
    270      */
    271     public boolean isScalingRequired() {
    272         return (mCompatibilityFlags&SCALING_REQUIRED) != 0;
    273     }
    274 
    275     public boolean supportsScreen() {
    276         return (mCompatibilityFlags&NEEDS_SCREEN_COMPAT) == 0;
    277     }
    278 
    279     public boolean neverSupportsScreen() {
    280         return (mCompatibilityFlags&ALWAYS_NEEDS_COMPAT) != 0;
    281     }
    282 
    283     public boolean alwaysSupportsScreen() {
    284         return (mCompatibilityFlags&NEVER_NEEDS_COMPAT) != 0;
    285     }
    286 
    287     public boolean needsCompatResources() {
    288         return (mCompatibilityFlags&NEEDS_COMPAT_RES) != 0;
    289     }
    290 
    291     /**
    292      * Returns the translator which translates the coordinates in compatibility mode.
    293      * @param params the window's parameter
    294      */
    295     public Translator getTranslator() {
    296         return isScalingRequired() ? new Translator() : null;
    297     }
    298 
    299     /**
    300      * A helper object to translate the screen and window coordinates back and forth.
    301      * @hide
    302      */
    303     public class Translator {
    304         final public float applicationScale;
    305         final public float applicationInvertedScale;
    306 
    307         private Rect mContentInsetsBuffer = null;
    308         private Rect mVisibleInsetsBuffer = null;
    309         private Region mTouchableAreaBuffer = null;
    310 
    311         Translator(float applicationScale, float applicationInvertedScale) {
    312             this.applicationScale = applicationScale;
    313             this.applicationInvertedScale = applicationInvertedScale;
    314         }
    315 
    316         Translator() {
    317             this(CompatibilityInfo.this.applicationScale,
    318                     CompatibilityInfo.this.applicationInvertedScale);
    319         }
    320 
    321         /**
    322          * Translate the screen rect to the application frame.
    323          */
    324         public void translateRectInScreenToAppWinFrame(Rect rect) {
    325             rect.scale(applicationInvertedScale);
    326         }
    327 
    328         /**
    329          * Translate the region in window to screen.
    330          */
    331         public void translateRegionInWindowToScreen(Region transparentRegion) {
    332             transparentRegion.scale(applicationScale);
    333         }
    334 
    335         /**
    336          * Apply translation to the canvas that is necessary to draw the content.
    337          */
    338         public void translateCanvas(Canvas canvas) {
    339             if (applicationScale == 1.5f) {
    340                 /*  When we scale for compatibility, we can put our stretched
    341                     bitmaps and ninepatches on exacty 1/2 pixel boundaries,
    342                     which can give us inconsistent drawing due to imperfect
    343                     float precision in the graphics engine's inverse matrix.
    344 
    345                     As a work-around, we translate by a tiny amount to avoid
    346                     landing on exact pixel centers and boundaries, giving us
    347                     the slop we need to draw consistently.
    348 
    349                     This constant is meant to resolve to 1/255 after it is
    350                     scaled by 1.5 (applicationScale). Note, this is just a guess
    351                     as to what is small enough not to create its own artifacts,
    352                     and big enough to avoid the precision problems. Feel free
    353                     to experiment with smaller values as you choose.
    354                  */
    355                 final float tinyOffset = 2.0f / (3 * 255);
    356                 canvas.translate(tinyOffset, tinyOffset);
    357             }
    358             canvas.scale(applicationScale, applicationScale);
    359         }
    360 
    361         /**
    362          * Translate the motion event captured on screen to the application's window.
    363          */
    364         public void translateEventInScreenToAppWindow(MotionEvent event) {
    365             event.scale(applicationInvertedScale);
    366         }
    367 
    368         /**
    369          * Translate the window's layout parameter, from application's view to
    370          * Screen's view.
    371          */
    372         public void translateWindowLayout(WindowManager.LayoutParams params) {
    373             params.scale(applicationScale);
    374         }
    375 
    376         /**
    377          * Translate a Rect in application's window to screen.
    378          */
    379         public void translateRectInAppWindowToScreen(Rect rect) {
    380             rect.scale(applicationScale);
    381         }
    382 
    383         /**
    384          * Translate a Rect in screen coordinates into the app window's coordinates.
    385          */
    386         public void translateRectInScreenToAppWindow(Rect rect) {
    387             rect.scale(applicationInvertedScale);
    388         }
    389 
    390         /**
    391          * Translate a Point in screen coordinates into the app window's coordinates.
    392          */
    393         public void translatePointInScreenToAppWindow(PointF point) {
    394             final float scale = applicationInvertedScale;
    395             if (scale != 1.0f) {
    396                 point.x *= scale;
    397                 point.y *= scale;
    398             }
    399         }
    400 
    401         /**
    402          * Translate the location of the sub window.
    403          * @param params
    404          */
    405         public void translateLayoutParamsInAppWindowToScreen(LayoutParams params) {
    406             params.scale(applicationScale);
    407         }
    408 
    409         /**
    410          * Translate the content insets in application window to Screen. This uses
    411          * the internal buffer for content insets to avoid extra object allocation.
    412          */
    413         public Rect getTranslatedContentInsets(Rect contentInsets) {
    414             if (mContentInsetsBuffer == null) mContentInsetsBuffer = new Rect();
    415             mContentInsetsBuffer.set(contentInsets);
    416             translateRectInAppWindowToScreen(mContentInsetsBuffer);
    417             return mContentInsetsBuffer;
    418         }
    419 
    420         /**
    421          * Translate the visible insets in application window to Screen. This uses
    422          * the internal buffer for visible insets to avoid extra object allocation.
    423          */
    424         public Rect getTranslatedVisibleInsets(Rect visibleInsets) {
    425             if (mVisibleInsetsBuffer == null) mVisibleInsetsBuffer = new Rect();
    426             mVisibleInsetsBuffer.set(visibleInsets);
    427             translateRectInAppWindowToScreen(mVisibleInsetsBuffer);
    428             return mVisibleInsetsBuffer;
    429         }
    430 
    431         /**
    432          * Translate the touchable area in application window to Screen. This uses
    433          * the internal buffer for touchable area to avoid extra object allocation.
    434          */
    435         public Region getTranslatedTouchableArea(Region touchableArea) {
    436             if (mTouchableAreaBuffer == null) mTouchableAreaBuffer = new Region();
    437             mTouchableAreaBuffer.set(touchableArea);
    438             mTouchableAreaBuffer.scale(applicationScale);
    439             return mTouchableAreaBuffer;
    440         }
    441     }
    442 
    443     public void applyToDisplayMetrics(DisplayMetrics inoutDm) {
    444         if (!supportsScreen()) {
    445             // This is a larger screen device and the app is not
    446             // compatible with large screens, so diddle it.
    447             CompatibilityInfo.computeCompatibleScaling(inoutDm, inoutDm);
    448         } else {
    449             inoutDm.widthPixels = inoutDm.noncompatWidthPixels;
    450             inoutDm.heightPixels = inoutDm.noncompatHeightPixels;
    451         }
    452 
    453         if (isScalingRequired()) {
    454             float invertedRatio = applicationInvertedScale;
    455             inoutDm.density = inoutDm.noncompatDensity * invertedRatio;
    456             inoutDm.densityDpi = (int)((inoutDm.noncompatDensityDpi * invertedRatio) + .5f);
    457             inoutDm.scaledDensity = inoutDm.noncompatScaledDensity * invertedRatio;
    458             inoutDm.xdpi = inoutDm.noncompatXdpi * invertedRatio;
    459             inoutDm.ydpi = inoutDm.noncompatYdpi * invertedRatio;
    460             inoutDm.widthPixels = (int) (inoutDm.widthPixels * invertedRatio + 0.5f);
    461             inoutDm.heightPixels = (int) (inoutDm.heightPixels * invertedRatio + 0.5f);
    462         }
    463     }
    464 
    465     public void applyToConfiguration(int displayDensity, Configuration inoutConfig) {
    466         if (!supportsScreen()) {
    467             // This is a larger screen device and the app is not
    468             // compatible with large screens, so we are forcing it to
    469             // run as if the screen is normal size.
    470             inoutConfig.screenLayout =
    471                     (inoutConfig.screenLayout&~Configuration.SCREENLAYOUT_SIZE_MASK)
    472                     | Configuration.SCREENLAYOUT_SIZE_NORMAL;
    473             inoutConfig.screenWidthDp = inoutConfig.compatScreenWidthDp;
    474             inoutConfig.screenHeightDp = inoutConfig.compatScreenHeightDp;
    475             inoutConfig.smallestScreenWidthDp = inoutConfig.compatSmallestScreenWidthDp;
    476         }
    477         inoutConfig.densityDpi = displayDensity;
    478         if (isScalingRequired()) {
    479             float invertedRatio = applicationInvertedScale;
    480             inoutConfig.densityDpi = (int)((inoutConfig.densityDpi * invertedRatio) + .5f);
    481         }
    482     }
    483 
    484     /**
    485      * Compute the frame Rect for applications runs under compatibility mode.
    486      *
    487      * @param dm the display metrics used to compute the frame size.
    488      * @param outDm If non-null the width and height will be set to their scaled values.
    489      * @return Returns the scaling factor for the window.
    490      */
    491     public static float computeCompatibleScaling(DisplayMetrics dm, DisplayMetrics outDm) {
    492         final int width = dm.noncompatWidthPixels;
    493         final int height = dm.noncompatHeightPixels;
    494         int shortSize, longSize;
    495         if (width < height) {
    496             shortSize = width;
    497             longSize = height;
    498         } else {
    499             shortSize = height;
    500             longSize = width;
    501         }
    502         int newShortSize = (int)(DEFAULT_NORMAL_SHORT_DIMENSION * dm.density + 0.5f);
    503         float aspect = ((float)longSize) / shortSize;
    504         if (aspect > MAXIMUM_ASPECT_RATIO) {
    505             aspect = MAXIMUM_ASPECT_RATIO;
    506         }
    507         int newLongSize = (int)(newShortSize * aspect + 0.5f);
    508         int newWidth, newHeight;
    509         if (width < height) {
    510             newWidth = newShortSize;
    511             newHeight = newLongSize;
    512         } else {
    513             newWidth = newLongSize;
    514             newHeight = newShortSize;
    515         }
    516 
    517         float sw = width/(float)newWidth;
    518         float sh = height/(float)newHeight;
    519         float scale = sw < sh ? sw : sh;
    520         if (scale < 1) {
    521             scale = 1;
    522         }
    523 
    524         if (outDm != null) {
    525             outDm.widthPixels = newWidth;
    526             outDm.heightPixels = newHeight;
    527         }
    528 
    529         return scale;
    530     }
    531 
    532     @Override
    533     public boolean equals(Object o) {
    534         if (this == o) {
    535             return true;
    536         }
    537         try {
    538             CompatibilityInfo oc = (CompatibilityInfo)o;
    539             if (mCompatibilityFlags != oc.mCompatibilityFlags) return false;
    540             if (applicationDensity != oc.applicationDensity) return false;
    541             if (applicationScale != oc.applicationScale) return false;
    542             if (applicationInvertedScale != oc.applicationInvertedScale) return false;
    543             return true;
    544         } catch (ClassCastException e) {
    545             return false;
    546         }
    547     }
    548 
    549     @Override
    550     public String toString() {
    551         StringBuilder sb = new StringBuilder(128);
    552         sb.append("{");
    553         sb.append(applicationDensity);
    554         sb.append("dpi");
    555         if (isScalingRequired()) {
    556             sb.append(" ");
    557             sb.append(applicationScale);
    558             sb.append("x");
    559         }
    560         if (!supportsScreen()) {
    561             sb.append(" resizing");
    562         }
    563         if (neverSupportsScreen()) {
    564             sb.append(" never-compat");
    565         }
    566         if (alwaysSupportsScreen()) {
    567             sb.append(" always-compat");
    568         }
    569         sb.append("}");
    570         return sb.toString();
    571     }
    572 
    573     @Override
    574     public int hashCode() {
    575         int result = 17;
    576         result = 31 * result + mCompatibilityFlags;
    577         result = 31 * result + applicationDensity;
    578         result = 31 * result + Float.floatToIntBits(applicationScale);
    579         result = 31 * result + Float.floatToIntBits(applicationInvertedScale);
    580         return result;
    581     }
    582 
    583     @Override
    584     public int describeContents() {
    585         return 0;
    586     }
    587 
    588     @Override
    589     public void writeToParcel(Parcel dest, int flags) {
    590         dest.writeInt(mCompatibilityFlags);
    591         dest.writeInt(applicationDensity);
    592         dest.writeFloat(applicationScale);
    593         dest.writeFloat(applicationInvertedScale);
    594     }
    595 
    596     public static final Parcelable.Creator<CompatibilityInfo> CREATOR
    597             = new Parcelable.Creator<CompatibilityInfo>() {
    598         @Override
    599         public CompatibilityInfo createFromParcel(Parcel source) {
    600             return new CompatibilityInfo(source);
    601         }
    602 
    603         @Override
    604         public CompatibilityInfo[] newArray(int size) {
    605             return new CompatibilityInfo[size];
    606         }
    607     };
    608 
    609     private CompatibilityInfo(Parcel source) {
    610         mCompatibilityFlags = source.readInt();
    611         applicationDensity = source.readInt();
    612         applicationScale = source.readFloat();
    613         applicationInvertedScale = source.readFloat();
    614     }
    615 }
    616