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