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