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