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.Rect;
     22 import android.graphics.Region;
     23 import android.util.DisplayMetrics;
     24 import android.util.Log;
     25 import android.view.Gravity;
     26 import android.view.MotionEvent;
     27 import android.view.WindowManager;
     28 import 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  */
     36 public 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         @Override
     43         public void setExpandable(boolean expandable) {
     44             throw new UnsupportedOperationException("trying to change default compatibility info");
     45         }
     46     };
     47 
     48     /**
     49      * The default width of the screen in portrait mode.
     50      */
     51     public static final int DEFAULT_PORTRAIT_WIDTH = 320;
     52 
     53     /**
     54      * The default height of the screen in portrait mode.
     55      */
     56     public static final int DEFAULT_PORTRAIT_HEIGHT = 480;
     57 
     58     /**
     59      *  A compatibility flags
     60      */
     61     private int mCompatibilityFlags;
     62 
     63     /**
     64      * A flag mask to tell if the application needs scaling (when mApplicationScale != 1.0f)
     65      * {@see compatibilityFlag}
     66      */
     67     private static final int SCALING_REQUIRED = 1;
     68 
     69     /**
     70      * A flag mask to indicates that the application can expand over the original size.
     71      * The flag is set to true if
     72      * 1) Application declares its expandable in manifest file using <supports-screens> or
     73      * 2) Configuration.SCREENLAYOUT_COMPAT_NEEDED is not set
     74      * {@see compatibilityFlag}
     75      */
     76     private static final int EXPANDABLE = 2;
     77 
     78     /**
     79      * A flag mask to tell if the application is configured to be expandable. This differs
     80      * from EXPANDABLE in that the application that is not expandable will be
     81      * marked as expandable if Configuration.SCREENLAYOUT_COMPAT_NEEDED is not set.
     82      */
     83     private static final int CONFIGURED_EXPANDABLE = 4;
     84 
     85     /**
     86      * A flag mask to indicates that the application supports large screens.
     87      * The flag is set to true if
     88      * 1) Application declares it supports large screens in manifest file using <supports-screens> or
     89      * 2) The screen size is not large
     90      * {@see compatibilityFlag}
     91      */
     92     private static final int LARGE_SCREENS = 8;
     93 
     94     /**
     95      * A flag mask to tell if the application supports large screens. This differs
     96      * from LARGE_SCREENS in that the application that does not support large
     97      * screens will be marked as supporting them if the current screen is not
     98      * large.
     99      */
    100     private static final int CONFIGURED_LARGE_SCREENS = 16;
    101 
    102     private static final int SCALING_EXPANDABLE_MASK = SCALING_REQUIRED | EXPANDABLE | LARGE_SCREENS;
    103 
    104     /**
    105      * The effective screen density we have selected for this application.
    106      */
    107     public final int applicationDensity;
    108 
    109     /**
    110      * Application's scale.
    111      */
    112     public final float applicationScale;
    113 
    114     /**
    115      * Application's inverted scale.
    116      */
    117     public final float applicationInvertedScale;
    118 
    119     /**
    120      * The flags from ApplicationInfo.
    121      */
    122     public final int appFlags;
    123 
    124     public CompatibilityInfo(ApplicationInfo appInfo) {
    125         appFlags = appInfo.flags;
    126 
    127         if ((appInfo.flags & ApplicationInfo.FLAG_SUPPORTS_LARGE_SCREENS) != 0) {
    128             mCompatibilityFlags |= LARGE_SCREENS | CONFIGURED_LARGE_SCREENS;
    129         }
    130         if ((appInfo.flags & ApplicationInfo.FLAG_RESIZEABLE_FOR_SCREENS) != 0) {
    131             mCompatibilityFlags |= EXPANDABLE | CONFIGURED_EXPANDABLE;
    132         }
    133 
    134         if ((appInfo.flags & ApplicationInfo.FLAG_SUPPORTS_SCREEN_DENSITIES) != 0) {
    135             applicationDensity = DisplayMetrics.DENSITY_DEVICE;
    136             applicationScale = 1.0f;
    137             applicationInvertedScale = 1.0f;
    138         } else {
    139             applicationDensity = DisplayMetrics.DENSITY_DEFAULT;
    140             applicationScale = DisplayMetrics.DENSITY_DEVICE
    141                     / (float) DisplayMetrics.DENSITY_DEFAULT;
    142             applicationInvertedScale = 1.0f / applicationScale;
    143             mCompatibilityFlags |= SCALING_REQUIRED;
    144         }
    145     }
    146 
    147     private CompatibilityInfo(int appFlags, int compFlags,
    148             int dens, float scale, float invertedScale) {
    149         this.appFlags = appFlags;
    150         mCompatibilityFlags = compFlags;
    151         applicationDensity = dens;
    152         applicationScale = scale;
    153         applicationInvertedScale = invertedScale;
    154     }
    155 
    156     private CompatibilityInfo() {
    157         this(ApplicationInfo.FLAG_SUPPORTS_SMALL_SCREENS
    158                 | ApplicationInfo.FLAG_SUPPORTS_NORMAL_SCREENS
    159                 | ApplicationInfo.FLAG_SUPPORTS_LARGE_SCREENS
    160                 | ApplicationInfo.FLAG_RESIZEABLE_FOR_SCREENS,
    161                 EXPANDABLE | CONFIGURED_EXPANDABLE,
    162                 DisplayMetrics.DENSITY_DEVICE,
    163                 1.0f,
    164                 1.0f);
    165     }
    166 
    167     /**
    168      * Returns the copy of this instance.
    169      */
    170     public CompatibilityInfo copy() {
    171         CompatibilityInfo info = new CompatibilityInfo(appFlags, mCompatibilityFlags,
    172                 applicationDensity, applicationScale, applicationInvertedScale);
    173         return info;
    174     }
    175 
    176     /**
    177      * Sets expandable bit in the compatibility flag.
    178      */
    179     public void setExpandable(boolean expandable) {
    180         if (expandable) {
    181             mCompatibilityFlags |= CompatibilityInfo.EXPANDABLE;
    182         } else {
    183             mCompatibilityFlags &= ~CompatibilityInfo.EXPANDABLE;
    184         }
    185     }
    186 
    187     /**
    188      * Sets large screen bit in the compatibility flag.
    189      */
    190     public void setLargeScreens(boolean expandable) {
    191         if (expandable) {
    192             mCompatibilityFlags |= CompatibilityInfo.LARGE_SCREENS;
    193         } else {
    194             mCompatibilityFlags &= ~CompatibilityInfo.LARGE_SCREENS;
    195         }
    196     }
    197 
    198     /**
    199      * @return true if the application is configured to be expandable.
    200      */
    201     public boolean isConfiguredExpandable() {
    202         return (mCompatibilityFlags & CompatibilityInfo.CONFIGURED_EXPANDABLE) != 0;
    203     }
    204 
    205     /**
    206      * @return true if the application is configured to be expandable.
    207      */
    208     public boolean isConfiguredLargeScreens() {
    209         return (mCompatibilityFlags & CompatibilityInfo.CONFIGURED_LARGE_SCREENS) != 0;
    210     }
    211 
    212     /**
    213      * @return true if the scaling is required
    214      */
    215     public boolean isScalingRequired() {
    216         return (mCompatibilityFlags & SCALING_REQUIRED) != 0;
    217     }
    218 
    219     public boolean supportsScreen() {
    220         return (mCompatibilityFlags & (EXPANDABLE|LARGE_SCREENS))
    221                 == (EXPANDABLE|LARGE_SCREENS);
    222     }
    223 
    224     @Override
    225     public String toString() {
    226         return "CompatibilityInfo{scale=" + applicationScale +
    227                 ", supports screen=" + supportsScreen() + "}";
    228     }
    229 
    230     /**
    231      * Returns the translator which translates the coordinates in compatibility mode.
    232      * @param params the window's parameter
    233      */
    234     public Translator getTranslator() {
    235         return isScalingRequired() ? new Translator() : null;
    236     }
    237 
    238     /**
    239      * A helper object to translate the screen and window coordinates back and forth.
    240      * @hide
    241      */
    242     public class Translator {
    243         final public float applicationScale;
    244         final public float applicationInvertedScale;
    245 
    246         private Rect mContentInsetsBuffer = null;
    247         private Rect mVisibleInsetsBuffer = null;
    248 
    249         Translator(float applicationScale, float applicationInvertedScale) {
    250             this.applicationScale = applicationScale;
    251             this.applicationInvertedScale = applicationInvertedScale;
    252         }
    253 
    254         Translator() {
    255             this(CompatibilityInfo.this.applicationScale,
    256                     CompatibilityInfo.this.applicationInvertedScale);
    257         }
    258 
    259         /**
    260          * Translate the screen rect to the application frame.
    261          */
    262         public void translateRectInScreenToAppWinFrame(Rect rect) {
    263             rect.scale(applicationInvertedScale);
    264         }
    265 
    266         /**
    267          * Translate the region in window to screen.
    268          */
    269         public void translateRegionInWindowToScreen(Region transparentRegion) {
    270             transparentRegion.scale(applicationScale);
    271         }
    272 
    273         /**
    274          * Apply translation to the canvas that is necessary to draw the content.
    275          */
    276         public void translateCanvas(Canvas canvas) {
    277             if (applicationScale == 1.5f) {
    278                 /*  When we scale for compatibility, we can put our stretched
    279                     bitmaps and ninepatches on exacty 1/2 pixel boundaries,
    280                     which can give us inconsistent drawing due to imperfect
    281                     float precision in the graphics engine's inverse matrix.
    282 
    283                     As a work-around, we translate by a tiny amount to avoid
    284                     landing on exact pixel centers and boundaries, giving us
    285                     the slop we need to draw consistently.
    286 
    287                     This constant is meant to resolve to 1/255 after it is
    288                     scaled by 1.5 (applicationScale). Note, this is just a guess
    289                     as to what is small enough not to create its own artifacts,
    290                     and big enough to avoid the precision problems. Feel free
    291                     to experiment with smaller values as you choose.
    292                  */
    293                 final float tinyOffset = 2.0f / (3 * 255);
    294                 canvas.translate(tinyOffset, tinyOffset);
    295             }
    296             canvas.scale(applicationScale, applicationScale);
    297         }
    298 
    299         /**
    300          * Translate the motion event captured on screen to the application's window.
    301          */
    302         public void translateEventInScreenToAppWindow(MotionEvent event) {
    303             event.scale(applicationInvertedScale);
    304         }
    305 
    306         /**
    307          * Translate the window's layout parameter, from application's view to
    308          * Screen's view.
    309          */
    310         public void translateWindowLayout(WindowManager.LayoutParams params) {
    311             params.scale(applicationScale);
    312         }
    313 
    314         /**
    315          * Translate a Rect in application's window to screen.
    316          */
    317         public void translateRectInAppWindowToScreen(Rect rect) {
    318             rect.scale(applicationScale);
    319         }
    320 
    321         /**
    322          * Translate a Rect in screen coordinates into the app window's coordinates.
    323          */
    324         public void translateRectInScreenToAppWindow(Rect rect) {
    325             rect.scale(applicationInvertedScale);
    326         }
    327 
    328         /**
    329          * Translate the location of the sub window.
    330          * @param params
    331          */
    332         public void translateLayoutParamsInAppWindowToScreen(LayoutParams params) {
    333             params.scale(applicationScale);
    334         }
    335 
    336         /**
    337          * Translate the content insets in application window to Screen. This uses
    338          * the internal buffer for content insets to avoid extra object allocation.
    339          */
    340         public Rect getTranslatedContentInsets(Rect contentInsets) {
    341             if (mContentInsetsBuffer == null) mContentInsetsBuffer = new Rect();
    342             mContentInsetsBuffer.set(contentInsets);
    343             translateRectInAppWindowToScreen(mContentInsetsBuffer);
    344             return mContentInsetsBuffer;
    345         }
    346 
    347         /**
    348          * Translate the visible insets in application window to Screen. This uses
    349          * the internal buffer for content insets to avoid extra object allocation.
    350          */
    351         public Rect getTranslatedVisbileInsets(Rect visibleInsets) {
    352             if (mVisibleInsetsBuffer == null) mVisibleInsetsBuffer = new Rect();
    353             mVisibleInsetsBuffer.set(visibleInsets);
    354             translateRectInAppWindowToScreen(mVisibleInsetsBuffer);
    355             return mVisibleInsetsBuffer;
    356         }
    357     }
    358 
    359     /**
    360      * Returns the frame Rect for applications runs under compatibility mode.
    361      *
    362      * @param dm the display metrics used to compute the frame size.
    363      * @param orientation the orientation of the screen.
    364      * @param outRect the output parameter which will contain the result.
    365      */
    366     public static void updateCompatibleScreenFrame(DisplayMetrics dm, int orientation,
    367             Rect outRect) {
    368         int width = dm.widthPixels;
    369         int portraitHeight = (int) (DEFAULT_PORTRAIT_HEIGHT * dm.density + 0.5f);
    370         int portraitWidth = (int) (DEFAULT_PORTRAIT_WIDTH * dm.density + 0.5f);
    371         if (orientation == Configuration.ORIENTATION_LANDSCAPE) {
    372             int xOffset = (width - portraitHeight) / 2 ;
    373             outRect.set(xOffset, 0, xOffset + portraitHeight, portraitWidth);
    374         } else {
    375             int xOffset = (width - portraitWidth) / 2 ;
    376             outRect.set(xOffset, 0, xOffset + portraitWidth, portraitHeight);
    377         }
    378     }
    379 }
    380