CompatibilityInfo.java revision 2f0b17573d4324832f7a20402a3d2b5920bc4866
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
17package android.content.res;
18
19import android.content.pm.ApplicationInfo;
20import android.graphics.Canvas;
21import android.graphics.PointF;
22import android.graphics.Rect;
23import android.graphics.Region;
24import android.os.Parcel;
25import android.os.Parcelable;
26import android.util.DisplayMetrics;
27import android.view.MotionEvent;
28import android.view.WindowManager;
29import android.view.WindowManager.LayoutParams;
30
31/**
32 * CompatibilityInfo class keeps the information about compatibility mode that the application is
33 * running under.
34 *
35 *  {@hide}
36 */
37public class CompatibilityInfo implements Parcelable {
38    /** default compatibility info object for compatible applications */
39    public static final CompatibilityInfo DEFAULT_COMPATIBILITY_INFO = new CompatibilityInfo() {
40    };
41
42    /**
43     * This is the number of pixels we would like to have along the
44     * short axis of an app that needs to run on a normal size screen.
45     */
46    public static final int DEFAULT_NORMAL_SHORT_DIMENSION = 320;
47
48    /**
49     * This is the maximum aspect ratio we will allow while keeping
50     * applications in a compatible screen size.
51     */
52    public static final float MAXIMUM_ASPECT_RATIO = (854f/480f);
53
54    /**
55     *  A compatibility flags
56     */
57    private final int mCompatibilityFlags;
58
59    /**
60     * A flag mask to tell if the application needs scaling (when mApplicationScale != 1.0f)
61     * {@see compatibilityFlag}
62     */
63    private static final int SCALING_REQUIRED = 1;
64
65    /**
66     * Application must always run in compatibility mode?
67     */
68    private static final int ALWAYS_NEEDS_COMPAT = 2;
69
70    /**
71     * Application never should run in compatibility mode?
72     */
73    private static final int NEVER_NEEDS_COMPAT = 4;
74
75    /**
76     * Set if the application needs to run in screen size compatibility mode.
77     */
78    private static final int NEEDS_SCREEN_COMPAT = 8;
79
80    /**
81     * The effective screen density we have selected for this application.
82     */
83    public final int applicationDensity;
84
85    /**
86     * Application's scale.
87     */
88    public final float applicationScale;
89
90    /**
91     * Application's inverted scale.
92     */
93    public final float applicationInvertedScale;
94
95    public CompatibilityInfo(ApplicationInfo appInfo, int screenLayout, int sw,
96            boolean forceCompat) {
97        int compatFlags = 0;
98
99        if (appInfo.requiresSmallestWidthDp != 0 || appInfo.compatibleWidthLimitDp != 0) {
100            // New style screen requirements spec.
101            int required = appInfo.requiresSmallestWidthDp != 0
102                    ? appInfo.requiresSmallestWidthDp
103                    : appInfo.compatibleWidthLimitDp;
104            int compat = appInfo.compatibleWidthLimitDp != 0
105                    ? appInfo.compatibleWidthLimitDp
106                    : appInfo.requiresSmallestWidthDp;
107            if (compat < required)  {
108                compat = required;
109            }
110
111            if (compat >= sw) {
112                compatFlags |= NEVER_NEEDS_COMPAT;
113            } else if (forceCompat) {
114                compatFlags |= NEEDS_SCREEN_COMPAT;
115            }
116
117            // Modern apps always support densities.
118            applicationDensity = DisplayMetrics.DENSITY_DEVICE;
119            applicationScale = 1.0f;
120            applicationInvertedScale = 1.0f;
121
122        } else {
123            /**
124             * Has the application said that its UI is expandable?  Based on the
125             * <supports-screen> android:expandible in the manifest.
126             */
127            final int EXPANDABLE = 2;
128
129            /**
130             * Has the application said that its UI supports large screens?  Based on the
131             * <supports-screen> android:largeScreens in the manifest.
132             */
133            final int LARGE_SCREENS = 8;
134
135            /**
136             * Has the application said that its UI supports xlarge screens?  Based on the
137             * <supports-screen> android:xlargeScreens in the manifest.
138             */
139            final int XLARGE_SCREENS = 32;
140
141            int sizeInfo = 0;
142
143            // We can't rely on the application always setting
144            // FLAG_RESIZEABLE_FOR_SCREENS so will compute it based on various input.
145            boolean anyResizeable = false;
146
147            if ((appInfo.flags & ApplicationInfo.FLAG_SUPPORTS_LARGE_SCREENS) != 0) {
148                sizeInfo |= LARGE_SCREENS;
149                anyResizeable = true;
150                if (!forceCompat) {
151                    // If we aren't forcing the app into compatibility mode, then
152                    // assume if it supports large screens that we should allow it
153                    // to use the full space of an xlarge screen as well.
154                    sizeInfo |= XLARGE_SCREENS | EXPANDABLE;
155                }
156            }
157            if ((appInfo.flags & ApplicationInfo.FLAG_SUPPORTS_XLARGE_SCREENS) != 0) {
158                anyResizeable = true;
159                if (!forceCompat) {
160                    sizeInfo |= XLARGE_SCREENS | EXPANDABLE;
161                }
162            }
163            if ((appInfo.flags & ApplicationInfo.FLAG_RESIZEABLE_FOR_SCREENS) != 0) {
164                anyResizeable = true;
165                sizeInfo |= EXPANDABLE;
166            }
167
168            if (forceCompat) {
169                // If we are forcing compatibility mode, then ignore an app that
170                // just says it is resizable for screens.  We'll only have it fill
171                // the screen if it explicitly says it supports the screen size we
172                // are running in.
173                sizeInfo &= ~EXPANDABLE;
174            }
175
176            compatFlags |= NEEDS_SCREEN_COMPAT;
177            switch (screenLayout&Configuration.SCREENLAYOUT_SIZE_MASK) {
178                case Configuration.SCREENLAYOUT_SIZE_XLARGE:
179                    if ((sizeInfo&XLARGE_SCREENS) != 0) {
180                        compatFlags &= ~NEEDS_SCREEN_COMPAT;
181                    }
182                    if ((appInfo.flags & ApplicationInfo.FLAG_SUPPORTS_XLARGE_SCREENS) != 0) {
183                        compatFlags |= NEVER_NEEDS_COMPAT;
184                    }
185                    break;
186                case Configuration.SCREENLAYOUT_SIZE_LARGE:
187                    if ((sizeInfo&LARGE_SCREENS) != 0) {
188                        compatFlags &= ~NEEDS_SCREEN_COMPAT;
189                    }
190                    if ((appInfo.flags & ApplicationInfo.FLAG_SUPPORTS_LARGE_SCREENS) != 0) {
191                        compatFlags |= NEVER_NEEDS_COMPAT;
192                    }
193                    break;
194            }
195
196            if ((screenLayout&Configuration.SCREENLAYOUT_COMPAT_NEEDED) != 0) {
197                if ((sizeInfo&EXPANDABLE) != 0) {
198                    compatFlags &= ~NEEDS_SCREEN_COMPAT;
199                } else if (!anyResizeable) {
200                    compatFlags |= ALWAYS_NEEDS_COMPAT;
201                }
202            }
203
204            if ((appInfo.flags & ApplicationInfo.FLAG_SUPPORTS_SCREEN_DENSITIES) != 0) {
205                applicationDensity = DisplayMetrics.DENSITY_DEVICE;
206                applicationScale = 1.0f;
207                applicationInvertedScale = 1.0f;
208            } else {
209                applicationDensity = DisplayMetrics.DENSITY_DEFAULT;
210                applicationScale = DisplayMetrics.DENSITY_DEVICE
211                        / (float) DisplayMetrics.DENSITY_DEFAULT;
212                applicationInvertedScale = 1.0f / applicationScale;
213                compatFlags |= SCALING_REQUIRED;
214            }
215        }
216
217        mCompatibilityFlags = compatFlags;
218    }
219
220    private CompatibilityInfo(int compFlags,
221            int dens, float scale, float invertedScale) {
222        mCompatibilityFlags = compFlags;
223        applicationDensity = dens;
224        applicationScale = scale;
225        applicationInvertedScale = invertedScale;
226    }
227
228    private CompatibilityInfo() {
229        this(NEVER_NEEDS_COMPAT, DisplayMetrics.DENSITY_DEVICE,
230                1.0f,
231                1.0f);
232    }
233
234    /**
235     * @return true if the scaling is required
236     */
237    public boolean isScalingRequired() {
238        return (mCompatibilityFlags&SCALING_REQUIRED) != 0;
239    }
240
241    public boolean supportsScreen() {
242        return (mCompatibilityFlags&NEEDS_SCREEN_COMPAT) == 0;
243    }
244
245    public boolean neverSupportsScreen() {
246        return (mCompatibilityFlags&ALWAYS_NEEDS_COMPAT) != 0;
247    }
248
249    public boolean alwaysSupportsScreen() {
250        return (mCompatibilityFlags&NEVER_NEEDS_COMPAT) != 0;
251    }
252
253    /**
254     * Returns the translator which translates the coordinates in compatibility mode.
255     * @param params the window's parameter
256     */
257    public Translator getTranslator() {
258        return isScalingRequired() ? new Translator() : null;
259    }
260
261    /**
262     * A helper object to translate the screen and window coordinates back and forth.
263     * @hide
264     */
265    public class Translator {
266        final public float applicationScale;
267        final public float applicationInvertedScale;
268
269        private Rect mContentInsetsBuffer = null;
270        private Rect mVisibleInsetsBuffer = null;
271        private Region mTouchableAreaBuffer = null;
272
273        Translator(float applicationScale, float applicationInvertedScale) {
274            this.applicationScale = applicationScale;
275            this.applicationInvertedScale = applicationInvertedScale;
276        }
277
278        Translator() {
279            this(CompatibilityInfo.this.applicationScale,
280                    CompatibilityInfo.this.applicationInvertedScale);
281        }
282
283        /**
284         * Translate the screen rect to the application frame.
285         */
286        public void translateRectInScreenToAppWinFrame(Rect rect) {
287            rect.scale(applicationInvertedScale);
288        }
289
290        /**
291         * Translate the region in window to screen.
292         */
293        public void translateRegionInWindowToScreen(Region transparentRegion) {
294            transparentRegion.scale(applicationScale);
295        }
296
297        /**
298         * Apply translation to the canvas that is necessary to draw the content.
299         */
300        public void translateCanvas(Canvas canvas) {
301            if (applicationScale == 1.5f) {
302                /*  When we scale for compatibility, we can put our stretched
303                    bitmaps and ninepatches on exacty 1/2 pixel boundaries,
304                    which can give us inconsistent drawing due to imperfect
305                    float precision in the graphics engine's inverse matrix.
306
307                    As a work-around, we translate by a tiny amount to avoid
308                    landing on exact pixel centers and boundaries, giving us
309                    the slop we need to draw consistently.
310
311                    This constant is meant to resolve to 1/255 after it is
312                    scaled by 1.5 (applicationScale). Note, this is just a guess
313                    as to what is small enough not to create its own artifacts,
314                    and big enough to avoid the precision problems. Feel free
315                    to experiment with smaller values as you choose.
316                 */
317                final float tinyOffset = 2.0f / (3 * 255);
318                canvas.translate(tinyOffset, tinyOffset);
319            }
320            canvas.scale(applicationScale, applicationScale);
321        }
322
323        /**
324         * Translate the motion event captured on screen to the application's window.
325         */
326        public void translateEventInScreenToAppWindow(MotionEvent event) {
327            event.scale(applicationInvertedScale);
328        }
329
330        /**
331         * Translate the window's layout parameter, from application's view to
332         * Screen's view.
333         */
334        public void translateWindowLayout(WindowManager.LayoutParams params) {
335            params.scale(applicationScale);
336        }
337
338        /**
339         * Translate a Rect in application's window to screen.
340         */
341        public void translateRectInAppWindowToScreen(Rect rect) {
342            rect.scale(applicationScale);
343        }
344
345        /**
346         * Translate a Rect in screen coordinates into the app window's coordinates.
347         */
348        public void translateRectInScreenToAppWindow(Rect rect) {
349            rect.scale(applicationInvertedScale);
350        }
351
352        /**
353         * Translate a Point in screen coordinates into the app window's coordinates.
354         */
355        public void translatePointInScreenToAppWindow(PointF point) {
356            final float scale = applicationInvertedScale;
357            if (scale != 1.0f) {
358                point.x *= scale;
359                point.y *= scale;
360            }
361        }
362
363        /**
364         * Translate the location of the sub window.
365         * @param params
366         */
367        public void translateLayoutParamsInAppWindowToScreen(LayoutParams params) {
368            params.scale(applicationScale);
369        }
370
371        /**
372         * Translate the content insets in application window to Screen. This uses
373         * the internal buffer for content insets to avoid extra object allocation.
374         */
375        public Rect getTranslatedContentInsets(Rect contentInsets) {
376            if (mContentInsetsBuffer == null) mContentInsetsBuffer = new Rect();
377            mContentInsetsBuffer.set(contentInsets);
378            translateRectInAppWindowToScreen(mContentInsetsBuffer);
379            return mContentInsetsBuffer;
380        }
381
382        /**
383         * Translate the visible insets in application window to Screen. This uses
384         * the internal buffer for visible insets to avoid extra object allocation.
385         */
386        public Rect getTranslatedVisibleInsets(Rect visibleInsets) {
387            if (mVisibleInsetsBuffer == null) mVisibleInsetsBuffer = new Rect();
388            mVisibleInsetsBuffer.set(visibleInsets);
389            translateRectInAppWindowToScreen(mVisibleInsetsBuffer);
390            return mVisibleInsetsBuffer;
391        }
392
393        /**
394         * Translate the touchable area in application window to Screen. This uses
395         * the internal buffer for touchable area to avoid extra object allocation.
396         */
397        public Region getTranslatedTouchableArea(Region touchableArea) {
398            if (mTouchableAreaBuffer == null) mTouchableAreaBuffer = new Region();
399            mTouchableAreaBuffer.set(touchableArea);
400            mTouchableAreaBuffer.scale(applicationScale);
401            return mTouchableAreaBuffer;
402        }
403    }
404
405    public void applyToDisplayMetrics(DisplayMetrics inoutDm) {
406        if (!supportsScreen()) {
407            // This is a larger screen device and the app is not
408            // compatible with large screens, so diddle it.
409            CompatibilityInfo.computeCompatibleScaling(inoutDm, inoutDm);
410        } else {
411            inoutDm.widthPixels = inoutDm.unscaledWidthPixels;
412            inoutDm.heightPixels = inoutDm.unscaledHeightPixels;
413        }
414
415        if (isScalingRequired()) {
416            float invertedRatio = applicationInvertedScale;
417            inoutDm.density *= invertedRatio;
418            inoutDm.densityDpi = (int)((inoutDm.density*DisplayMetrics.DENSITY_DEFAULT)+.5f);
419            inoutDm.scaledDensity *= invertedRatio;
420            inoutDm.xdpi *= invertedRatio;
421            inoutDm.ydpi *= invertedRatio;
422            inoutDm.widthPixels = (int) (inoutDm.widthPixels * invertedRatio + 0.5f);
423            inoutDm.heightPixels = (int) (inoutDm.heightPixels * invertedRatio + 0.5f);
424        }
425    }
426
427    public void applyToConfiguration(Configuration inoutConfig) {
428        if (!supportsScreen()) {
429            // This is a larger screen device and the app is not
430            // compatible with large screens, so we are forcing it to
431            // run as if the screen is normal size.
432            inoutConfig.screenLayout =
433                    (inoutConfig.screenLayout&~Configuration.SCREENLAYOUT_SIZE_MASK)
434                    | Configuration.SCREENLAYOUT_SIZE_NORMAL;
435        }
436    }
437
438    /**
439     * Compute the frame Rect for applications runs under compatibility mode.
440     *
441     * @param dm the display metrics used to compute the frame size.
442     * @param orientation the orientation of the screen.
443     * @param outRect the output parameter which will contain the result.
444     * @return Returns the scaling factor for the window.
445     */
446    public static float computeCompatibleScaling(DisplayMetrics dm, DisplayMetrics outDm) {
447        final int width = dm.unscaledWidthPixels;
448        final int height = dm.unscaledHeightPixels;
449        int shortSize, longSize;
450        if (width < height) {
451            shortSize = width;
452            longSize = height;
453        } else {
454            shortSize = height;
455            longSize = width;
456        }
457        int newShortSize = (int)(DEFAULT_NORMAL_SHORT_DIMENSION * dm.density + 0.5f);
458        float aspect = ((float)longSize) / shortSize;
459        if (aspect > MAXIMUM_ASPECT_RATIO) {
460            aspect = MAXIMUM_ASPECT_RATIO;
461        }
462        int newLongSize = (int)(newShortSize * aspect + 0.5f);
463        int newWidth, newHeight;
464        if (width < height) {
465            newWidth = newShortSize;
466            newHeight = newLongSize;
467        } else {
468            newWidth = newLongSize;
469            newHeight = newShortSize;
470        }
471
472        float sw = width/(float)newWidth;
473        float sh = height/(float)newHeight;
474        float scale = sw < sh ? sw : sh;
475        if (scale < 1) {
476            scale = 1;
477        }
478
479        if (outDm != null) {
480            outDm.widthPixels = newWidth;
481            outDm.heightPixels = newHeight;
482        }
483
484        return scale;
485    }
486
487    @Override
488    public boolean equals(Object o) {
489        try {
490            CompatibilityInfo oc = (CompatibilityInfo)o;
491            if (mCompatibilityFlags != oc.mCompatibilityFlags) return false;
492            if (applicationDensity != oc.applicationDensity) return false;
493            if (applicationScale != oc.applicationScale) return false;
494            if (applicationInvertedScale != oc.applicationInvertedScale) return false;
495            return true;
496        } catch (ClassCastException e) {
497            return false;
498        }
499    }
500
501    @Override
502    public String toString() {
503        StringBuilder sb = new StringBuilder(128);
504        sb.append("{");
505        sb.append(applicationDensity);
506        sb.append("dpi");
507        if (isScalingRequired()) {
508            sb.append(" scaling");
509        }
510        if (!supportsScreen()) {
511            sb.append(" resizing");
512        }
513        if (neverSupportsScreen()) {
514            sb.append(" never-compat");
515        }
516        if (alwaysSupportsScreen()) {
517            sb.append(" always-compat");
518        }
519        sb.append("}");
520        return sb.toString();
521    }
522
523    @Override
524    public int hashCode() {
525        int result = 17;
526        result = 31 * result + mCompatibilityFlags;
527        result = 31 * result + applicationDensity;
528        result = 31 * result + Float.floatToIntBits(applicationScale);
529        result = 31 * result + Float.floatToIntBits(applicationInvertedScale);
530        return result;
531    }
532
533    @Override
534    public int describeContents() {
535        return 0;
536    }
537
538    @Override
539    public void writeToParcel(Parcel dest, int flags) {
540        dest.writeInt(mCompatibilityFlags);
541        dest.writeInt(applicationDensity);
542        dest.writeFloat(applicationScale);
543        dest.writeFloat(applicationInvertedScale);
544    }
545
546    public static final Parcelable.Creator<CompatibilityInfo> CREATOR
547            = new Parcelable.Creator<CompatibilityInfo>() {
548        public CompatibilityInfo createFromParcel(Parcel source) {
549            return new CompatibilityInfo(source);
550        }
551
552        public CompatibilityInfo[] newArray(int size) {
553            return new CompatibilityInfo[size];
554        }
555    };
556
557    private CompatibilityInfo(Parcel source) {
558        mCompatibilityFlags = source.readInt();
559        applicationDensity = source.readInt();
560        applicationScale = source.readFloat();
561        applicationInvertedScale = source.readFloat();
562    }
563}
564