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