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