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