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