Home | History | Annotate | Download | only in bars
      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 com.android.layoutlib.bridge.bars;
     18 
     19 import com.android.ide.common.rendering.api.ActionBarCallback;
     20 import com.android.ide.common.rendering.api.RenderResources;
     21 import com.android.ide.common.rendering.api.ResourceValue;
     22 import com.android.internal.R;
     23 import com.android.internal.app.ToolbarActionBar;
     24 import com.android.internal.app.WindowDecorActionBar;
     25 import com.android.internal.view.menu.MenuBuilder;
     26 import com.android.internal.widget.ActionBarAccessor;
     27 import com.android.internal.widget.ActionBarView;
     28 import com.android.internal.widget.DecorToolbar;
     29 import com.android.layoutlib.bridge.android.BridgeContext;
     30 import com.android.layoutlib.bridge.impl.ResourceHelper;
     31 
     32 import android.annotation.NonNull;
     33 import android.annotation.Nullable;
     34 import android.app.ActionBar;
     35 import android.app.ActionBar.Tab;
     36 import android.app.ActionBar.TabListener;
     37 import android.app.FragmentTransaction;
     38 import android.content.Context;
     39 import android.content.res.Resources;
     40 import android.graphics.drawable.Drawable;
     41 import android.view.MenuInflater;
     42 import android.view.View;
     43 import android.view.ViewGroup;
     44 import android.view.WindowCallback;
     45 import android.widget.ActionMenuPresenter;
     46 import android.widget.ActionMenuView;
     47 import android.widget.Toolbar;
     48 import android.widget.Toolbar_Accessor;
     49 
     50 import static com.android.SdkConstants.ANDROID_NS_NAME_PREFIX;
     51 import static com.android.resources.ResourceType.MENU;
     52 
     53 /**
     54  * A common API to access {@link ToolbarActionBar} and {@link WindowDecorActionBar}.
     55  */
     56 public abstract class FrameworkActionBarWrapper {
     57 
     58     @NonNull protected ActionBar mActionBar;
     59     @NonNull protected ActionBarCallback mCallback;
     60     @NonNull protected BridgeContext mContext;
     61 
     62     /**
     63      * Returns a wrapper around different implementations of the Action Bar to provide a common API.
     64      *
     65      * @param decorContent the top level view returned by inflating
     66      *                     ?attr/windowActionBarFullscreenDecorLayout
     67      */
     68     @NonNull
     69     public static FrameworkActionBarWrapper getActionBarWrapper(@NonNull BridgeContext context,
     70             @NonNull ActionBarCallback callback, @NonNull View decorContent) {
     71         View view = decorContent.findViewById(R.id.action_bar);
     72         if (view instanceof Toolbar) {
     73             return new ToolbarWrapper(context, callback, (Toolbar) view);
     74         } else if (view instanceof ActionBarView) {
     75             return new WindowActionBarWrapper(context, callback, decorContent,
     76                     (ActionBarView) view);
     77         } else {
     78             throw new IllegalStateException("Can't make an action bar out of " +
     79                     view.getClass().getSimpleName());
     80         }
     81     }
     82 
     83     FrameworkActionBarWrapper(@NonNull BridgeContext context, @NonNull ActionBarCallback callback,
     84             @NonNull ActionBar actionBar) {
     85         mActionBar = actionBar;
     86         mCallback = callback;
     87         mContext = context;
     88     }
     89 
     90     /** A call to setup any custom properties. */
     91     protected void setupActionBar() {
     92         // Nothing to do here.
     93     }
     94 
     95     public void setTitle(CharSequence title) {
     96         mActionBar.setTitle(title);
     97     }
     98 
     99     public void setSubTitle(CharSequence subTitle) {
    100         if (subTitle != null) {
    101             mActionBar.setSubtitle(subTitle);
    102         }
    103     }
    104 
    105     public void setHomeAsUp(boolean homeAsUp) {
    106         mActionBar.setDisplayHomeAsUpEnabled(homeAsUp);
    107     }
    108 
    109     public void setIcon(String icon) {
    110         // Nothing to do.
    111     }
    112 
    113     protected boolean isSplit() {
    114         return getDecorToolbar().isSplit();
    115     }
    116 
    117     protected boolean isOverflowPopupNeeded() {
    118         return mCallback.isOverflowPopupNeeded();
    119     }
    120 
    121     /**
    122      * Gets the menus to add to the action bar from the callback, resolves them, inflates them and
    123      * adds them to the action bar.
    124      */
    125     protected void inflateMenus() {
    126         MenuInflater inflater = new MenuInflater(getActionMenuContext());
    127         MenuBuilder menuBuilder = getMenuBuilder();
    128         for (String name : mCallback.getMenuIdNames()) {
    129             int id;
    130             if (name.startsWith(ANDROID_NS_NAME_PREFIX)) {
    131                 // Framework menu.
    132                 name = name.substring(ANDROID_NS_NAME_PREFIX.length());
    133                 id = mContext.getFrameworkResourceValue(MENU, name, -1);
    134             } else {
    135                 // Project menu.
    136                 id = mContext.getProjectResourceValue(MENU, name, -1);
    137             }
    138             if (id > -1) {
    139                 inflater.inflate(id, menuBuilder);
    140             }
    141         }
    142     }
    143 
    144     /**
    145      * The context used for the ActionBar and the menus in the ActionBarView.
    146      */
    147     @NonNull
    148     protected Context getActionMenuContext() {
    149         return mActionBar.getThemedContext();
    150     }
    151 
    152     /**
    153      * The context used to inflate the popup menu.
    154      */
    155     @NonNull
    156     abstract Context getPopupContext();
    157 
    158     /**
    159      * The Menu in which to inflate the user's menus.
    160      */
    161     @NonNull
    162     abstract MenuBuilder getMenuBuilder();
    163 
    164     @Nullable
    165     abstract ActionMenuPresenter getActionMenuPresenter();
    166 
    167     /**
    168      * Framework's wrapper over two ActionBar implementations.
    169      */
    170     @NonNull
    171     abstract DecorToolbar getDecorToolbar();
    172 
    173     abstract int getMenuPopupElevation();
    174 
    175     /**
    176      * Margin between the menu popup and the action bar.
    177      */
    178     abstract int getMenuPopupMargin();
    179 
    180     // ---- The implementations ----
    181 
    182     /**
    183      * Material theme uses {@link Toolbar} as the action bar. This wrapper provides access to
    184      * Toolbar using a common API.
    185      */
    186     private static class ToolbarWrapper extends FrameworkActionBarWrapper {
    187 
    188         @NonNull
    189         private final Toolbar mToolbar;  // This is the view.
    190 
    191         ToolbarWrapper(@NonNull BridgeContext context, @NonNull ActionBarCallback callback,
    192                 @NonNull Toolbar toolbar) {
    193             super(context, callback, new ToolbarActionBar(toolbar, "", new WindowCallback()));
    194             mToolbar = toolbar;
    195         }
    196 
    197         @Override
    198         protected void inflateMenus() {
    199             super.inflateMenus();
    200             // Inflating the menus isn't enough. ActionMenuPresenter needs to be initialized too.
    201             MenuBuilder menu = getMenuBuilder();
    202             DecorToolbar decorToolbar = getDecorToolbar();
    203             // Setting a menu different from the above initializes the presenter.
    204             decorToolbar.setMenu(new MenuBuilder(getActionMenuContext()), null);
    205             // ActionMenuView needs to be recreated to be able to set the menu back.
    206             ActionMenuPresenter presenter = getActionMenuPresenter();
    207             if (presenter != null) {
    208                 presenter.setMenuView(new ActionMenuView(getPopupContext()));
    209             }
    210             decorToolbar.setMenu(menu, null);
    211         }
    212 
    213         @NonNull
    214         @Override
    215         Context getPopupContext() {
    216             return Toolbar_Accessor.getPopupContext(mToolbar);
    217         }
    218 
    219         @NonNull
    220         @Override
    221         MenuBuilder getMenuBuilder() {
    222             return (MenuBuilder) mToolbar.getMenu();
    223         }
    224 
    225         @Nullable
    226         @Override
    227         ActionMenuPresenter getActionMenuPresenter() {
    228             return Toolbar_Accessor.getActionMenuPresenter(mToolbar);
    229         }
    230 
    231         @NonNull
    232         @Override
    233         DecorToolbar getDecorToolbar() {
    234             return mToolbar.getWrapper();
    235         }
    236 
    237         @Override
    238         int getMenuPopupElevation() {
    239             return 10;
    240         }
    241 
    242         @Override
    243         int getMenuPopupMargin() {
    244             return 0;
    245         }
    246     }
    247 
    248     /**
    249      * Holo theme uses {@link WindowDecorActionBar} as the action bar. This wrapper provides
    250      * access to it using a common API.
    251      */
    252     private static class WindowActionBarWrapper extends FrameworkActionBarWrapper {
    253 
    254         @NonNull private final WindowDecorActionBar mActionBar;
    255         @NonNull private final ActionBarView mActionBarView;
    256         @NonNull private final View mDecorContentRoot;
    257         private MenuBuilder mMenuBuilder;
    258 
    259         public WindowActionBarWrapper(@NonNull BridgeContext context,
    260                 @NonNull ActionBarCallback callback, @NonNull View decorContentRoot,
    261                 @NonNull ActionBarView actionBarView) {
    262             super(context, callback, new WindowDecorActionBar(decorContentRoot));
    263             mActionBarView = actionBarView;
    264             mActionBar = (WindowDecorActionBar) super.mActionBar;
    265             mDecorContentRoot = decorContentRoot;
    266         }
    267 
    268         @Override
    269         protected void setupActionBar() {
    270 
    271             // Set the navigation mode.
    272             int navMode = mCallback.getNavigationMode();
    273             mActionBar.setNavigationMode(navMode);
    274             //noinspection deprecation
    275             if (navMode == ActionBar.NAVIGATION_MODE_TABS) {
    276                 setupTabs(3);
    277             }
    278 
    279             // Set action bar to be split, if needed.
    280             ViewGroup splitView = (ViewGroup) mDecorContentRoot.findViewById(R.id.split_action_bar);
    281             if (splitView != null) {
    282                 mActionBarView.setSplitView(splitView);
    283                 Resources res = mContext.getResources();
    284                 boolean split = res.getBoolean(R.bool.split_action_bar_is_narrow)
    285                         && mCallback.getSplitActionBarWhenNarrow();
    286                 mActionBarView.setSplitToolbar(split);
    287             }
    288         }
    289 
    290         @Override
    291         public void setIcon(String icon) {
    292             // Set the icon only if the action bar doesn't specify an icon.
    293             if (!mActionBar.hasIcon() && icon != null) {
    294                 Drawable iconDrawable = getDrawable(icon, false);
    295                 if (iconDrawable != null) {
    296                     mActionBar.setIcon(iconDrawable);
    297                 }
    298             }
    299         }
    300 
    301         @Override
    302         protected void inflateMenus() {
    303             super.inflateMenus();
    304             // The super implementation doesn't set the menu on the view. Set it here.
    305             mActionBarView.setMenu(getMenuBuilder(), null);
    306         }
    307 
    308         @NonNull
    309         @Override
    310         Context getPopupContext() {
    311             return getActionMenuContext();
    312         }
    313 
    314         @NonNull
    315         @Override
    316         MenuBuilder getMenuBuilder() {
    317             if (mMenuBuilder == null) {
    318                 mMenuBuilder = new MenuBuilder(getActionMenuContext());
    319             }
    320             return mMenuBuilder;
    321         }
    322 
    323         @Nullable
    324         @Override
    325         ActionMenuPresenter getActionMenuPresenter() {
    326             return ActionBarAccessor.getActionMenuPresenter(mActionBarView);
    327         }
    328 
    329         @NonNull
    330         @Override
    331         ActionBarView getDecorToolbar() {
    332             return mActionBarView;
    333         }
    334 
    335         @Override
    336         int getMenuPopupElevation() {
    337             return 0;
    338         }
    339 
    340         @Override
    341         int getMenuPopupMargin() {
    342             return -FrameworkActionBar.getPixelValue("10dp", mContext.getMetrics());
    343         }
    344 
    345         // TODO: Use an adapter, like List View to set up tabs.
    346         @SuppressWarnings("deprecation")  // For Tab
    347         private void setupTabs(int num) {
    348             for (int i = 1; i <= num; i++) {
    349                 Tab tab = mActionBar.newTab().setText("Tab" + i).setTabListener(new TabListener() {
    350                     @Override
    351                     public void onTabUnselected(Tab t, FragmentTransaction ft) {
    352                         // pass
    353                     }
    354                     @Override
    355                     public void onTabSelected(Tab t, FragmentTransaction ft) {
    356                         // pass
    357                     }
    358                     @Override
    359                     public void onTabReselected(Tab t, FragmentTransaction ft) {
    360                         // pass
    361                     }
    362                 });
    363                 mActionBar.addTab(tab);
    364             }
    365         }
    366 
    367         @Nullable
    368         private Drawable getDrawable(@NonNull String name, boolean isFramework) {
    369             RenderResources res = mContext.getRenderResources();
    370             ResourceValue value = res.findResValue(name, isFramework);
    371             value = res.resolveResValue(value);
    372             if (value != null) {
    373                 return ResourceHelper.getDrawable(value, mContext);
    374             }
    375             return null;
    376         }
    377 
    378     }
    379 }
    380