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