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