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