RenderSessionImpl.java revision 7f9f99ea11051614a7727dfb9f9578b518e76e3c
1/*
2 * Copyright (C) 2010 The Android Open Source Project
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 *      http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16
17package com.android.layoutlib.bridge.impl;
18
19import static com.android.ide.common.rendering.api.Result.Status.ERROR_ANIM_NOT_FOUND;
20import static com.android.ide.common.rendering.api.Result.Status.ERROR_INFLATION;
21import static com.android.ide.common.rendering.api.Result.Status.ERROR_NOT_INFLATED;
22import static com.android.ide.common.rendering.api.Result.Status.ERROR_UNKNOWN;
23import static com.android.ide.common.rendering.api.Result.Status.ERROR_VIEWGROUP_NO_CHILDREN;
24import static com.android.ide.common.rendering.api.Result.Status.SUCCESS;
25
26import com.android.ide.common.rendering.api.AdapterBinding;
27import com.android.ide.common.rendering.api.IAnimationListener;
28import com.android.ide.common.rendering.api.ILayoutPullParser;
29import com.android.ide.common.rendering.api.IProjectCallback;
30import com.android.ide.common.rendering.api.RenderParams;
31import com.android.ide.common.rendering.api.RenderResources;
32import com.android.ide.common.rendering.api.RenderSession;
33import com.android.ide.common.rendering.api.ResourceReference;
34import com.android.ide.common.rendering.api.ResourceValue;
35import com.android.ide.common.rendering.api.Result;
36import com.android.ide.common.rendering.api.SessionParams;
37import com.android.ide.common.rendering.api.ViewInfo;
38import com.android.ide.common.rendering.api.Result.Status;
39import com.android.ide.common.rendering.api.SessionParams.RenderingMode;
40import com.android.internal.util.XmlUtils;
41import com.android.layoutlib.bridge.Bridge;
42import com.android.layoutlib.bridge.android.BridgeContext;
43import com.android.layoutlib.bridge.android.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.ViewGroup;
73import android.view.View.MeasureSpec;
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.TabWidget;
86import android.widget.TabHost.TabSpec;
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                    public void startTransition(LayoutTransition transition, ViewGroup container,
839                            View view, int transitionType) {
840                        if (transitionType == LayoutTransition.CHANGE_DISAPPEARING) {
841                            mChangeDisappearingCount++;
842                        }
843                    }
844
845                    public void endTransition(LayoutTransition transition, ViewGroup container,
846                            View view, int transitionType) {
847                        if (transitionType == LayoutTransition.CHANGE_DISAPPEARING) {
848                            mChangeDisappearingCount--;
849                        }
850
851                        if (transitionType == LayoutTransition.CHANGE_DISAPPEARING &&
852                                mChangeDisappearingCount == 0) {
853                            // add it to the parentView in the correct location
854                            if (params != null) {
855                                newParent.addView(movedView, index, params);
856                            } else {
857                                newParent.addView(movedView, index);
858                            }
859                        }
860                    }
861                });
862
863                // remove the view from the current parent.
864                previousParent.removeView(movedView);
865
866                // and return since adding the view to the new parent is done in the listener.
867                return SUCCESS.createResult();
868            } else {
869                // standard code with no animation. pretty simple.
870                previousParent.removeView(movedView);
871
872                // add it to the parentView in the correct location
873                if (params != null) {
874                    newParent.addView(movedView, index, params);
875                } else {
876                    newParent.addView(movedView, index);
877                }
878
879                return SUCCESS.createResult();
880            }
881        } catch (UnsupportedOperationException e) {
882            // looks like this is a view class that doesn't support children manipulation!
883            return ERROR_VIEWGROUP_NO_CHILDREN.createResult();
884        }
885    }
886
887    /**
888     * Removes a child from its current parent.
889     * <p>
890     * {@link #acquire(long)} must have been called before this.
891     *
892     * @throws IllegalStateException if the current context is different than the one owned by
893     *      the scene, or if {@link #acquire(long)} was not called.
894     *
895     * @see RenderSession#removeChild(Object, IAnimationListener)
896     */
897    public Result removeChild(final View childView, IAnimationListener listener) {
898        checkLock();
899
900        invalidateRenderingSize();
901
902        final ViewGroup parent = (ViewGroup) childView.getParent();
903
904        if (listener != null) {
905            new AnimationThread(this, "moveChild", listener) {
906
907                @Override
908                public Result preAnimation() {
909                    parent.setLayoutTransition(new LayoutTransition());
910                    return removeView(parent, childView);
911                }
912
913                @Override
914                public void postAnimation() {
915                    parent.setLayoutTransition(null);
916                }
917            }.start();
918
919            // always return success since the real status will come through the listener.
920            return SUCCESS.createResult();
921        }
922
923        Result result = removeView(parent, childView);
924        if (result.isSuccess() == false) {
925            return result;
926        }
927
928        return render(false /*freshRender*/);
929    }
930
931    /**
932     * Removes a given view from its current parent.
933     *
934     * @param view the view to remove from its parent
935     *
936     * @return a Result with {@link Status#SUCCESS} or
937     *     {@link Status#ERROR_VIEWGROUP_NO_CHILDREN} if the given parent doesn't support
938     *     adding views.
939     */
940    private Result removeView(ViewGroup parent, View view) {
941        try {
942            parent.removeView(view);
943            return SUCCESS.createResult();
944        } catch (UnsupportedOperationException e) {
945            // looks like this is a view class that doesn't support children manipulation!
946            return ERROR_VIEWGROUP_NO_CHILDREN.createResult();
947        }
948    }
949
950
951    private void findBackground(RenderResources resources) {
952        if (getParams().isBgColorOverridden() == false) {
953            mWindowBackground = resources.findItemInTheme("windowBackground");
954            if (mWindowBackground != null) {
955                mWindowBackground = resources.resolveResValue(mWindowBackground);
956            }
957        }
958    }
959
960    private boolean isTabletUi() {
961        return getParams().getConfigScreenSize() == ScreenSize.XLARGE;
962    }
963
964    private void findStatusBar(RenderResources resources, DisplayMetrics metrics) {
965        if (isTabletUi() == false) {
966            boolean windowFullscreen = getBooleanThemeValue(resources,
967                    "windowFullscreen", false /*defaultValue*/);
968
969            if (windowFullscreen == false && mWindowIsFloating == false) {
970                // default value
971                mStatusBarSize = DEFAULT_STATUS_BAR_HEIGHT;
972
973                // get the real value
974                ResourceValue value = resources.getFrameworkResource(ResourceType.DIMEN,
975                        "status_bar_height");
976
977                if (value != null) {
978                    TypedValue typedValue = ResourceHelper.getValue("status_bar_height",
979                            value.getValue(), true /*requireUnit*/);
980                    if (typedValue != null) {
981                        // compute the pixel value based on the display metrics
982                        mStatusBarSize = (int)typedValue.getDimension(metrics);
983                    }
984                }
985            }
986        }
987    }
988
989    private void findActionBar(RenderResources resources, DisplayMetrics metrics) {
990        if (mWindowIsFloating) {
991            return;
992        }
993
994        boolean windowActionBar = getBooleanThemeValue(resources,
995                "windowActionBar", true /*defaultValue*/);
996
997        // if there's a value and it's false (default is true)
998        if (windowActionBar) {
999
1000            // default size of the window title bar
1001            mActionBarSize = DEFAULT_TITLE_BAR_HEIGHT;
1002
1003            // get value from the theme.
1004            ResourceValue value = resources.findItemInTheme("actionBarSize");
1005
1006            // resolve it
1007            value = resources.resolveResValue(value);
1008
1009            if (value != null) {
1010                // get the numerical value, if available
1011                TypedValue typedValue = ResourceHelper.getValue("actionBarSize", value.getValue(),
1012                        true /*requireUnit*/);
1013                if (typedValue != null) {
1014                    // compute the pixel value based on the display metrics
1015                    mActionBarSize = (int)typedValue.getDimension(metrics);
1016                }
1017            }
1018        } else {
1019            // action bar overrides title bar so only look for this one if action bar is hidden
1020            boolean windowNoTitle = getBooleanThemeValue(resources,
1021                    "windowNoTitle", false /*defaultValue*/);
1022
1023            if (windowNoTitle == false) {
1024
1025                // default size of the window title bar
1026                mTitleBarSize = DEFAULT_TITLE_BAR_HEIGHT;
1027
1028                // get value from the theme.
1029                ResourceValue value = resources.findItemInTheme("windowTitleSize");
1030
1031                // resolve it
1032                value = resources.resolveResValue(value);
1033
1034                if (value != null) {
1035                    // get the numerical value, if available
1036                    TypedValue typedValue = ResourceHelper.getValue("windowTitleSize",
1037                            value.getValue(), true /*requireUnit*/);
1038                    if (typedValue != null) {
1039                        // compute the pixel value based on the display metrics
1040                        mTitleBarSize = (int)typedValue.getDimension(metrics);
1041                    }
1042                }
1043            }
1044
1045        }
1046    }
1047
1048    private void findSystemBar(RenderResources resources, DisplayMetrics metrics) {
1049        if (isTabletUi() && mWindowIsFloating == false) {
1050
1051            // default value
1052            mSystemBarSize = 48; // ??
1053
1054            // get the real value
1055            ResourceValue value = resources.getFrameworkResource(ResourceType.DIMEN,
1056                    "status_bar_height");
1057
1058            if (value != null) {
1059                TypedValue typedValue = ResourceHelper.getValue("status_bar_height",
1060                        value.getValue(), true /*requireUnit*/);
1061                if (typedValue != null) {
1062                    // compute the pixel value based on the display metrics
1063                    mSystemBarSize = (int)typedValue.getDimension(metrics);
1064                }
1065            }
1066        }
1067    }
1068
1069    private boolean getBooleanThemeValue(RenderResources resources,
1070            String name, boolean defaultValue) {
1071
1072        // get the title bar flag from the current theme.
1073        ResourceValue value = resources.findItemInTheme(name);
1074
1075        // because it may reference something else, we resolve it.
1076        value = resources.resolveResValue(value);
1077
1078        // if there's no value, return the default.
1079        if (value == null || value.getValue() == null) {
1080            return defaultValue;
1081        }
1082
1083        return XmlUtils.convertValueToBoolean(value.getValue(), defaultValue);
1084    }
1085
1086    /**
1087     * Post process on a view hierachy that was just inflated.
1088     * <p/>At the moment this only support TabHost: If {@link TabHost} is detected, look for the
1089     * {@link TabWidget}, and the corresponding {@link FrameLayout} and make new tabs automatically
1090     * based on the content of the {@link FrameLayout}.
1091     * @param view the root view to process.
1092     * @param projectCallback callback to the project.
1093     */
1094    private void postInflateProcess(View view, IProjectCallback projectCallback)
1095            throws PostInflateException {
1096        if (view instanceof TabHost) {
1097            setupTabHost((TabHost)view, projectCallback);
1098        } else if (view instanceof QuickContactBadge) {
1099            QuickContactBadge badge = (QuickContactBadge) view;
1100            badge.setImageToDefault();
1101        } else if (view instanceof AdapterView<?>) {
1102            // get the view ID.
1103            int id = view.getId();
1104
1105            BridgeContext context = getContext();
1106
1107            // get a ResourceReference from the integer ID.
1108            ResourceReference listRef = context.resolveId(id);
1109
1110            if (listRef != null) {
1111                SessionParams params = getParams();
1112                AdapterBinding binding = params.getAdapterBindings().get(listRef);
1113
1114                // if there was no adapter binding, trying to get it from the call back.
1115                if (binding == null) {
1116                    binding = params.getProjectCallback().getAdapterBinding(listRef,
1117                            context.getViewKey(view), view);
1118                }
1119
1120                if (binding != null) {
1121
1122                    if (view instanceof AbsListView) {
1123                        if ((binding.getFooterCount() > 0 || binding.getHeaderCount() > 0) &&
1124                                view instanceof ListView) {
1125                            ListView list = (ListView) view;
1126
1127                            boolean skipCallbackParser = false;
1128
1129                            int count = binding.getHeaderCount();
1130                            for (int i = 0 ; i < count ; i++) {
1131                                Pair<View, Boolean> pair = context.inflateView(
1132                                        binding.getHeaderAt(i),
1133                                        list, false /*attachToRoot*/, skipCallbackParser);
1134                                if (pair.getFirst() != null) {
1135                                    list.addHeaderView(pair.getFirst());
1136                                }
1137
1138                                skipCallbackParser |= pair.getSecond();
1139                            }
1140
1141                            count = binding.getFooterCount();
1142                            for (int i = 0 ; i < count ; i++) {
1143                                Pair<View, Boolean> pair = context.inflateView(
1144                                        binding.getFooterAt(i),
1145                                        list, false /*attachToRoot*/, skipCallbackParser);
1146                                if (pair.getFirst() != null) {
1147                                    list.addFooterView(pair.getFirst());
1148                                }
1149
1150                                skipCallbackParser |= pair.getSecond();
1151                            }
1152                        }
1153
1154                        if (view instanceof ExpandableListView) {
1155                            ((ExpandableListView) view).setAdapter(
1156                                    new FakeExpandableAdapter(
1157                                            listRef, binding, params.getProjectCallback()));
1158                        } else {
1159                            ((AbsListView) view).setAdapter(
1160                                    new FakeAdapter(
1161                                            listRef, binding, params.getProjectCallback()));
1162                        }
1163                    } else if (view instanceof AbsSpinner) {
1164                        ((AbsSpinner) view).setAdapter(
1165                                new FakeAdapter(
1166                                        listRef, binding, params.getProjectCallback()));
1167                    }
1168                }
1169            }
1170        } else if (view instanceof ViewGroup) {
1171            ViewGroup group = (ViewGroup)view;
1172            final int count = group.getChildCount();
1173            for (int c = 0 ; c < count ; c++) {
1174                View child = group.getChildAt(c);
1175                postInflateProcess(child, projectCallback);
1176            }
1177        }
1178    }
1179
1180    /**
1181     * Sets up a {@link TabHost} object.
1182     * @param tabHost the TabHost to setup.
1183     * @param projectCallback The project callback object to access the project R class.
1184     * @throws PostInflateException
1185     */
1186    private void setupTabHost(TabHost tabHost, IProjectCallback projectCallback)
1187            throws PostInflateException {
1188        // look for the TabWidget, and the FrameLayout. They have their own specific names
1189        View v = tabHost.findViewById(android.R.id.tabs);
1190
1191        if (v == null) {
1192            throw new PostInflateException(
1193                    "TabHost requires a TabWidget with id \"android:id/tabs\".\n");
1194        }
1195
1196        if ((v instanceof TabWidget) == false) {
1197            throw new PostInflateException(String.format(
1198                    "TabHost requires a TabWidget with id \"android:id/tabs\".\n" +
1199                    "View found with id 'tabs' is '%s'", v.getClass().getCanonicalName()));
1200        }
1201
1202        v = tabHost.findViewById(android.R.id.tabcontent);
1203
1204        if (v == null) {
1205            // TODO: see if we can fake tabs even without the FrameLayout (same below when the framelayout is empty)
1206            throw new PostInflateException(
1207                    "TabHost requires a FrameLayout with id \"android:id/tabcontent\".");
1208        }
1209
1210        if ((v instanceof FrameLayout) == false) {
1211            throw new PostInflateException(String.format(
1212                    "TabHost requires a FrameLayout with id \"android:id/tabcontent\".\n" +
1213                    "View found with id 'tabcontent' is '%s'", v.getClass().getCanonicalName()));
1214        }
1215
1216        FrameLayout content = (FrameLayout)v;
1217
1218        // now process the content of the framelayout and dynamically create tabs for it.
1219        final int count = content.getChildCount();
1220
1221        // this must be called before addTab() so that the TabHost searches its TabWidget
1222        // and FrameLayout.
1223        tabHost.setup();
1224
1225        if (count == 0) {
1226            // Create a dummy child to get a single tab
1227            TabSpec spec = tabHost.newTabSpec("tag").setIndicator("Tab Label",
1228                    tabHost.getResources().getDrawable(android.R.drawable.ic_menu_info_details))
1229                    .setContent(new TabHost.TabContentFactory() {
1230                        public View createTabContent(String tag) {
1231                            return new LinearLayout(getContext());
1232                        }
1233                    });
1234            tabHost.addTab(spec);
1235            return;
1236        } else {
1237            // for each child of the framelayout, add a new TabSpec
1238            for (int i = 0 ; i < count ; i++) {
1239                View child = content.getChildAt(i);
1240                String tabSpec = String.format("tab_spec%d", i+1);
1241                int id = child.getId();
1242                Pair<ResourceType, String> resource = projectCallback.resolveResourceId(id);
1243                String name;
1244                if (resource != null) {
1245                    name = resource.getSecond();
1246                } else {
1247                    name = String.format("Tab %d", i+1); // default name if id is unresolved.
1248                }
1249                tabHost.addTab(tabHost.newTabSpec(tabSpec).setIndicator(name).setContent(id));
1250            }
1251        }
1252    }
1253
1254    private List<ViewInfo> startVisitingViews(View view, int offset, boolean setExtendedInfo) {
1255        if (view == null) {
1256            return null;
1257        }
1258
1259        // adjust the offset to this view.
1260        offset += view.getTop();
1261
1262        if (view == mContentRoot) {
1263            return visitAllChildren(mContentRoot, offset, setExtendedInfo);
1264        }
1265
1266        // otherwise, look for mContentRoot in the children
1267        if (view instanceof ViewGroup) {
1268            ViewGroup group = ((ViewGroup) view);
1269
1270            for (int i = 0; i < group.getChildCount(); i++) {
1271                List<ViewInfo> list = startVisitingViews(group.getChildAt(i), offset,
1272                        setExtendedInfo);
1273                if (list != null) {
1274                    return list;
1275                }
1276            }
1277        }
1278
1279        return null;
1280    }
1281
1282    /**
1283     * Visits a View and its children and generate a {@link ViewInfo} containing the
1284     * bounds of all the views.
1285     * @param view the root View
1286     * @param offset an offset for the view bounds.
1287     * @param setExtendedInfo whether to set the extended view info in the {@link ViewInfo} object.
1288     */
1289    private ViewInfo visit(View view, int offset, boolean setExtendedInfo) {
1290        if (view == null) {
1291            return null;
1292        }
1293
1294        ViewInfo result = new ViewInfo(view.getClass().getName(),
1295                getContext().getViewKey(view),
1296                view.getLeft(), view.getTop() + offset, view.getRight(), view.getBottom() + offset,
1297                view, view.getLayoutParams());
1298
1299        if (setExtendedInfo) {
1300            MarginLayoutParams marginParams = null;
1301            LayoutParams params = view.getLayoutParams();
1302            if (params instanceof MarginLayoutParams) {
1303                marginParams = (MarginLayoutParams) params;
1304            }
1305            result.setExtendedInfo(view.getBaseline(),
1306                    marginParams != null ? marginParams.leftMargin : 0,
1307                    marginParams != null ? marginParams.topMargin : 0,
1308                    marginParams != null ? marginParams.rightMargin : 0,
1309                    marginParams != null ? marginParams.bottomMargin : 0);
1310        }
1311
1312        if (view instanceof ViewGroup) {
1313            ViewGroup group = ((ViewGroup) view);
1314            result.setChildren(visitAllChildren(group, 0 /*offset*/, setExtendedInfo));
1315        }
1316
1317        return result;
1318    }
1319
1320    /**
1321     * Visits all the children of a given ViewGroup generate a list of {@link ViewInfo}
1322     * containing the bounds of all the views.
1323     * @param view the root View
1324     * @param offset an offset for the view bounds.
1325     * @param setExtendedInfo whether to set the extended view info in the {@link ViewInfo} object.
1326     */
1327    private List<ViewInfo> visitAllChildren(ViewGroup viewGroup, int offset,
1328            boolean setExtendedInfo) {
1329        if (viewGroup == null) {
1330            return null;
1331        }
1332
1333        List<ViewInfo> children = new ArrayList<ViewInfo>();
1334        for (int i = 0; i < viewGroup.getChildCount(); i++) {
1335            children.add(visit(viewGroup.getChildAt(i), offset, setExtendedInfo));
1336        }
1337        return children;
1338    }
1339
1340
1341    private void invalidateRenderingSize() {
1342        mMeasuredScreenWidth = mMeasuredScreenHeight = -1;
1343    }
1344
1345    public BufferedImage getImage() {
1346        return mImage;
1347    }
1348
1349    public boolean isAlphaChannelImage() {
1350        return mIsAlphaChannelImage;
1351    }
1352
1353    public List<ViewInfo> getViewInfos() {
1354        return mViewInfoList;
1355    }
1356
1357    public Map<String, String> getDefaultProperties(Object viewObject) {
1358        return getContext().getDefaultPropMap(viewObject);
1359    }
1360
1361    public void setScene(RenderSession session) {
1362        mScene = session;
1363    }
1364
1365    public RenderSession getSession() {
1366        return mScene;
1367    }
1368}
1369