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 
18 package com.android.internal.app;
19 
20 import com.android.internal.view.menu.MenuBuilder;
21 import com.android.internal.view.menu.MenuPresenter;
22 import com.android.internal.widget.DecorToolbar;
23 import com.android.internal.widget.ToolbarWidgetWrapper;
24 
25 import android.annotation.Nullable;
26 import android.app.ActionBar;
27 import android.content.Context;
28 import android.content.res.Configuration;
29 import android.graphics.drawable.Drawable;
30 import android.view.ActionMode;
31 import android.view.KeyCharacterMap;
32 import android.view.KeyEvent;
33 import android.view.LayoutInflater;
34 import android.view.Menu;
35 import android.view.MenuItem;
36 import android.view.View;
37 import android.view.Window;
38 import android.view.WindowCallbackWrapper;
39 import android.widget.SpinnerAdapter;
40 import android.widget.Toolbar;
41 
42 import java.util.ArrayList;
43 
44 public 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 
ToolbarActionBar(Toolbar toolbar, CharSequence title, Window.Callback windowCallback)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 
getWrappedWindowCallback()77     public Window.Callback getWrappedWindowCallback() {
78         return mWindowCallback;
79     }
80 
81     @Override
setCustomView(View view)82     public void setCustomView(View view) {
83         setCustomView(view, new LayoutParams(LayoutParams.WRAP_CONTENT, LayoutParams.WRAP_CONTENT));
84     }
85 
86     @Override
setCustomView(View view, LayoutParams layoutParams)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
setCustomView(int resId)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
setIcon(int resId)101     public void setIcon(int resId) {
102         mDecorToolbar.setIcon(resId);
103     }
104 
105     @Override
setIcon(Drawable icon)106     public void setIcon(Drawable icon) {
107         mDecorToolbar.setIcon(icon);
108     }
109 
110     @Override
setLogo(int resId)111     public void setLogo(int resId) {
112         mDecorToolbar.setLogo(resId);
113     }
114 
115     @Override
setLogo(Drawable logo)116     public void setLogo(Drawable logo) {
117         mDecorToolbar.setLogo(logo);
118     }
119 
120     @Override
setStackedBackgroundDrawable(Drawable d)121     public void setStackedBackgroundDrawable(Drawable d) {
122         // This space for rent (do nothing)
123     }
124 
125     @Override
setSplitBackgroundDrawable(Drawable d)126     public void setSplitBackgroundDrawable(Drawable d) {
127         // This space for rent (do nothing)
128     }
129 
130     @Override
setHomeButtonEnabled(boolean enabled)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
setElevation(float elevation)136     public void setElevation(float elevation) {
137         mDecorToolbar.getViewGroup().setElevation(elevation);
138     }
139 
140     @Override
getElevation()141     public float getElevation() {
142         return mDecorToolbar.getViewGroup().getElevation();
143     }
144 
145     @Override
getThemedContext()146     public Context getThemedContext() {
147         return mDecorToolbar.getContext();
148     }
149 
150     @Override
isTitleTruncated()151     public boolean isTitleTruncated() {
152         return super.isTitleTruncated();
153     }
154 
155     @Override
setHomeAsUpIndicator(Drawable indicator)156     public void setHomeAsUpIndicator(Drawable indicator) {
157         mDecorToolbar.setNavigationIcon(indicator);
158     }
159 
160     @Override
setHomeAsUpIndicator(int resId)161     public void setHomeAsUpIndicator(int resId) {
162         mDecorToolbar.setNavigationIcon(resId);
163     }
164 
165     @Override
setHomeActionContentDescription(CharSequence description)166     public void setHomeActionContentDescription(CharSequence description) {
167         mDecorToolbar.setNavigationContentDescription(description);
168     }
169 
170     @Override
setDefaultDisplayHomeAsUpEnabled(boolean enabled)171     public void setDefaultDisplayHomeAsUpEnabled(boolean enabled) {
172         // Do nothing
173     }
174 
175     @Override
setHomeActionContentDescription(int resId)176     public void setHomeActionContentDescription(int resId) {
177         mDecorToolbar.setNavigationContentDescription(resId);
178     }
179 
180     @Override
setShowHideAnimationEnabled(boolean enabled)181     public void setShowHideAnimationEnabled(boolean enabled) {
182         // This space for rent; no-op.
183     }
184 
185     @Override
onConfigurationChanged(Configuration config)186     public void onConfigurationChanged(Configuration config) {
187         super.onConfigurationChanged(config);
188     }
189 
190     @Override
startActionMode(ActionMode.Callback callback)191     public ActionMode startActionMode(ActionMode.Callback callback) {
192         return null;
193     }
194 
195     @Override
setListNavigationCallbacks(SpinnerAdapter adapter, OnNavigationListener callback)196     public void setListNavigationCallbacks(SpinnerAdapter adapter, OnNavigationListener callback) {
197         mDecorToolbar.setDropdownParams(adapter, new NavItemSelectedListener(callback));
198     }
199 
200     @Override
setSelectedNavigationItem(int position)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
getSelectedNavigationIndex()213     public int getSelectedNavigationIndex() {
214         return -1;
215     }
216 
217     @Override
getNavigationItemCount()218     public int getNavigationItemCount() {
219         return 0;
220     }
221 
222     @Override
setTitle(CharSequence title)223     public void setTitle(CharSequence title) {
224         mDecorToolbar.setTitle(title);
225     }
226 
227     @Override
setTitle(int resId)228     public void setTitle(int resId) {
229         mDecorToolbar.setTitle(resId != 0 ? mDecorToolbar.getContext().getText(resId) : null);
230     }
231 
232     @Override
setWindowTitle(CharSequence title)233     public void setWindowTitle(CharSequence title) {
234         mDecorToolbar.setWindowTitle(title);
235     }
236 
237     @Override
setSubtitle(CharSequence subtitle)238     public void setSubtitle(CharSequence subtitle) {
239         mDecorToolbar.setSubtitle(subtitle);
240     }
241 
242     @Override
setSubtitle(int resId)243     public void setSubtitle(int resId) {
244         mDecorToolbar.setSubtitle(resId != 0 ? mDecorToolbar.getContext().getText(resId) : null);
245     }
246 
247     @Override
setDisplayOptions(@isplayOptions int options)248     public void setDisplayOptions(@DisplayOptions int options) {
249         setDisplayOptions(options, 0xffffffff);
250     }
251 
252     @Override
setDisplayOptions(@isplayOptions int options, @DisplayOptions int mask)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
setDisplayUseLogoEnabled(boolean useLogo)259     public void setDisplayUseLogoEnabled(boolean useLogo) {
260         setDisplayOptions(useLogo ? DISPLAY_USE_LOGO : 0, DISPLAY_USE_LOGO);
261     }
262 
263     @Override
setDisplayShowHomeEnabled(boolean showHome)264     public void setDisplayShowHomeEnabled(boolean showHome) {
265         setDisplayOptions(showHome ? DISPLAY_SHOW_HOME : 0, DISPLAY_SHOW_HOME);
266     }
267 
268     @Override
setDisplayHomeAsUpEnabled(boolean showHomeAsUp)269     public void setDisplayHomeAsUpEnabled(boolean showHomeAsUp) {
270         setDisplayOptions(showHomeAsUp ? DISPLAY_HOME_AS_UP : 0, DISPLAY_HOME_AS_UP);
271     }
272 
273     @Override
setDisplayShowTitleEnabled(boolean showTitle)274     public void setDisplayShowTitleEnabled(boolean showTitle) {
275         setDisplayOptions(showTitle ? DISPLAY_SHOW_TITLE : 0, DISPLAY_SHOW_TITLE);
276     }
277 
278     @Override
setDisplayShowCustomEnabled(boolean showCustom)279     public void setDisplayShowCustomEnabled(boolean showCustom) {
280         setDisplayOptions(showCustom ? DISPLAY_SHOW_CUSTOM : 0, DISPLAY_SHOW_CUSTOM);
281     }
282 
283     @Override
setBackgroundDrawable(@ullable Drawable d)284     public void setBackgroundDrawable(@Nullable Drawable d) {
285         mDecorToolbar.setBackgroundDrawable(d);
286     }
287 
288     @Override
getCustomView()289     public View getCustomView() {
290         return mDecorToolbar.getCustomView();
291     }
292 
293     @Override
getTitle()294     public CharSequence getTitle() {
295         return mDecorToolbar.getTitle();
296     }
297 
298     @Override
getSubtitle()299     public CharSequence getSubtitle() {
300         return mDecorToolbar.getSubtitle();
301     }
302 
303     @Override
getNavigationMode()304     public int getNavigationMode() {
305         return NAVIGATION_MODE_STANDARD;
306     }
307 
308     @Override
setNavigationMode(@avigationMode int mode)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
getDisplayOptions()317     public int getDisplayOptions() {
318         return mDecorToolbar.getDisplayOptions();
319     }
320 
321     @Override
newTab()322     public Tab newTab() {
323         throw new UnsupportedOperationException(
324                 "Tabs are not supported in toolbar action bars");
325     }
326 
327     @Override
addTab(Tab tab)328     public void addTab(Tab tab) {
329         throw new UnsupportedOperationException(
330                 "Tabs are not supported in toolbar action bars");
331     }
332 
333     @Override
addTab(Tab tab, boolean setSelected)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
addTab(Tab tab, int position)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
addTab(Tab tab, int position, boolean setSelected)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
removeTab(Tab tab)352     public void removeTab(Tab tab) {
353         throw new UnsupportedOperationException(
354                 "Tabs are not supported in toolbar action bars");
355     }
356 
357     @Override
removeTabAt(int position)358     public void removeTabAt(int position) {
359         throw new UnsupportedOperationException(
360                 "Tabs are not supported in toolbar action bars");
361     }
362 
363     @Override
removeAllTabs()364     public void removeAllTabs() {
365         throw new UnsupportedOperationException(
366                 "Tabs are not supported in toolbar action bars");
367     }
368 
369     @Override
selectTab(Tab tab)370     public void selectTab(Tab tab) {
371         throw new UnsupportedOperationException(
372                 "Tabs are not supported in toolbar action bars");
373     }
374 
375     @Override
getSelectedTab()376     public Tab getSelectedTab() {
377         throw new UnsupportedOperationException(
378                 "Tabs are not supported in toolbar action bars");
379     }
380 
381     @Override
getTabAt(int index)382     public Tab getTabAt(int index) {
383         throw new UnsupportedOperationException(
384                 "Tabs are not supported in toolbar action bars");
385     }
386 
387     @Override
getTabCount()388     public int getTabCount() {
389         return 0;
390     }
391 
392     @Override
getHeight()393     public int getHeight() {
394         return mDecorToolbar.getHeight();
395     }
396 
397     @Override
show()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
hide()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
isShowing()412     public boolean isShowing() {
413         return mDecorToolbar.getVisibility() == View.VISIBLE;
414     }
415 
416     @Override
openOptionsMenu()417     public boolean openOptionsMenu() {
418         return mDecorToolbar.showOverflowMenu();
419     }
420 
421     @Override
closeOptionsMenu()422     public boolean closeOptionsMenu() {
423         return mDecorToolbar.hideOverflowMenu();
424     }
425 
426     @Override
invalidateOptionsMenu()427     public boolean invalidateOptionsMenu() {
428         mDecorToolbar.getViewGroup().removeCallbacks(mMenuInvalidator);
429         mDecorToolbar.getViewGroup().postOnAnimation(mMenuInvalidator);
430         return true;
431     }
432 
433     @Override
collapseActionView()434     public boolean collapseActionView() {
435         if (mDecorToolbar.hasExpandedActionView()) {
436             mDecorToolbar.collapseActionView();
437             return true;
438         }
439         return false;
440     }
441 
populateOptionsMenu()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
onMenuKeyEvent(KeyEvent event)466     public boolean onMenuKeyEvent(KeyEvent event) {
467         if (event.getAction() == KeyEvent.ACTION_UP) {
468             openOptionsMenu();
469         }
470         return true;
471     }
472 
473     @Override
onKeyShortcut(int keyCode, KeyEvent event)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
onDestroy()486     public void onDestroy() {
487         // Remove any invalidation callbacks
488         mDecorToolbar.getViewGroup().removeCallbacks(mMenuInvalidator);
489     }
490 
addOnMenuVisibilityListener(OnMenuVisibilityListener listener)491     public void addOnMenuVisibilityListener(OnMenuVisibilityListener listener) {
492         mMenuVisibilityListeners.add(listener);
493     }
494 
removeOnMenuVisibilityListener(OnMenuVisibilityListener listener)495     public void removeOnMenuVisibilityListener(OnMenuVisibilityListener listener) {
496         mMenuVisibilityListeners.remove(listener);
497     }
498 
dispatchMenuVisibilityChanged(boolean isVisible)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 {
ToolbarCallbackWrapper(Window.Callback wrapped)512         public ToolbarCallbackWrapper(Window.Callback wrapped) {
513             super(wrapped);
514         }
515 
516         @Override
onPreparePanel(int featureId, View view, Menu menu)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
onCreatePanelView(int featureId)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
onOpenSubMenu(MenuBuilder subMenu)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
onCloseMenu(MenuBuilder menu, boolean allMenusAreClosing)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
onMenuItemSelected(MenuBuilder menu, MenuItem item)568         public boolean onMenuItemSelected(MenuBuilder menu, MenuItem item) {
569             return false;
570         }
571 
572         @Override
onMenuModeChange(MenuBuilder menu)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