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