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