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