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 com.android.ide.common.rendering.api.DrawableParams; 20 import com.android.ide.common.rendering.api.ILayoutLog; 21 import com.android.ide.common.rendering.api.RenderSession; 22 import com.android.ide.common.rendering.api.ResourceNamespace; 23 import com.android.ide.common.rendering.api.ResourceReference; 24 import com.android.ide.common.rendering.api.Result; 25 import com.android.ide.common.rendering.api.Result.Status; 26 import com.android.ide.common.rendering.api.SessionParams; 27 import com.android.ide.common.rendering.api.XmlParserFactory; 28 import com.android.layoutlib.bridge.android.RenderParamsFlags; 29 import com.android.layoutlib.bridge.impl.ParserFactory; 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.layoutlib.common.util.ReflectionUtils; 34 import com.android.resources.ResourceType; 35 import com.android.tools.layoutlib.annotations.NonNull; 36 import com.android.tools.layoutlib.annotations.Nullable; 37 import com.android.tools.layoutlib.create.MethodAdapter; 38 import com.android.tools.layoutlib.create.NativeConfig; 39 import com.android.tools.layoutlib.create.OverrideMethod; 40 41 import org.kxml2.io.KXmlParser; 42 import org.xmlpull.v1.XmlPullParser; 43 44 import android.content.res.BridgeAssetManager; 45 import android.graphics.Bitmap; 46 import android.graphics.Typeface; 47 import android.graphics.Typeface_Builder_Delegate; 48 import android.graphics.fonts.SystemFonts_Delegate; 49 import android.icu.util.ULocale; 50 import android.os.Looper; 51 import android.os.Looper_Accessor; 52 import android.os.SystemProperties; 53 import android.util.Pair; 54 import android.view.Gravity; 55 import android.view.View; 56 import android.view.ViewGroup; 57 import android.view.ViewParent; 58 59 import java.io.File; 60 import java.lang.ref.SoftReference; 61 import java.lang.reflect.Constructor; 62 import java.lang.reflect.Field; 63 import java.lang.reflect.InvocationTargetException; 64 import java.lang.reflect.Modifier; 65 import java.util.Arrays; 66 import java.util.EnumMap; 67 import java.util.HashMap; 68 import java.util.Locale; 69 import java.util.Map; 70 import java.util.Map.Entry; 71 import java.util.WeakHashMap; 72 import java.util.concurrent.locks.ReentrantLock; 73 74 import libcore.io.MemoryMappedFile_Delegate; 75 76 import static android.graphics.Typeface.DEFAULT_FAMILY; 77 import static android.graphics.Typeface.RESOLVE_BY_FONT_TABLE; 78 import static com.android.ide.common.rendering.api.Result.Status.ERROR_UNKNOWN; 79 80 /** 81 * Main entry point of the LayoutLib Bridge. 82 * <p/>To use this bridge, simply instantiate an object of type {@link Bridge} and call 83 * {@link #createSession(SessionParams)} 84 */ 85 public final class Bridge extends com.android.ide.common.rendering.api.Bridge { 86 87 private static final String ICU_LOCALE_DIRECTION_RTL = "right-to-left"; 88 89 public static class StaticMethodNotImplementedException extends RuntimeException { 90 private static final long serialVersionUID = 1L; 91 StaticMethodNotImplementedException(String msg)92 public StaticMethodNotImplementedException(String msg) { 93 super(msg); 94 } 95 } 96 97 /** 98 * Lock to ensure only one rendering/inflating happens at a time. 99 * This is due to some singleton in the Android framework. 100 */ 101 private final static ReentrantLock sLock = new ReentrantLock(); 102 103 /** 104 * Maps from id to resource type/name. This is for com.android.internal.R 105 */ 106 private final static Map<Integer, Pair<ResourceType, String>> sRMap = new HashMap<>(); 107 108 /** 109 * Reverse map compared to sRMap, resource type -> (resource name -> id). 110 * This is for com.android.internal.R. 111 */ 112 private final static Map<ResourceType, Map<String, Integer>> sRevRMap = new EnumMap<>(ResourceType.class); 113 114 // framework resources are defined as 0x01XX#### where XX is the resource type (layout, 115 // drawable, etc...). Using FF as the type allows for 255 resource types before we get a 116 // collision which should be fine. 117 private final static int DYNAMIC_ID_SEED_START = 0x01ff0000; 118 private final static DynamicIdMap sDynamicIds = new DynamicIdMap(DYNAMIC_ID_SEED_START); 119 120 private final static Map<Object, Map<String, SoftReference<Bitmap>>> sProjectBitmapCache = 121 new WeakHashMap<>(); 122 123 private final static Map<String, SoftReference<Bitmap>> sFrameworkBitmapCache = new HashMap<>(); 124 125 private static Map<String, Map<String, Integer>> sEnumValueMap; 126 private static Map<String, String> sPlatformProperties; 127 128 /** 129 * A default log than prints to stdout/stderr. 130 */ 131 private final static ILayoutLog sDefaultLog = new ILayoutLog() { 132 @Override 133 public void error(String tag, String message, Object viewCookie, Object data) { 134 System.err.println(message); 135 } 136 137 @Override 138 public void error(String tag, String message, Throwable throwable, Object viewCookie, 139 Object data) { 140 System.err.println(message); 141 } 142 143 @Override 144 public void warning(String tag, String message, Object viewCookie, Object data) { 145 System.out.println(message); 146 } 147 148 @Override 149 public void logAndroidFramework(int priority, String tag, String message) { 150 System.out.println(message); 151 } 152 }; 153 154 /** 155 * Current log. 156 */ 157 private static ILayoutLog sCurrentLog = sDefaultLog; 158 159 private static String sIcuDataPath; 160 161 private static final String[] LINUX_NATIVE_LIBRARIES = {"libandroid_runtime.so"}; 162 private static final String[] MAC_NATIVE_LIBRARIES = {"libandroid_runtime.dylib"}; 163 private static final String[] WINDOWS_NATIVE_LIBRARIES = 164 {"libicuuc_stubdata.dll", "libicuuc-host.dll", "libandroid_runtime.dll"}; 165 166 @Override init(Map<String,String> platformProperties, File fontLocation, String nativeLibPath, String icuDataPath, Map<String, Map<String, Integer>> enumValueMap, ILayoutLog log)167 public boolean init(Map<String,String> platformProperties, 168 File fontLocation, 169 String nativeLibPath, 170 String icuDataPath, 171 Map<String, Map<String, Integer>> enumValueMap, 172 ILayoutLog log) { 173 sPlatformProperties = platformProperties; 174 sEnumValueMap = enumValueMap; 175 sIcuDataPath = icuDataPath; 176 sCurrentLog = log; 177 178 if (!loadNativeLibrariesIfNeeded(log, nativeLibPath)) { 179 return false; 180 } 181 182 // When DEBUG_LAYOUT is set and is not 0 or false, setup a default listener 183 // on static (native) methods which prints the signature on the console and 184 // throws an exception. 185 // This is useful when testing the rendering in ADT to identify static native 186 // methods that are ignored -- layoutlib_create makes them returns 0/false/null 187 // which is generally OK yet might be a problem, so this is how you'd find out. 188 // 189 // Currently layoutlib_create only overrides static native method. 190 // Static non-natives are not overridden and thus do not get here. 191 final String debug = System.getenv("DEBUG_LAYOUT"); 192 if (debug != null && !debug.equals("0") && !debug.equals("false")) { 193 194 OverrideMethod.setDefaultListener(new MethodAdapter() { 195 @Override 196 public void onInvokeV(String signature, boolean isNative, Object caller) { 197 sDefaultLog.error(null, "Missing Stub: " + signature + 198 (isNative ? " (native)" : ""), null, null /*data*/); 199 200 if (debug.equalsIgnoreCase("throw")) { 201 // Throwing this exception doesn't seem that useful. It breaks 202 // the layout editor yet doesn't display anything meaningful to the 203 // user. Having the error in the console is just as useful. We'll 204 // throw it only if the environment variable is "throw" or "THROW". 205 throw new StaticMethodNotImplementedException(signature); 206 } 207 } 208 }); 209 } 210 211 try { 212 BridgeAssetManager.initSystem(); 213 214 // Do the static initialization of all the classes for which it was deferred. 215 // In order to initialize Typeface, we first need to specify the location of fonts 216 // and set a parser factory that will be used to parse the fonts.xml file. 217 SystemFonts_Delegate.setFontLocation(fontLocation.getAbsolutePath() + File.separator); 218 MemoryMappedFile_Delegate.setDataDir(fontLocation.getAbsoluteFile().getParentFile()); 219 ParserFactory.setParserFactory(new XmlParserFactory() { 220 @Override 221 @Nullable 222 public XmlPullParser createXmlParserForPsiFile(@NonNull String fileName) { 223 return null; 224 } 225 226 @Override 227 @Nullable 228 public XmlPullParser createXmlParserForFile(@NonNull String fileName) { 229 return null; 230 } 231 232 @Override 233 @NonNull 234 public XmlPullParser createXmlParser() { 235 return new KXmlParser(); 236 } 237 }); 238 for (String deferredClass : NativeConfig.DEFERRED_STATIC_INITIALIZER_CLASSES) { 239 ReflectionUtils.invokeStatic(deferredClass, "deferredStaticInitializer"); 240 } 241 // Load system fonts now that Typeface has been initialized 242 Typeface.loadPreinstalledSystemFontMap(); 243 ParserFactory.setParserFactory(null); 244 } catch (Throwable t) { 245 if (log != null) { 246 log.error(ILayoutLog.TAG_BROKEN, "Layoutlib Bridge initialization failed", t, 247 null, null); 248 } 249 return false; 250 } 251 252 // now parse com.android.internal.R (and only this one as android.R is a subset of 253 // the internal version), and put the content in the maps. 254 try { 255 Class<?> r = com.android.internal.R.class; 256 // Parse the styleable class first, since it may contribute to attr values. 257 parseStyleable(); 258 259 for (Class<?> inner : r.getDeclaredClasses()) { 260 if (inner == com.android.internal.R.styleable.class) { 261 // Already handled the styleable case. Not skipping attr, as there may be attrs 262 // that are not referenced from styleables. 263 continue; 264 } 265 String resTypeName = inner.getSimpleName(); 266 ResourceType resType = ResourceType.fromClassName(resTypeName); 267 if (resType != null) { 268 Map<String, Integer> fullMap = null; 269 switch (resType) { 270 case ATTR: 271 fullMap = sRevRMap.get(ResourceType.ATTR); 272 break; 273 case STRING: 274 case STYLE: 275 // Slightly less than thousand entries in each. 276 fullMap = new HashMap<>(1280); 277 // no break. 278 default: 279 if (fullMap == null) { 280 fullMap = new HashMap<>(); 281 } 282 sRevRMap.put(resType, fullMap); 283 } 284 285 for (Field f : inner.getDeclaredFields()) { 286 // only process static final fields. Since the final attribute may have 287 // been altered by layoutlib_create, we only check static 288 if (!isValidRField(f)) { 289 continue; 290 } 291 Class<?> type = f.getType(); 292 if (!type.isArray()) { 293 Integer value = (Integer) f.get(null); 294 sRMap.put(value, Pair.create(resType, f.getName())); 295 fullMap.put(f.getName(), value); 296 } 297 } 298 } 299 } 300 } catch (Exception throwable) { 301 if (log != null) { 302 log.error(ILayoutLog.TAG_BROKEN, 303 "Failed to load com.android.internal.R from the layout library jar", 304 throwable, null, null); 305 } 306 return false; 307 } 308 309 return true; 310 } 311 312 /** 313 * Sets System properties using the Android framework code. 314 * This is accessed by the native libraries through JNI. 315 */ 316 @SuppressWarnings("unused") setSystemProperties()317 private static void setSystemProperties() { 318 for (Entry<String, String> property : sPlatformProperties.entrySet()) { 319 SystemProperties.set(property.getKey(), property.getValue()); 320 } 321 } 322 323 /** 324 * Tests if the field is pubic, static and one of int or int[]. 325 */ isValidRField(Field field)326 private static boolean isValidRField(Field field) { 327 int modifiers = field.getModifiers(); 328 boolean isAcceptable = Modifier.isPublic(modifiers) && Modifier.isStatic(modifiers); 329 Class<?> type = field.getType(); 330 return isAcceptable && type == int.class || 331 (type.isArray() && type.getComponentType() == int.class); 332 333 } 334 parseStyleable()335 private static void parseStyleable() throws Exception { 336 // R.attr doesn't contain all the needed values. There are too many resources in the 337 // framework for all to be in the R class. Only the ones specified manually in 338 // res/values/symbols.xml are put in R class. Since, we need to create a map of all attr 339 // values, we try and find them from the styleables. 340 341 // There were 1500 elements in this map at M timeframe. 342 Map<String, Integer> revRAttrMap = new HashMap<>(2048); 343 sRevRMap.put(ResourceType.ATTR, revRAttrMap); 344 // There were 2000 elements in this map at M timeframe. 345 Map<String, Integer> revRStyleableMap = new HashMap<>(3072); 346 sRevRMap.put(ResourceType.STYLEABLE, revRStyleableMap); 347 Class<?> c = com.android.internal.R.styleable.class; 348 Field[] fields = c.getDeclaredFields(); 349 // Sort the fields to bring all arrays to the beginning, so that indices into the array are 350 // able to refer back to the arrays (i.e. no forward references). 351 Arrays.sort(fields, (o1, o2) -> { 352 if (o1 == o2) { 353 return 0; 354 } 355 Class<?> t1 = o1.getType(); 356 Class<?> t2 = o2.getType(); 357 if (t1.isArray() && !t2.isArray()) { 358 return -1; 359 } else if (t2.isArray() && !t1.isArray()) { 360 return 1; 361 } 362 return o1.getName().compareTo(o2.getName()); 363 }); 364 Map<String, int[]> styleables = new HashMap<>(); 365 for (Field field : fields) { 366 if (!isValidRField(field)) { 367 // Only consider public static fields that are int or int[]. 368 // Don't check the final flag as it may have been modified by layoutlib_create. 369 continue; 370 } 371 String name = field.getName(); 372 if (field.getType().isArray()) { 373 int[] styleableValue = (int[]) field.get(null); 374 styleables.put(name, styleableValue); 375 continue; 376 } 377 // Not an array. 378 String arrayName = name; 379 int[] arrayValue = null; 380 int index; 381 while ((index = arrayName.lastIndexOf('_')) >= 0) { 382 // Find the name of the corresponding styleable. 383 // Search in reverse order so that attrs like LinearLayout_Layout_layout_gravity 384 // are mapped to LinearLayout_Layout and not to LinearLayout. 385 arrayName = arrayName.substring(0, index); 386 arrayValue = styleables.get(arrayName); 387 if (arrayValue != null) { 388 break; 389 } 390 } 391 index = (Integer) field.get(null); 392 if (arrayValue != null) { 393 String attrName = name.substring(arrayName.length() + 1); 394 int attrValue = arrayValue[index]; 395 sRMap.put(attrValue, Pair.create(ResourceType.ATTR, attrName)); 396 revRAttrMap.put(attrName, attrValue); 397 } 398 sRMap.put(index, Pair.create(ResourceType.STYLEABLE, name)); 399 revRStyleableMap.put(name, index); 400 } 401 } 402 403 @Override dispose()404 public boolean dispose() { 405 BridgeAssetManager.clearSystem(); 406 407 // dispose of the default typeface. 408 if (SystemFonts_Delegate.sIsTypefaceInitialized) { 409 Typeface.sDynamicTypefaceCache.evictAll(); 410 } 411 sProjectBitmapCache.clear(); 412 413 return true; 414 } 415 416 /** 417 * Starts a layout session by inflating and rendering it. The method returns a 418 * {@link RenderSession} on which further actions can be taken. 419 * <p/> 420 * If {@link SessionParams} includes the {@link RenderParamsFlags#FLAG_DO_NOT_RENDER_ON_CREATE}, 421 * this method will only inflate the layout but will NOT render it. 422 * @param params the {@link SessionParams} object with all the information necessary to create 423 * the scene. 424 * @return a new {@link RenderSession} object that contains the result of the layout. 425 * @since 5 426 */ 427 @Override createSession(SessionParams params)428 public RenderSession createSession(SessionParams params) { 429 try { 430 Result lastResult; 431 RenderSessionImpl scene = new RenderSessionImpl(params); 432 try { 433 prepareThread(); 434 lastResult = scene.init(params.getTimeout()); 435 if (lastResult.isSuccess()) { 436 lastResult = scene.inflate(); 437 438 boolean doNotRenderOnCreate = Boolean.TRUE.equals( 439 params.getFlag(RenderParamsFlags.FLAG_DO_NOT_RENDER_ON_CREATE)); 440 if (lastResult.isSuccess() && !doNotRenderOnCreate) { 441 lastResult = scene.render(true /*freshRender*/); 442 } 443 } 444 } finally { 445 scene.release(); 446 cleanupThread(); 447 } 448 449 return new BridgeRenderSession(scene, lastResult); 450 } catch (Throwable t) { 451 // get the real cause of the exception. 452 Throwable t2 = t; 453 while (t2.getCause() != null) { 454 t2 = t2.getCause(); 455 } 456 return new BridgeRenderSession(null, 457 ERROR_UNKNOWN.createResult(t2.getMessage(), t)); 458 } 459 } 460 461 @Override renderDrawable(DrawableParams params)462 public Result renderDrawable(DrawableParams params) { 463 try { 464 Result lastResult; 465 RenderDrawable action = new RenderDrawable(params); 466 try { 467 prepareThread(); 468 lastResult = action.init(params.getTimeout()); 469 if (lastResult.isSuccess()) { 470 lastResult = action.render(); 471 } 472 } finally { 473 action.release(); 474 cleanupThread(); 475 } 476 477 return lastResult; 478 } catch (Throwable t) { 479 // get the real cause of the exception. 480 Throwable t2 = t; 481 while (t2.getCause() != null) { 482 t2 = t.getCause(); 483 } 484 return ERROR_UNKNOWN.createResult(t2.getMessage(), t); 485 } 486 } 487 488 @Override clearResourceCaches(Object projectKey)489 public void clearResourceCaches(Object projectKey) { 490 if (projectKey != null) { 491 sProjectBitmapCache.remove(projectKey); 492 } 493 } 494 495 @Override clearAllCaches(Object projectKey)496 public void clearAllCaches(Object projectKey) { 497 clearResourceCaches(projectKey); 498 } 499 500 @Override getViewParent(Object viewObject)501 public Result getViewParent(Object viewObject) { 502 if (viewObject instanceof View) { 503 return Status.SUCCESS.createResult(((View)viewObject).getParent()); 504 } 505 506 throw new IllegalArgumentException("viewObject is not a View"); 507 } 508 509 @Override getViewIndex(Object viewObject)510 public Result getViewIndex(Object viewObject) { 511 if (viewObject instanceof View) { 512 View view = (View) viewObject; 513 ViewParent parentView = view.getParent(); 514 515 if (parentView instanceof ViewGroup) { 516 Status.SUCCESS.createResult(((ViewGroup) parentView).indexOfChild(view)); 517 } 518 519 return Status.SUCCESS.createResult(); 520 } 521 522 throw new IllegalArgumentException("viewObject is not a View"); 523 } 524 525 @Override isRtl(String locale)526 public boolean isRtl(String locale) { 527 return isLocaleRtl(locale); 528 } 529 isLocaleRtl(String locale)530 public static boolean isLocaleRtl(String locale) { 531 if (locale == null) { 532 locale = ""; 533 } 534 ULocale uLocale = new ULocale(locale); 535 return uLocale.getCharacterOrientation().equals(ICU_LOCALE_DIRECTION_RTL); 536 } 537 538 /** 539 * Returns the lock for the bridge 540 */ getLock()541 public static ReentrantLock getLock() { 542 return sLock; 543 } 544 545 /** 546 * Prepares the current thread for rendering. 547 * 548 * Note that while this can be called several time, the first call to {@link #cleanupThread()} 549 * will do the clean-up, and make the thread unable to do further scene actions. 550 */ prepareThread()551 public synchronized static void prepareThread() { 552 // We need to make sure the Looper has been initialized for this thread. 553 // This is required for View that creates Handler objects. 554 if (Looper.myLooper() == null) { 555 synchronized (Looper.class) { 556 // Check if the main looper has been prepared already. 557 if (Looper.getMainLooper() == null) { 558 Looper.prepareMainLooper(); 559 } 560 } 561 } 562 } 563 564 /** 565 * Cleans up thread-specific data. After this, the thread cannot be used for scene actions. 566 * <p> 567 * Note that it doesn't matter how many times {@link #prepareThread()} was called, a single 568 * call to this will prevent the thread from doing further scene actions 569 */ cleanupThread()570 public synchronized static void cleanupThread() { 571 // clean up the looper 572 Looper_Accessor.cleanupThread(); 573 } 574 getLog()575 public static ILayoutLog getLog() { 576 return sCurrentLog; 577 } 578 setLog(ILayoutLog log)579 public static void setLog(ILayoutLog log) { 580 // check only the thread currently owning the lock can do this. 581 if (!sLock.isHeldByCurrentThread()) { 582 throw new IllegalStateException("scene must be acquired first. see #acquire(long)"); 583 } 584 585 if (log != null) { 586 sCurrentLog = log; 587 } else { 588 sCurrentLog = sDefaultLog; 589 } 590 } 591 592 /** 593 * Returns details of a framework resource from its integer value. 594 * 595 * <p>TODO(b/156609434): remove this and just do all id resolution through the callback. 596 */ 597 @Nullable resolveResourceId(int value)598 public static ResourceReference resolveResourceId(int value) { 599 Pair<ResourceType, String> pair = sRMap.get(value); 600 if (pair == null) { 601 pair = sDynamicIds.resolveId(value); 602 } 603 604 if (pair != null) { 605 return new ResourceReference(ResourceNamespace.ANDROID, pair.first, pair.second); 606 } 607 return null; 608 } 609 610 /** 611 * Returns the integer id of a framework resource, from a given resource type and resource name. 612 * <p/> 613 * If no resource is found, it creates a dynamic id for the resource. 614 * 615 * @param type the type of the resource 616 * @param name the name of the resource. 617 * @return an int containing the resource id. 618 */ getResourceId(ResourceType type, String name)619 public static int getResourceId(ResourceType type, String name) { 620 Map<String, Integer> map = sRevRMap.get(type); 621 Integer value = map == null ? null : map.get(name); 622 return value == null ? sDynamicIds.getId(type, name) : value; 623 } 624 625 /** 626 * Returns the list of possible enums for a given attribute name. 627 */ 628 @Nullable getEnumValues(String attributeName)629 public static Map<String, Integer> getEnumValues(String attributeName) { 630 if (sEnumValueMap != null) { 631 return sEnumValueMap.get(attributeName); 632 } 633 634 return null; 635 } 636 637 /** 638 * Returns the bitmap for a specific path, from a specific project cache, or from the 639 * framework cache. 640 * @param value the path of the bitmap 641 * @param projectKey the key of the project, or null to query the framework cache. 642 * @return the cached Bitmap or null if not found. 643 */ getCachedBitmap(String value, Object projectKey)644 public static Bitmap getCachedBitmap(String value, Object projectKey) { 645 if (projectKey != null) { 646 Map<String, SoftReference<Bitmap>> map = sProjectBitmapCache.get(projectKey); 647 if (map != null) { 648 SoftReference<Bitmap> ref = map.get(value); 649 if (ref != null) { 650 return ref.get(); 651 } 652 } 653 } else { 654 SoftReference<Bitmap> ref = sFrameworkBitmapCache.get(value); 655 if (ref != null) { 656 return ref.get(); 657 } 658 } 659 660 return null; 661 } 662 663 /** 664 * Sets a bitmap in a project cache or in the framework cache. 665 * @param value the path of the bitmap 666 * @param bmp the Bitmap object 667 * @param projectKey the key of the project, or null to put the bitmap in the framework cache. 668 */ setCachedBitmap(String value, Bitmap bmp, Object projectKey)669 public static void setCachedBitmap(String value, Bitmap bmp, Object projectKey) { 670 if (projectKey != null) { 671 Map<String, SoftReference<Bitmap>> map = 672 sProjectBitmapCache.computeIfAbsent(projectKey, k -> new HashMap<>()); 673 674 map.put(value, new SoftReference<>(bmp)); 675 } else { 676 sFrameworkBitmapCache.put(value, new SoftReference<>(bmp)); 677 } 678 } 679 680 /** 681 * This is called by the native layoutlib loader. 682 */ 683 @SuppressWarnings("unused") getIcuDataPath()684 public static String getIcuDataPath() { 685 return sIcuDataPath; 686 } 687 688 private static boolean sJniLibLoadAttempted; 689 private static boolean sJniLibLoaded; 690 loadNativeLibrariesIfNeeded(ILayoutLog log, String nativeLibDir)691 private synchronized static boolean loadNativeLibrariesIfNeeded(ILayoutLog log, 692 String nativeLibDir) { 693 if (!sJniLibLoadAttempted) { 694 try { 695 loadNativeLibraries(nativeLibDir); 696 } 697 catch (Throwable t) { 698 log.error(ILayoutLog.TAG_BROKEN, "Native layoutlib failed to load", t, null, null); 699 } 700 } 701 return sJniLibLoaded; 702 } 703 loadNativeLibraries(String nativeLibDir)704 private synchronized static void loadNativeLibraries(String nativeLibDir) { 705 if (sJniLibLoadAttempted) { 706 // Already attempted to load, nothing to do here. 707 return; 708 } 709 try { 710 // set the system property so LayoutLibLoader.cpp can read it 711 System.setProperty("core_native_classes", String.join(",", 712 NativeConfig.CORE_CLASS_NATIVES)); 713 System.setProperty("graphics_native_classes", String.join(",", 714 NativeConfig.GRAPHICS_CLASS_NATIVES)); 715 System.setProperty("icu.data.path", Bridge.getIcuDataPath()); 716 System.setProperty("use_bridge_for_logging", "true"); 717 System.setProperty("register_properties_during_load", "true"); 718 for (String library : getNativeLibraries()) { 719 String path = new File(nativeLibDir, library).getAbsolutePath(); 720 System.load(path); 721 } 722 } 723 finally { 724 sJniLibLoadAttempted = true; 725 } 726 sJniLibLoaded = true; 727 } 728 getNativeLibraries()729 private static String[] getNativeLibraries() { 730 String osName = System.getProperty("os.name").toLowerCase(Locale.US); 731 if (osName.startsWith("windows")) { 732 return WINDOWS_NATIVE_LIBRARIES; 733 } 734 if (osName.startsWith("mac")) { 735 return MAC_NATIVE_LIBRARIES; 736 } 737 return LINUX_NATIVE_LIBRARIES; 738 } 739 740 @Override clearFontCache(String path)741 public void clearFontCache(String path) { 742 if (SystemFonts_Delegate.sIsTypefaceInitialized) { 743 final String key = 744 Typeface_Builder_Delegate.createAssetUid(BridgeAssetManager.initSystem(), path, 745 0, null, RESOLVE_BY_FONT_TABLE, RESOLVE_BY_FONT_TABLE, DEFAULT_FAMILY); 746 Typeface.sDynamicTypefaceCache.remove(key); 747 } 748 } 749 750 @Override createMockView(String label, Class<?>[] signature, Object[] args)751 public Object createMockView(String label, Class<?>[] signature, Object[] args) 752 throws NoSuchMethodException, InstantiationException, IllegalAccessException, 753 InvocationTargetException { 754 Constructor<MockView> constructor = MockView.class.getConstructor(signature); 755 MockView mockView = constructor.newInstance(args); 756 mockView.setText(label); 757 mockView.setGravity(Gravity.CENTER); 758 return mockView; 759 } 760 } 761