Bridge.java revision 755b46d597b8e8a616d53e2a7dfea295dd78d713
1/* 2 * Copyright (C) 2008 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; 18 19import com.android.layoutlib.api.ILayoutLog; 20import com.android.layoutlib.api.IProjectCallback; 21import com.android.layoutlib.api.IResourceValue; 22import com.android.layoutlib.api.IXmlPullParser; 23import com.android.layoutlib.api.LayoutBridge; 24import com.android.layoutlib.api.SceneParams; 25import com.android.layoutlib.api.SceneResult; 26import com.android.layoutlib.bridge.android.BridgeAssetManager; 27import com.android.layoutlib.bridge.impl.FontLoader; 28import com.android.layoutlib.bridge.impl.LayoutSceneImpl; 29import com.android.ninepatch.NinePatch; 30import com.android.tools.layoutlib.create.MethodAdapter; 31import com.android.tools.layoutlib.create.OverrideMethod; 32 33import android.graphics.Bitmap; 34import android.graphics.Typeface_Delegate; 35import android.util.Finalizers; 36 37import java.lang.ref.SoftReference; 38import java.lang.reflect.Field; 39import java.lang.reflect.Modifier; 40import java.util.Arrays; 41import java.util.HashMap; 42import java.util.Map; 43 44/** 45 * Main entry point of the LayoutLib Bridge. 46 * <p/>To use this bridge, simply instantiate an object of type {@link Bridge} and call 47 * {@link #computeLayout(IXmlPullParser, Object, int, int, String, boolean, Map, Map, IProjectCallback, ILayoutLog)}. 48 */ 49public final class Bridge extends LayoutBridge { 50 51 public static class StaticMethodNotImplementedException extends RuntimeException { 52 private static final long serialVersionUID = 1L; 53 54 public StaticMethodNotImplementedException(String msg) { 55 super(msg); 56 } 57 } 58 59 /** 60 * Maps from id to resource name/type. This is for android.R only. 61 */ 62 private final static Map<Integer, String[]> sRMap = new HashMap<Integer, String[]>(); 63 /** 64 * Same as sRMap except for int[] instead of int resources. This is for android.R only. 65 */ 66 private final static Map<IntArray, String> sRArrayMap = new HashMap<IntArray, String>(); 67 /** 68 * Reverse map compared to sRMap, resource type -> (resource name -> id). 69 * This is for android.R only. 70 */ 71 private final static Map<String, Map<String, Integer>> sRFullMap = 72 new HashMap<String, Map<String,Integer>>(); 73 74 private final static Map<Object, Map<String, SoftReference<Bitmap>>> sProjectBitmapCache = 75 new HashMap<Object, Map<String, SoftReference<Bitmap>>>(); 76 private final static Map<Object, Map<String, SoftReference<NinePatch>>> sProject9PatchCache = 77 new HashMap<Object, Map<String, SoftReference<NinePatch>>>(); 78 79 private final static Map<String, SoftReference<Bitmap>> sFrameworkBitmapCache = 80 new HashMap<String, SoftReference<Bitmap>>(); 81 private final static Map<String, SoftReference<NinePatch>> sFramework9PatchCache = 82 new HashMap<String, SoftReference<NinePatch>>(); 83 84 private static Map<String, Map<String, Integer>> sEnumValueMap; 85 86 /** 87 * int[] wrapper to use as keys in maps. 88 */ 89 private final static class IntArray { 90 private int[] mArray; 91 92 private IntArray() { 93 // do nothing 94 } 95 96 private IntArray(int[] a) { 97 mArray = a; 98 } 99 100 private void set(int[] a) { 101 mArray = a; 102 } 103 104 @Override 105 public int hashCode() { 106 return Arrays.hashCode(mArray); 107 } 108 109 @Override 110 public boolean equals(Object obj) { 111 if (this == obj) return true; 112 if (obj == null) return false; 113 if (getClass() != obj.getClass()) return false; 114 115 IntArray other = (IntArray) obj; 116 if (!Arrays.equals(mArray, other.mArray)) return false; 117 return true; 118 } 119 } 120 121 /** Instance of IntArrayWrapper to be reused in {@link #resolveResourceValue(int[])}. */ 122 private final static IntArray sIntArrayWrapper = new IntArray(); 123 124 /** 125 * A default logger than prints to stdout/stderr. 126 */ 127 private final static ILayoutLog sDefaultLogger = new ILayoutLog() { 128 public void error(String message) { 129 System.err.println(message); 130 } 131 132 public void error(Throwable t) { 133 String message = t.getMessage(); 134 if (message == null) { 135 message = t.getClass().getName(); 136 } 137 138 System.err.println(message); 139 } 140 141 public void warning(String message) { 142 System.out.println(message); 143 } 144 }; 145 146 @Override 147 public int getApiLevel() { 148 return LayoutBridge.API_CURRENT; 149 } 150 151 /* 152 * (non-Javadoc) 153 * @see com.android.layoutlib.api.ILayoutLibBridge#init(java.lang.String, java.util.Map) 154 */ 155 @Override 156 public boolean init(String fontOsLocation, Map<String, Map<String, Integer>> enumValueMap) { 157 sEnumValueMap = enumValueMap; 158 159 Finalizers.init(); 160 161 BridgeAssetManager.initSystem(); 162 163 164 // When DEBUG_LAYOUT is set and is not 0 or false, setup a default listener 165 // on static (native) methods which prints the signature on the console and 166 // throws an exception. 167 // This is useful when testing the rendering in ADT to identify static native 168 // methods that are ignored -- layoutlib_create makes them returns 0/false/null 169 // which is generally OK yet might be a problem, so this is how you'd find out. 170 // 171 // Currently layoutlib_create only overrides static native method. 172 // Static non-natives are not overridden and thus do not get here. 173 final String debug = System.getenv("DEBUG_LAYOUT"); 174 if (debug != null && !debug.equals("0") && !debug.equals("false")) { 175 176 OverrideMethod.setDefaultListener(new MethodAdapter() { 177 @Override 178 public void onInvokeV(String signature, boolean isNative, Object caller) { 179 sDefaultLogger.error("Missing Stub: " + signature + 180 (isNative ? " (native)" : "")); 181 182 if (debug.equalsIgnoreCase("throw")) { 183 // Throwing this exception doesn't seem that useful. It breaks 184 // the layout editor yet doesn't display anything meaningful to the 185 // user. Having the error in the console is just as useful. We'll 186 // throw it only if the environment variable is "throw" or "THROW". 187 throw new StaticMethodNotImplementedException(signature); 188 } 189 } 190 }); 191 } 192 193 // load the fonts. 194 FontLoader fontLoader = FontLoader.create(fontOsLocation); 195 if (fontLoader != null) { 196 Typeface_Delegate.init(fontLoader); 197 } else { 198 return false; 199 } 200 201 // now parse com.android.internal.R (and only this one as android.R is a subset of 202 // the internal version), and put the content in the maps. 203 try { 204 // WARNING: this only works because the class is already loaded, and therefore 205 // the objects returned by Field.get() are the same as the ones used by 206 // the code accessing the R class. 207 // int[] does not implement equals/hashCode, and if the parsing used a different class 208 // loader for the R class, this would NOT work. 209 Class<?> r = com.android.internal.R.class; 210 211 for (Class<?> inner : r.getDeclaredClasses()) { 212 String resType = inner.getSimpleName(); 213 214 Map<String, Integer> fullMap = new HashMap<String, Integer>(); 215 sRFullMap.put(resType, fullMap); 216 217 for (Field f : inner.getDeclaredFields()) { 218 // only process static final fields. Since the final attribute may have 219 // been altered by layoutlib_create, we only check static 220 int modifiers = f.getModifiers(); 221 if (Modifier.isStatic(modifiers)) { 222 Class<?> type = f.getType(); 223 if (type.isArray() && type.getComponentType() == int.class) { 224 // if the object is an int[] we put it in sRArrayMap 225 sRArrayMap.put(new IntArray((int[]) f.get(null)), f.getName()); 226 } else if (type == int.class) { 227 Integer value = (Integer) f.get(null); 228 sRMap.put(value, new String[] { f.getName(), resType }); 229 fullMap.put(f.getName(), value); 230 } else { 231 assert false; 232 } 233 } 234 } 235 } 236 } catch (IllegalArgumentException e) { 237 // FIXME: log/return the error (there's no logger object at this point!) 238 e.printStackTrace(); 239 return false; 240 } catch (IllegalAccessException e) { 241 e.printStackTrace(); 242 return false; 243 } 244 245 return true; 246 } 247 248 @Override 249 public boolean dispose() { 250 BridgeAssetManager.clearSystem(); 251 return true; 252 } 253 254 /** 255 * Sets a 9 patch in a project cache or in the framework cache. 256 * @param value the path of the 9 patch 257 * @param ninePatch the 9 patch object 258 * @param projectKey the key of the project, or null to put the bitmap in the framework cache. 259 */ 260 public static void setCached9Patch(String value, NinePatch ninePatch, Object projectKey) { 261 if (projectKey != null) { 262 Map<String, SoftReference<NinePatch>> map = sProject9PatchCache.get(projectKey); 263 264 if (map == null) { 265 map = new HashMap<String, SoftReference<NinePatch>>(); 266 sProject9PatchCache.put(projectKey, map); 267 } 268 269 map.put(value, new SoftReference<NinePatch>(ninePatch)); 270 } else { 271 sFramework9PatchCache.put(value, new SoftReference<NinePatch>(ninePatch)); 272 } 273 } 274 275 /** 276 * Starts a layout session by inflating and rendering it. The method returns a 277 * {@link ILayoutScene} on which further actions can be taken. 278 * 279 * @param layoutDescription the {@link IXmlPullParser} letting the LayoutLib Bridge visit the 280 * layout file. 281 * @param projectKey An Object identifying the project. This is used for the cache mechanism. 282 * @param screenWidth the screen width 283 * @param screenHeight the screen height 284 * @param renderFullSize if true, the rendering will render the full size needed by the 285 * layout. This size is never smaller than <var>screenWidth</var> x <var>screenHeight</var>. 286 * @param density the density factor for the screen. 287 * @param xdpi the screen actual dpi in X 288 * @param ydpi the screen actual dpi in Y 289 * @param themeName The name of the theme to use. 290 * @param isProjectTheme true if the theme is a project theme, false if it is a framework theme. 291 * @param projectResources the resources of the project. The map contains (String, map) pairs 292 * where the string is the type of the resource reference used in the layout file, and the 293 * map contains (String, {@link IResourceValue}) pairs where the key is the resource name, 294 * and the value is the resource value. 295 * @param frameworkResources the framework resources. The map contains (String, map) pairs 296 * where the string is the type of the resource reference used in the layout file, and the map 297 * contains (String, {@link IResourceValue}) pairs where the key is the resource name, and the 298 * value is the resource value. 299 * @param projectCallback The {@link IProjectCallback} object to get information from 300 * the project. 301 * @param logger the object responsible for displaying warning/errors to the user. 302 * @return a new {@link ILayoutScene} object that contains the result of the layout. 303 * @since 5 304 */ 305 @Override 306 public BridgeLayoutScene createScene(SceneParams params) { 307 try { 308 SceneResult lastResult = SceneResult.SUCCESS; 309 LayoutSceneImpl scene = null; 310 synchronized (this) { 311 try { 312 scene = new LayoutSceneImpl(params); 313 314 scene.prepare(); 315 lastResult = scene.inflate(); 316 if (lastResult == SceneResult.SUCCESS) { 317 lastResult = scene.render(); 318 } 319 } finally { 320 if (scene != null) { 321 scene.cleanup(); 322 } 323 } 324 } 325 326 return new BridgeLayoutScene(this, scene, lastResult); 327 } catch (Throwable t) { 328 t.printStackTrace(); 329 return new BridgeLayoutScene(this, null, new SceneResult("error!", t)); 330 } 331 } 332 333 /* 334 * (non-Javadoc) 335 * @see com.android.layoutlib.api.ILayoutLibBridge#clearCaches(java.lang.Object) 336 */ 337 @Override 338 public void clearCaches(Object projectKey) { 339 if (projectKey != null) { 340 sProjectBitmapCache.remove(projectKey); 341 sProject9PatchCache.remove(projectKey); 342 } 343 } 344 345 /** 346 * Returns details of a framework resource from its integer value. 347 * @param value the integer value 348 * @return an array of 2 strings containing the resource name and type, or null if the id 349 * does not match any resource. 350 */ 351 public static String[] resolveResourceValue(int value) { 352 return sRMap.get(value); 353 354 } 355 356 /** 357 * Returns the name of a framework resource whose value is an int array. 358 * @param array 359 */ 360 public static String resolveResourceValue(int[] array) { 361 sIntArrayWrapper.set(array); 362 return sRArrayMap.get(sIntArrayWrapper); 363 } 364 365 /** 366 * Returns the integer id of a framework resource, from a given resource type and resource name. 367 * @param type the type of the resource 368 * @param name the name of the resource. 369 * @return an {@link Integer} containing the resource id, or null if no resource were found. 370 */ 371 public static Integer getResourceValue(String type, String name) { 372 Map<String, Integer> map = sRFullMap.get(type); 373 if (map != null) { 374 return map.get(name); 375 } 376 377 return null; 378 } 379 380 /** 381 * Returns the list of possible enums for a given attribute name. 382 */ 383 public static Map<String, Integer> getEnumValues(String attributeName) { 384 if (sEnumValueMap != null) { 385 return sEnumValueMap.get(attributeName); 386 } 387 388 return null; 389 } 390 391 /** 392 * Returns the bitmap for a specific path, from a specific project cache, or from the 393 * framework cache. 394 * @param value the path of the bitmap 395 * @param projectKey the key of the project, or null to query the framework cache. 396 * @return the cached Bitmap or null if not found. 397 */ 398 public static Bitmap getCachedBitmap(String value, Object projectKey) { 399 if (projectKey != null) { 400 Map<String, SoftReference<Bitmap>> map = sProjectBitmapCache.get(projectKey); 401 if (map != null) { 402 SoftReference<Bitmap> ref = map.get(value); 403 if (ref != null) { 404 return ref.get(); 405 } 406 } 407 } else { 408 SoftReference<Bitmap> ref = sFrameworkBitmapCache.get(value); 409 if (ref != null) { 410 return ref.get(); 411 } 412 } 413 414 return null; 415 } 416 417 /** 418 * Sets a bitmap in a project cache or in the framework cache. 419 * @param value the path of the bitmap 420 * @param bmp the Bitmap object 421 * @param projectKey the key of the project, or null to put the bitmap in the framework cache. 422 */ 423 public static void setCachedBitmap(String value, Bitmap bmp, Object projectKey) { 424 if (projectKey != null) { 425 Map<String, SoftReference<Bitmap>> map = sProjectBitmapCache.get(projectKey); 426 427 if (map == null) { 428 map = new HashMap<String, SoftReference<Bitmap>>(); 429 sProjectBitmapCache.put(projectKey, map); 430 } 431 432 map.put(value, new SoftReference<Bitmap>(bmp)); 433 } else { 434 sFrameworkBitmapCache.put(value, new SoftReference<Bitmap>(bmp)); 435 } 436 } 437 438 /** 439 * Returns the 9 patch for a specific path, from a specific project cache, or from the 440 * framework cache. 441 * @param value the path of the 9 patch 442 * @param projectKey the key of the project, or null to query the framework cache. 443 * @return the cached 9 patch or null if not found. 444 */ 445 public static NinePatch getCached9Patch(String value, Object projectKey) { 446 if (projectKey != null) { 447 Map<String, SoftReference<NinePatch>> map = sProject9PatchCache.get(projectKey); 448 449 if (map != null) { 450 SoftReference<NinePatch> ref = map.get(value); 451 if (ref != null) { 452 return ref.get(); 453 } 454 } 455 } else { 456 SoftReference<NinePatch> ref = sFramework9PatchCache.get(value); 457 if (ref != null) { 458 return ref.get(); 459 } 460 } 461 462 return null; 463 } 464} 465