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.RenderResources;
     20 import com.android.ide.common.rendering.api.ResourceValue;
     21 import com.android.ide.common.rendering.api.SessionParams;
     22 import com.android.internal.R;
     23 import com.android.internal.view.menu.MenuBuilder;
     24 import com.android.internal.view.menu.MenuItemImpl;
     25 import com.android.layoutlib.bridge.android.BridgeContext;
     26 import com.android.layoutlib.bridge.impl.ResourceHelper;
     27 
     28 import android.annotation.NonNull;
     29 import android.annotation.Nullable;
     30 import android.content.Context;
     31 import android.content.res.TypedArray;
     32 import android.util.DisplayMetrics;
     33 import android.util.TypedValue;
     34 import android.view.InflateException;
     35 import android.view.View;
     36 import android.view.View.MeasureSpec;
     37 import android.view.ViewGroup;
     38 import android.view.ViewGroup.LayoutParams;
     39 import android.widget.ActionMenuPresenter;
     40 import android.widget.FrameLayout;
     41 import android.widget.ListAdapter;
     42 import android.widget.ListView;
     43 import android.widget.RelativeLayout;
     44 
     45 import java.util.ArrayList;
     46 
     47 /**
     48  * Creates the ActionBar as done by the framework.
     49  */
     50 public class FrameworkActionBar extends BridgeActionBar {
     51 
     52     private static final String LAYOUT_ATTR_NAME = "windowActionBarFullscreenDecorLayout";
     53 
     54     // The Action Bar
     55     @NonNull private FrameworkActionBarWrapper mActionBar;
     56 
     57     // A fake parent for measuring views.
     58     @Nullable private ViewGroup mMeasureParent;
     59 
     60     /**
     61      * Inflate the action bar and attach it to {@code parentView}
     62      */
     63     public FrameworkActionBar(@NonNull BridgeContext context, @NonNull SessionParams params) {
     64         super(context, params);
     65 
     66         View decorContent = getDecorContent();
     67 
     68         mActionBar = FrameworkActionBarWrapper.getActionBarWrapper(context, getCallBack(),
     69                 decorContent);
     70 
     71         FrameLayout contentRoot = (FrameLayout) decorContent.findViewById(android.R.id.content);
     72 
     73         // If something went wrong and we were not able to initialize the content root,
     74         // just add a frame layout inside this and return.
     75         if (contentRoot == null) {
     76             contentRoot = new FrameLayout(context);
     77             setMatchParent(contentRoot);
     78             if (mEnclosingLayout != null) {
     79                 mEnclosingLayout.addView(contentRoot);
     80             }
     81             setContentRoot(contentRoot);
     82         } else {
     83             setContentRoot(contentRoot);
     84             setupActionBar();
     85             mActionBar.inflateMenus();
     86         }
     87     }
     88 
     89     @Override
     90     protected ResourceValue getLayoutResource(BridgeContext context) {
     91         ResourceValue layoutName =
     92                 context.getRenderResources().findItemInTheme(LAYOUT_ATTR_NAME, true);
     93         if (layoutName != null) {
     94             // We may need to resolve the reference obtained.
     95             layoutName = context.getRenderResources().findResValue(layoutName.getValue(),
     96                     layoutName.isFramework());
     97         }
     98         if (layoutName == null) {
     99              throw new InflateException("Unable to find action bar layout (" + LAYOUT_ATTR_NAME
    100                     + ") in the current theme.");
    101         }
    102         return layoutName;
    103     }
    104 
    105     @Override
    106     protected void setupActionBar() {
    107         super.setupActionBar();
    108         mActionBar.setupActionBar();
    109     }
    110 
    111     @Override
    112     protected void setHomeAsUp(boolean homeAsUp) {
    113         mActionBar.setHomeAsUp(homeAsUp);
    114     }
    115 
    116     @Override
    117     protected void setTitle(CharSequence title) {
    118         mActionBar.setTitle(title);
    119     }
    120 
    121     @Override
    122     protected void setSubtitle(CharSequence subtitle) {
    123         mActionBar.setSubTitle(subtitle);
    124     }
    125 
    126     @Override
    127     protected void setIcon(String icon) {
    128         mActionBar.setIcon(icon);
    129     }
    130 
    131     /**
    132      * Creates a Popup and adds it to the content frame. It also adds another {@link FrameLayout} to
    133      * the content frame which shall serve as the new content root.
    134      */
    135     @Override
    136     public void createMenuPopup() {
    137         if (!isOverflowPopupNeeded()) {
    138             return;
    139         }
    140 
    141         DisplayMetrics metrics = mBridgeContext.getMetrics();
    142         MenuBuilder menu = mActionBar.getMenuBuilder();
    143         OverflowMenuAdapter adapter = new OverflowMenuAdapter(menu, mActionBar.getPopupContext());
    144 
    145         ListView listView = new ListView(mActionBar.getPopupContext(), null,
    146                 R.attr.dropDownListViewStyle);
    147         RelativeLayout.LayoutParams layoutParams = new RelativeLayout.LayoutParams(
    148                 measureContentWidth(adapter), LayoutParams.WRAP_CONTENT);
    149         layoutParams.addRule(RelativeLayout.ALIGN_PARENT_END);
    150         if (mActionBar.isSplit()) {
    151             layoutParams.addRule(RelativeLayout.ALIGN_PARENT_BOTTOM);
    152             layoutParams.bottomMargin = getActionBarHeight() + mActionBar.getMenuPopupMargin();
    153         } else {
    154             layoutParams.addRule(RelativeLayout.ALIGN_PARENT_TOP);
    155             layoutParams.topMargin = getActionBarHeight() + mActionBar.getMenuPopupMargin();
    156         }
    157         layoutParams.setMarginEnd(getPixelValue("5dp", metrics));
    158         listView.setLayoutParams(layoutParams);
    159         listView.setAdapter(adapter);
    160         final TypedArray a = mActionBar.getPopupContext().obtainStyledAttributes(null,
    161                 R.styleable.PopupWindow, R.attr.popupMenuStyle, 0);
    162         listView.setBackground(a.getDrawable(R.styleable.PopupWindow_popupBackground));
    163         listView.setDivider(a.getDrawable(R.attr.actionBarDivider));
    164         a.recycle();
    165         listView.setElevation(mActionBar.getMenuPopupElevation());
    166         assert mEnclosingLayout != null : "Unable to find view to attach ActionMenuPopup.";
    167         mEnclosingLayout.addView(listView);
    168     }
    169 
    170     private boolean isOverflowPopupNeeded() {
    171         boolean needed = mActionBar.isOverflowPopupNeeded();
    172         if (!needed) {
    173             return false;
    174         }
    175         // Copied from android.widget.ActionMenuPresenter.updateMenuView()
    176         ArrayList<MenuItemImpl> menus = mActionBar.getMenuBuilder().getNonActionItems();
    177         ActionMenuPresenter presenter = mActionBar.getActionMenuPresenter();
    178         if (presenter == null) {
    179             assert false : "Failed to create a Presenter for Action Bar Menus.";
    180             return false;
    181         }
    182         if (presenter.isOverflowReserved() &&
    183                 menus != null) {
    184             final int count = menus.size();
    185             if (count == 1) {
    186                 needed = !menus.get(0).isActionViewExpanded();
    187             } else {
    188                 needed = count > 0;
    189             }
    190         }
    191         return needed;
    192     }
    193 
    194     // Copied from com.android.internal.view.menu.MenuPopHelper.measureContentWidth()
    195     private int measureContentWidth(@NonNull ListAdapter adapter) {
    196         // Menus don't tend to be long, so this is more sane than it looks.
    197         int maxWidth = 0;
    198         View itemView = null;
    199         int itemType = 0;
    200 
    201         Context context = mActionBar.getPopupContext();
    202         final int widthMeasureSpec = MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED);
    203         final int heightMeasureSpec = MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED);
    204         final int count = adapter.getCount();
    205         for (int i = 0; i < count; i++) {
    206             final int positionType = adapter.getItemViewType(i);
    207             if (positionType != itemType) {
    208                 itemType = positionType;
    209                 itemView = null;
    210             }
    211 
    212             if (mMeasureParent == null) {
    213                 mMeasureParent = new FrameLayout(context);
    214             }
    215 
    216             itemView = adapter.getView(i, itemView, mMeasureParent);
    217             itemView.measure(widthMeasureSpec, heightMeasureSpec);
    218 
    219             final int itemWidth = itemView.getMeasuredWidth();
    220             int popupMaxWidth = Math.max(mBridgeContext.getMetrics().widthPixels / 2,
    221                     context.getResources().getDimensionPixelSize(R.dimen.config_prefDialogWidth));
    222             if (itemWidth >= popupMaxWidth) {
    223                 return popupMaxWidth;
    224             } else if (itemWidth > maxWidth) {
    225                 maxWidth = itemWidth;
    226             }
    227         }
    228 
    229         return maxWidth;
    230     }
    231 
    232     static int getPixelValue(@NonNull String value, @NonNull DisplayMetrics metrics) {
    233         TypedValue typedValue = ResourceHelper.getValue(null, value, false /*requireUnit*/);
    234         return (int) typedValue.getDimension(metrics);
    235     }
    236 
    237     // TODO: This is duplicated from RenderSessionImpl.
    238     private int getActionBarHeight() {
    239         RenderResources resources = mBridgeContext.getRenderResources();
    240         DisplayMetrics metrics = mBridgeContext.getMetrics();
    241         ResourceValue value = resources.findItemInTheme("actionBarSize", true);
    242 
    243         // resolve it
    244         value = resources.resolveResValue(value);
    245 
    246         if (value != null) {
    247             // get the numerical value, if available
    248             TypedValue typedValue = ResourceHelper.getValue("actionBarSize", value.getValue(),
    249                     true);
    250             if (typedValue != null) {
    251                 // compute the pixel value based on the display metrics
    252                 return (int) typedValue.getDimension(metrics);
    253 
    254             }
    255         }
    256         return 0;
    257     }
    258 }
    259