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