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