2 * Copyright (C) 2015 The Android Open Source Project
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
8 * http://www.apache.org/licenses/LICENSE-2.0
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.
17 package com
.android
.layoutlib
.bridge
.impl
;
19 import com
.android
.ide
.common
.rendering
.api
.HardwareConfig
;
20 import com
.android
.ide
.common
.rendering
.api
.RenderResources
;
21 import com
.android
.ide
.common
.rendering
.api
.ResourceValue
;
22 import com
.android
.ide
.common
.rendering
.api
.SessionParams
;
23 import com
.android
.ide
.common
.rendering
.api
.StyleResourceValue
;
24 import com
.android
.layoutlib
.bridge
.Bridge
;
25 import com
.android
.layoutlib
.bridge
.android
.BridgeContext
;
26 import com
.android
.layoutlib
.bridge
.bars
.AppCompatActionBar
;
27 import com
.android
.layoutlib
.bridge
.bars
.BridgeActionBar
;
28 import com
.android
.layoutlib
.bridge
.bars
.Config
;
29 import com
.android
.layoutlib
.bridge
.bars
.FrameworkActionBar
;
30 import com
.android
.layoutlib
.bridge
.bars
.NavigationBar
;
31 import com
.android
.layoutlib
.bridge
.bars
.StatusBar
;
32 import com
.android
.layoutlib
.bridge
.bars
.TitleBar
;
33 import com
.android
.resources
.Density
;
34 import com
.android
.resources
.ResourceType
;
35 import com
.android
.resources
.ScreenOrientation
;
37 import android
.annotation
.NonNull
;
38 import android
.graphics
.drawable
.Drawable
;
39 import android
.util
.DisplayMetrics
;
40 import android
.util
.TypedValue
;
41 import android
.view
.View
;
42 import android
.widget
.FrameLayout
;
43 import android
.widget
.LinearLayout
;
44 import android
.widget
.RelativeLayout
;
46 import static android
.view
.ViewGroup
.LayoutParams
.MATCH_PARENT
;
47 import static android
.widget
.LinearLayout
.VERTICAL
;
48 import static com
.android
.layoutlib
.bridge
.impl
.ResourceHelper
.getBooleanThemeValue
;
51 * The Layout used to create the system decor.
53 * The layout inflated will contain a content frame where the user's layout can be inflated.
55 * +-------------------------------------------------+---+
57 * +-------------------------------------------------+ a |
58 * | Title/Action bar (optional) | v |
59 * +-------------------------------------------------+ |
60 * | Content, vertical extending | b |
63 * +-------------------------------------------------+---+
67 * +-------------------------------------+
69 * +-------------------------------------+
70 * | Title/Action bar (optional) |
71 * +-------------------------------------+
72 * | Content, vertical extending |
75 * +-------------------------------------+
77 * +-------------------------------------+
81 class Layout
extends RelativeLayout
{
83 // Theme attributes used for configuring appearance of the system decor.
84 private static final String ATTR_WINDOW_FLOATING
= "windowIsFloating";
85 private static final String ATTR_WINDOW_BACKGROUND
= "windowBackground";
86 private static final String ATTR_WINDOW_FULL_SCREEN
= "windowFullscreen";
87 private static final String ATTR_NAV_BAR_HEIGHT
= "navigation_bar_height";
88 private static final String ATTR_NAV_BAR_WIDTH
= "navigation_bar_width";
89 private static final String ATTR_STATUS_BAR_HEIGHT
= "status_bar_height";
90 private static final String ATTR_WINDOW_ACTION_BAR
= "windowActionBar";
91 private static final String ATTR_ACTION_BAR_SIZE
= "actionBarSize";
92 private static final String ATTR_WINDOW_NO_TITLE
= "windowNoTitle";
93 private static final String ATTR_WINDOW_TITLE_SIZE
= "windowTitleSize";
94 private static final String ATTR_WINDOW_TRANSLUCENT_STATUS
= StatusBar
.ATTR_TRANSLUCENT
;
95 private static final String ATTR_WINDOW_TRANSLUCENT_NAV
= NavigationBar
.ATTR_TRANSLUCENT
;
96 private static final String PREFIX_THEME_APPCOMPAT
= "Theme.AppCompat";
99 private static final int DEFAULT_STATUS_BAR_HEIGHT
= 25;
100 private static final int DEFAULT_TITLE_BAR_HEIGHT
= 25;
101 private static final int DEFAULT_NAV_BAR_SIZE
= 48;
103 // Ids assigned to components created. This is so that we can refer to other components in
105 private static final String ID_NAV_BAR
= "navBar";
106 private static final String ID_STATUS_BAR
= "statusBar";
107 private static final String ID_TITLE_BAR
= "titleBar";
108 // Prefix used with the above ids in order to make them unique in framework namespace.
109 private static final String ID_PREFIX
= "android_layoutlib_";
112 * Temporarily store the builder so that it doesn't have to be passed to all methods used
115 private Builder mBuilder
;
118 * This holds user's layout.
120 private FrameLayout mContentRoot
;
122 public Layout(@NonNull Builder builder
) {
123 super(builder
.mContext
);
125 if (builder
.mWindowBackground
!= null) {
126 Drawable d
= ResourceHelper
.getDrawable(builder
.mWindowBackground
, builder
.mContext
);
130 int simulatedPlatformVersion
= getParams().getSimulatedPlatformVersion();
131 HardwareConfig hwConfig
= getParams().getHardwareConfig();
132 Density density
= hwConfig
.getDensity();
133 boolean isRtl
= Bridge
.isLocaleRtl(getParams().getLocale());
134 setLayoutDirection(isRtl? LAYOUT_DIRECTION_RTL
: LAYOUT_DIRECTION_LTR
);
136 NavigationBar navBar
= null;
137 if (mBuilder
.hasNavBar()) {
138 navBar
= createNavBar(getContext(), density
, isRtl
, getParams().isRtlSupported(),
139 simulatedPlatformVersion
);
142 StatusBar statusBar
= null;
143 if (builder
.mStatusBarSize
> 0) {
144 statusBar
= createStatusBar(getContext(), density
, isRtl
, getParams().isRtlSupported(),
145 simulatedPlatformVersion
);
148 View actionBar
= null;
149 TitleBar titleBar
= null;
150 if (builder
.mActionBarSize
> 0) {
151 BridgeActionBar bar
= createActionBar(getContext(), getParams());
152 mContentRoot
= bar
.getContentRoot();
153 actionBar
= bar
.getRootView();
154 } else if (mBuilder
.mTitleBarSize
> 0) {
155 titleBar
= createTitleBar(getContext(), getParams().getAppLabel(),
156 simulatedPlatformVersion
);
159 addViews(titleBar
, mContentRoot
== null ?
(mContentRoot
= createContentFrame()) : actionBar
,
161 // Done with the builder. Don't hold a reference to it.
166 private FrameLayout
createContentFrame() {
167 FrameLayout contentRoot
= new FrameLayout(getContext());
168 LayoutParams params
= createLayoutParams(MATCH_PARENT
, MATCH_PARENT
);
169 int rule
= mBuilder
.isNavBarVertical() ? START_OF
: ABOVE
;
170 if (mBuilder
.hasNavBar() && mBuilder
.solidBars()) {
171 params
.addRule(rule
, getId(ID_NAV_BAR
));
174 if (mBuilder
.mActionBarSize
<= 0 && mBuilder
.mTitleBarSize
> 0) {
175 below
= getId(ID_TITLE_BAR
);
176 } else if (mBuilder
.hasStatusBar() && mBuilder
.solidBars()) {
177 below
= getId(ID_STATUS_BAR
);
180 params
.addRule(BELOW
, below
);
182 contentRoot
.setLayoutParams(params
);
187 private LayoutParams
createLayoutParams(int width
, int height
) {
188 DisplayMetrics metrics
= getContext().getResources().getDisplayMetrics();
190 width
= (int) TypedValue
.applyDimension(TypedValue
.COMPLEX_UNIT_DIP
, width
, metrics
);
193 height
= (int) TypedValue
.applyDimension(TypedValue
.COMPLEX_UNIT_DIP
, height
, metrics
);
195 return new LayoutParams(width
, height
);
199 public FrameLayout
getContentRoot() {
204 private SessionParams
getParams() {
205 return mBuilder
.mParams
;
210 public BridgeContext
getContext(){
211 return (BridgeContext
) super.getContext();
215 * @param isRtl whether the current locale is an RTL locale.
216 * @param isRtlSupported whether the applications supports RTL (i.e. has supportsRtl=true
217 * in the manifest and targetSdkVersion >= 17.
220 private StatusBar
createStatusBar(BridgeContext context
, Density density
, boolean isRtl
,
221 boolean isRtlSupported
, int simulatedPlatformVersion
) {
222 StatusBar statusBar
=
223 new StatusBar(context
, density
, isRtl
, isRtlSupported
, simulatedPlatformVersion
);
224 LayoutParams params
= createLayoutParams(MATCH_PARENT
, mBuilder
.mStatusBarSize
);
225 if (mBuilder
.isNavBarVertical()) {
226 params
.addRule(START_OF
, getId(ID_NAV_BAR
));
228 statusBar
.setLayoutParams(params
);
229 statusBar
.setId(getId(ID_STATUS_BAR
));
233 private BridgeActionBar
createActionBar(@NonNull BridgeContext context
,
234 @NonNull SessionParams params
) {
235 BridgeActionBar actionBar
;
236 if (mBuilder
.isThemeAppCompat()) {
237 actionBar
= new AppCompatActionBar(context
, params
);
239 actionBar
= new FrameworkActionBar(context
, params
);
241 LayoutParams layoutParams
= createLayoutParams(MATCH_PARENT
, MATCH_PARENT
);
242 int rule
= mBuilder
.isNavBarVertical() ? START_OF
: ABOVE
;
243 if (mBuilder
.hasNavBar() && mBuilder
.solidBars()) {
244 layoutParams
.addRule(rule
, getId(ID_NAV_BAR
));
246 if (mBuilder
.hasStatusBar() && mBuilder
.solidBars()) {
247 layoutParams
.addRule(BELOW
, getId(ID_STATUS_BAR
));
249 actionBar
.getRootView().setLayoutParams(layoutParams
);
250 actionBar
.createMenuPopup();
255 private TitleBar
createTitleBar(BridgeContext context
, String title
,
256 int simulatedPlatformVersion
) {
257 TitleBar titleBar
= new TitleBar(context
, title
, simulatedPlatformVersion
);
258 LayoutParams params
= createLayoutParams(MATCH_PARENT
, mBuilder
.mTitleBarSize
);
259 if (mBuilder
.hasStatusBar() && mBuilder
.solidBars()) {
260 params
.addRule(BELOW
, getId(ID_STATUS_BAR
));
262 if (mBuilder
.isNavBarVertical() && mBuilder
.solidBars()) {
263 params
.addRule(START_OF
, getId(ID_NAV_BAR
));
265 titleBar
.setLayoutParams(params
);
266 titleBar
.setId(getId(ID_TITLE_BAR
));
271 * @param isRtl whether the current locale is an RTL locale.
272 * @param isRtlSupported whether the applications supports RTL (i.e. has supportsRtl=true
273 * in the manifest and targetSdkVersion >= 17.
276 private NavigationBar
createNavBar(BridgeContext context
, Density density
, boolean isRtl
,
277 boolean isRtlSupported
, int simulatedPlatformVersion
) {
278 int orientation
= mBuilder
.mNavBarOrientation
;
279 int size
= mBuilder
.mNavBarSize
;
280 NavigationBar navBar
= new NavigationBar(context
, density
, orientation
, isRtl
,
281 isRtlSupported
, simulatedPlatformVersion
);
282 boolean isVertical
= mBuilder
.isNavBarVertical();
283 int w
= isVertical ? size
: MATCH_PARENT
;
284 int h
= isVertical ? MATCH_PARENT
: size
;
285 LayoutParams params
= createLayoutParams(w
, h
);
286 params
.addRule(isVertical ? ALIGN_PARENT_END
: ALIGN_PARENT_BOTTOM
);
287 navBar
.setLayoutParams(params
);
288 navBar
.setId(getId(ID_NAV_BAR
));
292 private void addViews(@NonNull View
... views
) {
293 for (View view
: views
) {
300 private int getId(String name
) {
301 return Bridge
.getResourceId(ResourceType
.ID
, ID_PREFIX
+ name
);
305 * A helper class to help initialize the Layout.
307 static class Builder
{
309 private final SessionParams mParams
;
311 private final BridgeContext mContext
;
312 private final RenderResources mResources
;
314 private final boolean mWindowIsFloating
;
315 private ResourceValue mWindowBackground
;
316 private int mStatusBarSize
;
317 private int mNavBarSize
;
318 private int mNavBarOrientation
;
319 private int mActionBarSize
;
320 private int mTitleBarSize
;
321 private boolean mTranslucentStatus
;
322 private boolean mTranslucentNav
;
324 private Boolean mIsThemeAppCompat
;
326 public Builder(@NonNull SessionParams params
, @NonNull BridgeContext context
) {
329 mResources
= mParams
.getResources();
330 mWindowIsFloating
= getBooleanThemeValue(mResources
, ATTR_WINDOW_FLOATING
, true, true);
334 if (!mParams
.isForceNoDecor()) {
341 private void findBackground() {
342 if (!mParams
.isBgColorOverridden()) {
343 mWindowBackground
= mResources
.findItemInTheme(ATTR_WINDOW_BACKGROUND
, true);
344 mWindowBackground
= mResources
.resolveResValue(mWindowBackground
);
348 private void findStatusBar() {
349 boolean windowFullScreen
=
350 getBooleanThemeValue(mResources
, ATTR_WINDOW_FULL_SCREEN
, true, false);
351 if (!windowFullScreen
&& !mWindowIsFloating
) {
353 getDimension(ATTR_STATUS_BAR_HEIGHT
, true, DEFAULT_STATUS_BAR_HEIGHT
);
354 mTranslucentStatus
= getBooleanThemeValue(mResources
,
355 ATTR_WINDOW_TRANSLUCENT_STATUS
, true, false);
359 private void findActionBar() {
360 if (mWindowIsFloating
) {
363 // Check if an actionbar is needed
364 boolean windowActionBar
= getBooleanThemeValue(mResources
, ATTR_WINDOW_ACTION_BAR
,
365 !isThemeAppCompat(), true);
366 if (windowActionBar
) {
367 mActionBarSize
= getDimension(ATTR_ACTION_BAR_SIZE
, true, DEFAULT_TITLE_BAR_HEIGHT
);
369 // Maybe the gingerbread era title bar is needed
370 boolean windowNoTitle
=
371 getBooleanThemeValue(mResources
, ATTR_WINDOW_NO_TITLE
, true, false);
372 if (!windowNoTitle
) {
374 getDimension(ATTR_WINDOW_TITLE_SIZE
, true, DEFAULT_TITLE_BAR_HEIGHT
);
379 private void findNavBar() {
380 if (hasSoftwareButtons() && !mWindowIsFloating
) {
383 HardwareConfig hwConfig
= mParams
.getHardwareConfig();
384 boolean barOnBottom
= true;
386 if (hwConfig
.getOrientation() == ScreenOrientation
.LANDSCAPE
) {
387 int shortSize
= hwConfig
.getScreenHeight();
388 int shortSizeDp
= shortSize
* DisplayMetrics
.DENSITY_DEFAULT
/
389 hwConfig
.getDensity().getDpiValue();
391 // 0-599dp: "phone" UI with bar on the side
392 // 600+dp: "tablet" UI with bar on the bottom
393 barOnBottom
= shortSizeDp
>= 600;
396 mNavBarOrientation
= barOnBottom ? LinearLayout
.HORIZONTAL
: VERTICAL
;
397 mNavBarSize
= getDimension(barOnBottom ? ATTR_NAV_BAR_HEIGHT
: ATTR_NAV_BAR_WIDTH
,
398 true, DEFAULT_NAV_BAR_SIZE
);
399 mTranslucentNav
= getBooleanThemeValue(mResources
,
400 ATTR_WINDOW_TRANSLUCENT_NAV
, true, false);
404 private int getDimension(String attr
, boolean isFramework
, int defaultValue
) {
405 ResourceValue value
= mResources
.findItemInTheme(attr
, isFramework
);
406 value
= mResources
.resolveResValue(value
);
408 TypedValue typedValue
= ResourceHelper
.getValue(attr
, value
.getValue(), true);
409 if (typedValue
!= null) {
410 return (int) typedValue
.getDimension(mContext
.getMetrics());
416 private boolean hasSoftwareButtons() {
417 return mParams
.getHardwareConfig().hasSoftwareButtons();
420 private boolean isThemeAppCompat() {
421 // If a cached value exists, return it.
422 if (mIsThemeAppCompat
!= null) {
423 return mIsThemeAppCompat
;
425 // Ideally, we should check if the corresponding activity extends
426 // android.support.v7.app.ActionBarActivity, and not care about the theme name at all.
427 StyleResourceValue defaultTheme
= mResources
.getDefaultTheme();
428 // We can't simply check for parent using resources.themeIsParentOf() since the
429 // inheritance structure isn't really what one would expect. The first common parent
430 // between Theme.AppCompat.Light and Theme.AppCompat is Theme.Material (for v21).
431 boolean isThemeAppCompat
= false;
432 for (int i
= 0; i
< 50; i
++) {
433 if (defaultTheme
== null) {
436 // for loop ensures that we don't run into cyclic theme inheritance.
437 if (defaultTheme
.getName().startsWith(PREFIX_THEME_APPCOMPAT
)) {
438 isThemeAppCompat
= true;
441 defaultTheme
= mResources
.getParent(defaultTheme
);
443 mIsThemeAppCompat
= isThemeAppCompat
;
444 return isThemeAppCompat
;
448 * Return true if the status bar or nav bar are present, they are not translucent (i.e
449 * content doesn't overlap with them).
451 private boolean solidBars() {
452 return !(hasNavBar() && mTranslucentNav
) && !(hasStatusBar() && mTranslucentStatus
);
455 private boolean hasNavBar() {
456 return Config
.showOnScreenNavBar(mParams
.getSimulatedPlatformVersion()) &&
457 hasSoftwareButtons() && mNavBarSize
> 0;
460 private boolean hasStatusBar() {
461 return mStatusBarSize
> 0;
465 * Return true if the nav bar is present and is vertical.
467 private boolean isNavBarVertical() {
468 return hasNavBar() && mNavBarOrientation
== VERTICAL
;