2 * Copyright (C) 2008 The Android Open Source Project
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
8 * http://www.apache.org/licenses/LICENSE-2.0
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.
17 package com.android.layoutlib.bridge;
19 import static com.android.ide.common.rendering.api.Result.Status.ERROR_UNKNOWN;
20 import static com.android.ide.common.rendering.api.Result.Status.SUCCESS;
22 import com.android.ide.common.rendering.api.Capability;
23 import com.android.ide.common.rendering.api.DrawableParams;
24 import com.android.ide.common.rendering.api.LayoutLog;
25 import com.android.ide.common.rendering.api.RenderSession;
26 import com.android.ide.common.rendering.api.Result;
27 import com.android.ide.common.rendering.api.SessionParams;
28 import com.android.layoutlib.bridge.android.BridgeAssetManager;
29 import com.android.layoutlib.bridge.impl.FontLoader;
30 import com.android.layoutlib.bridge.impl.RenderDrawable;
31 import com.android.layoutlib.bridge.impl.RenderSessionImpl;
32 import com.android.ninepatch.NinePatchChunk;
33 import com.android.resources.ResourceType;
34 import com.android.tools.layoutlib.create.MethodAdapter;
35 import com.android.tools.layoutlib.create.OverrideMethod;
36 import com.android.util.Pair;
38 import android.graphics.Bitmap;
39 import android.graphics.Typeface;
40 import android.graphics.Typeface_Delegate;
41 import android.os.Looper;
44 import java.lang.ref.SoftReference;
45 import java.lang.reflect.Field;
46 import java.lang.reflect.Modifier;
47 import java.util.Arrays;
48 import java.util.EnumMap;
49 import java.util.EnumSet;
50 import java.util.HashMap;
52 import java.util.concurrent.locks.ReentrantLock;
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)}
59 public final class Bridge extends com.android.ide.common.rendering.api.Bridge {
61 public static class StaticMethodNotImplementedException extends RuntimeException {
62 private static final long serialVersionUID = 1L;
64 public StaticMethodNotImplementedException(String msg) {
70 * Lock to ensure only one rendering/inflating happens at a time.
71 * This is due to some singleton in the Android framework.
73 private final static ReentrantLock sLock = new ReentrantLock();
76 * Maps from id to resource type/name. This is for android.R only.
78 private final static Map<Integer, Pair<ResourceType, String>> sRMap =
79 new HashMap<Integer, Pair<ResourceType, String>>();
82 * Same as sRMap except for int[] instead of int resources. This is for android.R only.
84 private final static Map<IntArray, String> sRArrayMap = new HashMap<IntArray, String>();
86 * Reverse map compared to sRMap, resource type -> (resource name -> id).
87 * This is for android.R only.
89 private final static Map<ResourceType, Map<String, Integer>> sRFullMap =
90 new EnumMap<ResourceType, Map<String,Integer>>(ResourceType.class);
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>>>();
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>>();
102 private static Map<String, Map<String, Integer>> sEnumValueMap;
103 private static Map<String, String> sPlatformProperties;
106 * int[] wrapper to use as keys in maps.
108 private final static class IntArray {
109 private int[] mArray;
115 private IntArray(int[] a) {
119 private void set(int[] a) {
124 public int hashCode() {
125 return Arrays.hashCode(mArray);
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;
134 IntArray other = (IntArray) obj;
135 if (!Arrays.equals(mArray, other.mArray)) return false;
140 /** Instance of IntArrayWrapper to be reused in {@link #resolveResourceId(int[])}. */
141 private final static IntArray sIntArrayWrapper = new IntArray();
144 * A default log than prints to stdout/stderr.
146 private final static LayoutLog sDefaultLog = new LayoutLog() {
148 public void error(String tag, String message, Object data) {
149 System.err.println(message);
153 public void error(String tag, String message, Throwable throwable, Object data) {
154 System.err.println(message);
158 public void warning(String tag, String message, Object data) {
159 System.out.println(message);
166 private static LayoutLog sCurrentLog = sDefaultLog;
168 private EnumSet<Capability> mCapabilities;
171 public int getApiLevel() {
172 return com.android.ide.common.rendering.api.Bridge.API_CURRENT;
176 public EnumSet<Capability> getCapabilities() {
177 return mCapabilities;
181 public boolean init(Map<String,String> platformProperties,
183 Map<String, Map<String, Integer>> enumValueMap,
185 sPlatformProperties = platformProperties;
186 sEnumValueMap = enumValueMap;
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,
195 Capability.LAYOUT_ONLY,
196 Capability.EMBEDDED_LAYOUT,
197 Capability.VIEW_MANIPULATION,
198 Capability.PLAY_ANIMATION,
199 Capability.ANIMATED_VIEW_MANIPULATION);
202 BridgeAssetManager.initSystem();
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.
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")) {
216 OverrideMethod.setDefaultListener(new MethodAdapter() {
218 public void onInvokeV(String signature, boolean isNative, Object caller) {
219 sDefaultLog.error(null, "Missing Stub: " + signature +
220 (isNative ? " (native)" : ""), null /*data*/);
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);
234 FontLoader fontLoader = FontLoader.create(fontLocation.getAbsolutePath());
235 if (fontLoader != null) {
236 Typeface_Delegate.init(fontLoader);
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.
244 Class<?> r = com.android.internal.R.class;
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);
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);
275 } catch (Throwable throwable) {
277 log.error(LayoutLog.TAG_BROKEN,
278 "Failed to load com.android.internal.R from the layout library jar",
288 public boolean dispose() {
289 BridgeAssetManager.clearSystem();
291 // dispose of the default typeface.
292 Typeface.sDefaults = null;
298 * Starts a layout session by inflating and rendering it. The method returns a
299 * {@link RenderSession} on which further actions can be taken.
301 * @param params the {@link SessionParams} object with all the information necessary to create
303 * @return a new {@link RenderSession} object that contains the result of the layout.
307 public RenderSession createSession(SessionParams params) {
309 Result lastResult = SUCCESS.createResult();
310 RenderSessionImpl scene = new RenderSessionImpl(params);
313 lastResult = scene.init(params.getTimeout());
314 if (lastResult.isSuccess()) {
315 lastResult = scene.inflate();
316 if (lastResult.isSuccess()) {
317 lastResult = scene.render(true /*freshRender*/);
325 return new BridgeRenderSession(scene, lastResult);
326 } catch (Throwable t) {
327 // get the real cause of the exception.
329 while (t2.getCause() != null) {
332 return new BridgeRenderSession(null,
333 ERROR_UNKNOWN.createResult(t2.getMessage(), t));
338 public Result renderDrawable(DrawableParams params) {
340 Result lastResult = SUCCESS.createResult();
341 RenderDrawable action = new RenderDrawable(params);
344 lastResult = action.init(params.getTimeout());
345 if (lastResult.isSuccess()) {
346 lastResult = action.render();
354 } catch (Throwable t) {
355 // get the real cause of the exception.
357 while (t2.getCause() != null) {
360 return ERROR_UNKNOWN.createResult(t2.getMessage(), t);
365 public void clearCaches(Object projectKey) {
366 if (projectKey != null) {
367 sProjectBitmapCache.remove(projectKey);
368 sProject9PatchCache.remove(projectKey);
373 * Returns the lock for the bridge
375 public static ReentrantLock getLock() {
380 * Prepares the current thread for rendering.
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.
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) {
394 * Cleans up thread-specific data. After this, the thread cannot be used for scene actions.
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
399 public static void cleanupThread() {
400 // clean up the looper
401 Looper.sThreadLocal.remove();
404 public static LayoutLog getLog() {
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)");
417 sCurrentLog = sDefaultLog;
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.
427 public static Pair<ResourceType, String> resolveResourceId(int value) {
428 return sRMap.get(value);
432 * Returns the name of a framework resource whose value is an int array.
435 public static String resolveResourceId(int[] array) {
436 sIntArrayWrapper.set(array);
437 return sRArrayMap.get(sIntArrayWrapper);
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.
446 public static Integer getResourceId(ResourceType type, String name) {
447 Map<String, Integer> map = sRFullMap.get(type);
449 return map.get(name);
456 * Returns the list of possible enums for a given attribute name.
458 public static Map<String, Integer> getEnumValues(String attributeName) {
459 if (sEnumValueMap != null) {
460 return sEnumValueMap.get(attributeName);
467 * Returns the platform build properties.
469 public static Map<String, String> getPlatformProperties() {
470 return sPlatformProperties;
474 * Returns the bitmap for a specific path, from a specific project cache, or from the
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.
480 public static Bitmap getCachedBitmap(String value, Object projectKey) {
481 if (projectKey != null) {
482 Map<String, SoftReference<Bitmap>> map = sProjectBitmapCache.get(projectKey);
484 SoftReference<Bitmap> ref = map.get(value);
490 SoftReference<Bitmap> ref = sFrameworkBitmapCache.get(value);
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.
505 public static void setCachedBitmap(String value, Bitmap bmp, Object projectKey) {
506 if (projectKey != null) {
507 Map<String, SoftReference<Bitmap>> map = sProjectBitmapCache.get(projectKey);
510 map = new HashMap<String, SoftReference<Bitmap>>();
511 sProjectBitmapCache.put(projectKey, map);
514 map.put(value, new SoftReference<Bitmap>(bmp));
516 sFrameworkBitmapCache.put(value, new SoftReference<Bitmap>(bmp));
521 * Returns the 9 patch chunk for a specific path, from a specific project cache, or from the
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.
527 public static NinePatchChunk getCached9Patch(String value, Object projectKey) {
528 if (projectKey != null) {
529 Map<String, SoftReference<NinePatchChunk>> map = sProject9PatchCache.get(projectKey);
532 SoftReference<NinePatchChunk> ref = map.get(value);
538 SoftReference<NinePatchChunk> ref = sFramework9PatchCache.get(value);
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.
553 public static void setCached9Patch(String value, NinePatchChunk ninePatch, Object projectKey) {
554 if (projectKey != null) {
555 Map<String, SoftReference<NinePatchChunk>> map = sProject9PatchCache.get(projectKey);
558 map = new HashMap<String, SoftReference<NinePatchChunk>>();
559 sProject9PatchCache.put(projectKey, map);
562 map.put(value, new SoftReference<NinePatchChunk>(ninePatch));
564 sFramework9PatchCache.put(value, new SoftReference<NinePatchChunk>(ninePatch));