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