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