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