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