xref: /aosp_15_r20/frameworks/layoutlib/bridge/src/com/android/layoutlib/bridge/Bridge.java (revision fc3927be90a325f95c74a9043993a80ef388dc46)
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