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