CompatibilityInfo.java revision 8ea5e1d79eb1f05ee7628b0d45ea8fc8eea5330d
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&NEVER_NEEDS_COMPAT) != 0;
247    }
248
249    public boolean alwaysSupportsScreen() {
250        return (mCompatibilityFlags&ALWAYS_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.updateCompatibleScreenFrame(inoutDm, null, 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 updateCompatibleScreenFrame(DisplayMetrics dm,
447            Rect outRect, DisplayMetrics outDm) {
448        final int width = dm.unscaledWidthPixels;
449        final int height = dm.unscaledHeightPixels;
450        int shortSize, longSize;
451        if (width < height) {
452            shortSize = width;
453            longSize = height;
454        } else {
455            shortSize = height;
456            longSize = width;
457        }
458        int newShortSize = (int)(DEFAULT_NORMAL_SHORT_DIMENSION * dm.density + 0.5f);
459        float aspect = ((float)longSize) / shortSize;
460        if (aspect > MAXIMUM_ASPECT_RATIO) {
461            aspect = MAXIMUM_ASPECT_RATIO;
462        }
463        int newLongSize = (int)(newShortSize * aspect + 0.5f);
464        int newWidth, newHeight;
465        if (width < height) {
466            newWidth = newShortSize;
467            newHeight = newLongSize;
468        } else {
469            newWidth = newLongSize;
470            newHeight = newShortSize;
471        }
472
473        float sw = width/(float)newWidth;
474        float sh = height/(float)newHeight;
475        float scale = sw < sh ? sw : sh;
476        if (scale < 1) {
477            scale = 1;
478        }
479
480        if (outRect != null) {
481            final int left = (int)((width-(newWidth*scale))/2);
482            final int top = (int)((height-(newHeight*scale))/2);
483            outRect.set(left, top, left+newWidth, top+newHeight);
484        }
485
486        if (outDm != null) {
487            outDm.widthPixels = newWidth;
488            outDm.heightPixels = newHeight;
489        }
490
491        return scale;
492    }
493
494    @Override
495    public boolean equals(Object o) {
496        try {
497            CompatibilityInfo oc = (CompatibilityInfo)o;
498            if (mCompatibilityFlags != oc.mCompatibilityFlags) return false;
499            if (applicationDensity != oc.applicationDensity) return false;
500            if (applicationScale != oc.applicationScale) return false;
501            if (applicationInvertedScale != oc.applicationInvertedScale) return false;
502            return true;
503        } catch (ClassCastException e) {
504            return false;
505        }
506    }
507
508    @Override
509    public String toString() {
510        StringBuilder sb = new StringBuilder(128);
511        sb.append("{");
512        sb.append(applicationDensity);
513        sb.append("dpi");
514        if (isScalingRequired()) {
515            sb.append(" scaling");
516        }
517        if (!supportsScreen()) {
518            sb.append(" resizing");
519        }
520        if (neverSupportsScreen()) {
521            sb.append(" never-compat");
522        }
523        if (alwaysSupportsScreen()) {
524            sb.append(" always-compat");
525        }
526        sb.append("}");
527        return sb.toString();
528    }
529
530    @Override
531    public int hashCode() {
532        int result = 17;
533        result = 31 * result + mCompatibilityFlags;
534        result = 31 * result + applicationDensity;
535        result = 31 * result + Float.floatToIntBits(applicationScale);
536        result = 31 * result + Float.floatToIntBits(applicationInvertedScale);
537        return result;
538    }
539
540    @Override
541    public int describeContents() {
542        return 0;
543    }
544
545    @Override
546    public void writeToParcel(Parcel dest, int flags) {
547        dest.writeInt(mCompatibilityFlags);
548        dest.writeInt(applicationDensity);
549        dest.writeFloat(applicationScale);
550        dest.writeFloat(applicationInvertedScale);
551    }
552
553    public static final Parcelable.Creator<CompatibilityInfo> CREATOR
554            = new Parcelable.Creator<CompatibilityInfo>() {
555        public CompatibilityInfo createFromParcel(Parcel source) {
556            return new CompatibilityInfo(source);
557        }
558
559        public CompatibilityInfo[] newArray(int size) {
560            return new CompatibilityInfo[size];
561        }
562    };
563
564    private CompatibilityInfo(Parcel source) {
565        mCompatibilityFlags = source.readInt();
566        applicationDensity = source.readInt();
567        applicationScale = source.readFloat();
568        applicationInvertedScale = source.readFloat();
569    }
570}
571