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