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