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