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