1 package com.actionbarsherlock.internal;
3 import static android.view.ViewGroup.LayoutParams.MATCH_PARENT;
4 import static com.actionbarsherlock.internal.ResourcesCompat.getResources_getBoolean;
5 import java.util.ArrayList;
6 import java.util.HashMap;
8 import org.xmlpull.v1.XmlPullParser;
9 import android.app.Activity;
10 import android.content.Context;
11 import android.content.pm.ActivityInfo;
12 import android.content.res.AssetManager;
13 import android.content.res.Configuration;
14 import android.content.res.Resources;
15 import android.content.res.TypedArray;
16 import android.content.res.XmlResourceParser;
17 import android.os.Bundle;
18 import android.util.AndroidRuntimeException;
19 import android.util.Log;
20 import android.util.TypedValue;
21 import android.view.ContextThemeWrapper;
22 import android.view.KeyCharacterMap;
23 import android.view.KeyEvent;
24 import android.view.View;
25 import android.view.ViewGroup;
26 import android.view.ViewStub;
27 import android.view.Window;
28 import android.view.accessibility.AccessibilityEvent;
29 import android.view.animation.Animation;
30 import android.view.animation.AnimationUtils;
31 import android.widget.FrameLayout;
32 import android.widget.TextView;
33 import com.actionbarsherlock.ActionBarSherlock;
34 import com.actionbarsherlock.R;
35 import com.actionbarsherlock.app.ActionBar;
36 import com.actionbarsherlock.internal.app.ActionBarImpl;
37 import com.actionbarsherlock.internal.view.StandaloneActionMode;
38 import com.actionbarsherlock.internal.view.menu.ActionMenuPresenter;
39 import com.actionbarsherlock.internal.view.menu.MenuBuilder;
40 import com.actionbarsherlock.internal.view.menu.MenuItemImpl;
41 import com.actionbarsherlock.internal.view.menu.MenuPresenter;
42 import com.actionbarsherlock.internal.widget.ActionBarContainer;
43 import com.actionbarsherlock.internal.widget.ActionBarContextView;
44 import com.actionbarsherlock.internal.widget.ActionBarView;
45 import com.actionbarsherlock.internal.widget.IcsProgressBar;
46 import com.actionbarsherlock.view.ActionMode;
47 import com.actionbarsherlock.view.Menu;
48 import com.actionbarsherlock.view.MenuItem;
50 @ActionBarSherlock.Implementation(api = 7)
51 public class ActionBarSherlockCompat extends ActionBarSherlock implements MenuBuilder.Callback, com.actionbarsherlock.view.Window.Callback, MenuPresenter.Callback, android.view.MenuItem.OnMenuItemClickListener {
52 /** Window features which are enabled by default. */
53 protected static final int DEFAULT_FEATURES = 0;
56 public ActionBarSherlockCompat(Activity activity, int flags) {
57 super(activity, flags);
61 ///////////////////////////////////////////////////////////////////////////
63 ///////////////////////////////////////////////////////////////////////////
65 /** Whether or not the device has a dedicated menu key button. */
66 private boolean mReserveOverflow;
67 /** Lazy-load indicator for {@link #mReserveOverflow}. */
68 private boolean mReserveOverflowSet = false;
70 /** Current menu instance for managing action items. */
71 private MenuBuilder mMenu;
72 /** Map between native options items and sherlock items. */
73 protected HashMap<android.view.MenuItem, MenuItemImpl> mNativeItemMap;
74 /** Indication of a long-press on the hardware menu key. */
75 private boolean mMenuKeyIsLongPress = false;
77 /** Parent view of the window decoration (action bar, mode, etc.). */
78 private ViewGroup mDecor;
79 /** Parent view of the activity content. */
80 private ViewGroup mContentParent;
82 /** Whether or not the title is stable and can be displayed. */
83 private boolean mIsTitleReady = false;
84 /** Whether or not the parent activity has been destroyed. */
85 private boolean mIsDestroyed = false;
87 /* Emulate PanelFeatureState */
88 private boolean mClosingActionMenu;
89 private boolean mMenuIsPrepared;
90 private boolean mMenuRefreshContent;
91 private Bundle mMenuFrozenActionViewState;
93 /** Implementation which backs the action bar interface API. */
94 private ActionBarImpl aActionBar;
95 /** Main action bar view which displays the core content. */
96 private ActionBarView wActionBar;
97 /** Relevant window and action bar features flags. */
98 private int mFeatures = DEFAULT_FEATURES;
99 /** Relevant user interface option flags. */
100 private int mUiOptions = 0;
102 /** Decor indeterminate progress indicator. */
103 private IcsProgressBar mCircularProgressBar;
104 /** Decor progress indicator. */
105 private IcsProgressBar mHorizontalProgressBar;
107 /** Current displayed context action bar, if any. */
108 private ActionMode mActionMode;
109 /** Parent view in which the context action bar is displayed. */
110 private ActionBarContextView mActionModeView;
112 /** Title view used with dialogs. */
113 private TextView mTitleView;
114 /** Current activity title. */
115 private CharSequence mTitle = null;
116 /** Whether or not this "activity" is floating (i.e., a dialog) */
117 private boolean mIsFloating;
121 ///////////////////////////////////////////////////////////////////////////
123 ///////////////////////////////////////////////////////////////////////////
126 public ActionBar getActionBar() {
127 if (DEBUG) Log.d(TAG, "[getActionBar]");
133 private void initActionBar() {
134 if (DEBUG) Log.d(TAG, "[initActionBar]");
136 // Initializing the window decor can change window feature flags.
137 // Make sure that we have the correct set before performing the test below.
138 if (mDecor == null) {
142 if ((aActionBar != null) || !hasFeature(Window.FEATURE_ACTION_BAR) || hasFeature(Window.FEATURE_NO_TITLE) || mActivity.isChild()) {
146 aActionBar = new ActionBarImpl(mActivity, mFeatures);
149 //We may never get another chance to set the title
150 wActionBar.setWindowTitle(mActivity.getTitle());
155 protected Context getThemedContext() {
156 return aActionBar.getThemedContext();
160 public void setTitle(CharSequence title) {
161 if (DEBUG) Log.d(TAG, "[setTitle] title: " + title);
163 dispatchTitleChanged(title, 0);
167 public ActionMode startActionMode(ActionMode.Callback callback) {
168 if (DEBUG) Log.d(TAG, "[startActionMode] callback: " + callback);
170 if (mActionMode != null) {
171 mActionMode.finish();
174 final ActionMode.Callback wrappedCallback = new ActionModeCallbackWrapper(callback);
175 ActionMode mode = null;
177 //Emulate Activity's onWindowStartingActionMode:
179 if (aActionBar != null) {
180 mode = aActionBar.startActionMode(wrappedCallback);
186 if (mActionModeView == null) {
187 ViewStub stub = (ViewStub)mDecor.findViewById(R.id.abs__action_mode_bar_stub);
189 mActionModeView = (ActionBarContextView)stub.inflate();
192 if (mActionModeView != null) {
193 mActionModeView.killMode();
194 mode = new StandaloneActionMode(mActivity, mActionModeView, wrappedCallback, true);
195 if (callback.onCreateActionMode(mode, mode.getMenu())) {
197 mActionModeView.initForMode(mode);
198 mActionModeView.setVisibility(View.VISIBLE);
200 mActionModeView.sendAccessibilityEvent(AccessibilityEvent.TYPE_WINDOW_STATE_CHANGED);
206 if (mActionMode != null && mActivity instanceof OnActionModeStartedListener) {
207 ((OnActionModeStartedListener)mActivity).onActionModeStarted(mActionMode);
213 ///////////////////////////////////////////////////////////////////////////
214 // Lifecycle and interaction callbacks for delegation
215 ///////////////////////////////////////////////////////////////////////////
218 public void dispatchConfigurationChanged(Configuration newConfig) {
219 if (DEBUG) Log.d(TAG, "[dispatchConfigurationChanged] newConfig: " + newConfig);
221 if (aActionBar != null) {
222 aActionBar.onConfigurationChanged(newConfig);
227 public void dispatchPostResume() {
228 if (DEBUG) Log.d(TAG, "[dispatchPostResume]");
230 if (aActionBar != null) {
231 aActionBar.setShowHideAnimationEnabled(true);
236 public void dispatchPause() {
237 if (DEBUG) Log.d(TAG, "[dispatchPause]");
239 if (wActionBar != null && wActionBar.isOverflowMenuShowing()) {
240 wActionBar.hideOverflowMenu();
245 public void dispatchStop() {
246 if (DEBUG) Log.d(TAG, "[dispatchStop]");
248 if (aActionBar != null) {
249 aActionBar.setShowHideAnimationEnabled(false);
254 public void dispatchInvalidateOptionsMenu() {
255 if (DEBUG) Log.d(TAG, "[dispatchInvalidateOptionsMenu]");
257 Bundle savedActionViewStates = null;
259 savedActionViewStates = new Bundle();
260 mMenu.saveActionViewStates(savedActionViewStates);
261 if (savedActionViewStates.size() > 0) {
262 mMenuFrozenActionViewState = savedActionViewStates;
264 // This will be started again when the panel is prepared.
265 mMenu.stopDispatchingItemsChanged();
268 mMenuRefreshContent = true;
270 // Prepare the options panel if we have an action bar
271 if (wActionBar != null) {
272 mMenuIsPrepared = false;
278 public boolean dispatchOpenOptionsMenu() {
279 if (DEBUG) Log.d(TAG, "[dispatchOpenOptionsMenu]");
281 if (!isReservingOverflow()) {
285 return wActionBar.showOverflowMenu();
289 public boolean dispatchCloseOptionsMenu() {
290 if (DEBUG) Log.d(TAG, "[dispatchCloseOptionsMenu]");
292 if (!isReservingOverflow()) {
296 return wActionBar.hideOverflowMenu();
300 public void dispatchPostCreate(Bundle savedInstanceState) {
301 if (DEBUG) Log.d(TAG, "[dispatchOnPostCreate]");
304 mIsTitleReady = true;
307 if (mDecor == null) {
313 public boolean dispatchCreateOptionsMenu(android.view.Menu menu) {
315 Log.d(TAG, "[dispatchCreateOptionsMenu] android.view.Menu: " + menu);
316 Log.d(TAG, "[dispatchCreateOptionsMenu] returning true");
322 public boolean dispatchPrepareOptionsMenu(android.view.Menu menu) {
323 if (DEBUG) Log.d(TAG, "[dispatchPrepareOptionsMenu] android.view.Menu: " + menu);
325 if (mActionMode != null) {
329 mMenuIsPrepared = false;
330 if (!preparePanel()) {
334 if (isReservingOverflow()) {
338 if (mNativeItemMap == null) {
339 mNativeItemMap = new HashMap<android.view.MenuItem, MenuItemImpl>();
341 mNativeItemMap.clear();
348 boolean result = mMenu.bindNativeOverflow(menu, this, mNativeItemMap);
349 if (DEBUG) Log.d(TAG, "[dispatchPrepareOptionsMenu] returning " + result);
354 public boolean dispatchOptionsItemSelected(android.view.MenuItem item) {
355 throw new IllegalStateException("Native callback invoked. Create a test case and report!");
359 public boolean dispatchMenuOpened(int featureId, android.view.Menu menu) {
360 if (DEBUG) Log.d(TAG, "[dispatchMenuOpened] featureId: " + featureId + ", menu: " + menu);
362 if (featureId == Window.FEATURE_ACTION_BAR || featureId == Window.FEATURE_OPTIONS_PANEL) {
363 if (aActionBar != null) {
364 aActionBar.dispatchMenuVisibilityChanged(true);
373 public void dispatchPanelClosed(int featureId, android.view.Menu menu){
374 if (DEBUG) Log.d(TAG, "[dispatchPanelClosed] featureId: " + featureId + ", menu: " + menu);
376 if (featureId == Window.FEATURE_ACTION_BAR || featureId == Window.FEATURE_OPTIONS_PANEL) {
377 if (aActionBar != null) {
378 aActionBar.dispatchMenuVisibilityChanged(false);
384 public void dispatchTitleChanged(CharSequence title, int color) {
385 if (DEBUG) Log.d(TAG, "[dispatchTitleChanged] title: " + title + ", color: " + color);
387 if (!mIsDelegate || mIsTitleReady) {
388 if (mTitleView != null) {
389 mTitleView.setText(title);
390 } else if (wActionBar != null) {
391 wActionBar.setWindowTitle(title);
399 public boolean dispatchKeyEvent(KeyEvent event) {
400 if (DEBUG) Log.d(TAG, "[dispatchKeyEvent] event: " + event);
402 final int keyCode = event.getKeyCode();
404 // Not handled by the view hierarchy, does the action bar want it
405 // to cancel out of something special?
406 if (keyCode == KeyEvent.KEYCODE_BACK) {
407 final int action = event.getAction();
408 // Back cancels action modes first.
409 if (mActionMode != null) {
410 if (action == KeyEvent.ACTION_UP) {
411 mActionMode.finish();
413 if (DEBUG) Log.d(TAG, "[dispatchKeyEvent] returning true");
417 // Next collapse any expanded action views.
418 if (wActionBar != null && wActionBar.hasExpandedActionView()) {
419 if (action == KeyEvent.ACTION_UP) {
420 wActionBar.collapseActionView();
422 if (DEBUG) Log.d(TAG, "[dispatchKeyEvent] returning true");
427 boolean result = false;
428 if (keyCode == KeyEvent.KEYCODE_MENU && isReservingOverflow()) {
429 if (event.getAction() == KeyEvent.ACTION_DOWN && event.isLongPress()) {
430 mMenuKeyIsLongPress = true;
431 } else if (event.getAction() == KeyEvent.ACTION_UP) {
432 if (!mMenuKeyIsLongPress) {
433 if (mActionMode == null && wActionBar != null) {
434 if (wActionBar.isOverflowMenuShowing()) {
435 wActionBar.hideOverflowMenu();
437 wActionBar.showOverflowMenu();
442 mMenuKeyIsLongPress = false;
446 if (DEBUG) Log.d(TAG, "[dispatchKeyEvent] returning " + result);
451 public void dispatchDestroy() {
456 ///////////////////////////////////////////////////////////////////////////
457 // Menu callback lifecycle and creation
458 ///////////////////////////////////////////////////////////////////////////
460 private boolean preparePanel() {
461 // Already prepared (isPrepared will be reset to false later)
462 if (mMenuIsPrepared) {
466 // Init the panel state's menu--return false if init failed
467 if (mMenu == null || mMenuRefreshContent) {
469 if (!initializePanelMenu() || (mMenu == null)) {
474 if (wActionBar != null) {
475 wActionBar.setMenu(mMenu, this);
478 // Call callback, and return if it doesn't want to display menu.
480 // Creating the panel menu will involve a lot of manipulation;
481 // don't dispatch change events to presenters until we're done.
482 mMenu.stopDispatchingItemsChanged();
483 if (!callbackCreateOptionsMenu(mMenu)) {
484 // Ditch the menu created above
487 if (wActionBar != null) {
488 // Don't show it in the action bar either
489 wActionBar.setMenu(null, this);
495 mMenuRefreshContent = false;
498 // Callback and return if the callback does not want to show the menu
500 // Preparing the panel menu can involve a lot of manipulation;
501 // don't dispatch change events to presenters until we're done.
502 mMenu.stopDispatchingItemsChanged();
504 // Restore action view state before we prepare. This gives apps
505 // an opportunity to override frozen/restored state in onPrepare.
506 if (mMenuFrozenActionViewState != null) {
507 mMenu.restoreActionViewStates(mMenuFrozenActionViewState);
508 mMenuFrozenActionViewState = null;
511 if (!callbackPrepareOptionsMenu(mMenu)) {
512 if (wActionBar != null) {
513 // The app didn't want to show the menu for now but it still exists.
514 // Clear it out of the action bar.
515 wActionBar.setMenu(null, this);
517 mMenu.startDispatchingItemsChanged();
521 // Set the proper keymap
522 KeyCharacterMap kmap = KeyCharacterMap.load(KeyCharacterMap.VIRTUAL_KEYBOARD);
523 mMenu.setQwertyMode(kmap.getKeyboardType() != KeyCharacterMap.NUMERIC);
524 mMenu.startDispatchingItemsChanged();
527 mMenuIsPrepared = true;
532 public boolean onMenuItemSelected(MenuBuilder menu, MenuItem item) {
533 return callbackOptionsItemSelected(item);
536 public void onMenuModeChange(MenuBuilder menu) {
540 private void reopenMenu(boolean toggleMenuMode) {
541 if (wActionBar != null && wActionBar.isOverflowReserved()) {
542 if (!wActionBar.isOverflowMenuShowing() || !toggleMenuMode) {
543 if (wActionBar.getVisibility() == View.VISIBLE) {
544 if (callbackPrepareOptionsMenu(mMenu)) {
545 wActionBar.showOverflowMenu();
549 wActionBar.hideOverflowMenu();
555 private boolean initializePanelMenu() {
556 Context context = mActivity;//getContext();
558 // If we have an action bar, initialize the menu with a context themed for it.
559 if (wActionBar != null) {
560 TypedValue outValue = new TypedValue();
561 Resources.Theme currentTheme = context.getTheme();
562 currentTheme.resolveAttribute(R.attr.actionBarWidgetTheme,
564 final int targetThemeRes = outValue.resourceId;
566 if (targetThemeRes != 0 /*&& context.getThemeResId() != targetThemeRes*/) {
567 context = new ContextThemeWrapper(context, targetThemeRes);
571 mMenu = new MenuBuilder(context);
572 mMenu.setCallback(this);
577 void checkCloseActionMenu(Menu menu) {
578 if (mClosingActionMenu) {
582 mClosingActionMenu = true;
583 wActionBar.dismissPopupMenus();
584 //Callback cb = getCallback();
585 //if (cb != null && !isDestroyed()) {
586 // cb.onPanelClosed(FEATURE_ACTION_BAR, menu);
588 mClosingActionMenu = false;
592 public boolean onOpenSubMenu(MenuBuilder subMenu) {
597 public void onCloseMenu(MenuBuilder menu, boolean allMenusAreClosing) {
598 checkCloseActionMenu(menu);
602 public boolean onMenuItemClick(android.view.MenuItem item) {
603 if (DEBUG) Log.d(TAG, "[mNativeItemListener.onMenuItemClick] item: " + item);
605 final MenuItemImpl sherlockItem = mNativeItemMap.get(item);
606 if (sherlockItem != null) {
607 sherlockItem.invoke();
609 Log.e(TAG, "Options item \"" + item + "\" not found in mapping");
612 return true; //Do not allow continuation of native handling
616 public boolean onMenuItemSelected(int featureId, MenuItem item) {
617 return callbackOptionsItemSelected(item);
621 ///////////////////////////////////////////////////////////////////////////
622 // Progress bar interaction and internal handling
623 ///////////////////////////////////////////////////////////////////////////
626 public void setProgressBarVisibility(boolean visible) {
627 if (DEBUG) Log.d(TAG, "[setProgressBarVisibility] visible: " + visible);
629 setFeatureInt(Window.FEATURE_PROGRESS, visible ? Window.PROGRESS_VISIBILITY_ON :
630 Window.PROGRESS_VISIBILITY_OFF);
634 public void setProgressBarIndeterminateVisibility(boolean visible) {
635 if (DEBUG) Log.d(TAG, "[setProgressBarIndeterminateVisibility] visible: " + visible);
637 setFeatureInt(Window.FEATURE_INDETERMINATE_PROGRESS,
638 visible ? Window.PROGRESS_VISIBILITY_ON : Window.PROGRESS_VISIBILITY_OFF);
642 public void setProgressBarIndeterminate(boolean indeterminate) {
643 if (DEBUG) Log.d(TAG, "[setProgressBarIndeterminate] indeterminate: " + indeterminate);
645 setFeatureInt(Window.FEATURE_PROGRESS,
646 indeterminate ? Window.PROGRESS_INDETERMINATE_ON : Window.PROGRESS_INDETERMINATE_OFF);
650 public void setProgress(int progress) {
651 if (DEBUG) Log.d(TAG, "[setProgress] progress: " + progress);
653 setFeatureInt(Window.FEATURE_PROGRESS, progress + Window.PROGRESS_START);
657 public void setSecondaryProgress(int secondaryProgress) {
658 if (DEBUG) Log.d(TAG, "[setSecondaryProgress] secondaryProgress: " + secondaryProgress);
660 setFeatureInt(Window.FEATURE_PROGRESS,
661 secondaryProgress + Window.PROGRESS_SECONDARY_START);
664 private void setFeatureInt(int featureId, int value) {
665 updateInt(featureId, value, false);
668 private void updateInt(int featureId, int value, boolean fromResume) {
669 // Do nothing if the decor is not yet installed... an update will
670 // need to be forced when we eventually become active.
671 if (mContentParent == null) {
675 final int featureMask = 1 << featureId;
677 if ((getFeatures() & featureMask) == 0 && !fromResume) {
681 onIntChanged(featureId, value);
684 private void onIntChanged(int featureId, int value) {
685 if (featureId == Window.FEATURE_PROGRESS || featureId == Window.FEATURE_INDETERMINATE_PROGRESS) {
686 updateProgressBars(value);
690 private void updateProgressBars(int value) {
691 IcsProgressBar circularProgressBar = getCircularProgressBar(true);
692 IcsProgressBar horizontalProgressBar = getHorizontalProgressBar(true);
694 final int features = mFeatures;//getLocalFeatures();
695 if (value == Window.PROGRESS_VISIBILITY_ON) {
696 if ((features & (1 << Window.FEATURE_PROGRESS)) != 0) {
697 int level = horizontalProgressBar.getProgress();
698 int visibility = (horizontalProgressBar.isIndeterminate() || level < 10000) ?
699 View.VISIBLE : View.INVISIBLE;
700 horizontalProgressBar.setVisibility(visibility);
702 if ((features & (1 << Window.FEATURE_INDETERMINATE_PROGRESS)) != 0) {
703 circularProgressBar.setVisibility(View.VISIBLE);
705 } else if (value == Window.PROGRESS_VISIBILITY_OFF) {
706 if ((features & (1 << Window.FEATURE_PROGRESS)) != 0) {
707 horizontalProgressBar.setVisibility(View.GONE);
709 if ((features & (1 << Window.FEATURE_INDETERMINATE_PROGRESS)) != 0) {
710 circularProgressBar.setVisibility(View.GONE);
712 } else if (value == Window.PROGRESS_INDETERMINATE_ON) {
713 horizontalProgressBar.setIndeterminate(true);
714 } else if (value == Window.PROGRESS_INDETERMINATE_OFF) {
715 horizontalProgressBar.setIndeterminate(false);
716 } else if (Window.PROGRESS_START <= value && value <= Window.PROGRESS_END) {
717 // We want to set the progress value before testing for visibility
718 // so that when the progress bar becomes visible again, it has the
720 horizontalProgressBar.setProgress(value - Window.PROGRESS_START);
722 if (value < Window.PROGRESS_END) {
723 showProgressBars(horizontalProgressBar, circularProgressBar);
725 hideProgressBars(horizontalProgressBar, circularProgressBar);
727 } else if (Window.PROGRESS_SECONDARY_START <= value && value <= Window.PROGRESS_SECONDARY_END) {
728 horizontalProgressBar.setSecondaryProgress(value - Window.PROGRESS_SECONDARY_START);
730 showProgressBars(horizontalProgressBar, circularProgressBar);
734 private void showProgressBars(IcsProgressBar horizontalProgressBar, IcsProgressBar spinnyProgressBar) {
735 final int features = mFeatures;//getLocalFeatures();
736 if ((features & (1 << Window.FEATURE_INDETERMINATE_PROGRESS)) != 0 &&
737 spinnyProgressBar.getVisibility() == View.INVISIBLE) {
738 spinnyProgressBar.setVisibility(View.VISIBLE);
740 // Only show the progress bars if the primary progress is not complete
741 if ((features & (1 << Window.FEATURE_PROGRESS)) != 0 &&
742 horizontalProgressBar.getProgress() < 10000) {
743 horizontalProgressBar.setVisibility(View.VISIBLE);
747 private void hideProgressBars(IcsProgressBar horizontalProgressBar, IcsProgressBar spinnyProgressBar) {
748 final int features = mFeatures;//getLocalFeatures();
749 Animation anim = AnimationUtils.loadAnimation(mActivity, android.R.anim.fade_out);
750 anim.setDuration(1000);
751 if ((features & (1 << Window.FEATURE_INDETERMINATE_PROGRESS)) != 0 &&
752 spinnyProgressBar.getVisibility() == View.VISIBLE) {
753 spinnyProgressBar.startAnimation(anim);
754 spinnyProgressBar.setVisibility(View.INVISIBLE);
756 if ((features & (1 << Window.FEATURE_PROGRESS)) != 0 &&
757 horizontalProgressBar.getVisibility() == View.VISIBLE) {
758 horizontalProgressBar.startAnimation(anim);
759 horizontalProgressBar.setVisibility(View.INVISIBLE);
763 private IcsProgressBar getCircularProgressBar(boolean shouldInstallDecor) {
764 if (mCircularProgressBar != null) {
765 return mCircularProgressBar;
767 if (mContentParent == null && shouldInstallDecor) {
770 mCircularProgressBar = (IcsProgressBar)mDecor.findViewById(R.id.abs__progress_circular);
771 if (mCircularProgressBar != null) {
772 mCircularProgressBar.setVisibility(View.INVISIBLE);
774 return mCircularProgressBar;
777 private IcsProgressBar getHorizontalProgressBar(boolean shouldInstallDecor) {
778 if (mHorizontalProgressBar != null) {
779 return mHorizontalProgressBar;
781 if (mContentParent == null && shouldInstallDecor) {
784 mHorizontalProgressBar = (IcsProgressBar)mDecor.findViewById(R.id.abs__progress_horizontal);
785 if (mHorizontalProgressBar != null) {
786 mHorizontalProgressBar.setVisibility(View.INVISIBLE);
788 return mHorizontalProgressBar;
792 ///////////////////////////////////////////////////////////////////////////
793 // Feature management and content interaction and creation
794 ///////////////////////////////////////////////////////////////////////////
796 private int getFeatures() {
797 if (DEBUG) Log.d(TAG, "[getFeatures] returning " + mFeatures);
803 public boolean hasFeature(int featureId) {
804 if (DEBUG) Log.d(TAG, "[hasFeature] featureId: " + featureId);
806 boolean result = (mFeatures & (1 << featureId)) != 0;
807 if (DEBUG) Log.d(TAG, "[hasFeature] returning " + result);
812 public boolean requestFeature(int featureId) {
813 if (DEBUG) Log.d(TAG, "[requestFeature] featureId: " + featureId);
815 if (mContentParent != null) {
816 throw new AndroidRuntimeException("requestFeature() must be called before adding content");
820 case Window.FEATURE_ACTION_BAR:
821 case Window.FEATURE_ACTION_BAR_OVERLAY:
822 case Window.FEATURE_ACTION_MODE_OVERLAY:
823 case Window.FEATURE_INDETERMINATE_PROGRESS:
824 case Window.FEATURE_NO_TITLE:
825 case Window.FEATURE_PROGRESS:
826 mFeatures |= (1 << featureId);
835 public void setUiOptions(int uiOptions) {
836 if (DEBUG) Log.d(TAG, "[setUiOptions] uiOptions: " + uiOptions);
838 mUiOptions = uiOptions;
842 public void setUiOptions(int uiOptions, int mask) {
843 if (DEBUG) Log.d(TAG, "[setUiOptions] uiOptions: " + uiOptions + ", mask: " + mask);
845 mUiOptions = (mUiOptions & ~mask) | (uiOptions & mask);
849 public void setContentView(int layoutResId) {
850 if (DEBUG) Log.d(TAG, "[setContentView] layoutResId: " + layoutResId);
852 if (mContentParent == null) {
855 mContentParent.removeAllViews();
857 mActivity.getLayoutInflater().inflate(layoutResId, mContentParent);
859 android.view.Window.Callback callback = mActivity.getWindow().getCallback();
860 if (callback != null) {
861 callback.onContentChanged();
868 public void setContentView(View view, ViewGroup.LayoutParams params) {
869 if (DEBUG) Log.d(TAG, "[setContentView] view: " + view + ", params: " + params);
871 if (mContentParent == null) {
874 mContentParent.removeAllViews();
876 mContentParent.addView(view, params);
878 android.view.Window.Callback callback = mActivity.getWindow().getCallback();
879 if (callback != null) {
880 callback.onContentChanged();
887 public void addContentView(View view, ViewGroup.LayoutParams params) {
888 if (DEBUG) Log.d(TAG, "[addContentView] view: " + view + ", params: " + params);
890 if (mContentParent == null) {
893 mContentParent.addView(view, params);
898 private void installDecor() {
899 if (DEBUG) Log.d(TAG, "[installDecor]");
901 if (mDecor == null) {
902 mDecor = (ViewGroup)mActivity.getWindow().getDecorView().findViewById(android.R.id.content);
904 if (mContentParent == null) {
905 //Since we are not operating at the window level we need to take
906 //into account the fact that the true decor may have already been
907 //initialized and had content attached to it. If that is the case,
908 //copy over its children to our new content container.
909 List<View> views = null;
910 if (mDecor.getChildCount() > 0) {
911 views = new ArrayList<View>(1); //Usually there's only one child
912 for (int i = 0, children = mDecor.getChildCount(); i < children; i++) {
913 View child = mDecor.getChildAt(0);
914 mDecor.removeView(child);
919 mContentParent = generateLayout();
921 //Copy over the old children. See above for explanation.
923 for (View child : views) {
924 mContentParent.addView(child);
928 mTitleView = (TextView)mDecor.findViewById(android.R.id.title);
929 if (mTitleView != null) {
930 if (hasFeature(Window.FEATURE_NO_TITLE)) {
931 mTitleView.setVisibility(View.GONE);
932 if (mContentParent instanceof FrameLayout) {
933 ((FrameLayout)mContentParent).setForeground(null);
936 mTitleView.setText(mTitle);
939 wActionBar = (ActionBarView)mDecor.findViewById(R.id.abs__action_bar);
940 if (wActionBar != null) {
941 wActionBar.setWindowCallback(this);
942 if (wActionBar.getTitle() == null) {
943 wActionBar.setWindowTitle(mActivity.getTitle());
945 if (hasFeature(Window.FEATURE_PROGRESS)) {
946 wActionBar.initProgress();
948 if (hasFeature(Window.FEATURE_INDETERMINATE_PROGRESS)) {
949 wActionBar.initIndeterminateProgress();
952 //Since we don't require onCreate dispatching, parse for uiOptions here
953 int uiOptions = loadUiOptionsFromManifest(mActivity);
954 if (uiOptions != 0) {
955 mUiOptions = uiOptions;
958 boolean splitActionBar = false;
959 final boolean splitWhenNarrow = (mUiOptions & ActivityInfo.UIOPTION_SPLIT_ACTION_BAR_WHEN_NARROW) != 0;
960 if (splitWhenNarrow) {
961 splitActionBar = getResources_getBoolean(mActivity, R.bool.abs__split_action_bar_is_narrow);
963 splitActionBar = mActivity.getTheme()
964 .obtainStyledAttributes(R.styleable.SherlockTheme)
965 .getBoolean(R.styleable.SherlockTheme_windowSplitActionBar, false);
967 final ActionBarContainer splitView = (ActionBarContainer)mDecor.findViewById(R.id.abs__split_action_bar);
968 if (splitView != null) {
969 wActionBar.setSplitView(splitView);
970 wActionBar.setSplitActionBar(splitActionBar);
971 wActionBar.setSplitWhenNarrow(splitWhenNarrow);
973 mActionModeView = (ActionBarContextView)mDecor.findViewById(R.id.abs__action_context_bar);
974 mActionModeView.setSplitView(splitView);
975 mActionModeView.setSplitActionBar(splitActionBar);
976 mActionModeView.setSplitWhenNarrow(splitWhenNarrow);
977 } else if (splitActionBar) {
978 Log.e(TAG, "Requested split action bar with incompatible window decor! Ignoring request.");
981 // Post the panel invalidate for later; avoid application onCreateOptionsMenu
982 // being called in the middle of onCreate or similar.
983 mDecor.post(new Runnable() {
986 //Invalidate if the panel menu hasn't been created before this.
987 if (!mIsDestroyed && !mActivity.isFinishing() && mMenu == null) {
988 dispatchInvalidateOptionsMenu();
997 private ViewGroup generateLayout() {
998 if (DEBUG) Log.d(TAG, "[generateLayout]");
1000 // Apply data from current theme.
1002 TypedArray a = mActivity.getTheme().obtainStyledAttributes(R.styleable.SherlockTheme);
1004 mIsFloating = a.getBoolean(R.styleable.SherlockTheme_android_windowIsFloating, false);
1006 if (!a.hasValue(R.styleable.SherlockTheme_windowActionBar)) {
1007 throw new IllegalStateException("You must use Theme.Sherlock, Theme.Sherlock.Light, Theme.Sherlock.Light.DarkActionBar, or a derivative.");
1010 if (a.getBoolean(R.styleable.SherlockTheme_windowNoTitle, false)) {
1011 requestFeature(Window.FEATURE_NO_TITLE);
1012 } else if (a.getBoolean(R.styleable.SherlockTheme_windowActionBar, false)) {
1013 // Don't allow an action bar if there is no title.
1014 requestFeature(Window.FEATURE_ACTION_BAR);
1017 if (a.getBoolean(R.styleable.SherlockTheme_windowActionBarOverlay, false)) {
1018 requestFeature(Window.FEATURE_ACTION_BAR_OVERLAY);
1021 if (a.getBoolean(R.styleable.SherlockTheme_windowActionModeOverlay, false)) {
1022 requestFeature(Window.FEATURE_ACTION_MODE_OVERLAY);
1028 if (!hasFeature(Window.FEATURE_NO_TITLE)) {
1030 //Trash original dialog LinearLayout
1031 mDecor = (ViewGroup)mDecor.getParent();
1032 mDecor.removeAllViews();
1034 layoutResource = R.layout.abs__dialog_title_holo;
1036 if (hasFeature(Window.FEATURE_ACTION_BAR_OVERLAY)) {
1037 layoutResource = R.layout.abs__screen_action_bar_overlay;
1039 layoutResource = R.layout.abs__screen_action_bar;
1042 } else if (hasFeature(Window.FEATURE_ACTION_MODE_OVERLAY) && !hasFeature(Window.FEATURE_NO_TITLE)) {
1043 layoutResource = R.layout.abs__screen_simple_overlay_action_mode;
1045 layoutResource = R.layout.abs__screen_simple;
1048 if (DEBUG) Log.d(TAG, "[generateLayout] using screen XML " + mActivity.getResources().getString(layoutResource));
1049 View in = mActivity.getLayoutInflater().inflate(layoutResource, null);
1050 mDecor.addView(in, new ViewGroup.LayoutParams(MATCH_PARENT, MATCH_PARENT));
1052 ViewGroup contentParent = (ViewGroup)mDecor.findViewById(R.id.abs__content);
1053 if (contentParent == null) {
1054 throw new RuntimeException("Couldn't find content container view");
1057 //Make our new child the true content view (for fragments). VERY VOLATILE!
1058 mDecor.setId(View.NO_ID);
1059 contentParent.setId(android.R.id.content);
1061 if (hasFeature(Window.FEATURE_INDETERMINATE_PROGRESS)) {
1062 IcsProgressBar progress = getCircularProgressBar(false);
1063 if (progress != null) {
1064 progress.setIndeterminate(true);
1068 return contentParent;
1072 ///////////////////////////////////////////////////////////////////////////
1074 ///////////////////////////////////////////////////////////////////////////
1077 * Determine whether or not the device has a dedicated menu key.
1079 * @return {@code true} if native menu key is present.
1081 private boolean isReservingOverflow() {
1082 if (!mReserveOverflowSet) {
1083 mReserveOverflow = ActionMenuPresenter.reserveOverflow(mActivity);
1084 mReserveOverflowSet = true;
1086 return mReserveOverflow;
1089 private static int loadUiOptionsFromManifest(Activity activity) {
1092 final String thisPackage = activity.getClass().getName();
1093 if (DEBUG) Log.i(TAG, "Parsing AndroidManifest.xml for " + thisPackage);
1095 final String packageName = activity.getApplicationInfo().packageName;
1096 final AssetManager am = activity.createPackageContext(packageName, 0).getAssets();
1097 final XmlResourceParser xml = am.openXmlResourceParser("AndroidManifest.xml");
1099 int eventType = xml.getEventType();
1100 while (eventType != XmlPullParser.END_DOCUMENT) {
1101 if (eventType == XmlPullParser.START_TAG) {
1102 String name = xml.getName();
1104 if ("application".equals(name)) {
1105 //Check if the <application> has the attribute
1106 if (DEBUG) Log.d(TAG, "Got <application>");
1108 for (int i = xml.getAttributeCount() - 1; i >= 0; i--) {
1109 if (DEBUG) Log.d(TAG, xml.getAttributeName(i) + ": " + xml.getAttributeValue(i));
1111 if ("uiOptions".equals(xml.getAttributeName(i))) {
1112 uiOptions = xml.getAttributeIntValue(i, 0);
1113 break; //out of for loop
1116 } else if ("activity".equals(name)) {
1117 //Check if the <activity> is us and has the attribute
1118 if (DEBUG) Log.d(TAG, "Got <activity>");
1119 Integer activityUiOptions = null;
1120 String activityPackage = null;
1121 boolean isOurActivity = false;
1123 for (int i = xml.getAttributeCount() - 1; i >= 0; i--) {
1124 if (DEBUG) Log.d(TAG, xml.getAttributeName(i) + ": " + xml.getAttributeValue(i));
1126 //We need both uiOptions and name attributes
1127 String attrName = xml.getAttributeName(i);
1128 if ("uiOptions".equals(attrName)) {
1129 activityUiOptions = xml.getAttributeIntValue(i, 0);
1130 } else if ("name".equals(attrName)) {
1131 activityPackage = cleanActivityName(packageName, xml.getAttributeValue(i));
1132 if (!thisPackage.equals(activityPackage)) {
1133 break; //out of for loop
1135 isOurActivity = true;
1138 //Make sure we have both attributes before processing
1139 if ((activityUiOptions != null) && (activityPackage != null)) {
1140 //Our activity, uiOptions specified, override with our value
1141 uiOptions = activityUiOptions.intValue();
1144 if (isOurActivity) {
1145 //If we matched our activity but it had no logo don't
1146 //do any more processing of the manifest
1151 eventType = xml.nextToken();
1153 } catch (Exception e) {
1154 e.printStackTrace();
1156 if (DEBUG) Log.i(TAG, "Returning " + Integer.toHexString(uiOptions));
1160 public static String cleanActivityName(String manifestPackage, String activityName) {
1161 if (activityName.charAt(0) == '.') {
1162 //Relative activity name (e.g., android:name=".ui.SomeClass")
1163 return manifestPackage + activityName;
1165 if (activityName.indexOf('.', 1) == -1) {
1166 //Unqualified activity name (e.g., android:name="SomeClass")
1167 return manifestPackage + "." + activityName;
1169 //Fully-qualified activity name (e.g., "com.my.package.SomeClass")
1170 return activityName;
1174 * Clears out internal reference when the action mode is destroyed.
1176 private class ActionModeCallbackWrapper implements ActionMode.Callback {
1177 private final ActionMode.Callback mWrapped;
1179 public ActionModeCallbackWrapper(ActionMode.Callback wrapped) {
1183 public boolean onCreateActionMode(ActionMode mode, Menu menu) {
1184 return mWrapped.onCreateActionMode(mode, menu);
1187 public boolean onPrepareActionMode(ActionMode mode, Menu menu) {
1188 return mWrapped.onPrepareActionMode(mode, menu);
1191 public boolean onActionItemClicked(ActionMode mode, MenuItem item) {
1192 return mWrapped.onActionItemClicked(mode, item);
1195 public void onDestroyActionMode(ActionMode mode) {
1196 mWrapped.onDestroyActionMode(mode);
1197 if (mActionModeView != null) {
1198 mActionModeView.setVisibility(View.GONE);
1199 mActionModeView.removeAllViews();
1201 if (mActivity instanceof OnActionModeFinishedListener) {
1202 ((OnActionModeFinishedListener)mActivity).onActionModeFinished(mActionMode);