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