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