ActionBarActivityDelegateBase.java revision 8902df1bf0006a156503d40b1fc8a01f95d5b806
1/*
2 * Copyright (C) 2013 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 android.support.v7.app;
18
19import android.content.Context;
20import android.content.res.Configuration;
21import android.content.res.TypedArray;
22import android.graphics.drawable.Drawable;
23import android.os.Bundle;
24import android.support.v4.app.ActionBarDrawerToggle;
25import android.support.v4.view.WindowCompat;
26import android.support.v7.appcompat.R;
27import android.support.v7.internal.view.menu.ListMenuPresenter;
28import android.support.v7.internal.view.menu.MenuBuilder;
29import android.support.v7.internal.view.menu.MenuPresenter;
30import android.support.v7.internal.view.menu.MenuView;
31import android.support.v7.internal.view.menu.MenuWrapperFactory;
32import android.support.v7.internal.widget.ActionBarContainer;
33import android.support.v7.internal.widget.ActionBarContextView;
34import android.support.v7.internal.widget.ActionBarView;
35import android.support.v7.internal.widget.ProgressBarICS;
36import android.support.v7.view.ActionMode;
37import android.view.Menu;
38import android.view.MenuItem;
39import android.view.View;
40import android.view.ViewGroup;
41import android.view.Window;
42import android.widget.FrameLayout;
43
44class ActionBarActivityDelegateBase extends ActionBarActivityDelegate implements
45        MenuPresenter.Callback, MenuBuilder.Callback {
46    private static final String TAG = "ActionBarActivityDelegateBase";
47
48    private static final int[] ACTION_BAR_DRAWABLE_TOGGLE_ATTRS = new int[] {
49            R.attr.homeAsUpIndicator
50    };
51
52    private ActionBarView mActionBarView;
53    private ListMenuPresenter mListMenuPresenter;
54    private MenuBuilder mMenu;
55
56    private ActionMode mActionMode;
57
58    // true if we have installed a window sub-decor layout.
59    private boolean mSubDecorInstalled;
60
61    private CharSequence mTitleToSet;
62
63    // Used to keep track of Progress Bar Window features
64    private boolean mFeatureProgress, mFeatureIndeterminateProgress;
65
66    // Used for emulating PanelFeatureState
67    private boolean mClosingActionMenu;
68    private boolean mPanelIsPrepared;
69    private boolean mPanelRefreshContent;
70    private Bundle mPanelFrozenActionViewState;
71
72    ActionBarActivityDelegateBase(ActionBarActivity activity) {
73        super(activity);
74    }
75
76    @Override
77    public ActionBar createSupportActionBar() {
78        ensureSubDecor();
79        return new ActionBarImplBase(mActivity, mActivity);
80    }
81
82    @Override
83    public void onConfigurationChanged(Configuration newConfig) {
84        // If this is called before sub-decor is installed, ActionBar will not
85        // be properly initialized.
86        if (mHasActionBar && mSubDecorInstalled) {
87            // Note: The action bar will need to access
88            // view changes from superclass.
89            ActionBarImplBase actionBar = (ActionBarImplBase) getSupportActionBar();
90            actionBar.onConfigurationChanged(newConfig);
91        }
92    }
93
94    @Override
95    public void onStop() {
96        ActionBarImplBase ab = (ActionBarImplBase) getSupportActionBar();
97        if (ab != null) {
98            ab.setShowHideAnimationEnabled(false);
99        }
100    }
101
102    @Override
103    public void onPostResume() {
104        ActionBarImplBase ab = (ActionBarImplBase) getSupportActionBar();
105        if (ab != null) {
106            ab.setShowHideAnimationEnabled(true);
107        }
108    }
109
110    @Override
111    public void setContentView(View v) {
112        ensureSubDecor();
113        if (mHasActionBar) {
114            ViewGroup contentParent = (ViewGroup) mActivity.findViewById(android.R.id.content);
115            contentParent.removeAllViews();
116            contentParent.addView(v);
117        } else {
118            mActivity.superSetContentView(v);
119        }
120        mActivity.onSupportContentChanged();
121    }
122
123    @Override
124    public void setContentView(int resId) {
125        ensureSubDecor();
126        if (mHasActionBar) {
127            ViewGroup contentParent = (ViewGroup) mActivity.findViewById(android.R.id.content);
128            contentParent.removeAllViews();
129            mActivity.getLayoutInflater().inflate(resId, contentParent);
130        } else {
131            mActivity.superSetContentView(resId);
132        }
133        mActivity.onSupportContentChanged();
134    }
135
136    @Override
137    public void setContentView(View v, ViewGroup.LayoutParams lp) {
138        ensureSubDecor();
139        if (mHasActionBar) {
140            ViewGroup contentParent = (ViewGroup) mActivity.findViewById(android.R.id.content);
141            contentParent.removeAllViews();
142            contentParent.addView(v, lp);
143        } else {
144            mActivity.superSetContentView(v, lp);
145        }
146        mActivity.onSupportContentChanged();
147    }
148
149    @Override
150    public void addContentView(View v, ViewGroup.LayoutParams lp) {
151        ensureSubDecor();
152        if (mHasActionBar) {
153            ViewGroup contentParent = (ViewGroup) mActivity.findViewById(android.R.id.content);
154            contentParent.addView(v, lp);
155        } else {
156            mActivity.superSetContentView(v, lp);
157        }
158        mActivity.onSupportContentChanged();
159    }
160
161    @Override
162    public void onContentChanged() {
163        // Ignore all calls to this method as we call onSupportContentChanged manually above
164    }
165
166    final void ensureSubDecor() {
167        if (mHasActionBar && !mSubDecorInstalled) {
168            if (mOverlayActionBar) {
169                mActivity.superSetContentView(R.layout.abc_action_bar_decor_overlay);
170            } else {
171                mActivity.superSetContentView(R.layout.abc_action_bar_decor);
172            }
173            mActionBarView = (ActionBarView) mActivity.findViewById(R.id.action_bar);
174            mActionBarView.setWindowCallback(mActivity);
175
176            /**
177             * Progress Bars
178             */
179            if (mFeatureProgress) {
180                mActionBarView.initProgress();
181            }
182            if (mFeatureIndeterminateProgress) {
183                mActionBarView.initIndeterminateProgress();
184            }
185
186            /**
187             * Split Action Bar
188             */
189            boolean splitWhenNarrow = UIOPTION_SPLIT_ACTION_BAR_WHEN_NARROW
190                    .equals(getUiOptionsFromMetadata());
191            boolean splitActionBar;
192
193            if (splitWhenNarrow) {
194                splitActionBar = mActivity.getResources()
195                        .getBoolean(R.bool.abc_split_action_bar_is_narrow);
196            } else {
197                TypedArray a = mActivity.obtainStyledAttributes(R.styleable.ActionBarWindow);
198                splitActionBar = a
199                        .getBoolean(R.styleable.ActionBarWindow_windowSplitActionBar, false);
200                a.recycle();
201            }
202
203            final ActionBarContainer splitView = (ActionBarContainer) mActivity.findViewById(
204                    R.id.split_action_bar);
205            if (splitView != null) {
206                mActionBarView.setSplitView(splitView);
207                mActionBarView.setSplitActionBar(splitActionBar);
208                mActionBarView.setSplitWhenNarrow(splitWhenNarrow);
209
210                final ActionBarContextView cab = (ActionBarContextView) mActivity.findViewById(
211                        R.id.action_context_bar);
212                cab.setSplitView(splitView);
213                cab.setSplitActionBar(splitActionBar);
214                cab.setSplitWhenNarrow(splitWhenNarrow);
215            }
216
217            // Change our content FrameLayout to use the android.R.id.content id.
218            // Useful for fragments.
219            View content = mActivity.findViewById(android.R.id.content);
220            content.setId(View.NO_ID);
221            View abcContent = mActivity.findViewById(R.id.action_bar_activity_content);
222            abcContent.setId(android.R.id.content);
223
224            // A title was set before we've install the decor so set it now.
225            if (mTitleToSet != null) {
226                mActionBarView.setWindowTitle(mTitleToSet);
227                mTitleToSet  = null;
228            }
229
230            mSubDecorInstalled = true;
231
232            // Post supportInvalidateOptionsMenu() so that the menu is invalidated post-onCreate()
233            content.post(new Runnable() {
234                @Override
235                public void run() {
236                    supportInvalidateOptionsMenu();
237                }
238            });
239        }
240    }
241
242    @Override
243    public boolean supportRequestWindowFeature(int featureId) {
244        switch (featureId) {
245            case WindowCompat.FEATURE_ACTION_BAR:
246                mHasActionBar = true;
247                return true;
248            case WindowCompat.FEATURE_ACTION_BAR_OVERLAY:
249                mOverlayActionBar = true;
250                return true;
251            case Window.FEATURE_PROGRESS:
252                mFeatureProgress = true;
253                return true;
254            case Window.FEATURE_INDETERMINATE_PROGRESS:
255                mFeatureIndeterminateProgress = true;
256                return true;
257            default:
258                return mActivity.requestWindowFeature(featureId);
259        }
260    }
261
262    @Override
263    public void onTitleChanged(CharSequence title) {
264        if (mActionBarView != null) {
265            mActionBarView.setWindowTitle(title);
266        } else {
267            mTitleToSet = title;
268        }
269    }
270
271    @Override
272    public View onCreatePanelView(int featureId) {
273        View createdPanelView = null;
274
275        if (featureId == Window.FEATURE_OPTIONS_PANEL && preparePanel()) {
276            createdPanelView = (View) getListMenuView(mActivity, this);
277        }
278
279        return createdPanelView;
280    }
281
282    @Override
283    public boolean onCreatePanelMenu(int featureId, Menu menu) {
284        if (featureId != Window.FEATURE_OPTIONS_PANEL) {
285            return mActivity.superOnCreatePanelMenu(featureId, menu);
286        }
287        return false;
288    }
289
290    @Override
291    public boolean onPreparePanel(int featureId, View view, Menu menu) {
292        if (featureId != Window.FEATURE_OPTIONS_PANEL) {
293            return mActivity.superOnPreparePanel(featureId, view, menu);
294        }
295        return false;
296    }
297
298    @Override
299    public boolean onMenuItemSelected(int featureId, MenuItem item) {
300        if (featureId == Window.FEATURE_OPTIONS_PANEL) {
301            item = MenuWrapperFactory.createMenuItemWrapper(item);
302        }
303        return mActivity.superOnMenuItemSelected(featureId, item);
304    }
305
306    @Override
307    public boolean onMenuItemSelected(MenuBuilder menu, MenuItem item) {
308        return mActivity.onMenuItemSelected(Window.FEATURE_OPTIONS_PANEL, item);
309    }
310
311    @Override
312    public void onMenuModeChange(MenuBuilder menu) {
313        reopenMenu(menu, true);
314    }
315
316    @Override
317    public void onCloseMenu(MenuBuilder menu, boolean allMenusAreClosing) {
318        if (mClosingActionMenu) {
319            return;
320        }
321        mClosingActionMenu = true;
322        mActivity.closeOptionsMenu();
323        mActionBarView.dismissPopupMenus();
324        mClosingActionMenu = false;
325    }
326
327    @Override
328    public boolean onOpenSubMenu(MenuBuilder subMenu) {
329        return false;
330    }
331
332    @Override
333    public ActionMode startSupportActionMode(ActionMode.Callback callback) {
334        if (callback == null) {
335            throw new IllegalArgumentException("ActionMode callback can not be null.");
336        }
337
338        if (mActionMode != null) {
339            mActionMode.finish();
340        }
341
342        final ActionMode.Callback wrappedCallback = new ActionModeCallbackWrapper(callback);
343
344        ActionBarImplBase ab = (ActionBarImplBase) getSupportActionBar();
345        if (ab != null) {
346            mActionMode = ab.startActionMode(wrappedCallback);
347        }
348
349        if (mActionMode != null) {
350            mActivity.onSupportActionModeStarted(mActionMode);
351        }
352        return mActionMode;
353    }
354
355    @Override
356    public void supportInvalidateOptionsMenu() {
357        if (mMenu != null) {
358            Bundle savedActionViewStates = new Bundle();
359            mMenu.saveActionViewStates(savedActionViewStates);
360            if (savedActionViewStates.size() > 0) {
361                mPanelFrozenActionViewState = savedActionViewStates;
362            }
363            // This will be started again when the panel is prepared.
364            mMenu.stopDispatchingItemsChanged();
365            mMenu.clear();
366        }
367        mPanelRefreshContent = true;
368
369        // Prepare the options panel if we have an action bar
370        if (mActionBarView != null) {
371            mPanelIsPrepared = false;
372            preparePanel();
373        }
374    }
375
376    private void reopenMenu(MenuBuilder menu, boolean toggleMenuMode) {
377        if (mActionBarView != null && mActionBarView.isOverflowReserved()) {
378            if (!mActionBarView.isOverflowMenuShowing() || !toggleMenuMode) {
379                if (mActionBarView.getVisibility() == View.VISIBLE) {
380                    mActionBarView.showOverflowMenu();
381                }
382            } else {
383                mActionBarView.hideOverflowMenu();
384            }
385            return;
386        }
387
388        menu.close();
389    }
390
391    private MenuView getListMenuView(Context context, MenuPresenter.Callback cb) {
392        if (mMenu == null) {
393            return null;
394        }
395
396        if (mListMenuPresenter == null) {
397            TypedArray a = context.obtainStyledAttributes(R.styleable.Theme);
398            final int listPresenterTheme = a.getResourceId(
399                    R.styleable.Theme_panelMenuListTheme,
400                    R.style.Theme_AppCompat_CompactMenu);
401            a.recycle();
402
403            mListMenuPresenter = new ListMenuPresenter(
404                    R.layout.abc_list_menu_item_layout, listPresenterTheme);
405            mListMenuPresenter.setCallback(cb);
406            mMenu.addMenuPresenter(mListMenuPresenter);
407        } else {
408            // Make sure we update the ListView
409            mListMenuPresenter.updateMenuView(false);
410        }
411
412        return mListMenuPresenter.getMenuView(new FrameLayout(context));
413    }
414
415    @Override
416    public boolean onBackPressed() {
417        // Back cancels action modes first.
418        if (mActionMode != null) {
419            mActionMode.finish();
420            return true;
421        }
422
423        // Next collapse any expanded action views.
424        if (mActionBarView != null && mActionBarView.hasExpandedActionView()) {
425            mActionBarView.collapseActionView();
426            return true;
427        }
428
429        return false;
430    }
431
432    @Override
433    void setSupportProgressBarVisibility(boolean visible) {
434        updateProgressBars(visible ? Window.PROGRESS_VISIBILITY_ON :
435                Window.PROGRESS_VISIBILITY_OFF);
436    }
437
438    @Override
439    void setSupportProgressBarIndeterminateVisibility(boolean visible) {
440        updateProgressBars(visible ? Window.PROGRESS_VISIBILITY_ON :
441                Window.PROGRESS_VISIBILITY_OFF);
442    }
443
444    @Override
445    void setSupportProgressBarIndeterminate(boolean indeterminate) {
446        updateProgressBars(indeterminate ? Window.PROGRESS_INDETERMINATE_ON
447                : Window.PROGRESS_INDETERMINATE_OFF);
448    }
449
450    @Override
451    void setSupportProgress(int progress) {
452        updateProgressBars(Window.PROGRESS_START + progress);
453    }
454
455    @Override
456    ActionBarDrawerToggle.Delegate getDrawerToggleDelegate() {
457        return new ActionBarDrawableToggleImpl();
458    }
459
460    /**
461     * Progress Bar function. Mostly extracted from PhoneWindow.java
462     */
463    private void updateProgressBars(int value) {
464        ProgressBarICS circularProgressBar = getCircularProgressBar();
465        ProgressBarICS horizontalProgressBar = getHorizontalProgressBar();
466
467        if (value == Window.PROGRESS_VISIBILITY_ON) {
468            if (mFeatureProgress) {
469                int level = horizontalProgressBar.getProgress();
470                int visibility = (horizontalProgressBar.isIndeterminate() || level < 10000) ?
471                        View.VISIBLE : View.INVISIBLE;
472                horizontalProgressBar.setVisibility(visibility);
473            }
474            if (mFeatureIndeterminateProgress) {
475                circularProgressBar.setVisibility(View.VISIBLE);
476            }
477        } else if (value == Window.PROGRESS_VISIBILITY_OFF) {
478            if (mFeatureProgress) {
479                horizontalProgressBar.setVisibility(View.GONE);
480            }
481            if (mFeatureIndeterminateProgress) {
482                circularProgressBar.setVisibility(View.GONE);
483            }
484        } else if (value == Window.PROGRESS_INDETERMINATE_ON) {
485            horizontalProgressBar.setIndeterminate(true);
486        } else if (value == Window.PROGRESS_INDETERMINATE_OFF) {
487            horizontalProgressBar.setIndeterminate(false);
488        } else if (Window.PROGRESS_START <= value && value <= Window.PROGRESS_END) {
489            // We want to set the progress value before testing for visibility
490            // so that when the progress bar becomes visible again, it has the
491            // correct level.
492            horizontalProgressBar.setProgress(value - Window.PROGRESS_START);
493
494            if (value < Window.PROGRESS_END) {
495                showProgressBars(horizontalProgressBar, circularProgressBar);
496            } else {
497                hideProgressBars(horizontalProgressBar, circularProgressBar);
498            }
499        }
500    }
501
502    private void showProgressBars(ProgressBarICS horizontalProgressBar,
503            ProgressBarICS spinnyProgressBar) {
504        if (mFeatureIndeterminateProgress && spinnyProgressBar.getVisibility() == View.INVISIBLE) {
505            spinnyProgressBar.setVisibility(View.VISIBLE);
506        }
507        // Only show the progress bars if the primary progress is not complete
508        if (mFeatureProgress && horizontalProgressBar.getProgress() < 10000) {
509            horizontalProgressBar.setVisibility(View.VISIBLE);
510        }
511    }
512
513    private void hideProgressBars(ProgressBarICS horizontalProgressBar,
514            ProgressBarICS spinnyProgressBar) {
515        if (mFeatureIndeterminateProgress && spinnyProgressBar.getVisibility() == View.VISIBLE) {
516            spinnyProgressBar.setVisibility(View.INVISIBLE);
517        }
518        if (mFeatureProgress && horizontalProgressBar.getVisibility() == View.VISIBLE) {
519            horizontalProgressBar.setVisibility(View.INVISIBLE);
520        }
521    }
522
523    private ProgressBarICS getCircularProgressBar() {
524        ProgressBarICS pb = (ProgressBarICS) mActionBarView.findViewById(R.id.progress_circular);
525        if (pb != null) {
526            pb.setVisibility(View.INVISIBLE);
527        }
528        return pb;
529    }
530
531    private ProgressBarICS getHorizontalProgressBar() {
532        ProgressBarICS pb = (ProgressBarICS) mActionBarView.findViewById(R.id.progress_horizontal);
533        if (pb != null) {
534            pb.setVisibility(View.INVISIBLE);
535        }
536        return pb;
537    }
538
539    private boolean initializePanelMenu() {
540        mMenu = new MenuBuilder(getActionBarThemedContext());
541        mMenu.setCallback(this);
542        return true;
543    }
544
545    private boolean preparePanel() {
546        // Already prepared (isPrepared will be reset to false later)
547        if (mPanelIsPrepared) {
548            return true;
549        }
550
551        // Init the panel state's menu--return false if init failed
552        if (mMenu == null || mPanelRefreshContent) {
553            if (mMenu == null) {
554                if (!initializePanelMenu() || (mMenu == null)) {
555                    return false;
556                }
557            }
558
559            if (mActionBarView != null) {
560                mActionBarView.setMenu(mMenu, this);
561            }
562
563            // Creating the panel menu will involve a lot of manipulation;
564            // don't dispatch change events to presenters until we're done.
565            mMenu.stopDispatchingItemsChanged();
566
567            // Call callback, and return if it doesn't want to display menu.
568            if (!mActivity.superOnCreatePanelMenu(Window.FEATURE_OPTIONS_PANEL, mMenu)) {
569                // Ditch the menu created above
570                mMenu = null;
571
572                if (mActionBarView != null) {
573                    // Don't show it in the action bar either
574                    mActionBarView.setMenu(null, this);
575                }
576
577                return false;
578            }
579
580            mPanelRefreshContent = false;
581        }
582
583        // Preparing the panel menu can involve a lot of manipulation;
584        // don't dispatch change events to presenters until we're done.
585        mMenu.stopDispatchingItemsChanged();
586
587        // Restore action view state before we prepare. This gives apps
588        // an opportunity to override frozen/restored state in onPrepare.
589        if (mPanelFrozenActionViewState != null) {
590            mMenu.restoreActionViewStates(mPanelFrozenActionViewState);
591            mPanelFrozenActionViewState = null;
592        }
593
594        // Callback and return if the callback does not want to show the menu
595        if (!mActivity.superOnPreparePanel(Window.FEATURE_OPTIONS_PANEL, null, mMenu)) {
596            if (mActionBarView != null) {
597                // The app didn't want to show the menu for now but it still exists.
598                // Clear it out of the action bar.
599                mActionBarView.setMenu(null, this);
600            }
601            mMenu.startDispatchingItemsChanged();
602            return false;
603        }
604
605        mMenu.startDispatchingItemsChanged();
606
607        // Set other state
608        mPanelIsPrepared = true;
609
610        return true;
611    }
612
613    /**
614     * Clears out internal reference when the action mode is destroyed.
615     */
616    private class ActionModeCallbackWrapper implements ActionMode.Callback {
617        private ActionMode.Callback mWrapped;
618
619        public ActionModeCallbackWrapper(ActionMode.Callback wrapped) {
620            mWrapped = wrapped;
621        }
622
623        public boolean onCreateActionMode(ActionMode mode, Menu menu) {
624            return mWrapped.onCreateActionMode(mode, menu);
625        }
626
627        public boolean onPrepareActionMode(ActionMode mode, Menu menu) {
628            return mWrapped.onPrepareActionMode(mode, menu);
629        }
630
631        public boolean onActionItemClicked(ActionMode mode, MenuItem item) {
632            return mWrapped.onActionItemClicked(mode, item);
633        }
634
635        public void onDestroyActionMode(ActionMode mode) {
636            mWrapped.onDestroyActionMode(mode);
637            mActivity.onSupportActionModeFinished(mode);
638            mActionMode = null;
639        }
640    }
641
642    private class ActionBarDrawableToggleImpl
643            implements ActionBarDrawerToggle.Delegate {
644
645        @Override
646        public Drawable getThemeUpIndicator() {
647            final TypedArray a = mActivity.obtainStyledAttributes(ACTION_BAR_DRAWABLE_TOGGLE_ATTRS);
648            final Drawable result = a.getDrawable(0);
649            a.recycle();
650            return result;
651        }
652
653        @Override
654        public void setActionBarUpIndicator(Drawable upDrawable, int contentDescRes) {
655            if (mActionBarView != null) {
656                mActionBarView.setHomeAsUpIndicator(upDrawable);
657            }
658        }
659
660        @Override
661        public void setActionBarDescription(int contentDescRes) {
662            // No support for setting Action Bar content description
663        }
664    }
665
666}
667