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