• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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