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