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