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