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