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