OSDN Git Service

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