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