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