ActionBarOverlayLayout.java revision e021e6ed8931a0a8296af182fc9b0c76b64fb0c4
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.animation.Animator;
20import android.animation.AnimatorListenerAdapter;
21import android.content.Context;
22import android.content.pm.ActivityInfo;
23import android.content.res.TypedArray;
24import android.graphics.Canvas;
25import android.graphics.Rect;
26import android.graphics.drawable.Drawable;
27import android.os.Build;
28import android.os.Parcelable;
29import android.util.AttributeSet;
30import android.util.IntProperty;
31import android.util.Log;
32import android.util.Property;
33import android.util.SparseArray;
34import android.view.KeyEvent;
35import android.view.Menu;
36import android.view.View;
37import android.view.ViewGroup;
38import android.view.ViewPropertyAnimator;
39import android.view.Window;
40import android.view.WindowInsets;
41import android.widget.OverScroller;
42import android.widget.Toolbar;
43import com.android.internal.view.menu.MenuPresenter;
44
45/**
46 * Special layout for the containing of an overlay action bar (and its
47 * content) to correctly handle fitting system windows when the content
48 * has request that its layout ignore them.
49 */
50public class ActionBarOverlayLayout extends ViewGroup implements DecorContentParent {
51    private static final String TAG = "ActionBarOverlayLayout";
52
53    private int mActionBarHeight;
54    //private WindowDecorActionBar mActionBar;
55    private int mWindowVisibility = View.VISIBLE;
56
57    // The main UI elements that we handle the layout of.
58    private View mContent;
59    private ActionBarContainer mActionBarBottom;
60    private ActionBarContainer mActionBarTop;
61
62    // Some interior UI elements.
63    private DecorToolbar mDecorToolbar;
64
65    // Content overlay drawable - generally the action bar's shadow
66    private Drawable mWindowContentOverlay;
67    private boolean mIgnoreWindowContentOverlay;
68
69    private boolean mOverlayMode;
70    private boolean mHasNonEmbeddedTabs;
71    private boolean mHideOnContentScroll;
72    private boolean mAnimatingForFling;
73    private int mHideOnContentScrollReference;
74    private int mLastSystemUiVisibility;
75    private final Rect mBaseContentInsets = new Rect();
76    private final Rect mLastBaseContentInsets = new Rect();
77    private final Rect mContentInsets = new Rect();
78    private final Rect mBaseInnerInsets = new Rect();
79    private final Rect mInnerInsets = new Rect();
80    private final Rect mLastInnerInsets = new Rect();
81
82    private ActionBarVisibilityCallback mActionBarVisibilityCallback;
83
84    private final int ACTION_BAR_ANIMATE_DELAY = 600; // ms
85
86    private OverScroller mFlingEstimator;
87
88    private ViewPropertyAnimator mCurrentActionBarTopAnimator;
89    private ViewPropertyAnimator mCurrentActionBarBottomAnimator;
90
91    private final Animator.AnimatorListener mTopAnimatorListener = new AnimatorListenerAdapter() {
92        @Override
93        public void onAnimationEnd(Animator animation) {
94            mCurrentActionBarTopAnimator = null;
95            mAnimatingForFling = false;
96        }
97
98        @Override
99        public void onAnimationCancel(Animator animation) {
100            mCurrentActionBarTopAnimator = null;
101            mAnimatingForFling = false;
102        }
103    };
104
105    private final Animator.AnimatorListener mBottomAnimatorListener =
106            new AnimatorListenerAdapter() {
107        @Override
108        public void onAnimationEnd(Animator animation) {
109            mCurrentActionBarBottomAnimator = null;
110            mAnimatingForFling = false;
111        }
112
113        @Override
114        public void onAnimationCancel(Animator animation) {
115            mCurrentActionBarBottomAnimator = null;
116            mAnimatingForFling = false;
117        }
118    };
119
120    private final Runnable mRemoveActionBarHideOffset = new Runnable() {
121        public void run() {
122            haltActionBarHideOffsetAnimations();
123            mCurrentActionBarTopAnimator = mActionBarTop.animate().translationY(0)
124                    .setListener(mTopAnimatorListener);
125            if (mActionBarBottom != null && mActionBarBottom.getVisibility() != GONE) {
126                mCurrentActionBarBottomAnimator = mActionBarBottom.animate().translationY(0)
127                        .setListener(mBottomAnimatorListener);
128            }
129        }
130    };
131
132    private final Runnable mAddActionBarHideOffset = new Runnable() {
133        public void run() {
134            haltActionBarHideOffsetAnimations();
135            mCurrentActionBarTopAnimator = mActionBarTop.animate()
136                    .translationY(-mActionBarTop.getHeight())
137                    .setListener(mTopAnimatorListener);
138            if (mActionBarBottom != null && mActionBarBottom.getVisibility() != GONE) {
139                mCurrentActionBarBottomAnimator = mActionBarBottom.animate()
140                        .translationY(mActionBarBottom.getHeight())
141                        .setListener(mBottomAnimatorListener);
142            }
143        }
144    };
145
146    public static final Property<ActionBarOverlayLayout, Integer> ACTION_BAR_HIDE_OFFSET =
147            new IntProperty<ActionBarOverlayLayout>("actionBarHideOffset") {
148
149                @Override
150                public void setValue(ActionBarOverlayLayout object, int value) {
151                    object.setActionBarHideOffset(value);
152                }
153
154                @Override
155                public Integer get(ActionBarOverlayLayout object) {
156                    return object.getActionBarHideOffset();
157                }
158            };
159
160    static final int[] ATTRS = new int [] {
161            com.android.internal.R.attr.actionBarSize,
162            com.android.internal.R.attr.windowContentOverlay
163    };
164
165    public ActionBarOverlayLayout(Context context) {
166        super(context);
167        init(context);
168    }
169
170    public ActionBarOverlayLayout(Context context, AttributeSet attrs) {
171        super(context, attrs);
172        init(context);
173    }
174
175    private void init(Context context) {
176        TypedArray ta = getContext().getTheme().obtainStyledAttributes(ATTRS);
177        mActionBarHeight = ta.getDimensionPixelSize(0, 0);
178        mWindowContentOverlay = ta.getDrawable(1);
179        setWillNotDraw(mWindowContentOverlay == null);
180        ta.recycle();
181
182        mIgnoreWindowContentOverlay = context.getApplicationInfo().targetSdkVersion <
183                Build.VERSION_CODES.KITKAT;
184
185        mFlingEstimator = new OverScroller(context);
186    }
187
188    @Override
189    protected void onDetachedFromWindow() {
190        super.onDetachedFromWindow();
191        haltActionBarHideOffsetAnimations();
192    }
193
194    public void setActionBarVisibilityCallback(ActionBarVisibilityCallback cb) {
195        mActionBarVisibilityCallback = cb;
196        if (getWindowToken() != null) {
197            // This is being initialized after being added to a window;
198            // make sure to update all state now.
199            mActionBarVisibilityCallback.onWindowVisibilityChanged(mWindowVisibility);
200            if (mLastSystemUiVisibility != 0) {
201                int newVis = mLastSystemUiVisibility;
202                onWindowSystemUiVisibilityChanged(newVis);
203                requestApplyInsets();
204            }
205        }
206    }
207
208    public void setOverlayMode(boolean overlayMode) {
209        mOverlayMode = overlayMode;
210
211        /*
212         * Drawing the window content overlay was broken before K so starting to draw it
213         * again unexpectedly will cause artifacts in some apps. They should fix it.
214         */
215        mIgnoreWindowContentOverlay = overlayMode &&
216                getContext().getApplicationInfo().targetSdkVersion <
217                        Build.VERSION_CODES.KITKAT;
218    }
219
220    public boolean isInOverlayMode() {
221        return mOverlayMode;
222    }
223
224    public void setHasNonEmbeddedTabs(boolean hasNonEmbeddedTabs) {
225        mHasNonEmbeddedTabs = hasNonEmbeddedTabs;
226    }
227
228    public void setShowingForActionMode(boolean showing) {
229        if (showing) {
230            // Here's a fun hack: if the status bar is currently being hidden,
231            // and the application has asked for stable content insets, then
232            // we will end up with the action mode action bar being shown
233            // without the status bar, but moved below where the status bar
234            // would be.  Not nice.  Trying to have this be positioned
235            // correctly is not easy (basically we need yet *another* content
236            // inset from the window manager to know where to put it), so
237            // instead we will just temporarily force the status bar to be shown.
238            if ((getWindowSystemUiVisibility() & (SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN
239                    | SYSTEM_UI_FLAG_LAYOUT_STABLE))
240                    == (SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN | SYSTEM_UI_FLAG_LAYOUT_STABLE)) {
241                setDisabledSystemUiVisibility(SYSTEM_UI_FLAG_FULLSCREEN);
242            }
243        } else {
244            setDisabledSystemUiVisibility(0);
245        }
246    }
247
248    @Override
249    public void onWindowSystemUiVisibilityChanged(int visible) {
250        super.onWindowSystemUiVisibilityChanged(visible);
251        pullChildren();
252        final int diff = mLastSystemUiVisibility ^ visible;
253        mLastSystemUiVisibility = visible;
254        final boolean barVisible = (visible & SYSTEM_UI_FLAG_FULLSCREEN) == 0;
255        final boolean stable = (visible & SYSTEM_UI_FLAG_LAYOUT_STABLE) != 0;
256        if (mActionBarVisibilityCallback != null) {
257            // We want the bar to be visible if it is not being hidden,
258            // or the app has not turned on a stable UI mode (meaning they
259            // are performing explicit layout around the action bar).
260            mActionBarVisibilityCallback.enableContentAnimations(!stable);
261            if (barVisible || !stable) mActionBarVisibilityCallback.showForSystem();
262            else mActionBarVisibilityCallback.hideForSystem();
263        }
264        if ((diff & SYSTEM_UI_FLAG_LAYOUT_STABLE) != 0) {
265            if (mActionBarVisibilityCallback != null) {
266                requestApplyInsets();
267            }
268        }
269    }
270
271    @Override
272    protected void onWindowVisibilityChanged(int visibility) {
273        super.onWindowVisibilityChanged(visibility);
274        mWindowVisibility = visibility;
275        if (mActionBarVisibilityCallback != null) {
276            mActionBarVisibilityCallback.onWindowVisibilityChanged(visibility);
277        }
278    }
279
280    private boolean applyInsets(View view, Rect insets, boolean left, boolean top,
281            boolean bottom, boolean right) {
282        boolean changed = false;
283        LayoutParams lp = (LayoutParams)view.getLayoutParams();
284        if (left && lp.leftMargin != insets.left) {
285            changed = true;
286            lp.leftMargin = insets.left;
287        }
288        if (top && lp.topMargin != insets.top) {
289            changed = true;
290            lp.topMargin = insets.top;
291        }
292        if (right && lp.rightMargin != insets.right) {
293            changed = true;
294            lp.rightMargin = insets.right;
295        }
296        if (bottom && lp.bottomMargin != insets.bottom) {
297            changed = true;
298            lp.bottomMargin = insets.bottom;
299        }
300        return changed;
301    }
302
303    @Override
304    public WindowInsets onApplyWindowInsets(WindowInsets insets) {
305        pullChildren();
306
307        final int vis = getWindowSystemUiVisibility();
308        final boolean stable = (vis & SYSTEM_UI_FLAG_LAYOUT_STABLE) != 0;
309        final Rect systemInsets = insets.getSystemWindowInsets();
310
311        // The top and bottom action bars are always within the content area.
312        boolean changed = applyInsets(mActionBarTop, systemInsets, true, true, false, true);
313        if (mActionBarBottom != null) {
314            changed |= applyInsets(mActionBarBottom, systemInsets, true, false, true, true);
315        }
316
317        mBaseInnerInsets.set(systemInsets);
318        computeFitSystemWindows(mBaseInnerInsets, mBaseContentInsets);
319        if (!mLastBaseContentInsets.equals(mBaseContentInsets)) {
320            changed = true;
321            mLastBaseContentInsets.set(mBaseContentInsets);
322        }
323
324        if (changed) {
325            requestLayout();
326        }
327
328        // We don't do any more at this point.  To correctly compute the content/inner
329        // insets in all cases, we need to know the measured size of the various action
330        // bar elements.  onApplyWindowInsets() happens before the measure pass, so we can't
331        // do that here.  Instead we will take this up in onMeasure().
332        return WindowInsets.EMPTY;
333    }
334
335    @Override
336    protected LayoutParams generateDefaultLayoutParams() {
337        return new LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT);
338    }
339
340    @Override
341    public LayoutParams generateLayoutParams(AttributeSet attrs) {
342        return new LayoutParams(getContext(), attrs);
343    }
344
345    @Override
346    protected ViewGroup.LayoutParams generateLayoutParams(ViewGroup.LayoutParams p) {
347        return new LayoutParams(p);
348    }
349
350    @Override
351    protected boolean checkLayoutParams(ViewGroup.LayoutParams p) {
352        return p instanceof LayoutParams;
353    }
354
355    @Override
356    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
357        pullChildren();
358
359        int maxHeight = 0;
360        int maxWidth = 0;
361        int childState = 0;
362
363        int topInset = 0;
364        int bottomInset = 0;
365
366        measureChildWithMargins(mActionBarTop, widthMeasureSpec, 0, heightMeasureSpec, 0);
367        LayoutParams lp = (LayoutParams) mActionBarTop.getLayoutParams();
368        maxWidth = Math.max(maxWidth,
369                mActionBarTop.getMeasuredWidth() + lp.leftMargin + lp.rightMargin);
370        maxHeight = Math.max(maxHeight,
371                mActionBarTop.getMeasuredHeight() + lp.topMargin + lp.bottomMargin);
372        childState = combineMeasuredStates(childState, mActionBarTop.getMeasuredState());
373
374        // xlarge screen layout doesn't have bottom action bar.
375        if (mActionBarBottom != null) {
376            measureChildWithMargins(mActionBarBottom, widthMeasureSpec, 0, heightMeasureSpec, 0);
377            lp = (LayoutParams) mActionBarBottom.getLayoutParams();
378            maxWidth = Math.max(maxWidth,
379                    mActionBarBottom.getMeasuredWidth() + lp.leftMargin + lp.rightMargin);
380            maxHeight = Math.max(maxHeight,
381                    mActionBarBottom.getMeasuredHeight() + lp.topMargin + lp.bottomMargin);
382            childState = combineMeasuredStates(childState, mActionBarBottom.getMeasuredState());
383        }
384
385        final int vis = getWindowSystemUiVisibility();
386        final boolean stable = (vis & SYSTEM_UI_FLAG_LAYOUT_STABLE) != 0;
387
388        if (stable) {
389            // This is the standard space needed for the action bar.  For stable measurement,
390            // we can't depend on the size currently reported by it -- this must remain constant.
391            topInset = mActionBarHeight;
392            if (mHasNonEmbeddedTabs) {
393                final View tabs = mActionBarTop.getTabContainer();
394                if (tabs != null) {
395                    // If tabs are not embedded, increase space on top to account for them.
396                    topInset += mActionBarHeight;
397                }
398            }
399        } else if (mActionBarTop.getVisibility() != GONE) {
400            // This is the space needed on top of the window for all of the action bar
401            // and tabs.
402            topInset = mActionBarTop.getMeasuredHeight();
403        }
404
405        if (mDecorToolbar.isSplit()) {
406            // If action bar is split, adjust bottom insets for it.
407            if (mActionBarBottom != null) {
408                if (stable) {
409                    bottomInset = mActionBarHeight;
410                } else {
411                    bottomInset = mActionBarBottom.getMeasuredHeight();
412                }
413            }
414        }
415
416        // If the window has not requested system UI layout flags, we need to
417        // make sure its content is not being covered by system UI...  though it
418        // will still be covered by the action bar if they have requested it to
419        // overlay.
420        mContentInsets.set(mBaseContentInsets);
421        mInnerInsets.set(mBaseInnerInsets);
422        if (!mOverlayMode && !stable) {
423            mContentInsets.top += topInset;
424            mContentInsets.bottom += bottomInset;
425        } else {
426            mInnerInsets.top += topInset;
427            mInnerInsets.bottom += bottomInset;
428        }
429        applyInsets(mContent, mContentInsets, true, true, true, true);
430
431        if (!mLastInnerInsets.equals(mInnerInsets)) {
432            // If the inner insets have changed, we need to dispatch this down to
433            // the app's fitSystemWindows().  We do this before measuring the content
434            // view to keep the same semantics as the normal fitSystemWindows() call.
435            mLastInnerInsets.set(mInnerInsets);
436            mContent.dispatchApplyWindowInsets(new WindowInsets(mInnerInsets));
437        }
438
439        measureChildWithMargins(mContent, widthMeasureSpec, 0, heightMeasureSpec, 0);
440        lp = (LayoutParams) mContent.getLayoutParams();
441        maxWidth = Math.max(maxWidth,
442                mContent.getMeasuredWidth() + lp.leftMargin + lp.rightMargin);
443        maxHeight = Math.max(maxHeight,
444                mContent.getMeasuredHeight() + lp.topMargin + lp.bottomMargin);
445        childState = combineMeasuredStates(childState, mContent.getMeasuredState());
446
447        // Account for padding too
448        maxWidth += getPaddingLeft() + getPaddingRight();
449        maxHeight += getPaddingTop() + getPaddingBottom();
450
451        // Check against our minimum height and width
452        maxHeight = Math.max(maxHeight, getSuggestedMinimumHeight());
453        maxWidth = Math.max(maxWidth, getSuggestedMinimumWidth());
454
455        setMeasuredDimension(resolveSizeAndState(maxWidth, widthMeasureSpec, childState),
456                resolveSizeAndState(maxHeight, heightMeasureSpec,
457                        childState << MEASURED_HEIGHT_STATE_SHIFT));
458    }
459
460    @Override
461    protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
462        final int count = getChildCount();
463
464        final int parentLeft = getPaddingLeft();
465        final int parentRight = right - left - getPaddingRight();
466
467        final int parentTop = getPaddingTop();
468        final int parentBottom = bottom - top - getPaddingBottom();
469
470        for (int i = 0; i < count; i++) {
471            final View child = getChildAt(i);
472            if (child.getVisibility() != GONE) {
473                final LayoutParams lp = (LayoutParams) child.getLayoutParams();
474
475                final int width = child.getMeasuredWidth();
476                final int height = child.getMeasuredHeight();
477
478                int childLeft = parentLeft + lp.leftMargin;
479                int childTop;
480                if (child == mActionBarBottom) {
481                    childTop = parentBottom - height - lp.bottomMargin;
482                } else {
483                    childTop = parentTop + lp.topMargin;
484                }
485
486                child.layout(childLeft, childTop, childLeft + width, childTop + height);
487            }
488        }
489    }
490
491    @Override
492    public void draw(Canvas c) {
493        super.draw(c);
494        if (mWindowContentOverlay != null && !mIgnoreWindowContentOverlay) {
495            final int top = mActionBarTop.getVisibility() == VISIBLE ?
496                    (int) (mActionBarTop.getBottom() + mActionBarTop.getTranslationY() + 0.5f) : 0;
497            mWindowContentOverlay.setBounds(0, top, getWidth(),
498                    top + mWindowContentOverlay.getIntrinsicHeight());
499            mWindowContentOverlay.draw(c);
500        }
501    }
502
503    @Override
504    public boolean shouldDelayChildPressedState() {
505        return false;
506    }
507
508    @Override
509    public boolean onStartNestedScroll(View child, View target, int axes) {
510        if ((axes & SCROLL_AXIS_VERTICAL) == 0 || mActionBarTop.getVisibility() != VISIBLE) {
511            return false;
512        }
513        return mHideOnContentScroll;
514    }
515
516    @Override
517    public void onNestedScrollAccepted(View child, View target, int axes) {
518        super.onNestedScrollAccepted(child, target, axes);
519        mHideOnContentScrollReference = getActionBarHideOffset();
520        haltActionBarHideOffsetAnimations();
521        if (mActionBarVisibilityCallback != null) {
522            mActionBarVisibilityCallback.onContentScrollStarted();
523        }
524    }
525
526    @Override
527    public void onNestedScroll(View target, int dxConsumed, int dyConsumed,
528            int dxUnconsumed, int dyUnconsumed) {
529        mHideOnContentScrollReference += dyConsumed;
530        setActionBarHideOffset(mHideOnContentScrollReference);
531    }
532
533    @Override
534    public void onStopNestedScroll(View target) {
535        super.onStopNestedScroll(target);
536        if (mHideOnContentScroll && !mAnimatingForFling) {
537            if (mHideOnContentScrollReference <= mActionBarTop.getHeight()) {
538                postRemoveActionBarHideOffset();
539            } else {
540                postAddActionBarHideOffset();
541            }
542        }
543        if (mActionBarVisibilityCallback != null) {
544            mActionBarVisibilityCallback.onContentScrollStopped();
545        }
546    }
547
548    @Override
549    public boolean onNestedFling(View target, float velocityX, float velocityY, boolean consumed) {
550        if (!mHideOnContentScroll || !consumed) {
551            return false;
552        }
553        if (shouldHideActionBarOnFling(velocityX, velocityY)) {
554            addActionBarHideOffset();
555        } else {
556            removeActionBarHideOffset();
557        }
558        mAnimatingForFling = true;
559        return true;
560    }
561
562    void pullChildren() {
563        if (mContent == null) {
564            mContent = findViewById(com.android.internal.R.id.content);
565            mActionBarTop = (ActionBarContainer) findViewById(
566                    com.android.internal.R.id.action_bar_container);
567            mDecorToolbar = getDecorToolbar(findViewById(com.android.internal.R.id.action_bar));
568            mActionBarBottom = (ActionBarContainer) findViewById(
569                    com.android.internal.R.id.split_action_bar);
570        }
571    }
572
573    private DecorToolbar getDecorToolbar(View view) {
574        if (view instanceof DecorToolbar) {
575            return (DecorToolbar) view;
576        } else if (view instanceof Toolbar) {
577            return ((Toolbar) view).getWrapper();
578        } else {
579            throw new IllegalStateException("Can't make a decor toolbar out of " +
580                    view.getClass().getSimpleName());
581        }
582    }
583
584    public void setHideOnContentScrollEnabled(boolean hideOnContentScroll) {
585        if (hideOnContentScroll != mHideOnContentScroll) {
586            mHideOnContentScroll = hideOnContentScroll;
587            if (!hideOnContentScroll) {
588                stopNestedScroll();
589                haltActionBarHideOffsetAnimations();
590                setActionBarHideOffset(0);
591            }
592        }
593    }
594
595    public boolean isHideOnContentScrollEnabled() {
596        return mHideOnContentScroll;
597    }
598
599    public int getActionBarHideOffset() {
600        return mActionBarTop != null ? -((int) mActionBarTop.getTranslationY()) : 0;
601    }
602
603    public void setActionBarHideOffset(int offset) {
604        haltActionBarHideOffsetAnimations();
605        final int topHeight = mActionBarTop.getHeight();
606        offset = Math.max(0, Math.min(offset, topHeight));
607        mActionBarTop.setTranslationY(-offset);
608        if (mActionBarBottom != null && mActionBarBottom.getVisibility() != GONE) {
609            // Match the hide offset proportionally for a split bar
610            final float fOffset = (float) offset / topHeight;
611            final int bOffset = (int) (mActionBarBottom.getHeight() * fOffset);
612            mActionBarBottom.setTranslationY(bOffset);
613        }
614    }
615
616    private void haltActionBarHideOffsetAnimations() {
617        removeCallbacks(mRemoveActionBarHideOffset);
618        removeCallbacks(mAddActionBarHideOffset);
619        if (mCurrentActionBarTopAnimator != null) {
620            mCurrentActionBarTopAnimator.cancel();
621        }
622        if (mCurrentActionBarBottomAnimator != null) {
623            mCurrentActionBarBottomAnimator.cancel();
624        }
625    }
626
627    private void postRemoveActionBarHideOffset() {
628        haltActionBarHideOffsetAnimations();
629        postDelayed(mRemoveActionBarHideOffset, ACTION_BAR_ANIMATE_DELAY);
630    }
631
632    private void postAddActionBarHideOffset() {
633        haltActionBarHideOffsetAnimations();
634        postDelayed(mAddActionBarHideOffset, ACTION_BAR_ANIMATE_DELAY);
635    }
636
637    private void removeActionBarHideOffset() {
638        haltActionBarHideOffsetAnimations();
639        mRemoveActionBarHideOffset.run();
640    }
641
642    private void addActionBarHideOffset() {
643        haltActionBarHideOffsetAnimations();
644        mAddActionBarHideOffset.run();
645    }
646
647    private boolean shouldHideActionBarOnFling(float velocityX, float velocityY) {
648        mFlingEstimator.fling(0, 0, 0, (int) velocityY, 0, 0, Integer.MIN_VALUE, Integer.MAX_VALUE);
649        final int finalY = mFlingEstimator.getFinalY();
650        return finalY > mActionBarTop.getHeight();
651    }
652
653    @Override
654    public boolean dispatchKeyEvent(KeyEvent event) {
655        if (super.dispatchKeyEvent(event)) {
656            return true;
657        }
658
659        if (event.getKeyCode() == KeyEvent.KEYCODE_BACK) {
660            final int action = event.getAction();
661
662            // Collapse any expanded action views.
663            if (mDecorToolbar != null && mDecorToolbar.hasExpandedActionView()) {
664                if (action == KeyEvent.ACTION_UP) {
665                    mDecorToolbar.collapseActionView();
666                }
667                return true;
668            }
669        }
670
671        return false;
672    }
673
674    @Override
675    public void setWindowCallback(Window.Callback cb) {
676        pullChildren();
677        mDecorToolbar.setWindowCallback(cb);
678    }
679
680    @Override
681    public void setWindowTitle(CharSequence title) {
682        pullChildren();
683        mDecorToolbar.setWindowTitle(title);
684    }
685
686    @Override
687    public CharSequence getTitle() {
688        pullChildren();
689        return mDecorToolbar.getTitle();
690    }
691
692    @Override
693    public void initFeature(int windowFeature) {
694        pullChildren();
695        switch (windowFeature) {
696            case Window.FEATURE_PROGRESS:
697                mDecorToolbar.initProgress();
698                break;
699            case Window.FEATURE_INDETERMINATE_PROGRESS:
700                mDecorToolbar.initIndeterminateProgress();
701                break;
702            case Window.FEATURE_ACTION_BAR_OVERLAY:
703                setOverlayMode(true);
704                break;
705        }
706    }
707
708    @Override
709    public void setUiOptions(int uiOptions) {
710        boolean splitActionBar = false;
711        final boolean splitWhenNarrow =
712                (uiOptions & ActivityInfo.UIOPTION_SPLIT_ACTION_BAR_WHEN_NARROW) != 0;
713        if (splitWhenNarrow) {
714            splitActionBar = getContext().getResources().getBoolean(
715                    com.android.internal.R.bool.split_action_bar_is_narrow);
716        }
717        if (splitActionBar) {
718            pullChildren();
719            if (mActionBarBottom != null && mDecorToolbar.canSplit()) {
720                mDecorToolbar.setSplitView(mActionBarBottom);
721                mDecorToolbar.setSplitToolbar(splitActionBar);
722                mDecorToolbar.setSplitWhenNarrow(splitWhenNarrow);
723
724                final ActionBarContextView cab = (ActionBarContextView) findViewById(
725                        com.android.internal.R.id.action_context_bar);
726                cab.setSplitView(mActionBarBottom);
727                cab.setSplitToolbar(splitActionBar);
728                cab.setSplitWhenNarrow(splitWhenNarrow);
729            } else if (splitActionBar) {
730                Log.e(TAG, "Requested split action bar with " +
731                        "incompatible window decor! Ignoring request.");
732            }
733        }
734    }
735
736    @Override
737    public boolean hasIcon() {
738        pullChildren();
739        return mDecorToolbar.hasIcon();
740    }
741
742    @Override
743    public boolean hasLogo() {
744        pullChildren();
745        return mDecorToolbar.hasLogo();
746    }
747
748    @Override
749    public void setIcon(int resId) {
750        pullChildren();
751        mDecorToolbar.setIcon(resId);
752    }
753
754    @Override
755    public void setIcon(Drawable d) {
756        pullChildren();
757        mDecorToolbar.setIcon(d);
758    }
759
760    @Override
761    public void setLogo(int resId) {
762        pullChildren();
763        mDecorToolbar.setLogo(resId);
764    }
765
766    @Override
767    public boolean canShowOverflowMenu() {
768        pullChildren();
769        return mDecorToolbar.canShowOverflowMenu();
770    }
771
772    @Override
773    public boolean isOverflowMenuShowing() {
774        pullChildren();
775        return mDecorToolbar.isOverflowMenuShowing();
776    }
777
778    @Override
779    public boolean isOverflowMenuShowPending() {
780        pullChildren();
781        return mDecorToolbar.isOverflowMenuShowPending();
782    }
783
784    @Override
785    public boolean showOverflowMenu() {
786        pullChildren();
787        return mDecorToolbar.showOverflowMenu();
788    }
789
790    @Override
791    public boolean hideOverflowMenu() {
792        pullChildren();
793        return mDecorToolbar.hideOverflowMenu();
794    }
795
796    @Override
797    public void setMenuPrepared() {
798        pullChildren();
799        mDecorToolbar.setMenuPrepared();
800    }
801
802    @Override
803    public void setMenu(Menu menu, MenuPresenter.Callback cb) {
804        pullChildren();
805        mDecorToolbar.setMenu(menu, cb);
806    }
807
808    @Override
809    public void saveToolbarHierarchyState(SparseArray<Parcelable> toolbarStates) {
810        pullChildren();
811        mDecorToolbar.saveHierarchyState(toolbarStates);
812    }
813
814    @Override
815    public void restoreToolbarHierarchyState(SparseArray<Parcelable> toolbarStates) {
816        pullChildren();
817        mDecorToolbar.restoreHierarchyState(toolbarStates);
818    }
819
820    @Override
821    public void dismissPopups() {
822        pullChildren();
823        mDecorToolbar.dismissPopupMenus();
824    }
825
826    public static class LayoutParams extends MarginLayoutParams {
827        public LayoutParams(Context c, AttributeSet attrs) {
828            super(c, attrs);
829        }
830
831        public LayoutParams(int width, int height) {
832            super(width, height);
833        }
834
835        public LayoutParams(ViewGroup.LayoutParams source) {
836            super(source);
837        }
838
839        public LayoutParams(ViewGroup.MarginLayoutParams source) {
840            super(source);
841        }
842    }
843
844    public interface ActionBarVisibilityCallback {
845        void onWindowVisibilityChanged(int visibility);
846        void showForSystem();
847        void hideForSystem();
848        void enableContentAnimations(boolean enable);
849        void onContentScrollStarted();
850        void onContentScrollStopped();
851    }
852}
853