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