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