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