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 
CompatibilityInfo(ApplicationInfo appInfo, int screenLayout, int sw, boolean forceCompat)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 
CompatibilityInfo(int compFlags, int dens, float scale, float invertedScale)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 
CompatibilityInfo()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      */
isScalingRequired()261     public boolean isScalingRequired() {
262         return (mCompatibilityFlags&SCALING_REQUIRED) != 0;
263     }
264 
supportsScreen()265     public boolean supportsScreen() {
266         return (mCompatibilityFlags&NEEDS_SCREEN_COMPAT) == 0;
267     }
268 
neverSupportsScreen()269     public boolean neverSupportsScreen() {
270         return (mCompatibilityFlags&ALWAYS_NEEDS_COMPAT) != 0;
271     }
272 
alwaysSupportsScreen()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      */
getTranslator()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 
Translator(float applicationScale, float applicationInvertedScale)297         Translator(float applicationScale, float applicationInvertedScale) {
298             this.applicationScale = applicationScale;
299             this.applicationInvertedScale = applicationInvertedScale;
300         }
301 
Translator()302         Translator() {
303             this(CompatibilityInfo.this.applicationScale,
304                     CompatibilityInfo.this.applicationInvertedScale);
305         }
306 
307         /**
308          * Translate the screen rect to the application frame.
309          */
translateRectInScreenToAppWinFrame(Rect rect)310         public void translateRectInScreenToAppWinFrame(Rect rect) {
311             rect.scale(applicationInvertedScale);
312         }
313 
314         /**
315          * Translate the region in window to screen.
316          */
translateRegionInWindowToScreen(Region transparentRegion)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          */
translateCanvas(Canvas canvas)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          */
translateEventInScreenToAppWindow(MotionEvent event)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          */
translateWindowLayout(WindowManager.LayoutParams params)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          */
translateRectInAppWindowToScreen(Rect rect)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          */
translateRectInScreenToAppWindow(Rect rect)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          */
translatePointInScreenToAppWindow(PointF point)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          */
translateLayoutParamsInAppWindowToScreen(LayoutParams params)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          */
getTranslatedContentInsets(Rect contentInsets)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          */
getTranslatedVisibleInsets(Rect visibleInsets)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          */
getTranslatedTouchableArea(Region touchableArea)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 
applyToDisplayMetrics(DisplayMetrics inoutDm)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 
applyToConfiguration(int displayDensity, Configuration inoutConfig)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      */
computeCompatibleScaling(DisplayMetrics dm, DisplayMetrics outDm)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