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