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