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