Bridge.java revision adaa12cd9e421edf34e36de8cfacc3e3c334bb64
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.LAYOUT_ONLY, // disable to run on ADT 10.0 which doesn't include this.
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}
570