1/* 2 * Copyright (C) 2015 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 17package com.android.layoutlib.bridge.bars; 18 19import com.android.ide.common.rendering.api.LayoutLog; 20import com.android.ide.common.rendering.api.LayoutlibCallback; 21import com.android.ide.common.rendering.api.RenderResources; 22import com.android.ide.common.rendering.api.ResourceValue; 23import com.android.ide.common.rendering.api.SessionParams; 24import com.android.ide.common.rendering.api.StyleResourceValue; 25import com.android.layoutlib.bridge.Bridge; 26import com.android.layoutlib.bridge.android.BridgeContext; 27import com.android.layoutlib.bridge.impl.ResourceHelper; 28import com.android.resources.ResourceType; 29 30import android.annotation.NonNull; 31import android.annotation.Nullable; 32import android.content.Context; 33import android.graphics.drawable.Drawable; 34import android.view.ContextThemeWrapper; 35import android.view.LayoutInflater; 36import android.view.View; 37import android.widget.FrameLayout; 38 39import java.lang.reflect.InvocationTargetException; 40import java.lang.reflect.Method; 41 42 43/** 44 * Assumes that the AppCompat library is present in the project's classpath and creates an 45 * actionbar around it. 46 */ 47public class AppCompatActionBar extends BridgeActionBar { 48 49 private Object mWindowDecorActionBar; 50 private static final String WINDOW_ACTION_BAR_CLASS = "android.support.v7.internal.app.WindowDecorActionBar"; 51 // This is used on v23.1.1 and later. 52 private static final String WINDOW_ACTION_BAR_CLASS_NEW = "android.support.v7.app.WindowDecorActionBar"; 53 private Class<?> mWindowActionBarClass; 54 55 /** 56 * Inflate the action bar and attach it to {@code parentView} 57 */ 58 public AppCompatActionBar(@NonNull BridgeContext context, @NonNull SessionParams params) { 59 super(context, params); 60 int contentRootId = context.getProjectResourceValue(ResourceType.ID, 61 "action_bar_activity_content", 0); 62 View contentView = getDecorContent().findViewById(contentRootId); 63 if (contentView != null) { 64 assert contentView instanceof FrameLayout; 65 setContentRoot((FrameLayout) contentView); 66 } else { 67 // Something went wrong. Create a new FrameLayout in the enclosing layout. 68 FrameLayout contentRoot = new FrameLayout(context); 69 setMatchParent(contentRoot); 70 if (mEnclosingLayout != null) { 71 mEnclosingLayout.addView(contentRoot); 72 } 73 setContentRoot(contentRoot); 74 } 75 try { 76 Class[] constructorParams = {View.class}; 77 Object[] constructorArgs = {getDecorContent()}; 78 LayoutlibCallback callback = params.getLayoutlibCallback(); 79 80 // Check if the old action bar class is present. 81 String actionBarClass = WINDOW_ACTION_BAR_CLASS; 82 try { 83 callback.findClass(actionBarClass); 84 } catch (ClassNotFoundException expected) { 85 // Failed to find the old class, use the newer one. 86 actionBarClass = WINDOW_ACTION_BAR_CLASS_NEW; 87 } 88 89 mWindowDecorActionBar = callback.loadView(actionBarClass, 90 constructorParams, constructorArgs); 91 mWindowActionBarClass = mWindowDecorActionBar == null ? null : 92 mWindowDecorActionBar.getClass(); 93 setupActionBar(); 94 } catch (Exception e) { 95 Bridge.getLog().warning(LayoutLog.TAG_BROKEN, 96 "Failed to load AppCompat ActionBar with unknown error.", e); 97 } 98 } 99 100 @Override 101 protected ResourceValue getLayoutResource(BridgeContext context) { 102 // We always assume that the app has requested the action bar. 103 return context.getRenderResources().getProjectResource(ResourceType.LAYOUT, 104 "abc_screen_toolbar"); 105 } 106 107 @Override 108 protected LayoutInflater getInflater(BridgeContext context) { 109 // Other than the resource resolution part, the code has been taken from the support 110 // library. see code from line 269 onwards in 111 // https://android.googlesource.com/platform/frameworks/support/+/android-5.1.0_r1/v7/appcompat/src/android/support/v7/app/ActionBarActivityDelegateBase.java 112 Context themedContext = context; 113 RenderResources resources = context.getRenderResources(); 114 ResourceValue actionBarTheme = resources.findItemInTheme("actionBarTheme", false); 115 if (actionBarTheme != null) { 116 // resolve it, if needed. 117 actionBarTheme = resources.resolveResValue(actionBarTheme); 118 } 119 if (actionBarTheme instanceof StyleResourceValue) { 120 int styleId = context.getDynamicIdByStyle(((StyleResourceValue) actionBarTheme)); 121 if (styleId != 0) { 122 themedContext = new ContextThemeWrapper(context, styleId); 123 } 124 } 125 return LayoutInflater.from(themedContext); 126 } 127 128 @Override 129 protected void setTitle(CharSequence title) { 130 if (title != null && mWindowDecorActionBar != null) { 131 Method setTitle = getMethod(mWindowActionBarClass, "setTitle", CharSequence.class); 132 invoke(setTitle, mWindowDecorActionBar, title); 133 } 134 } 135 136 @Override 137 protected void setSubtitle(CharSequence subtitle) { 138 if (subtitle != null && mWindowDecorActionBar != null) { 139 Method setSubtitle = getMethod(mWindowActionBarClass, "setSubtitle", CharSequence.class); 140 invoke(setSubtitle, mWindowDecorActionBar, subtitle); 141 } 142 } 143 144 @Override 145 protected void setIcon(String icon) { 146 // Do this only if the action bar doesn't already have an icon. 147 if (icon != null && !icon.isEmpty() && mWindowDecorActionBar != null) { 148 if (invoke(getMethod(mWindowActionBarClass, "hasIcon"), mWindowDecorActionBar) 149 == Boolean.TRUE) { 150 Drawable iconDrawable = getDrawable(icon, false); 151 if (iconDrawable != null) { 152 Method setIcon = getMethod(mWindowActionBarClass, "setIcon", Drawable.class); 153 invoke(setIcon, mWindowDecorActionBar, iconDrawable); 154 } 155 } 156 } 157 } 158 159 @Override 160 protected void setHomeAsUp(boolean homeAsUp) { 161 if (mWindowDecorActionBar != null) { 162 Method setHomeAsUp = getMethod(mWindowActionBarClass, 163 "setDefaultDisplayHomeAsUpEnabled", boolean.class); 164 invoke(setHomeAsUp, mWindowDecorActionBar, homeAsUp); 165 } 166 } 167 168 @Override 169 public void createMenuPopup() { 170 // it's hard to add menus to appcompat's actionbar, since it'll use a lot of reflection. 171 // so we skip it for now. 172 } 173 174 @Nullable 175 private static Method getMethod(Class<?> owner, String name, Class<?>... parameterTypes) { 176 try { 177 return owner == null ? null : owner.getMethod(name, parameterTypes); 178 } catch (NoSuchMethodException e) { 179 e.printStackTrace(); 180 } 181 return null; 182 } 183 184 @Nullable 185 private static Object invoke(Method method, Object owner, Object... args) { 186 try { 187 return method == null ? null : method.invoke(owner, args); 188 } catch (InvocationTargetException e) { 189 e.printStackTrace(); 190 } catch (IllegalAccessException e) { 191 e.printStackTrace(); 192 } 193 return null; 194 } 195 196 // TODO: this is duplicated from FrameworkActionBarWrapper$WindowActionBarWrapper 197 @Nullable 198 private Drawable getDrawable(@NonNull String name, boolean isFramework) { 199 RenderResources res = mBridgeContext.getRenderResources(); 200 ResourceValue value = res.findResValue(name, isFramework); 201 value = res.resolveResValue(value); 202 if (value != null) { 203 return ResourceHelper.getDrawable(value, mBridgeContext); 204 } 205 return null; 206 } 207} 208