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