RenderSessionImpl.java revision 04ce81113107d2bfa0b8248b13145b4cf24cb943
1/* 2 * Copyright (C) 2010 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.impl; 18 19import static com.android.ide.common.rendering.api.Result.Status.ERROR_ANIM_NOT_FOUND; 20import static com.android.ide.common.rendering.api.Result.Status.ERROR_INFLATION; 21import static com.android.ide.common.rendering.api.Result.Status.ERROR_NOT_INFLATED; 22import static com.android.ide.common.rendering.api.Result.Status.ERROR_UNKNOWN; 23import static com.android.ide.common.rendering.api.Result.Status.ERROR_VIEWGROUP_NO_CHILDREN; 24import static com.android.ide.common.rendering.api.Result.Status.SUCCESS; 25 26import com.android.ide.common.rendering.api.AdapterBinding; 27import com.android.ide.common.rendering.api.IAnimationListener; 28import com.android.ide.common.rendering.api.ILayoutPullParser; 29import com.android.ide.common.rendering.api.IProjectCallback; 30import com.android.ide.common.rendering.api.RenderParams; 31import com.android.ide.common.rendering.api.RenderResources; 32import com.android.ide.common.rendering.api.RenderSession; 33import com.android.ide.common.rendering.api.ResourceReference; 34import com.android.ide.common.rendering.api.ResourceValue; 35import com.android.ide.common.rendering.api.Result; 36import com.android.ide.common.rendering.api.SessionParams; 37import com.android.ide.common.rendering.api.ViewInfo; 38import com.android.ide.common.rendering.api.Result.Status; 39import com.android.ide.common.rendering.api.SessionParams.RenderingMode; 40import com.android.internal.util.XmlUtils; 41import com.android.layoutlib.bridge.Bridge; 42import com.android.layoutlib.bridge.android.BridgeContext; 43import com.android.layoutlib.bridge.android.BridgeInflater; 44import com.android.layoutlib.bridge.android.BridgeLayoutParamsMapAttributes; 45import com.android.layoutlib.bridge.android.BridgeWindow; 46import com.android.layoutlib.bridge.android.BridgeWindowSession; 47import com.android.layoutlib.bridge.android.BridgeXmlBlockParser; 48import com.android.layoutlib.bridge.bars.FakeActionBar; 49import com.android.layoutlib.bridge.bars.PhoneSystemBar; 50import com.android.layoutlib.bridge.bars.TabletSystemBar; 51import com.android.layoutlib.bridge.bars.TitleBar; 52import com.android.layoutlib.bridge.impl.binding.FakeAdapter; 53import com.android.layoutlib.bridge.impl.binding.FakeExpandableAdapter; 54import com.android.resources.ResourceType; 55import com.android.resources.ScreenSize; 56import com.android.util.Pair; 57 58import org.xmlpull.v1.XmlPullParserException; 59 60import android.animation.Animator; 61import android.animation.AnimatorInflater; 62import android.animation.LayoutTransition; 63import android.animation.LayoutTransition.TransitionListener; 64import android.app.Fragment_Delegate; 65import android.graphics.Bitmap; 66import android.graphics.Bitmap_Delegate; 67import android.graphics.Canvas; 68import android.graphics.drawable.Drawable; 69import android.os.Handler; 70import android.util.DisplayMetrics; 71import android.util.TypedValue; 72import android.view.View; 73import android.view.ViewGroup; 74import android.view.View.AttachInfo; 75import android.view.View.MeasureSpec; 76import android.view.ViewGroup.LayoutParams; 77import android.view.ViewGroup.MarginLayoutParams; 78import android.widget.AbsListView; 79import android.widget.AbsSpinner; 80import android.widget.AdapterView; 81import android.widget.ExpandableListView; 82import android.widget.FrameLayout; 83import android.widget.LinearLayout; 84import android.widget.ListView; 85import android.widget.QuickContactBadge; 86import android.widget.TabHost; 87import android.widget.TabWidget; 88import android.widget.TabHost.TabSpec; 89 90import java.awt.AlphaComposite; 91import java.awt.Color; 92import java.awt.Graphics2D; 93import java.awt.image.BufferedImage; 94import java.util.ArrayList; 95import java.util.List; 96import java.util.Map; 97 98/** 99 * Class implementing the render session. 100 * 101 * A session is a stateful representation of a layout file. It is initialized with data coming 102 * through the {@link Bridge} API to inflate the layout. Further actions and rendering can then 103 * be done on the layout. 104 * 105 */ 106public class RenderSessionImpl extends RenderAction<SessionParams> { 107 108 private static final int DEFAULT_TITLE_BAR_HEIGHT = 25; 109 private static final int DEFAULT_STATUS_BAR_HEIGHT = 25; 110 111 // scene state 112 private RenderSession mScene; 113 private BridgeXmlBlockParser mBlockParser; 114 private BridgeInflater mInflater; 115 private ResourceValue mWindowBackground; 116 private ViewGroup mViewRoot; 117 private FrameLayout mContentRoot; 118 private Canvas mCanvas; 119 private int mMeasuredScreenWidth = -1; 120 private int mMeasuredScreenHeight = -1; 121 private boolean mIsAlphaChannelImage; 122 private boolean mWindowIsFloating; 123 124 private int mStatusBarSize; 125 private int mSystemBarSize; 126 private int mTitleBarSize; 127 private int mActionBarSize; 128 129 130 // information being returned through the API 131 private BufferedImage mImage; 132 private List<ViewInfo> mViewInfoList; 133 134 private static final class PostInflateException extends Exception { 135 private static final long serialVersionUID = 1L; 136 137 public PostInflateException(String message) { 138 super(message); 139 } 140 } 141 142 /** 143 * Creates a layout scene with all the information coming from the layout bridge API. 144 * <p> 145 * This <b>must</b> be followed by a call to {@link RenderSessionImpl#init()}, which act as a 146 * call to {@link RenderSessionImpl#acquire(long)} 147 * 148 * @see LayoutBridge#createScene(com.android.layoutlib.api.SceneParams) 149 */ 150 public RenderSessionImpl(SessionParams params) { 151 super(new SessionParams(params)); 152 } 153 154 /** 155 * Initializes and acquires the scene, creating various Android objects such as context, 156 * inflater, and parser. 157 * 158 * @param timeout the time to wait if another rendering is happening. 159 * 160 * @return whether the scene was prepared 161 * 162 * @see #acquire(long) 163 * @see #release() 164 */ 165 @Override 166 public Result init(long timeout) { 167 Result result = super.init(timeout); 168 if (result.isSuccess() == false) { 169 return result; 170 } 171 172 SessionParams params = getParams(); 173 BridgeContext context = getContext(); 174 175 RenderResources resources = getParams().getResources(); 176 DisplayMetrics metrics = getContext().getMetrics(); 177 178 // use default of true in case it's not found to use alpha by default 179 mIsAlphaChannelImage = getBooleanThemeValue(resources, 180 "windowIsFloating", true /*defaultValue*/); 181 182 mWindowIsFloating = getBooleanThemeValue(resources, "windowIsFloating", 183 true /*defaultValue*/); 184 185 findBackground(resources); 186 findStatusBar(resources, metrics); 187 findActionBar(resources, metrics); 188 findSystemBar(resources, metrics); 189 190 // build the inflater and parser. 191 mInflater = new BridgeInflater(context, params.getProjectCallback()); 192 context.setBridgeInflater(mInflater); 193 mInflater.setFactory2(context); 194 195 mBlockParser = new BridgeXmlBlockParser( 196 params.getLayoutDescription(), context, false /* platformResourceFlag */); 197 198 return SUCCESS.createResult(); 199 } 200 201 /** 202 * Inflates the layout. 203 * <p> 204 * {@link #acquire(long)} must have been called before this. 205 * 206 * @throws IllegalStateException if the current context is different than the one owned by 207 * the scene, or if {@link #init(long)} was not called. 208 */ 209 public Result inflate() { 210 checkLock(); 211 212 try { 213 214 SessionParams params = getParams(); 215 BridgeContext context = getContext(); 216 217 // the view group that receives the window background. 218 ViewGroup backgroundView = null; 219 220 if (mWindowIsFloating || params.isForceNoDecor()) { 221 backgroundView = mViewRoot = mContentRoot = new FrameLayout(context); 222 } else { 223 /* 224 * we're creating the following layout 225 * 226 +-------------------------------------------------+ 227 | System bar (only in phone UI) | 228 +-------------------------------------------------+ 229 | (Layout with background drawable) | 230 | +---------------------------------------------+ | 231 | | Title/Action bar (optional) | | 232 | +---------------------------------------------+ | 233 | | Content, vertical extending | | 234 | | | | 235 | +---------------------------------------------+ | 236 +-------------------------------------------------+ 237 | System bar (only in tablet UI) | 238 +-------------------------------------------------+ 239 240 */ 241 242 LinearLayout topLayout = new LinearLayout(context); 243 mViewRoot = topLayout; 244 topLayout.setOrientation(LinearLayout.VERTICAL); 245 246 if (mStatusBarSize > 0) { 247 // system bar 248 try { 249 PhoneSystemBar systemBar = new PhoneSystemBar(context, 250 params.getDensity()); 251 systemBar.setLayoutParams( 252 new LinearLayout.LayoutParams( 253 LayoutParams.MATCH_PARENT, mStatusBarSize)); 254 topLayout.addView(systemBar); 255 } catch (XmlPullParserException e) { 256 257 } 258 } 259 260 LinearLayout backgroundLayout = new LinearLayout(context); 261 backgroundView = backgroundLayout; 262 backgroundLayout.setOrientation(LinearLayout.VERTICAL); 263 LinearLayout.LayoutParams layoutParams = new LinearLayout.LayoutParams( 264 LayoutParams.MATCH_PARENT, LayoutParams.WRAP_CONTENT); 265 layoutParams.weight = 1; 266 backgroundLayout.setLayoutParams(layoutParams); 267 topLayout.addView(backgroundLayout); 268 269 270 // if the theme says no title/action bar, then the size will be 0 271 if (mActionBarSize > 0) { 272 try { 273 FakeActionBar actionBar = new FakeActionBar(context, 274 params.getDensity(), 275 params.getAppLabel(), params.getAppIcon()); 276 actionBar.setLayoutParams( 277 new LinearLayout.LayoutParams( 278 LayoutParams.MATCH_PARENT, mActionBarSize)); 279 backgroundLayout.addView(actionBar); 280 } catch (XmlPullParserException e) { 281 282 } 283 } else if (mTitleBarSize > 0) { 284 try { 285 TitleBar titleBar = new TitleBar(context, 286 params.getDensity(), params.getAppLabel()); 287 titleBar.setLayoutParams( 288 new LinearLayout.LayoutParams( 289 LayoutParams.MATCH_PARENT, mTitleBarSize)); 290 backgroundLayout.addView(titleBar); 291 } catch (XmlPullParserException e) { 292 293 } 294 } 295 296 // content frame 297 mContentRoot = new FrameLayout(context); 298 layoutParams = new LinearLayout.LayoutParams( 299 LayoutParams.MATCH_PARENT, LayoutParams.WRAP_CONTENT); 300 layoutParams.weight = 1; 301 mContentRoot.setLayoutParams(layoutParams); 302 backgroundLayout.addView(mContentRoot); 303 304 if (mSystemBarSize > 0) { 305 // system bar 306 try { 307 TabletSystemBar systemBar = new TabletSystemBar(context, 308 params.getDensity()); 309 systemBar.setLayoutParams( 310 new LinearLayout.LayoutParams( 311 LayoutParams.MATCH_PARENT, mSystemBarSize)); 312 topLayout.addView(systemBar); 313 } catch (XmlPullParserException e) { 314 315 } 316 } 317 } 318 319 320 // Sets the project callback (custom view loader) to the fragment delegate so that 321 // it can instantiate the custom Fragment. 322 Fragment_Delegate.setProjectCallback(params.getProjectCallback()); 323 324 View view = mInflater.inflate(mBlockParser, mContentRoot); 325 326 // done with the parser, pop it. 327 context.popParser(); 328 329 Fragment_Delegate.setProjectCallback(null); 330 331 // set the AttachInfo on the root view. 332 AttachInfo info = new AttachInfo(new BridgeWindowSession(), new BridgeWindow(), 333 new Handler(), null); 334 info.mHasWindowFocus = true; 335 info.mWindowVisibility = View.VISIBLE; 336 info.mInTouchMode = false; // this is so that we can display selections. 337 info.mHardwareAccelerated = false; 338 mViewRoot.dispatchAttachedToWindow(info, 0); 339 340 // post-inflate process. For now this supports TabHost/TabWidget 341 postInflateProcess(view, params.getProjectCallback()); 342 343 // get the background drawable 344 if (mWindowBackground != null && backgroundView != null) { 345 Drawable d = ResourceHelper.getDrawable(mWindowBackground, context); 346 backgroundView.setBackgroundDrawable(d); 347 } 348 349 return SUCCESS.createResult(); 350 } catch (PostInflateException e) { 351 return ERROR_INFLATION.createResult(e.getMessage(), e); 352 } catch (Throwable e) { 353 // get the real cause of the exception. 354 Throwable t = e; 355 while (t.getCause() != null) { 356 t = t.getCause(); 357 } 358 359 return ERROR_INFLATION.createResult(t.getMessage(), t); 360 } 361 } 362 363 /** 364 * Renders the scene. 365 * <p> 366 * {@link #acquire(long)} must have been called before this. 367 * 368 * @param freshRender whether the render is a new one and should erase the existing bitmap (in 369 * the case where bitmaps are reused). This is typically needed when not playing 370 * animations.) 371 * 372 * @throws IllegalStateException if the current context is different than the one owned by 373 * the scene, or if {@link #acquire(long)} was not called. 374 * 375 * @see RenderParams#getRenderingMode() 376 * @see RenderSession#render(long) 377 */ 378 public Result render(boolean freshRender) { 379 checkLock(); 380 381 SessionParams params = getParams(); 382 383 try { 384 if (mViewRoot == null) { 385 return ERROR_NOT_INFLATED.createResult(); 386 } 387 388 RenderingMode renderingMode = params.getRenderingMode(); 389 390 // only do the screen measure when needed. 391 boolean newRenderSize = false; 392 if (mMeasuredScreenWidth == -1) { 393 newRenderSize = true; 394 mMeasuredScreenWidth = params.getScreenWidth(); 395 mMeasuredScreenHeight = params.getScreenHeight(); 396 397 if (renderingMode != RenderingMode.NORMAL) { 398 int widthMeasureSpecMode = renderingMode.isHorizExpand() ? 399 MeasureSpec.UNSPECIFIED // this lets us know the actual needed size 400 : MeasureSpec.EXACTLY; 401 int heightMeasureSpecMode = renderingMode.isVertExpand() ? 402 MeasureSpec.UNSPECIFIED // this lets us know the actual needed size 403 : MeasureSpec.EXACTLY; 404 405 // We used to compare the measured size of the content to the screen size but 406 // this does not work anymore due to the 2 following issues: 407 // - If the content is in a decor (system bar, title/action bar), the root view 408 // will not resize even with the UNSPECIFIED because of the embedded layout. 409 // - If there is no decor, but a dialog frame, then the dialog padding prevents 410 // comparing the size of the content to the screen frame (as it would not 411 // take into account the dialog padding). 412 413 // The solution is to first get the content size in a normal rendering, inside 414 // the decor or the dialog padding. 415 // Then measure only the content with UNSPECIFIED to see the size difference 416 // and apply this to the screen size. 417 418 // first measure the full layout, with EXACTLY to get the size of the 419 // content as it is inside the decor/dialog 420 Pair<Integer, Integer> exactMeasure = measureView( 421 mViewRoot, mContentRoot.getChildAt(0), 422 mMeasuredScreenWidth, MeasureSpec.EXACTLY, 423 mMeasuredScreenHeight, MeasureSpec.EXACTLY); 424 425 // now measure the content only using UNSPECIFIED (where applicable, based on 426 // the rendering mode). This will give us the size the content needs. 427 Pair<Integer, Integer> result = measureView( 428 mContentRoot, mContentRoot.getChildAt(0), 429 mMeasuredScreenWidth, widthMeasureSpecMode, 430 mMeasuredScreenHeight, heightMeasureSpecMode); 431 432 // now look at the difference and add what is needed. 433 if (renderingMode.isHorizExpand()) { 434 int measuredWidth = exactMeasure.getFirst(); 435 int neededWidth = result.getFirst(); 436 if (neededWidth > measuredWidth) { 437 mMeasuredScreenWidth += neededWidth - measuredWidth; 438 } 439 } 440 441 if (renderingMode.isVertExpand()) { 442 int measuredHeight = exactMeasure.getSecond(); 443 int neededHeight = result.getSecond(); 444 if (neededHeight > measuredHeight) { 445 mMeasuredScreenHeight += neededHeight - measuredHeight; 446 } 447 } 448 } 449 } 450 451 // measure again with the size we need 452 // This must always be done before the call to layout 453 measureView(mViewRoot, null /*measuredView*/, 454 mMeasuredScreenWidth, MeasureSpec.EXACTLY, 455 mMeasuredScreenHeight, MeasureSpec.EXACTLY); 456 457 // now do the layout. 458 mViewRoot.layout(0, 0, mMeasuredScreenWidth, mMeasuredScreenHeight); 459 460 if (params.isLayoutOnly()) { 461 // delete the canvas and image to reset them on the next full rendering 462 mImage = null; 463 mCanvas = null; 464 } else { 465 mViewRoot.mAttachInfo.mTreeObserver.dispatchOnPreDraw(); 466 467 // draw the views 468 // create the BufferedImage into which the layout will be rendered. 469 boolean newImage = false; 470 if (newRenderSize || mCanvas == null) { 471 if (params.getImageFactory() != null) { 472 mImage = params.getImageFactory().getImage( 473 mMeasuredScreenWidth, 474 mMeasuredScreenHeight); 475 } else { 476 mImage = new BufferedImage( 477 mMeasuredScreenWidth, 478 mMeasuredScreenHeight, 479 BufferedImage.TYPE_INT_ARGB); 480 newImage = true; 481 } 482 483 if (params.isBgColorOverridden()) { 484 // since we override the content, it's the same as if it was a new image. 485 newImage = true; 486 Graphics2D gc = mImage.createGraphics(); 487 gc.setColor(new Color(params.getOverrideBgColor(), true)); 488 gc.setComposite(AlphaComposite.Src); 489 gc.fillRect(0, 0, mMeasuredScreenWidth, mMeasuredScreenHeight); 490 gc.dispose(); 491 } 492 493 // create an Android bitmap around the BufferedImage 494 Bitmap bitmap = Bitmap_Delegate.createBitmap(mImage, 495 true /*isMutable*/, params.getDensity()); 496 497 // create a Canvas around the Android bitmap 498 mCanvas = new Canvas(bitmap); 499 mCanvas.setDensity(params.getDensity().getDpiValue()); 500 } 501 502 if (freshRender && newImage == false) { 503 Graphics2D gc = mImage.createGraphics(); 504 gc.setComposite(AlphaComposite.Src); 505 506 gc.setColor(new Color(0x00000000, true)); 507 gc.fillRect(0, 0, 508 mMeasuredScreenWidth, mMeasuredScreenHeight); 509 510 // done 511 gc.dispose(); 512 } 513 514 mViewRoot.draw(mCanvas); 515 } 516 517 mViewInfoList = startVisitingViews(mViewRoot, 0, params.getExtendedViewInfoMode()); 518 519 // success! 520 return SUCCESS.createResult(); 521 } catch (Throwable e) { 522 // get the real cause of the exception. 523 Throwable t = e; 524 while (t.getCause() != null) { 525 t = t.getCause(); 526 } 527 528 return ERROR_UNKNOWN.createResult(t.getMessage(), t); 529 } 530 } 531 532 /** 533 * Executes {@link View#measure(int, int)} on a given view with the given parameters (used 534 * to create measure specs with {@link MeasureSpec#makeMeasureSpec(int, int)}. 535 * 536 * if <var>measuredView</var> is non null, the method returns a {@link Pair} of (width, height) 537 * for the view (using {@link View#getMeasuredWidth()} and {@link View#getMeasuredHeight()}). 538 * 539 * @param viewToMeasure the view on which to execute measure(). 540 * @param measuredView if non null, the view to query for its measured width/height. 541 * @param width the width to use in the MeasureSpec. 542 * @param widthMode the MeasureSpec mode to use for the width. 543 * @param height the height to use in the MeasureSpec. 544 * @param heightMode the MeasureSpec mode to use for the height. 545 * @return the measured width/height if measuredView is non-null, null otherwise. 546 */ 547 private Pair<Integer, Integer> measureView(ViewGroup viewToMeasure, View measuredView, 548 int width, int widthMode, int height, int heightMode) { 549 int w_spec = MeasureSpec.makeMeasureSpec(width, widthMode); 550 int h_spec = MeasureSpec.makeMeasureSpec(height, heightMode); 551 viewToMeasure.measure(w_spec, h_spec); 552 553 if (measuredView != null) { 554 return Pair.of(measuredView.getMeasuredWidth(), measuredView.getMeasuredHeight()); 555 } 556 557 return null; 558 } 559 560 /** 561 * Animate an object 562 * <p> 563 * {@link #acquire(long)} must have been called before this. 564 * 565 * @throws IllegalStateException if the current context is different than the one owned by 566 * the scene, or if {@link #acquire(long)} was not called. 567 * 568 * @see RenderSession#animate(Object, String, boolean, IAnimationListener) 569 */ 570 public Result animate(Object targetObject, String animationName, 571 boolean isFrameworkAnimation, IAnimationListener listener) { 572 checkLock(); 573 574 BridgeContext context = getContext(); 575 576 // find the animation file. 577 ResourceValue animationResource = null; 578 int animationId = 0; 579 if (isFrameworkAnimation) { 580 animationResource = context.getRenderResources().getFrameworkResource( 581 ResourceType.ANIMATOR, animationName); 582 if (animationResource != null) { 583 animationId = Bridge.getResourceId(ResourceType.ANIMATOR, animationName); 584 } 585 } else { 586 animationResource = context.getRenderResources().getProjectResource( 587 ResourceType.ANIMATOR, animationName); 588 if (animationResource != null) { 589 animationId = context.getProjectCallback().getResourceId( 590 ResourceType.ANIMATOR, animationName); 591 } 592 } 593 594 if (animationResource != null) { 595 try { 596 Animator anim = AnimatorInflater.loadAnimator(context, animationId); 597 if (anim != null) { 598 anim.setTarget(targetObject); 599 600 new PlayAnimationThread(anim, this, animationName, listener).start(); 601 602 return SUCCESS.createResult(); 603 } 604 } catch (Exception e) { 605 // get the real cause of the exception. 606 Throwable t = e; 607 while (t.getCause() != null) { 608 t = t.getCause(); 609 } 610 611 return ERROR_UNKNOWN.createResult(t.getMessage(), t); 612 } 613 } 614 615 return ERROR_ANIM_NOT_FOUND.createResult(); 616 } 617 618 /** 619 * Insert a new child into an existing parent. 620 * <p> 621 * {@link #acquire(long)} must have been called before this. 622 * 623 * @throws IllegalStateException if the current context is different than the one owned by 624 * the scene, or if {@link #acquire(long)} was not called. 625 * 626 * @see RenderSession#insertChild(Object, ILayoutPullParser, int, IAnimationListener) 627 */ 628 public Result insertChild(final ViewGroup parentView, ILayoutPullParser childXml, 629 final int index, IAnimationListener listener) { 630 checkLock(); 631 632 BridgeContext context = getContext(); 633 634 // create a block parser for the XML 635 BridgeXmlBlockParser blockParser = new BridgeXmlBlockParser( 636 childXml, context, false /* platformResourceFlag */); 637 638 // inflate the child without adding it to the root since we want to control where it'll 639 // get added. We do pass the parentView however to ensure that the layoutParams will 640 // be created correctly. 641 final View child = mInflater.inflate(blockParser, parentView, false /*attachToRoot*/); 642 blockParser.ensurePopped(); 643 644 invalidateRenderingSize(); 645 646 if (listener != null) { 647 new AnimationThread(this, "insertChild", listener) { 648 649 @Override 650 public Result preAnimation() { 651 parentView.setLayoutTransition(new LayoutTransition()); 652 return addView(parentView, child, index); 653 } 654 655 @Override 656 public void postAnimation() { 657 parentView.setLayoutTransition(null); 658 } 659 }.start(); 660 661 // always return success since the real status will come through the listener. 662 return SUCCESS.createResult(child); 663 } 664 665 // add it to the parentView in the correct location 666 Result result = addView(parentView, child, index); 667 if (result.isSuccess() == false) { 668 return result; 669 } 670 671 result = render(false /*freshRender*/); 672 if (result.isSuccess()) { 673 result = result.getCopyWithData(child); 674 } 675 676 return result; 677 } 678 679 /** 680 * Adds a given view to a given parent at a given index. 681 * 682 * @param parent the parent to receive the view 683 * @param view the view to add to the parent 684 * @param index the index where to do the add. 685 * 686 * @return a Result with {@link Status#SUCCESS} or 687 * {@link Status#ERROR_VIEWGROUP_NO_CHILDREN} if the given parent doesn't support 688 * adding views. 689 */ 690 private Result addView(ViewGroup parent, View view, int index) { 691 try { 692 parent.addView(view, index); 693 return SUCCESS.createResult(); 694 } catch (UnsupportedOperationException e) { 695 // looks like this is a view class that doesn't support children manipulation! 696 return ERROR_VIEWGROUP_NO_CHILDREN.createResult(); 697 } 698 } 699 700 /** 701 * Moves a view to a new parent at a given location 702 * <p> 703 * {@link #acquire(long)} must have been called before this. 704 * 705 * @throws IllegalStateException if the current context is different than the one owned by 706 * the scene, or if {@link #acquire(long)} was not called. 707 * 708 * @see RenderSession#moveChild(Object, Object, int, Map, IAnimationListener) 709 */ 710 public Result moveChild(final ViewGroup newParentView, final View childView, final int index, 711 Map<String, String> layoutParamsMap, final IAnimationListener listener) { 712 checkLock(); 713 714 invalidateRenderingSize(); 715 716 LayoutParams layoutParams = null; 717 if (layoutParamsMap != null) { 718 // need to create a new LayoutParams object for the new parent. 719 layoutParams = newParentView.generateLayoutParams( 720 new BridgeLayoutParamsMapAttributes(layoutParamsMap)); 721 } 722 723 // get the current parent of the view that needs to be moved. 724 final ViewGroup previousParent = (ViewGroup) childView.getParent(); 725 726 if (listener != null) { 727 final LayoutParams params = layoutParams; 728 729 // there is no support for animating views across layouts, so in case the new and old 730 // parent views are different we fake the animation through a no animation thread. 731 if (previousParent != newParentView) { 732 new Thread("not animated moveChild") { 733 @Override 734 public void run() { 735 Result result = moveView(previousParent, newParentView, childView, index, 736 params); 737 if (result.isSuccess() == false) { 738 listener.done(result); 739 } 740 741 // ready to do the work, acquire the scene. 742 result = acquire(250); 743 if (result.isSuccess() == false) { 744 listener.done(result); 745 return; 746 } 747 748 try { 749 result = render(false /*freshRender*/); 750 if (result.isSuccess()) { 751 listener.onNewFrame(RenderSessionImpl.this.getSession()); 752 } 753 } finally { 754 release(); 755 } 756 757 listener.done(result); 758 } 759 }.start(); 760 } else { 761 new AnimationThread(this, "moveChild", listener) { 762 763 @Override 764 public Result preAnimation() { 765 // set up the transition for the parent. 766 LayoutTransition transition = new LayoutTransition(); 767 previousParent.setLayoutTransition(transition); 768 769 // tweak the animation durations and start delays (to match the duration of 770 // animation playing just before). 771 // Note: Cannot user Animation.setDuration() directly. Have to set it 772 // on the LayoutTransition. 773 transition.setDuration(LayoutTransition.DISAPPEARING, 100); 774 // CHANGE_DISAPPEARING plays after DISAPPEARING 775 transition.setStartDelay(LayoutTransition.CHANGE_DISAPPEARING, 100); 776 777 transition.setDuration(LayoutTransition.CHANGE_DISAPPEARING, 100); 778 779 transition.setDuration(LayoutTransition.CHANGE_APPEARING, 100); 780 // CHANGE_APPEARING plays after CHANGE_APPEARING 781 transition.setStartDelay(LayoutTransition.APPEARING, 100); 782 783 transition.setDuration(LayoutTransition.APPEARING, 100); 784 785 return moveView(previousParent, newParentView, childView, index, params); 786 } 787 788 @Override 789 public void postAnimation() { 790 previousParent.setLayoutTransition(null); 791 newParentView.setLayoutTransition(null); 792 } 793 }.start(); 794 } 795 796 // always return success since the real status will come through the listener. 797 return SUCCESS.createResult(layoutParams); 798 } 799 800 Result result = moveView(previousParent, newParentView, childView, index, layoutParams); 801 if (result.isSuccess() == false) { 802 return result; 803 } 804 805 result = render(false /*freshRender*/); 806 if (layoutParams != null && result.isSuccess()) { 807 result = result.getCopyWithData(layoutParams); 808 } 809 810 return result; 811 } 812 813 /** 814 * Moves a View from its current parent to a new given parent at a new given location, with 815 * an optional new {@link LayoutParams} instance 816 * 817 * @param previousParent the previous parent, still owning the child at the time of the call. 818 * @param newParent the new parent 819 * @param movedView the view to move 820 * @param index the new location in the new parent 821 * @param params an option (can be null) {@link LayoutParams} instance. 822 * 823 * @return a Result with {@link Status#SUCCESS} or 824 * {@link Status#ERROR_VIEWGROUP_NO_CHILDREN} if the given parent doesn't support 825 * adding views. 826 */ 827 private Result moveView(ViewGroup previousParent, final ViewGroup newParent, 828 final View movedView, final int index, final LayoutParams params) { 829 try { 830 // check if there is a transition on the previousParent. 831 LayoutTransition previousTransition = previousParent.getLayoutTransition(); 832 if (previousTransition != null) { 833 // in this case there is an animation. This means we have to wait for the child's 834 // parent reference to be null'ed out so that we can add it to the new parent. 835 // It is technically removed right before the DISAPPEARING animation is done (if 836 // the animation of this type is not null, otherwise it's after which is impossible 837 // to handle). 838 // Because there is no move animation, if the new parent is the same as the old 839 // parent, we need to wait until the CHANGE_DISAPPEARING animation is done before 840 // adding the child or the child will appear in its new location before the 841 // other children have made room for it. 842 843 // add a listener to the transition to be notified of the actual removal. 844 previousTransition.addTransitionListener(new TransitionListener() { 845 private int mChangeDisappearingCount = 0; 846 847 public void startTransition(LayoutTransition transition, ViewGroup container, 848 View view, int transitionType) { 849 if (transitionType == LayoutTransition.CHANGE_DISAPPEARING) { 850 mChangeDisappearingCount++; 851 } 852 } 853 854 public void endTransition(LayoutTransition transition, ViewGroup container, 855 View view, int transitionType) { 856 if (transitionType == LayoutTransition.CHANGE_DISAPPEARING) { 857 mChangeDisappearingCount--; 858 } 859 860 if (transitionType == LayoutTransition.CHANGE_DISAPPEARING && 861 mChangeDisappearingCount == 0) { 862 // add it to the parentView in the correct location 863 if (params != null) { 864 newParent.addView(movedView, index, params); 865 } else { 866 newParent.addView(movedView, index); 867 } 868 } 869 } 870 }); 871 872 // remove the view from the current parent. 873 previousParent.removeView(movedView); 874 875 // and return since adding the view to the new parent is done in the listener. 876 return SUCCESS.createResult(); 877 } else { 878 // standard code with no animation. pretty simple. 879 previousParent.removeView(movedView); 880 881 // add it to the parentView in the correct location 882 if (params != null) { 883 newParent.addView(movedView, index, params); 884 } else { 885 newParent.addView(movedView, index); 886 } 887 888 return SUCCESS.createResult(); 889 } 890 } catch (UnsupportedOperationException e) { 891 // looks like this is a view class that doesn't support children manipulation! 892 return ERROR_VIEWGROUP_NO_CHILDREN.createResult(); 893 } 894 } 895 896 /** 897 * Removes a child from its current parent. 898 * <p> 899 * {@link #acquire(long)} must have been called before this. 900 * 901 * @throws IllegalStateException if the current context is different than the one owned by 902 * the scene, or if {@link #acquire(long)} was not called. 903 * 904 * @see RenderSession#removeChild(Object, IAnimationListener) 905 */ 906 public Result removeChild(final View childView, IAnimationListener listener) { 907 checkLock(); 908 909 invalidateRenderingSize(); 910 911 final ViewGroup parent = (ViewGroup) childView.getParent(); 912 913 if (listener != null) { 914 new AnimationThread(this, "moveChild", listener) { 915 916 @Override 917 public Result preAnimation() { 918 parent.setLayoutTransition(new LayoutTransition()); 919 return removeView(parent, childView); 920 } 921 922 @Override 923 public void postAnimation() { 924 parent.setLayoutTransition(null); 925 } 926 }.start(); 927 928 // always return success since the real status will come through the listener. 929 return SUCCESS.createResult(); 930 } 931 932 Result result = removeView(parent, childView); 933 if (result.isSuccess() == false) { 934 return result; 935 } 936 937 return render(false /*freshRender*/); 938 } 939 940 /** 941 * Removes a given view from its current parent. 942 * 943 * @param view the view to remove from its parent 944 * 945 * @return a Result with {@link Status#SUCCESS} or 946 * {@link Status#ERROR_VIEWGROUP_NO_CHILDREN} if the given parent doesn't support 947 * adding views. 948 */ 949 private Result removeView(ViewGroup parent, View view) { 950 try { 951 parent.removeView(view); 952 return SUCCESS.createResult(); 953 } catch (UnsupportedOperationException e) { 954 // looks like this is a view class that doesn't support children manipulation! 955 return ERROR_VIEWGROUP_NO_CHILDREN.createResult(); 956 } 957 } 958 959 960 private void findBackground(RenderResources resources) { 961 if (getParams().isBgColorOverridden() == false) { 962 mWindowBackground = resources.findItemInTheme("windowBackground"); 963 if (mWindowBackground != null) { 964 mWindowBackground = resources.resolveResValue(mWindowBackground); 965 } 966 } 967 } 968 969 private boolean isTabletUi() { 970 return getParams().getConfigScreenSize() == ScreenSize.XLARGE; 971 } 972 973 private void findStatusBar(RenderResources resources, DisplayMetrics metrics) { 974 if (isTabletUi() == false) { 975 boolean windowFullscreen = getBooleanThemeValue(resources, 976 "windowFullscreen", false /*defaultValue*/); 977 978 if (windowFullscreen == false && mWindowIsFloating == false) { 979 // default value 980 mStatusBarSize = DEFAULT_STATUS_BAR_HEIGHT; 981 982 // get the real value 983 ResourceValue value = resources.getFrameworkResource(ResourceType.DIMEN, 984 "status_bar_height"); 985 986 if (value != null) { 987 TypedValue typedValue = ResourceHelper.getValue("status_bar_height", 988 value.getValue(), true /*requireUnit*/); 989 if (typedValue != null) { 990 // compute the pixel value based on the display metrics 991 mStatusBarSize = (int)typedValue.getDimension(metrics); 992 } 993 } 994 } 995 } 996 } 997 998 private void findActionBar(RenderResources resources, DisplayMetrics metrics) { 999 if (mWindowIsFloating) { 1000 return; 1001 } 1002 1003 boolean windowActionBar = getBooleanThemeValue(resources, 1004 "windowActionBar", true /*defaultValue*/); 1005 1006 // if there's a value and it's false (default is true) 1007 if (windowActionBar) { 1008 1009 // default size of the window title bar 1010 mActionBarSize = DEFAULT_TITLE_BAR_HEIGHT; 1011 1012 // get value from the theme. 1013 ResourceValue value = resources.findItemInTheme("actionBarSize"); 1014 1015 // resolve it 1016 value = resources.resolveResValue(value); 1017 1018 if (value != null) { 1019 // get the numerical value, if available 1020 TypedValue typedValue = ResourceHelper.getValue("actionBarSize", value.getValue(), 1021 true /*requireUnit*/); 1022 if (typedValue != null) { 1023 // compute the pixel value based on the display metrics 1024 mActionBarSize = (int)typedValue.getDimension(metrics); 1025 } 1026 } 1027 } else { 1028 // action bar overrides title bar so only look for this one if action bar is hidden 1029 boolean windowNoTitle = getBooleanThemeValue(resources, 1030 "windowNoTitle", false /*defaultValue*/); 1031 1032 if (windowNoTitle == false) { 1033 1034 // default size of the window title bar 1035 mTitleBarSize = DEFAULT_TITLE_BAR_HEIGHT; 1036 1037 // get value from the theme. 1038 ResourceValue value = resources.findItemInTheme("windowTitleSize"); 1039 1040 // resolve it 1041 value = resources.resolveResValue(value); 1042 1043 if (value != null) { 1044 // get the numerical value, if available 1045 TypedValue typedValue = ResourceHelper.getValue("windowTitleSize", 1046 value.getValue(), true /*requireUnit*/); 1047 if (typedValue != null) { 1048 // compute the pixel value based on the display metrics 1049 mTitleBarSize = (int)typedValue.getDimension(metrics); 1050 } 1051 } 1052 } 1053 1054 } 1055 } 1056 1057 private void findSystemBar(RenderResources resources, DisplayMetrics metrics) { 1058 if (isTabletUi() && mWindowIsFloating == false) { 1059 1060 // default value 1061 mSystemBarSize = 48; // ?? 1062 1063 // get the real value 1064 ResourceValue value = resources.getFrameworkResource(ResourceType.DIMEN, 1065 "status_bar_height"); 1066 1067 if (value != null) { 1068 TypedValue typedValue = ResourceHelper.getValue("status_bar_height", 1069 value.getValue(), true /*requireUnit*/); 1070 if (typedValue != null) { 1071 // compute the pixel value based on the display metrics 1072 mSystemBarSize = (int)typedValue.getDimension(metrics); 1073 } 1074 } 1075 } 1076 } 1077 1078 private boolean getBooleanThemeValue(RenderResources resources, 1079 String name, boolean defaultValue) { 1080 1081 // get the title bar flag from the current theme. 1082 ResourceValue value = resources.findItemInTheme(name); 1083 1084 // because it may reference something else, we resolve it. 1085 value = resources.resolveResValue(value); 1086 1087 // if there's no value, return the default. 1088 if (value == null || value.getValue() == null) { 1089 return defaultValue; 1090 } 1091 1092 return XmlUtils.convertValueToBoolean(value.getValue(), defaultValue); 1093 } 1094 1095 /** 1096 * Post process on a view hierachy that was just inflated. 1097 * <p/>At the moment this only support TabHost: If {@link TabHost} is detected, look for the 1098 * {@link TabWidget}, and the corresponding {@link FrameLayout} and make new tabs automatically 1099 * based on the content of the {@link FrameLayout}. 1100 * @param view the root view to process. 1101 * @param projectCallback callback to the project. 1102 */ 1103 private void postInflateProcess(View view, IProjectCallback projectCallback) 1104 throws PostInflateException { 1105 if (view instanceof TabHost) { 1106 setupTabHost((TabHost)view, projectCallback); 1107 } else if (view instanceof QuickContactBadge) { 1108 QuickContactBadge badge = (QuickContactBadge) view; 1109 badge.setImageToDefault(); 1110 } else if (view instanceof AdapterView<?>) { 1111 // get the view ID. 1112 int id = view.getId(); 1113 1114 BridgeContext context = getContext(); 1115 1116 // get a ResourceReference from the integer ID. 1117 ResourceReference listRef = context.resolveId(id); 1118 1119 if (listRef != null) { 1120 SessionParams params = getParams(); 1121 AdapterBinding binding = params.getAdapterBindings().get(listRef); 1122 1123 // if there was no adapter binding, trying to get it from the call back. 1124 if (binding == null) { 1125 binding = params.getProjectCallback().getAdapterBinding(listRef, 1126 context.getViewKey(view), view); 1127 } 1128 1129 if (binding != null) { 1130 1131 if (view instanceof AbsListView) { 1132 if ((binding.getFooterCount() > 0 || binding.getHeaderCount() > 0) && 1133 view instanceof ListView) { 1134 ListView list = (ListView) view; 1135 1136 boolean skipCallbackParser = false; 1137 1138 int count = binding.getHeaderCount(); 1139 for (int i = 0 ; i < count ; i++) { 1140 Pair<View, Boolean> pair = context.inflateView( 1141 binding.getHeaderAt(i), 1142 list, false /*attachToRoot*/, skipCallbackParser); 1143 if (pair.getFirst() != null) { 1144 list.addHeaderView(pair.getFirst()); 1145 } 1146 1147 skipCallbackParser |= pair.getSecond(); 1148 } 1149 1150 count = binding.getFooterCount(); 1151 for (int i = 0 ; i < count ; i++) { 1152 Pair<View, Boolean> pair = context.inflateView( 1153 binding.getFooterAt(i), 1154 list, false /*attachToRoot*/, skipCallbackParser); 1155 if (pair.getFirst() != null) { 1156 list.addFooterView(pair.getFirst()); 1157 } 1158 1159 skipCallbackParser |= pair.getSecond(); 1160 } 1161 } 1162 1163 if (view instanceof ExpandableListView) { 1164 ((ExpandableListView) view).setAdapter( 1165 new FakeExpandableAdapter( 1166 listRef, binding, params.getProjectCallback())); 1167 } else { 1168 ((AbsListView) view).setAdapter( 1169 new FakeAdapter( 1170 listRef, binding, params.getProjectCallback())); 1171 } 1172 } else if (view instanceof AbsSpinner) { 1173 ((AbsSpinner) view).setAdapter( 1174 new FakeAdapter( 1175 listRef, binding, params.getProjectCallback())); 1176 } 1177 } 1178 } 1179 } else if (view instanceof ViewGroup) { 1180 ViewGroup group = (ViewGroup)view; 1181 final int count = group.getChildCount(); 1182 for (int c = 0 ; c < count ; c++) { 1183 View child = group.getChildAt(c); 1184 postInflateProcess(child, projectCallback); 1185 } 1186 } 1187 } 1188 1189 /** 1190 * Sets up a {@link TabHost} object. 1191 * @param tabHost the TabHost to setup. 1192 * @param projectCallback The project callback object to access the project R class. 1193 * @throws PostInflateException 1194 */ 1195 private void setupTabHost(TabHost tabHost, IProjectCallback projectCallback) 1196 throws PostInflateException { 1197 // look for the TabWidget, and the FrameLayout. They have their own specific names 1198 View v = tabHost.findViewById(android.R.id.tabs); 1199 1200 if (v == null) { 1201 throw new PostInflateException( 1202 "TabHost requires a TabWidget with id \"android:id/tabs\".\n"); 1203 } 1204 1205 if ((v instanceof TabWidget) == false) { 1206 throw new PostInflateException(String.format( 1207 "TabHost requires a TabWidget with id \"android:id/tabs\".\n" + 1208 "View found with id 'tabs' is '%s'", v.getClass().getCanonicalName())); 1209 } 1210 1211 v = tabHost.findViewById(android.R.id.tabcontent); 1212 1213 if (v == null) { 1214 // TODO: see if we can fake tabs even without the FrameLayout (same below when the framelayout is empty) 1215 throw new PostInflateException( 1216 "TabHost requires a FrameLayout with id \"android:id/tabcontent\"."); 1217 } 1218 1219 if ((v instanceof FrameLayout) == false) { 1220 throw new PostInflateException(String.format( 1221 "TabHost requires a FrameLayout with id \"android:id/tabcontent\".\n" + 1222 "View found with id 'tabcontent' is '%s'", v.getClass().getCanonicalName())); 1223 } 1224 1225 FrameLayout content = (FrameLayout)v; 1226 1227 // now process the content of the framelayout and dynamically create tabs for it. 1228 final int count = content.getChildCount(); 1229 1230 // this must be called before addTab() so that the TabHost searches its TabWidget 1231 // and FrameLayout. 1232 tabHost.setup(); 1233 1234 if (count == 0) { 1235 // Create a dummy child to get a single tab 1236 TabSpec spec = tabHost.newTabSpec("tag").setIndicator("Tab Label", 1237 tabHost.getResources().getDrawable(android.R.drawable.ic_menu_info_details)) 1238 .setContent(new TabHost.TabContentFactory() { 1239 public View createTabContent(String tag) { 1240 return new LinearLayout(getContext()); 1241 } 1242 }); 1243 tabHost.addTab(spec); 1244 return; 1245 } else { 1246 // for each child of the framelayout, add a new TabSpec 1247 for (int i = 0 ; i < count ; i++) { 1248 View child = content.getChildAt(i); 1249 String tabSpec = String.format("tab_spec%d", i+1); 1250 int id = child.getId(); 1251 Pair<ResourceType, String> resource = projectCallback.resolveResourceId(id); 1252 String name; 1253 if (resource != null) { 1254 name = resource.getSecond(); 1255 } else { 1256 name = String.format("Tab %d", i+1); // default name if id is unresolved. 1257 } 1258 tabHost.addTab(tabHost.newTabSpec(tabSpec).setIndicator(name).setContent(id)); 1259 } 1260 } 1261 } 1262 1263 private List<ViewInfo> startVisitingViews(View view, int offset, boolean setExtendedInfo) { 1264 if (view == null) { 1265 return null; 1266 } 1267 1268 // adjust the offset to this view. 1269 offset += view.getTop(); 1270 1271 if (view == mContentRoot) { 1272 return visitAllChildren(mContentRoot, offset, setExtendedInfo); 1273 } 1274 1275 // otherwise, look for mContentRoot in the children 1276 if (view instanceof ViewGroup) { 1277 ViewGroup group = ((ViewGroup) view); 1278 1279 for (int i = 0; i < group.getChildCount(); i++) { 1280 List<ViewInfo> list = startVisitingViews(group.getChildAt(i), offset, 1281 setExtendedInfo); 1282 if (list != null) { 1283 return list; 1284 } 1285 } 1286 } 1287 1288 return null; 1289 } 1290 1291 /** 1292 * Visits a View and its children and generate a {@link ViewInfo} containing the 1293 * bounds of all the views. 1294 * @param view the root View 1295 * @param offset an offset for the view bounds. 1296 * @param setExtendedInfo whether to set the extended view info in the {@link ViewInfo} object. 1297 */ 1298 private ViewInfo visit(View view, int offset, boolean setExtendedInfo) { 1299 if (view == null) { 1300 return null; 1301 } 1302 1303 ViewInfo result = new ViewInfo(view.getClass().getName(), 1304 getContext().getViewKey(view), 1305 view.getLeft(), view.getTop() + offset, view.getRight(), view.getBottom() + offset, 1306 view, view.getLayoutParams()); 1307 1308 if (setExtendedInfo) { 1309 MarginLayoutParams marginParams = null; 1310 LayoutParams params = view.getLayoutParams(); 1311 if (params instanceof MarginLayoutParams) { 1312 marginParams = (MarginLayoutParams) params; 1313 } 1314 result.setExtendedInfo(view.getBaseline(), 1315 marginParams != null ? marginParams.leftMargin : 0, 1316 marginParams != null ? marginParams.topMargin : 0, 1317 marginParams != null ? marginParams.rightMargin : 0, 1318 marginParams != null ? marginParams.bottomMargin : 0); 1319 } 1320 1321 if (view instanceof ViewGroup) { 1322 ViewGroup group = ((ViewGroup) view); 1323 result.setChildren(visitAllChildren(group, 0 /*offset*/, setExtendedInfo)); 1324 } 1325 1326 return result; 1327 } 1328 1329 /** 1330 * Visits all the children of a given ViewGroup generate a list of {@link ViewInfo} 1331 * containing the bounds of all the views. 1332 * @param view the root View 1333 * @param offset an offset for the view bounds. 1334 * @param setExtendedInfo whether to set the extended view info in the {@link ViewInfo} object. 1335 */ 1336 private List<ViewInfo> visitAllChildren(ViewGroup viewGroup, int offset, 1337 boolean setExtendedInfo) { 1338 if (viewGroup == null) { 1339 return null; 1340 } 1341 1342 List<ViewInfo> children = new ArrayList<ViewInfo>(); 1343 for (int i = 0; i < viewGroup.getChildCount(); i++) { 1344 children.add(visit(viewGroup.getChildAt(i), offset, setExtendedInfo)); 1345 } 1346 return children; 1347 } 1348 1349 1350 private void invalidateRenderingSize() { 1351 mMeasuredScreenWidth = mMeasuredScreenHeight = -1; 1352 } 1353 1354 public BufferedImage getImage() { 1355 return mImage; 1356 } 1357 1358 public boolean isAlphaChannelImage() { 1359 return mIsAlphaChannelImage; 1360 } 1361 1362 public List<ViewInfo> getViewInfos() { 1363 return mViewInfoList; 1364 } 1365 1366 public Map<String, String> getDefaultProperties(Object viewObject) { 1367 return getContext().getDefaultPropMap(viewObject); 1368 } 1369 1370 public void setScene(RenderSession session) { 1371 mScene = session; 1372 } 1373 1374 public RenderSession getSession() { 1375 return mScene; 1376 } 1377} 1378