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