Layout.java revision 3ba16c1d9d53d520543e1bd363236453101096ec
1/*
2 * Copyright (C) 2015 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 com.android.layoutlib.bridge.impl;
18
19import com.android.ide.common.rendering.api.HardwareConfig;
20import com.android.ide.common.rendering.api.RenderResources;
21import com.android.ide.common.rendering.api.ResourceValue;
22import com.android.ide.common.rendering.api.SessionParams;
23import com.android.ide.common.rendering.api.StyleResourceValue;
24import com.android.layoutlib.bridge.Bridge;
25import com.android.layoutlib.bridge.android.BridgeContext;
26import com.android.layoutlib.bridge.bars.AppCompatActionBar;
27import com.android.layoutlib.bridge.bars.BridgeActionBar;
28import com.android.layoutlib.bridge.bars.Config;
29import com.android.layoutlib.bridge.bars.FrameworkActionBar;
30import com.android.layoutlib.bridge.bars.NavigationBar;
31import com.android.layoutlib.bridge.bars.StatusBar;
32import com.android.layoutlib.bridge.bars.TitleBar;
33import com.android.resources.Density;
34import com.android.resources.ResourceType;
35import com.android.resources.ScreenOrientation;
36
37import android.annotation.NonNull;
38import android.graphics.drawable.Drawable;
39import android.util.DisplayMetrics;
40import android.util.TypedValue;
41import android.view.View;
42import android.widget.FrameLayout;
43import android.widget.LinearLayout;
44import android.widget.RelativeLayout;
45
46import static android.view.ViewGroup.LayoutParams.MATCH_PARENT;
47import static android.widget.LinearLayout.VERTICAL;
48
49/**
50 * The Layout used to create the system decor.
51 *
52 * The layout inflated will contain a content frame where the user's layout can be inflated.
53 * <pre>
54 *  +-------------------------------------------------+---+
55 *  | Status bar                                      | N |
56 *  +-------------------------------------------------+ a |
57 *  | Title/Action bar (optional)                     | v |
58 *  +-------------------------------------------------+   |
59 *  | Content, vertical extending                     | b |
60 *  |                                                 | a |
61 *  |                                                 | r |
62 *  +-------------------------------------------------+---+
63 * </pre>
64 * or
65 * <pre>
66 *  +-------------------------------------+
67 *  | Status bar                          |
68 *  +-------------------------------------+
69 *  | Title/Action bar (optional)         |
70 *  +-------------------------------------+
71 *  | Content, vertical extending         |
72 *  |                                     |
73 *  |                                     |
74 *  +-------------------------------------+
75 *  | Nav bar                             |
76 *  +-------------------------------------+
77 * </pre>
78 *
79 */
80class Layout extends RelativeLayout {
81
82    // Theme attributes used for configuring appearance of the system decor.
83    private static final String ATTR_WINDOW_FLOATING = "windowIsFloating";
84    private static final String ATTR_WINDOW_BACKGROUND = "windowBackground";
85    private static final String ATTR_WINDOW_FULL_SCREEN = "windowFullScreen";
86    private static final String ATTR_NAV_BAR_HEIGHT = "navigation_bar_height";
87    private static final String ATTR_NAV_BAR_WIDTH = "navigation_bar_width";
88    private static final String ATTR_STATUS_BAR_HEIGHT = "status_bar_height";
89    private static final String ATTR_WINDOW_ACTION_BAR = "windowActionBar";
90    private static final String ATTR_ACTION_BAR_SIZE = "actionBarSize";
91    private static final String ATTR_WINDOW_NO_TITLE = "windowNoTitle";
92    private static final String ATTR_WINDOW_TITLE_SIZE = "windowTitleSize";
93    private static final String ATTR_WINDOW_TRANSLUCENT_STATUS = StatusBar.ATTR_TRANSLUCENT;
94    private static final String ATTR_WINDOW_TRANSLUCENT_NAV = NavigationBar.ATTR_TRANSLUCENT;
95    private static final String PREFIX_THEME_APPCOMPAT = "Theme.AppCompat";
96
97    // Default sizes
98    private static final int DEFAULT_STATUS_BAR_HEIGHT = 25;
99    private static final int DEFAULT_TITLE_BAR_HEIGHT = 25;
100    private static final int DEFAULT_NAV_BAR_SIZE = 48;
101
102    // Ids assigned to components created. This is so that we can refer to other components in
103    // layout params.
104    private static final String ID_NAV_BAR = "navBar";
105    private static final String ID_STATUS_BAR = "statusBar";
106    private static final String ID_TITLE_BAR = "titleBar";
107    // Prefix used with the above ids in order to make them unique in framework namespace.
108    private static final String ID_PREFIX = "android_layoutlib_";
109
110    /**
111     * Temporarily store the builder so that it doesn't have to be passed to all methods used
112     * during inflation.
113     */
114    private Builder mBuilder;
115
116    /**
117     * This holds user's layout.
118     */
119    private FrameLayout mContentRoot;
120
121    public Layout(@NonNull Builder builder) {
122        super(builder.mContext);
123        mBuilder = builder;
124        if (builder.mWindowBackground != null) {
125            Drawable d = ResourceHelper.getDrawable(builder.mWindowBackground, builder.mContext);
126            setBackground(d);
127        }
128
129        int simulatedPlatformVersion = getParams().getSimulatedPlatformVersion();
130        HardwareConfig hwConfig = getParams().getHardwareConfig();
131        Density density = hwConfig.getDensity();
132        boolean isRtl = Bridge.isLocaleRtl(getParams().getLocale());
133
134        NavigationBar navBar = null;
135        if (mBuilder.hasNavBar()) {
136            navBar = createNavBar(getContext(), density, isRtl, getParams().isRtlSupported(),
137                    simulatedPlatformVersion);
138        }
139
140        StatusBar statusBar = null;
141        if (builder.mStatusBarSize > 0) {
142            statusBar = createStatusBar(getContext(), density, isRtl, getParams().isRtlSupported(),
143                    simulatedPlatformVersion);
144        }
145
146        View actionBar = null;
147        TitleBar titleBar = null;
148        if (builder.mActionBarSize > 0) {
149            BridgeActionBar bar = createActionBar(getContext(), getParams());
150            mContentRoot = bar.getContentRoot();
151            actionBar = bar.getRootView();
152        } else if (mBuilder.mTitleBarSize > 0) {
153            titleBar = createTitleBar(getContext(), getParams().getAppLabel(),
154                    simulatedPlatformVersion);
155        }
156
157        addViews(titleBar, mContentRoot == null ? (mContentRoot = createContentFrame()) : actionBar,
158                statusBar, navBar);
159        // Done with the builder. Don't hold a reference to it.
160        mBuilder = null;
161     }
162
163    @NonNull
164    private FrameLayout createContentFrame() {
165        FrameLayout contentRoot = new FrameLayout(getContext());
166        LayoutParams params = createLayoutParams(MATCH_PARENT, MATCH_PARENT);
167        int rule = mBuilder.isNavBarVertical() ? START_OF : ABOVE;
168        if (mBuilder.hasNavBar() && mBuilder.solidBars()) {
169            params.addRule(rule, getId(ID_NAV_BAR));
170        }
171        int below = -1;
172        if (mBuilder.mActionBarSize <= 0 && mBuilder.mTitleBarSize > 0) {
173            below = getId(ID_TITLE_BAR);
174        } else if (mBuilder.hasStatusBar() && mBuilder.solidBars()) {
175            below = getId(ID_STATUS_BAR);
176        }
177        if (below != -1) {
178            params.addRule(BELOW, below);
179        }
180        contentRoot.setLayoutParams(params);
181        return contentRoot;
182    }
183
184    @NonNull
185    private LayoutParams createLayoutParams(int width, int height) {
186        DisplayMetrics metrics = getContext().getResources().getDisplayMetrics();
187        if (width > 0) {
188            width = (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, width, metrics);
189        }
190        if (height > 0) {
191            height = (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, height, metrics);
192        }
193        return new LayoutParams(width, height);
194    }
195
196    @NonNull
197    public FrameLayout getContentRoot() {
198        return mContentRoot;
199    }
200
201    @NonNull
202    private SessionParams getParams() {
203        return mBuilder.mParams;
204    }
205
206    @NonNull
207    @Override
208    public BridgeContext getContext(){
209        return (BridgeContext) super.getContext();
210    }
211
212    /**
213     * @param isRtl    whether the current locale is an RTL locale.
214     * @param isRtlSupported    whether the applications supports RTL (i.e. has supportsRtl=true
215     * in the manifest and targetSdkVersion >= 17.
216     */
217    @NonNull
218    private StatusBar createStatusBar(BridgeContext context, Density density, boolean isRtl,
219            boolean isRtlSupported, int simulatedPlatformVersion) {
220        StatusBar statusBar =
221                new StatusBar(context, density, isRtl, isRtlSupported, simulatedPlatformVersion);
222        LayoutParams params = createLayoutParams(MATCH_PARENT, mBuilder.mStatusBarSize);
223        if (mBuilder.isNavBarVertical()) {
224            params.addRule(START_OF, getId(ID_NAV_BAR));
225        }
226        statusBar.setLayoutParams(params);
227        statusBar.setId(getId(ID_STATUS_BAR));
228        return statusBar;
229    }
230
231    private BridgeActionBar createActionBar(@NonNull BridgeContext context,
232            @NonNull SessionParams params) {
233        BridgeActionBar actionBar;
234        if (mBuilder.isThemeAppCompat()) {
235            actionBar = new AppCompatActionBar(context, params);
236        } else {
237            actionBar = new FrameworkActionBar(context, params);
238        }
239        LayoutParams layoutParams = createLayoutParams(MATCH_PARENT, MATCH_PARENT);
240        int rule = mBuilder.isNavBarVertical() ? START_OF : ABOVE;
241        if (mBuilder.hasNavBar() && mBuilder.solidBars()) {
242            layoutParams.addRule(rule, getId(ID_NAV_BAR));
243        }
244        if (mBuilder.hasStatusBar() && mBuilder.solidBars()) {
245            layoutParams.addRule(BELOW, getId(ID_STATUS_BAR));
246        }
247        actionBar.getRootView().setLayoutParams(layoutParams);
248        actionBar.createMenuPopup();
249        return actionBar;
250    }
251
252    @NonNull
253    private TitleBar createTitleBar(BridgeContext context, String title,
254            int simulatedPlatformVersion) {
255        TitleBar titleBar = new TitleBar(context, title, simulatedPlatformVersion);
256        LayoutParams params = createLayoutParams(MATCH_PARENT, mBuilder.mTitleBarSize);
257        if (mBuilder.hasStatusBar() && mBuilder.solidBars()) {
258            params.addRule(BELOW, getId(ID_STATUS_BAR));
259        }
260        if (mBuilder.isNavBarVertical() && mBuilder.solidBars()) {
261            params.addRule(START_OF, getId(ID_NAV_BAR));
262        }
263        titleBar.setLayoutParams(params);
264        titleBar.setId(getId(ID_TITLE_BAR));
265        return titleBar;
266    }
267
268    /**
269     * @param isRtl    whether the current locale is an RTL locale.
270     * @param isRtlSupported    whether the applications supports RTL (i.e. has supportsRtl=true
271     * in the manifest and targetSdkVersion >= 17.
272     */
273    @NonNull
274    private NavigationBar createNavBar(BridgeContext context, Density density, boolean isRtl,
275            boolean isRtlSupported, int simulatedPlatformVersion) {
276        int orientation = mBuilder.mNavBarOrientation;
277        int size = mBuilder.mNavBarSize;
278        NavigationBar navBar = new NavigationBar(context, density, orientation, isRtl,
279                isRtlSupported, simulatedPlatformVersion);
280        boolean isVertical = mBuilder.isNavBarVertical();
281        int w = isVertical ? size : MATCH_PARENT;
282        int h = isVertical ? MATCH_PARENT : size;
283        LayoutParams params = createLayoutParams(w, h);
284        params.addRule(isVertical ? ALIGN_PARENT_END : ALIGN_PARENT_BOTTOM);
285        navBar.setLayoutParams(params);
286        navBar.setId(getId(ID_NAV_BAR));
287        return navBar;
288    }
289
290    private void addViews(@NonNull View... views) {
291        for (View view : views) {
292            if (view != null) {
293                addView(view);
294            }
295        }
296    }
297
298    private int getId(String name) {
299        return Bridge.getResourceId(ResourceType.ID, ID_PREFIX + name);
300    }
301
302    /**
303     * A helper class to help initialize the Layout.
304     */
305    static class Builder {
306        @NonNull
307        private final SessionParams mParams;
308        @NonNull
309        private final BridgeContext mContext;
310        private final RenderResources mResources;
311
312        private final boolean mWindowIsFloating;
313        private ResourceValue mWindowBackground;
314        private int mStatusBarSize;
315        private int mNavBarSize;
316        private int mNavBarOrientation;
317        private int mActionBarSize;
318        private int mTitleBarSize;
319        private boolean mTranslucentStatus;
320        private boolean mTranslucentNav;
321
322        private Boolean mIsThemeAppCompat;
323
324        public Builder(@NonNull SessionParams params, @NonNull BridgeContext context) {
325            mParams = params;
326            mContext = context;
327            mResources = mParams.getResources();
328            mWindowIsFloating = ResourceHelper.getBooleanThemeValue(mResources, ATTR_WINDOW_FLOATING, true, true);
329
330            findBackground();
331            findStatusBar();
332            findActionBar();
333            findNavBar();
334        }
335
336        private void findBackground() {
337            if (!mParams.isBgColorOverridden()) {
338                mWindowBackground = mResources.findItemInTheme(ATTR_WINDOW_BACKGROUND, true);
339                mWindowBackground = mResources.resolveResValue(mWindowBackground);
340            }
341        }
342
343        private void findStatusBar() {
344            boolean windowFullScreen =
345                    ResourceHelper.getBooleanThemeValue(mResources, ATTR_WINDOW_FULL_SCREEN, true, false);
346            if (!windowFullScreen && !mWindowIsFloating) {
347                mStatusBarSize =
348                        getDimension(ATTR_STATUS_BAR_HEIGHT, true, DEFAULT_STATUS_BAR_HEIGHT);
349                mTranslucentStatus = ResourceHelper.getBooleanThemeValue(mResources,
350                        ATTR_WINDOW_TRANSLUCENT_STATUS, true, false);
351            }
352        }
353
354        private void  findActionBar() {
355            if (mWindowIsFloating) {
356                return;
357            }
358            // Check if an actionbar is needed
359            boolean windowActionBar = ResourceHelper.getBooleanThemeValue(mResources, ATTR_WINDOW_ACTION_BAR,
360                    !isThemeAppCompat(), true);
361            if (windowActionBar) {
362                mActionBarSize = getDimension(ATTR_ACTION_BAR_SIZE, true, DEFAULT_TITLE_BAR_HEIGHT);
363            } else {
364                // Maybe the gingerbread era title bar is needed
365                boolean windowNoTitle =
366                        ResourceHelper.getBooleanThemeValue(mResources, ATTR_WINDOW_NO_TITLE, true, false);
367                if (!windowNoTitle) {
368                    mTitleBarSize =
369                            getDimension(ATTR_WINDOW_TITLE_SIZE, true, DEFAULT_TITLE_BAR_HEIGHT);
370                }
371            }
372        }
373
374        private void findNavBar() {
375            if (hasSoftwareButtons() && !mWindowIsFloating) {
376
377                // get orientation
378                HardwareConfig hwConfig = mParams.getHardwareConfig();
379                boolean barOnBottom = true;
380
381                if (hwConfig.getOrientation() == ScreenOrientation.LANDSCAPE) {
382                    int shortSize = hwConfig.getScreenHeight();
383                    int shortSizeDp = shortSize * DisplayMetrics.DENSITY_DEFAULT /
384                            hwConfig.getDensity().getDpiValue();
385
386                    // 0-599dp: "phone" UI with bar on the side
387                    // 600+dp: "tablet" UI with bar on the bottom
388                    barOnBottom = shortSizeDp >= 600;
389                }
390
391                mNavBarOrientation = barOnBottom ? LinearLayout.HORIZONTAL : VERTICAL;
392                mNavBarSize = getDimension(barOnBottom ? ATTR_NAV_BAR_HEIGHT : ATTR_NAV_BAR_WIDTH,
393                        true, DEFAULT_NAV_BAR_SIZE);
394                mTranslucentNav = ResourceHelper.getBooleanThemeValue(mResources,
395                        ATTR_WINDOW_TRANSLUCENT_NAV, true, false);
396            }
397        }
398
399        private int getDimension(String attr, boolean isFramework, int defaultValue) {
400            ResourceValue value = mResources.findItemInTheme(attr, isFramework);
401            value = mResources.resolveResValue(value);
402            if (value != null) {
403                TypedValue typedValue = ResourceHelper.getValue(attr, value.getValue(), true);
404                if (typedValue != null) {
405                    return (int) typedValue.getDimension(mContext.getMetrics());
406                }
407            }
408            return defaultValue;
409        }
410
411        private boolean hasSoftwareButtons() {
412            return mParams.getHardwareConfig().hasSoftwareButtons();
413        }
414
415        private boolean isThemeAppCompat() {
416            // If a cached value exists, return it.
417            if (mIsThemeAppCompat != null) {
418                return mIsThemeAppCompat;
419            }
420            // Ideally, we should check if the corresponding activity extends
421            // android.support.v7.app.ActionBarActivity, and not care about the theme name at all.
422            StyleResourceValue defaultTheme = mResources.getDefaultTheme();
423            // We can't simply check for parent using resources.themeIsParentOf() since the
424            // inheritance structure isn't really what one would expect. The first common parent
425            // between Theme.AppCompat.Light and Theme.AppCompat is Theme.Material (for v21).
426            boolean isThemeAppCompat = false;
427            for (int i = 0; i < 50; i++) {
428                if (defaultTheme == null) {
429                    break;
430                }
431                // for loop ensures that we don't run into cyclic theme inheritance.
432                if (defaultTheme.getName().startsWith(PREFIX_THEME_APPCOMPAT)) {
433                    isThemeAppCompat = true;
434                    break;
435                }
436                defaultTheme = mResources.getParent(defaultTheme);
437            }
438            mIsThemeAppCompat = isThemeAppCompat;
439            return isThemeAppCompat;
440        }
441
442        /**
443         * Return true if the status bar or nav bar are present, they are not translucent (i.e
444         * content doesn't overlap with them).
445         */
446        private boolean solidBars() {
447            return !(hasNavBar() && mTranslucentNav) && !(hasStatusBar() && mTranslucentStatus);
448        }
449
450        private boolean hasNavBar() {
451            return Config.showOnScreenNavBar(mParams.getSimulatedPlatformVersion()) &&
452                    hasSoftwareButtons() && mNavBarSize > 0;
453        }
454
455        private boolean hasStatusBar() {
456            return mStatusBarSize > 0;
457        }
458
459        /**
460         * Return true if the nav bar is present and is vertical.
461         */
462        private boolean isNavBarVertical() {
463            return hasNavBar() && mNavBarOrientation == VERTICAL;
464        }
465    }
466}
467