1/* 2 * Copyright (C) 2012 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.internal.widget; 18 19import android.graphics.Canvas; 20import android.graphics.drawable.Drawable; 21import android.os.Build; 22import android.view.ViewGroup; 23import com.android.internal.app.ActionBarImpl; 24 25import android.content.Context; 26import android.content.res.TypedArray; 27import android.graphics.Rect; 28import android.util.AttributeSet; 29import android.view.View; 30 31/** 32 * Special layout for the containing of an overlay action bar (and its 33 * content) to correctly handle fitting system windows when the content 34 * has request that its layout ignore them. 35 */ 36public class ActionBarOverlayLayout extends ViewGroup { 37 private static final String TAG = "ActionBarOverlayLayout"; 38 39 private int mActionBarHeight; 40 private ActionBarImpl mActionBar; 41 private int mWindowVisibility = View.VISIBLE; 42 43 // The main UI elements that we handle the layout of. 44 private View mContent; 45 private View mActionBarBottom; 46 private ActionBarContainer mActionBarTop; 47 48 // Some interior UI elements. 49 private ActionBarView mActionBarView; 50 51 // Content overlay drawable - generally the action bar's shadow 52 private Drawable mWindowContentOverlay; 53 private boolean mIgnoreWindowContentOverlay; 54 55 private boolean mOverlayMode; 56 private int mLastSystemUiVisibility; 57 private final Rect mBaseContentInsets = new Rect(); 58 private final Rect mLastBaseContentInsets = new Rect(); 59 private final Rect mContentInsets = new Rect(); 60 private final Rect mBaseInnerInsets = new Rect(); 61 private final Rect mInnerInsets = new Rect(); 62 private final Rect mLastInnerInsets = new Rect(); 63 64 static final int[] ATTRS = new int [] { 65 com.android.internal.R.attr.actionBarSize, 66 com.android.internal.R.attr.windowContentOverlay 67 }; 68 69 public ActionBarOverlayLayout(Context context) { 70 super(context); 71 init(context); 72 } 73 74 public ActionBarOverlayLayout(Context context, AttributeSet attrs) { 75 super(context, attrs); 76 init(context); 77 } 78 79 private void init(Context context) { 80 TypedArray ta = getContext().getTheme().obtainStyledAttributes(ATTRS); 81 mActionBarHeight = ta.getDimensionPixelSize(0, 0); 82 mWindowContentOverlay = ta.getDrawable(1); 83 setWillNotDraw(mWindowContentOverlay == null); 84 ta.recycle(); 85 86 mIgnoreWindowContentOverlay = context.getApplicationInfo().targetSdkVersion < 87 Build.VERSION_CODES.KITKAT; 88 } 89 90 public void setActionBar(ActionBarImpl impl) { 91 mActionBar = impl; 92 if (getWindowToken() != null) { 93 // This is being initialized after being added to a window; 94 // make sure to update all state now. 95 mActionBar.setWindowVisibility(mWindowVisibility); 96 if (mLastSystemUiVisibility != 0) { 97 int newVis = mLastSystemUiVisibility; 98 onWindowSystemUiVisibilityChanged(newVis); 99 requestFitSystemWindows(); 100 } 101 } 102 } 103 104 public void setOverlayMode(boolean overlayMode) { 105 mOverlayMode = overlayMode; 106 107 /* 108 * Drawing the window content overlay was broken before K so starting to draw it 109 * again unexpectedly will cause artifacts in some apps. They should fix it. 110 */ 111 mIgnoreWindowContentOverlay = overlayMode && 112 getContext().getApplicationInfo().targetSdkVersion < 113 Build.VERSION_CODES.KITKAT; 114 } 115 116 public void setShowingForActionMode(boolean showing) { 117 if (showing) { 118 // Here's a fun hack: if the status bar is currently being hidden, 119 // and the application has asked for stable content insets, then 120 // we will end up with the action mode action bar being shown 121 // without the status bar, but moved below where the status bar 122 // would be. Not nice. Trying to have this be positioned 123 // correctly is not easy (basically we need yet *another* content 124 // inset from the window manager to know where to put it), so 125 // instead we will just temporarily force the status bar to be shown. 126 if ((getWindowSystemUiVisibility() & (SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN 127 | SYSTEM_UI_FLAG_LAYOUT_STABLE)) 128 == (SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN | SYSTEM_UI_FLAG_LAYOUT_STABLE)) { 129 setDisabledSystemUiVisibility(SYSTEM_UI_FLAG_FULLSCREEN); 130 } 131 } else { 132 setDisabledSystemUiVisibility(0); 133 } 134 } 135 136 @Override 137 public void onWindowSystemUiVisibilityChanged(int visible) { 138 super.onWindowSystemUiVisibilityChanged(visible); 139 pullChildren(); 140 final int diff = mLastSystemUiVisibility ^ visible; 141 mLastSystemUiVisibility = visible; 142 final boolean barVisible = (visible&SYSTEM_UI_FLAG_FULLSCREEN) == 0; 143 final boolean wasVisible = mActionBar != null ? mActionBar.isSystemShowing() : true; 144 final boolean stable = (visible&SYSTEM_UI_FLAG_LAYOUT_STABLE) != 0; 145 if (mActionBar != null) { 146 // We want the bar to be visible if it is not being hidden, 147 // or the app has not turned on a stable UI mode (meaning they 148 // are performing explicit layout around the action bar). 149 mActionBar.enableContentAnimations(!stable); 150 if (barVisible || !stable) mActionBar.showForSystem(); 151 else mActionBar.hideForSystem(); 152 } 153 if ((diff&SYSTEM_UI_FLAG_LAYOUT_STABLE) != 0) { 154 if (mActionBar != null) { 155 requestFitSystemWindows(); 156 } 157 } 158 } 159 160 @Override 161 protected void onWindowVisibilityChanged(int visibility) { 162 super.onWindowVisibilityChanged(visibility); 163 mWindowVisibility = visibility; 164 if (mActionBar != null) { 165 mActionBar.setWindowVisibility(visibility); 166 } 167 } 168 169 private boolean applyInsets(View view, Rect insets, boolean left, boolean top, 170 boolean bottom, boolean right) { 171 boolean changed = false; 172 LayoutParams lp = (LayoutParams)view.getLayoutParams(); 173 if (left && lp.leftMargin != insets.left) { 174 changed = true; 175 lp.leftMargin = insets.left; 176 } 177 if (top && lp.topMargin != insets.top) { 178 changed = true; 179 lp.topMargin = insets.top; 180 } 181 if (right && lp.rightMargin != insets.right) { 182 changed = true; 183 lp.rightMargin = insets.right; 184 } 185 if (bottom && lp.bottomMargin != insets.bottom) { 186 changed = true; 187 lp.bottomMargin = insets.bottom; 188 } 189 return changed; 190 } 191 192 @Override 193 protected boolean fitSystemWindows(Rect insets) { 194 pullChildren(); 195 196 final int vis = getWindowSystemUiVisibility(); 197 final boolean stable = (vis & SYSTEM_UI_FLAG_LAYOUT_STABLE) != 0; 198 199 // The top and bottom action bars are always within the content area. 200 boolean changed = applyInsets(mActionBarTop, insets, true, true, false, true); 201 if (mActionBarBottom != null) { 202 changed |= applyInsets(mActionBarBottom, insets, true, false, true, true); 203 } 204 205 mBaseInnerInsets.set(insets); 206 computeFitSystemWindows(mBaseInnerInsets, mBaseContentInsets); 207 if (!mLastBaseContentInsets.equals(mBaseContentInsets)) { 208 changed = true; 209 mLastBaseContentInsets.set(mBaseContentInsets); 210 } 211 212 if (changed) { 213 requestLayout(); 214 } 215 216 // We don't do any more at this point. To correctly compute the content/inner 217 // insets in all cases, we need to know the measured size of the various action 218 // bar elements. fitSystemWindows() happens before the measure pass, so we can't 219 // do that here. Instead we will take this up in onMeasure(). 220 return true; 221 } 222 223 @Override 224 protected LayoutParams generateDefaultLayoutParams() { 225 return new LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT); 226 } 227 228 @Override 229 public LayoutParams generateLayoutParams(AttributeSet attrs) { 230 return new LayoutParams(getContext(), attrs); 231 } 232 233 @Override 234 protected ViewGroup.LayoutParams generateLayoutParams(ViewGroup.LayoutParams p) { 235 return new LayoutParams(p); 236 } 237 238 @Override 239 protected boolean checkLayoutParams(ViewGroup.LayoutParams p) { 240 return p instanceof LayoutParams; 241 } 242 243 @Override 244 protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { 245 pullChildren(); 246 247 int maxHeight = 0; 248 int maxWidth = 0; 249 int childState = 0; 250 251 int topInset = 0; 252 int bottomInset = 0; 253 254 measureChildWithMargins(mActionBarTop, widthMeasureSpec, 0, heightMeasureSpec, 0); 255 LayoutParams lp = (LayoutParams) mActionBarTop.getLayoutParams(); 256 maxWidth = Math.max(maxWidth, 257 mActionBarTop.getMeasuredWidth() + lp.leftMargin + lp.rightMargin); 258 maxHeight = Math.max(maxHeight, 259 mActionBarTop.getMeasuredHeight() + lp.topMargin + lp.bottomMargin); 260 childState = combineMeasuredStates(childState, mActionBarTop.getMeasuredState()); 261 262 // xlarge screen layout doesn't have bottom action bar. 263 if (mActionBarBottom != null) { 264 measureChildWithMargins(mActionBarBottom, widthMeasureSpec, 0, heightMeasureSpec, 0); 265 lp = (LayoutParams) mActionBarBottom.getLayoutParams(); 266 maxWidth = Math.max(maxWidth, 267 mActionBarBottom.getMeasuredWidth() + lp.leftMargin + lp.rightMargin); 268 maxHeight = Math.max(maxHeight, 269 mActionBarBottom.getMeasuredHeight() + lp.topMargin + lp.bottomMargin); 270 childState = combineMeasuredStates(childState, mActionBarBottom.getMeasuredState()); 271 } 272 273 final int vis = getWindowSystemUiVisibility(); 274 final boolean stable = (vis & SYSTEM_UI_FLAG_LAYOUT_STABLE) != 0; 275 276 if (stable) { 277 // This is the standard space needed for the action bar. For stable measurement, 278 // we can't depend on the size currently reported by it -- this must remain constant. 279 topInset = mActionBarHeight; 280 if (mActionBar != null && mActionBar.hasNonEmbeddedTabs()) { 281 View tabs = mActionBarTop.getTabContainer(); 282 if (tabs != null) { 283 // If tabs are not embedded, increase space on top to account for them. 284 topInset += mActionBarHeight; 285 } 286 } 287 } else if (mActionBarTop.getVisibility() == VISIBLE) { 288 // This is the space needed on top of the window for all of the action bar 289 // and tabs. 290 topInset = mActionBarTop.getMeasuredHeight(); 291 } 292 293 if (mActionBarView.isSplitActionBar()) { 294 // If action bar is split, adjust bottom insets for it. 295 if (mActionBarBottom != null) { 296 if (stable) { 297 bottomInset = mActionBarHeight; 298 } else { 299 bottomInset = mActionBarBottom.getMeasuredHeight(); 300 } 301 } 302 } 303 304 // If the window has not requested system UI layout flags, we need to 305 // make sure its content is not being covered by system UI... though it 306 // will still be covered by the action bar if they have requested it to 307 // overlay. 308 mContentInsets.set(mBaseContentInsets); 309 mInnerInsets.set(mBaseInnerInsets); 310 if (!mOverlayMode && !stable) { 311 mContentInsets.top += topInset; 312 mContentInsets.bottom += bottomInset; 313 } else { 314 mInnerInsets.top += topInset; 315 mInnerInsets.bottom += bottomInset; 316 } 317 applyInsets(mContent, mContentInsets, true, true, true, true); 318 319 if (!mLastInnerInsets.equals(mInnerInsets)) { 320 // If the inner insets have changed, we need to dispatch this down to 321 // the app's fitSystemWindows(). We do this before measuring the content 322 // view to keep the same semantics as the normal fitSystemWindows() call. 323 mLastInnerInsets.set(mInnerInsets); 324 super.fitSystemWindows(mInnerInsets); 325 } 326 327 measureChildWithMargins(mContent, widthMeasureSpec, 0, heightMeasureSpec, 0); 328 lp = (LayoutParams) mContent.getLayoutParams(); 329 maxWidth = Math.max(maxWidth, 330 mContent.getMeasuredWidth() + lp.leftMargin + lp.rightMargin); 331 maxHeight = Math.max(maxHeight, 332 mContent.getMeasuredHeight() + lp.topMargin + lp.bottomMargin); 333 childState = combineMeasuredStates(childState, mContent.getMeasuredState()); 334 335 // Account for padding too 336 maxWidth += getPaddingLeft() + getPaddingRight(); 337 maxHeight += getPaddingTop() + getPaddingBottom(); 338 339 // Check against our minimum height and width 340 maxHeight = Math.max(maxHeight, getSuggestedMinimumHeight()); 341 maxWidth = Math.max(maxWidth, getSuggestedMinimumWidth()); 342 343 setMeasuredDimension(resolveSizeAndState(maxWidth, widthMeasureSpec, childState), 344 resolveSizeAndState(maxHeight, heightMeasureSpec, 345 childState << MEASURED_HEIGHT_STATE_SHIFT)); 346 } 347 348 @Override 349 protected void onLayout(boolean changed, int left, int top, int right, int bottom) { 350 final int count = getChildCount(); 351 352 final int parentLeft = getPaddingLeft(); 353 final int parentRight = right - left - getPaddingRight(); 354 355 final int parentTop = getPaddingTop(); 356 final int parentBottom = bottom - top - getPaddingBottom(); 357 358 for (int i = 0; i < count; i++) { 359 final View child = getChildAt(i); 360 if (child.getVisibility() != GONE) { 361 final LayoutParams lp = (LayoutParams) child.getLayoutParams(); 362 363 final int width = child.getMeasuredWidth(); 364 final int height = child.getMeasuredHeight(); 365 366 int childLeft = parentLeft + lp.leftMargin; 367 int childTop; 368 if (child == mActionBarBottom) { 369 childTop = parentBottom - height - lp.bottomMargin; 370 } else { 371 childTop = parentTop + lp.topMargin; 372 } 373 374 child.layout(childLeft, childTop, childLeft + width, childTop + height); 375 } 376 } 377 } 378 379 @Override 380 public void draw(Canvas c) { 381 super.draw(c); 382 if (mWindowContentOverlay != null && !mIgnoreWindowContentOverlay) { 383 final int top = mActionBarTop.getVisibility() == VISIBLE ? 384 (int) (mActionBarTop.getBottom() + mActionBarTop.getTranslationY() + 0.5f) : 0; 385 mWindowContentOverlay.setBounds(0, top, getWidth(), 386 top + mWindowContentOverlay.getIntrinsicHeight()); 387 mWindowContentOverlay.draw(c); 388 } 389 } 390 391 @Override 392 public boolean shouldDelayChildPressedState() { 393 return false; 394 } 395 396 void pullChildren() { 397 if (mContent == null) { 398 mContent = findViewById(com.android.internal.R.id.content); 399 mActionBarTop = (ActionBarContainer)findViewById( 400 com.android.internal.R.id.action_bar_container); 401 mActionBarView = (ActionBarView) findViewById(com.android.internal.R.id.action_bar); 402 mActionBarBottom = findViewById(com.android.internal.R.id.split_action_bar); 403 } 404 } 405 406 407 public static class LayoutParams extends MarginLayoutParams { 408 public LayoutParams(Context c, AttributeSet attrs) { 409 super(c, attrs); 410 } 411 412 public LayoutParams(int width, int height) { 413 super(width, height); 414 } 415 416 public LayoutParams(ViewGroup.LayoutParams source) { 417 super(source); 418 } 419 420 public LayoutParams(ViewGroup.MarginLayoutParams source) { 421 super(source); 422 } 423 } 424} 425