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