create changelog entry
[debian/openrocket] / android-libraries / ActionBarSherlock / src / com / actionbarsherlock / internal / app / ActionBarImpl.java
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
17 package com.actionbarsherlock.internal.app;
18
19 import java.lang.ref.WeakReference;
20 import java.util.ArrayList;
21 import android.app.Activity;
22 import android.app.Dialog;
23 import android.content.Context;
24 import android.content.res.Configuration;
25 import android.content.res.Resources;
26 import android.graphics.drawable.Drawable;
27 import android.os.Build;
28 import android.os.Handler;
29 import android.support.v4.app.FragmentTransaction;
30 import android.util.TypedValue;
31 import android.view.ContextThemeWrapper;
32 import android.view.LayoutInflater;
33 import android.view.View;
34 import android.view.Window;
35 import android.view.accessibility.AccessibilityEvent;
36 import android.widget.SpinnerAdapter;
37 import com.actionbarsherlock.R;
38 import com.actionbarsherlock.app.ActionBar;
39 import com.actionbarsherlock.app.SherlockFragmentActivity;
40 import com.actionbarsherlock.internal.nineoldandroids.animation.Animator;
41 import com.actionbarsherlock.internal.nineoldandroids.animation.AnimatorListenerAdapter;
42 import com.actionbarsherlock.internal.nineoldandroids.animation.AnimatorSet;
43 import com.actionbarsherlock.internal.nineoldandroids.animation.ObjectAnimator;
44 import com.actionbarsherlock.internal.nineoldandroids.animation.Animator.AnimatorListener;
45 import com.actionbarsherlock.internal.nineoldandroids.widget.NineFrameLayout;
46 import com.actionbarsherlock.internal.view.menu.MenuBuilder;
47 import com.actionbarsherlock.internal.view.menu.MenuPopupHelper;
48 import com.actionbarsherlock.internal.view.menu.SubMenuBuilder;
49 import com.actionbarsherlock.internal.widget.ActionBarContainer;
50 import com.actionbarsherlock.internal.widget.ActionBarContextView;
51 import com.actionbarsherlock.internal.widget.ActionBarView;
52 import com.actionbarsherlock.internal.widget.ScrollingTabContainerView;
53 import com.actionbarsherlock.view.ActionMode;
54 import com.actionbarsherlock.view.Menu;
55 import com.actionbarsherlock.view.MenuInflater;
56 import com.actionbarsherlock.view.MenuItem;
57 import static com.actionbarsherlock.internal.ResourcesCompat.getResources_getBoolean;
58
59 /**
60  * ActionBarImpl is the ActionBar implementation used
61  * by devices of all screen sizes. If it detects a compatible decor,
62  * it will split contextual modes across both the ActionBarView at
63  * the top of the screen and a horizontal LinearLayout at the bottom
64  * which is normally hidden.
65  */
66 public class ActionBarImpl extends ActionBar {
67     //UNUSED private static final String TAG = "ActionBarImpl";
68
69     private Context mContext;
70     private Context mThemedContext;
71     private Activity mActivity;
72     //UNUSED private Dialog mDialog;
73
74     private ActionBarContainer mContainerView;
75     private ActionBarView mActionView;
76     private ActionBarContextView mContextView;
77     private ActionBarContainer mSplitView;
78     private NineFrameLayout mContentView;
79     private ScrollingTabContainerView mTabScrollView;
80
81     private ArrayList<TabImpl> mTabs = new ArrayList<TabImpl>();
82
83     private TabImpl mSelectedTab;
84     private int mSavedTabPosition = INVALID_POSITION;
85
86     ActionModeImpl mActionMode;
87     ActionMode mDeferredDestroyActionMode;
88     ActionMode.Callback mDeferredModeDestroyCallback;
89
90     private boolean mLastMenuVisibility;
91     private ArrayList<OnMenuVisibilityListener> mMenuVisibilityListeners =
92             new ArrayList<OnMenuVisibilityListener>();
93
94     private static final int CONTEXT_DISPLAY_NORMAL = 0;
95     private static final int CONTEXT_DISPLAY_SPLIT = 1;
96
97     private static final int INVALID_POSITION = -1;
98
99     private int mContextDisplayMode;
100     private boolean mHasEmbeddedTabs;
101
102     final Handler mHandler = new Handler();
103     Runnable mTabSelector;
104
105     private Animator mCurrentShowAnim;
106     private Animator mCurrentModeAnim;
107     private boolean mShowHideAnimationEnabled;
108     boolean mWasHiddenBeforeMode;
109
110     final AnimatorListener mHideListener = new AnimatorListenerAdapter() {
111         @Override
112         public void onAnimationEnd(Animator animation) {
113             if (mContentView != null) {
114                 mContentView.setTranslationY(0);
115                 mContainerView.setTranslationY(0);
116             }
117             if (mSplitView != null && mContextDisplayMode == CONTEXT_DISPLAY_SPLIT) {
118                 mSplitView.setVisibility(View.GONE);
119             }
120             mContainerView.setVisibility(View.GONE);
121             mContainerView.setTransitioning(false);
122             mCurrentShowAnim = null;
123             completeDeferredDestroyActionMode();
124         }
125     };
126
127     final AnimatorListener mShowListener = new AnimatorListenerAdapter() {
128         @Override
129         public void onAnimationEnd(Animator animation) {
130             mCurrentShowAnim = null;
131             mContainerView.requestLayout();
132         }
133     };
134
135     public ActionBarImpl(Activity activity, int features) {
136         mActivity = activity;
137         Window window = activity.getWindow();
138         View decor = window.getDecorView();
139         init(decor);
140
141         //window.hasFeature() workaround for pre-3.0
142         if ((features & (1 << Window.FEATURE_ACTION_BAR_OVERLAY)) == 0) {
143             mContentView = (NineFrameLayout)decor.findViewById(android.R.id.content);
144         }
145     }
146
147     public ActionBarImpl(Dialog dialog) {
148         //UNUSED mDialog = dialog;
149         init(dialog.getWindow().getDecorView());
150     }
151
152     private void init(View decor) {
153         mContext = decor.getContext();
154         mActionView = (ActionBarView) decor.findViewById(R.id.abs__action_bar);
155         mContextView = (ActionBarContextView) decor.findViewById(
156                 R.id.abs__action_context_bar);
157         mContainerView = (ActionBarContainer) decor.findViewById(
158                 R.id.abs__action_bar_container);
159         mSplitView = (ActionBarContainer) decor.findViewById(
160                 R.id.abs__split_action_bar);
161
162         if (mActionView == null || mContextView == null || mContainerView == null) {
163             throw new IllegalStateException(getClass().getSimpleName() + " can only be used " +
164                     "with a compatible window decor layout");
165         }
166
167         mActionView.setContextView(mContextView);
168         mContextDisplayMode = mActionView.isSplitActionBar() ?
169                 CONTEXT_DISPLAY_SPLIT : CONTEXT_DISPLAY_NORMAL;
170
171         // Older apps get the home button interaction enabled by default.
172         // Newer apps need to enable it explicitly.
173         setHomeButtonEnabled(mContext.getApplicationInfo().targetSdkVersion < 14);
174
175         setHasEmbeddedTabs(getResources_getBoolean(mContext,
176                 R.bool.abs__action_bar_embed_tabs));
177     }
178
179     public void onConfigurationChanged(Configuration newConfig) {
180         setHasEmbeddedTabs(getResources_getBoolean(mContext,
181                 R.bool.abs__action_bar_embed_tabs));
182
183         //Manually dispatch a configuration change to the action bar view on pre-2.2
184         if (Build.VERSION.SDK_INT < Build.VERSION_CODES.FROYO) {
185             mActionView.onConfigurationChanged(newConfig);
186             if (mContextView != null) {
187                 mContextView.onConfigurationChanged(newConfig);
188             }
189         }
190     }
191
192     private void setHasEmbeddedTabs(boolean hasEmbeddedTabs) {
193         mHasEmbeddedTabs = hasEmbeddedTabs;
194         // Switch tab layout configuration if needed
195         if (!mHasEmbeddedTabs) {
196             mActionView.setEmbeddedTabView(null);
197             mContainerView.setTabContainer(mTabScrollView);
198         } else {
199             mContainerView.setTabContainer(null);
200             mActionView.setEmbeddedTabView(mTabScrollView);
201         }
202         final boolean isInTabMode = getNavigationMode() == NAVIGATION_MODE_TABS;
203         if (mTabScrollView != null) {
204             mTabScrollView.setVisibility(isInTabMode ? View.VISIBLE : View.GONE);
205         }
206         mActionView.setCollapsable(!mHasEmbeddedTabs && isInTabMode);
207     }
208
209     private void ensureTabsExist() {
210         if (mTabScrollView != null) {
211             return;
212         }
213
214         ScrollingTabContainerView tabScroller = new ScrollingTabContainerView(mContext);
215
216         if (mHasEmbeddedTabs) {
217             tabScroller.setVisibility(View.VISIBLE);
218             mActionView.setEmbeddedTabView(tabScroller);
219         } else {
220             tabScroller.setVisibility(getNavigationMode() == NAVIGATION_MODE_TABS ?
221                     View.VISIBLE : View.GONE);
222             mContainerView.setTabContainer(tabScroller);
223         }
224         mTabScrollView = tabScroller;
225     }
226
227     void completeDeferredDestroyActionMode() {
228         if (mDeferredModeDestroyCallback != null) {
229             mDeferredModeDestroyCallback.onDestroyActionMode(mDeferredDestroyActionMode);
230             mDeferredDestroyActionMode = null;
231             mDeferredModeDestroyCallback = null;
232         }
233     }
234
235     /**
236      * Enables or disables animation between show/hide states.
237      * If animation is disabled using this method, animations in progress
238      * will be finished.
239      *
240      * @param enabled true to animate, false to not animate.
241      */
242     public void setShowHideAnimationEnabled(boolean enabled) {
243         mShowHideAnimationEnabled = enabled;
244         if (!enabled && mCurrentShowAnim != null) {
245             mCurrentShowAnim.end();
246         }
247     }
248
249     public void addOnMenuVisibilityListener(OnMenuVisibilityListener listener) {
250         mMenuVisibilityListeners.add(listener);
251     }
252
253     public void removeOnMenuVisibilityListener(OnMenuVisibilityListener listener) {
254         mMenuVisibilityListeners.remove(listener);
255     }
256
257     public void dispatchMenuVisibilityChanged(boolean isVisible) {
258         if (isVisible == mLastMenuVisibility) {
259             return;
260         }
261         mLastMenuVisibility = isVisible;
262
263         final int count = mMenuVisibilityListeners.size();
264         for (int i = 0; i < count; i++) {
265             mMenuVisibilityListeners.get(i).onMenuVisibilityChanged(isVisible);
266         }
267     }
268
269     @Override
270     public void setCustomView(int resId) {
271         setCustomView(LayoutInflater.from(getThemedContext()).inflate(resId, mActionView, false));
272     }
273
274     @Override
275     public void setDisplayUseLogoEnabled(boolean useLogo) {
276         setDisplayOptions(useLogo ? DISPLAY_USE_LOGO : 0, DISPLAY_USE_LOGO);
277     }
278
279     @Override
280     public void setDisplayShowHomeEnabled(boolean showHome) {
281         setDisplayOptions(showHome ? DISPLAY_SHOW_HOME : 0, DISPLAY_SHOW_HOME);
282     }
283
284     @Override
285     public void setDisplayHomeAsUpEnabled(boolean showHomeAsUp) {
286         setDisplayOptions(showHomeAsUp ? DISPLAY_HOME_AS_UP : 0, DISPLAY_HOME_AS_UP);
287     }
288
289     @Override
290     public void setDisplayShowTitleEnabled(boolean showTitle) {
291         setDisplayOptions(showTitle ? DISPLAY_SHOW_TITLE : 0, DISPLAY_SHOW_TITLE);
292     }
293
294     @Override
295     public void setDisplayShowCustomEnabled(boolean showCustom) {
296         setDisplayOptions(showCustom ? DISPLAY_SHOW_CUSTOM : 0, DISPLAY_SHOW_CUSTOM);
297     }
298
299     @Override
300     public void setHomeButtonEnabled(boolean enable) {
301         mActionView.setHomeButtonEnabled(enable);
302     }
303
304     @Override
305     public void setTitle(int resId) {
306         setTitle(mContext.getString(resId));
307     }
308
309     @Override
310     public void setSubtitle(int resId) {
311         setSubtitle(mContext.getString(resId));
312     }
313
314     public void setSelectedNavigationItem(int position) {
315         switch (mActionView.getNavigationMode()) {
316         case NAVIGATION_MODE_TABS:
317             selectTab(mTabs.get(position));
318             break;
319         case NAVIGATION_MODE_LIST:
320             mActionView.setDropdownSelectedPosition(position);
321             break;
322         default:
323             throw new IllegalStateException(
324                     "setSelectedNavigationIndex not valid for current navigation mode");
325         }
326     }
327
328     public void removeAllTabs() {
329         cleanupTabs();
330     }
331
332     private void cleanupTabs() {
333         if (mSelectedTab != null) {
334             selectTab(null);
335         }
336         mTabs.clear();
337         if (mTabScrollView != null) {
338             mTabScrollView.removeAllTabs();
339         }
340         mSavedTabPosition = INVALID_POSITION;
341     }
342
343     public void setTitle(CharSequence title) {
344         mActionView.setTitle(title);
345     }
346
347     public void setSubtitle(CharSequence subtitle) {
348         mActionView.setSubtitle(subtitle);
349     }
350
351     public void setDisplayOptions(int options) {
352         mActionView.setDisplayOptions(options);
353     }
354
355     public void setDisplayOptions(int options, int mask) {
356         final int current = mActionView.getDisplayOptions();
357         mActionView.setDisplayOptions((options & mask) | (current & ~mask));
358     }
359
360     public void setBackgroundDrawable(Drawable d) {
361         mContainerView.setPrimaryBackground(d);
362     }
363
364     public void setStackedBackgroundDrawable(Drawable d) {
365         mContainerView.setStackedBackground(d);
366     }
367
368     public void setSplitBackgroundDrawable(Drawable d) {
369         if (mSplitView != null) {
370             mSplitView.setSplitBackground(d);
371         }
372     }
373
374     public View getCustomView() {
375         return mActionView.getCustomNavigationView();
376     }
377
378     public CharSequence getTitle() {
379         return mActionView.getTitle();
380     }
381
382     public CharSequence getSubtitle() {
383         return mActionView.getSubtitle();
384     }
385
386     public int getNavigationMode() {
387         return mActionView.getNavigationMode();
388     }
389
390     public int getDisplayOptions() {
391         return mActionView.getDisplayOptions();
392     }
393
394     public ActionMode startActionMode(ActionMode.Callback callback) {
395         boolean wasHidden = false;
396         if (mActionMode != null) {
397             wasHidden = mWasHiddenBeforeMode;
398             mActionMode.finish();
399         }
400
401         mContextView.killMode();
402         ActionModeImpl mode = new ActionModeImpl(callback);
403         if (mode.dispatchOnCreate()) {
404             mWasHiddenBeforeMode = !isShowing() || wasHidden;
405             mode.invalidate();
406             mContextView.initForMode(mode);
407             animateToMode(true);
408             if (mSplitView != null && mContextDisplayMode == CONTEXT_DISPLAY_SPLIT) {
409                 // TODO animate this
410                 mSplitView.setVisibility(View.VISIBLE);
411             }
412             mContextView.sendAccessibilityEvent(AccessibilityEvent.TYPE_WINDOW_STATE_CHANGED);
413             mActionMode = mode;
414             return mode;
415         }
416         return null;
417     }
418
419     private void configureTab(Tab tab, int position) {
420         final TabImpl tabi = (TabImpl) tab;
421         final ActionBar.TabListener callback = tabi.getCallback();
422
423         if (callback == null) {
424             throw new IllegalStateException("Action Bar Tab must have a Callback");
425         }
426
427         tabi.setPosition(position);
428         mTabs.add(position, tabi);
429
430         final int count = mTabs.size();
431         for (int i = position + 1; i < count; i++) {
432             mTabs.get(i).setPosition(i);
433         }
434     }
435
436     @Override
437     public void addTab(Tab tab) {
438         addTab(tab, mTabs.isEmpty());
439     }
440
441     @Override
442     public void addTab(Tab tab, int position) {
443         addTab(tab, position, mTabs.isEmpty());
444     }
445
446     @Override
447     public void addTab(Tab tab, boolean setSelected) {
448         ensureTabsExist();
449         mTabScrollView.addTab(tab, setSelected);
450         configureTab(tab, mTabs.size());
451         if (setSelected) {
452             selectTab(tab);
453         }
454     }
455
456     @Override
457     public void addTab(Tab tab, int position, boolean setSelected) {
458         ensureTabsExist();
459         mTabScrollView.addTab(tab, position, setSelected);
460         configureTab(tab, position);
461         if (setSelected) {
462             selectTab(tab);
463         }
464     }
465
466     @Override
467     public Tab newTab() {
468         return new TabImpl();
469     }
470
471     @Override
472     public void removeTab(Tab tab) {
473         removeTabAt(tab.getPosition());
474     }
475
476     @Override
477     public void removeTabAt(int position) {
478         if (mTabScrollView == null) {
479             // No tabs around to remove
480             return;
481         }
482
483         int selectedTabPosition = mSelectedTab != null
484                 ? mSelectedTab.getPosition() : mSavedTabPosition;
485         mTabScrollView.removeTabAt(position);
486         TabImpl removedTab = mTabs.remove(position);
487         if (removedTab != null) {
488             removedTab.setPosition(-1);
489         }
490
491         final int newTabCount = mTabs.size();
492         for (int i = position; i < newTabCount; i++) {
493             mTabs.get(i).setPosition(i);
494         }
495
496         if (selectedTabPosition == position) {
497             selectTab(mTabs.isEmpty() ? null : mTabs.get(Math.max(0, position - 1)));
498         }
499     }
500
501     @Override
502     public void selectTab(Tab tab) {
503         if (getNavigationMode() != NAVIGATION_MODE_TABS) {
504             mSavedTabPosition = tab != null ? tab.getPosition() : INVALID_POSITION;
505             return;
506         }
507
508         FragmentTransaction trans = null;
509         if (mActivity instanceof SherlockFragmentActivity) {
510             trans = ((SherlockFragmentActivity)mActivity).getSupportFragmentManager().beginTransaction()
511                     .disallowAddToBackStack();
512         }
513
514         if (mSelectedTab == tab) {
515             if (mSelectedTab != null) {
516                 mSelectedTab.getCallback().onTabReselected(mSelectedTab, trans);
517                 mTabScrollView.animateToTab(tab.getPosition());
518             }
519         } else {
520             mTabScrollView.setTabSelected(tab != null ? tab.getPosition() : Tab.INVALID_POSITION);
521             if (mSelectedTab != null) {
522                 mSelectedTab.getCallback().onTabUnselected(mSelectedTab, trans);
523             }
524             mSelectedTab = (TabImpl) tab;
525             if (mSelectedTab != null) {
526                 mSelectedTab.getCallback().onTabSelected(mSelectedTab, trans);
527             }
528         }
529
530         if (trans != null && !trans.isEmpty()) {
531             trans.commit();
532         }
533     }
534
535     @Override
536     public Tab getSelectedTab() {
537         return mSelectedTab;
538     }
539
540     @Override
541     public int getHeight() {
542         return mContainerView.getHeight();
543     }
544
545     @Override
546     public void show() {
547         show(true);
548     }
549
550     void show(boolean markHiddenBeforeMode) {
551         if (mCurrentShowAnim != null) {
552             mCurrentShowAnim.end();
553         }
554         if (mContainerView.getVisibility() == View.VISIBLE) {
555             if (markHiddenBeforeMode) mWasHiddenBeforeMode = false;
556             return;
557         }
558         mContainerView.setVisibility(View.VISIBLE);
559
560         if (mShowHideAnimationEnabled) {
561             mContainerView.setAlpha(0);
562             AnimatorSet anim = new AnimatorSet();
563             AnimatorSet.Builder b = anim.play(ObjectAnimator.ofFloat(mContainerView, "alpha", 1));
564             if (mContentView != null) {
565                 b.with(ObjectAnimator.ofFloat(mContentView, "translationY",
566                         -mContainerView.getHeight(), 0));
567                 mContainerView.setTranslationY(-mContainerView.getHeight());
568                 b.with(ObjectAnimator.ofFloat(mContainerView, "translationY", 0));
569             }
570             if (mSplitView != null && mContextDisplayMode == CONTEXT_DISPLAY_SPLIT) {
571                 mSplitView.setAlpha(0);
572                 mSplitView.setVisibility(View.VISIBLE);
573                 b.with(ObjectAnimator.ofFloat(mSplitView, "alpha", 1));
574             }
575             anim.addListener(mShowListener);
576             mCurrentShowAnim = anim;
577             anim.start();
578         } else {
579             mContainerView.setAlpha(1);
580             mContainerView.setTranslationY(0);
581             mShowListener.onAnimationEnd(null);
582         }
583     }
584
585     @Override
586     public void hide() {
587         if (mCurrentShowAnim != null) {
588             mCurrentShowAnim.end();
589         }
590         if (mContainerView.getVisibility() == View.GONE) {
591             return;
592         }
593
594         if (mShowHideAnimationEnabled) {
595             mContainerView.setAlpha(1);
596             mContainerView.setTransitioning(true);
597             AnimatorSet anim = new AnimatorSet();
598             AnimatorSet.Builder b = anim.play(ObjectAnimator.ofFloat(mContainerView, "alpha", 0));
599             if (mContentView != null) {
600                 b.with(ObjectAnimator.ofFloat(mContentView, "translationY",
601                         0, -mContainerView.getHeight()));
602                 b.with(ObjectAnimator.ofFloat(mContainerView, "translationY",
603                         -mContainerView.getHeight()));
604             }
605             if (mSplitView != null && mSplitView.getVisibility() == View.VISIBLE) {
606                 mSplitView.setAlpha(1);
607                 b.with(ObjectAnimator.ofFloat(mSplitView, "alpha", 0));
608             }
609             anim.addListener(mHideListener);
610             mCurrentShowAnim = anim;
611             anim.start();
612         } else {
613             mHideListener.onAnimationEnd(null);
614         }
615     }
616
617     public boolean isShowing() {
618         return mContainerView.getVisibility() == View.VISIBLE;
619     }
620
621     void animateToMode(boolean toActionMode) {
622         if (toActionMode) {
623             show(false);
624         }
625         if (mCurrentModeAnim != null) {
626             mCurrentModeAnim.end();
627         }
628
629         mActionView.animateToVisibility(toActionMode ? View.GONE : View.VISIBLE);
630         mContextView.animateToVisibility(toActionMode ? View.VISIBLE : View.GONE);
631         if (mTabScrollView != null && !mActionView.hasEmbeddedTabs() && mActionView.isCollapsed()) {
632             mTabScrollView.animateToVisibility(toActionMode ? View.GONE : View.VISIBLE);
633         }
634     }
635
636     public Context getThemedContext() {
637         if (mThemedContext == null) {
638             TypedValue outValue = new TypedValue();
639             Resources.Theme currentTheme = mContext.getTheme();
640             currentTheme.resolveAttribute(R.attr.actionBarWidgetTheme,
641                     outValue, true);
642             final int targetThemeRes = outValue.resourceId;
643
644             if (targetThemeRes != 0) { //XXX && mContext.getThemeResId() != targetThemeRes) {
645                 mThemedContext = new ContextThemeWrapper(mContext, targetThemeRes);
646             } else {
647                 mThemedContext = mContext;
648             }
649         }
650         return mThemedContext;
651     }
652
653     /**
654      * @hide
655      */
656     public class ActionModeImpl extends ActionMode implements MenuBuilder.Callback {
657         private ActionMode.Callback mCallback;
658         private MenuBuilder mMenu;
659         private WeakReference<View> mCustomView;
660
661         public ActionModeImpl(ActionMode.Callback callback) {
662             mCallback = callback;
663             mMenu = new MenuBuilder(getThemedContext())
664                     .setDefaultShowAsAction(MenuItem.SHOW_AS_ACTION_IF_ROOM);
665             mMenu.setCallback(this);
666         }
667
668         @Override
669         public MenuInflater getMenuInflater() {
670             return new MenuInflater(getThemedContext());
671         }
672
673         @Override
674         public Menu getMenu() {
675             return mMenu;
676         }
677
678         @Override
679         public void finish() {
680             if (mActionMode != this) {
681                 // Not the active action mode - no-op
682                 return;
683             }
684
685             // If we were hidden before the mode was shown, defer the onDestroy
686             // callback until the animation is finished and associated relayout
687             // is about to happen. This lets apps better anticipate visibility
688             // and layout behavior.
689             if (mWasHiddenBeforeMode) {
690                 mDeferredDestroyActionMode = this;
691                 mDeferredModeDestroyCallback = mCallback;
692             } else {
693                 mCallback.onDestroyActionMode(this);
694             }
695             mCallback = null;
696             animateToMode(false);
697
698             // Clear out the context mode views after the animation finishes
699             mContextView.closeMode();
700             mActionView.sendAccessibilityEvent(AccessibilityEvent.TYPE_WINDOW_STATE_CHANGED);
701
702             mActionMode = null;
703
704             if (mWasHiddenBeforeMode) {
705                 hide();
706             }
707         }
708
709         @Override
710         public void invalidate() {
711             mMenu.stopDispatchingItemsChanged();
712             try {
713                 mCallback.onPrepareActionMode(this, mMenu);
714             } finally {
715                 mMenu.startDispatchingItemsChanged();
716             }
717         }
718
719         public boolean dispatchOnCreate() {
720             mMenu.stopDispatchingItemsChanged();
721             try {
722                 return mCallback.onCreateActionMode(this, mMenu);
723             } finally {
724                 mMenu.startDispatchingItemsChanged();
725             }
726         }
727
728         @Override
729         public void setCustomView(View view) {
730             mContextView.setCustomView(view);
731             mCustomView = new WeakReference<View>(view);
732         }
733
734         @Override
735         public void setSubtitle(CharSequence subtitle) {
736             mContextView.setSubtitle(subtitle);
737         }
738
739         @Override
740         public void setTitle(CharSequence title) {
741             mContextView.setTitle(title);
742         }
743
744         @Override
745         public void setTitle(int resId) {
746             setTitle(mContext.getResources().getString(resId));
747         }
748
749         @Override
750         public void setSubtitle(int resId) {
751             setSubtitle(mContext.getResources().getString(resId));
752         }
753
754         @Override
755         public CharSequence getTitle() {
756             return mContextView.getTitle();
757         }
758
759         @Override
760         public CharSequence getSubtitle() {
761             return mContextView.getSubtitle();
762         }
763
764         @Override
765         public View getCustomView() {
766             return mCustomView != null ? mCustomView.get() : null;
767         }
768
769         public boolean onMenuItemSelected(MenuBuilder menu, MenuItem item) {
770             if (mCallback != null) {
771                 return mCallback.onActionItemClicked(this, item);
772             } else {
773                 return false;
774             }
775         }
776
777         public void onCloseMenu(MenuBuilder menu, boolean allMenusAreClosing) {
778         }
779
780         public boolean onSubMenuSelected(SubMenuBuilder subMenu) {
781             if (mCallback == null) {
782                 return false;
783             }
784
785             if (!subMenu.hasVisibleItems()) {
786                 return true;
787             }
788
789             new MenuPopupHelper(getThemedContext(), subMenu).show();
790             return true;
791         }
792
793         public void onCloseSubMenu(SubMenuBuilder menu) {
794         }
795
796         public void onMenuModeChange(MenuBuilder menu) {
797             if (mCallback == null) {
798                 return;
799             }
800             invalidate();
801             mContextView.showOverflowMenu();
802         }
803     }
804
805     /**
806      * @hide
807      */
808     public class TabImpl extends ActionBar.Tab {
809         private ActionBar.TabListener mCallback;
810         private Object mTag;
811         private Drawable mIcon;
812         private CharSequence mText;
813         private CharSequence mContentDesc;
814         private int mPosition = -1;
815         private View mCustomView;
816
817         @Override
818         public Object getTag() {
819             return mTag;
820         }
821
822         @Override
823         public Tab setTag(Object tag) {
824             mTag = tag;
825             return this;
826         }
827
828         public ActionBar.TabListener getCallback() {
829             return mCallback;
830         }
831
832         @Override
833         public Tab setTabListener(ActionBar.TabListener callback) {
834             mCallback = callback;
835             return this;
836         }
837
838         @Override
839         public View getCustomView() {
840             return mCustomView;
841         }
842
843         @Override
844         public Tab setCustomView(View view) {
845             mCustomView = view;
846             if (mPosition >= 0) {
847                 mTabScrollView.updateTab(mPosition);
848             }
849             return this;
850         }
851
852         @Override
853         public Tab setCustomView(int layoutResId) {
854             return setCustomView(LayoutInflater.from(getThemedContext())
855                     .inflate(layoutResId, null));
856         }
857
858         @Override
859         public Drawable getIcon() {
860             return mIcon;
861         }
862
863         @Override
864         public int getPosition() {
865             return mPosition;
866         }
867
868         public void setPosition(int position) {
869             mPosition = position;
870         }
871
872         @Override
873         public CharSequence getText() {
874             return mText;
875         }
876
877         @Override
878         public Tab setIcon(Drawable icon) {
879             mIcon = icon;
880             if (mPosition >= 0) {
881                 mTabScrollView.updateTab(mPosition);
882             }
883             return this;
884         }
885
886         @Override
887         public Tab setIcon(int resId) {
888             return setIcon(mContext.getResources().getDrawable(resId));
889         }
890
891         @Override
892         public Tab setText(CharSequence text) {
893             mText = text;
894             if (mPosition >= 0) {
895                 mTabScrollView.updateTab(mPosition);
896             }
897             return this;
898         }
899
900         @Override
901         public Tab setText(int resId) {
902             return setText(mContext.getResources().getText(resId));
903         }
904
905         @Override
906         public void select() {
907             selectTab(this);
908         }
909
910         @Override
911         public Tab setContentDescription(int resId) {
912             return setContentDescription(mContext.getResources().getText(resId));
913         }
914
915         @Override
916         public Tab setContentDescription(CharSequence contentDesc) {
917             mContentDesc = contentDesc;
918             if (mPosition >= 0) {
919                 mTabScrollView.updateTab(mPosition);
920             }
921             return this;
922         }
923
924         @Override
925         public CharSequence getContentDescription() {
926             return mContentDesc;
927         }
928     }
929
930     @Override
931     public void setCustomView(View view) {
932         mActionView.setCustomNavigationView(view);
933     }
934
935     @Override
936     public void setCustomView(View view, LayoutParams layoutParams) {
937         view.setLayoutParams(layoutParams);
938         mActionView.setCustomNavigationView(view);
939     }
940
941     @Override
942     public void setListNavigationCallbacks(SpinnerAdapter adapter, OnNavigationListener callback) {
943         mActionView.setDropdownAdapter(adapter);
944         mActionView.setCallback(callback);
945     }
946
947     @Override
948     public int getSelectedNavigationIndex() {
949         switch (mActionView.getNavigationMode()) {
950             case NAVIGATION_MODE_TABS:
951                 return mSelectedTab != null ? mSelectedTab.getPosition() : -1;
952             case NAVIGATION_MODE_LIST:
953                 return mActionView.getDropdownSelectedPosition();
954             default:
955                 return -1;
956         }
957     }
958
959     @Override
960     public int getNavigationItemCount() {
961         switch (mActionView.getNavigationMode()) {
962             case NAVIGATION_MODE_TABS:
963                 return mTabs.size();
964             case NAVIGATION_MODE_LIST:
965                 SpinnerAdapter adapter = mActionView.getDropdownAdapter();
966                 return adapter != null ? adapter.getCount() : 0;
967             default:
968                 return 0;
969         }
970     }
971
972     @Override
973     public int getTabCount() {
974         return mTabs.size();
975     }
976
977     @Override
978     public void setNavigationMode(int mode) {
979         final int oldMode = mActionView.getNavigationMode();
980         switch (oldMode) {
981             case NAVIGATION_MODE_TABS:
982                 mSavedTabPosition = getSelectedNavigationIndex();
983                 selectTab(null);
984                 mTabScrollView.setVisibility(View.GONE);
985                 break;
986         }
987         mActionView.setNavigationMode(mode);
988         switch (mode) {
989             case NAVIGATION_MODE_TABS:
990                 ensureTabsExist();
991                 mTabScrollView.setVisibility(View.VISIBLE);
992                 if (mSavedTabPosition != INVALID_POSITION) {
993                     setSelectedNavigationItem(mSavedTabPosition);
994                     mSavedTabPosition = INVALID_POSITION;
995                 }
996                 break;
997         }
998         mActionView.setCollapsable(mode == NAVIGATION_MODE_TABS && !mHasEmbeddedTabs);
999     }
1000
1001     @Override
1002     public Tab getTabAt(int index) {
1003         return mTabs.get(index);
1004     }
1005
1006
1007     @Override
1008     public void setIcon(int resId) {
1009         mActionView.setIcon(resId);
1010     }
1011
1012     @Override
1013     public void setIcon(Drawable icon) {
1014         mActionView.setIcon(icon);
1015     }
1016
1017     @Override
1018     public void setLogo(int resId) {
1019         mActionView.setLogo(resId);
1020     }
1021
1022     @Override
1023     public void setLogo(Drawable logo) {
1024         mActionView.setLogo(logo);
1025     }
1026 }