[go: nahoru, domu]

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