• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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 
CompatibilityInfo(ApplicationInfo appInfo, int screenLayout, int sw, boolean forceCompat)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 
CompatibilityInfo(int compFlags, int dens, float scale, float invertedScale)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 
CompatibilityInfo()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      */
isScalingRequired()271     public boolean isScalingRequired() {
272         return (mCompatibilityFlags&SCALING_REQUIRED) != 0;
273     }
274 
supportsScreen()275     public boolean supportsScreen() {
276         return (mCompatibilityFlags&NEEDS_SCREEN_COMPAT) == 0;
277     }
278 
neverSupportsScreen()279     public boolean neverSupportsScreen() {
280         return (mCompatibilityFlags&ALWAYS_NEEDS_COMPAT) != 0;
281     }
282 
alwaysSupportsScreen()283     public boolean alwaysSupportsScreen() {
284         return (mCompatibilityFlags&NEVER_NEEDS_COMPAT) != 0;
285     }
286 
needsCompatResources()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      */
getTranslator()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 
Translator(float applicationScale, float applicationInvertedScale)311         Translator(float applicationScale, float applicationInvertedScale) {
312             this.applicationScale = applicationScale;
313             this.applicationInvertedScale = applicationInvertedScale;
314         }
315 
Translator()316         Translator() {
317             this(CompatibilityInfo.this.applicationScale,
318                     CompatibilityInfo.this.applicationInvertedScale);
319         }
320 
321         /**
322          * Translate the screen rect to the application frame.
323          */
translateRectInScreenToAppWinFrame(Rect rect)324         public void translateRectInScreenToAppWinFrame(Rect rect) {
325             rect.scale(applicationInvertedScale);
326         }
327 
328         /**
329          * Translate the region in window to screen.
330          */
translateRegionInWindowToScreen(Region transparentRegion)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          */
translateCanvas(Canvas canvas)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          */
translateEventInScreenToAppWindow(MotionEvent event)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          */
translateWindowLayout(WindowManager.LayoutParams params)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          */
translateRectInAppWindowToScreen(Rect rect)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          */
translateRectInScreenToAppWindow(Rect rect)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          */
translatePointInScreenToAppWindow(PointF point)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          */
translateLayoutParamsInAppWindowToScreen(LayoutParams params)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          */
getTranslatedContentInsets(Rect contentInsets)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          */
getTranslatedVisibleInsets(Rect visibleInsets)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          */
getTranslatedTouchableArea(Region touchableArea)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 
applyToDisplayMetrics(DisplayMetrics inoutDm)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 
applyToConfiguration(int displayDensity, Configuration inoutConfig)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      */
computeCompatibleScaling(DisplayMetrics dm, DisplayMetrics outDm)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