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