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