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