Home | History | Annotate | Download | only in widget
      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.widget;
     19 
     20 import android.animation.Animator;
     21 import android.animation.AnimatorListenerAdapter;
     22 import android.app.ActionBar;
     23 import android.content.Context;
     24 import android.content.res.TypedArray;
     25 import android.graphics.drawable.Drawable;
     26 import android.os.Parcelable;
     27 import android.text.TextUtils;
     28 import android.util.Log;
     29 import android.util.SparseArray;
     30 import android.util.TypedValue;
     31 import android.view.ContextThemeWrapper;
     32 import android.view.Gravity;
     33 import android.view.LayoutInflater;
     34 import android.view.Menu;
     35 import android.view.View;
     36 import android.view.ViewGroup;
     37 import android.view.Window;
     38 import android.widget.ActionMenuPresenter;
     39 import android.widget.AdapterView;
     40 import android.widget.Spinner;
     41 import android.widget.SpinnerAdapter;
     42 import android.widget.Toolbar;
     43 import com.android.internal.R;
     44 import com.android.internal.view.menu.ActionMenuItem;
     45 import com.android.internal.view.menu.MenuBuilder;
     46 import com.android.internal.view.menu.MenuPresenter;
     47 
     48 /**
     49  * Internal class used to interact with the Toolbar widget without
     50  * exposing interface methods to the public API.
     51  *
     52  * <p>ToolbarWidgetWrapper manages the differences between Toolbar and ActionBarView
     53  * so that either variant acting as a
     54  * {@link com.android.internal.app.WindowDecorActionBar WindowDecorActionBar} can behave
     55  * in the same way.</p>
     56  *
     57  * @hide
     58  */
     59 public class ToolbarWidgetWrapper implements DecorToolbar {
     60     private static final String TAG = "ToolbarWidgetWrapper";
     61 
     62     private static final int AFFECTS_LOGO_MASK =
     63             ActionBar.DISPLAY_SHOW_HOME | ActionBar.DISPLAY_USE_LOGO;
     64 
     65     private Toolbar mToolbar;
     66 
     67     private int mDisplayOpts;
     68     private View mTabView;
     69     private Spinner mSpinner;
     70     private View mCustomView;
     71 
     72     private Drawable mIcon;
     73     private Drawable mLogo;
     74     private Drawable mNavIcon;
     75 
     76     private boolean mTitleSet;
     77     private CharSequence mTitle;
     78     private CharSequence mSubtitle;
     79     private CharSequence mHomeDescription;
     80 
     81     private Window.Callback mWindowCallback;
     82     private boolean mMenuPrepared;
     83     private ActionMenuPresenter mActionMenuPresenter;
     84 
     85     private int mNavigationMode = ActionBar.NAVIGATION_MODE_STANDARD;
     86     private int mDefaultNavigationContentDescription = 0;
     87     private Drawable mDefaultNavigationIcon;
     88 
     89     public ToolbarWidgetWrapper(Toolbar toolbar, boolean style) {
     90         this(toolbar, style, R.string.action_bar_up_description);
     91     }
     92 
     93     public ToolbarWidgetWrapper(Toolbar toolbar, boolean style,
     94             int defaultNavigationContentDescription) {
     95         mToolbar = toolbar;
     96 
     97         mTitle = toolbar.getTitle();
     98         mSubtitle = toolbar.getSubtitle();
     99         mTitleSet = mTitle != null;
    100         final TypedArray a = toolbar.getContext().obtainStyledAttributes(null,
    101                 R.styleable.ActionBar, R.attr.actionBarStyle, 0);
    102         mDefaultNavigationIcon = a.getDrawable(R.styleable.ActionBar_homeAsUpIndicator);
    103         if (style) {
    104             final CharSequence title = a.getText(R.styleable.ActionBar_title);
    105             if (!TextUtils.isEmpty(title)) {
    106                 setTitle(title);
    107             }
    108 
    109             final CharSequence subtitle = a.getText(R.styleable.ActionBar_subtitle);
    110             if (!TextUtils.isEmpty(subtitle)) {
    111                 setSubtitle(subtitle);
    112             }
    113 
    114             final Drawable logo = a.getDrawable(R.styleable.ActionBar_logo);
    115             if (logo != null) {
    116                 setLogo(logo);
    117             }
    118 
    119             final Drawable icon = a.getDrawable(R.styleable.ActionBar_icon);
    120             if (icon != null) {
    121                 setIcon(icon);
    122             }
    123             if (mDefaultNavigationIcon != null) {
    124                 setNavigationIcon(mDefaultNavigationIcon);
    125             }
    126             setDisplayOptions(a.getInt(R.styleable.ActionBar_displayOptions, 0));
    127 
    128             final int customNavId = a.getResourceId(
    129                     R.styleable.ActionBar_customNavigationLayout, 0);
    130             if (customNavId != 0) {
    131                 setCustomView(LayoutInflater.from(mToolbar.getContext()).inflate(customNavId,
    132                         mToolbar, false));
    133                 setDisplayOptions(mDisplayOpts | ActionBar.DISPLAY_SHOW_CUSTOM);
    134             }
    135 
    136             final int height = a.getLayoutDimension(R.styleable.ActionBar_height, 0);
    137             if (height > 0) {
    138                 final ViewGroup.LayoutParams lp = mToolbar.getLayoutParams();
    139                 lp.height = height;
    140                 mToolbar.setLayoutParams(lp);
    141             }
    142 
    143             final int contentInsetStart = a.getDimensionPixelOffset(
    144                     R.styleable.ActionBar_contentInsetStart, -1);
    145             final int contentInsetEnd = a.getDimensionPixelOffset(
    146                     R.styleable.ActionBar_contentInsetEnd, -1);
    147             if (contentInsetStart >= 0 || contentInsetEnd >= 0) {
    148                 mToolbar.setContentInsetsRelative(Math.max(contentInsetStart, 0),
    149                         Math.max(contentInsetEnd, 0));
    150             }
    151 
    152             final int titleTextStyle = a.getResourceId(R.styleable.ActionBar_titleTextStyle, 0);
    153             if (titleTextStyle != 0) {
    154                 mToolbar.setTitleTextAppearance(mToolbar.getContext(), titleTextStyle);
    155             }
    156 
    157             final int subtitleTextStyle = a.getResourceId(
    158                     R.styleable.ActionBar_subtitleTextStyle, 0);
    159             if (subtitleTextStyle != 0) {
    160                 mToolbar.setSubtitleTextAppearance(mToolbar.getContext(), subtitleTextStyle);
    161             }
    162 
    163             final int popupTheme = a.getResourceId(R.styleable.ActionBar_popupTheme, 0);
    164             if (popupTheme != 0) {
    165                 mToolbar.setPopupTheme(popupTheme);
    166             }
    167         } else {
    168             mDisplayOpts = detectDisplayOptions();
    169         }
    170         a.recycle();
    171 
    172         setDefaultNavigationContentDescription(defaultNavigationContentDescription);
    173         mHomeDescription = mToolbar.getNavigationContentDescription();
    174 
    175         mToolbar.setNavigationOnClickListener(new View.OnClickListener() {
    176             final ActionMenuItem mNavItem = new ActionMenuItem(mToolbar.getContext(),
    177                     0, android.R.id.home, 0, 0, mTitle);
    178             @Override
    179             public void onClick(View v) {
    180                 if (mWindowCallback != null && mMenuPrepared) {
    181                     mWindowCallback.onMenuItemSelected(Window.FEATURE_OPTIONS_PANEL, mNavItem);
    182                 }
    183             }
    184         });
    185     }
    186 
    187     @Override
    188     public void setDefaultNavigationContentDescription(int defaultNavigationContentDescription) {
    189         if (defaultNavigationContentDescription == mDefaultNavigationContentDescription) {
    190             return;
    191         }
    192         mDefaultNavigationContentDescription = defaultNavigationContentDescription;
    193         if (TextUtils.isEmpty(mToolbar.getNavigationContentDescription())) {
    194             setNavigationContentDescription(mDefaultNavigationContentDescription);
    195         }
    196     }
    197 
    198     private int detectDisplayOptions() {
    199         int opts = ActionBar.DISPLAY_SHOW_TITLE | ActionBar.DISPLAY_SHOW_HOME |
    200                 ActionBar.DISPLAY_USE_LOGO;
    201         if (mToolbar.getNavigationIcon() != null) {
    202             opts |= ActionBar.DISPLAY_HOME_AS_UP;
    203             mDefaultNavigationIcon = mToolbar.getNavigationIcon();
    204         }
    205         return opts;
    206     }
    207 
    208     @Override
    209     public ViewGroup getViewGroup() {
    210         return mToolbar;
    211     }
    212 
    213     @Override
    214     public Context getContext() {
    215         return mToolbar.getContext();
    216     }
    217 
    218     @Override
    219     public boolean isSplit() {
    220         return false;
    221     }
    222 
    223     @Override
    224     public boolean hasExpandedActionView() {
    225         return mToolbar.hasExpandedActionView();
    226     }
    227 
    228     @Override
    229     public void collapseActionView() {
    230         mToolbar.collapseActionView();
    231     }
    232 
    233     @Override
    234     public void setWindowCallback(Window.Callback cb) {
    235         mWindowCallback = cb;
    236     }
    237 
    238     @Override
    239     public void setWindowTitle(CharSequence title) {
    240         // "Real" title always trumps window title.
    241         if (!mTitleSet) {
    242             setTitleInt(title);
    243         }
    244     }
    245 
    246     @Override
    247     public CharSequence getTitle() {
    248         return mToolbar.getTitle();
    249     }
    250 
    251     @Override
    252     public void setTitle(CharSequence title) {
    253         mTitleSet = true;
    254         setTitleInt(title);
    255     }
    256 
    257     private void setTitleInt(CharSequence title) {
    258         mTitle = title;
    259         if ((mDisplayOpts & ActionBar.DISPLAY_SHOW_TITLE) != 0) {
    260             mToolbar.setTitle(title);
    261         }
    262     }
    263 
    264     @Override
    265     public CharSequence getSubtitle() {
    266         return mToolbar.getSubtitle();
    267     }
    268 
    269     @Override
    270     public void setSubtitle(CharSequence subtitle) {
    271         mSubtitle = subtitle;
    272         if ((mDisplayOpts & ActionBar.DISPLAY_SHOW_TITLE) != 0) {
    273             mToolbar.setSubtitle(subtitle);
    274         }
    275     }
    276 
    277     @Override
    278     public void initProgress() {
    279         Log.i(TAG, "Progress display unsupported");
    280     }
    281 
    282     @Override
    283     public void initIndeterminateProgress() {
    284         Log.i(TAG, "Progress display unsupported");
    285     }
    286 
    287     @Override
    288     public boolean canSplit() {
    289         return false;
    290     }
    291 
    292     @Override
    293     public void setSplitView(ViewGroup splitView) {
    294     }
    295 
    296     @Override
    297     public void setSplitToolbar(boolean split) {
    298         if (split) {
    299             throw new UnsupportedOperationException("Cannot split an android.widget.Toolbar");
    300         }
    301     }
    302 
    303     @Override
    304     public void setSplitWhenNarrow(boolean splitWhenNarrow) {
    305         // Ignore.
    306     }
    307 
    308     @Override
    309     public boolean hasIcon() {
    310         return mIcon != null;
    311     }
    312 
    313     @Override
    314     public boolean hasLogo() {
    315         return mLogo != null;
    316     }
    317 
    318     @Override
    319     public void setIcon(int resId) {
    320         setIcon(resId != 0 ? getContext().getDrawable(resId) : null);
    321     }
    322 
    323     @Override
    324     public void setIcon(Drawable d) {
    325         mIcon = d;
    326         updateToolbarLogo();
    327     }
    328 
    329     @Override
    330     public void setLogo(int resId) {
    331         setLogo(resId != 0 ? getContext().getDrawable(resId) : null);
    332     }
    333 
    334     @Override
    335     public void setLogo(Drawable d) {
    336         mLogo = d;
    337         updateToolbarLogo();
    338     }
    339 
    340     private void updateToolbarLogo() {
    341         Drawable logo = null;
    342         if ((mDisplayOpts & ActionBar.DISPLAY_SHOW_HOME) != 0) {
    343             if ((mDisplayOpts & ActionBar.DISPLAY_USE_LOGO) != 0) {
    344                 logo = mLogo != null ? mLogo : mIcon;
    345             } else {
    346                 logo = mIcon;
    347             }
    348         }
    349         mToolbar.setLogo(logo);
    350     }
    351 
    352     @Override
    353     public boolean canShowOverflowMenu() {
    354         return mToolbar.canShowOverflowMenu();
    355     }
    356 
    357     @Override
    358     public boolean isOverflowMenuShowing() {
    359         return mToolbar.isOverflowMenuShowing();
    360     }
    361 
    362     @Override
    363     public boolean isOverflowMenuShowPending() {
    364         return mToolbar.isOverflowMenuShowPending();
    365     }
    366 
    367     @Override
    368     public boolean showOverflowMenu() {
    369         return mToolbar.showOverflowMenu();
    370     }
    371 
    372     @Override
    373     public boolean hideOverflowMenu() {
    374         return mToolbar.hideOverflowMenu();
    375     }
    376 
    377     @Override
    378     public void setMenuPrepared() {
    379         mMenuPrepared = true;
    380     }
    381 
    382     @Override
    383     public void setMenu(Menu menu, MenuPresenter.Callback cb) {
    384         if (mActionMenuPresenter == null) {
    385             mActionMenuPresenter = new ActionMenuPresenter(mToolbar.getContext());
    386             mActionMenuPresenter.setId(com.android.internal.R.id.action_menu_presenter);
    387         }
    388         mActionMenuPresenter.setCallback(cb);
    389         mToolbar.setMenu((MenuBuilder) menu, mActionMenuPresenter);
    390     }
    391 
    392     @Override
    393     public void dismissPopupMenus() {
    394         mToolbar.dismissPopupMenus();
    395     }
    396 
    397     @Override
    398     public int getDisplayOptions() {
    399         return mDisplayOpts;
    400     }
    401 
    402     @Override
    403     public void setDisplayOptions(int newOpts) {
    404         final int oldOpts = mDisplayOpts;
    405         final int changed = oldOpts ^ newOpts;
    406         mDisplayOpts = newOpts;
    407         if (changed != 0) {
    408             if ((changed & ActionBar.DISPLAY_HOME_AS_UP) != 0) {
    409                 if ((newOpts & ActionBar.DISPLAY_HOME_AS_UP) != 0) {
    410                     updateHomeAccessibility();
    411                 }
    412                 updateNavigationIcon();
    413             }
    414 
    415             if ((changed & AFFECTS_LOGO_MASK) != 0) {
    416                 updateToolbarLogo();
    417             }
    418 
    419             if ((changed & ActionBar.DISPLAY_SHOW_TITLE) != 0) {
    420                 if ((newOpts & ActionBar.DISPLAY_SHOW_TITLE) != 0) {
    421                     mToolbar.setTitle(mTitle);
    422                     mToolbar.setSubtitle(mSubtitle);
    423                 } else {
    424                     mToolbar.setTitle(null);
    425                     mToolbar.setSubtitle(null);
    426                 }
    427             }
    428 
    429             if ((changed & ActionBar.DISPLAY_SHOW_CUSTOM) != 0 && mCustomView != null) {
    430                 if ((newOpts & ActionBar.DISPLAY_SHOW_CUSTOM) != 0) {
    431                     mToolbar.addView(mCustomView);
    432                 } else {
    433                     mToolbar.removeView(mCustomView);
    434                 }
    435             }
    436         }
    437     }
    438 
    439     @Override
    440     public void setEmbeddedTabView(ScrollingTabContainerView tabView) {
    441         if (mTabView != null && mTabView.getParent() == mToolbar) {
    442             mToolbar.removeView(mTabView);
    443         }
    444         mTabView = tabView;
    445         if (tabView != null && mNavigationMode == ActionBar.NAVIGATION_MODE_TABS) {
    446             mToolbar.addView(mTabView, 0);
    447             Toolbar.LayoutParams lp = (Toolbar.LayoutParams) mTabView.getLayoutParams();
    448             lp.width = ViewGroup.LayoutParams.WRAP_CONTENT;
    449             lp.height = ViewGroup.LayoutParams.WRAP_CONTENT;
    450             lp.gravity = Gravity.START | Gravity.BOTTOM;
    451             tabView.setAllowCollapse(true);
    452         }
    453     }
    454 
    455     @Override
    456     public boolean hasEmbeddedTabs() {
    457         return mTabView != null;
    458     }
    459 
    460     @Override
    461     public boolean isTitleTruncated() {
    462         return mToolbar.isTitleTruncated();
    463     }
    464 
    465     @Override
    466     public void setCollapsible(boolean collapsible) {
    467         mToolbar.setCollapsible(collapsible);
    468     }
    469 
    470     @Override
    471     public void setHomeButtonEnabled(boolean enable) {
    472         // Ignore
    473     }
    474 
    475     @Override
    476     public int getNavigationMode() {
    477         return mNavigationMode;
    478     }
    479 
    480     @Override
    481     public void setNavigationMode(int mode) {
    482         final int oldMode = mNavigationMode;
    483         if (mode != oldMode) {
    484             switch (oldMode) {
    485                 case ActionBar.NAVIGATION_MODE_LIST:
    486                     if (mSpinner != null && mSpinner.getParent() == mToolbar) {
    487                         mToolbar.removeView(mSpinner);
    488                     }
    489                     break;
    490                 case ActionBar.NAVIGATION_MODE_TABS:
    491                     if (mTabView != null && mTabView.getParent() == mToolbar) {
    492                         mToolbar.removeView(mTabView);
    493                     }
    494                     break;
    495             }
    496 
    497             mNavigationMode = mode;
    498 
    499             switch (mode) {
    500                 case ActionBar.NAVIGATION_MODE_STANDARD:
    501                     break;
    502                 case ActionBar.NAVIGATION_MODE_LIST:
    503                     ensureSpinner();
    504                     mToolbar.addView(mSpinner, 0);
    505                     break;
    506                 case ActionBar.NAVIGATION_MODE_TABS:
    507                     if (mTabView != null) {
    508                         mToolbar.addView(mTabView, 0);
    509                         Toolbar.LayoutParams lp = (Toolbar.LayoutParams) mTabView.getLayoutParams();
    510                         lp.width = ViewGroup.LayoutParams.WRAP_CONTENT;
    511                         lp.height = ViewGroup.LayoutParams.WRAP_CONTENT;
    512                         lp.gravity = Gravity.START | Gravity.BOTTOM;
    513                     }
    514                     break;
    515                 default:
    516                     throw new IllegalArgumentException("Invalid navigation mode " + mode);
    517             }
    518         }
    519     }
    520 
    521     private void ensureSpinner() {
    522         if (mSpinner == null) {
    523             mSpinner = new Spinner(getContext(), null, R.attr.actionDropDownStyle);
    524             Toolbar.LayoutParams lp = new Toolbar.LayoutParams(ViewGroup.LayoutParams.WRAP_CONTENT,
    525                     ViewGroup.LayoutParams.WRAP_CONTENT, Gravity.START | Gravity.CENTER_VERTICAL);
    526             mSpinner.setLayoutParams(lp);
    527         }
    528     }
    529 
    530     @Override
    531     public void setDropdownParams(SpinnerAdapter adapter,
    532             AdapterView.OnItemSelectedListener listener) {
    533         ensureSpinner();
    534         mSpinner.setAdapter(adapter);
    535         mSpinner.setOnItemSelectedListener(listener);
    536     }
    537 
    538     @Override
    539     public void setDropdownSelectedPosition(int position) {
    540         if (mSpinner == null) {
    541             throw new IllegalStateException(
    542                     "Can't set dropdown selected position without an adapter");
    543         }
    544         mSpinner.setSelection(position);
    545     }
    546 
    547     @Override
    548     public int getDropdownSelectedPosition() {
    549         return mSpinner != null ? mSpinner.getSelectedItemPosition() : 0;
    550     }
    551 
    552     @Override
    553     public int getDropdownItemCount() {
    554         return mSpinner != null ? mSpinner.getCount() : 0;
    555     }
    556 
    557     @Override
    558     public void setCustomView(View view) {
    559         if (mCustomView != null && (mDisplayOpts & ActionBar.DISPLAY_SHOW_CUSTOM) != 0) {
    560             mToolbar.removeView(mCustomView);
    561         }
    562         mCustomView = view;
    563         if (view != null && (mDisplayOpts & ActionBar.DISPLAY_SHOW_CUSTOM) != 0) {
    564             mToolbar.addView(mCustomView);
    565         }
    566     }
    567 
    568     @Override
    569     public View getCustomView() {
    570         return mCustomView;
    571     }
    572 
    573     @Override
    574     public void animateToVisibility(int visibility) {
    575         if (visibility == View.GONE) {
    576             mToolbar.animate().alpha(0)
    577                     .setListener(new AnimatorListenerAdapter() {
    578                         private boolean mCanceled = false;
    579                         @Override
    580                         public void onAnimationEnd(Animator animation) {
    581                             if (!mCanceled) {
    582                                 mToolbar.setVisibility(View.GONE);
    583                             }
    584                         }
    585 
    586                         @Override
    587                         public void onAnimationCancel(Animator animation) {
    588                             mCanceled = true;
    589                         }
    590                     });
    591         } else if (visibility == View.VISIBLE) {
    592             mToolbar.animate().alpha(1)
    593                     .setListener(new AnimatorListenerAdapter() {
    594                         @Override
    595                         public void onAnimationStart(Animator animation) {
    596                             mToolbar.setVisibility(View.VISIBLE);
    597                         }
    598                     });
    599         }
    600     }
    601 
    602     @Override
    603     public void setNavigationIcon(Drawable icon) {
    604         mNavIcon = icon;
    605         updateNavigationIcon();
    606     }
    607 
    608     @Override
    609     public void setNavigationIcon(int resId) {
    610         setNavigationIcon(resId != 0 ? mToolbar.getContext().getDrawable(resId) : null);
    611     }
    612 
    613     @Override
    614     public void setDefaultNavigationIcon(Drawable defaultNavigationIcon) {
    615         if (mDefaultNavigationIcon != defaultNavigationIcon) {
    616             mDefaultNavigationIcon = defaultNavigationIcon;
    617             updateNavigationIcon();
    618         }
    619     }
    620 
    621     private void updateNavigationIcon() {
    622         if ((mDisplayOpts & ActionBar.DISPLAY_HOME_AS_UP) != 0) {
    623             mToolbar.setNavigationIcon(mNavIcon != null ? mNavIcon : mDefaultNavigationIcon);
    624         } else {
    625             mToolbar.setNavigationIcon(null);
    626         }
    627     }
    628 
    629     @Override
    630     public void setNavigationContentDescription(CharSequence description) {
    631         mHomeDescription = description;
    632         updateHomeAccessibility();
    633     }
    634 
    635     @Override
    636     public void setNavigationContentDescription(int resId) {
    637         setNavigationContentDescription(resId == 0 ? null : getContext().getString(resId));
    638     }
    639 
    640     private void updateHomeAccessibility() {
    641         if ((mDisplayOpts & ActionBar.DISPLAY_HOME_AS_UP) != 0) {
    642             if (TextUtils.isEmpty(mHomeDescription)) {
    643                 mToolbar.setNavigationContentDescription(mDefaultNavigationContentDescription);
    644             } else {
    645                 mToolbar.setNavigationContentDescription(mHomeDescription);
    646             }
    647         }
    648     }
    649 
    650     @Override
    651     public void saveHierarchyState(SparseArray<Parcelable> toolbarStates) {
    652         mToolbar.saveHierarchyState(toolbarStates);
    653     }
    654 
    655     @Override
    656     public void restoreHierarchyState(SparseArray<Parcelable> toolbarStates) {
    657         mToolbar.restoreHierarchyState(toolbarStates);
    658     }
    659 
    660 }
    661