Bridge.java revision 4dd2299d5766bf14479578772a0946c5399a0158
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.DrawableParams;
24import com.android.ide.common.rendering.api.LayoutLog;
25import com.android.ide.common.rendering.api.RenderSession;
26import com.android.ide.common.rendering.api.Result;
27import com.android.ide.common.rendering.api.SessionParams;
28import com.android.layoutlib.bridge.android.BridgeAssetManager;
29import com.android.layoutlib.bridge.impl.FontLoader;
30import com.android.layoutlib.bridge.impl.RenderDrawable;
31import com.android.layoutlib.bridge.impl.RenderSessionImpl;
32import com.android.ninepatch.NinePatchChunk;
33import com.android.resources.ResourceType;
34import com.android.tools.layoutlib.create.MethodAdapter;
35import com.android.tools.layoutlib.create.OverrideMethod;
36import com.android.util.Pair;
37
38import android.graphics.Bitmap;
39import android.graphics.Typeface;
40import android.graphics.Typeface_Delegate;
41import android.os.Looper;
42
43import java.io.File;
44import java.lang.ref.SoftReference;
45import java.lang.reflect.Field;
46import java.lang.reflect.Modifier;
47import java.util.Arrays;
48import java.util.EnumMap;
49import java.util.EnumSet;
50import java.util.HashMap;
51import java.util.Map;
52import 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 */
59public 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.EMBEDDED_LAYOUT,
196                Capability.VIEW_MANIPULATION,
197                Capability.PLAY_ANIMATION,
198                Capability.ANIMATED_VIEW_MANIPULATION);
199
200
201        BridgeAssetManager.initSystem();
202
203        // When DEBUG_LAYOUT is set and is not 0 or false, setup a default listener
204        // on static (native) methods which prints the signature on the console and
205        // throws an exception.
206        // This is useful when testing the rendering in ADT to identify static native
207        // methods that are ignored -- layoutlib_create makes them returns 0/false/null
208        // which is generally OK yet might be a problem, so this is how you'd find out.
209        //
210        // Currently layoutlib_create only overrides static native method.
211        // Static non-natives are not overridden and thus do not get here.
212        final String debug = System.getenv("DEBUG_LAYOUT");
213        if (debug != null && !debug.equals("0") && !debug.equals("false")) {
214
215            OverrideMethod.setDefaultListener(new MethodAdapter() {
216                @Override
217                public void onInvokeV(String signature, boolean isNative, Object caller) {
218                    sDefaultLog.error(null, "Missing Stub: " + signature +
219                            (isNative ? " (native)" : ""), null /*data*/);
220
221                    if (debug.equalsIgnoreCase("throw")) {
222                        // Throwing this exception doesn't seem that useful. It breaks
223                        // the layout editor yet doesn't display anything meaningful to the
224                        // user. Having the error in the console is just as useful. We'll
225                        // throw it only if the environment variable is "throw" or "THROW".
226                        throw new StaticMethodNotImplementedException(signature);
227                    }
228                }
229            });
230        }
231
232        // load the fonts.
233        FontLoader fontLoader = FontLoader.create(fontLocation.getAbsolutePath());
234        if (fontLoader != null) {
235            Typeface_Delegate.init(fontLoader);
236        } else {
237            return false;
238        }
239
240        // now parse com.android.internal.R (and only this one as android.R is a subset of
241        // the internal version), and put the content in the maps.
242        try {
243            Class<?> r = com.android.internal.R.class;
244
245            for (Class<?> inner : r.getDeclaredClasses()) {
246                String resTypeName = inner.getSimpleName();
247                ResourceType resType = ResourceType.getEnum(resTypeName);
248                if (resType != null) {
249                    Map<String, Integer> fullMap = new HashMap<String, Integer>();
250                    sRFullMap.put(resType, fullMap);
251
252                    for (Field f : inner.getDeclaredFields()) {
253                        // only process static final fields. Since the final attribute may have
254                        // been altered by layoutlib_create, we only check static
255                        int modifiers = f.getModifiers();
256                        if (Modifier.isStatic(modifiers)) {
257                            Class<?> type = f.getType();
258                            if (type.isArray() && type.getComponentType() == int.class) {
259                                // if the object is an int[] we put it in sRArrayMap using an IntArray
260                                // wrapper that properly implements equals and hashcode for the array
261                                // objects, as required by the map contract.
262                                sRArrayMap.put(new IntArray((int[]) f.get(null)), f.getName());
263                            } else if (type == int.class) {
264                                Integer value = (Integer) f.get(null);
265                                sRMap.put(value, Pair.of(resType, f.getName()));
266                                fullMap.put(f.getName(), value);
267                            } else {
268                                assert false;
269                            }
270                        }
271                    }
272                }
273            }
274        } catch (Throwable throwable) {
275            if (log != null) {
276                log.error(LayoutLog.TAG_BROKEN,
277                        "Failed to load com.android.internal.R from the layout library jar",
278                        throwable);
279            }
280            return false;
281        }
282
283        return true;
284    }
285
286    @Override
287    public boolean dispose() {
288        BridgeAssetManager.clearSystem();
289
290        // dispose of the default typeface.
291        Typeface.sDefaults = null;
292
293        return true;
294    }
295
296    /**
297     * Starts a layout session by inflating and rendering it. The method returns a
298     * {@link RenderSession} on which further actions can be taken.
299     *
300     * @param params the {@link SessionParams} object with all the information necessary to create
301     *           the scene.
302     * @return a new {@link RenderSession} object that contains the result of the layout.
303     * @since 5
304     */
305    @Override
306    public RenderSession createSession(SessionParams params) {
307        try {
308            Result lastResult = SUCCESS.createResult();
309            RenderSessionImpl scene = new RenderSessionImpl(params);
310            try {
311                prepareThread();
312                lastResult = scene.init(params.getTimeout());
313                if (lastResult.isSuccess()) {
314                    lastResult = scene.inflate();
315                    if (lastResult.isSuccess()) {
316                        lastResult = scene.render(true /*freshRender*/);
317                    }
318                }
319            } finally {
320                scene.release();
321                cleanupThread();
322            }
323
324            return new BridgeRenderSession(scene, lastResult);
325        } catch (Throwable t) {
326            // get the real cause of the exception.
327            Throwable t2 = t;
328            while (t2.getCause() != null) {
329                t2 = t.getCause();
330            }
331            return new BridgeRenderSession(null,
332                    ERROR_UNKNOWN.createResult(t2.getMessage(), t));
333        }
334    }
335
336    @Override
337    public Result renderDrawable(DrawableParams params) {
338        try {
339            Result lastResult = SUCCESS.createResult();
340            RenderDrawable action = new RenderDrawable(params);
341            try {
342                prepareThread();
343                lastResult = action.init(params.getTimeout());
344                if (lastResult.isSuccess()) {
345                    lastResult = action.render();
346                }
347            } finally {
348                action.release();
349                cleanupThread();
350            }
351
352            return lastResult;
353        } catch (Throwable t) {
354            // get the real cause of the exception.
355            Throwable t2 = t;
356            while (t2.getCause() != null) {
357                t2 = t.getCause();
358            }
359            return ERROR_UNKNOWN.createResult(t2.getMessage(), t);
360        }
361    }
362
363    @Override
364    public void clearCaches(Object projectKey) {
365        if (projectKey != null) {
366            sProjectBitmapCache.remove(projectKey);
367            sProject9PatchCache.remove(projectKey);
368        }
369    }
370
371    /**
372     * Returns the lock for the bridge
373     */
374    public static ReentrantLock getLock() {
375        return sLock;
376    }
377
378    /**
379     * Prepares the current thread for rendering.
380     *
381     * Note that while this can be called several time, the first call to {@link #cleanupThread()}
382     * will do the clean-up, and make the thread unable to do further scene actions.
383     */
384    public static void prepareThread() {
385        // we need to make sure the Looper has been initialized for this thread.
386        // this is required for View that creates Handler objects.
387        if (Looper.myLooper() == null) {
388            Looper.prepare();
389        }
390    }
391
392    /**
393     * Cleans up thread-specific data. After this, the thread cannot be used for scene actions.
394     * <p>
395     * Note that it doesn't matter how many times {@link #prepareThread()} was called, a single
396     * call to this will prevent the thread from doing further scene actions
397     */
398    public static void cleanupThread() {
399        // clean up the looper
400        Looper.sThreadLocal.remove();
401    }
402
403    public static LayoutLog getLog() {
404        return sCurrentLog;
405    }
406
407    public static void setLog(LayoutLog log) {
408        // check only the thread currently owning the lock can do this.
409        if (sLock.isHeldByCurrentThread() == false) {
410            throw new IllegalStateException("scene must be acquired first. see #acquire(long)");
411        }
412
413        if (log != null) {
414            sCurrentLog = log;
415        } else {
416            sCurrentLog = sDefaultLog;
417        }
418    }
419
420    /**
421     * Returns details of a framework resource from its integer value.
422     * @param value the integer value
423     * @return a Pair containing the resource type and name, or null if the id
424     *     does not match any resource.
425     */
426    public static Pair<ResourceType, String> resolveResourceId(int value) {
427        return sRMap.get(value);
428    }
429
430    /**
431     * Returns the name of a framework resource whose value is an int array.
432     * @param array
433     */
434    public static String resolveResourceId(int[] array) {
435        sIntArrayWrapper.set(array);
436        return sRArrayMap.get(sIntArrayWrapper);
437    }
438
439    /**
440     * Returns the integer id of a framework resource, from a given resource type and resource name.
441     * @param type the type of the resource
442     * @param name the name of the resource.
443     * @return an {@link Integer} containing the resource id, or null if no resource were found.
444     */
445    public static Integer getResourceId(ResourceType type, String name) {
446        Map<String, Integer> map = sRFullMap.get(type);
447        if (map != null) {
448            return map.get(name);
449        }
450
451        return null;
452    }
453
454    /**
455     * Returns the list of possible enums for a given attribute name.
456     */
457    public static Map<String, Integer> getEnumValues(String attributeName) {
458        if (sEnumValueMap != null) {
459            return sEnumValueMap.get(attributeName);
460        }
461
462        return null;
463    }
464
465    /**
466     * Returns the platform build properties.
467     */
468    public static Map<String, String> getPlatformProperties() {
469        return sPlatformProperties;
470    }
471
472    /**
473     * Returns the bitmap for a specific path, from a specific project cache, or from the
474     * framework cache.
475     * @param value the path of the bitmap
476     * @param projectKey the key of the project, or null to query the framework cache.
477     * @return the cached Bitmap or null if not found.
478     */
479    public static Bitmap getCachedBitmap(String value, Object projectKey) {
480        if (projectKey != null) {
481            Map<String, SoftReference<Bitmap>> map = sProjectBitmapCache.get(projectKey);
482            if (map != null) {
483                SoftReference<Bitmap> ref = map.get(value);
484                if (ref != null) {
485                    return ref.get();
486                }
487            }
488        } else {
489            SoftReference<Bitmap> ref = sFrameworkBitmapCache.get(value);
490            if (ref != null) {
491                return ref.get();
492            }
493        }
494
495        return null;
496    }
497
498    /**
499     * Sets a bitmap in a project cache or in the framework cache.
500     * @param value the path of the bitmap
501     * @param bmp the Bitmap object
502     * @param projectKey the key of the project, or null to put the bitmap in the framework cache.
503     */
504    public static void setCachedBitmap(String value, Bitmap bmp, Object projectKey) {
505        if (projectKey != null) {
506            Map<String, SoftReference<Bitmap>> map = sProjectBitmapCache.get(projectKey);
507
508            if (map == null) {
509                map = new HashMap<String, SoftReference<Bitmap>>();
510                sProjectBitmapCache.put(projectKey, map);
511            }
512
513            map.put(value, new SoftReference<Bitmap>(bmp));
514        } else {
515            sFrameworkBitmapCache.put(value, new SoftReference<Bitmap>(bmp));
516        }
517    }
518
519    /**
520     * Returns the 9 patch chunk for a specific path, from a specific project cache, or from the
521     * framework cache.
522     * @param value the path of the 9 patch
523     * @param projectKey the key of the project, or null to query the framework cache.
524     * @return the cached 9 patch or null if not found.
525     */
526    public static NinePatchChunk getCached9Patch(String value, Object projectKey) {
527        if (projectKey != null) {
528            Map<String, SoftReference<NinePatchChunk>> map = sProject9PatchCache.get(projectKey);
529
530            if (map != null) {
531                SoftReference<NinePatchChunk> ref = map.get(value);
532                if (ref != null) {
533                    return ref.get();
534                }
535            }
536        } else {
537            SoftReference<NinePatchChunk> ref = sFramework9PatchCache.get(value);
538            if (ref != null) {
539                return ref.get();
540            }
541        }
542
543        return null;
544    }
545
546    /**
547     * Sets a 9 patch chunk in a project cache or in the framework cache.
548     * @param value the path of the 9 patch
549     * @param ninePatch the 9 patch object
550     * @param projectKey the key of the project, or null to put the bitmap in the framework cache.
551     */
552    public static void setCached9Patch(String value, NinePatchChunk ninePatch, Object projectKey) {
553        if (projectKey != null) {
554            Map<String, SoftReference<NinePatchChunk>> map = sProject9PatchCache.get(projectKey);
555
556            if (map == null) {
557                map = new HashMap<String, SoftReference<NinePatchChunk>>();
558                sProject9PatchCache.put(projectKey, map);
559            }
560
561            map.put(value, new SoftReference<NinePatchChunk>(ninePatch));
562        } else {
563            sFramework9PatchCache.put(value, new SoftReference<NinePatchChunk>(ninePatch));
564        }
565    }
566
567
568}
569