Roll android_tools support library to 25.1.0
[android_tools.git] / sdk / sources / android-23 / com / android / layoutlib / bridge / impl / Layout.java
blobcbd041593d9504850d0d694da5af099309e90a6a
1 /*
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;
50 /**
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.
54 * <pre>
55 * +-------------------------------------------------+---+
56 * | Status bar | N |
57 * +-------------------------------------------------+ a |
58 * | Title/Action bar (optional) | v |
59 * +-------------------------------------------------+ |
60 * | Content, vertical extending | b |
61 * | | a |
62 * | | r |
63 * +-------------------------------------------------+---+
64 * </pre>
65 * or
66 * <pre>
67 * +-------------------------------------+
68 * | Status bar |
69 * +-------------------------------------+
70 * | Title/Action bar (optional) |
71 * +-------------------------------------+
72 * | Content, vertical extending |
73 * | |
74 * | |
75 * +-------------------------------------+
76 * | Nav bar |
77 * +-------------------------------------+
78 * </pre>
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";
98 // Default sizes
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
104 // layout params.
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
113 * during inflation.
115 private Builder mBuilder;
118 * This holds user's layout.
120 private FrameLayout mContentRoot;
122 public Layout(@NonNull Builder builder) {
123 super(builder.mContext);
124 mBuilder = builder;
125 if (builder.mWindowBackground != null) {
126 Drawable d = ResourceHelper.getDrawable(builder.mWindowBackground, builder.mContext);
127 setBackground(d);
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,
160 statusBar, navBar);
161 // Done with the builder. Don't hold a reference to it.
162 mBuilder = null;
165 @NonNull
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));
173 int below = -1;
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);
179 if (below != -1) {
180 params.addRule(BELOW, below);
182 contentRoot.setLayoutParams(params);
183 return contentRoot;
186 @NonNull
187 private LayoutParams createLayoutParams(int width, int height) {
188 DisplayMetrics metrics = getContext().getResources().getDisplayMetrics();
189 if (width > 0) {
190 width = (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, width, metrics);
192 if (height > 0) {
193 height = (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, height, metrics);
195 return new LayoutParams(width, height);
198 @NonNull
199 public FrameLayout getContentRoot() {
200 return mContentRoot;
203 @NonNull
204 private SessionParams getParams() {
205 return mBuilder.mParams;
208 @NonNull
209 @Override
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.
219 @NonNull
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));
230 return statusBar;
233 private BridgeActionBar createActionBar(@NonNull BridgeContext context,
234 @NonNull SessionParams params) {
235 BridgeActionBar actionBar;
236 if (mBuilder.isThemeAppCompat()) {
237 actionBar = new AppCompatActionBar(context, params);
238 } else {
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();
251 return actionBar;
254 @NonNull
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));
267 return titleBar;
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.
275 @NonNull
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));
289 return navBar;
292 private void addViews(@NonNull View... views) {
293 for (View view : views) {
294 if (view != null) {
295 addView(view);
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 {
308 @NonNull
309 private final SessionParams mParams;
310 @NonNull
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) {
327 mParams = params;
328 mContext = context;
329 mResources = mParams.getResources();
330 mWindowIsFloating = getBooleanThemeValue(mResources, ATTR_WINDOW_FLOATING, true, true);
332 findBackground();
334 if (!mParams.isForceNoDecor()) {
335 findStatusBar();
336 findActionBar();
337 findNavBar();
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) {
352 mStatusBarSize =
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) {
361 return;
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);
368 } else {
369 // Maybe the gingerbread era title bar is needed
370 boolean windowNoTitle =
371 getBooleanThemeValue(mResources, ATTR_WINDOW_NO_TITLE, true, false);
372 if (!windowNoTitle) {
373 mTitleBarSize =
374 getDimension(ATTR_WINDOW_TITLE_SIZE, true, DEFAULT_TITLE_BAR_HEIGHT);
379 private void findNavBar() {
380 if (hasSoftwareButtons() && !mWindowIsFloating) {
382 // get orientation
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);
407 if (value != null) {
408 TypedValue typedValue = ResourceHelper.getValue(attr, value.getValue(), true);
409 if (typedValue != null) {
410 return (int) typedValue.getDimension(mContext.getMetrics());
413 return defaultValue;
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) {
434 break;
436 // for loop ensures that we don't run into cyclic theme inheritance.
437 if (defaultTheme.getName().startsWith(PREFIX_THEME_APPCOMPAT)) {
438 isThemeAppCompat = true;
439 break;
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;