Home | History | Annotate | Download | only in impl
      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 
     17 package com.android.layoutlib.bridge.impl;
     18 
     19 import static com.android.ide.common.rendering.api.Result.Status.ERROR_ANIM_NOT_FOUND;
     20 import static com.android.ide.common.rendering.api.Result.Status.ERROR_INFLATION;
     21 import static com.android.ide.common.rendering.api.Result.Status.ERROR_NOT_INFLATED;
     22 import static com.android.ide.common.rendering.api.Result.Status.ERROR_UNKNOWN;
     23 import static com.android.ide.common.rendering.api.Result.Status.ERROR_VIEWGROUP_NO_CHILDREN;
     24 import static com.android.ide.common.rendering.api.Result.Status.SUCCESS;
     25 
     26 import com.android.ide.common.rendering.api.AdapterBinding;
     27 import com.android.ide.common.rendering.api.IAnimationListener;
     28 import com.android.ide.common.rendering.api.ILayoutPullParser;
     29 import com.android.ide.common.rendering.api.IProjectCallback;
     30 import com.android.ide.common.rendering.api.RenderParams;
     31 import com.android.ide.common.rendering.api.RenderResources;
     32 import com.android.ide.common.rendering.api.RenderSession;
     33 import com.android.ide.common.rendering.api.ResourceReference;
     34 import com.android.ide.common.rendering.api.ResourceValue;
     35 import com.android.ide.common.rendering.api.Result;
     36 import com.android.ide.common.rendering.api.SessionParams;
     37 import com.android.ide.common.rendering.api.ViewInfo;
     38 import com.android.ide.common.rendering.api.Result.Status;
     39 import com.android.ide.common.rendering.api.SessionParams.RenderingMode;
     40 import com.android.internal.util.XmlUtils;
     41 import com.android.layoutlib.bridge.Bridge;
     42 import com.android.layoutlib.bridge.android.BridgeContext;
     43 import com.android.layoutlib.bridge.android.BridgeLayoutParamsMapAttributes;
     44 import com.android.layoutlib.bridge.android.BridgeXmlBlockParser;
     45 import com.android.layoutlib.bridge.bars.FakeActionBar;
     46 import com.android.layoutlib.bridge.bars.PhoneSystemBar;
     47 import com.android.layoutlib.bridge.bars.TabletSystemBar;
     48 import com.android.layoutlib.bridge.bars.TitleBar;
     49 import com.android.layoutlib.bridge.impl.binding.FakeAdapter;
     50 import com.android.layoutlib.bridge.impl.binding.FakeExpandableAdapter;
     51 import com.android.resources.ResourceType;
     52 import com.android.resources.ScreenSize;
     53 import com.android.util.Pair;
     54 
     55 import org.xmlpull.v1.XmlPullParserException;
     56 
     57 import android.animation.AnimationThread;
     58 import android.animation.Animator;
     59 import android.animation.AnimatorInflater;
     60 import android.animation.LayoutTransition;
     61 import android.animation.LayoutTransition.TransitionListener;
     62 import android.app.Fragment_Delegate;
     63 import android.graphics.Bitmap;
     64 import android.graphics.Bitmap_Delegate;
     65 import android.graphics.Canvas;
     66 import android.graphics.drawable.Drawable;
     67 import android.util.DisplayMetrics;
     68 import android.util.TypedValue;
     69 import android.view.AttachInfo_Accessor;
     70 import android.view.BridgeInflater;
     71 import android.view.View;
     72 import android.view.ViewGroup;
     73 import android.view.View.MeasureSpec;
     74 import android.view.ViewGroup.LayoutParams;
     75 import android.view.ViewGroup.MarginLayoutParams;
     76 import android.widget.AbsListView;
     77 import android.widget.AbsSpinner;
     78 import android.widget.AdapterView;
     79 import android.widget.ExpandableListView;
     80 import android.widget.FrameLayout;
     81 import android.widget.LinearLayout;
     82 import android.widget.ListView;
     83 import android.widget.QuickContactBadge;
     84 import android.widget.TabHost;
     85 import android.widget.TabWidget;
     86 import android.widget.TabHost.TabSpec;
     87 
     88 import java.awt.AlphaComposite;
     89 import java.awt.Color;
     90 import java.awt.Graphics2D;
     91 import java.awt.image.BufferedImage;
     92 import java.util.ArrayList;
     93 import java.util.List;
     94 import 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  */
    104 public 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