FileDocCategorySizeDatePackage
RenderSessionImpl.javaAPI DocAndroid 5.1 API73493Thu Mar 12 22:22:44 GMT 2015com.android.layoutlib.bridge.impl

RenderSessionImpl

public class RenderSessionImpl extends RenderAction
Class implementing the render session.

A session is a stateful representation of a layout file. It is initialized with data coming through the {@link Bridge} API to inflate the layout. Further actions and rendering can then be done on the layout.

Fields Summary
private static final int
DEFAULT_TITLE_BAR_HEIGHT
private static final int
DEFAULT_STATUS_BAR_HEIGHT
private com.android.ide.common.rendering.api.RenderSession
mScene
private com.android.layoutlib.bridge.android.BridgeXmlBlockParser
mBlockParser
private android.view.BridgeInflater
mInflater
private com.android.ide.common.rendering.api.ResourceValue
mWindowBackground
private android.view.ViewGroup
mViewRoot
private android.widget.FrameLayout
mContentRoot
private android.graphics.Canvas
mCanvas
private int
mMeasuredScreenWidth
private int
mMeasuredScreenHeight
private boolean
mIsAlphaChannelImage
private boolean
mWindowIsFloating
private Boolean
mIsThemeAppCompat
private int
mStatusBarSize
private int
mNavigationBarSize
private int
mNavigationBarOrientation
private int
mTitleBarSize
private int
mActionBarSize
private BufferedImage
mImage
private List
mViewInfoList
private List
mSystemViewInfoList
Constructors Summary
public RenderSessionImpl(com.android.ide.common.rendering.api.SessionParams params)
Creates a layout scene with all the information coming from the layout bridge API.

