[go: nahoru, domu]

1/*
2 * Copyright (C) 2014 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.Resources;
22import android.graphics.drawable.Drawable;
23import android.support.annotation.Nullable;
24import android.support.v4.view.ViewCompat;
25import android.support.v7.appcompat.R;
26import android.support.v7.view.WindowCallbackWrapper;
27import android.support.v7.view.menu.ListMenuPresenter;
28import android.support.v7.view.menu.MenuBuilder;
29import android.support.v7.view.menu.MenuPresenter;
30import android.support.v7.widget.DecorToolbar;
31import android.support.v7.widget.Toolbar;
32import android.support.v7.widget.ToolbarWidgetWrapper;
33import android.util.TypedValue;
34import android.view.ContextThemeWrapper;
35import android.view.KeyCharacterMap;
36import android.view.KeyEvent;
37import android.view.LayoutInflater;
38import android.view.Menu;
39import android.view.MenuItem;
40import android.view.View;
41import android.view.ViewGroup;
42import android.view.Window;
43import android.widget.SpinnerAdapter;
44
45import java.util.ArrayList;
46
47class ToolbarActionBar extends ActionBar {
48    private DecorToolbar mDecorToolbar;
49    private boolean mToolbarMenuPrepared;
50    private Window.Callback mWindowCallback;
51    private boolean mMenuCallbackSet;
52
53    private boolean mLastMenuVisibility;
54    private ArrayList<OnMenuVisibilityListener> mMenuVisibilityListeners = new ArrayList<>();
55
56    private ListMenuPresenter mListMenuPresenter;
57
58    private final Runnable mMenuInvalidator = new Runnable() {
59        @Override
60        public void run() {
61            populateOptionsMenu();
62        }
63    };
64
65    private final Toolbar.OnMenuItemClickListener mMenuClicker =
66            new Toolbar.OnMenuItemClickListener() {
67                @Override
68                public boolean onMenuItemClick(MenuItem item) {
69                    return mWindowCallback.onMenuItemSelected(Window.FEATURE_OPTIONS_PANEL, item);
70                }
71            };
72
73    public ToolbarActionBar(Toolbar toolbar, CharSequence title, Window.Callback callback) {
74        mDecorToolbar = new ToolbarWidgetWrapper(toolbar, false);
75        mWindowCallback = new ToolbarCallbackWrapper(callback);
76        mDecorToolbar.setWindowCallback(mWindowCallback);
77        toolbar.setOnMenuItemClickListener(mMenuClicker);
78        mDecorToolbar.setWindowTitle(title);
79    }
80
81    public Window.Callback getWrappedWindowCallback() {
82        return mWindowCallback;
83    }
84
85    @Override
86    public void setCustomView(View view) {
87        setCustomView(view, new LayoutParams(LayoutParams.WRAP_CONTENT, LayoutParams.WRAP_CONTENT));
88    }
89
90    @Override
91    public void setCustomView(View view, LayoutParams layoutParams) {
92        if (view != null) {
93            view.setLayoutParams(layoutParams);
94        }
95        mDecorToolbar.setCustomView(view);
96    }
97
98    @Override
99    public void setCustomView(int resId) {
100        final LayoutInflater inflater = LayoutInflater.from(mDecorToolbar.getContext());
101        setCustomView(inflater.inflate(resId, mDecorToolbar.getViewGroup(), false));
102    }
103
104    @Override
105    public void setIcon(int resId) {
106        mDecorToolbar.setIcon(resId);
107    }
108
109    @Override
110    public void setIcon(Drawable icon) {
111        mDecorToolbar.setIcon(icon);
112    }
113
114    @Override
115    public void setLogo(int resId) {
116        mDecorToolbar.setLogo(resId);
117    }
118
119    @Override
120    public void setLogo(Drawable logo) {
121        mDecorToolbar.setLogo(logo);
122    }
123
124    @Override
125    public void setStackedBackgroundDrawable(Drawable d) {
126        // This space for rent (do nothing)
127    }
128
129    @Override
130    public void setSplitBackgroundDrawable(Drawable d) {
131        // This space for rent (do nothing)
132    }
133
134    @Override
135    public void setHomeButtonEnabled(boolean enabled) {
136        // If the nav button on a Toolbar is present, it's enabled. No-op.
137    }
138
139    @Override
140    public void setElevation(float elevation) {
141        ViewCompat.setElevation(mDecorToolbar.getViewGroup(), elevation);
142    }
143
144    @Override
145    public float getElevation() {
146        return ViewCompat.getElevation(mDecorToolbar.getViewGroup());
147    }
148
149    @Override
150    public Context getThemedContext() {
151        return mDecorToolbar.getContext();
152    }
153
154    @Override
155    public boolean isTitleTruncated() {
156        return super.isTitleTruncated();
157    }
158
159    @Override
160    public void setHomeAsUpIndicator(Drawable indicator) {
161        mDecorToolbar.setNavigationIcon(indicator);
162    }
163
164    @Override
165    public void setHomeAsUpIndicator(int resId) {
166        mDecorToolbar.setNavigationIcon(resId);
167    }
168
169    @Override
170    public void setHomeActionContentDescription(CharSequence description) {
171        mDecorToolbar.setNavigationContentDescription(description);
172    }
173
174    @Override
175    public void setDefaultDisplayHomeAsUpEnabled(boolean enabled) {
176        // Do nothing
177    }
178
179    @Override
180    public void setHomeActionContentDescription(int resId) {
181        mDecorToolbar.setNavigationContentDescription(resId);
182    }
183
184    @Override
185    public void setShowHideAnimationEnabled(boolean enabled) {
186        // This space for rent; no-op.
187    }
188
189    @Override
190    public void onConfigurationChanged(Configuration config) {
191        super.onConfigurationChanged(config);
192    }
193
194    @Override
195    public void setListNavigationCallbacks(SpinnerAdapter adapter, OnNavigationListener callback) {
196        mDecorToolbar.setDropdownParams(adapter, new NavItemSelectedListener(callback));
197    }
198
199    @Override
200    public void setSelectedNavigationItem(int position) {
201        switch (mDecorToolbar.getNavigationMode()) {
202            case NAVIGATION_MODE_LIST:
203                mDecorToolbar.setDropdownSelectedPosition(position);
204                break;
205            default:
206                throw new IllegalStateException(
207                        "setSelectedNavigationIndex not valid for current navigation mode");
208        }
209    }
210
211    @Override
212    public int getSelectedNavigationIndex() {
213        return -1;
214    }
215
216    @Override
217    public int getNavigationItemCount() {
218        return 0;
219    }
220
221    @Override
222    public void setTitle(CharSequence title) {
223        mDecorToolbar.setTitle(title);
224    }
225
226    @Override
227    public void setTitle(int resId) {
228        mDecorToolbar.setTitle(resId != 0 ? mDecorToolbar.getContext().getText(resId) : null);
229    }
230
231    @Override
232    public void setWindowTitle(CharSequence title) {
233        mDecorToolbar.setWindowTitle(title);
234    }
235
236    @Override
237    public boolean requestFocus() {
238        final ViewGroup viewGroup = mDecorToolbar.getViewGroup();
239        if (viewGroup != null && !viewGroup.hasFocus()) {
240            viewGroup.requestFocus();
241            return true;
242        }
243        return false;
244    }
245
246    @Override
247    public void setSubtitle(CharSequence subtitle) {
248        mDecorToolbar.setSubtitle(subtitle);
249    }
250
251    @Override
252    public void setSubtitle(int resId) {
253        mDecorToolbar.setSubtitle(resId != 0 ? mDecorToolbar.getContext().getText(resId) : null);
254    }
255
256    @Override
257    public void setDisplayOptions(@DisplayOptions int options) {
258        setDisplayOptions(options, 0xffffffff);
259    }
260
261    @Override
262    public void setDisplayOptions(@DisplayOptions int options, @DisplayOptions int mask) {
263        final int currentOptions = mDecorToolbar.getDisplayOptions();
264        mDecorToolbar.setDisplayOptions(options & mask | currentOptions & ~mask);
265    }
266
267    @Override
268    public void setDisplayUseLogoEnabled(boolean useLogo) {
269        setDisplayOptions(useLogo ? DISPLAY_USE_LOGO : 0, DISPLAY_USE_LOGO);
270    }
271
272    @Override
273    public void setDisplayShowHomeEnabled(boolean showHome) {
274        setDisplayOptions(showHome ? DISPLAY_SHOW_HOME : 0, DISPLAY_SHOW_HOME);
275    }
276
277    @Override
278    public void setDisplayHomeAsUpEnabled(boolean showHomeAsUp) {
279        setDisplayOptions(showHomeAsUp ? DISPLAY_HOME_AS_UP : 0, DISPLAY_HOME_AS_UP);
280    }
281
282    @Override
283    public void setDisplayShowTitleEnabled(boolean showTitle) {
284        setDisplayOptions(showTitle ? DISPLAY_SHOW_TITLE : 0, DISPLAY_SHOW_TITLE);
285    }
286
287    @Override
288    public void setDisplayShowCustomEnabled(boolean showCustom) {
289        setDisplayOptions(showCustom ? DISPLAY_SHOW_CUSTOM : 0, DISPLAY_SHOW_CUSTOM);
290    }
291
292    @Override
293    public void setBackgroundDrawable(@Nullable Drawable d) {
294        mDecorToolbar.setBackgroundDrawable(d);
295    }
296
297    @Override
298    public View getCustomView() {
299        return mDecorToolbar.getCustomView();
300    }
301
302    @Override
303    public CharSequence getTitle() {
304        return mDecorToolbar.getTitle();
305    }
306
307    @Override
308    public CharSequence getSubtitle() {
309        return mDecorToolbar.getSubtitle();
310    }
311
312    @Override
313    public int getNavigationMode() {
314        return NAVIGATION_MODE_STANDARD;
315    }
316
317    @Override
318    public void setNavigationMode(@NavigationMode int mode) {
319        if (mode == ActionBar.NAVIGATION_MODE_TABS) {
320            throw new IllegalArgumentException("Tabs not supported in this configuration");
321        }
322        mDecorToolbar.setNavigationMode(mode);
323    }
324
325    @Override
326    public int getDisplayOptions() {
327        return mDecorToolbar.getDisplayOptions();
328    }
329
330    @Override
331    public Tab newTab() {
332        throw new UnsupportedOperationException(
333                "Tabs are not supported in toolbar action bars");
334    }
335
336    @Override
337    public void addTab(Tab tab) {
338        throw new UnsupportedOperationException(
339                "Tabs are not supported in toolbar action bars");
340    }
341
342    @Override
343    public void addTab(Tab tab, boolean setSelected) {
344        throw new UnsupportedOperationException(
345                "Tabs are not supported in toolbar action bars");
346    }
347
348    @Override
349    public void addTab(Tab tab, int position) {
350        throw new UnsupportedOperationException(
351                "Tabs are not supported in toolbar action bars");
352    }
353
354    @Override
355    public void addTab(Tab tab, int position, boolean setSelected) {
356        throw new UnsupportedOperationException(
357                "Tabs are not supported in toolbar action bars");
358    }
359
360    @Override
361    public void removeTab(Tab tab) {
362        throw new UnsupportedOperationException(
363                "Tabs are not supported in toolbar action bars");
364    }
365
366    @Override
367    public void removeTabAt(int position) {
368        throw new UnsupportedOperationException(
369                "Tabs are not supported in toolbar action bars");
370    }
371
372    @Override
373    public void removeAllTabs() {
374        throw new UnsupportedOperationException(
375                "Tabs are not supported in toolbar action bars");
376    }
377
378    @Override
379    public void selectTab(Tab tab) {
380        throw new UnsupportedOperationException(
381                "Tabs are not supported in toolbar action bars");
382    }
383
384    @Override
385    public Tab getSelectedTab() {
386        throw new UnsupportedOperationException(
387                "Tabs are not supported in toolbar action bars");
388    }
389
390    @Override
391    public Tab getTabAt(int index) {
392        throw new UnsupportedOperationException(
393                "Tabs are not supported in toolbar action bars");
394    }
395
396    @Override
397    public int getTabCount() {
398        return 0;
399    }
400
401    @Override
402    public int getHeight() {
403        return mDecorToolbar.getHeight();
404    }
405
406    @Override
407    public void show() {
408        // TODO: Consider a better transition for this.
409        // Right now use no automatic transition so that the app can supply one if desired.
410        mDecorToolbar.setVisibility(View.VISIBLE);
411    }
412
413    @Override
414    public void hide() {
415        // TODO: Consider a better transition for this.
416        // Right now use no automatic transition so that the app can supply one if desired.
417        mDecorToolbar.setVisibility(View.GONE);
418    }
419
420    @Override
421    public boolean isShowing() {
422        return mDecorToolbar.getVisibility() == View.VISIBLE;
423    }
424
425    @Override
426    public boolean openOptionsMenu() {
427        return mDecorToolbar.showOverflowMenu();
428    }
429
430    @Override
431    public boolean invalidateOptionsMenu() {
432        mDecorToolbar.getViewGroup().removeCallbacks(mMenuInvalidator);
433        ViewCompat.postOnAnimation(mDecorToolbar.getViewGroup(), mMenuInvalidator);
434        return true;
435    }
436
437    @Override
438    public boolean collapseActionView() {
439        if (mDecorToolbar.hasExpandedActionView()) {
440            mDecorToolbar.collapseActionView();
441            return true;
442        }
443        return false;
444    }
445
446    void populateOptionsMenu() {
447        final Menu menu = getMenu();
448        final MenuBuilder mb = menu instanceof MenuBuilder ? (MenuBuilder) menu : null;
449        if (mb != null) {
450            mb.stopDispatchingItemsChanged();
451        }
452        try {
453            menu.clear();
454            if (!mWindowCallback.onCreatePanelMenu(Window.FEATURE_OPTIONS_PANEL, menu) ||
455                    !mWindowCallback.onPreparePanel(Window.FEATURE_OPTIONS_PANEL, null, menu)) {
456                menu.clear();
457            }
458        } finally {
459            if (mb != null) {
460                mb.startDispatchingItemsChanged();
461            }
462        }
463    }
464
465    @Override
466    public boolean onMenuKeyEvent(KeyEvent event) {
467        if (event.getAction() == KeyEvent.ACTION_UP) {
468            openOptionsMenu();
469        }
470        return true;
471    }
472
473    @Override
474    public boolean onKeyShortcut(int keyCode, KeyEvent ev) {
475        Menu menu = getMenu();
476        if (menu != null) {
477            final KeyCharacterMap kmap = KeyCharacterMap.load(
478                    ev != null ? ev.getDeviceId() : KeyCharacterMap.VIRTUAL_KEYBOARD);
479            menu.setQwertyMode(kmap.getKeyboardType() != KeyCharacterMap.NUMERIC);
480            menu.performShortcut(keyCode, ev, 0);
481        }
482        // This action bar always returns true for handling keyboard shortcuts.
483        // This will block the window from preparing a temporary panel to handle
484        // keyboard shortcuts.
485        return true;
486    }
487
488    @Override
489    void onDestroy() {
490        // Remove any invalidation callbacks
491        mDecorToolbar.getViewGroup().removeCallbacks(mMenuInvalidator);
492    }
493
494    public void addOnMenuVisibilityListener(OnMenuVisibilityListener listener) {
495        mMenuVisibilityListeners.add(listener);
496    }
497
498    public void removeOnMenuVisibilityListener(OnMenuVisibilityListener listener) {
499        mMenuVisibilityListeners.remove(listener);
500    }
501
502    public void dispatchMenuVisibilityChanged(boolean isVisible) {
503        if (isVisible == mLastMenuVisibility) {
504            return;
505        }
506        mLastMenuVisibility = isVisible;
507
508        final int count = mMenuVisibilityListeners.size();
509        for (int i = 0; i < count; i++) {
510            mMenuVisibilityListeners.get(i).onMenuVisibilityChanged(isVisible);
511        }
512    }
513
514    private View getListMenuView(Menu menu) {
515        ensureListMenuPresenter(menu);
516
517        if (menu == null || mListMenuPresenter == null) {
518            return null;
519        }
520
521        if (mListMenuPresenter.getAdapter().getCount() > 0) {
522            return (View) mListMenuPresenter.getMenuView(mDecorToolbar.getViewGroup());
523        }
524        return null;
525    }
526
527    private void ensureListMenuPresenter(Menu menu) {
528        if (mListMenuPresenter == null && (menu instanceof MenuBuilder)) {
529            MenuBuilder mb = (MenuBuilder) menu;
530
531            Context context = mDecorToolbar.getContext();
532            final TypedValue outValue = new TypedValue();
533            final Resources.Theme widgetTheme = context.getResources().newTheme();
534            widgetTheme.setTo(context.getTheme());
535
536            // First apply the actionBarPopupTheme
537            widgetTheme.resolveAttribute(R.attr.actionBarPopupTheme, outValue, true);
538            if (outValue.resourceId != 0) {
539                widgetTheme.applyStyle(outValue.resourceId, true);
540            }
541
542            // Apply the panelMenuListTheme
543            widgetTheme.resolveAttribute(R.attr.panelMenuListTheme, outValue, true);
544            if (outValue.resourceId != 0) {
545                widgetTheme.applyStyle(outValue.resourceId, true);
546            } else {
547                widgetTheme.applyStyle(R.style.Theme_AppCompat_CompactMenu, true);
548            }
549
550            context = new ContextThemeWrapper(context, 0);
551            context.getTheme().setTo(widgetTheme);
552
553            // Finally create the list menu presenter
554            mListMenuPresenter = new ListMenuPresenter(context, R.layout.abc_list_menu_item_layout);
555            mListMenuPresenter.setCallback(new PanelMenuPresenterCallback());
556            mb.addMenuPresenter(mListMenuPresenter);
557        }
558    }
559
560    private class ToolbarCallbackWrapper extends WindowCallbackWrapper {
561        public ToolbarCallbackWrapper(Window.Callback wrapped) {
562            super(wrapped);
563        }
564
565        @Override
566        public boolean onPreparePanel(int featureId, View view, Menu menu) {
567            final boolean result = super.onPreparePanel(featureId, view, menu);
568            if (result && !mToolbarMenuPrepared) {
569                mDecorToolbar.setMenuPrepared();
570                mToolbarMenuPrepared = true;
571            }
572            return result;
573        }
574
575        @Override
576        public View onCreatePanelView(int featureId) {
577            switch (featureId) {
578                case Window.FEATURE_OPTIONS_PANEL:
579                    final Menu menu = mDecorToolbar.getMenu();
580                    if (onPreparePanel(featureId, null, menu) && onMenuOpened(featureId, menu)) {
581                        return getListMenuView(menu);
582                    }
583                    break;
584            }
585            return super.onCreatePanelView(featureId);
586        }
587    }
588
589    private Menu getMenu() {
590        if (!mMenuCallbackSet) {
591            mDecorToolbar.setMenuCallbacks(new ActionMenuPresenterCallback(),
592                    new MenuBuilderCallback());
593            mMenuCallbackSet = true;
594        }
595        return mDecorToolbar.getMenu();
596    }
597
598    private final class ActionMenuPresenterCallback implements MenuPresenter.Callback {
599        private boolean mClosingActionMenu;
600
601        @Override
602        public boolean onOpenSubMenu(MenuBuilder subMenu) {
603            if (mWindowCallback != null) {
604                mWindowCallback.onMenuOpened(AppCompatDelegate.FEATURE_SUPPORT_ACTION_BAR, subMenu);
605                return true;
606            }
607            return false;
608        }
609
610        @Override
611        public void onCloseMenu(MenuBuilder menu, boolean allMenusAreClosing) {
612            if (mClosingActionMenu) {
613                return;
614            }
615
616            mClosingActionMenu = true;
617            mDecorToolbar.dismissPopupMenus();
618            if (mWindowCallback != null) {
619                mWindowCallback.onPanelClosed(AppCompatDelegate.FEATURE_SUPPORT_ACTION_BAR, menu);
620            }
621            mClosingActionMenu = false;
622        }
623    }
624
625    private final class PanelMenuPresenterCallback implements MenuPresenter.Callback {
626        @Override
627        public void onCloseMenu(MenuBuilder menu, boolean allMenusAreClosing) {
628            if (mWindowCallback != null) {
629                mWindowCallback.onPanelClosed(Window.FEATURE_OPTIONS_PANEL, menu);
630            }
631        }
632
633        @Override
634        public boolean onOpenSubMenu(MenuBuilder subMenu) {
635            if (subMenu == null && mWindowCallback != null) {
636                mWindowCallback.onMenuOpened(Window.FEATURE_OPTIONS_PANEL, subMenu);
637            }
638            return true;
639        }
640    }
641
642    private final class MenuBuilderCallback implements MenuBuilder.Callback {
643
644        @Override
645        public boolean onMenuItemSelected(MenuBuilder menu, MenuItem item) {
646            return false;
647        }
648
649        @Override
650        public void onMenuModeChange(MenuBuilder menu) {
651            if (mWindowCallback != null) {
652                if (mDecorToolbar.isOverflowMenuShowing()) {
653                    mWindowCallback.onPanelClosed(AppCompatDelegate.FEATURE_SUPPORT_ACTION_BAR, menu);
654                } else if (mWindowCallback.onPreparePanel(Window.FEATURE_OPTIONS_PANEL,
655                        null, menu)) {
656                    mWindowCallback.onMenuOpened(AppCompatDelegate.FEATURE_SUPPORT_ACTION_BAR, menu);
657                }
658            }
659        }
660    }
661}
662