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