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