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.KeyCharacterMap;
27import android.view.KeyEvent;
28import android.view.LayoutInflater;
29import android.view.Menu;
30import android.view.MenuItem;
31import android.view.View;
32import android.view.View.OnFocusChangeListener;
33import android.view.ViewGroup;
34import android.view.ViewParent;
35import android.view.Window;
36import android.view.WindowCallbackWrapper;
37import android.widget.SpinnerAdapter;
38import android.widget.Toolbar;
39
40import com.android.internal.view.menu.MenuBuilder;
41import com.android.internal.view.menu.MenuPresenter;
42import com.android.internal.widget.DecorToolbar;
43import com.android.internal.widget.ToolbarWidgetWrapper;
44
45import java.util.ArrayList;
46
47public class ToolbarActionBar extends ActionBar {
48    private DecorToolbar mDecorToolbar;
49    private boolean mToolbarMenuPrepared;
50    private Window.Callback mWindowCallback;
51    private boolean mMenuCallbackSet;
52
53    private boolean mLastMenuVisibility;
54    private ArrayList<OnMenuVisibilityListener> mMenuVisibilityListeners =
55            new ArrayList<OnMenuVisibilityListener>();
56
57    private final Runnable mMenuInvalidator = new Runnable() {
58        @Override
59        public void run() {
60            populateOptionsMenu();
61        }
62    };
63
64    private final Toolbar.OnMenuItemClickListener mMenuClicker =
65            new Toolbar.OnMenuItemClickListener() {
66        @Override
67        public boolean onMenuItemClick(MenuItem item) {
68            return mWindowCallback.onMenuItemSelected(Window.FEATURE_OPTIONS_PANEL, item);
69        }
70    };
71
72    public ToolbarActionBar(Toolbar toolbar, CharSequence title, Window.Callback windowCallback) {
73        mDecorToolbar = new ToolbarWidgetWrapper(toolbar, false);
74        mWindowCallback = new ToolbarCallbackWrapper(windowCallback);
75        mDecorToolbar.setWindowCallback(mWindowCallback);
76        toolbar.setOnMenuItemClickListener(mMenuClicker);
77        mDecorToolbar.setWindowTitle(title);
78    }
79
80    public Window.Callback getWrappedWindowCallback() {
81        return mWindowCallback;
82    }
83
84    @Override
85    public void setCustomView(View view) {
86        setCustomView(view, new LayoutParams(LayoutParams.WRAP_CONTENT, LayoutParams.WRAP_CONTENT));
87    }
88
89    @Override
90    public void setCustomView(View view, LayoutParams layoutParams) {
91        if (view != null) {
92            view.setLayoutParams(layoutParams);
93        }
94        mDecorToolbar.setCustomView(view);
95    }
96
97    @Override
98    public void setCustomView(int resId) {
99        final LayoutInflater inflater = LayoutInflater.from(mDecorToolbar.getContext());
100        setCustomView(inflater.inflate(resId, mDecorToolbar.getViewGroup(), false));
101    }
102
103    @Override
104    public void setIcon(int resId) {
105        mDecorToolbar.setIcon(resId);
106    }
107
108    @Override
109    public void setIcon(Drawable icon) {
110        mDecorToolbar.setIcon(icon);
111    }
112
113    @Override
114    public void setLogo(int resId) {
115        mDecorToolbar.setLogo(resId);
116    }
117
118    @Override
119    public void setLogo(Drawable logo) {
120        mDecorToolbar.setLogo(logo);
121    }
122
123    @Override
124    public void setStackedBackgroundDrawable(Drawable d) {
125        // This space for rent (do nothing)
126    }
127
128    @Override
129    public void setSplitBackgroundDrawable(Drawable d) {
130        // This space for rent (do nothing)
131    }
132
133    @Override
134    public void setHomeButtonEnabled(boolean enabled) {
135        // If the nav button on a Toolbar is present, it's enabled. No-op.
136    }
137
138    @Override
139    public void setElevation(float elevation) {
140        mDecorToolbar.getViewGroup().setElevation(elevation);
141    }
142
143    @Override
144    public float getElevation() {
145        return mDecorToolbar.getViewGroup().getElevation();
146    }
147
148    @Override
149    public Context getThemedContext() {
150        return mDecorToolbar.getContext();
151    }
152
153    @Override
154    public boolean isTitleTruncated() {
155        return super.isTitleTruncated();
156    }
157
158    @Override
159    public void setHomeAsUpIndicator(Drawable indicator) {
160        mDecorToolbar.setNavigationIcon(indicator);
161    }
162
163    @Override
164    public void setHomeAsUpIndicator(int resId) {
165        mDecorToolbar.setNavigationIcon(resId);
166    }
167
168    @Override
169    public void setHomeActionContentDescription(CharSequence description) {
170        mDecorToolbar.setNavigationContentDescription(description);
171    }
172
173    @Override
174    public void setDefaultDisplayHomeAsUpEnabled(boolean enabled) {
175        // Do nothing
176    }
177
178    @Override
179    public void setHomeActionContentDescription(int resId) {
180        mDecorToolbar.setNavigationContentDescription(resId);
181    }
182
183    @Override
184    public void setShowHideAnimationEnabled(boolean enabled) {
185        // This space for rent; no-op.
186    }
187
188    @Override
189    public void onConfigurationChanged(Configuration config) {
190        super.onConfigurationChanged(config);
191    }
192
193    @Override
194    public ActionMode startActionMode(ActionMode.Callback callback) {
195        return null;
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        mDecorToolbar.getViewGroup().postOnAnimation(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        if (!mMenuCallbackSet) {
442            mDecorToolbar.setMenuCallbacks(new ActionMenuPresenterCallback(), new MenuBuilderCallback());
443            mMenuCallbackSet = true;
444        }
445        final Menu menu = mDecorToolbar.getMenu();
446        final MenuBuilder mb = menu instanceof MenuBuilder ? (MenuBuilder) menu : null;
447        if (mb != null) {
448            mb.stopDispatchingItemsChanged();
449        }
450        try {
451            menu.clear();
452            if (!mWindowCallback.onCreatePanelMenu(Window.FEATURE_OPTIONS_PANEL, menu) ||
453                    !mWindowCallback.onPreparePanel(Window.FEATURE_OPTIONS_PANEL, null, menu)) {
454                menu.clear();
455            }
456        } finally {
457            if (mb != null) {
458                mb.startDispatchingItemsChanged();
459            }
460        }
461    }
462
463    @Override
464    public boolean onMenuKeyEvent(KeyEvent event) {
465        if (event.getAction() == KeyEvent.ACTION_UP) {
466            openOptionsMenu();
467        }
468        return true;
469    }
470
471    @Override
472    public boolean onKeyShortcut(int keyCode, KeyEvent event) {
473        Menu menu = mDecorToolbar.getMenu();
474        if (menu != null) {
475            final KeyCharacterMap kmap = KeyCharacterMap.load(
476                    event != null ? event.getDeviceId() : KeyCharacterMap.VIRTUAL_KEYBOARD);
477            menu.setQwertyMode(kmap.getKeyboardType() != KeyCharacterMap.NUMERIC);
478            menu.performShortcut(keyCode, event, 0);
479        }
480        // This action bar always returns true for handling keyboard shortcuts.
481        // This will block the window from preparing a temporary panel to handle
482        // keyboard shortcuts.
483        return true;
484    }
485
486    @Override
487    public void onDestroy() {
488        // Remove any invalidation callbacks
489        mDecorToolbar.getViewGroup().removeCallbacks(mMenuInvalidator);
490    }
491
492    public void addOnMenuVisibilityListener(OnMenuVisibilityListener listener) {
493        mMenuVisibilityListeners.add(listener);
494    }
495
496    public void removeOnMenuVisibilityListener(OnMenuVisibilityListener listener) {
497        mMenuVisibilityListeners.remove(listener);
498    }
499
500    public void dispatchMenuVisibilityChanged(boolean isVisible) {
501        if (isVisible == mLastMenuVisibility) {
502            return;
503        }
504        mLastMenuVisibility = isVisible;
505
506        final int count = mMenuVisibilityListeners.size();
507        for (int i = 0; i < count; i++) {
508            mMenuVisibilityListeners.get(i).onMenuVisibilityChanged(isVisible);
509        }
510    }
511
512    /** @hide */
513    @Override
514    public boolean requestFocus() {
515        return requestFocus(mDecorToolbar.getViewGroup());
516    }
517
518    private class ToolbarCallbackWrapper extends WindowCallbackWrapper {
519        public ToolbarCallbackWrapper(Window.Callback wrapped) {
520            super(wrapped);
521        }
522
523        @Override
524        public boolean onPreparePanel(int featureId, View view, Menu menu) {
525            final boolean result = super.onPreparePanel(featureId, view, menu);
526            if (result && !mToolbarMenuPrepared) {
527                mDecorToolbar.setMenuPrepared();
528                mToolbarMenuPrepared = true;
529            }
530            return result;
531        }
532    }
533
534    private final class ActionMenuPresenterCallback implements MenuPresenter.Callback {
535        private boolean mClosingActionMenu;
536
537        @Override
538        public boolean onOpenSubMenu(MenuBuilder subMenu) {
539            if (mWindowCallback != null) {
540                mWindowCallback.onMenuOpened(Window.FEATURE_ACTION_BAR, subMenu);
541                return true;
542            }
543            return false;
544        }
545
546        @Override
547        public void onCloseMenu(MenuBuilder menu, boolean allMenusAreClosing) {
548            if (mClosingActionMenu) {
549                return;
550            }
551
552            mClosingActionMenu = true;
553            mDecorToolbar.dismissPopupMenus();
554            if (mWindowCallback != null) {
555                mWindowCallback.onPanelClosed(Window.FEATURE_ACTION_BAR, menu);
556            }
557            mClosingActionMenu = false;
558        }
559    }
560
561    private final class MenuBuilderCallback implements MenuBuilder.Callback {
562
563        @Override
564        public boolean onMenuItemSelected(MenuBuilder menu, MenuItem item) {
565            return false;
566        }
567
568        @Override
569        public void onMenuModeChange(MenuBuilder menu) {
570            if (mWindowCallback != null) {
571                if (mDecorToolbar.isOverflowMenuShowing()) {
572                    mWindowCallback.onPanelClosed(Window.FEATURE_ACTION_BAR, menu);
573                } else if (mWindowCallback.onPreparePanel(Window.FEATURE_OPTIONS_PANEL,
574                        null, menu)) {
575                    mWindowCallback.onMenuOpened(Window.FEATURE_ACTION_BAR, menu);
576                }
577            }
578        }
579    }
580}
581