/* * Copyright (C) 2014 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.android.layoutlib.bridge.bars; import com.android.ide.common.rendering.api.ActionBarCallback; import com.android.ide.common.rendering.api.RenderResources; import com.android.ide.common.rendering.api.ResourceValue; import com.android.internal.R; import com.android.internal.app.ToolbarActionBar; import com.android.internal.app.WindowDecorActionBar; import com.android.internal.view.menu.MenuBuilder; import com.android.internal.widget.ActionBarAccessor; import com.android.internal.widget.ActionBarView; import com.android.internal.widget.DecorToolbar; import com.android.layoutlib.bridge.android.BridgeContext; import com.android.layoutlib.bridge.impl.ResourceHelper; import android.annotation.NonNull; import android.annotation.Nullable; import android.app.ActionBar; import android.app.ActionBar.Tab; import android.app.ActionBar.TabListener; import android.app.FragmentTransaction; import android.content.Context; import android.content.res.Resources; import android.graphics.drawable.Drawable; import android.view.MenuInflater; import android.view.View; import android.view.ViewGroup; import android.view.WindowCallback; import android.widget.ActionMenuPresenter; import android.widget.Toolbar; import android.widget.Toolbar_Accessor; import static com.android.SdkConstants.ANDROID_NS_NAME_PREFIX; import static com.android.resources.ResourceType.MENU; /** * A common API to access {@link ToolbarActionBar} and {@link WindowDecorActionBar}. */ public abstract class FrameworkActionBarWrapper { @NonNull protected ActionBar mActionBar; @NonNull protected ActionBarCallback mCallback; @NonNull protected BridgeContext mContext; /** * Returns a wrapper around different implementations of the Action Bar to provide a common API. * * @param decorContent the top level view returned by inflating * ?attr/windowActionBarFullscreenDecorLayout */ @NonNull public static FrameworkActionBarWrapper getActionBarWrapper(@NonNull BridgeContext context, @NonNull ActionBarCallback callback, @NonNull View decorContent) { View view = decorContent.findViewById(R.id.action_bar); if (view instanceof Toolbar) { return new ToolbarWrapper(context, callback, (Toolbar) view); } else if (view instanceof ActionBarView) { return new WindowActionBarWrapper(context, callback, decorContent, (ActionBarView) view); } else { throw new IllegalStateException("Can't make an action bar out of " + view.getClass().getSimpleName()); } } FrameworkActionBarWrapper(@NonNull BridgeContext context, @NonNull ActionBarCallback callback, @NonNull ActionBar actionBar) { mActionBar = actionBar; mCallback = callback; mContext = context; } /** A call to setup any custom properties. */ protected void setupActionBar() { // Nothing to do here. } public void setTitle(CharSequence title) { mActionBar.setTitle(title); } public void setSubTitle(CharSequence subTitle) { if (subTitle != null) { mActionBar.setSubtitle(subTitle); } } public void setHomeAsUp(boolean homeAsUp) { mActionBar.setDisplayHomeAsUpEnabled(homeAsUp); } public void setIcon(String icon) { // Nothing to do. } protected boolean isSplit() { return getDecorToolbar().isSplit(); } protected boolean isOverflowPopupNeeded() { return mCallback.isOverflowPopupNeeded(); } /** * Gets the menus to add to the action bar from the callback, resolves them, inflates them and * adds them to the action bar. */ protected void inflateMenus() { MenuInflater inflater = new MenuInflater(getActionMenuContext()); MenuBuilder menuBuilder = getMenuBuilder(); for (String name : mCallback.getMenuIdNames()) { int id; if (name.startsWith(ANDROID_NS_NAME_PREFIX)) { // Framework menu. name = name.substring(ANDROID_NS_NAME_PREFIX.length()); id = mContext.getFrameworkResourceValue(MENU, name, -1); } else { // Project menu. id = mContext.getProjectResourceValue(MENU, name, -1); } if (id > -1) { inflater.inflate(id, menuBuilder); } } } /** * The context used for the ActionBar and the menus in the ActionBarView. */ @NonNull protected Context getActionMenuContext() { return mActionBar.getThemedContext(); } /** * The context used to inflate the popup menu. */ @NonNull abstract Context getPopupContext(); /** * The Menu in which to inflate the user's menus. */ @NonNull abstract MenuBuilder getMenuBuilder(); @Nullable abstract ActionMenuPresenter getActionMenuPresenter(); /** * Framework's wrapper over two ActionBar implementations. */ @NonNull abstract DecorToolbar getDecorToolbar(); abstract int getMenuPopupElevation(); /** * Margin between the menu popup and the action bar. */ abstract int getMenuPopupMargin(); // ---- The implementations ---- /** * Material theme uses {@link Toolbar} as the action bar. This wrapper provides access to * Toolbar using a common API. */ private static class ToolbarWrapper extends FrameworkActionBarWrapper { @NonNull private final Toolbar mToolbar; // This is the view. ToolbarWrapper(@NonNull BridgeContext context, @NonNull ActionBarCallback callback, @NonNull Toolbar toolbar) { super(context, callback, new ToolbarActionBar(toolbar, "", new WindowCallback())); mToolbar = toolbar; } @Override protected void inflateMenus() { super.inflateMenus(); // Inflating the menus doesn't initialize the ActionMenuPresenter. Setting a fake menu // and then setting it back does the trick. MenuBuilder menu = getMenuBuilder(); DecorToolbar decorToolbar = getDecorToolbar(); decorToolbar.setMenu(new MenuBuilder(getActionMenuContext()), null); decorToolbar.setMenu(menu, null); } @NonNull @Override Context getPopupContext() { return Toolbar_Accessor.getPopupContext(mToolbar); } @NonNull @Override MenuBuilder getMenuBuilder() { return (MenuBuilder) mToolbar.getMenu(); } @Nullable @Override ActionMenuPresenter getActionMenuPresenter() { return Toolbar_Accessor.getActionMenuPresenter(mToolbar); } @NonNull @Override DecorToolbar getDecorToolbar() { return mToolbar.getWrapper(); } @Override int getMenuPopupElevation() { return 10; } @Override int getMenuPopupMargin() { return 0; } } /** * Holo theme uses {@link WindowDecorActionBar} as the action bar. This wrapper provides * access to it using a common API. */ private static class WindowActionBarWrapper extends FrameworkActionBarWrapper { @NonNull private final WindowDecorActionBar mActionBar; @NonNull private final ActionBarView mActionBarView; @NonNull private final View mDecorContentRoot; private MenuBuilder mMenuBuilder; public WindowActionBarWrapper(@NonNull BridgeContext context, @NonNull ActionBarCallback callback, @NonNull View decorContentRoot, @NonNull ActionBarView actionBarView) { super(context, callback, new WindowDecorActionBar(decorContentRoot)); mActionBarView = actionBarView; mActionBar = ((WindowDecorActionBar) super.mActionBar); mDecorContentRoot = decorContentRoot; } @Override protected void setupActionBar() { // Set the navigation mode. int navMode = mCallback.getNavigationMode(); mActionBar.setNavigationMode(navMode); //noinspection deprecation if (navMode == ActionBar.NAVIGATION_MODE_TABS) { setupTabs(3); } // Set action bar to be split, if needed. ViewGroup splitView = (ViewGroup) mDecorContentRoot.findViewById(R.id.split_action_bar); if (splitView != null) { mActionBarView.setSplitView(splitView); Resources res = mContext.getResources(); boolean split = res.getBoolean(R.bool.split_action_bar_is_narrow) && mCallback.getSplitActionBarWhenNarrow(); mActionBarView.setSplitToolbar(split); } } @Override public void setIcon(String icon) { // Set the icon only if the action bar doesn't specify an icon. if (!mActionBar.hasIcon() && icon != null) { Drawable iconDrawable = getDrawable(icon, false); if (iconDrawable != null) { mActionBar.setIcon(iconDrawable); } } } @Override protected void inflateMenus() { super.inflateMenus(); // The super implementation doesn't set the menu on the view. Set it here. mActionBarView.setMenu(getMenuBuilder(), null); } @NonNull @Override Context getPopupContext() { return getActionMenuContext(); } @NonNull @Override MenuBuilder getMenuBuilder() { if (mMenuBuilder == null) { mMenuBuilder = new MenuBuilder(getActionMenuContext()); } return mMenuBuilder; } @Nullable @Override ActionMenuPresenter getActionMenuPresenter() { return ActionBarAccessor.getActionMenuPresenter(mActionBarView); } @NonNull @Override ActionBarView getDecorToolbar() { return mActionBarView; } @Override int getMenuPopupElevation() { return 0; } @Override int getMenuPopupMargin() { return -FrameworkActionBar.getPixelValue("10dp", mContext.getMetrics()); } // TODO: Use an adapter, like List View to set up tabs. @SuppressWarnings("deprecation") // For Tab private void setupTabs(int num) { for (int i = 1; i <= num; i++) { Tab tab = mActionBar.newTab().setText("Tab" + i).setTabListener(new TabListener() { @Override public void onTabUnselected(Tab t, FragmentTransaction ft) { // pass } @Override public void onTabSelected(Tab t, FragmentTransaction ft) { // pass } @Override public void onTabReselected(Tab t, FragmentTransaction ft) { // pass } }); mActionBar.addTab(tab); } } @Nullable private Drawable getDrawable(@NonNull String name, boolean isFramework) { RenderResources res = mContext.getRenderResources(); ResourceValue value = res.findResValue(name, isFramework); value = res.resolveResValue(value); if (value != null) { return ResourceHelper.getDrawable(value, mContext); } return null; } } }