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