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.noncompatDensityDpi * invertedRatio) + .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(int displayDensity, 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        inoutConfig.densityDpi = displayDensity;
464        if (isScalingRequired()) {
465            float invertedRatio = applicationInvertedScale;
466            inoutConfig.densityDpi = (int)((inoutConfig.densityDpi * invertedRatio) + .5f);
467        }
468    }
469
470    /**
471     * Compute the frame Rect for applications runs under compatibility mode.
472     *
473     * @param dm the display metrics used to compute the frame size.
474     * @param outDm If non-null the width and height will be set to their scaled values.
475     * @return Returns the scaling factor for the window.
476     */
477    public static float computeCompatibleScaling(DisplayMetrics dm, DisplayMetrics outDm) {
478        final int width = dm.noncompatWidthPixels;
479        final int height = dm.noncompatHeightPixels;
480        int shortSize, longSize;
481        if (width < height) {
482            shortSize = width;
483            longSize = height;
484        } else {
485            shortSize = height;
486            longSize = width;
487        }
488        int newShortSize = (int)(DEFAULT_NORMAL_SHORT_DIMENSION * dm.density + 0.5f);
489        float aspect = ((float)longSize) / shortSize;
490        if (aspect > MAXIMUM_ASPECT_RATIO) {
491            aspect = MAXIMUM_ASPECT_RATIO;
492        }
493        int newLongSize = (int)(newShortSize * aspect + 0.5f);
494        int newWidth, newHeight;
495        if (width < height) {
496            newWidth = newShortSize;
497            newHeight = newLongSize;
498        } else {
499            newWidth = newLongSize;
500            newHeight = newShortSize;
501        }
502
503        float sw = width/(float)newWidth;
504        float sh = height/(float)newHeight;
505        float scale = sw < sh ? sw : sh;
506        if (scale < 1) {
507            scale = 1;
508        }
509
510        if (outDm != null) {
511            outDm.widthPixels = newWidth;
512            outDm.heightPixels = newHeight;
513        }
514
515        return scale;
516    }
517
518    @Override
519    public boolean equals(Object o) {
520        if (this == o) {
521            return true;
522        }
523        try {
524            CompatibilityInfo oc = (CompatibilityInfo)o;
525            if (mCompatibilityFlags != oc.mCompatibilityFlags) return false;
526            if (applicationDensity != oc.applicationDensity) return false;
527            if (applicationScale != oc.applicationScale) return false;
528            if (applicationInvertedScale != oc.applicationInvertedScale) return false;
529            return true;
530        } catch (ClassCastException e) {
531            return false;
532        }
533    }
534
535    @Override
536    public String toString() {
537        StringBuilder sb = new StringBuilder(128);
538        sb.append("{");
539        sb.append(applicationDensity);
540        sb.append("dpi");
541        if (isScalingRequired()) {
542            sb.append(" ");
543            sb.append(applicationScale);
544            sb.append("x");
545        }
546        if (!supportsScreen()) {
547            sb.append(" resizing");
548        }
549        if (neverSupportsScreen()) {
550            sb.append(" never-compat");
551        }
552        if (alwaysSupportsScreen()) {
553            sb.append(" always-compat");
554        }
555        sb.append("}");
556        return sb.toString();
557    }
558
559    @Override
560    public int hashCode() {
561        int result = 17;
562        result = 31 * result + mCompatibilityFlags;
563        result = 31 * result + applicationDensity;
564        result = 31 * result + Float.floatToIntBits(applicationScale);
565        result = 31 * result + Float.floatToIntBits(applicationInvertedScale);
566        return result;
567    }
568
569    @Override
570    public int describeContents() {
571        return 0;
572    }
573
574    @Override
575    public void writeToParcel(Parcel dest, int flags) {
576        dest.writeInt(mCompatibilityFlags);
577        dest.writeInt(applicationDensity);
578        dest.writeFloat(applicationScale);
579        dest.writeFloat(applicationInvertedScale);
580    }
581
582    public static final Parcelable.Creator<CompatibilityInfo> CREATOR
583            = new Parcelable.Creator<CompatibilityInfo>() {
584        @Override
585        public CompatibilityInfo createFromParcel(Parcel source) {
586            return new CompatibilityInfo(source);
587        }
588
589        @Override
590        public CompatibilityInfo[] newArray(int size) {
591            return new CompatibilityInfo[size];
592        }
593    };
594
595    private CompatibilityInfo(Parcel source) {
596        mCompatibilityFlags = source.readInt();
597        applicationDensity = source.readInt();
598        applicationScale = source.readFloat();
599        applicationInvertedScale = source.readFloat();
600    }
601}
602