CompatibilityInfo.java revision cf4550c3198d6b3d92cdc52707fe70d7cc0caa9f
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.Rect;
22import android.graphics.Region;
23import android.util.DisplayMetrics;
24import android.util.Log;
25import android.view.Gravity;
26import android.view.MotionEvent;
27import android.view.WindowManager;
28import android.view.WindowManager.LayoutParams;
29
30/**
31 * CompatibilityInfo class keeps the information about compatibility mode that the application is
32 * running under.
33 *
34 *  {@hide}
35 */
36public class CompatibilityInfo {
37    private static final boolean DBG = false;
38    private static final String TAG = "CompatibilityInfo";
39
40    /** default compatibility info object for compatible applications */
41    public static final CompatibilityInfo DEFAULT_COMPATIBILITY_INFO = new CompatibilityInfo();
42
43    /**
44     * The default width of the screen in portrait mode.
45     */
46    public static final int DEFAULT_PORTRAIT_WIDTH = 320;
47
48    /**
49     * The default height of the screen in portrait mode.
50     */
51    public static final int DEFAULT_PORTRAIT_HEIGHT = 480;
52
53    /**
54     * The x-shift mode that controls the position of the content or the window under
55     * compatibility mode.
56     * {@see getTranslator}
57     * {@see Translator#mShiftMode}
58     */
59    private static final int X_SHIFT_NONE = 0;
60    private static final int X_SHIFT_CONTENT = 1;
61    private static final int X_SHIFT_AND_CLIP_CONTENT = 2;
62    private static final int X_SHIFT_WINDOW = 3;
63
64
65    /**
66     *  A compatibility flags
67     */
68    private int mCompatibilityFlags;
69
70    /**
71     * A flag mask to tell if the application needs scaling (when mApplicationScale != 1.0f)
72     * {@see compatibilityFlag}
73     */
74    private static final int SCALING_REQUIRED = 1;
75
76    /**
77     * A flag mask to indicates that the application can expand over the original size.
78     * The flag is set to true if
79     * 1) Application declares its expandable in manifest file using <expandable /> or
80     * 2) The screen size is same as (320 x 480) * density.
81     * {@see compatibilityFlag}
82     */
83    private static final int EXPANDABLE = 2;
84
85    /**
86     * A flag mask to tell if the application is configured to be expandable. This differs
87     * from EXPANDABLE in that the application that is not expandable will be
88     * marked as expandable if it runs in (320x 480) * density screen size.
89     */
90    private static final int CONFIGURED_EXPANDABLE = 4;
91
92    private static final int SCALING_EXPANDABLE_MASK = SCALING_REQUIRED | EXPANDABLE;
93
94    /**
95     * Application's scale.
96     */
97    public final float applicationScale;
98
99    /**
100     * Application's inverted scale.
101     */
102    public final float applicationInvertedScale;
103
104    /**
105     * The flags from ApplicationInfo.
106     */
107    public final int appFlags;
108
109    /**
110     * Window size in Compatibility Mode, in real pixels. This is updated by
111     * {@link DisplayMetrics#updateMetrics}.
112     */
113    private int mWidth;
114    private int mHeight;
115
116    /**
117     * The x offset to center the window content. In X_SHIFT_WINDOW mode, the offset is added
118     * to the window's layout. In X_SHIFT_CONTENT/X_SHIFT_AND_CLIP_CONTENT mode, the offset
119     * is used to translate the Canvas.
120     */
121    private int mXOffset;
122
123    public CompatibilityInfo(ApplicationInfo appInfo) {
124        appFlags = appInfo.flags;
125
126        if ((appInfo.flags & ApplicationInfo.FLAG_SUPPORTS_LARGE_SCREENS) != 0) {
127            mCompatibilityFlags = EXPANDABLE | CONFIGURED_EXPANDABLE;
128        }
129
130        float packageDensityScale = -1.0f;
131        if (appInfo.supportsDensities != null) {
132            int minDiff = Integer.MAX_VALUE;
133            for (int density : appInfo.supportsDensities) {
134                if (density == ApplicationInfo.ANY_DENSITY) {
135                    packageDensityScale = 1.0f;
136                    break;
137                }
138                int tmpDiff = Math.abs(DisplayMetrics.DEVICE_DENSITY - density);
139                if (tmpDiff == 0) {
140                    packageDensityScale = 1.0f;
141                    break;
142                }
143                // prefer higher density (appScale>1.0), unless that's only option.
144                if (tmpDiff < minDiff && packageDensityScale < 1.0f) {
145                    packageDensityScale = DisplayMetrics.DEVICE_DENSITY / (float) density;
146                    minDiff = tmpDiff;
147                }
148            }
149        }
150        if (packageDensityScale > 0.0f) {
151            applicationScale = packageDensityScale;
152        } else {
153            applicationScale =
154                    DisplayMetrics.DEVICE_DENSITY / (float) DisplayMetrics.DEFAULT_DENSITY;
155        }
156        applicationInvertedScale = 1.0f / applicationScale;
157        if (applicationScale != 1.0f) {
158            mCompatibilityFlags |= SCALING_REQUIRED;
159        }
160    }
161
162    private CompatibilityInfo(int appFlags, int compFlags, float scale, float invertedScale) {
163        this.appFlags = appFlags;
164        mCompatibilityFlags = compFlags;
165        applicationScale = scale;
166        applicationInvertedScale = invertedScale;
167    }
168
169    private CompatibilityInfo() {
170        this(ApplicationInfo.FLAG_SUPPORTS_SMALL_SCREENS
171                | ApplicationInfo.FLAG_SUPPORTS_NORMAL_SCREENS
172                | ApplicationInfo.FLAG_SUPPORTS_LARGE_SCREENS,
173                EXPANDABLE | CONFIGURED_EXPANDABLE,
174                1.0f,
175                1.0f);
176    }
177
178    /**
179     * Returns the copy of this instance.
180     */
181    public CompatibilityInfo copy() {
182        CompatibilityInfo info = new CompatibilityInfo(appFlags, mCompatibilityFlags,
183                applicationScale, applicationInvertedScale);
184        info.setVisibleRect(mXOffset, mWidth, mHeight);
185        return info;
186    }
187
188    /**
189     * Sets the application's visible rect in compatibility mode.
190     * @param xOffset the application's x offset that is added to center the content.
191     * @param widthPixels the application's width in real pixels on the screen.
192     * @param heightPixels the application's height in real pixels on the screen.
193     */
194    public void setVisibleRect(int xOffset, int widthPixels, int heightPixels) {
195        this.mXOffset = xOffset;
196        mWidth = widthPixels;
197        mHeight = heightPixels;
198    }
199
200    /**
201     * Sets expandable bit in the compatibility flag.
202     */
203    public void setExpandable(boolean expandable) {
204        if (expandable) {
205            mCompatibilityFlags |= CompatibilityInfo.EXPANDABLE;
206        } else {
207            mCompatibilityFlags &= ~CompatibilityInfo.EXPANDABLE;
208        }
209    }
210
211    /**
212     * @return true if the application is configured to be expandable.
213     */
214    public boolean isConfiguredExpandable() {
215        return (mCompatibilityFlags & CompatibilityInfo.CONFIGURED_EXPANDABLE) != 0;
216    }
217
218    /**
219     * @return true if the scaling is required
220     */
221    public boolean isScalingRequired() {
222        return (mCompatibilityFlags & SCALING_REQUIRED) != 0;
223    }
224
225    @Override
226    public String toString() {
227        return "CompatibilityInfo{scale=" + applicationScale +
228                ", compatibility flag=" + mCompatibilityFlags + "}";
229    }
230
231    /**
232     * Returns the translator which can translate the coordinates of the window.
233     * There are five different types of Translator.
234     *
235     * 1) {@link CompatibilityInfo#X_SHIFT_AND_CLIP_CONTENT}
236     *   Shift and clip the content of the window at drawing time. Used for activities'
237     *   main window (with no gravity).
238     * 2) {@link CompatibilityInfo#X_SHIFT_CONTENT}
239     *   Shift the content of the window at drawing time. Used for windows that is created by
240     *   an application and expected to be aligned with the application window.
241     * 3) {@link CompatibilityInfo#X_SHIFT_WINDOW}
242     *   Create the window with adjusted x- coordinates. This is typically used
243     *   in popup window, where it has to be placed relative to main window.
244     * 4) {@link CompatibilityInfo#X_SHIFT_NONE}
245     *   No adjustment required, such as dialog.
246     * 5) Same as X_SHIFT_WINDOW, but no scaling. This is used by {@link SurfaceView}, which
247     *  does not require scaling, but its window's location has to be adjusted.
248     *
249     * @param params the window's parameter
250     */
251    public Translator getTranslator(WindowManager.LayoutParams params) {
252        if ( (mCompatibilityFlags & CompatibilityInfo.SCALING_EXPANDABLE_MASK)
253                == CompatibilityInfo.EXPANDABLE) {
254            if (DBG) Log.d(TAG, "no translation required");
255            return null;
256        }
257
258        if ((mCompatibilityFlags & CompatibilityInfo.EXPANDABLE) == 0) {
259            if ((params.flags & WindowManager.LayoutParams.FLAG_NO_COMPATIBILITY_SCALING) != 0) {
260                if (DBG) Log.d(TAG, "translation for surface view selected");
261                return new Translator(X_SHIFT_WINDOW, false, 1.0f, 1.0f);
262            } else {
263                int shiftMode;
264                if (params.gravity == Gravity.NO_GRAVITY) {
265                    // For Regular Application window
266                    shiftMode = X_SHIFT_AND_CLIP_CONTENT;
267                    if (DBG) Log.d(TAG, "shift and clip translator");
268                } else if (params.width == WindowManager.LayoutParams.FILL_PARENT) {
269                    // For Regular Application window
270                    shiftMode = X_SHIFT_CONTENT;
271                    if (DBG) Log.d(TAG, "shift content translator");
272                } else if ((params.gravity & Gravity.LEFT) != 0 && params.x > 0) {
273                    shiftMode = X_SHIFT_WINDOW;
274                    if (DBG) Log.d(TAG, "shift window translator");
275                } else {
276                    shiftMode = X_SHIFT_NONE;
277                    if (DBG) Log.d(TAG, "no content/window translator");
278                }
279                return new Translator(shiftMode);
280            }
281        } else if (isScalingRequired()) {
282            return new Translator();
283        } else {
284            return null;
285        }
286    }
287
288    /**
289     * A helper object to translate the screen and window coordinates back and forth.
290     * @hide
291     */
292    public class Translator {
293        final private int mShiftMode;
294        final public boolean scalingRequired;
295        final public float applicationScale;
296        final public float applicationInvertedScale;
297
298        private Rect mContentInsetsBuffer = null;
299        private Rect mVisibleInsets = null;
300
301        Translator(int shiftMode, boolean scalingRequired, float applicationScale,
302                float applicationInvertedScale) {
303            mShiftMode = shiftMode;
304            this.scalingRequired = scalingRequired;
305            this.applicationScale = applicationScale;
306            this.applicationInvertedScale = applicationInvertedScale;
307        }
308
309        Translator(int shiftMode) {
310            this(shiftMode,
311                    isScalingRequired(),
312                    CompatibilityInfo.this.applicationScale,
313                    CompatibilityInfo.this.applicationInvertedScale);
314        }
315
316        Translator() {
317            this(X_SHIFT_NONE);
318        }
319
320        /**
321         * Translate the screen rect to the application frame.
322         */
323        public void translateRectInScreenToAppWinFrame(Rect rect) {
324            if (rect.isEmpty()) return; // skip if the window size is empty.
325            switch (mShiftMode) {
326                case X_SHIFT_AND_CLIP_CONTENT:
327                    rect.intersect(0, 0, mWidth, mHeight);
328                    break;
329                case X_SHIFT_CONTENT:
330                    rect.intersect(0, 0, mWidth + mXOffset, mHeight);
331                    break;
332                case X_SHIFT_WINDOW:
333                case X_SHIFT_NONE:
334                    break;
335            }
336            if (scalingRequired) {
337                rect.scale(applicationInvertedScale);
338            }
339        }
340
341        /**
342         * Translate the region in window to screen.
343         */
344        public void translateRegionInWindowToScreen(Region transparentRegion) {
345            switch (mShiftMode) {
346                case X_SHIFT_AND_CLIP_CONTENT:
347                case X_SHIFT_CONTENT:
348                    transparentRegion.scale(applicationScale);
349                    transparentRegion.translate(mXOffset, 0);
350                    break;
351                case X_SHIFT_WINDOW:
352                case X_SHIFT_NONE:
353                    transparentRegion.scale(applicationScale);
354            }
355        }
356
357        /**
358         * Apply translation to the canvas that is necessary to draw the content.
359         */
360        public void translateCanvas(Canvas canvas) {
361            if (mShiftMode == X_SHIFT_CONTENT ||
362                    mShiftMode == X_SHIFT_AND_CLIP_CONTENT) {
363                // TODO: clear outside when rotation is changed.
364
365                // Translate x-offset only when the content is shifted.
366                canvas.translate(mXOffset, 0);
367            }
368            if (scalingRequired) {
369                canvas.scale(applicationScale, applicationScale);
370            }
371        }
372
373        /**
374         * Translate the motion event captured on screen to the application's window.
375         */
376        public void translateEventInScreenToAppWindow(MotionEvent event) {
377            if (mShiftMode == X_SHIFT_CONTENT ||
378                    mShiftMode == X_SHIFT_AND_CLIP_CONTENT) {
379                event.translate(-mXOffset, 0);
380            }
381            if (scalingRequired) {
382                event.scale(applicationInvertedScale);
383            }
384        }
385
386        /**
387         * Translate the window's layout parameter, from application's view to
388         * Screen's view.
389         */
390        public void translateWindowLayout(WindowManager.LayoutParams params) {
391            switch (mShiftMode) {
392                case X_SHIFT_NONE:
393                case X_SHIFT_AND_CLIP_CONTENT:
394                case X_SHIFT_CONTENT:
395                    params.scale(applicationScale);
396                    break;
397                case X_SHIFT_WINDOW:
398                    params.scale(applicationScale);
399                    params.x += mXOffset;
400                    break;
401            }
402        }
403
404        /**
405         * Translate a Rect in application's window to screen.
406         */
407        public void translateRectInAppWindowToScreen(Rect rect) {
408            // TODO Auto-generated method stub
409            if (scalingRequired) {
410                rect.scale(applicationScale);
411            }
412            switch(mShiftMode) {
413                case X_SHIFT_NONE:
414                case X_SHIFT_WINDOW:
415                    break;
416                case X_SHIFT_CONTENT:
417                case X_SHIFT_AND_CLIP_CONTENT:
418                    rect.offset(mXOffset, 0);
419                    break;
420            }
421        }
422
423        /**
424         * Translate a Rect in screen coordinates into the app window's coordinates.
425         */
426        public void translateRectInScreenToAppWindow(Rect rect) {
427            switch (mShiftMode) {
428                case X_SHIFT_NONE:
429                case X_SHIFT_WINDOW:
430                    break;
431                case X_SHIFT_CONTENT: {
432                    rect.intersects(mXOffset, 0, rect.right, rect.bottom);
433                    int dx = Math.min(mXOffset, rect.left);
434                    rect.offset(-dx, 0);
435                    break;
436                }
437                case X_SHIFT_AND_CLIP_CONTENT: {
438                    rect.intersects(mXOffset, 0, mWidth + mXOffset, mHeight);
439                    int dx = Math.min(mXOffset, rect.left);
440                    rect.offset(-dx, 0);
441                    break;
442                }
443            }
444            if (scalingRequired) {
445                rect.scale(applicationInvertedScale);
446            }
447        }
448
449        /**
450         * Translate the location of the sub window.
451         * @param params
452         */
453        public void translateLayoutParamsInAppWindowToScreen(LayoutParams params) {
454            if (scalingRequired) {
455                params.scale(applicationScale);
456            }
457            switch (mShiftMode) {
458                // the window location on these mode does not require adjustmenet.
459                case X_SHIFT_NONE:
460                case X_SHIFT_WINDOW:
461                    break;
462                case X_SHIFT_CONTENT:
463                case X_SHIFT_AND_CLIP_CONTENT:
464                    params.x += mXOffset;
465                    break;
466            }
467        }
468
469        /**
470         * Translate the content insets in application window to Screen. This uses
471         * the internal buffer for content insets to avoid extra object allocation.
472         */
473        public Rect getTranslatedContentInsets(Rect contentInsets) {
474            if (mContentInsetsBuffer == null) mContentInsetsBuffer = new Rect();
475            mContentInsetsBuffer.set(contentInsets);
476            translateRectInAppWindowToScreen(mContentInsetsBuffer);
477            return mContentInsetsBuffer;
478        }
479
480        /**
481         * Translate the visible insets in application window to Screen. This uses
482         * the internal buffer for content insets to avoid extra object allocation.
483         */
484        public Rect getTranslatedVisbileInsets(Rect visibleInsets) {
485            if (mVisibleInsets == null) mVisibleInsets = new Rect();
486            mVisibleInsets.set(visibleInsets);
487            translateRectInAppWindowToScreen(mVisibleInsets);
488            return mVisibleInsets;
489        }
490    }
491}
492