Bridge.java revision 19a021038f2f4683dddef651543d7298f5bd7218
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 static com.android.ide.common.rendering.api.Result.Status.ERROR_UNKNOWN; 20import static com.android.ide.common.rendering.api.Result.Status.SUCCESS; 21 22import com.android.ide.common.rendering.api.Capability; 23import com.android.ide.common.rendering.api.LayoutLog; 24import com.android.ide.common.rendering.api.Params; 25import com.android.ide.common.rendering.api.RenderSession; 26import com.android.ide.common.rendering.api.Result; 27import com.android.layoutlib.bridge.android.BridgeAssetManager; 28import com.android.layoutlib.bridge.impl.FontLoader; 29import com.android.layoutlib.bridge.impl.RenderSessionImpl; 30import com.android.ninepatch.NinePatchChunk; 31import com.android.tools.layoutlib.create.MethodAdapter; 32import com.android.tools.layoutlib.create.OverrideMethod; 33 34import android.graphics.Bitmap; 35import android.graphics.Typeface_Delegate; 36import android.os.Looper; 37 38import java.io.File; 39import java.lang.ref.SoftReference; 40import java.lang.reflect.Field; 41import java.lang.reflect.Modifier; 42import java.util.Arrays; 43import java.util.EnumSet; 44import java.util.HashMap; 45import java.util.Map; 46import java.util.concurrent.locks.ReentrantLock; 47 48/** 49 * Main entry point of the LayoutLib Bridge. 50 * <p/>To use this bridge, simply instantiate an object of type {@link Bridge} and call 51 * {@link #createScene(SceneParams)} 52 */ 53public final class Bridge extends com.android.ide.common.rendering.api.Bridge { 54 55 public static class StaticMethodNotImplementedException extends RuntimeException { 56 private static final long serialVersionUID = 1L; 57 58 public StaticMethodNotImplementedException(String msg) { 59 super(msg); 60 } 61 } 62 63 /** 64 * Lock to ensure only one rendering/inflating happens at a time. 65 * This is due to some singleton in the Android framework. 66 */ 67 private final static ReentrantLock sLock = new ReentrantLock(); 68 69 /** 70 * Maps from id to resource name/type. This is for android.R only. 71 */ 72 private final static Map<Integer, String[]> sRMap = new HashMap<Integer, String[]>(); 73 /** 74 * Same as sRMap except for int[] instead of int resources. This is for android.R only. 75 */ 76 private final static Map<IntArray, String> sRArrayMap = new HashMap<IntArray, String>(); 77 /** 78 * Reverse map compared to sRMap, resource type -> (resource name -> id). 79 * This is for android.R only. 80 */ 81 private final static Map<String, Map<String, Integer>> sRFullMap = 82 new HashMap<String, Map<String,Integer>>(); 83 84 private final static Map<Object, Map<String, SoftReference<Bitmap>>> sProjectBitmapCache = 85 new HashMap<Object, Map<String, SoftReference<Bitmap>>>(); 86 private final static Map<Object, Map<String, SoftReference<NinePatchChunk>>> sProject9PatchCache = 87 new HashMap<Object, Map<String, SoftReference<NinePatchChunk>>>(); 88 89 private final static Map<String, SoftReference<Bitmap>> sFrameworkBitmapCache = 90 new HashMap<String, SoftReference<Bitmap>>(); 91 private final static Map<String, SoftReference<NinePatchChunk>> sFramework9PatchCache = 92 new HashMap<String, SoftReference<NinePatchChunk>>(); 93 94 private static Map<String, Map<String, Integer>> sEnumValueMap; 95 96 /** 97 * int[] wrapper to use as keys in maps. 98 */ 99 private final static class IntArray { 100 private int[] mArray; 101 102 private IntArray() { 103 // do nothing 104 } 105 106 private IntArray(int[] a) { 107 mArray = a; 108 } 109 110 private void set(int[] a) { 111 mArray = a; 112 } 113 114 @Override 115 public int hashCode() { 116 return Arrays.hashCode(mArray); 117 } 118 119 @Override 120 public boolean equals(Object obj) { 121 if (this == obj) return true; 122 if (obj == null) return false; 123 if (getClass() != obj.getClass()) return false; 124 125 IntArray other = (IntArray) obj; 126 if (!Arrays.equals(mArray, other.mArray)) return false; 127 return true; 128 } 129 } 130 131 /** Instance of IntArrayWrapper to be reused in {@link #resolveResourceValue(int[])}. */ 132 private final static IntArray sIntArrayWrapper = new IntArray(); 133 134 /** 135 * A default log than prints to stdout/stderr. 136 */ 137 private final static LayoutLog sDefaultLog = new LayoutLog() { 138 @Override 139 public void error(String tag, String message) { 140 System.err.println(message); 141 } 142 143 @Override 144 public void error(String tag, Throwable t) { 145 String message = t.getMessage(); 146 if (message == null) { 147 message = t.getClass().getName(); 148 } 149 150 System.err.println(message); 151 } 152 153 @Override 154 public void error(String tag, String message, Throwable throwable) { 155 System.err.println(message); 156 } 157 158 @Override 159 public void warning(String tag, String message) { 160 System.out.println(message); 161 } 162 }; 163 164 /** 165 * Current log. 166 */ 167 private static LayoutLog sCurrentLog = sDefaultLog; 168 169 170 private EnumSet<Capability> mCapabilities; 171 172 173 @Override 174 public int getApiLevel() { 175 return com.android.ide.common.rendering.api.Bridge.API_CURRENT; 176 } 177 178 @Override 179 public EnumSet<Capability> getCapabilities() { 180 return mCapabilities; 181 } 182 183 /* 184 * (non-Javadoc) 185 * @see com.android.layoutlib.api.ILayoutLibBridge#init(java.io.File, java.util.Map) 186 */ 187 @Override 188 public boolean init(File fontLocation, Map<String, Map<String, Integer>> enumValueMap) { 189 sEnumValueMap = enumValueMap; 190 191 // don't use EnumSet.allOf(), because the bridge doesn't come with its specific version 192 // of layoutlib_api. It is provided by the client which could have a more recent version 193 // with newer, unsupported capabilities. 194 mCapabilities = EnumSet.of( 195 Capability.UNBOUND_RENDERING, 196 Capability.TRANSPARENCY, 197 Capability.RENDER, 198 Capability.EMBEDDED_LAYOUT, 199 Capability.VIEW_MANIPULATION, 200 Capability.ANIMATE); 201 202 203 BridgeAssetManager.initSystem(); 204 205 // When DEBUG_LAYOUT is set and is not 0 or false, setup a default listener 206 // on static (native) methods which prints the signature on the console and 207 // throws an exception. 208 // This is useful when testing the rendering in ADT to identify static native 209 // methods that are ignored -- layoutlib_create makes them returns 0/false/null 210 // which is generally OK yet might be a problem, so this is how you'd find out. 211 // 212 // Currently layoutlib_create only overrides static native method. 213 // Static non-natives are not overridden and thus do not get here. 214 final String debug = System.getenv("DEBUG_LAYOUT"); 215 if (debug != null && !debug.equals("0") && !debug.equals("false")) { 216 217 OverrideMethod.setDefaultListener(new MethodAdapter() { 218 @Override 219 public void onInvokeV(String signature, boolean isNative, Object caller) { 220 sDefaultLog.error(null, "Missing Stub: " + signature + 221 (isNative ? " (native)" : "")); 222 223 if (debug.equalsIgnoreCase("throw")) { 224 // Throwing this exception doesn't seem that useful. It breaks 225 // the layout editor yet doesn't display anything meaningful to the 226 // user. Having the error in the console is just as useful. We'll 227 // throw it only if the environment variable is "throw" or "THROW". 228 throw new StaticMethodNotImplementedException(signature); 229 } 230 } 231 }); 232 } 233 234 // load the fonts. 235 FontLoader fontLoader = FontLoader.create(fontLocation.getAbsolutePath()); 236 if (fontLoader != null) { 237 Typeface_Delegate.init(fontLoader); 238 } else { 239 return false; 240 } 241 242 // now parse com.android.internal.R (and only this one as android.R is a subset of 243 // the internal version), and put the content in the maps. 244 try { 245 // WARNING: this only works because the class is already loaded, and therefore 246 // the objects returned by Field.get() are the same as the ones used by 247 // the code accessing the R class. 248 // int[] does not implement equals/hashCode, and if the parsing used a different class 249 // loader for the R class, this would NOT work. 250 Class<?> r = com.android.internal.R.class; 251 252 for (Class<?> inner : r.getDeclaredClasses()) { 253 String resType = inner.getSimpleName(); 254 255 Map<String, Integer> fullMap = new HashMap<String, Integer>(); 256 sRFullMap.put(resType, fullMap); 257 258 for (Field f : inner.getDeclaredFields()) { 259 // only process static final fields. Since the final attribute may have 260 // been altered by layoutlib_create, we only check static 261 int modifiers = f.getModifiers(); 262 if (Modifier.isStatic(modifiers)) { 263 Class<?> type = f.getType(); 264 if (type.isArray() && type.getComponentType() == int.class) { 265 // if the object is an int[] we put it in sRArrayMap 266 sRArrayMap.put(new IntArray((int[]) f.get(null)), f.getName()); 267 } else if (type == int.class) { 268 Integer value = (Integer) f.get(null); 269 sRMap.put(value, new String[] { f.getName(), resType }); 270 fullMap.put(f.getName(), value); 271 } else { 272 assert false; 273 } 274 } 275 } 276 } 277 } catch (IllegalArgumentException e) { 278 // FIXME: log/return the error (there's no logger object at this point!) 279 e.printStackTrace(); 280 return false; 281 } catch (IllegalAccessException e) { 282 e.printStackTrace(); 283 return false; 284 } 285 286 return true; 287 } 288 289 @Override 290 public boolean dispose() { 291 BridgeAssetManager.clearSystem(); 292 return true; 293 } 294 295 /** 296 * Starts a layout session by inflating and rendering it. The method returns a 297 * {@link ILayoutScene} on which further actions can be taken. 298 * 299 * @param params the {@link SceneParams} object with all the information necessary to create 300 * the scene. 301 * @return a new {@link ILayoutScene} object that contains the result of the layout. 302 * @since 5 303 */ 304 @Override 305 public RenderSession createSession(Params params) { 306 try { 307 Result lastResult = SUCCESS.createResult(); 308 RenderSessionImpl scene = new RenderSessionImpl(params); 309 try { 310 prepareThread(); 311 lastResult = scene.init(params.getTimeout()); 312 if (lastResult.isSuccess()) { 313 lastResult = scene.inflate(); 314 if (lastResult.isSuccess()) { 315 lastResult = scene.render(); 316 } 317 } 318 } finally { 319 scene.release(); 320 cleanupThread(); 321 } 322 323 return new BridgeRenderSession(scene, lastResult); 324 } catch (Throwable t) { 325 // get the real cause of the exception. 326 Throwable t2 = t; 327 while (t2.getCause() != null) { 328 t2 = t.getCause(); 329 } 330 return new BridgeRenderSession(null, 331 ERROR_UNKNOWN.createResult(t2.getMessage(), t2)); 332 } 333 } 334 335 /* 336 * (non-Javadoc) 337 * @see com.android.layoutlib.api.ILayoutLibBridge#clearCaches(java.lang.Object) 338 */ 339 @Override 340 public void clearCaches(Object projectKey) { 341 if (projectKey != null) { 342 sProjectBitmapCache.remove(projectKey); 343 sProject9PatchCache.remove(projectKey); 344 } 345 } 346 347 /** 348 * Returns the lock for the bridge 349 */ 350 public static ReentrantLock getLock() { 351 return sLock; 352 } 353 354 /** 355 * Prepares the current thread for rendering. 356 * 357 * Note that while this can be called several time, the first call to {@link #cleanupThread()} 358 * will do the clean-up, and make the thread unable to do further scene actions. 359 */ 360 public static void prepareThread() { 361 // we need to make sure the Looper has been initialized for this thread. 362 // this is required for View that creates Handler objects. 363 if (Looper.myLooper() == null) { 364 Looper.prepare(); 365 } 366 } 367 368 /** 369 * Cleans up thread-specific data. After this, the thread cannot be used for scene actions. 370 * <p> 371 * Note that it doesn't matter how many times {@link #prepareThread()} was called, a single 372 * call to this will prevent the thread from doing further scene actions 373 */ 374 public static void cleanupThread() { 375 // clean up the looper 376 Looper.sThreadLocal.remove(); 377 } 378 379 public static LayoutLog getLog() { 380 return sCurrentLog; 381 } 382 383 public static void setLog(LayoutLog log) { 384 // check only the thread currently owning the lock can do this. 385 if (sLock.isHeldByCurrentThread() == false) { 386 throw new IllegalStateException("scene must be acquired first. see #acquire(long)"); 387 } 388 389 if (log != null) { 390 sCurrentLog = log; 391 } else { 392 sCurrentLog = sDefaultLog; 393 } 394 } 395 396 /** 397 * Returns details of a framework resource from its integer value. 398 * @param value the integer value 399 * @return an array of 2 strings containing the resource name and type, or null if the id 400 * does not match any resource. 401 */ 402 public static String[] resolveResourceValue(int value) { 403 return sRMap.get(value); 404 } 405 406 /** 407 * Returns the name of a framework resource whose value is an int array. 408 * @param array 409 */ 410 public static String resolveResourceValue(int[] array) { 411 sIntArrayWrapper.set(array); 412 return sRArrayMap.get(sIntArrayWrapper); 413 } 414 415 /** 416 * Returns the integer id of a framework resource, from a given resource type and resource name. 417 * @param type the type of the resource 418 * @param name the name of the resource. 419 * @return an {@link Integer} containing the resource id, or null if no resource were found. 420 */ 421 public static Integer getResourceValue(String type, String name) { 422 Map<String, Integer> map = sRFullMap.get(type); 423 if (map != null) { 424 return map.get(name); 425 } 426 427 return null; 428 } 429 430 /** 431 * Returns the list of possible enums for a given attribute name. 432 */ 433 public static Map<String, Integer> getEnumValues(String attributeName) { 434 if (sEnumValueMap != null) { 435 return sEnumValueMap.get(attributeName); 436 } 437 438 return null; 439 } 440 441 /** 442 * Returns the bitmap for a specific path, from a specific project cache, or from the 443 * framework cache. 444 * @param value the path of the bitmap 445 * @param projectKey the key of the project, or null to query the framework cache. 446 * @return the cached Bitmap or null if not found. 447 */ 448 public static Bitmap getCachedBitmap(String value, Object projectKey) { 449 if (projectKey != null) { 450 Map<String, SoftReference<Bitmap>> map = sProjectBitmapCache.get(projectKey); 451 if (map != null) { 452 SoftReference<Bitmap> ref = map.get(value); 453 if (ref != null) { 454 return ref.get(); 455 } 456 } 457 } else { 458 SoftReference<Bitmap> ref = sFrameworkBitmapCache.get(value); 459 if (ref != null) { 460 return ref.get(); 461 } 462 } 463 464 return null; 465 } 466 467 /** 468 * Sets a bitmap in a project cache or in the framework cache. 469 * @param value the path of the bitmap 470 * @param bmp the Bitmap object 471 * @param projectKey the key of the project, or null to put the bitmap in the framework cache. 472 */ 473 public static void setCachedBitmap(String value, Bitmap bmp, Object projectKey) { 474 if (projectKey != null) { 475 Map<String, SoftReference<Bitmap>> map = sProjectBitmapCache.get(projectKey); 476 477 if (map == null) { 478 map = new HashMap<String, SoftReference<Bitmap>>(); 479 sProjectBitmapCache.put(projectKey, map); 480 } 481 482 map.put(value, new SoftReference<Bitmap>(bmp)); 483 } else { 484 sFrameworkBitmapCache.put(value, new SoftReference<Bitmap>(bmp)); 485 } 486 } 487 488 /** 489 * Returns the 9 patch chunk for a specific path, from a specific project cache, or from the 490 * framework cache. 491 * @param value the path of the 9 patch 492 * @param projectKey the key of the project, or null to query the framework cache. 493 * @return the cached 9 patch or null if not found. 494 */ 495 public static NinePatchChunk getCached9Patch(String value, Object projectKey) { 496 if (projectKey != null) { 497 Map<String, SoftReference<NinePatchChunk>> map = sProject9PatchCache.get(projectKey); 498 499 if (map != null) { 500 SoftReference<NinePatchChunk> ref = map.get(value); 501 if (ref != null) { 502 return ref.get(); 503 } 504 } 505 } else { 506 SoftReference<NinePatchChunk> ref = sFramework9PatchCache.get(value); 507 if (ref != null) { 508 return ref.get(); 509 } 510 } 511 512 return null; 513 } 514 515 /** 516 * Sets a 9 patch chunk in a project cache or in the framework cache. 517 * @param value the path of the 9 patch 518 * @param ninePatch the 9 patch object 519 * @param projectKey the key of the project, or null to put the bitmap in the framework cache. 520 */ 521 public static void setCached9Patch(String value, NinePatchChunk ninePatch, Object projectKey) { 522 if (projectKey != null) { 523 Map<String, SoftReference<NinePatchChunk>> map = sProject9PatchCache.get(projectKey); 524 525 if (map == null) { 526 map = new HashMap<String, SoftReference<NinePatchChunk>>(); 527 sProject9PatchCache.put(projectKey, map); 528 } 529 530 map.put(value, new SoftReference<NinePatchChunk>(ninePatch)); 531 } else { 532 sFramework9PatchCache.put(value, new SoftReference<NinePatchChunk>(ninePatch)); 533 } 534 } 535 536 537} 538