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