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