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