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