1/*
2 * Copyright (C) 2014 The Android Open Source Project
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 *      http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16
17package android.support.v7.app;
18
19import android.app.Activity;
20import android.app.Dialog;
21import android.content.Context;
22import android.content.res.Configuration;
23import android.content.res.Resources;
24import android.content.res.TypedArray;
25import android.graphics.drawable.Drawable;
26import android.os.Build;
27import android.support.annotation.RestrictTo;
28import android.support.v4.app.FragmentActivity;
29import android.support.v4.app.FragmentTransaction;
30import android.support.v4.view.ViewCompat;
31import android.support.v4.view.ViewPropertyAnimatorCompat;
32import android.support.v4.view.ViewPropertyAnimatorListener;
33import android.support.v4.view.ViewPropertyAnimatorListenerAdapter;
34import android.support.v4.view.ViewPropertyAnimatorUpdateListener;
35import android.support.v7.appcompat.R;
36import android.support.v7.content.res.AppCompatResources;
37import android.support.v7.view.ActionBarPolicy;
38import android.support.v7.view.ActionMode;
39import android.support.v7.view.SupportMenuInflater;
40import android.support.v7.view.ViewPropertyAnimatorCompatSet;
41import android.support.v7.view.menu.MenuBuilder;
42import android.support.v7.view.menu.MenuPopupHelper;
43import android.support.v7.view.menu.SubMenuBuilder;
44import android.support.v7.widget.ActionBarContainer;
45import android.support.v7.widget.ActionBarContextView;
46import android.support.v7.widget.ActionBarOverlayLayout;
47import android.support.v7.widget.DecorToolbar;
48import android.support.v7.widget.ScrollingTabContainerView;
49import android.support.v7.widget.Toolbar;
50import android.util.TypedValue;
51import android.view.ContextThemeWrapper;
52import android.view.LayoutInflater;
53import android.view.Menu;
54import android.view.MenuInflater;
55import android.view.MenuItem;
56import android.view.View;
57import android.view.ViewGroup;
58import android.view.ViewParent;
59import android.view.Window;
60import android.view.accessibility.AccessibilityEvent;
61import android.view.animation.AccelerateInterpolator;
62import android.view.animation.DecelerateInterpolator;
63import android.view.animation.Interpolator;
64import android.widget.SpinnerAdapter;
65
66import java.lang.ref.WeakReference;
67import java.util.ArrayList;
68
69import static android.support.annotation.RestrictTo.Scope.GROUP_ID;
70
71/**
72 * WindowDecorActionBar is the ActionBar implementation used
73 * by devices of all screen sizes as part of the window decor layout.
74 *
75 * @hide
76 */
77@RestrictTo(GROUP_ID)
78public class WindowDecorActionBar extends ActionBar implements
79        ActionBarOverlayLayout.ActionBarVisibilityCallback {
80    private static final String TAG = "WindowDecorActionBar";
81
82    private static final Interpolator sHideInterpolator = new AccelerateInterpolator();
83    private static final Interpolator sShowInterpolator = new DecelerateInterpolator();
84
85    /**
86     * Only allow show/hide animations on ICS+, as that is what ViewPropertyAnimatorCompat supports
87     */
88    private static final boolean ALLOW_SHOW_HIDE_ANIMATIONS = Build.VERSION.SDK_INT >= 14;
89
90    Context mContext;
91    private Context mThemedContext;
92    private Activity mActivity;
93    private Dialog mDialog;
94
95    ActionBarOverlayLayout mOverlayLayout;
96    ActionBarContainer mContainerView;
97    DecorToolbar mDecorToolbar;
98    ActionBarContextView mContextView;
99    View mContentView;
100    ScrollingTabContainerView mTabScrollView;
101
102    private ArrayList<TabImpl> mTabs = new ArrayList<TabImpl>();
103
104    private TabImpl mSelectedTab;
105    private int mSavedTabPosition = INVALID_POSITION;
106
107    private boolean mDisplayHomeAsUpSet;
108
109    ActionModeImpl mActionMode;
110    ActionMode mDeferredDestroyActionMode;
111    ActionMode.Callback mDeferredModeDestroyCallback;
112
113    private boolean mLastMenuVisibility;
114    private ArrayList<OnMenuVisibilityListener> mMenuVisibilityListeners =
115            new ArrayList<OnMenuVisibilityListener>();
116
117    private static final int INVALID_POSITION = -1;
118
119    // The fade duration for toolbar and action bar when entering/exiting action mode.
120    private static final long FADE_OUT_DURATION_MS = 100;
121    private static final long FADE_IN_DURATION_MS = 200;
122
123    private boolean mHasEmbeddedTabs;
124
125    private int mCurWindowVisibility = View.VISIBLE;
126
127    boolean mContentAnimations = true;
128    boolean mHiddenByApp;
129    boolean mHiddenBySystem;
130    private boolean mShowingForMode;
131
132    private boolean mNowShowing = true;
133
134    ViewPropertyAnimatorCompatSet mCurrentShowAnim;
135    private boolean mShowHideAnimationEnabled;
136    boolean mHideOnContentScroll;
137
138    final ViewPropertyAnimatorListener mHideListener = new ViewPropertyAnimatorListenerAdapter() {
139        @Override
140        public void onAnimationEnd(View view) {
141            if (mContentAnimations && mContentView != null) {
142                ViewCompat.setTranslationY(mContentView, 0f);
143                ViewCompat.setTranslationY(mContainerView, 0f);
144            }
145            mContainerView.setVisibility(View.GONE);
146            mContainerView.setTransitioning(false);
147            mCurrentShowAnim = null;
148            completeDeferredDestroyActionMode();
149            if (mOverlayLayout != null) {
150                ViewCompat.requestApplyInsets(mOverlayLayout);
151            }
152        }
153    };
154
155    final ViewPropertyAnimatorListener mShowListener = new ViewPropertyAnimatorListenerAdapter() {
156        @Override
157        public void onAnimationEnd(View view) {
158            mCurrentShowAnim = null;
159            mContainerView.requestLayout();
160        }
161    };
162
163    final ViewPropertyAnimatorUpdateListener mUpdateListener =
164            new ViewPropertyAnimatorUpdateListener() {
165                @Override
166                public void onAnimationUpdate(View view) {
167                    final ViewParent parent = mContainerView.getParent();
168                    ((View) parent).invalidate();
169                }
170            };
171
172    public WindowDecorActionBar(Activity activity, boolean overlayMode) {
173        mActivity = activity;
174        Window window = activity.getWindow();
175        View decor = window.getDecorView();
176        init(decor);
177        if (!overlayMode) {
178            mContentView = decor.findViewById(android.R.id.content);
179        }
180    }
181
182    public WindowDecorActionBar(Dialog dialog) {
183        mDialog = dialog;
184        init(dialog.getWindow().getDecorView());
185    }
186
187    /**
188     * Only for edit mode.
189     * @hide
190     */
191    @RestrictTo(GROUP_ID)
192    public WindowDecorActionBar(View layout) {
193        assert layout.isInEditMode();
194        init(layout);
195    }
196
197    private void init(View decor) {
198        mOverlayLayout = (ActionBarOverlayLayout) decor.findViewById(R.id.decor_content_parent);
199        if (mOverlayLayout != null) {
200            mOverlayLayout.setActionBarVisibilityCallback(this);
201        }
202        mDecorToolbar = getDecorToolbar(decor.findViewById(R.id.action_bar));
203        mContextView = (ActionBarContextView) decor.findViewById(
204                R.id.action_context_bar);
205        mContainerView = (ActionBarContainer) decor.findViewById(
206                R.id.action_bar_container);
207
208        if (mDecorToolbar == null || mContextView == null || mContainerView == null) {
209            throw new IllegalStateException(getClass().getSimpleName() + " can only be used " +
210                    "with a compatible window decor layout");
211        }
212
213        mContext = mDecorToolbar.getContext();
214
215        // This was initially read from the action bar style
216        final int current = mDecorToolbar.getDisplayOptions();
217        final boolean homeAsUp = (current & DISPLAY_HOME_AS_UP) != 0;
218        if (homeAsUp) {
219            mDisplayHomeAsUpSet = true;
220        }
221
222        ActionBarPolicy abp = ActionBarPolicy.get(mContext);
223        setHomeButtonEnabled(abp.enableHomeButtonByDefault() || homeAsUp);
224        setHasEmbeddedTabs(abp.hasEmbeddedTabs());
225
226        final TypedArray a = mContext.obtainStyledAttributes(null,
227                R.styleable.ActionBar,
228                R.attr.actionBarStyle, 0);
229        if (a.getBoolean(R.styleable.ActionBar_hideOnContentScroll, false)) {
230            setHideOnContentScrollEnabled(true);
231        }
232        final int elevation = a.getDimensionPixelSize(R.styleable.ActionBar_elevation, 0);
233        if (elevation != 0) {
234            setElevation(elevation);
235        }
236        a.recycle();
237    }
238
239    private DecorToolbar getDecorToolbar(View view) {
240        if (view instanceof DecorToolbar) {
241            return (DecorToolbar) view;
242        } else if (view instanceof Toolbar) {
243            return ((Toolbar) view).getWrapper();
244        } else {
245            throw new IllegalStateException("Can't make a decor toolbar out of " +
246                    view != null ? view.getClass().getSimpleName() : "null");
247        }
248    }
249
250    @Override
251    public void setElevation(float elevation) {
252        ViewCompat.setElevation(mContainerView, elevation);
253    }
254
255    @Override
256    public float getElevation() {
257        return ViewCompat.getElevation(mContainerView);
258    }
259
260    public void onConfigurationChanged(Configuration newConfig) {
261        setHasEmbeddedTabs(ActionBarPolicy.get(mContext).hasEmbeddedTabs());
262    }
263
264    private void setHasEmbeddedTabs(boolean hasEmbeddedTabs) {
265        mHasEmbeddedTabs = hasEmbeddedTabs;
266        // Switch tab layout configuration if needed
267        if (!mHasEmbeddedTabs) {
268            mDecorToolbar.setEmbeddedTabView(null);
269            mContainerView.setTabContainer(mTabScrollView);
270        } else {
271            mContainerView.setTabContainer(null);
272            mDecorToolbar.setEmbeddedTabView(mTabScrollView);
273        }
274        final boolean isInTabMode = getNavigationMode() == NAVIGATION_MODE_TABS;
275        if (mTabScrollView != null) {
276            if (isInTabMode) {
277                mTabScrollView.setVisibility(View.VISIBLE);
278                if (mOverlayLayout != null) {
279                    ViewCompat.requestApplyInsets(mOverlayLayout);
280                }
281            } else {
282                mTabScrollView.setVisibility(View.GONE);
283            }
284        }
285        mDecorToolbar.setCollapsible(!mHasEmbeddedTabs && isInTabMode);
286        mOverlayLayout.setHasNonEmbeddedTabs(!mHasEmbeddedTabs && isInTabMode);
287    }
288
289    private void ensureTabsExist() {
290        if (mTabScrollView != null) {
291            return;
292        }
293
294        ScrollingTabContainerView tabScroller = new ScrollingTabContainerView(mContext);
295
296        if (mHasEmbeddedTabs) {
297            tabScroller.setVisibility(View.VISIBLE);
298            mDecorToolbar.setEmbeddedTabView(tabScroller);
299        } else {
300            if (getNavigationMode() == NAVIGATION_MODE_TABS) {
301                tabScroller.setVisibility(View.VISIBLE);
302                if (mOverlayLayout != null) {
303                    ViewCompat.requestApplyInsets(mOverlayLayout);
304                }
305            } else {
306                tabScroller.setVisibility(View.GONE);
307            }
308            mContainerView.setTabContainer(tabScroller);
309        }
310        mTabScrollView = tabScroller;
311    }
312
313    void completeDeferredDestroyActionMode() {
314        if (mDeferredModeDestroyCallback != null) {
315            mDeferredModeDestroyCallback.onDestroyActionMode(mDeferredDestroyActionMode);
316            mDeferredDestroyActionMode = null;
317            mDeferredModeDestroyCallback = null;
318        }
319    }
320
321    public void onWindowVisibilityChanged(int visibility) {
322        mCurWindowVisibility = visibility;
323    }
324
325    /**
326     * Enables or disables animation between show/hide states.
327     * If animation is disabled using this method, animations in progress
328     * will be finished.
329     *
330     * @param enabled true to animate, false to not animate.
331     */
332    public void setShowHideAnimationEnabled(boolean enabled) {
333        mShowHideAnimationEnabled = enabled;
334        if (!enabled && mCurrentShowAnim != null) {
335            mCurrentShowAnim.cancel();
336        }
337    }
338
339    public void addOnMenuVisibilityListener(OnMenuVisibilityListener listener) {
340        mMenuVisibilityListeners.add(listener);
341    }
342
343    public void removeOnMenuVisibilityListener(OnMenuVisibilityListener listener) {
344        mMenuVisibilityListeners.remove(listener);
345    }
346
347    public void dispatchMenuVisibilityChanged(boolean isVisible) {
348        if (isVisible == mLastMenuVisibility) {
349            return;
350        }
351        mLastMenuVisibility = isVisible;
352
353        final int count = mMenuVisibilityListeners.size();
354        for (int i = 0; i < count; i++) {
355            mMenuVisibilityListeners.get(i).onMenuVisibilityChanged(isVisible);
356        }
357    }
358
359    @Override
360    public void setCustomView(int resId) {
361        setCustomView(LayoutInflater.from(getThemedContext()).inflate(resId,
362                mDecorToolbar.getViewGroup(), false));
363    }
364
365    @Override
366    public void setDisplayUseLogoEnabled(boolean useLogo) {
367        setDisplayOptions(useLogo ? DISPLAY_USE_LOGO : 0, DISPLAY_USE_LOGO);
368    }
369
370    @Override
371    public void setDisplayShowHomeEnabled(boolean showHome) {
372        setDisplayOptions(showHome ? DISPLAY_SHOW_HOME : 0, DISPLAY_SHOW_HOME);
373    }
374
375    @Override
376    public void setDisplayHomeAsUpEnabled(boolean showHomeAsUp) {
377        setDisplayOptions(showHomeAsUp ? DISPLAY_HOME_AS_UP : 0, DISPLAY_HOME_AS_UP);
378    }
379
380    @Override
381    public void setDisplayShowTitleEnabled(boolean showTitle) {
382        setDisplayOptions(showTitle ? DISPLAY_SHOW_TITLE : 0, DISPLAY_SHOW_TITLE);
383    }
384
385    @Override
386    public void setDisplayShowCustomEnabled(boolean showCustom) {
387        setDisplayOptions(showCustom ? DISPLAY_SHOW_CUSTOM : 0, DISPLAY_SHOW_CUSTOM);
388    }
389
390    @Override
391    public void setHomeButtonEnabled(boolean enable) {
392        mDecorToolbar.setHomeButtonEnabled(enable);
393    }
394
395    @Override
396    public void setTitle(int resId) {
397        setTitle(mContext.getString(resId));
398    }
399
400    @Override
401    public void setSubtitle(int resId) {
402        setSubtitle(mContext.getString(resId));
403    }
404
405    public void setSelectedNavigationItem(int position) {
406        switch (mDecorToolbar.getNavigationMode()) {
407            case NAVIGATION_MODE_TABS:
408                selectTab(mTabs.get(position));
409                break;
410            case NAVIGATION_MODE_LIST:
411                mDecorToolbar.setDropdownSelectedPosition(position);
412                break;
413            default:
414                throw new IllegalStateException(
415                        "setSelectedNavigationIndex not valid for current navigation mode");
416        }
417    }
418
419    public void removeAllTabs() {
420        cleanupTabs();
421    }
422
423    private void cleanupTabs() {
424        if (mSelectedTab != null) {
425            selectTab(null);
426        }
427        mTabs.clear();
428        if (mTabScrollView != null) {
429            mTabScrollView.removeAllTabs();
430        }
431        mSavedTabPosition = INVALID_POSITION;
432    }
433
434    public void setTitle(CharSequence title) {
435        mDecorToolbar.setTitle(title);
436    }
437
438    @Override
439    public void setWindowTitle(CharSequence title) {
440        mDecorToolbar.setWindowTitle(title);
441    }
442
443    @Override
444    public boolean requestFocus() {
445        final ViewGroup viewGroup = mDecorToolbar.getViewGroup();
446        if (viewGroup != null && !viewGroup.hasFocus()) {
447            viewGroup.requestFocus();
448            return true;
449        }
450        return false;
451    }
452
453    public void setSubtitle(CharSequence subtitle) {
454        mDecorToolbar.setSubtitle(subtitle);
455    }
456
457    public void setDisplayOptions(int options) {
458        if ((options & DISPLAY_HOME_AS_UP) != 0) {
459            mDisplayHomeAsUpSet = true;
460        }
461        mDecorToolbar.setDisplayOptions(options);
462    }
463
464    public void setDisplayOptions(int options, int mask) {
465        final int current = mDecorToolbar.getDisplayOptions();
466        if ((mask & DISPLAY_HOME_AS_UP) != 0) {
467            mDisplayHomeAsUpSet = true;
468        }
469        mDecorToolbar.setDisplayOptions((options & mask) | (current & ~mask));
470    }
471
472    public void setBackgroundDrawable(Drawable d) {
473        mContainerView.setPrimaryBackground(d);
474    }
475
476    public void setStackedBackgroundDrawable(Drawable d) {
477        mContainerView.setStackedBackground(d);
478    }
479
480    public void setSplitBackgroundDrawable(Drawable d) {
481        // no-op. We don't support split action bars
482    }
483
484    public View getCustomView() {
485        return mDecorToolbar.getCustomView();
486    }
487
488    public CharSequence getTitle() {
489        return mDecorToolbar.getTitle();
490    }
491
492    public CharSequence getSubtitle() {
493        return mDecorToolbar.getSubtitle();
494    }
495
496    public int getNavigationMode() {
497        return mDecorToolbar.getNavigationMode();
498    }
499
500    public int getDisplayOptions() {
501        return mDecorToolbar.getDisplayOptions();
502    }
503
504    public ActionMode startActionMode(ActionMode.Callback callback) {
505        if (mActionMode != null) {
506            mActionMode.finish();
507        }
508
509        mOverlayLayout.setHideOnContentScrollEnabled(false);
510        mContextView.killMode();
511        ActionModeImpl mode = new ActionModeImpl(mContextView.getContext(), callback);
512        if (mode.dispatchOnCreate()) {
513            // This needs to be set before invalidate() so that it calls
514            // onPrepareActionMode()
515            mActionMode = mode;
516            mode.invalidate();
517            mContextView.initForMode(mode);
518            animateToMode(true);
519            mContextView.sendAccessibilityEvent(AccessibilityEvent.TYPE_WINDOW_STATE_CHANGED);
520            return mode;
521        }
522        return null;
523    }
524
525    private void configureTab(Tab tab, int position) {
526        final TabImpl tabi = (TabImpl) tab;
527        final ActionBar.TabListener callback = tabi.getCallback();
528
529        if (callback == null) {
530            throw new IllegalStateException("Action Bar Tab must have a Callback");
531        }
532
533        tabi.setPosition(position);
534        mTabs.add(position, tabi);
535
536        final int count = mTabs.size();
537        for (int i = position + 1; i < count; i++) {
538            mTabs.get(i).setPosition(i);
539        }
540    }
541
542    @Override
543    public void addTab(Tab tab) {
544        addTab(tab, mTabs.isEmpty());
545    }
546
547    @Override
548    public void addTab(Tab tab, int position) {
549        addTab(tab, position, mTabs.isEmpty());
550    }
551
552    @Override
553    public void addTab(Tab tab, boolean setSelected) {
554        ensureTabsExist();
555        mTabScrollView.addTab(tab, setSelected);
556        configureTab(tab, mTabs.size());
557        if (setSelected) {
558            selectTab(tab);
559        }
560    }
561
562    @Override
563    public void addTab(Tab tab, int position, boolean setSelected) {
564        ensureTabsExist();
565        mTabScrollView.addTab(tab, position, setSelected);
566        configureTab(tab, position);
567        if (setSelected) {
568            selectTab(tab);
569        }
570    }
571
572    @Override
573    public Tab newTab() {
574        return new TabImpl();
575    }
576
577    @Override
578    public void removeTab(Tab tab) {
579        removeTabAt(tab.getPosition());
580    }
581
582    @Override
583    public void removeTabAt(int position) {
584        if (mTabScrollView == null) {
585            // No tabs around to remove
586            return;
587        }
588
589        int selectedTabPosition = mSelectedTab != null
590                ? mSelectedTab.getPosition() : mSavedTabPosition;
591        mTabScrollView.removeTabAt(position);
592        TabImpl removedTab = mTabs.remove(position);
593        if (removedTab != null) {
594            removedTab.setPosition(-1);
595        }
596
597        final int newTabCount = mTabs.size();
598        for (int i = position; i < newTabCount; i++) {
599            mTabs.get(i).setPosition(i);
600        }
601
602        if (selectedTabPosition == position) {
603            selectTab(mTabs.isEmpty() ? null : mTabs.get(Math.max(0, position - 1)));
604        }
605    }
606
607    @Override
608    public void selectTab(Tab tab) {
609        if (getNavigationMode() != NAVIGATION_MODE_TABS) {
610            mSavedTabPosition = tab != null ? tab.getPosition() : INVALID_POSITION;
611            return;
612        }
613
614        final FragmentTransaction trans;
615        if (mActivity instanceof FragmentActivity && !mDecorToolbar.getViewGroup().isInEditMode()) {
616            // If we're not in edit mode and our Activity is a FragmentActivity, start a tx
617            trans = ((FragmentActivity) mActivity).getSupportFragmentManager()
618                    .beginTransaction().disallowAddToBackStack();
619        } else {
620            trans = null;
621        }
622
623        if (mSelectedTab == tab) {
624            if (mSelectedTab != null) {
625                mSelectedTab.getCallback().onTabReselected(mSelectedTab, trans);
626                mTabScrollView.animateToTab(tab.getPosition());
627            }
628        } else {
629            mTabScrollView.setTabSelected(tab != null ? tab.getPosition() : Tab.INVALID_POSITION);
630            if (mSelectedTab != null) {
631                mSelectedTab.getCallback().onTabUnselected(mSelectedTab, trans);
632            }
633            mSelectedTab = (TabImpl) tab;
634            if (mSelectedTab != null) {
635                mSelectedTab.getCallback().onTabSelected(mSelectedTab, trans);
636            }
637        }
638
639        if (trans != null && !trans.isEmpty()) {
640            trans.commit();
641        }
642    }
643
644    @Override
645    public Tab getSelectedTab() {
646        return mSelectedTab;
647    }
648
649    @Override
650    public int getHeight() {
651        return mContainerView.getHeight();
652    }
653
654    public void enableContentAnimations(boolean enabled) {
655        mContentAnimations = enabled;
656    }
657
658    @Override
659    public void show() {
660        if (mHiddenByApp) {
661            mHiddenByApp = false;
662            updateVisibility(false);
663        }
664    }
665
666    private void showForActionMode() {
667        if (!mShowingForMode) {
668            mShowingForMode = true;
669            if (mOverlayLayout != null) {
670                mOverlayLayout.setShowingForActionMode(true);
671            }
672            updateVisibility(false);
673        }
674    }
675
676    public void showForSystem() {
677        if (mHiddenBySystem) {
678            mHiddenBySystem = false;
679            updateVisibility(true);
680        }
681    }
682
683    @Override
684    public void hide() {
685        if (!mHiddenByApp) {
686            mHiddenByApp = true;
687            updateVisibility(false);
688        }
689    }
690
691    private void hideForActionMode() {
692        if (mShowingForMode) {
693            mShowingForMode = false;
694            if (mOverlayLayout != null) {
695                mOverlayLayout.setShowingForActionMode(false);
696            }
697            updateVisibility(false);
698        }
699    }
700
701    public void hideForSystem() {
702        if (!mHiddenBySystem) {
703            mHiddenBySystem = true;
704            updateVisibility(true);
705        }
706    }
707
708    @Override
709    public void setHideOnContentScrollEnabled(boolean hideOnContentScroll) {
710        if (hideOnContentScroll && !mOverlayLayout.isInOverlayMode()) {
711            throw new IllegalStateException("Action bar must be in overlay mode " +
712                    "(Window.FEATURE_OVERLAY_ACTION_BAR) to enable hide on content scroll");
713        }
714        mHideOnContentScroll = hideOnContentScroll;
715        mOverlayLayout.setHideOnContentScrollEnabled(hideOnContentScroll);
716    }
717
718    @Override
719    public boolean isHideOnContentScrollEnabled() {
720        return mOverlayLayout.isHideOnContentScrollEnabled();
721    }
722
723    @Override
724    public int getHideOffset() {
725        return mOverlayLayout.getActionBarHideOffset();
726    }
727
728    @Override
729    public void setHideOffset(int offset) {
730        if (offset != 0 && !mOverlayLayout.isInOverlayMode()) {
731            throw new IllegalStateException("Action bar must be in overlay mode " +
732                    "(Window.FEATURE_OVERLAY_ACTION_BAR) to set a non-zero hide offset");
733        }
734        mOverlayLayout.setActionBarHideOffset(offset);
735    }
736
737    static boolean checkShowingFlags(boolean hiddenByApp, boolean hiddenBySystem,
738            boolean showingForMode) {
739        if (showingForMode) {
740            return true;
741        } else if (hiddenByApp || hiddenBySystem) {
742            return false;
743        } else {
744            return true;
745        }
746    }
747
748    private void updateVisibility(boolean fromSystem) {
749        // Based on the current state, should we be hidden or shown?
750        final boolean shown = checkShowingFlags(mHiddenByApp, mHiddenBySystem,
751                mShowingForMode);
752
753        if (shown) {
754            if (!mNowShowing) {
755                mNowShowing = true;
756                doShow(fromSystem);
757            }
758        } else {
759            if (mNowShowing) {
760                mNowShowing = false;
761                doHide(fromSystem);
762            }
763        }
764    }
765
766    public void doShow(boolean fromSystem) {
767        if (mCurrentShowAnim != null) {
768            mCurrentShowAnim.cancel();
769        }
770        mContainerView.setVisibility(View.VISIBLE);
771
772        if (mCurWindowVisibility == View.VISIBLE && ALLOW_SHOW_HIDE_ANIMATIONS &&
773                (mShowHideAnimationEnabled || fromSystem)) {
774            // because we're about to ask its window loc
775            ViewCompat.setTranslationY(mContainerView, 0f);
776            float startingY = -mContainerView.getHeight();
777            if (fromSystem) {
778                int topLeft[] = {0, 0};
779                mContainerView.getLocationInWindow(topLeft);
780                startingY -= topLeft[1];
781            }
782            ViewCompat.setTranslationY(mContainerView, startingY);
783            ViewPropertyAnimatorCompatSet anim = new ViewPropertyAnimatorCompatSet();
784            ViewPropertyAnimatorCompat a = ViewCompat.animate(mContainerView).translationY(0f);
785            a.setUpdateListener(mUpdateListener);
786            anim.play(a);
787            if (mContentAnimations && mContentView != null) {
788                ViewCompat.setTranslationY(mContentView, startingY);
789                anim.play(ViewCompat.animate(mContentView).translationY(0f));
790            }
791            anim.setInterpolator(sShowInterpolator);
792            anim.setDuration(250);
793            // If this is being shown from the system, add a small delay.
794            // This is because we will also be animating in the status bar,
795            // and these two elements can't be done in lock-step.  So we give
796            // a little time for the status bar to start its animation before
797            // the action bar animates.  (This corresponds to the corresponding
798            // case when hiding, where the status bar has a small delay before
799            // starting.)
800            anim.setListener(mShowListener);
801            mCurrentShowAnim = anim;
802            anim.start();
803        } else {
804            ViewCompat.setAlpha(mContainerView, 1f);
805            ViewCompat.setTranslationY(mContainerView, 0);
806            if (mContentAnimations && mContentView != null) {
807                ViewCompat.setTranslationY(mContentView, 0);
808            }
809            mShowListener.onAnimationEnd(null);
810        }
811        if (mOverlayLayout != null) {
812            ViewCompat.requestApplyInsets(mOverlayLayout);
813        }
814    }
815
816    public void doHide(boolean fromSystem) {
817        if (mCurrentShowAnim != null) {
818            mCurrentShowAnim.cancel();
819        }
820
821        if (mCurWindowVisibility == View.VISIBLE && ALLOW_SHOW_HIDE_ANIMATIONS &&
822                (mShowHideAnimationEnabled || fromSystem)) {
823            ViewCompat.setAlpha(mContainerView, 1f);
824            mContainerView.setTransitioning(true);
825            ViewPropertyAnimatorCompatSet anim = new ViewPropertyAnimatorCompatSet();
826            float endingY = -mContainerView.getHeight();
827            if (fromSystem) {
828                int topLeft[] = {0, 0};
829                mContainerView.getLocationInWindow(topLeft);
830                endingY -= topLeft[1];
831            }
832            ViewPropertyAnimatorCompat a = ViewCompat.animate(mContainerView).translationY(endingY);
833            a.setUpdateListener(mUpdateListener);
834            anim.play(a);
835            if (mContentAnimations && mContentView != null) {
836                anim.play(ViewCompat.animate(mContentView).translationY(endingY));
837            }
838            anim.setInterpolator(sHideInterpolator);
839            anim.setDuration(250);
840            anim.setListener(mHideListener);
841            mCurrentShowAnim = anim;
842            anim.start();
843        } else {
844            mHideListener.onAnimationEnd(null);
845        }
846    }
847
848    public boolean isShowing() {
849        final int height = getHeight();
850        // Take into account the case where the bar has a 0 height due to not being measured yet.
851        return mNowShowing && (height == 0 || getHideOffset() < height);
852    }
853
854    public void animateToMode(boolean toActionMode) {
855        if (toActionMode) {
856            showForActionMode();
857        } else {
858            hideForActionMode();
859        }
860
861        if (shouldAnimateContextView()) {
862            ViewPropertyAnimatorCompat fadeIn, fadeOut;
863            if (toActionMode) {
864                // We use INVISIBLE for the Toolbar to make sure that the container has a non-zero
865                // height throughout. The context view is GONE initially, so will not have been laid
866                // out when the animation starts. This can lead to the container collapsing to 0px
867                // height for a short period.
868                fadeOut = mDecorToolbar.setupAnimatorToVisibility(View.INVISIBLE,
869                        FADE_OUT_DURATION_MS);
870                fadeIn = mContextView.setupAnimatorToVisibility(View.VISIBLE,
871                        FADE_IN_DURATION_MS);
872            } else {
873                fadeIn = mDecorToolbar.setupAnimatorToVisibility(View.VISIBLE,
874                        FADE_IN_DURATION_MS);
875                fadeOut = mContextView.setupAnimatorToVisibility(View.GONE,
876                        FADE_OUT_DURATION_MS);
877            }
878            ViewPropertyAnimatorCompatSet set = new ViewPropertyAnimatorCompatSet();
879            set.playSequentially(fadeOut, fadeIn);
880            set.start();
881        } else {
882            if (toActionMode) {
883                mDecorToolbar.setVisibility(View.INVISIBLE);
884                mContextView.setVisibility(View.VISIBLE);
885            } else {
886                mDecorToolbar.setVisibility(View.VISIBLE);
887                mContextView.setVisibility(View.GONE);
888            }
889        }
890        // mTabScrollView's visibility is not affected by action mode.
891    }
892
893    private boolean shouldAnimateContextView() {
894        // We only to animate the action mode in if the container view has already been laid out.
895        // If it hasn't been laid out, it hasn't been drawn to screen yet.
896        return ViewCompat.isLaidOut(mContainerView);
897    }
898
899    public Context getThemedContext() {
900        if (mThemedContext == null) {
901            TypedValue outValue = new TypedValue();
902            Resources.Theme currentTheme = mContext.getTheme();
903            currentTheme.resolveAttribute(R.attr.actionBarWidgetTheme, outValue, true);
904            final int targetThemeRes = outValue.resourceId;
905
906            if (targetThemeRes != 0) {
907                mThemedContext = new ContextThemeWrapper(mContext, targetThemeRes);
908            } else {
909                mThemedContext = mContext;
910            }
911        }
912        return mThemedContext;
913    }
914
915    @Override
916    public boolean isTitleTruncated() {
917        return mDecorToolbar != null && mDecorToolbar.isTitleTruncated();
918    }
919
920    @Override
921    public void setHomeAsUpIndicator(Drawable indicator) {
922        mDecorToolbar.setNavigationIcon(indicator);
923    }
924
925    @Override
926    public void setHomeAsUpIndicator(int resId) {
927        mDecorToolbar.setNavigationIcon(resId);
928    }
929
930    @Override
931    public void setHomeActionContentDescription(CharSequence description) {
932        mDecorToolbar.setNavigationContentDescription(description);
933    }
934
935    @Override
936    public void setHomeActionContentDescription(int resId) {
937        mDecorToolbar.setNavigationContentDescription(resId);
938    }
939
940    @Override
941    public void onContentScrollStarted() {
942        if (mCurrentShowAnim != null) {
943            mCurrentShowAnim.cancel();
944            mCurrentShowAnim = null;
945        }
946    }
947
948    @Override
949    public void onContentScrollStopped() {
950    }
951
952    @Override
953    public boolean collapseActionView() {
954        if (mDecorToolbar != null && mDecorToolbar.hasExpandedActionView()) {
955            mDecorToolbar.collapseActionView();
956            return true;
957        }
958        return false;
959    }
960
961    /**
962     * @hide
963     */
964    @RestrictTo(GROUP_ID)
965    public class ActionModeImpl extends ActionMode implements MenuBuilder.Callback {
966        private final Context mActionModeContext;
967        private final MenuBuilder mMenu;
968
969        private ActionMode.Callback mCallback;
970        private WeakReference<View> mCustomView;
971
972        public ActionModeImpl(Context context, ActionMode.Callback callback) {
973            mActionModeContext = context;
974            mCallback = callback;
975            mMenu = new MenuBuilder(context)
976                    .setDefaultShowAsAction(MenuItem.SHOW_AS_ACTION_IF_ROOM);
977            mMenu.setCallback(this);
978        }
979
980        @Override
981        public MenuInflater getMenuInflater() {
982            return new SupportMenuInflater(mActionModeContext);
983        }
984
985        @Override
986        public Menu getMenu() {
987            return mMenu;
988        }
989
990        @Override
991        public void finish() {
992            if (mActionMode != this) {
993                // Not the active action mode - no-op
994                return;
995            }
996
997            // If this change in state is going to cause the action bar
998            // to be hidden, defer the onDestroy callback until the animation
999            // is finished and associated relayout is about to happen. This lets
1000            // apps better anticipate visibility and layout behavior.
1001            if (!checkShowingFlags(mHiddenByApp, mHiddenBySystem, false)) {
1002                // With the current state but the action bar hidden, our
1003                // overall showing state is going to be false.
1004                mDeferredDestroyActionMode = this;
1005                mDeferredModeDestroyCallback = mCallback;
1006            } else {
1007                mCallback.onDestroyActionMode(this);
1008            }
1009            mCallback = null;
1010            animateToMode(false);
1011
1012            // Clear out the context mode views after the animation finishes
1013            mContextView.closeMode();
1014            mDecorToolbar.getViewGroup().sendAccessibilityEvent(
1015                    AccessibilityEvent.TYPE_WINDOW_STATE_CHANGED);
1016            mOverlayLayout.setHideOnContentScrollEnabled(mHideOnContentScroll);
1017
1018            mActionMode = null;
1019        }
1020
1021        @Override
1022        public void invalidate() {
1023            if (mActionMode != this) {
1024                // Not the active action mode - no-op. It's possible we are
1025                // currently deferring onDestroy, so the app doesn't yet know we
1026                // are going away and is trying to use us. That's also a no-op.
1027                return;
1028            }
1029
1030            mMenu.stopDispatchingItemsChanged();
1031            try {
1032                mCallback.onPrepareActionMode(this, mMenu);
1033            } finally {
1034                mMenu.startDispatchingItemsChanged();
1035            }
1036        }
1037
1038        public boolean dispatchOnCreate() {
1039            mMenu.stopDispatchingItemsChanged();
1040            try {
1041                return mCallback.onCreateActionMode(this, mMenu);
1042            } finally {
1043                mMenu.startDispatchingItemsChanged();
1044            }
1045        }
1046
1047        @Override
1048        public void setCustomView(View view) {
1049            mContextView.setCustomView(view);
1050            mCustomView = new WeakReference<View>(view);
1051        }
1052
1053        @Override
1054        public void setSubtitle(CharSequence subtitle) {
1055            mContextView.setSubtitle(subtitle);
1056        }
1057
1058        @Override
1059        public void setTitle(CharSequence title) {
1060            mContextView.setTitle(title);
1061        }
1062
1063        @Override
1064        public void setTitle(int resId) {
1065            setTitle(mContext.getResources().getString(resId));
1066        }
1067
1068        @Override
1069        public void setSubtitle(int resId) {
1070            setSubtitle(mContext.getResources().getString(resId));
1071        }
1072
1073        @Override
1074        public CharSequence getTitle() {
1075            return mContextView.getTitle();
1076        }
1077
1078        @Override
1079        public CharSequence getSubtitle() {
1080            return mContextView.getSubtitle();
1081        }
1082
1083        @Override
1084        public void setTitleOptionalHint(boolean titleOptional) {
1085            super.setTitleOptionalHint(titleOptional);
1086            mContextView.setTitleOptional(titleOptional);
1087        }
1088
1089        @Override
1090        public boolean isTitleOptional() {
1091            return mContextView.isTitleOptional();
1092        }
1093
1094        @Override
1095        public View getCustomView() {
1096            return mCustomView != null ? mCustomView.get() : null;
1097        }
1098
1099        public boolean onMenuItemSelected(MenuBuilder menu, MenuItem item) {
1100            if (mCallback != null) {
1101                return mCallback.onActionItemClicked(this, item);
1102            } else {
1103                return false;
1104            }
1105        }
1106
1107        public void onCloseMenu(MenuBuilder menu, boolean allMenusAreClosing) {
1108        }
1109
1110        public boolean onSubMenuSelected(SubMenuBuilder subMenu) {
1111            if (mCallback == null) {
1112                return false;
1113            }
1114
1115            if (!subMenu.hasVisibleItems()) {
1116                return true;
1117            }
1118
1119            new MenuPopupHelper(getThemedContext(), subMenu).show();
1120            return true;
1121        }
1122
1123        public void onCloseSubMenu(SubMenuBuilder menu) {
1124        }
1125
1126        public void onMenuModeChange(MenuBuilder menu) {
1127            if (mCallback == null) {
1128                return;
1129            }
1130            invalidate();
1131            mContextView.showOverflowMenu();
1132        }
1133    }
1134
1135    /**
1136     * @hide
1137     */
1138    @RestrictTo(GROUP_ID)
1139    public class TabImpl extends ActionBar.Tab {
1140        private ActionBar.TabListener mCallback;
1141        private Object mTag;
1142        private Drawable mIcon;
1143        private CharSequence mText;
1144        private CharSequence mContentDesc;
1145        private int mPosition = -1;
1146        private View mCustomView;
1147
1148        @Override
1149        public Object getTag() {
1150            return mTag;
1151        }
1152
1153        @Override
1154        public Tab setTag(Object tag) {
1155            mTag = tag;
1156            return this;
1157        }
1158
1159        public ActionBar.TabListener getCallback() {
1160            return mCallback;
1161        }
1162
1163        @Override
1164        public Tab setTabListener(ActionBar.TabListener callback) {
1165            mCallback = callback;
1166            return this;
1167        }
1168
1169        @Override
1170        public View getCustomView() {
1171            return mCustomView;
1172        }
1173
1174        @Override
1175        public Tab setCustomView(View view) {
1176            mCustomView = view;
1177            if (mPosition >= 0) {
1178                mTabScrollView.updateTab(mPosition);
1179            }
1180            return this;
1181        }
1182
1183        @Override
1184        public Tab setCustomView(int layoutResId) {
1185            return setCustomView(LayoutInflater.from(getThemedContext())
1186                    .inflate(layoutResId, null));
1187        }
1188
1189        @Override
1190        public Drawable getIcon() {
1191            return mIcon;
1192        }
1193
1194        @Override
1195        public int getPosition() {
1196            return mPosition;
1197        }
1198
1199        public void setPosition(int position) {
1200            mPosition = position;
1201        }
1202
1203        @Override
1204        public CharSequence getText() {
1205            return mText;
1206        }
1207
1208        @Override
1209        public Tab setIcon(Drawable icon) {
1210            mIcon = icon;
1211            if (mPosition >= 0) {
1212                mTabScrollView.updateTab(mPosition);
1213            }
1214            return this;
1215        }
1216
1217        @Override
1218        public Tab setIcon(int resId) {
1219            return setIcon(AppCompatResources.getDrawable(mContext, resId));
1220        }
1221
1222        @Override
1223        public Tab setText(CharSequence text) {
1224            mText = text;
1225            if (mPosition >= 0) {
1226                mTabScrollView.updateTab(mPosition);
1227            }
1228            return this;
1229        }
1230
1231        @Override
1232        public Tab setText(int resId) {
1233            return setText(mContext.getResources().getText(resId));
1234        }
1235
1236        @Override
1237        public void select() {
1238            selectTab(this);
1239        }
1240
1241        @Override
1242        public Tab setContentDescription(int resId) {
1243            return setContentDescription(mContext.getResources().getText(resId));
1244        }
1245
1246        @Override
1247        public Tab setContentDescription(CharSequence contentDesc) {
1248            mContentDesc = contentDesc;
1249            if (mPosition >= 0) {
1250                mTabScrollView.updateTab(mPosition);
1251            }
1252            return this;
1253        }
1254
1255        @Override
1256        public CharSequence getContentDescription() {
1257            return mContentDesc;
1258        }
1259    }
1260
1261    @Override
1262    public void setCustomView(View view) {
1263        mDecorToolbar.setCustomView(view);
1264    }
1265
1266    @Override
1267    public void setCustomView(View view, LayoutParams layoutParams) {
1268        view.setLayoutParams(layoutParams);
1269        mDecorToolbar.setCustomView(view);
1270    }
1271
1272    @Override
1273    public void setListNavigationCallbacks(SpinnerAdapter adapter, OnNavigationListener callback) {
1274        mDecorToolbar.setDropdownParams(adapter, new NavItemSelectedListener(callback));
1275    }
1276
1277    @Override
1278    public int getSelectedNavigationIndex() {
1279        switch (mDecorToolbar.getNavigationMode()) {
1280            case NAVIGATION_MODE_TABS:
1281                return mSelectedTab != null ? mSelectedTab.getPosition() : -1;
1282            case NAVIGATION_MODE_LIST:
1283                return mDecorToolbar.getDropdownSelectedPosition();
1284            default:
1285                return -1;
1286        }
1287    }
1288
1289    @Override
1290    public int getNavigationItemCount() {
1291        switch (mDecorToolbar.getNavigationMode()) {
1292            case NAVIGATION_MODE_TABS:
1293                return mTabs.size();
1294            case NAVIGATION_MODE_LIST:
1295                return mDecorToolbar.getDropdownItemCount();
1296            default:
1297                return 0;
1298        }
1299    }
1300
1301    @Override
1302    public int getTabCount() {
1303        return mTabs.size();
1304    }
1305
1306    @Override
1307    public void setNavigationMode(int mode) {
1308        final int oldMode = mDecorToolbar.getNavigationMode();
1309        switch (oldMode) {
1310            case NAVIGATION_MODE_TABS:
1311                mSavedTabPosition = getSelectedNavigationIndex();
1312                selectTab(null);
1313                mTabScrollView.setVisibility(View.GONE);
1314                break;
1315        }
1316        if (oldMode != mode && !mHasEmbeddedTabs) {
1317            if (mOverlayLayout != null) {
1318                ViewCompat.requestApplyInsets(mOverlayLayout);
1319            }
1320        }
1321        mDecorToolbar.setNavigationMode(mode);
1322        switch (mode) {
1323            case NAVIGATION_MODE_TABS:
1324                ensureTabsExist();
1325                mTabScrollView.setVisibility(View.VISIBLE);
1326                if (mSavedTabPosition != INVALID_POSITION) {
1327                    setSelectedNavigationItem(mSavedTabPosition);
1328                    mSavedTabPosition = INVALID_POSITION;
1329                }
1330                break;
1331        }
1332        mDecorToolbar.setCollapsible(mode == NAVIGATION_MODE_TABS && !mHasEmbeddedTabs);
1333        mOverlayLayout.setHasNonEmbeddedTabs(mode == NAVIGATION_MODE_TABS && !mHasEmbeddedTabs);
1334    }
1335
1336    @Override
1337    public Tab getTabAt(int index) {
1338        return mTabs.get(index);
1339    }
1340
1341
1342    @Override
1343    public void setIcon(int resId) {
1344        mDecorToolbar.setIcon(resId);
1345    }
1346
1347    @Override
1348    public void setIcon(Drawable icon) {
1349        mDecorToolbar.setIcon(icon);
1350    }
1351
1352    public boolean hasIcon() {
1353        return mDecorToolbar.hasIcon();
1354    }
1355
1356    @Override
1357    public void setLogo(int resId) {
1358        mDecorToolbar.setLogo(resId);
1359    }
1360
1361    @Override
1362    public void setLogo(Drawable logo) {
1363        mDecorToolbar.setLogo(logo);
1364    }
1365
1366    public boolean hasLogo() {
1367        return mDecorToolbar.hasLogo();
1368    }
1369
1370    public void setDefaultDisplayHomeAsUpEnabled(boolean enable) {
1371        if (!mDisplayHomeAsUpSet) {
1372            setDisplayHomeAsUpEnabled(enable);
1373        }
1374    }
1375}
1376