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