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 
17 package com.android.internal.widget;
18 
19 import android.animation.Animator;
20 import android.animation.AnimatorListenerAdapter;
21 import android.content.Context;
22 import android.content.pm.ActivityInfo;
23 import android.content.res.Configuration;
24 import android.content.res.TypedArray;
25 import android.graphics.Canvas;
26 import android.graphics.Rect;
27 import android.graphics.drawable.Drawable;
28 import android.os.Build;
29 import android.os.Parcelable;
30 import android.util.AttributeSet;
31 import android.util.IntProperty;
32 import android.util.Log;
33 import android.util.Property;
34 import android.util.SparseArray;
35 import android.view.Menu;
36 import android.view.View;
37 import android.view.ViewGroup;
38 import android.view.ViewPropertyAnimator;
39 import android.view.Window;
40 import android.view.WindowInsets;
41 import android.widget.OverScroller;
42 import android.widget.Toolbar;
43 import 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  */
50 public 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 
ActionBarOverlayLayout(Context context)165     public ActionBarOverlayLayout(Context context) {
166         super(context);
167         init(context);
168     }
169 
ActionBarOverlayLayout(Context context, AttributeSet attrs)170     public ActionBarOverlayLayout(Context context, AttributeSet attrs) {
171         super(context, attrs);
172         init(context);
173     }
174 
init(Context context)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     protected void onConfigurationChanged(Configuration newConfig) {
250         super.onConfigurationChanged(newConfig);
251         init(getContext());
252         requestApplyInsets();
253     }
254 
255     @Override
256     public void onWindowSystemUiVisibilityChanged(int visible) {
257         super.onWindowSystemUiVisibilityChanged(visible);
258         pullChildren();
259         final int diff = mLastSystemUiVisibility ^ visible;
260         mLastSystemUiVisibility = visible;
261         final boolean barVisible = (visible & SYSTEM_UI_FLAG_FULLSCREEN) == 0;
262         final boolean stable = (visible & SYSTEM_UI_FLAG_LAYOUT_STABLE) != 0;
263         if (mActionBarVisibilityCallback != null) {
264             // We want the bar to be visible if it is not being hidden,
265             // or the app has not turned on a stable UI mode (meaning they
266             // are performing explicit layout around the action bar).
267             mActionBarVisibilityCallback.enableContentAnimations(!stable);
268             if (barVisible || !stable) mActionBarVisibilityCallback.showForSystem();
269             else mActionBarVisibilityCallback.hideForSystem();
270         }
271         if ((diff & SYSTEM_UI_FLAG_LAYOUT_STABLE) != 0) {
272             if (mActionBarVisibilityCallback != null) {
273                 requestApplyInsets();
274             }
275         }
276     }
277 
278     @Override
279     protected void onWindowVisibilityChanged(int visibility) {
280         super.onWindowVisibilityChanged(visibility);
281         mWindowVisibility = visibility;
282         if (mActionBarVisibilityCallback != null) {
283             mActionBarVisibilityCallback.onWindowVisibilityChanged(visibility);
284         }
285     }
286 
287     private boolean applyInsets(View view, Rect insets, boolean left, boolean top,
288             boolean bottom, boolean right) {
289         boolean changed = false;
290         LayoutParams lp = (LayoutParams)view.getLayoutParams();
291         if (left && lp.leftMargin != insets.left) {
292             changed = true;
293             lp.leftMargin = insets.left;
294         }
295         if (top && lp.topMargin != insets.top) {
296             changed = true;
297             lp.topMargin = insets.top;
298         }
299         if (right && lp.rightMargin != insets.right) {
300             changed = true;
301             lp.rightMargin = insets.right;
302         }
303         if (bottom && lp.bottomMargin != insets.bottom) {
304             changed = true;
305             lp.bottomMargin = insets.bottom;
306         }
307         return changed;
308     }
309 
310     @Override
311     public WindowInsets onApplyWindowInsets(WindowInsets insets) {
312         pullChildren();
313 
314         final int vis = getWindowSystemUiVisibility();
315         final boolean stable = (vis & SYSTEM_UI_FLAG_LAYOUT_STABLE) != 0;
316         final Rect systemInsets = insets.getSystemWindowInsets();
317 
318         // The top and bottom action bars are always within the content area.
319         boolean changed = applyInsets(mActionBarTop, systemInsets, true, true, false, true);
320         if (mActionBarBottom != null) {
321             changed |= applyInsets(mActionBarBottom, systemInsets, true, false, true, true);
322         }
323 
324         mBaseInnerInsets.set(systemInsets);
325         computeFitSystemWindows(mBaseInnerInsets, mBaseContentInsets);
326         if (!mLastBaseContentInsets.equals(mBaseContentInsets)) {
327             changed = true;
328             mLastBaseContentInsets.set(mBaseContentInsets);
329         }
330 
331         if (changed) {
332             requestLayout();
333         }
334 
335         // We don't do any more at this point.  To correctly compute the content/inner
336         // insets in all cases, we need to know the measured size of the various action
337         // bar elements.  onApplyWindowInsets() happens before the measure pass, so we can't
338         // do that here.  Instead we will take this up in onMeasure().
339         return WindowInsets.CONSUMED;
340     }
341 
342     @Override
343     protected LayoutParams generateDefaultLayoutParams() {
344         return new LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT);
345     }
346 
347     @Override
348     public LayoutParams generateLayoutParams(AttributeSet attrs) {
349         return new LayoutParams(getContext(), attrs);
350     }
351 
352     @Override
353     protected ViewGroup.LayoutParams generateLayoutParams(ViewGroup.LayoutParams p) {
354         return new LayoutParams(p);
355     }
356 
357     @Override
358     protected boolean checkLayoutParams(ViewGroup.LayoutParams p) {
359         return p instanceof LayoutParams;
360     }
361 
362     @Override
363     protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
364         pullChildren();
365 
366         int maxHeight = 0;
367         int maxWidth = 0;
368         int childState = 0;
369 
370         int topInset = 0;
371         int bottomInset = 0;
372 
373         measureChildWithMargins(mActionBarTop, widthMeasureSpec, 0, heightMeasureSpec, 0);
374         LayoutParams lp = (LayoutParams) mActionBarTop.getLayoutParams();
375         maxWidth = Math.max(maxWidth,
376                 mActionBarTop.getMeasuredWidth() + lp.leftMargin + lp.rightMargin);
377         maxHeight = Math.max(maxHeight,
378                 mActionBarTop.getMeasuredHeight() + lp.topMargin + lp.bottomMargin);
379         childState = combineMeasuredStates(childState, mActionBarTop.getMeasuredState());
380 
381         // xlarge screen layout doesn't have bottom action bar.
382         if (mActionBarBottom != null) {
383             measureChildWithMargins(mActionBarBottom, widthMeasureSpec, 0, heightMeasureSpec, 0);
384             lp = (LayoutParams) mActionBarBottom.getLayoutParams();
385             maxWidth = Math.max(maxWidth,
386                     mActionBarBottom.getMeasuredWidth() + lp.leftMargin + lp.rightMargin);
387             maxHeight = Math.max(maxHeight,
388                     mActionBarBottom.getMeasuredHeight() + lp.topMargin + lp.bottomMargin);
389             childState = combineMeasuredStates(childState, mActionBarBottom.getMeasuredState());
390         }
391 
392         final int vis = getWindowSystemUiVisibility();
393         final boolean stable = (vis & SYSTEM_UI_FLAG_LAYOUT_STABLE) != 0;
394 
395         if (stable) {
396             // This is the standard space needed for the action bar.  For stable measurement,
397             // we can't depend on the size currently reported by it -- this must remain constant.
398             topInset = mActionBarHeight;
399             if (mHasNonEmbeddedTabs) {
400                 final View tabs = mActionBarTop.getTabContainer();
401                 if (tabs != null) {
402                     // If tabs are not embedded, increase space on top to account for them.
403                     topInset += mActionBarHeight;
404                 }
405             }
406         } else if (mActionBarTop.getVisibility() != GONE) {
407             // This is the space needed on top of the window for all of the action bar
408             // and tabs.
409             topInset = mActionBarTop.getMeasuredHeight();
410         }
411 
412         if (mDecorToolbar.isSplit()) {
413             // If action bar is split, adjust bottom insets for it.
414             if (mActionBarBottom != null) {
415                 if (stable) {
416                     bottomInset = mActionBarHeight;
417                 } else {
418                     bottomInset = mActionBarBottom.getMeasuredHeight();
419                 }
420             }
421         }
422 
423         // If the window has not requested system UI layout flags, we need to
424         // make sure its content is not being covered by system UI...  though it
425         // will still be covered by the action bar if they have requested it to
426         // overlay.
427         mContentInsets.set(mBaseContentInsets);
428         mInnerInsets.set(mBaseInnerInsets);
429         if (!mOverlayMode && !stable) {
430             mContentInsets.top += topInset;
431             mContentInsets.bottom += bottomInset;
432         } else {
433             mInnerInsets.top += topInset;
434             mInnerInsets.bottom += bottomInset;
435         }
436         applyInsets(mContent, mContentInsets, true, true, true, true);
437 
438         if (!mLastInnerInsets.equals(mInnerInsets)) {
439             // If the inner insets have changed, we need to dispatch this down to
440             // the app's fitSystemWindows().  We do this before measuring the content
441             // view to keep the same semantics as the normal fitSystemWindows() call.
442             mLastInnerInsets.set(mInnerInsets);
443             mContent.dispatchApplyWindowInsets(new WindowInsets(mInnerInsets));
444         }
445 
446         measureChildWithMargins(mContent, widthMeasureSpec, 0, heightMeasureSpec, 0);
447         lp = (LayoutParams) mContent.getLayoutParams();
448         maxWidth = Math.max(maxWidth,
449                 mContent.getMeasuredWidth() + lp.leftMargin + lp.rightMargin);
450         maxHeight = Math.max(maxHeight,
451                 mContent.getMeasuredHeight() + lp.topMargin + lp.bottomMargin);
452         childState = combineMeasuredStates(childState, mContent.getMeasuredState());
453 
454         // Account for padding too
455         maxWidth += getPaddingLeft() + getPaddingRight();
456         maxHeight += getPaddingTop() + getPaddingBottom();
457 
458         // Check against our minimum height and width
459         maxHeight = Math.max(maxHeight, getSuggestedMinimumHeight());
460         maxWidth = Math.max(maxWidth, getSuggestedMinimumWidth());
461 
462         setMeasuredDimension(resolveSizeAndState(maxWidth, widthMeasureSpec, childState),
463                 resolveSizeAndState(maxHeight, heightMeasureSpec,
464                         childState << MEASURED_HEIGHT_STATE_SHIFT));
465     }
466 
467     @Override
468     protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
469         final int count = getChildCount();
470 
471         final int parentLeft = getPaddingLeft();
472         final int parentRight = right - left - getPaddingRight();
473 
474         final int parentTop = getPaddingTop();
475         final int parentBottom = bottom - top - getPaddingBottom();
476 
477         for (int i = 0; i < count; i++) {
478             final View child = getChildAt(i);
479             if (child.getVisibility() != GONE) {
480                 final LayoutParams lp = (LayoutParams) child.getLayoutParams();
481 
482                 final int width = child.getMeasuredWidth();
483                 final int height = child.getMeasuredHeight();
484 
485                 int childLeft = parentLeft + lp.leftMargin;
486                 int childTop;
487                 if (child == mActionBarBottom) {
488                     childTop = parentBottom - height - lp.bottomMargin;
489                 } else {
490                     childTop = parentTop + lp.topMargin;
491                 }
492 
493                 child.layout(childLeft, childTop, childLeft + width, childTop + height);
494             }
495         }
496     }
497 
498     @Override
499     public void draw(Canvas c) {
500         super.draw(c);
501         if (mWindowContentOverlay != null && !mIgnoreWindowContentOverlay) {
502             final int top = mActionBarTop.getVisibility() == VISIBLE ?
503                     (int) (mActionBarTop.getBottom() + mActionBarTop.getTranslationY() + 0.5f) : 0;
504             mWindowContentOverlay.setBounds(0, top, getWidth(),
505                     top + mWindowContentOverlay.getIntrinsicHeight());
506             mWindowContentOverlay.draw(c);
507         }
508     }
509 
510     @Override
511     public boolean shouldDelayChildPressedState() {
512         return false;
513     }
514 
515     @Override
516     public boolean onStartNestedScroll(View child, View target, int axes) {
517         if ((axes & SCROLL_AXIS_VERTICAL) == 0 || mActionBarTop.getVisibility() != VISIBLE) {
518             return false;
519         }
520         return mHideOnContentScroll;
521     }
522 
523     @Override
524     public void onNestedScrollAccepted(View child, View target, int axes) {
525         super.onNestedScrollAccepted(child, target, axes);
526         mHideOnContentScrollReference = getActionBarHideOffset();
527         haltActionBarHideOffsetAnimations();
528         if (mActionBarVisibilityCallback != null) {
529             mActionBarVisibilityCallback.onContentScrollStarted();
530         }
531     }
532 
533     @Override
534     public void onNestedScroll(View target, int dxConsumed, int dyConsumed,
535             int dxUnconsumed, int dyUnconsumed) {
536         mHideOnContentScrollReference += dyConsumed;
537         setActionBarHideOffset(mHideOnContentScrollReference);
538     }
539 
540     @Override
541     public void onStopNestedScroll(View target) {
542         super.onStopNestedScroll(target);
543         if (mHideOnContentScroll && !mAnimatingForFling) {
544             if (mHideOnContentScrollReference <= mActionBarTop.getHeight()) {
545                 postRemoveActionBarHideOffset();
546             } else {
547                 postAddActionBarHideOffset();
548             }
549         }
550         if (mActionBarVisibilityCallback != null) {
551             mActionBarVisibilityCallback.onContentScrollStopped();
552         }
553     }
554 
555     @Override
556     public boolean onNestedFling(View target, float velocityX, float velocityY, boolean consumed) {
557         if (!mHideOnContentScroll || !consumed) {
558             return false;
559         }
560         if (shouldHideActionBarOnFling(velocityX, velocityY)) {
561             addActionBarHideOffset();
562         } else {
563             removeActionBarHideOffset();
564         }
565         mAnimatingForFling = true;
566         return true;
567     }
568 
569     void pullChildren() {
570         if (mContent == null) {
571             mContent = findViewById(com.android.internal.R.id.content);
572             mActionBarTop = (ActionBarContainer) findViewById(
573                     com.android.internal.R.id.action_bar_container);
574             mDecorToolbar = getDecorToolbar(findViewById(com.android.internal.R.id.action_bar));
575             mActionBarBottom = (ActionBarContainer) findViewById(
576                     com.android.internal.R.id.split_action_bar);
577         }
578     }
579 
580     private DecorToolbar getDecorToolbar(View view) {
581         if (view instanceof DecorToolbar) {
582             return (DecorToolbar) view;
583         } else if (view instanceof Toolbar) {
584             return ((Toolbar) view).getWrapper();
585         } else {
586             throw new IllegalStateException("Can't make a decor toolbar out of " +
587                     view.getClass().getSimpleName());
588         }
589     }
590 
591     public void setHideOnContentScrollEnabled(boolean hideOnContentScroll) {
592         if (hideOnContentScroll != mHideOnContentScroll) {
593             mHideOnContentScroll = hideOnContentScroll;
594             if (!hideOnContentScroll) {
595                 stopNestedScroll();
596                 haltActionBarHideOffsetAnimations();
597                 setActionBarHideOffset(0);
598             }
599         }
600     }
601 
602     public boolean isHideOnContentScrollEnabled() {
603         return mHideOnContentScroll;
604     }
605 
606     public int getActionBarHideOffset() {
607         return mActionBarTop != null ? -((int) mActionBarTop.getTranslationY()) : 0;
608     }
609 
610     public void setActionBarHideOffset(int offset) {
611         haltActionBarHideOffsetAnimations();
612         final int topHeight = mActionBarTop.getHeight();
613         offset = Math.max(0, Math.min(offset, topHeight));
614         mActionBarTop.setTranslationY(-offset);
615         if (mActionBarBottom != null && mActionBarBottom.getVisibility() != GONE) {
616             // Match the hide offset proportionally for a split bar
617             final float fOffset = (float) offset / topHeight;
618             final int bOffset = (int) (mActionBarBottom.getHeight() * fOffset);
619             mActionBarBottom.setTranslationY(bOffset);
620         }
621     }
622 
623     private void haltActionBarHideOffsetAnimations() {
624         removeCallbacks(mRemoveActionBarHideOffset);
625         removeCallbacks(mAddActionBarHideOffset);
626         if (mCurrentActionBarTopAnimator != null) {
627             mCurrentActionBarTopAnimator.cancel();
628         }
629         if (mCurrentActionBarBottomAnimator != null) {
630             mCurrentActionBarBottomAnimator.cancel();
631         }
632     }
633 
634     private void postRemoveActionBarHideOffset() {
635         haltActionBarHideOffsetAnimations();
636         postDelayed(mRemoveActionBarHideOffset, ACTION_BAR_ANIMATE_DELAY);
637     }
638 
639     private void postAddActionBarHideOffset() {
640         haltActionBarHideOffsetAnimations();
641         postDelayed(mAddActionBarHideOffset, ACTION_BAR_ANIMATE_DELAY);
642     }
643 
644     private void removeActionBarHideOffset() {
645         haltActionBarHideOffsetAnimations();
646         mRemoveActionBarHideOffset.run();
647     }
648 
649     private void addActionBarHideOffset() {
650         haltActionBarHideOffsetAnimations();
651         mAddActionBarHideOffset.run();
652     }
653 
654     private boolean shouldHideActionBarOnFling(float velocityX, float velocityY) {
655         mFlingEstimator.fling(0, 0, 0, (int) velocityY, 0, 0, Integer.MIN_VALUE, Integer.MAX_VALUE);
656         final int finalY = mFlingEstimator.getFinalY();
657         return finalY > mActionBarTop.getHeight();
658     }
659 
660     @Override
661     public void setWindowCallback(Window.Callback cb) {
662         pullChildren();
663         mDecorToolbar.setWindowCallback(cb);
664     }
665 
666     @Override
667     public void setWindowTitle(CharSequence title) {
668         pullChildren();
669         mDecorToolbar.setWindowTitle(title);
670     }
671 
672     @Override
673     public CharSequence getTitle() {
674         pullChildren();
675         return mDecorToolbar.getTitle();
676     }
677 
678     @Override
679     public void initFeature(int windowFeature) {
680         pullChildren();
681         switch (windowFeature) {
682             case Window.FEATURE_PROGRESS:
683                 mDecorToolbar.initProgress();
684                 break;
685             case Window.FEATURE_INDETERMINATE_PROGRESS:
686                 mDecorToolbar.initIndeterminateProgress();
687                 break;
688             case Window.FEATURE_ACTION_BAR_OVERLAY:
689                 setOverlayMode(true);
690                 break;
691         }
692     }
693 
694     @Override
695     public void setUiOptions(int uiOptions) {
696         boolean splitActionBar = false;
697         final boolean splitWhenNarrow =
698                 (uiOptions & ActivityInfo.UIOPTION_SPLIT_ACTION_BAR_WHEN_NARROW) != 0;
699         if (splitWhenNarrow) {
700             splitActionBar = getContext().getResources().getBoolean(
701                     com.android.internal.R.bool.split_action_bar_is_narrow);
702         }
703         if (splitActionBar) {
704             pullChildren();
705             if (mActionBarBottom != null && mDecorToolbar.canSplit()) {
706                 mDecorToolbar.setSplitView(mActionBarBottom);
707                 mDecorToolbar.setSplitToolbar(splitActionBar);
708                 mDecorToolbar.setSplitWhenNarrow(splitWhenNarrow);
709 
710                 final ActionBarContextView cab = (ActionBarContextView) findViewById(
711                         com.android.internal.R.id.action_context_bar);
712                 cab.setSplitView(mActionBarBottom);
713                 cab.setSplitToolbar(splitActionBar);
714                 cab.setSplitWhenNarrow(splitWhenNarrow);
715             } else if (splitActionBar) {
716                 Log.e(TAG, "Requested split action bar with " +
717                         "incompatible window decor! Ignoring request.");
718             }
719         }
720     }
721 
722     @Override
723     public boolean hasIcon() {
724         pullChildren();
725         return mDecorToolbar.hasIcon();
726     }
727 
728     @Override
729     public boolean hasLogo() {
730         pullChildren();
731         return mDecorToolbar.hasLogo();
732     }
733 
734     @Override
735     public void setIcon(int resId) {
736         pullChildren();
737         mDecorToolbar.setIcon(resId);
738     }
739 
740     @Override
741     public void setIcon(Drawable d) {
742         pullChildren();
743         mDecorToolbar.setIcon(d);
744     }
745 
746     @Override
747     public void setLogo(int resId) {
748         pullChildren();
749         mDecorToolbar.setLogo(resId);
750     }
751 
752     @Override
753     public boolean canShowOverflowMenu() {
754         pullChildren();
755         return mDecorToolbar.canShowOverflowMenu();
756     }
757 
758     @Override
759     public boolean isOverflowMenuShowing() {
760         pullChildren();
761         return mDecorToolbar.isOverflowMenuShowing();
762     }
763 
764     @Override
765     public boolean isOverflowMenuShowPending() {
766         pullChildren();
767         return mDecorToolbar.isOverflowMenuShowPending();
768     }
769 
770     @Override
771     public boolean showOverflowMenu() {
772         pullChildren();
773         return mDecorToolbar.showOverflowMenu();
774     }
775 
776     @Override
777     public boolean hideOverflowMenu() {
778         pullChildren();
779         return mDecorToolbar.hideOverflowMenu();
780     }
781 
782     @Override
783     public void setMenuPrepared() {
784         pullChildren();
785         mDecorToolbar.setMenuPrepared();
786     }
787 
788     @Override
789     public void setMenu(Menu menu, MenuPresenter.Callback cb) {
790         pullChildren();
791         mDecorToolbar.setMenu(menu, cb);
792     }
793 
794     @Override
795     public void saveToolbarHierarchyState(SparseArray<Parcelable> toolbarStates) {
796         pullChildren();
797         mDecorToolbar.saveHierarchyState(toolbarStates);
798     }
799 
800     @Override
801     public void restoreToolbarHierarchyState(SparseArray<Parcelable> toolbarStates) {
802         pullChildren();
803         mDecorToolbar.restoreHierarchyState(toolbarStates);
804     }
805 
806     @Override
807     public void dismissPopups() {
808         pullChildren();
809         mDecorToolbar.dismissPopupMenus();
810     }
811 
812     public static class LayoutParams extends MarginLayoutParams {
813         public LayoutParams(Context c, AttributeSet attrs) {
814             super(c, attrs);
815         }
816 
817         public LayoutParams(int width, int height) {
818             super(width, height);
819         }
820 
821         public LayoutParams(ViewGroup.LayoutParams source) {
822             super(source);
823         }
824 
825         public LayoutParams(ViewGroup.MarginLayoutParams source) {
826             super(source);
827         }
828     }
829 
830     public interface ActionBarVisibilityCallback {
831         void onWindowVisibilityChanged(int visibility);
832         void showForSystem();
833         void hideForSystem();
834         void enableContentAnimations(boolean enable);
835         void onContentScrollStarted();
836         void onContentScrollStopped();
837     }
838 }
839