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