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