This must be followed by a call to {@link RenderSessionImpl#init(long)}, which act as a call to {@link RenderSessionImpl#acquire(long)}

see
Bridge#createSession(SessionParams)

        super(new SessionParams(params));
    
Methods Summary
private com.android.ide.common.rendering.api.ResultaddView(android.view.ViewGroup parent, android.view.View view, int index)
Adds a given view to a given parent at a given index.

param
parent the parent to receive the view
param
view the view to add to the parent
param
index the index where to do the add.
return
a Result with {@link Status#SUCCESS} or {@link Status#ERROR_VIEWGROUP_NO_CHILDREN} if the given parent doesn't support adding views.

        try {
            parent.addView(view, index);
            return SUCCESS.createResult();
        } catch (UnsupportedOperationException e) {
            // looks like this is a view class that doesn't support children manipulation!
            return ERROR_VIEWGROUP_NO_CHILDREN.createResult();
        }
    
public com.android.ide.common.rendering.api.Resultanimate(java.lang.Object targetObject, java.lang.String animationName, boolean isFrameworkAnimation, com.android.ide.common.rendering.api.IAnimationListener listener)
Animate an object

{@link #acquire(long)} must have been called before this.

throws
IllegalStateException if the current context is different than the one owned by the scene, or if {@link #acquire(long)} was not called.
see
RenderSession#animate(Object, String, boolean, IAnimationListener)

        checkLock();

        BridgeContext context = getContext();

        // find the animation file.
        ResourceValue animationResource;
        int animationId = 0;
        if (isFrameworkAnimation) {
            animationResource = context.getRenderResources().getFrameworkResource(
                    ResourceType.ANIMATOR, animationName);
            if (animationResource != null) {
                animationId = Bridge.getResourceId(ResourceType.ANIMATOR, animationName);
            }
        } else {
            animationResource = context.getRenderResources().getProjectResource(
                    ResourceType.ANIMATOR, animationName);
            if (animationResource != null) {
                animationId = context.getProjectCallback().getResourceId(
                        ResourceType.ANIMATOR, animationName);
            }
        }

        if (animationResource != null) {
            try {
                Animator anim = AnimatorInflater.loadAnimator(context, animationId);
                if (anim != null) {
                    anim.setTarget(targetObject);

                    new PlayAnimationThread(anim, this, animationName, listener).start();

                    return SUCCESS.createResult();
                }
            } catch (Exception e) {
                // get the real cause of the exception.
                Throwable t = e;
                while (t.getCause() != null) {
                    t = t.getCause();
                }

                return ERROR_UNKNOWN.createResult(t.getMessage(), t);
            }
        }

        return ERROR_ANIM_NOT_FOUND.createResult();
    
private com.android.layoutlib.bridge.bars.BridgeActionBarcreateActionBar(com.android.layoutlib.bridge.android.BridgeContext context, com.android.ide.common.rendering.api.SessionParams params, android.view.ViewGroup parentView)
Creates the action bar. Also queries the project callback for missing information.

        if (mIsThemeAppCompat == Boolean.TRUE) {
            return new AppCompatActionBar(context, params, parentView);
        } else {
            return new FrameworkActionBar(context, params, parentView);
        }
    
private com.android.layoutlib.bridge.bars.NavigationBarcreateNavigationBar(com.android.layoutlib.bridge.android.BridgeContext context, com.android.resources.Density density, boolean isRtl, boolean isRtlSupported, int simulatedPlatformVersion)
Creates the navigation bar with back, home and recent buttons.

param
isRtl true if the current locale is right-to-left
param
isRtlSupported true is the project manifest declares that the application is RTL aware.

        NavigationBar navigationBar = new NavigationBar(context,
                density, mNavigationBarOrientation, isRtl,
                isRtlSupported, simulatedPlatformVersion);
        if (mNavigationBarOrientation == LinearLayout.VERTICAL) {
            navigationBar.setLayoutParams(new LinearLayout.LayoutParams(mNavigationBarSize,
                    LayoutParams.MATCH_PARENT));
        } else {
            navigationBar.setLayoutParams(new LinearLayout.LayoutParams(LayoutParams.MATCH_PARENT,
                    mNavigationBarSize));
        }
        return navigationBar;
    
private com.android.layoutlib.bridge.bars.StatusBarcreateStatusBar(com.android.layoutlib.bridge.android.BridgeContext context, com.android.resources.Density density, int direction, boolean isRtlSupported, int platformVersion)
Creates the status bar with wifi and battery icons.

        StatusBar statusBar = new StatusBar(context, density,
                direction, isRtlSupported, platformVersion);
        statusBar.setLayoutParams(
                new LinearLayout.LayoutParams(
                        LayoutParams.MATCH_PARENT, mStatusBarSize));
        return statusBar;
    
private com.android.layoutlib.bridge.bars.TitleBarcreateTitleBar(com.android.layoutlib.bridge.android.BridgeContext context, java.lang.String title, int simulatedPlatformVersion)

        TitleBar titleBar = new TitleBar(context, title, simulatedPlatformVersion);
        titleBar.setLayoutParams(
                new LinearLayout.LayoutParams(LayoutParams.MATCH_PARENT, mTitleBarSize));
        return titleBar;
    
private com.android.ide.common.rendering.api.ViewInfocreateViewInfo(android.view.View view, int offset, boolean setExtendedInfo, boolean isContentFrame)
Creates a {@link ViewInfo} for the view. The {@code ViewInfo} corresponding to the children of the {@code view} are not created. Consequently, the children of {@code ViewInfo} is not set.

param
offset an offset for the view bounds. Used only if view is part of the content frame.

        if (view == null) {
            return null;
        }

        ViewInfo result;
        if (isContentFrame) {
            // The view is part of the layout added by the user. Hence,
            // the ViewCookie may be obtained only through the Context.
            result = new ViewInfo(view.getClass().getName(),
                    getContext().getViewKey(view),
                    view.getLeft(), view.getTop() + offset, view.getRight(),
                    view.getBottom() + offset, view, view.getLayoutParams());
        } else {
            // We are part of the system decor.
            SystemViewInfo r = new SystemViewInfo(view.getClass().getName(),
                    getViewKey(view),
                    view.getLeft(), view.getTop(), view.getRight(),
                    view.getBottom(), view, view.getLayoutParams());
            result = r;
            // We currently mark three kinds of views:
            // 1. Menus in the Action Bar
            // 2. Menus in the Overflow popup.
            // 3. The overflow popup button.
            if (view instanceof ListMenuItemView) {
                // Mark 2.
                // All menus in the popup are of type ListMenuItemView.
                r.setViewType(ViewType.ACTION_BAR_OVERFLOW_MENU);
            } else {
                // Mark 3.
                ViewGroup.LayoutParams lp = view.getLayoutParams();
                if (lp instanceof ActionMenuView.LayoutParams &&
                        ((ActionMenuView.LayoutParams) lp).isOverflowButton) {
                    r.setViewType(ViewType.ACTION_BAR_OVERFLOW);
                } else {
                    // Mark 1.
                    // A view is a menu in the Action Bar is it is not the overflow button and of
                    // its parent is of type ActionMenuView. We can also check if the view is
                    // instanceof ActionMenuItemView but that will fail for menus using
                    // actionProviderClass.
                    ViewParent parent = view.getParent();
                    while (parent != mViewRoot && parent instanceof ViewGroup) {
                        if (parent instanceof ActionMenuView) {
                            r.setViewType(ViewType.ACTION_BAR_MENU);
                            break;
                        }
                        parent = parent.getParent();
                    }
                }
            }
        }

        if (setExtendedInfo) {
            MarginLayoutParams marginParams = null;
            LayoutParams params = view.getLayoutParams();
            if (params instanceof MarginLayoutParams) {
                marginParams = (MarginLayoutParams) params;
            }
            result.setExtendedInfo(view.getBaseline(),
                    marginParams != null ? marginParams.leftMargin : 0,
                    marginParams != null ? marginParams.topMargin : 0,
                    marginParams != null ? marginParams.rightMargin : 0,
                    marginParams != null ? marginParams.bottomMargin : 0);
        }

        return result;
    
private voidfindActionBar(com.android.ide.common.rendering.api.RenderResources resources, android.util.DisplayMetrics metrics)

        if (mWindowIsFloating) {
            return;
        }

        boolean windowActionBar = getBooleanThemeValue(resources,
                "windowActionBar", true, !isThemeAppCompat(resources));

        // if there's a value and it's false (default is true)
        if (windowActionBar) {

            // default size of the window title bar
            mActionBarSize = DEFAULT_TITLE_BAR_HEIGHT;

            // get value from the theme.
            ResourceValue value = resources.findItemInTheme("actionBarSize",
                    true /*isFrameworkAttr*/);

            // resolve it
            value = resources.resolveResValue(value);

            if (value != null) {
                // get the numerical value, if available
                TypedValue typedValue = ResourceHelper.getValue("actionBarSize", value.getValue(),
                        true /*requireUnit*/);
                if (typedValue != null) {
                    // compute the pixel value based on the display metrics
                    mActionBarSize = (int)typedValue.getDimension(metrics);
                }
            }
        } else {
            // action bar overrides title bar so only look for this one if action bar is hidden
            boolean windowNoTitle = getBooleanThemeValue(resources,
                    "windowNoTitle", false, !isThemeAppCompat(resources));

            if (!windowNoTitle) {

                // default size of the window title bar
                mTitleBarSize = DEFAULT_TITLE_BAR_HEIGHT;

                // get value from the theme.
                ResourceValue value = resources.findItemInTheme("windowTitleSize",
                        true /*isFrameworkAttr*/);

                // resolve it
                value = resources.resolveResValue(value);

                if (value != null) {
                    // get the numerical value, if available
                    TypedValue typedValue = ResourceHelper.getValue("windowTitleSize",
                            value.getValue(), true /*requireUnit*/);
                    if (typedValue != null) {
                        // compute the pixel value based on the display metrics
                        mTitleBarSize = (int)typedValue.getDimension(metrics);
                    }
                }
            }

        }
    
private voidfindBackground(com.android.ide.common.rendering.api.RenderResources resources)

        if (!getParams().isBgColorOverridden()) {
            mWindowBackground = resources.findItemInTheme("windowBackground",
                    true /*isFrameworkAttr*/);
            if (mWindowBackground != null) {
                mWindowBackground = resources.resolveResValue(mWindowBackground);
            }
        }
    
private voidfindNavigationBar(com.android.ide.common.rendering.api.RenderResources resources, android.util.DisplayMetrics metrics)

        if (hasSoftwareButtons() && !mWindowIsFloating) {

            // default value
            mNavigationBarSize = 48; // ??

            HardwareConfig hardwareConfig = getParams().getHardwareConfig();

            boolean barOnBottom = true;

            if (hardwareConfig.getOrientation() == ScreenOrientation.LANDSCAPE) {
                // compute the dp of the screen.
                int shortSize = hardwareConfig.getScreenHeight();

                // compute in dp
                int shortSizeDp = shortSize * DisplayMetrics.DENSITY_DEFAULT /
                        hardwareConfig.getDensity().getDpiValue();

                // 0-599dp: "phone" UI with bar on the side
                // 600+dp: "tablet" UI with bar on the bottom
                barOnBottom = shortSizeDp >= 600;
            }

            if (barOnBottom) {
                mNavigationBarOrientation = LinearLayout.HORIZONTAL;
            } else {
                mNavigationBarOrientation = LinearLayout.VERTICAL;
            }

            // get the real value
            ResourceValue value = resources.getFrameworkResource(ResourceType.DIMEN,
                    barOnBottom ? "navigation_bar_height" : "navigation_bar_width");

            if (value != null) {
                TypedValue typedValue = ResourceHelper.getValue("navigation_bar_height",
                        value.getValue(), true /*requireUnit*/);
                if (typedValue != null) {
                    // compute the pixel value based on the display metrics
                    mNavigationBarSize = (int)typedValue.getDimension(metrics);
                }
            }
        }
    
private voidfindStatusBar(com.android.ide.common.rendering.api.RenderResources resources, android.util.DisplayMetrics metrics)

        boolean windowFullscreen = getBooleanThemeValue(resources,
                "windowFullscreen", false, !isThemeAppCompat(resources));

        if (!windowFullscreen && !mWindowIsFloating) {
            // default value
            mStatusBarSize = DEFAULT_STATUS_BAR_HEIGHT;

            // get the real value
            ResourceValue value = resources.getFrameworkResource(ResourceType.DIMEN,
                    "status_bar_height");

            if (value != null) {
                TypedValue typedValue = ResourceHelper.getValue("status_bar_height",
                        value.getValue(), true /*requireUnit*/);
                if (typedValue != null) {
                    // compute the pixel value based on the display metrics
                    mStatusBarSize = (int)typedValue.getDimension(metrics);
                }
            }
        }
    
private booleangetBooleanThemeValue(com.android.ide.common.rendering.api.RenderResources resources, java.lang.String name, boolean defaultValue, boolean isFrameworkAttr)
Looks for an attribute in the current theme.

param
resources the render resources
param
name the name of the attribute
param
defaultValue the default value.
param
isFrameworkAttr if the attribute is in android namespace
return
the value of the attribute or the default one if not found.


        ResourceValue value = resources.findItemInTheme(name, isFrameworkAttr);

        // because it may reference something else, we resolve it.
        value = resources.resolveResValue(value);

        // if there's no value, return the default.
        if (value == null || value.getValue() == null) {
            return defaultValue;
        }

        return XmlUtils.convertValueToBoolean(value.getValue(), defaultValue);
    
public java.util.MapgetDefaultProperties(java.lang.Object viewObject)

        return getContext().getDefaultPropMap(viewObject);
    
public java.awt.image.BufferedImagegetImage()

        return mImage;
    
public com.android.ide.common.rendering.api.RenderSessiongetSession()

        return mScene;
    
public java.util.ListgetSystemViewInfos()

        return mSystemViewInfoList;
    
public java.util.ListgetViewInfos()

        return mViewInfoList;
    
private java.lang.ObjectgetViewKey(android.view.View view)

        BridgeContext context = getContext();
        if (!(view instanceof MenuView.ItemView)) {
            return context.getViewKey(view);
        }
        MenuItemImpl menuItem;
        if (view instanceof ActionMenuItemView) {
            menuItem = ((ActionMenuItemView) view).getItemData();
        } else if (view instanceof ListMenuItemView) {
            menuItem = ((ListMenuItemView) view).getItemData();
        } else if (view instanceof IconMenuItemView) {
            menuItem = ((IconMenuItemView) view).getItemData();
        } else {
            menuItem = null;
        }
        if (menuItem instanceof BridgeMenuItemImpl) {
            return ((BridgeMenuItemImpl) menuItem).getViewCookie();
        }

        return null;
    
private booleanhasSoftwareButtons()

        return getParams().getHardwareConfig().hasSoftwareButtons();
    
public com.android.ide.common.rendering.api.Resultinflate()
Inflates the layout.

{@link #acquire(long)} must have been called before this.

throws
IllegalStateException if the current context is different than the one owned by the scene, or if {@link #init(long)} was not called.

        checkLock();

        try {

            SessionParams params = getParams();
            HardwareConfig hardwareConfig = params.getHardwareConfig();
            BridgeContext context = getContext();
            boolean isRtl = Bridge.isLocaleRtl(params.getLocale());
            int layoutDirection = isRtl ? View.LAYOUT_DIRECTION_RTL : View.LAYOUT_DIRECTION_LTR;

            // the view group that receives the window background.
            ViewGroup backgroundView;

            if (mWindowIsFloating || params.isForceNoDecor()) {
                backgroundView = mViewRoot = mContentRoot = new FrameLayout(context);
                mViewRoot.setLayoutDirection(layoutDirection);
            } else {
                int simulatedPlatformVersion = params.getSimulatedPlatformVersion();
                if (hasSoftwareButtons() && mNavigationBarOrientation == LinearLayout.VERTICAL) {
                    /*
                     * This is a special case where the navigation bar is on the right.
                       +-------------------------------------------------+---+
                       | Status bar (always)                             |   |
                       +-------------------------------------------------+   |
                       | (Layout with background drawable)               |   |
                       | +---------------------------------------------+ |   |
                       | | Title/Action bar (optional)                 | |   |
                       | +---------------------------------------------+ |   |
                       | | Content, vertical extending                 | |   |
                       | |                                             | |   |
                       | +---------------------------------------------+ |   |
                       +-------------------------------------------------+---+

                       So we create a horizontal layout, with the nav bar on the right,
                       and the left part is the normal layout below without the nav bar at
                       the bottom
                     */
                    LinearLayout topLayout = new LinearLayout(context);
                    topLayout.setLayoutDirection(layoutDirection);
                    mViewRoot = topLayout;
                    topLayout.setOrientation(LinearLayout.HORIZONTAL);

                    if (Config.showOnScreenNavBar(simulatedPlatformVersion)) {
                        try {
                            NavigationBar navigationBar = createNavigationBar(context,
                                    hardwareConfig.getDensity(), isRtl, params.isRtlSupported(),
                                    simulatedPlatformVersion);
                            topLayout.addView(navigationBar);
                        } catch (XmlPullParserException ignored) {
                        }
                    }
                }

                /*
                 * we're creating the following layout
                 *
                   +-------------------------------------------------+
                   | Status bar (always)                             |
                   +-------------------------------------------------+
                   | (Layout with background drawable)               |
                   | +---------------------------------------------+ |
                   | | Title/Action bar (optional)                 | |
                   | +---------------------------------------------+ |
                   | | Content, vertical extending                 | |
                   | |                                             | |
                   | +---------------------------------------------+ |
                   +-------------------------------------------------+
                   | Navigation bar for soft buttons, maybe see above|
                   +-------------------------------------------------+

                 */

                LinearLayout topLayout = new LinearLayout(context);
                topLayout.setOrientation(LinearLayout.VERTICAL);
                topLayout.setLayoutDirection(layoutDirection);
                // if we don't already have a view root this is it
                if (mViewRoot == null) {
                    mViewRoot = topLayout;
                } else {
                    int topLayoutWidth =
                            params.getHardwareConfig().getScreenWidth() - mNavigationBarSize;
                    LinearLayout.LayoutParams layoutParams = new LinearLayout.LayoutParams(
                            topLayoutWidth, LayoutParams.MATCH_PARENT);
                    topLayout.setLayoutParams(layoutParams);

                    // this is the case of soft buttons + vertical bar.
                    // this top layout is the first layout in the horizontal layout. see above)
                    if (isRtl && params.isRtlSupported()) {
                        // If RTL is enabled, layoutlib will mirror the layouts. So, add the
                        // topLayout to the right of Navigation Bar and layoutlib will draw it
                        // to the left.
                        mViewRoot.addView(topLayout);
                    } else {
                        // Add the top layout to the left of the Navigation Bar.
                        mViewRoot.addView(topLayout, 0);
                    }
                }

                if (mStatusBarSize > 0) {
                    // system bar
                    try {
                        StatusBar statusBar = createStatusBar(context, hardwareConfig.getDensity(),
                                layoutDirection, params.isRtlSupported(),
                                simulatedPlatformVersion);
                        topLayout.addView(statusBar);
                    } catch (XmlPullParserException ignored) {

                    }
                }

                LinearLayout backgroundLayout = new LinearLayout(context);
                backgroundView = backgroundLayout;
                backgroundLayout.setOrientation(LinearLayout.VERTICAL);
                LinearLayout.LayoutParams layoutParams = new LinearLayout.LayoutParams(
                        LayoutParams.MATCH_PARENT, 0);
                layoutParams.weight = 1;
                backgroundLayout.setLayoutParams(layoutParams);
                topLayout.addView(backgroundLayout);


                // if the theme says no title/action bar, then the size will be 0
                if (mActionBarSize > 0) {
                    BridgeActionBar actionBar = createActionBar(context, params, backgroundLayout);
                    actionBar.createMenuPopup();
                    mContentRoot = actionBar.getContentRoot();
                } else if (mTitleBarSize > 0) {
                    try {
                        TitleBar titleBar = createTitleBar(context,
                                params.getAppLabel(),
                                simulatedPlatformVersion);
                        backgroundLayout.addView(titleBar);
                    } catch (XmlPullParserException ignored) {

                    }
                }

                // content frame
                if (mContentRoot == null) {
                    mContentRoot = new FrameLayout(context);
                    layoutParams = new LinearLayout.LayoutParams(
                            LayoutParams.MATCH_PARENT, 0);
                    layoutParams.weight = 1;
                    mContentRoot.setLayoutParams(layoutParams);
                    backgroundLayout.addView(mContentRoot);
                }

                if (Config.showOnScreenNavBar(simulatedPlatformVersion) &&
                        mNavigationBarOrientation == LinearLayout.HORIZONTAL &&
                        mNavigationBarSize > 0) {
                    // system bar
                    try {
                        NavigationBar navigationBar = createNavigationBar(context,
                                hardwareConfig.getDensity(), isRtl, params.isRtlSupported(),
                                simulatedPlatformVersion);
                        topLayout.addView(navigationBar);
                    } catch (XmlPullParserException ignored) {

                    }
                }
            }


            // Sets the project callback (custom view loader) to the fragment delegate so that
            // it can instantiate the custom Fragment.
            Fragment_Delegate.setProjectCallback(params.getProjectCallback());

            String rootTag = params.getFlag(SessionParamsFlags.FLAG_KEY_ROOT_TAG);
            boolean isPreference = "PreferenceScreen".equals(rootTag);
            View view;
            if (isPreference) {
                view = Preference_Delegate.inflatePreference(getContext(), mBlockParser,
                  mContentRoot);
            } else {
                view = mInflater.inflate(mBlockParser, mContentRoot);
            }

            // done with the parser, pop it.
            context.popParser();

            Fragment_Delegate.setProjectCallback(null);

            // set the AttachInfo on the root view.
            AttachInfo_Accessor.setAttachInfo(mViewRoot);

            // post-inflate process. For now this supports TabHost/TabWidget
            postInflateProcess(view, params.getProjectCallback(), isPreference ? view : null);

            // get the background drawable
            if (mWindowBackground != null) {
                Drawable d = ResourceHelper.getDrawable(mWindowBackground, context);
                backgroundView.setBackground(d);
            }

            return SUCCESS.createResult();
        } catch (PostInflateException e) {
            return ERROR_INFLATION.createResult(e.getMessage(), e);
        } catch (Throwable e) {
            // get the real cause of the exception.
            Throwable t = e;
            while (t.getCause() != null) {
                t = t.getCause();
            }

            return ERROR_INFLATION.createResult(t.getMessage(), t);
        }
    
public com.android.ide.common.rendering.api.Resultinit(long timeout)
Initializes and acquires the scene, creating various Android objects such as context, inflater, and parser.

param
timeout the time to wait if another rendering is happening.
return
whether the scene was prepared
see
#acquire(long)
see
#release()

        Result result = super.init(timeout);
        if (!result.isSuccess()) {
            return result;
        }

        SessionParams params = getParams();
        BridgeContext context = getContext();


        RenderResources resources = getParams().getResources();
        DisplayMetrics metrics = getContext().getMetrics();

        // use default of true in case it's not found to use alpha by default
        mIsAlphaChannelImage  = getBooleanThemeValue(resources, "windowIsFloating", true, true);
        // FIXME: Find out why both variables are taking the same value.
        mWindowIsFloating = getBooleanThemeValue(resources, "windowIsFloating", true, true);

        findBackground(resources);
        findStatusBar(resources, metrics);
        findActionBar(resources, metrics);
        findNavigationBar(resources, metrics);

        // FIXME: find those out, and possibly add them to the render params
        boolean hasNavigationBar = true;
        //noinspection ConstantConditions
        IWindowManager iwm = new IWindowManagerImpl(getContext().getConfiguration(),
                metrics, Surface.ROTATION_0,
                hasNavigationBar);
        WindowManagerGlobal_Delegate.setWindowManagerService(iwm);

        // build the inflater and parser.
        mInflater = new BridgeInflater(context, params.getProjectCallback());
        context.setBridgeInflater(mInflater);

        mBlockParser = new BridgeXmlBlockParser(
                params.getLayoutDescription(), context, false /* platformResourceFlag */);

        return SUCCESS.createResult();
    
public com.android.ide.common.rendering.api.ResultinsertChild(android.view.ViewGroup parentView, com.android.ide.common.rendering.api.ILayoutPullParser childXml, int index, com.android.ide.common.rendering.api.IAnimationListener listener)
Insert a new child into an existing parent.

{@link #acquire(long)} must have been called before this.

throws
IllegalStateException if the current context is different than the one owned by the scene, or if {@link #acquire(long)} was not called.
see
RenderSession#insertChild(Object, ILayoutPullParser, int, IAnimationListener)

        checkLock();

        BridgeContext context = getContext();

        // create a block parser for the XML
        BridgeXmlBlockParser blockParser = new BridgeXmlBlockParser(
                childXml, context, false /* platformResourceFlag */);

        // inflate the child without adding it to the root since we want to control where it'll
        // get added. We do pass the parentView however to ensure that the layoutParams will
        // be created correctly.
        final View child = mInflater.inflate(blockParser, parentView, false /*attachToRoot*/);
        blockParser.ensurePopped();

        invalidateRenderingSize();

        if (listener != null) {
            new AnimationThread(this, "insertChild", listener) {

                @Override
                public Result preAnimation() {
                    parentView.setLayoutTransition(new LayoutTransition());
                    return addView(parentView, child, index);
                }

                @Override
                public void postAnimation() {
                    parentView.setLayoutTransition(null);
                }
            }.start();

            // always return success since the real status will come through the listener.
            return SUCCESS.createResult(child);
        }

        // add it to the parentView in the correct location
        Result result = addView(parentView, child, index);
        if (!result.isSuccess()) {
            return result;
        }

        result = render(false /*freshRender*/);
        if (result.isSuccess()) {
            result = result.getCopyWithData(child);
        }

        return result;
    
private voidinvalidateRenderingSize()

        mMeasuredScreenWidth = mMeasuredScreenHeight = -1;
    
public booleanisAlphaChannelImage()

        return mIsAlphaChannelImage;
    
private booleanisThemeAppCompat(com.android.ide.common.rendering.api.RenderResources resources)

        // Ideally, we should check if the corresponding activity extends
        // android.support.v7.app.ActionBarActivity, and not care about the theme name at all.
        if (mIsThemeAppCompat == null) {
            StyleResourceValue defaultTheme = resources.getDefaultTheme();
          // We can't simply check for parent using resources.themeIsParentOf() since the
          // inheritance structure isn't really what one would expect. The first common parent
          // between Theme.AppCompat.Light and Theme.AppCompat is Theme.Material (for v21).
            boolean isThemeAppCompat = false;
            for (int i = 0; i < 50; i++) {
                // for loop ensures that we don't run into cyclic theme inheritance.
                if (defaultTheme.getName().startsWith("Theme.AppCompat")) {
                    isThemeAppCompat = true;
                    break;
                }
                defaultTheme = resources.getParent(defaultTheme);
                if (defaultTheme == null) {
                    break;
                }
            }
            mIsThemeAppCompat = isThemeAppCompat;
        }
        return mIsThemeAppCompat;
    
private com.android.util.PairmeasureView(android.view.ViewGroup viewToMeasure, android.view.View measuredView, int width, int widthMode, int height, int heightMode)
Executes {@link View#measure(int, int)} on a given view with the given parameters (used to create measure specs with {@link MeasureSpec#makeMeasureSpec(int, int)}. if measuredView is non null, the method returns a {@link Pair} of (width, height) for the view (using {@link View#getMeasuredWidth()} and {@link View#getMeasuredHeight()}).

param
viewToMeasure the view on which to execute measure().
param
measuredView if non null, the view to query for its measured width/height.
param
width the width to use in the MeasureSpec.
param
widthMode the MeasureSpec mode to use for the width.
param
height the height to use in the MeasureSpec.
param
heightMode the MeasureSpec mode to use for the height.
return
the measured width/height if measuredView is non-null, null otherwise.

        int w_spec = MeasureSpec.makeMeasureSpec(width, widthMode);
        int h_spec = MeasureSpec.makeMeasureSpec(height, heightMode);
        viewToMeasure.measure(w_spec, h_spec);

        if (measuredView != null) {
            return Pair.of(measuredView.getMeasuredWidth(), measuredView.getMeasuredHeight());
        }

        return null;
    
public com.android.ide.common.rendering.api.ResultmoveChild(android.view.ViewGroup newParentView, android.view.View childView, int index, java.util.Map layoutParamsMap, com.android.ide.common.rendering.api.IAnimationListener listener)
Moves a view to a new parent at a given location

{@link #acquire(long)} must have been called before this.

throws
IllegalStateException if the current context is different than the one owned by the scene, or if {@link #acquire(long)} was not called.
see
RenderSession#moveChild(Object, Object, int, Map, IAnimationListener)

        checkLock();

        invalidateRenderingSize();

        LayoutParams layoutParams = null;
        if (layoutParamsMap != null) {
            // need to create a new LayoutParams object for the new parent.
            layoutParams = newParentView.generateLayoutParams(
                    new BridgeLayoutParamsMapAttributes(layoutParamsMap));
        }

        // get the current parent of the view that needs to be moved.
        final ViewGroup previousParent = (ViewGroup) childView.getParent();

        if (listener != null) {
            final LayoutParams params = layoutParams;

            // there is no support for animating views across layouts, so in case the new and old
            // parent views are different we fake the animation through a no animation thread.
            if (previousParent != newParentView) {
                new Thread("not animated moveChild") {
                    @Override
                    public void run() {
                        Result result = moveView(previousParent, newParentView, childView, index,
                                params);
                        if (!result.isSuccess()) {
                            listener.done(result);
                        }

                        // ready to do the work, acquire the scene.
                        result = acquire(250);
                        if (!result.isSuccess()) {
                            listener.done(result);
                            return;
                        }

                        try {
                            result = render(false /*freshRender*/);
                            if (result.isSuccess()) {
                                listener.onNewFrame(RenderSessionImpl.this.getSession());
                            }
                        } finally {
                            release();
                        }

                        listener.done(result);
                    }
                }.start();
            } else {
                new AnimationThread(this, "moveChild", listener) {

                    @Override
                    public Result preAnimation() {
                        // set up the transition for the parent.
                        LayoutTransition transition = new LayoutTransition();
                        previousParent.setLayoutTransition(transition);

                        // tweak the animation durations and start delays (to match the duration of
                        // animation playing just before).
                        // Note: Cannot user Animation.setDuration() directly. Have to set it
                        // on the LayoutTransition.
                        transition.setDuration(LayoutTransition.DISAPPEARING, 100);
                        // CHANGE_DISAPPEARING plays after DISAPPEARING
                        transition.setStartDelay(LayoutTransition.CHANGE_DISAPPEARING, 100);

                        transition.setDuration(LayoutTransition.CHANGE_DISAPPEARING, 100);

                        transition.setDuration(LayoutTransition.CHANGE_APPEARING, 100);
                        // CHANGE_APPEARING plays after CHANGE_APPEARING
                        transition.setStartDelay(LayoutTransition.APPEARING, 100);

                        transition.setDuration(LayoutTransition.APPEARING, 100);

                        return moveView(previousParent, newParentView, childView, index, params);
                    }

                    @Override
                    public void postAnimation() {
                        previousParent.setLayoutTransition(null);
                        newParentView.setLayoutTransition(null);
                    }
                }.start();
            }

            // always return success since the real status will come through the listener.
            return SUCCESS.createResult(layoutParams);
        }

        Result result = moveView(previousParent, newParentView, childView, index, layoutParams);
        if (!result.isSuccess()) {
            return result;
        }

        result = render(false /*freshRender*/);
        if (layoutParams != null && result.isSuccess()) {
            result = result.getCopyWithData(layoutParams);
        }

        return result;
    
private com.android.ide.common.rendering.api.ResultmoveView(android.view.ViewGroup previousParent, android.view.ViewGroup newParent, android.view.View movedView, int index, android.view.ViewGroup.LayoutParams params)
Moves a View from its current parent to a new given parent at a new given location, with an optional new {@link LayoutParams} instance

param
previousParent the previous parent, still owning the child at the time of the call.
param
newParent the new parent
param
movedView the view to move
param
index the new location in the new parent
param
params an option (can be null) {@link LayoutParams} instance.
return
a Result with {@link Status#SUCCESS} or {@link Status#ERROR_VIEWGROUP_NO_CHILDREN} if the given parent doesn't support adding views.

        try {
            // check if there is a transition on the previousParent.
            LayoutTransition previousTransition = previousParent.getLayoutTransition();
            if (previousTransition != null) {
                // in this case there is an animation. This means we have to wait for the child's
                // parent reference to be null'ed out so that we can add it to the new parent.
                // It is technically removed right before the DISAPPEARING animation is done (if
                // the animation of this type is not null, otherwise it's after which is impossible
                // to handle).
                // Because there is no move animation, if the new parent is the same as the old
                // parent, we need to wait until the CHANGE_DISAPPEARING animation is done before
                // adding the child or the child will appear in its new location before the
                // other children have made room for it.

                // add a listener to the transition to be notified of the actual removal.
                previousTransition.addTransitionListener(new TransitionListener() {
                    private int mChangeDisappearingCount = 0;

                    @Override
                    public void startTransition(LayoutTransition transition, ViewGroup container,
                            View view, int transitionType) {
                        if (transitionType == LayoutTransition.CHANGE_DISAPPEARING) {
                            mChangeDisappearingCount++;
                        }
                    }

                    @Override
                    public void endTransition(LayoutTransition transition, ViewGroup container,
                            View view, int transitionType) {
                        if (transitionType == LayoutTransition.CHANGE_DISAPPEARING) {
                            mChangeDisappearingCount--;
                        }

                        if (transitionType == LayoutTransition.CHANGE_DISAPPEARING &&
                                mChangeDisappearingCount == 0) {
                            // add it to the parentView in the correct location
                            if (params != null) {
                                newParent.addView(movedView, index, params);
                            } else {
                                newParent.addView(movedView, index);
                            }
                        }
                    }
                });

                // remove the view from the current parent.
                previousParent.removeView(movedView);

                // and return since adding the view to the new parent is done in the listener.
                return SUCCESS.createResult();
            } else {
                // standard code with no animation. pretty simple.
                previousParent.removeView(movedView);

                // add it to the parentView in the correct location
                if (params != null) {
                    newParent.addView(movedView, index, params);
                } else {
                    newParent.addView(movedView, index);
                }

                return SUCCESS.createResult();
            }
        } catch (UnsupportedOperationException e) {
            // looks like this is a view class that doesn't support children manipulation!
            return ERROR_VIEWGROUP_NO_CHILDREN.createResult();
        }
    
private voidpostInflateProcess(android.view.View view, com.android.ide.common.rendering.api.IProjectCallback projectCallback, android.view.View skip)
Post process on a view hierarchy that was just inflated.

At the moment this only supports TabHost: If {@link TabHost} is detected, look for the {@link TabWidget}, and the corresponding {@link FrameLayout} and make new tabs automatically based on the content of the {@link FrameLayout}.

param
view the root view to process.
param
projectCallback callback to the project.
param
skip the view and it's children are not processed.

        if (view == skip) {
            return;
        }
        if (view instanceof TabHost) {
            setupTabHost((TabHost) view, projectCallback);
        } else if (view instanceof QuickContactBadge) {
            QuickContactBadge badge = (QuickContactBadge) view;
            badge.setImageToDefault();
        } else if (view instanceof AdapterView<?>) {
            // get the view ID.
            int id = view.getId();

            BridgeContext context = getContext();

            // get a ResourceReference from the integer ID.
            ResourceReference listRef = context.resolveId(id);

            if (listRef != null) {
                SessionParams params = getParams();
                AdapterBinding binding = params.getAdapterBindings().get(listRef);

                // if there was no adapter binding, trying to get it from the call back.
                if (binding == null) {
                    binding = params.getProjectCallback().getAdapterBinding(listRef,
                            context.getViewKey(view), view);
                }

                if (binding != null) {

                    if (view instanceof AbsListView) {
                        if ((binding.getFooterCount() > 0 || binding.getHeaderCount() > 0) &&
                                view instanceof ListView) {
                            ListView list = (ListView) view;

                            boolean skipCallbackParser = false;

                            int count = binding.getHeaderCount();
                            for (int i = 0; i < count; i++) {
                                Pair<View, Boolean> pair = context.inflateView(
                                        binding.getHeaderAt(i),
                                        list, false /*attachToRoot*/, skipCallbackParser);
                                if (pair.getFirst() != null) {
                                    list.addHeaderView(pair.getFirst());
                                }

                                skipCallbackParser |= pair.getSecond();
                            }

                            count = binding.getFooterCount();
                            for (int i = 0; i < count; i++) {
                                Pair<View, Boolean> pair = context.inflateView(
                                        binding.getFooterAt(i),
                                        list, false /*attachToRoot*/, skipCallbackParser);
                                if (pair.getFirst() != null) {
                                    list.addFooterView(pair.getFirst());
                                }

                                skipCallbackParser |= pair.getSecond();
                            }
                        }

                        if (view instanceof ExpandableListView) {
                            ((ExpandableListView) view).setAdapter(
                                    new FakeExpandableAdapter(
                                            listRef, binding, params.getProjectCallback()));
                        } else {
                            ((AbsListView) view).setAdapter(
                                    new FakeAdapter(
                                            listRef, binding, params.getProjectCallback()));
                        }
                    } else if (view instanceof AbsSpinner) {
                        ((AbsSpinner) view).setAdapter(
                                new FakeAdapter(
                                        listRef, binding, params.getProjectCallback()));
                    }
                }
            }
        } else if (view instanceof ViewGroup) {
            ViewGroup group = (ViewGroup) view;
            final int count = group.getChildCount();
            for (int c = 0; c < count; c++) {
                View child = group.getChildAt(c);
                postInflateProcess(child, projectCallback, skip);
            }
        }
    
public com.android.ide.common.rendering.api.ResultremoveChild(android.view.View childView, com.android.ide.common.rendering.api.IAnimationListener listener)
Removes a child from its current parent.

{@link #acquire(long)} must have been called before this.

throws
IllegalStateException if the current context is different than the one owned by the scene, or if {@link #acquire(long)} was not called.
see
RenderSession#removeChild(Object, IAnimationListener)

        checkLock();

        invalidateRenderingSize();

        final ViewGroup parent = (ViewGroup) childView.getParent();

        if (listener != null) {
            new AnimationThread(this, "moveChild", listener) {

                @Override
                public Result preAnimation() {
                    parent.setLayoutTransition(new LayoutTransition());
                    return removeView(parent, childView);
                }

                @Override
                public void postAnimation() {
                    parent.setLayoutTransition(null);
                }
            }.start();

            // always return success since the real status will come through the listener.
            return SUCCESS.createResult();
        }

        Result result = removeView(parent, childView);
        if (!result.isSuccess()) {
            return result;
        }

        return render(false /*freshRender*/);
    
private com.android.ide.common.rendering.api.ResultremoveView(android.view.ViewGroup parent, android.view.View view)
Removes a given view from its current parent.

param
view the view to remove from its parent
return
a Result with {@link Status#SUCCESS} or {@link Status#ERROR_VIEWGROUP_NO_CHILDREN} if the given parent doesn't support adding views.

        try {
            parent.removeView(view);
            return SUCCESS.createResult();
        } catch (UnsupportedOperationException e) {
            // looks like this is a view class that doesn't support children manipulation!
            return ERROR_VIEWGROUP_NO_CHILDREN.createResult();
        }
    
public com.android.ide.common.rendering.api.Resultrender(boolean freshRender)
Renders the scene.

{@link #acquire(long)} must have been called before this.

param
freshRender whether the render is a new one and should erase the existing bitmap (in the case where bitmaps are reused). This is typically needed when not playing animations.)
throws
IllegalStateException if the current context is different than the one owned by the scene, or if {@link #acquire(long)} was not called.
see
SessionParams#getRenderingMode()
see
RenderSession#render(long)

        checkLock();

        SessionParams params = getParams();

        try {
            if (mViewRoot == null) {
                return ERROR_NOT_INFLATED.createResult();
            }

            RenderingMode renderingMode = params.getRenderingMode();
            HardwareConfig hardwareConfig = params.getHardwareConfig();

            // only do the screen measure when needed.
            boolean newRenderSize = false;
            if (mMeasuredScreenWidth == -1) {
                newRenderSize = true;
                mMeasuredScreenWidth = hardwareConfig.getScreenWidth();
                mMeasuredScreenHeight = hardwareConfig.getScreenHeight();

                if (renderingMode != RenderingMode.NORMAL) {
                    int widthMeasureSpecMode = renderingMode.isHorizExpand() ?
                            MeasureSpec.UNSPECIFIED // this lets us know the actual needed size
                            : MeasureSpec.EXACTLY;
                    int heightMeasureSpecMode = renderingMode.isVertExpand() ?
                            MeasureSpec.UNSPECIFIED // this lets us know the actual needed size
                            : MeasureSpec.EXACTLY;

                    // We used to compare the measured size of the content to the screen size but
                    // this does not work anymore due to the 2 following issues:
                    // - If the content is in a decor (system bar, title/action bar), the root view
                    //   will not resize even with the UNSPECIFIED because of the embedded layout.
                    // - If there is no decor, but a dialog frame, then the dialog padding prevents
                    //   comparing the size of the content to the screen frame (as it would not
                    //   take into account the dialog padding).

                    // The solution is to first get the content size in a normal rendering, inside
                    // the decor or the dialog padding.
                    // Then measure only the content with UNSPECIFIED to see the size difference
                    // and apply this to the screen size.

                    // first measure the full layout, with EXACTLY to get the size of the
                    // content as it is inside the decor/dialog
                    @SuppressWarnings("deprecation")
                    Pair<Integer, Integer> exactMeasure = measureView(
                            mViewRoot, mContentRoot.getChildAt(0),
                            mMeasuredScreenWidth, MeasureSpec.EXACTLY,
                            mMeasuredScreenHeight, MeasureSpec.EXACTLY);

                    // now measure the content only using UNSPECIFIED (where applicable, based on
                    // the rendering mode). This will give us the size the content needs.
                    @SuppressWarnings("deprecation")
                    Pair<Integer, Integer> result = measureView(
                            mContentRoot, mContentRoot.getChildAt(0),
                            mMeasuredScreenWidth, widthMeasureSpecMode,
                            mMeasuredScreenHeight, heightMeasureSpecMode);

                    // now look at the difference and add what is needed.
                    if (renderingMode.isHorizExpand()) {
                        int measuredWidth = exactMeasure.getFirst();
                        int neededWidth = result.getFirst();
                        if (neededWidth > measuredWidth) {
                            mMeasuredScreenWidth += neededWidth - measuredWidth;
                        }
                    }

                    if (renderingMode.isVertExpand()) {
                        int measuredHeight = exactMeasure.getSecond();
                        int neededHeight = result.getSecond();
                        if (neededHeight > measuredHeight) {
                            mMeasuredScreenHeight += neededHeight - measuredHeight;
                        }
                    }
                }
            }

            // measure again with the size we need
            // This must always be done before the call to layout
            measureView(mViewRoot, null /*measuredView*/,
                    mMeasuredScreenWidth, MeasureSpec.EXACTLY,
                    mMeasuredScreenHeight, MeasureSpec.EXACTLY);

            // now do the layout.
            mViewRoot.layout(0, 0, mMeasuredScreenWidth, mMeasuredScreenHeight);

            if (params.isLayoutOnly()) {
                // delete the canvas and image to reset them on the next full rendering
                mImage = null;
                mCanvas = null;
            } else {
                AttachInfo_Accessor.dispatchOnPreDraw(mViewRoot);

                // draw the views
                // create the BufferedImage into which the layout will be rendered.
                boolean newImage = false;
                if (newRenderSize || mCanvas == null) {
                    if (params.getImageFactory() != null) {
                        mImage = params.getImageFactory().getImage(
                                mMeasuredScreenWidth,
                                mMeasuredScreenHeight);
                    } else {
                        mImage = new BufferedImage(
                                mMeasuredScreenWidth,
                                mMeasuredScreenHeight,
                                BufferedImage.TYPE_INT_ARGB);
                        newImage = true;
                    }

                    if (params.isBgColorOverridden()) {
                        // since we override the content, it's the same as if it was a new image.
                        newImage = true;
                        Graphics2D gc = mImage.createGraphics();
                        gc.setColor(new Color(params.getOverrideBgColor(), true));
                        gc.setComposite(AlphaComposite.Src);
                        gc.fillRect(0, 0, mMeasuredScreenWidth, mMeasuredScreenHeight);
                        gc.dispose();
                    }

                    // create an Android bitmap around the BufferedImage
                    Bitmap bitmap = Bitmap_Delegate.createBitmap(mImage,
                            true /*isMutable*/, hardwareConfig.getDensity());

                    // create a Canvas around the Android bitmap
                    mCanvas = new Canvas(bitmap);
                    mCanvas.setDensity(hardwareConfig.getDensity().getDpiValue());
                }

                if (freshRender && !newImage) {
                    Graphics2D gc = mImage.createGraphics();
                    gc.setComposite(AlphaComposite.Src);

                    gc.setColor(new Color(0x00000000, true));
                    gc.fillRect(0, 0,
                            mMeasuredScreenWidth, mMeasuredScreenHeight);

                    // done
                    gc.dispose();
                }

                mViewRoot.draw(mCanvas);
            }

            mSystemViewInfoList = visitAllChildren(mViewRoot, 0, params.getExtendedViewInfoMode(),
                    false);

            // success!
            return SUCCESS.createResult();
        } catch (Throwable e) {
            // get the real cause of the exception.
            Throwable t = e;
            while (t.getCause() != null) {
                t = t.getCause();
            }

            return ERROR_UNKNOWN.createResult(t.getMessage(), t);
        }
    
public voidsetScene(com.android.ide.common.rendering.api.RenderSession session)

        mScene = session;
    
private voidsetupTabHost(android.widget.TabHost tabHost, com.android.ide.common.rendering.api.IProjectCallback projectCallback)
Sets up a {@link TabHost} object.

param
tabHost the TabHost to setup.
param
projectCallback The project callback object to access the project R class.
throws
PostInflateException

        // look for the TabWidget, and the FrameLayout. They have their own specific names
        View v = tabHost.findViewById(android.R.id.tabs);

        if (v == null) {
            throw new PostInflateException(
                    "TabHost requires a TabWidget with id \"android:id/tabs\".\n");
        }

        if (!(v instanceof TabWidget)) {
            throw new PostInflateException(String.format(
                    "TabHost requires a TabWidget with id \"android:id/tabs\".\n" +
                    "View found with id 'tabs' is '%s'", v.getClass().getCanonicalName()));
        }

        v = tabHost.findViewById(android.R.id.tabcontent);

        if (v == null) {
            // TODO: see if we can fake tabs even without the FrameLayout (same below when the frameLayout is empty)
            //noinspection SpellCheckingInspection
            throw new PostInflateException(
                    "TabHost requires a FrameLayout with id \"android:id/tabcontent\".");
        }

        if (!(v instanceof FrameLayout)) {
            //noinspection SpellCheckingInspection
            throw new PostInflateException(String.format(
                    "TabHost requires a FrameLayout with id \"android:id/tabcontent\".\n" +
                    "View found with id 'tabcontent' is '%s'", v.getClass().getCanonicalName()));
        }

        FrameLayout content = (FrameLayout)v;

        // now process the content of the frameLayout and dynamically create tabs for it.
        final int count = content.getChildCount();

        // this must be called before addTab() so that the TabHost searches its TabWidget
        // and FrameLayout.
        tabHost.setup();

        if (count == 0) {
            // Create a dummy child to get a single tab
            TabSpec spec = tabHost.newTabSpec("tag").setIndicator("Tab Label",
                    tabHost.getResources().getDrawable(android.R.drawable.ic_menu_info_details))
                    .setContent(new TabHost.TabContentFactory() {
                        @Override
                        public View createTabContent(String tag) {
                            return new LinearLayout(getContext());
                        }
                    });
            tabHost.addTab(spec);
        } else {
            // for each child of the frameLayout, add a new TabSpec
            for (int i = 0 ; i < count ; i++) {
                View child = content.getChildAt(i);
                String tabSpec = String.format("tab_spec%d", i+1);
                @SuppressWarnings("ConstantConditions")  // child cannot be null.
                int id = child.getId();
                @SuppressWarnings("deprecation")
                Pair<ResourceType, String> resource = projectCallback.resolveResourceId(id);
                String name;
                if (resource != null) {
                    name = resource.getSecond();
                } else {
                    name = String.format("Tab %d", i+1); // default name if id is unresolved.
                }
                tabHost.addTab(tabHost.newTabSpec(tabSpec).setIndicator(name).setContent(id));
            }
        }
    
private com.android.ide.common.rendering.api.ViewInfovisit(android.view.View view, int offset, boolean setExtendedInfo, boolean isContentFrame)
Visits a {@link View} and its children and generate a {@link ViewInfo} containing the bounds of all the views.

param
view the root View
param
offset an offset for the view bounds.
param
setExtendedInfo whether to set the extended view info in the {@link ViewInfo} object.
param
isContentFrame {@code true} if the {@code ViewInfo} to be created is part of the content frame.
return
{@code ViewInfo} containing the bounds of the view and it children otherwise.

        ViewInfo result = createViewInfo(view, offset, setExtendedInfo, isContentFrame);

        if (view instanceof ViewGroup) {
            ViewGroup group = ((ViewGroup) view);
            result.setChildren(visitAllChildren(group, isContentFrame ? 0 : offset,
                    setExtendedInfo, isContentFrame));
        }
        return result;
    
private java.util.ListvisitAllChildren(android.view.ViewGroup viewGroup, int offset, boolean setExtendedInfo, boolean isContentFrame)
Visits all the children of a given ViewGroup and generates a list of {@link ViewInfo} containing the bounds of all the views. It also initializes the {@link #mViewInfoList} with the children of the {@code mContentRoot}.

param
viewGroup the root View
param
offset an offset from the top for the content view frame.
param
setExtendedInfo whether to set the extended view info in the {@link ViewInfo} object.
param
isContentFrame {@code true} if the {@code ViewInfo} to be created is part of the content frame. {@code false} if the {@code ViewInfo} to be created is part of the system decor.

        if (viewGroup == null) {
            return null;
        }

        if (!isContentFrame) {
            offset += viewGroup.getTop();
        }

        int childCount = viewGroup.getChildCount();
        if (viewGroup == mContentRoot) {
            List<ViewInfo> childrenWithoutOffset = new ArrayList<ViewInfo>(childCount);
            List<ViewInfo> childrenWithOffset = new ArrayList<ViewInfo>(childCount);
            for (int i = 0; i < childCount; i++) {
                ViewInfo[] childViewInfo = visitContentRoot(viewGroup.getChildAt(i), offset,
                        setExtendedInfo);
                childrenWithoutOffset.add(childViewInfo[0]);
                childrenWithOffset.add(childViewInfo[1]);
            }
            mViewInfoList = childrenWithOffset;
            return childrenWithoutOffset;
        } else {
            List<ViewInfo> children = new ArrayList<ViewInfo>(childCount);
            for (int i = 0; i < childCount; i++) {
                children.add(visit(viewGroup.getChildAt(i), offset, setExtendedInfo,
                        isContentFrame));
            }
            return children;
        }
    
private com.android.ide.common.rendering.api.ViewInfo[]visitContentRoot(android.view.View view, int offset, boolean setExtendedInfo)
Visits the children of {@link #mContentRoot} and generates {@link ViewInfo} containing the bounds of all the views. It returns two {@code ViewInfo} objects with the same children, one with the {@code offset} and other without the {@code offset}. The offset is needed to get the right bounds if the {@code ViewInfo} hierarchy is accessed from {@code mViewInfoList}. When the hierarchy is accessed via {@code mSystemViewInfoList}, the offset is not needed.

return
an array of length two, with ViewInfo at index 0 is without offset and ViewInfo at index 1 is with the offset.

        ViewInfo[] result = new ViewInfo[2];
        if (view == null) {
            return result;
        }

        result[0] = createViewInfo(view, 0, setExtendedInfo, true);
        result[1] = createViewInfo(view, offset, setExtendedInfo, true);
        if (view instanceof ViewGroup) {
            List<ViewInfo> children = visitAllChildren((ViewGroup) view, 0, setExtendedInfo, true);
            result[0].setChildren(children);
            result[1].setChildren(children);
        }
        return result;