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