Bridge.java revision bf6956b1d95442e9d9c483894d578fe6b7044cbb
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.internal.util.XmlUtils; 20import com.android.layoutlib.api.ILayoutBridge; 21import com.android.layoutlib.api.ILayoutLog; 22import com.android.layoutlib.api.ILayoutResult; 23import com.android.layoutlib.api.IProjectCallback; 24import com.android.layoutlib.api.IResourceValue; 25import com.android.layoutlib.api.IStyleResourceValue; 26import com.android.layoutlib.api.IXmlPullParser; 27import com.android.layoutlib.api.ILayoutResult.ILayoutViewInfo; 28import com.android.layoutlib.bridge.LayoutResult.LayoutViewInfo; 29import com.android.ninepatch.NinePatch; 30import com.android.tools.layoutlib.create.MethodAdapter; 31import com.android.tools.layoutlib.create.OverrideMethod; 32 33import android.graphics.Bitmap; 34import android.graphics.Canvas; 35import android.graphics.Rect; 36import android.graphics.Region; 37import android.graphics.Typeface; 38import android.graphics.drawable.Drawable; 39import android.os.Bundle; 40import android.os.Handler; 41import android.os.IBinder; 42import android.os.Looper; 43import android.os.ParcelFileDescriptor; 44import android.os.RemoteException; 45import android.util.DisplayMetrics; 46import android.util.TypedValue; 47import android.view.BridgeInflater; 48import android.view.IWindow; 49import android.view.IWindowSession; 50import android.view.KeyEvent; 51import android.view.MotionEvent; 52import android.view.Surface; 53import android.view.SurfaceView; 54import android.view.View; 55import android.view.ViewGroup; 56import android.view.View.AttachInfo; 57import android.view.View.MeasureSpec; 58import android.view.WindowManager.LayoutParams; 59import android.widget.FrameLayout; 60import android.widget.TabHost; 61import android.widget.TabWidget; 62 63import java.lang.ref.SoftReference; 64import java.lang.reflect.Field; 65import java.lang.reflect.Modifier; 66import java.util.Collection; 67import java.util.HashMap; 68import java.util.Map; 69 70/** 71 * Main entry point of the LayoutLib Bridge. 72 * <p/>To use this bridge, simply instantiate an object of type {@link Bridge} and call 73 * {@link #computeLayout(IXmlPullParser, Object, int, int, String, boolean, Map, Map, IProjectCallback, ILayoutLog)}. 74 */ 75public final class Bridge implements ILayoutBridge { 76 77 private static final int DEFAULT_TITLE_BAR_HEIGHT = 25; 78 private static final int DEFAULT_STATUS_BAR_HEIGHT = 25; 79 80 public static class StaticMethodNotImplementedException extends RuntimeException { 81 private static final long serialVersionUID = 1L; 82 83 public StaticMethodNotImplementedException(String msg) { 84 super(msg); 85 } 86 } 87 88 /** 89 * Maps from id to resource name/type. This is for android.R only. 90 */ 91 private final static Map<Integer, String[]> sRMap = new HashMap<Integer, String[]>(); 92 /** 93 * Same as sRMap except for int[] instead of int resources. This is for android.R only. 94 */ 95 private final static Map<int[], String> sRArrayMap = new HashMap<int[], String>(); 96 /** 97 * Reverse map compared to sRMap, resource type -> (resource name -> id). 98 * This is for android.R only. 99 */ 100 private final static Map<String, Map<String, Integer>> sRFullMap = 101 new HashMap<String, Map<String,Integer>>(); 102 103 private final static Map<Object, Map<String, SoftReference<Bitmap>>> sProjectBitmapCache = 104 new HashMap<Object, Map<String, SoftReference<Bitmap>>>(); 105 private final static Map<Object, Map<String, SoftReference<NinePatch>>> sProject9PatchCache = 106 new HashMap<Object, Map<String, SoftReference<NinePatch>>>(); 107 108 private final static Map<String, SoftReference<Bitmap>> sFrameworkBitmapCache = 109 new HashMap<String, SoftReference<Bitmap>>(); 110 private final static Map<String, SoftReference<NinePatch>> sFramework9PatchCache = 111 new HashMap<String, SoftReference<NinePatch>>(); 112 113 private static Map<String, Map<String, Integer>> sEnumValueMap; 114 115 /** 116 * A default logger than prints to stdout/stderr. 117 */ 118 private final static ILayoutLog sDefaultLogger = new ILayoutLog() { 119 public void error(String message) { 120 System.err.println(message); 121 } 122 123 public void error(Throwable t) { 124 String message = t.getMessage(); 125 if (message == null) { 126 message = t.getClass().getName(); 127 } 128 129 System.err.println(message); 130 } 131 132 public void warning(String message) { 133 System.out.println(message); 134 } 135 }; 136 137 /** 138 * Logger defined during a compute layout operation. 139 * <p/> 140 * This logger is generally set to {@link #sDefaultLogger} except during rendering 141 * operations when it might be set to a specific provided logger. 142 * <p/> 143 * To change this value, use a block synchronized on {@link #sDefaultLogger}. 144 */ 145 private static ILayoutLog sLogger = sDefaultLogger; 146 147 /* 148 * (non-Javadoc) 149 * @see com.android.layoutlib.api.ILayoutBridge#getApiLevel() 150 */ 151 public int getApiLevel() { 152 return API_CURRENT; 153 } 154 155 /* 156 * (non-Javadoc) 157 * @see com.android.layoutlib.api.ILayoutLibBridge#init(java.lang.String, java.util.Map) 158 */ 159 public boolean init( 160 String fontOsLocation, Map<String, Map<String, Integer>> enumValueMap) { 161 162 return sinit(fontOsLocation, enumValueMap); 163 } 164 165 private static synchronized boolean sinit(String fontOsLocation, 166 Map<String, Map<String, Integer>> enumValueMap) { 167 168 // When DEBUG_LAYOUT is set and is not 0 or false, setup a default listener 169 // on static (native) methods which prints the signature on the console and 170 // throws an exception. 171 // This is useful when testing the rendering in ADT to identify static native 172 // methods that are ignored -- layoutlib_create makes them returns 0/false/null 173 // which is generally OK yet might be a problem, so this is how you'd find out. 174 // 175 // Currently layoutlib_create only overrides static native method. 176 // Static non-natives are not overridden and thus do not get here. 177 final String debug = System.getenv("DEBUG_LAYOUT"); 178 if (debug != null && !debug.equals("0") && !debug.equals("false")) { 179 180 OverrideMethod.setDefaultListener(new MethodAdapter() { 181 @Override 182 public void onInvokeV(String signature, boolean isNative, Object caller) { 183 if (sLogger != null) { 184 synchronized (sDefaultLogger) { 185 sLogger.error("Missing Stub: " + signature + 186 (isNative ? " (native)" : "")); 187 } 188 } 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 // Override View.isInEditMode to return true. 202 // 203 // This allows custom views that are drawn in the Graphical Layout Editor to adapt their 204 // rendering for preview. Most important this let custom views know that they can't expect 205 // the rest of their activities to be alive. 206 OverrideMethod.setMethodListener("android.view.View#isInEditMode()Z", 207 new MethodAdapter() { 208 @Override 209 public int onInvokeI(String signature, boolean isNative, Object caller) { 210 return 1; 211 } 212 } 213 ); 214 215 // load the fonts. 216 FontLoader fontLoader = FontLoader.create(fontOsLocation); 217 if (fontLoader != null) { 218 Typeface.init(fontLoader); 219 } else { 220 return false; 221 } 222 223 sEnumValueMap = enumValueMap; 224 225 // now parse com.android.internal.R (and only this one as android.R is a subset of 226 // the internal version), and put the content in the maps. 227 try { 228 // WARNING: this only works because the class is already loaded, and therefore 229 // the objects returned by Field.get() are the same as the ones used by 230 // the code accessing the R class. 231 // int[] does not implement equals/hashCode, and if the parsing used a different class 232 // loader for the R class, this would NOT work. 233 Class<?> r = com.android.internal.R.class; 234 235 for (Class<?> inner : r.getDeclaredClasses()) { 236 String resType = inner.getSimpleName(); 237 238 Map<String, Integer> fullMap = new HashMap<String, Integer>(); 239 sRFullMap.put(resType, fullMap); 240 241 for (Field f : inner.getDeclaredFields()) { 242 // only process static final fields. Since the final attribute may have 243 // been altered by layoutlib_create, we only check static 244 int modifiers = f.getModifiers(); 245 if (Modifier.isStatic(modifiers)) { 246 Class<?> type = f.getType(); 247 if (type.isArray() && type.getComponentType() == int.class) { 248 // if the object is an int[] we put it in sRArrayMap 249 sRArrayMap.put((int[]) f.get(null), f.getName()); 250 } else if (type == int.class) { 251 Integer value = (Integer) f.get(null); 252 sRMap.put(value, new String[] { f.getName(), resType }); 253 fullMap.put(f.getName(), value); 254 } else { 255 assert false; 256 } 257 } 258 } 259 } 260 } catch (IllegalArgumentException e) { 261 // FIXME: log/return the error (there's no logger object at this point!) 262 e.printStackTrace(); 263 return false; 264 } catch (IllegalAccessException e) { 265 e.printStackTrace(); 266 return false; 267 } 268 269 return true; 270 } 271 272 /* 273 * For compatilibty purposes, we implement the old deprecated version of computeLayout. 274 * (non-Javadoc) 275 * @see com.android.layoutlib.api.ILayoutBridge#computeLayout(com.android.layoutlib.api.IXmlPullParser, java.lang.Object, int, int, java.lang.String, java.util.Map, java.util.Map, com.android.layoutlib.api.IProjectCallback, com.android.layoutlib.api.ILayoutLog) 276 */ 277 @Deprecated 278 public ILayoutResult computeLayout(IXmlPullParser layoutDescription, 279 Object projectKey, 280 int screenWidth, int screenHeight, String themeName, 281 Map<String, Map<String, IResourceValue>> projectResources, 282 Map<String, Map<String, IResourceValue>> frameworkResources, 283 IProjectCallback customViewLoader, ILayoutLog logger) { 284 boolean isProjectTheme = false; 285 if (themeName.charAt(0) == '*') { 286 themeName = themeName.substring(1); 287 isProjectTheme = true; 288 } 289 290 return computeLayout(layoutDescription, projectKey, 291 screenWidth, screenHeight, DisplayMetrics.DENSITY_DEFAULT, 292 DisplayMetrics.DENSITY_DEFAULT, DisplayMetrics.DENSITY_DEFAULT, 293 themeName, isProjectTheme, 294 projectResources, frameworkResources, customViewLoader, logger); 295 } 296 297 /* 298 * For compatilibty purposes, we implement the old deprecated version of computeLayout. 299 * (non-Javadoc) 300 * @see com.android.layoutlib.api.ILayoutBridge#computeLayout(com.android.layoutlib.api.IXmlPullParser, java.lang.Object, int, int, java.lang.String, boolean, java.util.Map, java.util.Map, com.android.layoutlib.api.IProjectCallback, com.android.layoutlib.api.ILayoutLog) 301 */ 302 @Deprecated 303 public ILayoutResult computeLayout(IXmlPullParser layoutDescription, Object projectKey, 304 int screenWidth, int screenHeight, String themeName, boolean isProjectTheme, 305 Map<String, Map<String, IResourceValue>> projectResources, 306 Map<String, Map<String, IResourceValue>> frameworkResources, 307 IProjectCallback customViewLoader, ILayoutLog logger) { 308 return computeLayout(layoutDescription, projectKey, 309 screenWidth, screenHeight, DisplayMetrics.DENSITY_DEFAULT, 310 DisplayMetrics.DENSITY_DEFAULT, DisplayMetrics.DENSITY_DEFAULT, 311 themeName, isProjectTheme, 312 projectResources, frameworkResources, customViewLoader, logger); 313 } 314 315 /* 316 * For compatilibty purposes, we implement the old deprecated version of computeLayout. 317 * (non-Javadoc) 318 * @see com.android.layoutlib.api.ILayoutBridge#computeLayout(com.android.layoutlib.api.IXmlPullParser, java.lang.Object, int, int, int, float, float, java.lang.String, boolean, java.util.Map, java.util.Map, com.android.layoutlib.api.IProjectCallback, com.android.layoutlib.api.ILayoutLog) 319 */ 320 public ILayoutResult computeLayout(IXmlPullParser layoutDescription, Object projectKey, 321 int screenWidth, int screenHeight, int density, float xdpi, float ydpi, 322 String themeName, boolean isProjectTheme, 323 Map<String, Map<String, IResourceValue>> projectResources, 324 Map<String, Map<String, IResourceValue>> frameworkResources, 325 IProjectCallback customViewLoader, ILayoutLog logger) { 326 return computeLayout(layoutDescription, projectKey, 327 screenWidth, screenHeight, false /* renderFullSize */, 328 density, xdpi, ydpi, themeName, isProjectTheme, 329 projectResources, frameworkResources, customViewLoader, logger); 330 } 331 332 /* 333 * (non-Javadoc) 334 * @see com.android.layoutlib.api.ILayoutBridge#computeLayout(com.android.layoutlib.api.IXmlPullParser, java.lang.Object, int, int, boolean, int, float, float, java.lang.String, boolean, java.util.Map, java.util.Map, com.android.layoutlib.api.IProjectCallback, com.android.layoutlib.api.ILayoutLog) 335 */ 336 public ILayoutResult computeLayout(IXmlPullParser layoutDescription, Object projectKey, 337 int screenWidth, int screenHeight, boolean renderFullSize, 338 int density, float xdpi, float ydpi, 339 String themeName, boolean isProjectTheme, 340 Map<String, Map<String, IResourceValue>> projectResources, 341 Map<String, Map<String, IResourceValue>> frameworkResources, 342 IProjectCallback customViewLoader, ILayoutLog logger) { 343 if (logger == null) { 344 logger = sDefaultLogger; 345 } 346 347 synchronized (sDefaultLogger) { 348 sLogger = logger; 349 } 350 351 // find the current theme and compute the style inheritance map 352 Map<IStyleResourceValue, IStyleResourceValue> styleParentMap = 353 new HashMap<IStyleResourceValue, IStyleResourceValue>(); 354 355 IStyleResourceValue currentTheme = computeStyleMaps(themeName, isProjectTheme, 356 projectResources.get(BridgeConstants.RES_STYLE), 357 frameworkResources.get(BridgeConstants.RES_STYLE), styleParentMap); 358 359 BridgeContext context = null; 360 try { 361 // setup the display Metrics. 362 DisplayMetrics metrics = new DisplayMetrics(); 363 metrics.densityDpi = density; 364 metrics.density = density / (float) DisplayMetrics.DENSITY_DEFAULT; 365 metrics.scaledDensity = metrics.density; 366 metrics.widthPixels = screenWidth; 367 metrics.heightPixels = screenHeight; 368 metrics.xdpi = xdpi; 369 metrics.ydpi = ydpi; 370 371 context = new BridgeContext(projectKey, metrics, currentTheme, projectResources, 372 frameworkResources, styleParentMap, customViewLoader, logger); 373 BridgeInflater inflater = new BridgeInflater(context, customViewLoader); 374 context.setBridgeInflater(inflater); 375 376 IResourceValue windowBackground = null; 377 int screenOffset = 0; 378 if (currentTheme != null) { 379 windowBackground = context.findItemInStyle(currentTheme, "windowBackground"); 380 windowBackground = context.resolveResValue(windowBackground); 381 382 screenOffset = getScreenOffset(frameworkResources, currentTheme, context); 383 } 384 385 // we need to make sure the Looper has been initialized for this thread. 386 // this is required for View that creates Handler objects. 387 if (Looper.myLooper() == null) { 388 Looper.prepare(); 389 } 390 391 BridgeXmlBlockParser parser = new BridgeXmlBlockParser(layoutDescription, 392 context, false /* platformResourceFlag */); 393 394 ViewGroup root = new FrameLayout(context); 395 396 View view = inflater.inflate(parser, root); 397 398 // post-inflate process. For now this supports TabHost/TabWidget 399 postInflateProcess(view, customViewLoader); 400 401 // set the AttachInfo on the root view. 402 AttachInfo info = new AttachInfo(new WindowSession(), new Window(), 403 new Handler(), null); 404 info.mHasWindowFocus = true; 405 info.mWindowVisibility = View.VISIBLE; 406 info.mInTouchMode = false; // this is so that we can display selections. 407 root.dispatchAttachedToWindow(info, 0); 408 409 // get the background drawable 410 if (windowBackground != null) { 411 Drawable d = ResourceHelper.getDrawable(windowBackground, 412 context, true /* isFramework */); 413 root.setBackgroundDrawable(d); 414 } 415 416 // measure the views 417 int w_spec, h_spec; 418 419 if (renderFullSize) { 420 // measure the full size needed by the layout. 421 w_spec = MeasureSpec.makeMeasureSpec(screenWidth, 422 MeasureSpec.UNSPECIFIED); // this lets us know the actual needed size 423 h_spec = MeasureSpec.makeMeasureSpec(screenHeight - screenOffset, 424 MeasureSpec.UNSPECIFIED); // this lets us know the actual needed size 425 view.measure(w_spec, h_spec); 426 427 int neededWidth = root.getChildAt(0).getMeasuredWidth(); 428 if (neededWidth > screenWidth) { 429 screenWidth = neededWidth; 430 } 431 432 int neededHeight = root.getChildAt(0).getMeasuredHeight(); 433 if (neededHeight > screenHeight - screenOffset) { 434 screenHeight = neededHeight + screenOffset; 435 } 436 } 437 438 // remeasure with only the size we need 439 // This must always be done before the call to layout 440 w_spec = MeasureSpec.makeMeasureSpec(screenWidth, MeasureSpec.EXACTLY); 441 h_spec = MeasureSpec.makeMeasureSpec(screenHeight - screenOffset, 442 MeasureSpec.EXACTLY); 443 view.measure(w_spec, h_spec); 444 445 // now do the layout. 446 view.layout(0, screenOffset, screenWidth, screenHeight); 447 448 // draw the views 449 Canvas canvas = new Canvas(screenWidth, screenHeight - screenOffset, logger); 450 451 root.draw(canvas); 452 canvas.dispose(); 453 454 return new LayoutResult(visit(((ViewGroup)view).getChildAt(0), context), 455 canvas.getImage()); 456 } catch (PostInflateException e) { 457 return new LayoutResult(ILayoutResult.ERROR, "Error during post inflation process:\n" 458 + e.getMessage()); 459 } catch (Throwable e) { 460 // get the real cause of the exception. 461 Throwable t = e; 462 while (t.getCause() != null) { 463 t = t.getCause(); 464 } 465 466 // log it 467 logger.error(t); 468 469 // then return with an ERROR status and the message from the real exception 470 return new LayoutResult(ILayoutResult.ERROR, 471 t.getClass().getSimpleName() + ": " + t.getMessage()); 472 } finally { 473 // Make sure to remove static references, otherwise we could not unload the lib 474 BridgeResources.clearSystem(); 475 BridgeAssetManager.clearSystem(); 476 477 // Remove the global logger 478 synchronized (sDefaultLogger) { 479 sLogger = sDefaultLogger; 480 } 481 } 482 } 483 484 /* 485 * (non-Javadoc) 486 * @see com.android.layoutlib.api.ILayoutLibBridge#clearCaches(java.lang.Object) 487 */ 488 public void clearCaches(Object projectKey) { 489 if (projectKey != null) { 490 sProjectBitmapCache.remove(projectKey); 491 sProject9PatchCache.remove(projectKey); 492 } 493 } 494 495 /** 496 * Returns details of a framework resource from its integer value. 497 * @param value the integer value 498 * @return an array of 2 strings containing the resource name and type, or null if the id 499 * does not match any resource. 500 */ 501 public static String[] resolveResourceValue(int value) { 502 return sRMap.get(value); 503 504 } 505 506 /** 507 * Returns the name of a framework resource whose value is an int array. 508 * @param array 509 */ 510 public static String resolveResourceValue(int[] array) { 511 return sRArrayMap.get(array); 512 } 513 514 /** 515 * Returns the integer id of a framework resource, from a given resource type and resource name. 516 * @param type the type of the resource 517 * @param name the name of the resource. 518 * @return an {@link Integer} containing the resource id, or null if no resource were found. 519 */ 520 public static Integer getResourceValue(String type, String name) { 521 Map<String, Integer> map = sRFullMap.get(type); 522 if (map != null) { 523 return map.get(name); 524 } 525 526 return null; 527 } 528 529 static Map<String, Integer> getEnumValues(String attributeName) { 530 if (sEnumValueMap != null) { 531 return sEnumValueMap.get(attributeName); 532 } 533 534 return null; 535 } 536 537 /** 538 * Visits a View and its children and generate a {@link ILayoutViewInfo} containing the 539 * bounds of all the views. 540 * @param view the root View 541 * @param context the context. 542 */ 543 private ILayoutViewInfo visit(View view, BridgeContext context) { 544 if (view == null) { 545 return null; 546 } 547 548 LayoutViewInfo result = new LayoutViewInfo(view.getClass().getName(), 549 context.getViewKey(view), 550 view.getLeft(), view.getTop(), view.getRight(), view.getBottom()); 551 552 if (view instanceof ViewGroup) { 553 ViewGroup group = ((ViewGroup) view); 554 int n = group.getChildCount(); 555 ILayoutViewInfo[] children = new ILayoutViewInfo[n]; 556 for (int i = 0; i < group.getChildCount(); i++) { 557 children[i] = visit(group.getChildAt(i), context); 558 } 559 result.setChildren(children); 560 } 561 562 return result; 563 } 564 565 /** 566 * Compute style information from the given list of style for the project and framework. 567 * @param themeName the name of the current theme. In order to differentiate project and 568 * platform themes sharing the same name, all project themes must be prepended with 569 * a '*' character. 570 * @param isProjectTheme Is this a project theme 571 * @param inProjectStyleMap the project style map 572 * @param inFrameworkStyleMap the framework style map 573 * @param outInheritanceMap the map of style inheritance. This is filled by the method 574 * @return the {@link IStyleResourceValue} matching <var>themeName</var> 575 */ 576 private IStyleResourceValue computeStyleMaps( 577 String themeName, boolean isProjectTheme, Map<String, 578 IResourceValue> inProjectStyleMap, Map<String, IResourceValue> inFrameworkStyleMap, 579 Map<IStyleResourceValue, IStyleResourceValue> outInheritanceMap) { 580 581 if (inProjectStyleMap != null && inFrameworkStyleMap != null) { 582 // first, get the theme 583 IResourceValue theme = null; 584 585 // project theme names have been prepended with a * 586 if (isProjectTheme) { 587 theme = inProjectStyleMap.get(themeName); 588 } else { 589 theme = inFrameworkStyleMap.get(themeName); 590 } 591 592 if (theme instanceof IStyleResourceValue) { 593 // compute the inheritance map for both the project and framework styles 594 computeStyleInheritance(inProjectStyleMap.values(), inProjectStyleMap, 595 inFrameworkStyleMap, outInheritanceMap); 596 597 // Compute the style inheritance for the framework styles/themes. 598 // Since, for those, the style parent values do not contain 'android:' 599 // we want to force looking in the framework style only to avoid using 600 // similarly named styles from the project. 601 // To do this, we pass null in lieu of the project style map. 602 computeStyleInheritance(inFrameworkStyleMap.values(), null /*inProjectStyleMap */, 603 inFrameworkStyleMap, outInheritanceMap); 604 605 return (IStyleResourceValue)theme; 606 } 607 } 608 609 return null; 610 } 611 612 /** 613 * Compute the parent style for all the styles in a given list. 614 * @param styles the styles for which we compute the parent. 615 * @param inProjectStyleMap the map of project styles. 616 * @param inFrameworkStyleMap the map of framework styles. 617 * @param outInheritanceMap the map of style inheritance. This is filled by the method. 618 */ 619 private void computeStyleInheritance(Collection<IResourceValue> styles, 620 Map<String, IResourceValue> inProjectStyleMap, 621 Map<String, IResourceValue> inFrameworkStyleMap, 622 Map<IStyleResourceValue, IStyleResourceValue> outInheritanceMap) { 623 for (IResourceValue value : styles) { 624 if (value instanceof IStyleResourceValue) { 625 IStyleResourceValue style = (IStyleResourceValue)value; 626 IStyleResourceValue parentStyle = null; 627 628 // first look for a specified parent. 629 String parentName = style.getParentStyle(); 630 631 // no specified parent? try to infer it from the name of the style. 632 if (parentName == null) { 633 parentName = getParentName(value.getName()); 634 } 635 636 if (parentName != null) { 637 parentStyle = getStyle(parentName, inProjectStyleMap, inFrameworkStyleMap); 638 639 if (parentStyle != null) { 640 outInheritanceMap.put(style, parentStyle); 641 } 642 } 643 } 644 } 645 } 646 647 /** 648 * Searches for and returns the {@link IStyleResourceValue} from a given name. 649 * <p/>The format of the name can be: 650 * <ul> 651 * <li>[android:]<name></li> 652 * <li>[android:]style/<name></li> 653 * <li>@[android:]style/<name></li> 654 * </ul> 655 * @param parentName the name of the style. 656 * @param inProjectStyleMap the project style map. Can be <code>null</code> 657 * @param inFrameworkStyleMap the framework style map. 658 * @return The matching {@link IStyleResourceValue} object or <code>null</code> if not found. 659 */ 660 private IStyleResourceValue getStyle(String parentName, 661 Map<String, IResourceValue> inProjectStyleMap, 662 Map<String, IResourceValue> inFrameworkStyleMap) { 663 boolean frameworkOnly = false; 664 665 String name = parentName; 666 667 // remove the useless @ if it's there 668 if (name.startsWith(BridgeConstants.PREFIX_RESOURCE_REF)) { 669 name = name.substring(BridgeConstants.PREFIX_RESOURCE_REF.length()); 670 } 671 672 // check for framework identifier. 673 if (name.startsWith(BridgeConstants.PREFIX_ANDROID)) { 674 frameworkOnly = true; 675 name = name.substring(BridgeConstants.PREFIX_ANDROID.length()); 676 } 677 678 // at this point we could have the format style/<name>. we want only the name 679 if (name.startsWith(BridgeConstants.REFERENCE_STYLE)) { 680 name = name.substring(BridgeConstants.REFERENCE_STYLE.length()); 681 } 682 683 IResourceValue parent = null; 684 685 // if allowed, search in the project resources. 686 if (frameworkOnly == false && inProjectStyleMap != null) { 687 parent = inProjectStyleMap.get(name); 688 } 689 690 // if not found, then look in the framework resources. 691 if (parent == null) { 692 parent = inFrameworkStyleMap.get(name); 693 } 694 695 // make sure the result is the proper class type and return it. 696 if (parent instanceof IStyleResourceValue) { 697 return (IStyleResourceValue)parent; 698 } 699 700 sLogger.error(String.format("Unable to resolve parent style name: ", parentName)); 701 702 return null; 703 } 704 705 /** 706 * Computes the name of the parent style, or <code>null</code> if the style is a root style. 707 */ 708 private String getParentName(String styleName) { 709 int index = styleName.lastIndexOf('.'); 710 if (index != -1) { 711 return styleName.substring(0, index); 712 } 713 714 return null; 715 } 716 717 /** 718 * Returns the top screen offset. This depends on whether the current theme defines the user 719 * of the title and status bars. 720 * @param frameworkResources The framework resources 721 * @param currentTheme The current theme 722 * @param context The context 723 * @return the pixel height offset 724 */ 725 private int getScreenOffset(Map<String, Map<String, IResourceValue>> frameworkResources, 726 IStyleResourceValue currentTheme, BridgeContext context) { 727 int offset = 0; 728 729 // get the title bar flag from the current theme. 730 IResourceValue value = context.findItemInStyle(currentTheme, "windowNoTitle"); 731 732 // because it may reference something else, we resolve it. 733 value = context.resolveResValue(value); 734 735 // if there's a value and it's true (default is false) 736 if (value == null || value.getValue() == null || 737 XmlUtils.convertValueToBoolean(value.getValue(), false /* defValue */) == false) { 738 // default size of the window title bar 739 int defaultOffset = DEFAULT_TITLE_BAR_HEIGHT; 740 741 // get value from the theme. 742 value = context.findItemInStyle(currentTheme, "windowTitleSize"); 743 744 // resolve it 745 value = context.resolveResValue(value); 746 747 if (value != null) { 748 // get the numerical value, if available 749 TypedValue typedValue = ResourceHelper.getValue(value.getValue()); 750 if (typedValue != null) { 751 // compute the pixel value based on the display metrics 752 defaultOffset = (int)typedValue.getDimension(context.getResources().mMetrics); 753 } 754 } 755 756 offset += defaultOffset; 757 } 758 759 // get the fullscreen flag from the current theme. 760 value = context.findItemInStyle(currentTheme, "windowFullscreen"); 761 762 // because it may reference something else, we resolve it. 763 value = context.resolveResValue(value); 764 765 if (value == null || value.getValue() == null || 766 XmlUtils.convertValueToBoolean(value.getValue(), false /* defValue */) == false) { 767 768 // default value 769 int defaultOffset = DEFAULT_STATUS_BAR_HEIGHT; 770 771 // get the real value, first the list of Dimensions from the framework map 772 Map<String, IResourceValue> dimens = frameworkResources.get(BridgeConstants.RES_DIMEN); 773 774 // now get the value 775 value = dimens.get("status_bar_height"); 776 if (value != null) { 777 TypedValue typedValue = ResourceHelper.getValue(value.getValue()); 778 if (typedValue != null) { 779 // compute the pixel value based on the display metrics 780 defaultOffset = (int)typedValue.getDimension(context.getResources().mMetrics); 781 } 782 } 783 784 // add the computed offset. 785 offset += defaultOffset; 786 } 787 788 return offset; 789 } 790 791 /** 792 * Post process on a view hierachy that was just inflated. 793 * <p/>At the moment this only support TabHost: If {@link TabHost} is detected, look for the 794 * {@link TabWidget}, and the corresponding {@link FrameLayout} and make new tabs automatically 795 * based on the content of the {@link FrameLayout}. 796 * @param view the root view to process. 797 * @param projectCallback callback to the project. 798 */ 799 private void postInflateProcess(View view, IProjectCallback projectCallback) 800 throws PostInflateException { 801 if (view instanceof TabHost) { 802 setupTabHost((TabHost)view, projectCallback); 803 } else if (view instanceof ViewGroup) { 804 ViewGroup group = (ViewGroup)view; 805 final int count = group.getChildCount(); 806 for (int c = 0 ; c < count ; c++) { 807 View child = group.getChildAt(c); 808 postInflateProcess(child, projectCallback); 809 } 810 } 811 } 812 813 /** 814 * Sets up a {@link TabHost} object. 815 * @param tabHost the TabHost to setup. 816 * @param projectCallback The project callback object to access the project R class. 817 * @throws PostInflateException 818 */ 819 private void setupTabHost(TabHost tabHost, IProjectCallback projectCallback) 820 throws PostInflateException { 821 // look for the TabWidget, and the FrameLayout. They have their own specific names 822 View v = tabHost.findViewById(android.R.id.tabs); 823 824 if (v == null) { 825 throw new PostInflateException( 826 "TabHost requires a TabWidget with id \"android:id/tabs\".\n"); 827 } 828 829 if ((v instanceof TabWidget) == false) { 830 throw new PostInflateException(String.format( 831 "TabHost requires a TabWidget with id \"android:id/tabs\".\n" + 832 "View found with id 'tabs' is '%s'", v.getClass().getCanonicalName())); 833 } 834 835 v = tabHost.findViewById(android.R.id.tabcontent); 836 837 if (v == null) { 838 // TODO: see if we can fake tabs even without the FrameLayout (same below when the framelayout is empty) 839 throw new PostInflateException( 840 "TabHost requires a FrameLayout with id \"android:id/tabcontent\"."); 841 } 842 843 if ((v instanceof FrameLayout) == false) { 844 throw new PostInflateException(String.format( 845 "TabHost requires a FrameLayout with id \"android:id/tabcontent\".\n" + 846 "View found with id 'tabcontent' is '%s'", v.getClass().getCanonicalName())); 847 } 848 849 FrameLayout content = (FrameLayout)v; 850 851 // now process the content of the framelayout and dynamically create tabs for it. 852 final int count = content.getChildCount(); 853 854 if (count == 0) { 855 throw new PostInflateException( 856 "The FrameLayout for the TabHost has no content. Rendering failed.\n"); 857 } 858 859 // this must be called before addTab() so that the TabHost searches its TabWidget 860 // and FrameLayout. 861 tabHost.setup(); 862 863 // for each child of the framelayout, add a new TabSpec 864 for (int i = 0 ; i < count ; i++) { 865 View child = content.getChildAt(i); 866 String tabSpec = String.format("tab_spec%d", i+1); 867 int id = child.getId(); 868 String[] resource = projectCallback.resolveResourceValue(id); 869 String name; 870 if (resource != null) { 871 name = resource[0]; // 0 is resource name, 1 is resource type. 872 } else { 873 name = String.format("Tab %d", i+1); // default name if id is unresolved. 874 } 875 tabHost.addTab(tabHost.newTabSpec(tabSpec).setIndicator(name).setContent(id)); 876 } 877 } 878 879 /** 880 * Returns the bitmap for a specific path, from a specific project cache, or from the 881 * framework cache. 882 * @param value the path of the bitmap 883 * @param projectKey the key of the project, or null to query the framework cache. 884 * @return the cached Bitmap or null if not found. 885 */ 886 static Bitmap getCachedBitmap(String value, Object projectKey) { 887 if (projectKey != null) { 888 Map<String, SoftReference<Bitmap>> map = sProjectBitmapCache.get(projectKey); 889 if (map != null) { 890 SoftReference<Bitmap> ref = map.get(value); 891 if (ref != null) { 892 return ref.get(); 893 } 894 } 895 } else { 896 SoftReference<Bitmap> ref = sFrameworkBitmapCache.get(value); 897 if (ref != null) { 898 return ref.get(); 899 } 900 } 901 902 return null; 903 } 904 905 /** 906 * Sets a bitmap in a project cache or in the framework cache. 907 * @param value the path of the bitmap 908 * @param bmp the Bitmap object 909 * @param projectKey the key of the project, or null to put the bitmap in the framework cache. 910 */ 911 static void setCachedBitmap(String value, Bitmap bmp, Object projectKey) { 912 if (projectKey != null) { 913 Map<String, SoftReference<Bitmap>> map = sProjectBitmapCache.get(projectKey); 914 915 if (map == null) { 916 map = new HashMap<String, SoftReference<Bitmap>>(); 917 sProjectBitmapCache.put(projectKey, map); 918 } 919 920 map.put(value, new SoftReference<Bitmap>(bmp)); 921 } else { 922 sFrameworkBitmapCache.put(value, new SoftReference<Bitmap>(bmp)); 923 } 924 } 925 926 /** 927 * Returns the 9 patch for a specific path, from a specific project cache, or from the 928 * framework cache. 929 * @param value the path of the 9 patch 930 * @param projectKey the key of the project, or null to query the framework cache. 931 * @return the cached 9 patch or null if not found. 932 */ 933 static NinePatch getCached9Patch(String value, Object projectKey) { 934 if (projectKey != null) { 935 Map<String, SoftReference<NinePatch>> map = sProject9PatchCache.get(projectKey); 936 937 if (map != null) { 938 SoftReference<NinePatch> ref = map.get(value); 939 if (ref != null) { 940 return ref.get(); 941 } 942 } 943 } else { 944 SoftReference<NinePatch> ref = sFramework9PatchCache.get(value); 945 if (ref != null) { 946 return ref.get(); 947 } 948 } 949 950 return null; 951 } 952 953 /** 954 * Sets a 9 patch in a project cache or in the framework cache. 955 * @param value the path of the 9 patch 956 * @param ninePatch the 9 patch object 957 * @param projectKey the key of the project, or null to put the bitmap in the framework cache. 958 */ 959 static void setCached9Patch(String value, NinePatch ninePatch, Object projectKey) { 960 if (projectKey != null) { 961 Map<String, SoftReference<NinePatch>> map = sProject9PatchCache.get(projectKey); 962 963 if (map == null) { 964 map = new HashMap<String, SoftReference<NinePatch>>(); 965 sProject9PatchCache.put(projectKey, map); 966 } 967 968 map.put(value, new SoftReference<NinePatch>(ninePatch)); 969 } else { 970 sFramework9PatchCache.put(value, new SoftReference<NinePatch>(ninePatch)); 971 } 972 } 973 974 private static final class PostInflateException extends Exception { 975 private static final long serialVersionUID = 1L; 976 977 public PostInflateException(String message) { 978 super(message); 979 } 980 } 981 982 /** 983 * Implementation of {@link IWindowSession} so that mSession is not null in 984 * the {@link SurfaceView}. 985 */ 986 private static final class WindowSession implements IWindowSession { 987 988 @SuppressWarnings("unused") 989 public int add(IWindow arg0, LayoutParams arg1, int arg2, Rect arg3) 990 throws RemoteException { 991 // pass for now. 992 return 0; 993 } 994 995 @SuppressWarnings("unused") 996 public void finishDrawing(IWindow arg0) throws RemoteException { 997 // pass for now. 998 } 999 1000 @SuppressWarnings("unused") 1001 public void finishKey(IWindow arg0) throws RemoteException { 1002 // pass for now. 1003 } 1004 1005 @SuppressWarnings("unused") 1006 public boolean getInTouchMode() throws RemoteException { 1007 // pass for now. 1008 return false; 1009 } 1010 1011 @SuppressWarnings("unused") 1012 public boolean performHapticFeedback(IWindow window, int effectId, boolean always) { 1013 // pass for now. 1014 return false; 1015 } 1016 1017 @SuppressWarnings("unused") 1018 public MotionEvent getPendingPointerMove(IWindow arg0) throws RemoteException { 1019 // pass for now. 1020 return null; 1021 } 1022 1023 @SuppressWarnings("unused") 1024 public MotionEvent getPendingTrackballMove(IWindow arg0) throws RemoteException { 1025 // pass for now. 1026 return null; 1027 } 1028 1029 @SuppressWarnings("unused") 1030 public int relayout(IWindow arg0, LayoutParams arg1, int arg2, int arg3, int arg4, 1031 boolean arg4_5, Rect arg5, Rect arg6, Rect arg7, Surface arg8) 1032 throws RemoteException { 1033 // pass for now. 1034 return 0; 1035 } 1036 1037 public void getDisplayFrame(IWindow window, Rect outDisplayFrame) { 1038 // pass for now. 1039 } 1040 1041 @SuppressWarnings("unused") 1042 public void remove(IWindow arg0) throws RemoteException { 1043 // pass for now. 1044 } 1045 1046 @SuppressWarnings("unused") 1047 public void setInTouchMode(boolean arg0) throws RemoteException { 1048 // pass for now. 1049 } 1050 1051 @SuppressWarnings("unused") 1052 public void setTransparentRegion(IWindow arg0, Region arg1) throws RemoteException { 1053 // pass for now. 1054 } 1055 1056 @SuppressWarnings("unused") 1057 public void setInsets(IWindow window, int touchable, Rect contentInsets, 1058 Rect visibleInsets) { 1059 // pass for now. 1060 } 1061 1062 @SuppressWarnings("unused") 1063 public void setWallpaperPosition(IBinder window, float x, float y, 1064 float xStep, float yStep) { 1065 // pass for now. 1066 } 1067 1068 @SuppressWarnings("unused") 1069 public void wallpaperOffsetsComplete(IBinder window) { 1070 // pass for now. 1071 } 1072 1073 @SuppressWarnings("unused") 1074 public Bundle sendWallpaperCommand(IBinder window, String action, int x, int y, 1075 int z, Bundle extras, boolean sync) { 1076 // pass for now. 1077 return null; 1078 } 1079 1080 @SuppressWarnings("unused") 1081 public void wallpaperCommandComplete(IBinder window, Bundle result) { 1082 // pass for now. 1083 } 1084 1085 @SuppressWarnings("unused") 1086 public void closeSystemDialogs(String reason) { 1087 // pass for now. 1088 } 1089 1090 public IBinder asBinder() { 1091 // pass for now. 1092 return null; 1093 } 1094 } 1095 1096 /** 1097 * Implementation of {@link IWindow} to pass to the {@link AttachInfo}. 1098 */ 1099 private static final class Window implements IWindow { 1100 1101 @SuppressWarnings("unused") 1102 public void dispatchAppVisibility(boolean arg0) throws RemoteException { 1103 // pass for now. 1104 } 1105 1106 @SuppressWarnings("unused") 1107 public void dispatchGetNewSurface() throws RemoteException { 1108 // pass for now. 1109 } 1110 1111 @SuppressWarnings("unused") 1112 public void dispatchKey(KeyEvent arg0) throws RemoteException { 1113 // pass for now. 1114 } 1115 1116 @SuppressWarnings("unused") 1117 public void dispatchPointer(MotionEvent arg0, long arg1, boolean arg2) throws RemoteException { 1118 // pass for now. 1119 } 1120 1121 @SuppressWarnings("unused") 1122 public void dispatchTrackball(MotionEvent arg0, long arg1, boolean arg2) throws RemoteException { 1123 // pass for now. 1124 } 1125 1126 @SuppressWarnings("unused") 1127 public void executeCommand(String arg0, String arg1, ParcelFileDescriptor arg2) 1128 throws RemoteException { 1129 // pass for now. 1130 } 1131 1132 @SuppressWarnings("unused") 1133 public void resized(int arg0, int arg1, Rect arg2, Rect arg3, boolean arg4) 1134 throws RemoteException { 1135 // pass for now. 1136 } 1137 1138 @SuppressWarnings("unused") 1139 public void windowFocusChanged(boolean arg0, boolean arg1) throws RemoteException { 1140 // pass for now. 1141 } 1142 1143 @SuppressWarnings("unused") 1144 public void dispatchWallpaperOffsets(float x, float y, float xStep, float yStep, 1145 boolean sync) { 1146 // pass for now. 1147 } 1148 1149 @SuppressWarnings("unused") 1150 public void dispatchWallpaperCommand(String action, int x, int y, 1151 int z, Bundle extras, boolean sync) { 1152 // pass for now. 1153 } 1154 1155 @SuppressWarnings("unused") 1156 public void closeSystemDialogs(String reason) { 1157 // pass for now. 1158 } 1159 1160 public IBinder asBinder() { 1161 // pass for now. 1162 return null; 1163 } 1164 } 1165 1166} 1167