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