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