• 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 static com.android.ide.common.rendering.api.Result.Status.ERROR_UNKNOWN;
20 import static com.android.ide.common.rendering.api.Result.Status.SUCCESS;
21 
22 import com.android.ide.common.rendering.api.Capability;
23 import com.android.ide.common.rendering.api.DrawableParams;
24 import com.android.ide.common.rendering.api.LayoutLog;
25 import com.android.ide.common.rendering.api.RenderSession;
26 import com.android.ide.common.rendering.api.Result;
27 import com.android.ide.common.rendering.api.SessionParams;
28 import com.android.ide.common.rendering.api.Result.Status;
29 import com.android.layoutlib.bridge.android.BridgeAssetManager;
30 import com.android.layoutlib.bridge.impl.FontLoader;
31 import com.android.layoutlib.bridge.impl.RenderDrawable;
32 import com.android.layoutlib.bridge.impl.RenderSessionImpl;
33 import com.android.ninepatch.NinePatchChunk;
34 import com.android.resources.ResourceType;
35 import com.android.tools.layoutlib.create.MethodAdapter;
36 import com.android.tools.layoutlib.create.OverrideMethod;
37 import com.android.util.Pair;
38 
39 import android.graphics.Bitmap;
40 import android.graphics.Typeface;
41 import android.graphics.Typeface_Delegate;
42 import android.os.Looper;
43 import android.view.View;
44 import android.view.ViewGroup;
45 import android.view.ViewParent;
46 
47 import java.io.File;
48 import java.lang.ref.SoftReference;
49 import java.lang.reflect.Field;
50 import java.lang.reflect.Modifier;
51 import java.util.Arrays;
52 import java.util.EnumMap;
53 import java.util.EnumSet;
54 import java.util.HashMap;
55 import java.util.Map;
56 import java.util.concurrent.locks.ReentrantLock;
57 
58 /**
59  * Main entry point of the LayoutLib Bridge.
60  * <p/>To use this bridge, simply instantiate an object of type {@link Bridge} and call
61  * {@link #createScene(SceneParams)}
62  */
63 public final class Bridge extends com.android.ide.common.rendering.api.Bridge {
64 
65     public static class StaticMethodNotImplementedException extends RuntimeException {
66         private static final long serialVersionUID = 1L;
67 
StaticMethodNotImplementedException(String msg)68         public StaticMethodNotImplementedException(String msg) {
69             super(msg);
70         }
71     }
72 
73     /**
74      * Lock to ensure only one rendering/inflating happens at a time.
75      * This is due to some singleton in the Android framework.
76      */
77     private final static ReentrantLock sLock = new ReentrantLock();
78 
79     /**
80      * Maps from id to resource type/name. This is for android.R only.
81      */
82     private final static Map<Integer, Pair<ResourceType, String>> sRMap =
83         new HashMap<Integer, Pair<ResourceType, String>>();
84 
85     /**
86      * Same as sRMap except for int[] instead of int resources. This is for android.R only.
87      */
88     private final static Map<IntArray, String> sRArrayMap = new HashMap<IntArray, String>();
89     /**
90      * Reverse map compared to sRMap, resource type -> (resource name -> id).
91      * This is for android.R only.
92      */
93     private final static Map<ResourceType, Map<String, Integer>> sRFullMap =
94         new EnumMap<ResourceType, Map<String,Integer>>(ResourceType.class);
95 
96     private final static Map<Object, Map<String, SoftReference<Bitmap>>> sProjectBitmapCache =
97         new HashMap<Object, Map<String, SoftReference<Bitmap>>>();
98     private final static Map<Object, Map<String, SoftReference<NinePatchChunk>>> sProject9PatchCache =
99         new HashMap<Object, Map<String, SoftReference<NinePatchChunk>>>();
100 
101     private final static Map<String, SoftReference<Bitmap>> sFrameworkBitmapCache =
102         new HashMap<String, SoftReference<Bitmap>>();
103     private final static Map<String, SoftReference<NinePatchChunk>> sFramework9PatchCache =
104         new HashMap<String, SoftReference<NinePatchChunk>>();
105 
106     private static Map<String, Map<String, Integer>> sEnumValueMap;
107     private static Map<String, String> sPlatformProperties;
108 
109     /**
110      * int[] wrapper to use as keys in maps.
111      */
112     private final static class IntArray {
113         private int[] mArray;
114 
IntArray()115         private IntArray() {
116             // do nothing
117         }
118 
IntArray(int[] a)119         private IntArray(int[] a) {
120             mArray = a;
121         }
122 
set(int[] a)123         private void set(int[] a) {
124             mArray = a;
125         }
126 
127         @Override
hashCode()128         public int hashCode() {
129             return Arrays.hashCode(mArray);
130         }
131 
132         @Override
equals(Object obj)133         public boolean equals(Object obj) {
134             if (this == obj) return true;
135             if (obj == null) return false;
136             if (getClass() != obj.getClass()) return false;
137 
138             IntArray other = (IntArray) obj;
139             if (!Arrays.equals(mArray, other.mArray)) return false;
140             return true;
141         }
142     }
143 
144     /** Instance of IntArrayWrapper to be reused in {@link #resolveResourceId(int[])}. */
145     private final static IntArray sIntArrayWrapper = new IntArray();
146 
147     /**
148      * A default log than prints to stdout/stderr.
149      */
150     private final static LayoutLog sDefaultLog = new LayoutLog() {
151         @Override
152         public void error(String tag, String message, Object data) {
153             System.err.println(message);
154         }
155 
156         @Override
157         public void error(String tag, String message, Throwable throwable, Object data) {
158             System.err.println(message);
159         }
160 
161         @Override
162         public void warning(String tag, String message, Object data) {
163             System.out.println(message);
164         }
165     };
166 
167     /**
168      * Current log.
169      */
170     private static LayoutLog sCurrentLog = sDefaultLog;
171 
172     private EnumSet<Capability> mCapabilities;
173 
174     @Override
getApiLevel()175     public int getApiLevel() {
176         return com.android.ide.common.rendering.api.Bridge.API_CURRENT;
177     }
178 
179     @Override
getCapabilities()180     public EnumSet<Capability> getCapabilities() {
181         return mCapabilities;
182     }
183 
184     @Override
init(Map<String,String> platformProperties, File fontLocation, Map<String, Map<String, Integer>> enumValueMap, LayoutLog log)185     public boolean init(Map<String,String> platformProperties,
186             File fontLocation,
187             Map<String, Map<String, Integer>> enumValueMap,
188             LayoutLog log) {
189         sPlatformProperties = platformProperties;
190         sEnumValueMap = enumValueMap;
191 
192         // don't use EnumSet.allOf(), because the bridge doesn't come with its specific version
193         // of layoutlib_api. It is provided by the client which could have a more recent version
194         // with newer, unsupported capabilities.
195         mCapabilities = EnumSet.of(
196                 Capability.UNBOUND_RENDERING,
197                 Capability.CUSTOM_BACKGROUND_COLOR,
198                 Capability.RENDER,
199                 Capability.LAYOUT_ONLY,
200                 Capability.EMBEDDED_LAYOUT,
201                 Capability.VIEW_MANIPULATION,
202                 Capability.ADAPTER_BINDING,
203                 Capability.EXTENDED_VIEWINFO);
204 
205         BridgeAssetManager.initSystem();
206 
207         // When DEBUG_LAYOUT is set and is not 0 or false, setup a default listener
208         // on static (native) methods which prints the signature on the console and
209         // throws an exception.
210         // This is useful when testing the rendering in ADT to identify static native
211         // methods that are ignored -- layoutlib_create makes them returns 0/false/null
212         // which is generally OK yet might be a problem, so this is how you'd find out.
213         //
214         // Currently layoutlib_create only overrides static native method.
215         // Static non-natives are not overridden and thus do not get here.
216         final String debug = System.getenv("DEBUG_LAYOUT");
217         if (debug != null && !debug.equals("0") && !debug.equals("false")) {
218 
219             OverrideMethod.setDefaultListener(new MethodAdapter() {
220                 @Override
221                 public void onInvokeV(String signature, boolean isNative, Object caller) {
222                     sDefaultLog.error(null, "Missing Stub: " + signature +
223                             (isNative ? " (native)" : ""), null /*data*/);
224 
225                     if (debug.equalsIgnoreCase("throw")) {
226                         // Throwing this exception doesn't seem that useful. It breaks
227                         // the layout editor yet doesn't display anything meaningful to the
228                         // user. Having the error in the console is just as useful. We'll
229                         // throw it only if the environment variable is "throw" or "THROW".
230                         throw new StaticMethodNotImplementedException(signature);
231                     }
232                 }
233             });
234         }
235 
236         // load the fonts.
237         FontLoader fontLoader = FontLoader.create(fontLocation.getAbsolutePath());
238         if (fontLoader != null) {
239             Typeface_Delegate.init(fontLoader);
240         } else {
241             return false;
242         }
243 
244         // now parse com.android.internal.R (and only this one as android.R is a subset of
245         // the internal version), and put the content in the maps.
246         try {
247             Class<?> r = com.android.internal.R.class;
248 
249             for (Class<?> inner : r.getDeclaredClasses()) {
250                 String resTypeName = inner.getSimpleName();
251                 ResourceType resType = ResourceType.getEnum(resTypeName);
252                 if (resType != null) {
253                     Map<String, Integer> fullMap = new HashMap<String, Integer>();
254                     sRFullMap.put(resType, fullMap);
255 
256                     for (Field f : inner.getDeclaredFields()) {
257                         // only process static final fields. Since the final attribute may have
258                         // been altered by layoutlib_create, we only check static
259                         int modifiers = f.getModifiers();
260                         if (Modifier.isStatic(modifiers)) {
261                             Class<?> type = f.getType();
262                             if (type.isArray() && type.getComponentType() == int.class) {
263                                 // if the object is an int[] we put it in sRArrayMap using an IntArray
264                                 // wrapper that properly implements equals and hashcode for the array
265                                 // objects, as required by the map contract.
266                                 sRArrayMap.put(new IntArray((int[]) f.get(null)), f.getName());
267                             } else if (type == int.class) {
268                                 Integer value = (Integer) f.get(null);
269                                 sRMap.put(value, Pair.of(resType, f.getName()));
270                                 fullMap.put(f.getName(), value);
271                             } else {
272                                 assert false;
273                             }
274                         }
275                     }
276                 }
277             }
278         } catch (Throwable throwable) {
279             if (log != null) {
280                 log.error(LayoutLog.TAG_BROKEN,
281                         "Failed to load com.android.internal.R from the layout library jar",
282                         throwable);
283             }
284             return false;
285         }
286 
287         return true;
288     }
289 
290     @Override
dispose()291     public boolean dispose() {
292         BridgeAssetManager.clearSystem();
293 
294         // dispose of the default typeface.
295         Typeface.sDefaults = null;
296 
297         return true;
298     }
299 
300     /**
301      * Starts a layout session by inflating and rendering it. The method returns a
302      * {@link RenderSession} on which further actions can be taken.
303      *
304      * @param params the {@link SessionParams} object with all the information necessary to create
305      *           the scene.
306      * @return a new {@link RenderSession} object that contains the result of the layout.
307      * @since 5
308      */
309     @Override
createSession(SessionParams params)310     public RenderSession createSession(SessionParams params) {
311         try {
312             Result lastResult = SUCCESS.createResult();
313             RenderSessionImpl scene = new RenderSessionImpl(params);
314             try {
315                 prepareThread();
316                 lastResult = scene.init(params.getTimeout());
317                 if (lastResult.isSuccess()) {
318                     lastResult = scene.inflate();
319                     if (lastResult.isSuccess()) {
320                         lastResult = scene.render(true /*freshRender*/);
321                     }
322                 }
323             } finally {
324                 scene.release();
325                 cleanupThread();
326             }
327 
328             return new BridgeRenderSession(scene, lastResult);
329         } catch (Throwable t) {
330             // get the real cause of the exception.
331             Throwable t2 = t;
332             while (t2.getCause() != null) {
333                 t2 = t.getCause();
334             }
335             return new BridgeRenderSession(null,
336                     ERROR_UNKNOWN.createResult(t2.getMessage(), t));
337         }
338     }
339 
340     @Override
renderDrawable(DrawableParams params)341     public Result renderDrawable(DrawableParams params) {
342         try {
343             Result lastResult = SUCCESS.createResult();
344             RenderDrawable action = new RenderDrawable(params);
345             try {
346                 prepareThread();
347                 lastResult = action.init(params.getTimeout());
348                 if (lastResult.isSuccess()) {
349                     lastResult = action.render();
350                 }
351             } finally {
352                 action.release();
353                 cleanupThread();
354             }
355 
356             return lastResult;
357         } catch (Throwable t) {
358             // get the real cause of the exception.
359             Throwable t2 = t;
360             while (t2.getCause() != null) {
361                 t2 = t.getCause();
362             }
363             return ERROR_UNKNOWN.createResult(t2.getMessage(), t);
364         }
365     }
366 
367     @Override
clearCaches(Object projectKey)368     public void clearCaches(Object projectKey) {
369         if (projectKey != null) {
370             sProjectBitmapCache.remove(projectKey);
371             sProject9PatchCache.remove(projectKey);
372         }
373     }
374 
375     @Override
getViewParent(Object viewObject)376     public Result getViewParent(Object viewObject) {
377         if (viewObject instanceof View) {
378             return Status.SUCCESS.createResult(((View)viewObject).getParent());
379         }
380 
381         throw new IllegalArgumentException("viewObject is not a View");
382     }
383 
384     @Override
getViewIndex(Object viewObject)385     public Result getViewIndex(Object viewObject) {
386         if (viewObject instanceof View) {
387             View view = (View) viewObject;
388             ViewParent parentView = view.getParent();
389 
390             if (parentView instanceof ViewGroup) {
391                 Status.SUCCESS.createResult(((ViewGroup) parentView).indexOfChild(view));
392             }
393 
394             return Status.SUCCESS.createResult();
395         }
396 
397         throw new IllegalArgumentException("viewObject is not a View");
398     }
399 
400     /**
401      * Returns the lock for the bridge
402      */
getLock()403     public static ReentrantLock getLock() {
404         return sLock;
405     }
406 
407     /**
408      * Prepares the current thread for rendering.
409      *
410      * Note that while this can be called several time, the first call to {@link #cleanupThread()}
411      * will do the clean-up, and make the thread unable to do further scene actions.
412      */
prepareThread()413     public static void prepareThread() {
414         // we need to make sure the Looper has been initialized for this thread.
415         // this is required for View that creates Handler objects.
416         if (Looper.myLooper() == null) {
417             Looper.prepare();
418         }
419     }
420 
421     /**
422      * Cleans up thread-specific data. After this, the thread cannot be used for scene actions.
423      * <p>
424      * Note that it doesn't matter how many times {@link #prepareThread()} was called, a single
425      * call to this will prevent the thread from doing further scene actions
426      */
cleanupThread()427     public static void cleanupThread() {
428         // clean up the looper
429         Looper.sThreadLocal.remove();
430     }
431 
getLog()432     public static LayoutLog getLog() {
433         return sCurrentLog;
434     }
435 
setLog(LayoutLog log)436     public static void setLog(LayoutLog log) {
437         // check only the thread currently owning the lock can do this.
438         if (sLock.isHeldByCurrentThread() == false) {
439             throw new IllegalStateException("scene must be acquired first. see #acquire(long)");
440         }
441 
442         if (log != null) {
443             sCurrentLog = log;
444         } else {
445             sCurrentLog = sDefaultLog;
446         }
447     }
448 
449     /**
450      * Returns details of a framework resource from its integer value.
451      * @param value the integer value
452      * @return a Pair containing the resource type and name, or null if the id
453      *     does not match any resource.
454      */
resolveResourceId(int value)455     public static Pair<ResourceType, String> resolveResourceId(int value) {
456         return sRMap.get(value);
457     }
458 
459     /**
460      * Returns the name of a framework resource whose value is an int array.
461      * @param array
462      */
resolveResourceId(int[] array)463     public static String resolveResourceId(int[] array) {
464         sIntArrayWrapper.set(array);
465         return sRArrayMap.get(sIntArrayWrapper);
466     }
467 
468     /**
469      * Returns the integer id of a framework resource, from a given resource type and resource name.
470      * @param type the type of the resource
471      * @param name the name of the resource.
472      * @return an {@link Integer} containing the resource id, or null if no resource were found.
473      */
getResourceId(ResourceType type, String name)474     public static Integer getResourceId(ResourceType type, String name) {
475         Map<String, Integer> map = sRFullMap.get(type);
476         if (map != null) {
477             return map.get(name);
478         }
479 
480         return null;
481     }
482 
483     /**
484      * Returns the list of possible enums for a given attribute name.
485      */
getEnumValues(String attributeName)486     public static Map<String, Integer> getEnumValues(String attributeName) {
487         if (sEnumValueMap != null) {
488             return sEnumValueMap.get(attributeName);
489         }
490 
491         return null;
492     }
493 
494     /**
495      * Returns the platform build properties.
496      */
getPlatformProperties()497     public static Map<String, String> getPlatformProperties() {
498         return sPlatformProperties;
499     }
500 
501     /**
502      * Returns the bitmap for a specific path, from a specific project cache, or from the
503      * framework cache.
504      * @param value the path of the bitmap
505      * @param projectKey the key of the project, or null to query the framework cache.
506      * @return the cached Bitmap or null if not found.
507      */
getCachedBitmap(String value, Object projectKey)508     public static Bitmap getCachedBitmap(String value, Object projectKey) {
509         if (projectKey != null) {
510             Map<String, SoftReference<Bitmap>> map = sProjectBitmapCache.get(projectKey);
511             if (map != null) {
512                 SoftReference<Bitmap> ref = map.get(value);
513                 if (ref != null) {
514                     return ref.get();
515                 }
516             }
517         } else {
518             SoftReference<Bitmap> ref = sFrameworkBitmapCache.get(value);
519             if (ref != null) {
520                 return ref.get();
521             }
522         }
523 
524         return null;
525     }
526 
527     /**
528      * Sets a bitmap in a project cache or in the framework cache.
529      * @param value the path of the bitmap
530      * @param bmp the Bitmap object
531      * @param projectKey the key of the project, or null to put the bitmap in the framework cache.
532      */
setCachedBitmap(String value, Bitmap bmp, Object projectKey)533     public static void setCachedBitmap(String value, Bitmap bmp, Object projectKey) {
534         if (projectKey != null) {
535             Map<String, SoftReference<Bitmap>> map = sProjectBitmapCache.get(projectKey);
536 
537             if (map == null) {
538                 map = new HashMap<String, SoftReference<Bitmap>>();
539                 sProjectBitmapCache.put(projectKey, map);
540             }
541 
542             map.put(value, new SoftReference<Bitmap>(bmp));
543         } else {
544             sFrameworkBitmapCache.put(value, new SoftReference<Bitmap>(bmp));
545         }
546     }
547 
548     /**
549      * Returns the 9 patch chunk for a specific path, from a specific project cache, or from the
550      * framework cache.
551      * @param value the path of the 9 patch
552      * @param projectKey the key of the project, or null to query the framework cache.
553      * @return the cached 9 patch or null if not found.
554      */
getCached9Patch(String value, Object projectKey)555     public static NinePatchChunk getCached9Patch(String value, Object projectKey) {
556         if (projectKey != null) {
557             Map<String, SoftReference<NinePatchChunk>> map = sProject9PatchCache.get(projectKey);
558 
559             if (map != null) {
560                 SoftReference<NinePatchChunk> ref = map.get(value);
561                 if (ref != null) {
562                     return ref.get();
563                 }
564             }
565         } else {
566             SoftReference<NinePatchChunk> ref = sFramework9PatchCache.get(value);
567             if (ref != null) {
568                 return ref.get();
569             }
570         }
571 
572         return null;
573     }
574 
575     /**
576      * Sets a 9 patch chunk in a project cache or in the framework cache.
577      * @param value the path of the 9 patch
578      * @param ninePatch the 9 patch object
579      * @param projectKey the key of the project, or null to put the bitmap in the framework cache.
580      */
setCached9Patch(String value, NinePatchChunk ninePatch, Object projectKey)581     public static void setCached9Patch(String value, NinePatchChunk ninePatch, Object projectKey) {
582         if (projectKey != null) {
583             Map<String, SoftReference<NinePatchChunk>> map = sProject9PatchCache.get(projectKey);
584 
585             if (map == null) {
586                 map = new HashMap<String, SoftReference<NinePatchChunk>>();
587                 sProject9PatchCache.put(projectKey, map);
588             }
589 
590             map.put(value, new SoftReference<NinePatchChunk>(ninePatch));
591         } else {
592             sFramework9PatchCache.put(value, new SoftReference<NinePatchChunk>(ninePatch));
593         }
594     }
595 
596 
597 }
598