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