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.annotation.Nullable;
20 import android.compat.annotation.UnsupportedAppUsage;
21 import android.content.pm.ApplicationInfo;
22 import android.graphics.Canvas;
23 import android.graphics.Insets;
24 import android.graphics.PointF;
25 import android.graphics.Rect;
26 import android.graphics.Region;
27 import android.os.Build;
28 import android.os.Build.VERSION_CODES;
29 import android.os.Parcel;
30 import android.os.Parcelable;
31 import android.util.DisplayMetrics;
32 import android.util.MergedConfiguration;
33 import android.view.InsetsSourceControl;
34 import android.view.InsetsState;
35 import android.view.MotionEvent;
36 import android.view.WindowManager;
37 import android.view.WindowManager.LayoutParams;
38 
39 /**
40  * CompatibilityInfo class keeps the information about the screen compatibility mode that the
41  * application is running under.
42  *
43  *  {@hide}
44  */
45 public class CompatibilityInfo implements Parcelable {
46     /** default compatibility info object for compatible applications */
47     @UnsupportedAppUsage
48     public static final CompatibilityInfo DEFAULT_COMPATIBILITY_INFO = new CompatibilityInfo() {
49     };
50 
51     /**
52      * This is the number of pixels we would like to have along the
53      * short axis of an app that needs to run on a normal size screen.
54      */
55     public static final int DEFAULT_NORMAL_SHORT_DIMENSION = 320;
56 
57     /**
58      * This is the maximum aspect ratio we will allow while keeping
59      * applications in a compatible screen size.
60      */
61     public static final float MAXIMUM_ASPECT_RATIO = (854f/480f);
62 
63     /**
64      *  A compatibility flags
65      */
66     private final int mCompatibilityFlags;
67 
68     /**
69      * A flag mask to tell if the application needs scaling (when mApplicationScale != 1.0f)
70      * {@see compatibilityFlag}
71      */
72     private static final int SCALING_REQUIRED = 1;
73 
74     /**
75      * Application must always run in compatibility mode?
76      */
77     private static final int ALWAYS_NEEDS_COMPAT = 2;
78 
79     /**
80      * Application never should run in compatibility mode?
81      */
82     private static final int NEVER_NEEDS_COMPAT = 4;
83 
84     /**
85      * Set if the application needs to run in screen size compatibility mode.
86      */
87     private static final int NEEDS_SCREEN_COMPAT = 8;
88 
89     /**
90      * Set if the application needs to run in with compat resources.
91      */
92     private static final int NEEDS_COMPAT_RES = 16;
93 
94     /**
95      * Set if the application needs to be forcibly downscaled
96      */
97     private static final int HAS_OVERRIDE_SCALING = 32;
98 
99     /**
100      * The effective screen density we have selected for this application.
101      */
102     public final int applicationDensity;
103 
104     /**
105      * Application's scale.
106      */
107     @UnsupportedAppUsage
108     public final float applicationScale;
109 
110     /**
111      * Application's inverted scale.
112      */
113     public final float applicationInvertedScale;
114 
115     /** The process level override inverted scale. See {@link #HAS_OVERRIDE_SCALING}. */
116     private static float sOverrideInvertedScale = 1f;
117 
118     @UnsupportedAppUsage
119     @Deprecated
CompatibilityInfo(ApplicationInfo appInfo, int screenLayout, int sw, boolean forceCompat)120     public CompatibilityInfo(ApplicationInfo appInfo, int screenLayout, int sw,
121             boolean forceCompat) {
122         this(appInfo, screenLayout, sw, forceCompat, 1f);
123     }
124 
CompatibilityInfo(ApplicationInfo appInfo, int screenLayout, int sw, boolean forceCompat, float overrideScale)125     public CompatibilityInfo(ApplicationInfo appInfo, int screenLayout, int sw,
126             boolean forceCompat, float overrideScale) {
127         int compatFlags = 0;
128 
129         if (appInfo.targetSdkVersion < VERSION_CODES.O) {
130             compatFlags |= NEEDS_COMPAT_RES;
131         }
132         if (overrideScale != 1.0f) {
133             applicationScale = overrideScale;
134             applicationInvertedScale = 1.0f / overrideScale;
135             applicationDensity = (int) ((DisplayMetrics.DENSITY_DEVICE_STABLE
136                     * applicationInvertedScale) + .5f);
137             mCompatibilityFlags = NEVER_NEEDS_COMPAT | HAS_OVERRIDE_SCALING;
138             // Override scale has the highest priority. So ignore other compatibility attributes.
139             return;
140         }
141         if (appInfo.requiresSmallestWidthDp != 0 || appInfo.compatibleWidthLimitDp != 0
142                 || appInfo.largestWidthLimitDp != 0) {
143             // New style screen requirements spec.
144             int required = appInfo.requiresSmallestWidthDp != 0
145                     ? appInfo.requiresSmallestWidthDp
146                     : appInfo.compatibleWidthLimitDp;
147             if (required == 0) {
148                 required = appInfo.largestWidthLimitDp;
149             }
150             int compat = appInfo.compatibleWidthLimitDp != 0
151                     ? appInfo.compatibleWidthLimitDp : required;
152             if (compat < required)  {
153                 compat = required;
154             }
155             int largest = appInfo.largestWidthLimitDp;
156 
157             if (required > DEFAULT_NORMAL_SHORT_DIMENSION) {
158                 // For now -- if they require a size larger than the only
159                 // size we can do in compatibility mode, then don't ever
160                 // allow the app to go in to compat mode.  Trying to run
161                 // it at a smaller size it can handle will make it far more
162                 // broken than running at a larger size than it wants or
163                 // thinks it can handle.
164                 compatFlags |= NEVER_NEEDS_COMPAT;
165             } else if (largest != 0 && sw > largest) {
166                 // If the screen size is larger than the largest size the
167                 // app thinks it can work with, then always force it in to
168                 // compatibility mode.
169                 compatFlags |= NEEDS_SCREEN_COMPAT | ALWAYS_NEEDS_COMPAT;
170             } else if (compat >= sw) {
171                 // The screen size is something the app says it was designed
172                 // for, so never do compatibility mode.
173                 compatFlags |= NEVER_NEEDS_COMPAT;
174             } else if (forceCompat) {
175                 // The app may work better with or without compatibility mode.
176                 // Let the user decide.
177                 compatFlags |= NEEDS_SCREEN_COMPAT;
178             }
179 
180             // Modern apps always support densities.
181             applicationDensity = DisplayMetrics.DENSITY_DEVICE;
182             applicationScale = 1.0f;
183             applicationInvertedScale = 1.0f;
184 
185         } else {
186             /**
187              * Has the application said that its UI is expandable?  Based on the
188              * <supports-screen> android:expandible in the manifest.
189              */
190             final int EXPANDABLE = 2;
191 
192             /**
193              * Has the application said that its UI supports large screens?  Based on the
194              * <supports-screen> android:largeScreens in the manifest.
195              */
196             final int LARGE_SCREENS = 8;
197 
198             /**
199              * Has the application said that its UI supports xlarge screens?  Based on the
200              * <supports-screen> android:xlargeScreens in the manifest.
201              */
202             final int XLARGE_SCREENS = 32;
203 
204             int sizeInfo = 0;
205 
206             // We can't rely on the application always setting
207             // FLAG_RESIZEABLE_FOR_SCREENS so will compute it based on various input.
208             boolean anyResizeable = false;
209 
210             if ((appInfo.flags & ApplicationInfo.FLAG_SUPPORTS_LARGE_SCREENS) != 0) {
211                 sizeInfo |= LARGE_SCREENS;
212                 anyResizeable = true;
213                 if (!forceCompat) {
214                     // If we aren't forcing the app into compatibility mode, then
215                     // assume if it supports large screens that we should allow it
216                     // to use the full space of an xlarge screen as well.
217                     sizeInfo |= XLARGE_SCREENS | EXPANDABLE;
218                 }
219             }
220             if ((appInfo.flags & ApplicationInfo.FLAG_SUPPORTS_XLARGE_SCREENS) != 0) {
221                 anyResizeable = true;
222                 if (!forceCompat) {
223                     sizeInfo |= XLARGE_SCREENS | EXPANDABLE;
224                 }
225             }
226             if ((appInfo.flags & ApplicationInfo.FLAG_RESIZEABLE_FOR_SCREENS) != 0) {
227                 anyResizeable = true;
228                 sizeInfo |= EXPANDABLE;
229             }
230 
231             if (forceCompat) {
232                 // If we are forcing compatibility mode, then ignore an app that
233                 // just says it is resizable for screens.  We'll only have it fill
234                 // the screen if it explicitly says it supports the screen size we
235                 // are running in.
236                 sizeInfo &= ~EXPANDABLE;
237             }
238 
239             compatFlags |= NEEDS_SCREEN_COMPAT;
240             switch (screenLayout&Configuration.SCREENLAYOUT_SIZE_MASK) {
241                 case Configuration.SCREENLAYOUT_SIZE_XLARGE:
242                     if ((sizeInfo&XLARGE_SCREENS) != 0) {
243                         compatFlags &= ~NEEDS_SCREEN_COMPAT;
244                     }
245                     if ((appInfo.flags & ApplicationInfo.FLAG_SUPPORTS_XLARGE_SCREENS) != 0) {
246                         compatFlags |= NEVER_NEEDS_COMPAT;
247                     }
248                     break;
249                 case Configuration.SCREENLAYOUT_SIZE_LARGE:
250                     if ((sizeInfo&LARGE_SCREENS) != 0) {
251                         compatFlags &= ~NEEDS_SCREEN_COMPAT;
252                     }
253                     if ((appInfo.flags & ApplicationInfo.FLAG_SUPPORTS_LARGE_SCREENS) != 0) {
254                         compatFlags |= NEVER_NEEDS_COMPAT;
255                     }
256                     break;
257             }
258 
259             if ((screenLayout&Configuration.SCREENLAYOUT_COMPAT_NEEDED) != 0) {
260                 if ((sizeInfo&EXPANDABLE) != 0) {
261                     compatFlags &= ~NEEDS_SCREEN_COMPAT;
262                 } else if (!anyResizeable) {
263                     compatFlags |= ALWAYS_NEEDS_COMPAT;
264                 }
265             } else {
266                 compatFlags &= ~NEEDS_SCREEN_COMPAT;
267                 compatFlags |= NEVER_NEEDS_COMPAT;
268             }
269 
270             if ((appInfo.flags & ApplicationInfo.FLAG_SUPPORTS_SCREEN_DENSITIES) != 0) {
271                 applicationDensity = DisplayMetrics.DENSITY_DEVICE;
272                 applicationScale = 1.0f;
273                 applicationInvertedScale = 1.0f;
274             } else {
275                 applicationDensity = DisplayMetrics.DENSITY_DEFAULT;
276                 applicationScale = DisplayMetrics.DENSITY_DEVICE
277                         / (float) DisplayMetrics.DENSITY_DEFAULT;
278                 applicationInvertedScale = 1.0f / applicationScale;
279                 compatFlags |= SCALING_REQUIRED;
280             }
281         }
282 
283         mCompatibilityFlags = compatFlags;
284     }
285 
CompatibilityInfo(int compFlags, int dens, float scale, float invertedScale)286     private CompatibilityInfo(int compFlags,
287             int dens, float scale, float invertedScale) {
288         mCompatibilityFlags = compFlags;
289         applicationDensity = dens;
290         applicationScale = scale;
291         applicationInvertedScale = invertedScale;
292     }
293 
294     @UnsupportedAppUsage
CompatibilityInfo()295     private CompatibilityInfo() {
296         this(NEVER_NEEDS_COMPAT, DisplayMetrics.DENSITY_DEVICE,
297                 1.0f,
298                 1.0f);
299     }
300 
301     /**
302      * @return true if the scaling is required
303      */
304     @UnsupportedAppUsage
isScalingRequired()305     public boolean isScalingRequired() {
306         return (mCompatibilityFlags & SCALING_REQUIRED) != 0;
307     }
308 
309     /** Returns {@code true} if {@link #sOverrideInvertedScale} should be set. */
hasOverrideScaling()310     public boolean hasOverrideScaling() {
311         return (mCompatibilityFlags & HAS_OVERRIDE_SCALING) != 0;
312     }
313 
314     @UnsupportedAppUsage
supportsScreen()315     public boolean supportsScreen() {
316         return (mCompatibilityFlags&NEEDS_SCREEN_COMPAT) == 0;
317     }
318 
neverSupportsScreen()319     public boolean neverSupportsScreen() {
320         return (mCompatibilityFlags&ALWAYS_NEEDS_COMPAT) != 0;
321     }
322 
alwaysSupportsScreen()323     public boolean alwaysSupportsScreen() {
324         return (mCompatibilityFlags&NEVER_NEEDS_COMPAT) != 0;
325     }
326 
needsCompatResources()327     public boolean needsCompatResources() {
328         return (mCompatibilityFlags&NEEDS_COMPAT_RES) != 0;
329     }
330 
331     /**
332      * Returns the translator which translates the coordinates in compatibility mode.
333      * @param params the window's parameter
334      */
335     @UnsupportedAppUsage
getTranslator()336     public Translator getTranslator() {
337         return (mCompatibilityFlags & SCALING_REQUIRED) != 0 ? new Translator() : null;
338     }
339 
340     /**
341      * A helper object to translate the screen and window coordinates back and forth.
342      * @hide
343      */
344     public class Translator {
345         @UnsupportedAppUsage
346         final public float applicationScale;
347         @UnsupportedAppUsage
348         final public float applicationInvertedScale;
349 
350         private Rect mContentInsetsBuffer = null;
351         private Rect mVisibleInsetsBuffer = null;
352         private Region mTouchableAreaBuffer = null;
353 
Translator(float applicationScale, float applicationInvertedScale)354         Translator(float applicationScale, float applicationInvertedScale) {
355             this.applicationScale = applicationScale;
356             this.applicationInvertedScale = applicationInvertedScale;
357         }
358 
Translator()359         Translator() {
360             this(CompatibilityInfo.this.applicationScale,
361                     CompatibilityInfo.this.applicationInvertedScale);
362         }
363 
364         /**
365          * Translate the region in window to screen.
366          */
367         @UnsupportedAppUsage
translateRegionInWindowToScreen(Region transparentRegion)368         public void translateRegionInWindowToScreen(Region transparentRegion) {
369             transparentRegion.scale(applicationScale);
370         }
371 
372         /**
373          * Apply translation to the canvas that is necessary to draw the content.
374          */
375         @UnsupportedAppUsage
translateCanvas(Canvas canvas)376         public void translateCanvas(Canvas canvas) {
377             if (applicationScale == 1.5f) {
378                 /*  When we scale for compatibility, we can put our stretched
379                     bitmaps and ninepatches on exacty 1/2 pixel boundaries,
380                     which can give us inconsistent drawing due to imperfect
381                     float precision in the graphics engine's inverse matrix.
382 
383                     As a work-around, we translate by a tiny amount to avoid
384                     landing on exact pixel centers and boundaries, giving us
385                     the slop we need to draw consistently.
386 
387                     This constant is meant to resolve to 1/255 after it is
388                     scaled by 1.5 (applicationScale). Note, this is just a guess
389                     as to what is small enough not to create its own artifacts,
390                     and big enough to avoid the precision problems. Feel free
391                     to experiment with smaller values as you choose.
392                  */
393                 final float tinyOffset = 2.0f / (3 * 255);
394                 canvas.translate(tinyOffset, tinyOffset);
395             }
396             canvas.scale(applicationScale, applicationScale);
397         }
398 
399         /**
400          * Translate the motion event captured on screen to the application's window.
401          */
402         @UnsupportedAppUsage
translateEventInScreenToAppWindow(MotionEvent event)403         public void translateEventInScreenToAppWindow(MotionEvent event) {
404             event.scale(applicationInvertedScale);
405         }
406 
407         /**
408          * Translate the window's layout parameter, from application's view to
409          * Screen's view.
410          */
411         @UnsupportedAppUsage
translateWindowLayout(WindowManager.LayoutParams params)412         public void translateWindowLayout(WindowManager.LayoutParams params) {
413             params.scale(applicationScale);
414         }
415 
416         /**
417          * Translate a length in application's window to screen.
418          */
translateLengthInAppWindowToScreen(float length)419         public float translateLengthInAppWindowToScreen(float length) {
420             return length * applicationScale;
421         }
422 
423         /**
424          * Translate a Rect in application's window to screen.
425          */
426         @UnsupportedAppUsage
translateRectInAppWindowToScreen(Rect rect)427         public void translateRectInAppWindowToScreen(Rect rect) {
428             rect.scale(applicationScale);
429         }
430 
431         /**
432          * Translate a Rect in screen coordinates into the app window's coordinates.
433          */
434         @UnsupportedAppUsage
translateRectInScreenToAppWindow(@ullable Rect rect)435         public void translateRectInScreenToAppWindow(@Nullable Rect rect) {
436             if (rect == null) {
437                 return;
438             }
439             rect.scale(applicationInvertedScale);
440         }
441 
442         /**
443          * Translate an {@link InsetsState} in screen coordinates into the app window's coordinates.
444          */
translateInsetsStateInScreenToAppWindow(InsetsState state)445         public void translateInsetsStateInScreenToAppWindow(InsetsState state) {
446             state.scale(applicationInvertedScale);
447         }
448 
449         /**
450          * Translate {@link InsetsSourceControl}s in screen coordinates into the app window's
451          * coordinates.
452          */
translateSourceControlsInScreenToAppWindow(InsetsSourceControl[] controls)453         public void translateSourceControlsInScreenToAppWindow(InsetsSourceControl[] controls) {
454             if (controls == null) {
455                 return;
456             }
457             final float scale = applicationInvertedScale;
458             if (scale == 1f) {
459                 return;
460             }
461             for (InsetsSourceControl control : controls) {
462                 if (control == null) {
463                     continue;
464                 }
465                 final Insets hint = control.getInsetsHint();
466                 control.setInsetsHint(
467                         (int) (scale * hint.left),
468                         (int) (scale * hint.top),
469                         (int) (scale * hint.right),
470                         (int) (scale * hint.bottom));
471             }
472         }
473 
474         /**
475          * Translate a Point in screen coordinates into the app window's coordinates.
476          */
translatePointInScreenToAppWindow(PointF point)477         public void translatePointInScreenToAppWindow(PointF point) {
478             final float scale = applicationInvertedScale;
479             if (scale != 1.0f) {
480                 point.x *= scale;
481                 point.y *= scale;
482             }
483         }
484 
485         /**
486          * Translate the location of the sub window.
487          * @param params
488          */
translateLayoutParamsInAppWindowToScreen(LayoutParams params)489         public void translateLayoutParamsInAppWindowToScreen(LayoutParams params) {
490             params.scale(applicationScale);
491         }
492 
493         /**
494          * Translate the content insets in application window to Screen. This uses
495          * the internal buffer for content insets to avoid extra object allocation.
496          */
497         @UnsupportedAppUsage
getTranslatedContentInsets(Rect contentInsets)498         public Rect getTranslatedContentInsets(Rect contentInsets) {
499             if (mContentInsetsBuffer == null) mContentInsetsBuffer = new Rect();
500             mContentInsetsBuffer.set(contentInsets);
501             translateRectInAppWindowToScreen(mContentInsetsBuffer);
502             return mContentInsetsBuffer;
503         }
504 
505         /**
506          * Translate the visible insets in application window to Screen. This uses
507          * the internal buffer for visible insets to avoid extra object allocation.
508          */
getTranslatedVisibleInsets(Rect visibleInsets)509         public Rect getTranslatedVisibleInsets(Rect visibleInsets) {
510             if (mVisibleInsetsBuffer == null) mVisibleInsetsBuffer = new Rect();
511             mVisibleInsetsBuffer.set(visibleInsets);
512             translateRectInAppWindowToScreen(mVisibleInsetsBuffer);
513             return mVisibleInsetsBuffer;
514         }
515 
516         /**
517          * Translate the touchable area in application window to Screen. This uses
518          * the internal buffer for touchable area to avoid extra object allocation.
519          */
getTranslatedTouchableArea(Region touchableArea)520         public Region getTranslatedTouchableArea(Region touchableArea) {
521             if (mTouchableAreaBuffer == null) mTouchableAreaBuffer = new Region();
522             mTouchableAreaBuffer.set(touchableArea);
523             mTouchableAreaBuffer.scale(applicationScale);
524             return mTouchableAreaBuffer;
525         }
526     }
527 
528     /** Applies the compatibility adjustment to the display metrics. */
applyDisplayMetricsIfNeeded(DisplayMetrics inoutDm, boolean applyToSize)529     public void applyDisplayMetricsIfNeeded(DisplayMetrics inoutDm, boolean applyToSize) {
530         if (hasOverrideScale()) {
531             scaleDisplayMetrics(sOverrideInvertedScale, inoutDm, applyToSize);
532             return;
533         }
534         if (!equals(DEFAULT_COMPATIBILITY_INFO)) {
535             applyToDisplayMetrics(inoutDm);
536         }
537     }
538 
applyToDisplayMetrics(DisplayMetrics inoutDm)539     public void applyToDisplayMetrics(DisplayMetrics inoutDm) {
540         if (hasOverrideScale()) return;
541         if (!supportsScreen()) {
542             // This is a larger screen device and the app is not
543             // compatible with large screens, so diddle it.
544             CompatibilityInfo.computeCompatibleScaling(inoutDm, inoutDm);
545         } else {
546             inoutDm.widthPixels = inoutDm.noncompatWidthPixels;
547             inoutDm.heightPixels = inoutDm.noncompatHeightPixels;
548         }
549 
550         if (isScalingRequired()) {
551             scaleDisplayMetrics(applicationInvertedScale, inoutDm, true /* applyToSize */);
552         }
553     }
554 
555     /** Scales the density of the given display metrics. */
scaleDisplayMetrics(float invertedRatio, DisplayMetrics inoutDm, boolean applyToSize)556     private static void scaleDisplayMetrics(float invertedRatio, DisplayMetrics inoutDm,
557             boolean applyToSize) {
558         inoutDm.density = inoutDm.noncompatDensity * invertedRatio;
559         inoutDm.densityDpi = (int) ((inoutDm.noncompatDensityDpi * invertedRatio) + .5f);
560         // Note: since this is changing the scaledDensity, you might think we also need to change
561         // inoutDm.fontScaleConverter to accurately calculate non-linear font scaling. But we're not
562         // going to do that, for a couple of reasons (see b/265695259 for details):
563         // 1. The first case is only for apps targeting SDK < 4. These ancient apps will just have
564         //    to live with linear font scaling. We don't want to make anything more unpredictable.
565         // 2. The second case where this is called is for scaling down games. But it is called in
566         //    two situations:
567         //    a. When from ResourcesImpl.updateConfiguration(), we will set the fontScaleConverter
568         //       *after* this method is called. That's the only place where the app will actually
569         //       use the DisplayMetrics for scaling fonts in its resources.
570         //    b. Sometime later by WindowManager in onResume or other windowing events. In this case
571         //       the DisplayMetrics object is never used by the app/resources, so it's ok if
572         //       fontScaleConverter is null because it's not being used to scale fonts anyway.
573         inoutDm.scaledDensity = inoutDm.noncompatScaledDensity * invertedRatio;
574         inoutDm.xdpi = inoutDm.noncompatXdpi * invertedRatio;
575         inoutDm.ydpi = inoutDm.noncompatYdpi * invertedRatio;
576         if (applyToSize) {
577             inoutDm.widthPixels = (int) (inoutDm.widthPixels * invertedRatio + 0.5f);
578             inoutDm.heightPixels = (int) (inoutDm.heightPixels * invertedRatio + 0.5f);
579         }
580     }
581 
applyToConfiguration(int displayDensity, Configuration inoutConfig)582     public void applyToConfiguration(int displayDensity, Configuration inoutConfig) {
583         if (hasOverrideScale()) return;
584         if (!supportsScreen()) {
585             // This is a larger screen device and the app is not
586             // compatible with large screens, so we are forcing it to
587             // run as if the screen is normal size.
588             inoutConfig.screenLayout =
589                     (inoutConfig.screenLayout&~Configuration.SCREENLAYOUT_SIZE_MASK)
590                     | Configuration.SCREENLAYOUT_SIZE_NORMAL;
591             inoutConfig.screenWidthDp = inoutConfig.compatScreenWidthDp;
592             inoutConfig.screenHeightDp = inoutConfig.compatScreenHeightDp;
593             inoutConfig.smallestScreenWidthDp = inoutConfig.compatSmallestScreenWidthDp;
594         }
595         inoutConfig.densityDpi = displayDensity;
596         if (isScalingRequired()) {
597             scaleConfiguration(applicationInvertedScale, inoutConfig);
598         }
599     }
600 
601     /** Scales the density and bounds of the given configuration. */
scaleConfiguration(float invertedRatio, Configuration inoutConfig)602     public static void scaleConfiguration(float invertedRatio, Configuration inoutConfig) {
603         inoutConfig.densityDpi = (int) ((inoutConfig.densityDpi * invertedRatio) + .5f);
604         inoutConfig.windowConfiguration.scale(invertedRatio);
605     }
606 
607     /** @see #sOverrideInvertedScale */
applyOverrideScaleIfNeeded(Configuration config)608     public static void applyOverrideScaleIfNeeded(Configuration config) {
609         if (!hasOverrideScale()) return;
610         scaleConfiguration(sOverrideInvertedScale, config);
611     }
612 
613     /** @see #sOverrideInvertedScale */
applyOverrideScaleIfNeeded(MergedConfiguration mergedConfig)614     public static void applyOverrideScaleIfNeeded(MergedConfiguration mergedConfig) {
615         if (!hasOverrideScale()) return;
616         scaleConfiguration(sOverrideInvertedScale, mergedConfig.getGlobalConfiguration());
617         scaleConfiguration(sOverrideInvertedScale, mergedConfig.getOverrideConfiguration());
618         scaleConfiguration(sOverrideInvertedScale, mergedConfig.getMergedConfiguration());
619     }
620 
621     /** Returns {@code true} if this process is in a environment with override scale. */
hasOverrideScale()622     private static boolean hasOverrideScale() {
623         return sOverrideInvertedScale != 1f;
624     }
625 
626     /** @see #sOverrideInvertedScale */
setOverrideInvertedScale(float invertedRatio)627     public static void setOverrideInvertedScale(float invertedRatio) {
628         sOverrideInvertedScale = invertedRatio;
629     }
630 
631     /** @see #sOverrideInvertedScale */
getOverrideInvertedScale()632     public static float getOverrideInvertedScale() {
633         return sOverrideInvertedScale;
634     }
635 
636     /**
637      * Compute the frame Rect for applications runs under compatibility mode.
638      *
639      * @param dm the display metrics used to compute the frame size.
640      * @param outDm If non-null the width and height will be set to their scaled values.
641      * @return Returns the scaling factor for the window.
642      */
643     @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
computeCompatibleScaling(DisplayMetrics dm, DisplayMetrics outDm)644     public static float computeCompatibleScaling(DisplayMetrics dm, DisplayMetrics outDm) {
645         final int width = dm.noncompatWidthPixels;
646         final int height = dm.noncompatHeightPixels;
647         int shortSize, longSize;
648         if (width < height) {
649             shortSize = width;
650             longSize = height;
651         } else {
652             shortSize = height;
653             longSize = width;
654         }
655         int newShortSize = (int)(DEFAULT_NORMAL_SHORT_DIMENSION * dm.density + 0.5f);
656         float aspect = ((float)longSize) / shortSize;
657         if (aspect > MAXIMUM_ASPECT_RATIO) {
658             aspect = MAXIMUM_ASPECT_RATIO;
659         }
660         int newLongSize = (int)(newShortSize * aspect + 0.5f);
661         int newWidth, newHeight;
662         if (width < height) {
663             newWidth = newShortSize;
664             newHeight = newLongSize;
665         } else {
666             newWidth = newLongSize;
667             newHeight = newShortSize;
668         }
669 
670         float sw = width/(float)newWidth;
671         float sh = height/(float)newHeight;
672         float scale = sw < sh ? sw : sh;
673         if (scale < 1) {
674             scale = 1;
675         }
676 
677         if (outDm != null) {
678             outDm.widthPixels = newWidth;
679             outDm.heightPixels = newHeight;
680         }
681 
682         return scale;
683     }
684 
685     @Override
686     public boolean equals(@Nullable Object o) {
687         if (this == o) {
688             return true;
689         }
690         try {
691             CompatibilityInfo oc = (CompatibilityInfo)o;
692             if (mCompatibilityFlags != oc.mCompatibilityFlags) return false;
693             if (applicationDensity != oc.applicationDensity) return false;
694             if (applicationScale != oc.applicationScale) return false;
695             if (applicationInvertedScale != oc.applicationInvertedScale) return false;
696             return true;
697         } catch (ClassCastException e) {
698             return false;
699         }
700     }
701 
702     @Override
703     public String toString() {
704         StringBuilder sb = new StringBuilder(128);
705         sb.append("{");
706         sb.append(applicationDensity);
707         sb.append("dpi");
708         if (isScalingRequired()) {
709             sb.append(" ");
710             sb.append(applicationScale);
711             sb.append("x");
712         }
713         if (hasOverrideScaling()) {
714             sb.append(" overrideInvScale=");
715             sb.append(applicationInvertedScale);
716         }
717         if (!supportsScreen()) {
718             sb.append(" resizing");
719         }
720         if (neverSupportsScreen()) {
721             sb.append(" never-compat");
722         }
723         if (alwaysSupportsScreen()) {
724             sb.append(" always-compat");
725         }
726         sb.append("}");
727         return sb.toString();
728     }
729 
730     @Override
731     public int hashCode() {
732         int result = 17;
733         result = 31 * result + mCompatibilityFlags;
734         result = 31 * result + applicationDensity;
735         result = 31 * result + Float.floatToIntBits(applicationScale);
736         result = 31 * result + Float.floatToIntBits(applicationInvertedScale);
737         return result;
738     }
739 
740     @Override
741     public int describeContents() {
742         return 0;
743     }
744 
745     @Override
746     public void writeToParcel(Parcel dest, int flags) {
747         dest.writeInt(mCompatibilityFlags);
748         dest.writeInt(applicationDensity);
749         dest.writeFloat(applicationScale);
750         dest.writeFloat(applicationInvertedScale);
751     }
752 
753     @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P, trackingBug = 115609023)
754     public static final @android.annotation.NonNull Parcelable.Creator<CompatibilityInfo> CREATOR
755             = new Parcelable.Creator<CompatibilityInfo>() {
756         @Override
757         public CompatibilityInfo createFromParcel(Parcel source) {
758             return new CompatibilityInfo(source);
759         }
760 
761         @Override
762         public CompatibilityInfo[] newArray(int size) {
763             return new CompatibilityInfo[size];
764         }
765     };
766 
767     private CompatibilityInfo(Parcel source) {
768         mCompatibilityFlags = source.readInt();
769         applicationDensity = source.readInt();
770         applicationScale = source.readFloat();
771         applicationInvertedScale = source.readFloat();
772     }
773 }
774