/* * Copyright (C) 2015 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.RenderResources; import com.android.ide.common.rendering.api.ResourceValue; import com.android.ide.common.rendering.api.SessionParams; import com.android.ide.common.rendering.api.StyleResourceValue; import com.android.layoutlib.bridge.android.BridgeContext; import com.android.layoutlib.bridge.impl.ResourceHelper; import com.android.resources.ResourceType; import android.annotation.NonNull; import android.annotation.Nullable; import android.content.Context; import android.graphics.drawable.Drawable; import android.view.ContextThemeWrapper; import android.view.LayoutInflater; import android.view.View; import android.widget.FrameLayout; import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; /** * Assumes that the AppCompat library is present in the project's classpath and creates an * actionbar around it. */ public class AppCompatActionBar extends BridgeActionBar { private Object mWindowDecorActionBar; private static final String WINDOW_ACTION_BAR_CLASS = "android.support.v7.internal.app.WindowDecorActionBar"; private Class mWindowActionBarClass; /** * Inflate the action bar and attach it to {@code parentView} */ public AppCompatActionBar(@NonNull BridgeContext context, @NonNull SessionParams params) { super(context, params); int contentRootId = context.getProjectResourceValue(ResourceType.ID, "action_bar_activity_content", 0); View contentView = getDecorContent().findViewById(contentRootId); if (contentView != null) { assert contentView instanceof FrameLayout; setContentRoot((FrameLayout) contentView); } else { // Something went wrong. Create a new FrameLayout in the enclosing layout. FrameLayout contentRoot = new FrameLayout(context); setMatchParent(contentRoot); if (mEnclosingLayout != null) { mEnclosingLayout.addView(contentRoot); } setContentRoot(contentRoot); } try { Class[] constructorParams = {View.class}; Object[] constructorArgs = {getDecorContent()}; mWindowDecorActionBar = params.getLayoutlibCallback().loadView(WINDOW_ACTION_BAR_CLASS, constructorParams, constructorArgs); mWindowActionBarClass = mWindowDecorActionBar == null ? null : mWindowDecorActionBar.getClass(); setupActionBar(); } catch (Exception e) { e.printStackTrace(); } } @Override protected ResourceValue getLayoutResource(BridgeContext context) { // We always assume that the app has requested the action bar. return context.getRenderResources().getProjectResource(ResourceType.LAYOUT, "abc_screen_toolbar"); } @Override protected LayoutInflater getInflater(BridgeContext context) { // Other than the resource resolution part, the code has been taken from the support // library. see code from line 269 onwards in // https://android.googlesource.com/platform/frameworks/support/+/android-5.1.0_r1/v7/appcompat/src/android/support/v7/app/ActionBarActivityDelegateBase.java Context themedContext = context; RenderResources resources = context.getRenderResources(); ResourceValue actionBarTheme = resources.findItemInTheme("actionBarTheme", false); if (actionBarTheme != null) { // resolve it, if needed. actionBarTheme = resources.resolveResValue(actionBarTheme); } if (actionBarTheme instanceof StyleResourceValue) { int styleId = context.getDynamicIdByStyle(((StyleResourceValue) actionBarTheme)); if (styleId != 0) { themedContext = new ContextThemeWrapper(context, styleId); } } return LayoutInflater.from(themedContext); } @Override protected void setTitle(CharSequence title) { if (title != null && mWindowDecorActionBar != null) { Method setTitle = getMethod(mWindowActionBarClass, "setTitle", CharSequence.class); invoke(setTitle, mWindowDecorActionBar, title); } } @Override protected void setSubtitle(CharSequence subtitle) { if (subtitle != null && mWindowDecorActionBar != null) { Method setSubtitle = getMethod(mWindowActionBarClass, "setSubtitle", CharSequence.class); invoke(setSubtitle, mWindowDecorActionBar, subtitle); } } @Override protected void setIcon(String icon) { // Do this only if the action bar doesn't already have an icon. if (icon != null && !icon.isEmpty() && mWindowDecorActionBar != null) { if (invoke(getMethod(mWindowActionBarClass, "hasIcon"), mWindowDecorActionBar) == Boolean.TRUE) { Drawable iconDrawable = getDrawable(icon, false); if (iconDrawable != null) { Method setIcon = getMethod(mWindowActionBarClass, "setIcon", Drawable.class); invoke(setIcon, mWindowDecorActionBar, iconDrawable); } } } } @Override protected void setHomeAsUp(boolean homeAsUp) { if (mWindowDecorActionBar != null) { Method setHomeAsUp = getMethod(mWindowActionBarClass, "setDefaultDisplayHomeAsUpEnabled", boolean.class); invoke(setHomeAsUp, mWindowDecorActionBar, homeAsUp); } } @Override public void createMenuPopup() { // it's hard to add menus to appcompat's actionbar, since it'll use a lot of reflection. // so we skip it for now. } @Nullable private static Method getMethod(Class owner, String name, Class... parameterTypes) { try { return owner == null ? null : owner.getMethod(name, parameterTypes); } catch (NoSuchMethodException e) { e.printStackTrace(); } return null; } @Nullable private static Object invoke(Method method, Object owner, Object... args) { try { return method == null ? null : method.invoke(owner, args); } catch (InvocationTargetException e) { e.printStackTrace(); } catch (IllegalAccessException e) { e.printStackTrace(); } return null; } // TODO: this is duplicated from FrameworkActionBarWrapper$WindowActionBarWrapper @Nullable private Drawable getDrawable(@NonNull String name, boolean isFramework) { RenderResources res = mBridgeContext.getRenderResources(); ResourceValue value = res.findResValue(name, isFramework); value = res.resolveResValue(value); if (value != null) { return ResourceHelper.getDrawable(value, mBridgeContext); } return null; } }