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