RenderSessionImpl.java revision 01cdf8b135be3bf67b9386dc929109c3db82c730
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
17package com.android.layoutlib.bridge.impl;
18
19import static com.android.ide.common.rendering.api.Result.Status.ERROR_ANIM_NOT_FOUND;
20import static com.android.ide.common.rendering.api.Result.Status.ERROR_INFLATION;
21import static com.android.ide.common.rendering.api.Result.Status.ERROR_NOT_INFLATED;
22import static com.android.ide.common.rendering.api.Result.Status.ERROR_UNKNOWN;
23import static com.android.ide.common.rendering.api.Result.Status.ERROR_VIEWGROUP_NO_CHILDREN;
24import static com.android.ide.common.rendering.api.Result.Status.SUCCESS;
25
26import com.android.ide.common.rendering.api.AdapterBinding;
27import com.android.ide.common.rendering.api.HardwareConfig;
28import com.android.ide.common.rendering.api.IAnimationListener;
29import com.android.ide.common.rendering.api.ILayoutPullParser;
30import com.android.ide.common.rendering.api.IProjectCallback;
31import com.android.ide.common.rendering.api.RenderResources;
32import com.android.ide.common.rendering.api.RenderSession;
33import com.android.ide.common.rendering.api.ResourceReference;
34import com.android.ide.common.rendering.api.ResourceValue;
35import com.android.ide.common.rendering.api.Result;
36import com.android.ide.common.rendering.api.Result.Status;
37import com.android.ide.common.rendering.api.SessionParams;
38import com.android.ide.common.rendering.api.SessionParams.RenderingMode;
39import com.android.ide.common.rendering.api.StyleResourceValue;
40import com.android.ide.common.rendering.api.ViewInfo;
41import com.android.ide.common.rendering.api.ViewType;
42import com.android.internal.util.XmlUtils;
43import com.android.internal.view.menu.ActionMenuItemView;
44import com.android.internal.view.menu.BridgeMenuItemImpl;
45import com.android.internal.view.menu.IconMenuItemView;
46import com.android.internal.view.menu.ListMenuItemView;
47import com.android.internal.view.menu.MenuItemImpl;
48import com.android.internal.view.menu.MenuView;
49import com.android.layoutlib.bridge.Bridge;
50import com.android.layoutlib.bridge.android.BridgeContext;
51import com.android.layoutlib.bridge.android.BridgeLayoutParamsMapAttributes;
52import com.android.layoutlib.bridge.android.BridgeXmlBlockParser;
53import com.android.layoutlib.bridge.android.SessionParamsFlags;
54import com.android.layoutlib.bridge.bars.BridgeActionBar;
55import com.android.layoutlib.bridge.bars.AppCompatActionBar;
56import com.android.layoutlib.bridge.bars.Config;
57import com.android.layoutlib.bridge.bars.NavigationBar;
58import com.android.layoutlib.bridge.bars.StatusBar;
59import com.android.layoutlib.bridge.bars.TitleBar;
60import com.android.layoutlib.bridge.bars.FrameworkActionBar;
61import com.android.layoutlib.bridge.impl.binding.FakeAdapter;
62import com.android.layoutlib.bridge.impl.binding.FakeExpandableAdapter;
63import com.android.resources.Density;
64import com.android.resources.ResourceType;
65import com.android.resources.ScreenOrientation;
66import com.android.util.Pair;
67
68import org.xmlpull.v1.XmlPullParserException;
69
70import android.animation.AnimationThread;
71import android.animation.Animator;
72import android.animation.AnimatorInflater;
73import android.animation.LayoutTransition;
74import android.animation.LayoutTransition.TransitionListener;
75import android.app.Fragment_Delegate;
76import android.graphics.Bitmap;
77import android.graphics.Bitmap_Delegate;
78import android.graphics.Canvas;
79import android.graphics.drawable.Drawable;
80import android.preference.Preference_Delegate;
81import android.util.DisplayMetrics;
82import android.util.TypedValue;
83import android.view.AttachInfo_Accessor;
84import android.view.BridgeInflater;
85import android.view.IWindowManager;
86import android.view.IWindowManagerImpl;
87import android.view.Surface;
88import android.view.View;
89import android.view.View.MeasureSpec;
90import android.view.ViewGroup;
91import android.view.ViewGroup.LayoutParams;
92import android.view.ViewGroup.MarginLayoutParams;
93import android.view.ViewParent;
94import android.view.WindowManagerGlobal_Delegate;
95import android.widget.AbsListView;
96import android.widget.AbsSpinner;
97import android.widget.ActionMenuView;
98import android.widget.AdapterView;
99import android.widget.ExpandableListView;
100import android.widget.FrameLayout;
101import android.widget.LinearLayout;
102import android.widget.ListView;
103import android.widget.QuickContactBadge;
104import android.widget.TabHost;
105import android.widget.TabHost.TabSpec;
106import android.widget.TabWidget;
107
108import java.awt.AlphaComposite;
109import java.awt.Color;
110import java.awt.Graphics2D;
111import java.awt.image.BufferedImage;
112import java.util.ArrayList;
113import java.util.List;
114import 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 */
123public 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
555                // When disableBitmapCaching is true, we do not reuse mImage and
556                // we create a new one in every render.
557                // This is useful when mImage is just a wrapper of Graphics2D so
558                // it doesn't get cached.
559                boolean disableBitmapCaching = Boolean.TRUE.equals(params.getFlag(
560                    SessionParamsFlags.FLAG_KEY_DISABLE_BITMAP_CACHING));
561                if (newRenderSize || mCanvas == null || disableBitmapCaching) {
562                    if (params.getImageFactory() != null) {
563                        mImage = params.getImageFactory().getImage(
564                                mMeasuredScreenWidth,
565                                mMeasuredScreenHeight);
566                    } else {
567                        mImage = new BufferedImage(
568                                mMeasuredScreenWidth,
569                                mMeasuredScreenHeight,
570                                BufferedImage.TYPE_INT_ARGB);
571                        newImage = true;
572                    }
573
574                    if (params.isBgColorOverridden()) {
575                        // since we override the content, it's the same as if it was a new image.
576                        newImage = true;
577                        Graphics2D gc = mImage.createGraphics();
578                        gc.setColor(new Color(params.getOverrideBgColor(), true));
579                        gc.setComposite(AlphaComposite.Src);
580                        gc.fillRect(0, 0, mMeasuredScreenWidth, mMeasuredScreenHeight);
581                        gc.dispose();
582                    }
583
584                    // create an Android bitmap around the BufferedImage
585                    Bitmap bitmap = Bitmap_Delegate.createBitmap(mImage,
586                            true /*isMutable*/, hardwareConfig.getDensity());
587
588                    if (mCanvas == null) {
589                        // create a Canvas around the Android bitmap
590                        mCanvas = new Canvas(bitmap);
591                    } else {
592                        mCanvas.setBitmap(bitmap);
593                    }
594                    mCanvas.setDensity(hardwareConfig.getDensity().getDpiValue());
595                }
596
597                if (freshRender && !newImage) {
598                    Graphics2D gc = mImage.createGraphics();
599                    gc.setComposite(AlphaComposite.Src);
600
601                    gc.setColor(new Color(0x00000000, true));
602                    gc.fillRect(0, 0,
603                            mMeasuredScreenWidth, mMeasuredScreenHeight);
604
605                    // done
606                    gc.dispose();
607                }
608
609                mViewRoot.draw(mCanvas);
610            }
611
612            mSystemViewInfoList = visitAllChildren(mViewRoot, 0, params.getExtendedViewInfoMode(),
613                    false);
614
615            // success!
616            return SUCCESS.createResult();
617        } catch (Throwable e) {
618            // get the real cause of the exception.
619            Throwable t = e;
620            while (t.getCause() != null) {
621                t = t.getCause();
622            }
623
624            return ERROR_UNKNOWN.createResult(t.getMessage(), t);
625        }
626    }
627
628    /**
629     * Executes {@link View#measure(int, int)} on a given view with the given parameters (used
630     * to create measure specs with {@link MeasureSpec#makeMeasureSpec(int, int)}.
631     *
632     * if <var>measuredView</var> is non null, the method returns a {@link Pair} of (width, height)
633     * for the view (using {@link View#getMeasuredWidth()} and {@link View#getMeasuredHeight()}).
634     *
635     * @param viewToMeasure the view on which to execute measure().
636     * @param measuredView if non null, the view to query for its measured width/height.
637     * @param width the width to use in the MeasureSpec.
638     * @param widthMode the MeasureSpec mode to use for the width.
639     * @param height the height to use in the MeasureSpec.
640     * @param heightMode the MeasureSpec mode to use for the height.
641     * @return the measured width/height if measuredView is non-null, null otherwise.
642     */
643    @SuppressWarnings("deprecation")  // For the use of Pair
644    private Pair<Integer, Integer> measureView(ViewGroup viewToMeasure, View measuredView,
645            int width, int widthMode, int height, int heightMode) {
646        int w_spec = MeasureSpec.makeMeasureSpec(width, widthMode);
647        int h_spec = MeasureSpec.makeMeasureSpec(height, heightMode);
648        viewToMeasure.measure(w_spec, h_spec);
649
650        if (measuredView != null) {
651            return Pair.of(measuredView.getMeasuredWidth(), measuredView.getMeasuredHeight());
652        }
653
654        return null;
655    }
656
657    /**
658     * Animate an object
659     * <p>
660     * {@link #acquire(long)} must have been called before this.
661     *
662     * @throws IllegalStateException if the current context is different than the one owned by
663     *      the scene, or if {@link #acquire(long)} was not called.
664     *
665     * @see RenderSession#animate(Object, String, boolean, IAnimationListener)
666     */
667    public Result animate(Object targetObject, String animationName,
668            boolean isFrameworkAnimation, IAnimationListener listener) {
669        checkLock();
670
671        BridgeContext context = getContext();
672
673        // find the animation file.
674        ResourceValue animationResource;
675        int animationId = 0;
676        if (isFrameworkAnimation) {
677            animationResource = context.getRenderResources().getFrameworkResource(
678                    ResourceType.ANIMATOR, animationName);
679            if (animationResource != null) {
680                animationId = Bridge.getResourceId(ResourceType.ANIMATOR, animationName);
681            }
682        } else {
683            animationResource = context.getRenderResources().getProjectResource(
684                    ResourceType.ANIMATOR, animationName);
685            if (animationResource != null) {
686                animationId = context.getProjectCallback().getResourceId(
687                        ResourceType.ANIMATOR, animationName);
688            }
689        }
690
691        if (animationResource != null) {
692            try {
693                Animator anim = AnimatorInflater.loadAnimator(context, animationId);
694                if (anim != null) {
695                    anim.setTarget(targetObject);
696
697                    new PlayAnimationThread(anim, this, animationName, listener).start();
698
699                    return SUCCESS.createResult();
700                }
701            } catch (Exception e) {
702                // get the real cause of the exception.
703                Throwable t = e;
704                while (t.getCause() != null) {
705                    t = t.getCause();
706                }
707
708                return ERROR_UNKNOWN.createResult(t.getMessage(), t);
709            }
710        }
711
712        return ERROR_ANIM_NOT_FOUND.createResult();
713    }
714
715    /**
716     * Insert a new child into an existing parent.
717     * <p>
718     * {@link #acquire(long)} must have been called before this.
719     *
720     * @throws IllegalStateException if the current context is different than the one owned by
721     *      the scene, or if {@link #acquire(long)} was not called.
722     *
723     * @see RenderSession#insertChild(Object, ILayoutPullParser, int, IAnimationListener)
724     */
725    public Result insertChild(final ViewGroup parentView, ILayoutPullParser childXml,
726            final int index, IAnimationListener listener) {
727        checkLock();
728
729        BridgeContext context = getContext();
730
731        // create a block parser for the XML
732        BridgeXmlBlockParser blockParser = new BridgeXmlBlockParser(
733                childXml, context, false /* platformResourceFlag */);
734
735        // inflate the child without adding it to the root since we want to control where it'll
736        // get added. We do pass the parentView however to ensure that the layoutParams will
737        // be created correctly.
738        final View child = mInflater.inflate(blockParser, parentView, false /*attachToRoot*/);
739        blockParser.ensurePopped();
740
741        invalidateRenderingSize();
742
743        if (listener != null) {
744            new AnimationThread(this, "insertChild", listener) {
745
746                @Override
747                public Result preAnimation() {
748                    parentView.setLayoutTransition(new LayoutTransition());
749                    return addView(parentView, child, index);
750                }
751
752                @Override
753                public void postAnimation() {
754                    parentView.setLayoutTransition(null);
755                }
756            }.start();
757
758            // always return success since the real status will come through the listener.
759            return SUCCESS.createResult(child);
760        }
761
762        // add it to the parentView in the correct location
763        Result result = addView(parentView, child, index);
764        if (!result.isSuccess()) {
765            return result;
766        }
767
768        result = render(false /*freshRender*/);
769        if (result.isSuccess()) {
770            result = result.getCopyWithData(child);
771        }
772
773        return result;
774    }
775
776    /**
777     * Adds a given view to a given parent at a given index.
778     *
779     * @param parent the parent to receive the view
780     * @param view the view to add to the parent
781     * @param index the index where to do the add.
782     *
783     * @return a Result with {@link Status#SUCCESS} or
784     *     {@link Status#ERROR_VIEWGROUP_NO_CHILDREN} if the given parent doesn't support
785     *     adding views.
786     */
787    private Result addView(ViewGroup parent, View view, int index) {
788        try {
789            parent.addView(view, index);
790            return SUCCESS.createResult();
791        } catch (UnsupportedOperationException e) {
792            // looks like this is a view class that doesn't support children manipulation!
793            return ERROR_VIEWGROUP_NO_CHILDREN.createResult();
794        }
795    }
796
797    /**
798     * Moves a view to a new parent at a given location
799     * <p>
800     * {@link #acquire(long)} must have been called before this.
801     *
802     * @throws IllegalStateException if the current context is different than the one owned by
803     *      the scene, or if {@link #acquire(long)} was not called.
804     *
805     * @see RenderSession#moveChild(Object, Object, int, Map, IAnimationListener)
806     */
807    public Result moveChild(final ViewGroup newParentView, final View childView, final int index,
808            Map<String, String> layoutParamsMap, final IAnimationListener listener) {
809        checkLock();
810
811        invalidateRenderingSize();
812
813        LayoutParams layoutParams = null;
814        if (layoutParamsMap != null) {
815            // need to create a new LayoutParams object for the new parent.
816            layoutParams = newParentView.generateLayoutParams(
817                    new BridgeLayoutParamsMapAttributes(layoutParamsMap));
818        }
819
820        // get the current parent of the view that needs to be moved.
821        final ViewGroup previousParent = (ViewGroup) childView.getParent();
822
823        if (listener != null) {
824            final LayoutParams params = layoutParams;
825
826            // there is no support for animating views across layouts, so in case the new and old
827            // parent views are different we fake the animation through a no animation thread.
828            if (previousParent != newParentView) {
829                new Thread("not animated moveChild") {
830                    @Override
831                    public void run() {
832                        Result result = moveView(previousParent, newParentView, childView, index,
833                                params);
834                        if (!result.isSuccess()) {
835                            listener.done(result);
836                        }
837
838                        // ready to do the work, acquire the scene.
839                        result = acquire(250);
840                        if (!result.isSuccess()) {
841                            listener.done(result);
842                            return;
843                        }
844
845                        try {
846                            result = render(false /*freshRender*/);
847                            if (result.isSuccess()) {
848                                listener.onNewFrame(RenderSessionImpl.this.getSession());
849                            }
850                        } finally {
851                            release();
852                        }
853
854                        listener.done(result);
855                    }
856                }.start();
857            } else {
858                new AnimationThread(this, "moveChild", listener) {
859
860                    @Override
861                    public Result preAnimation() {
862                        // set up the transition for the parent.
863                        LayoutTransition transition = new LayoutTransition();
864                        previousParent.setLayoutTransition(transition);
865
866                        // tweak the animation durations and start delays (to match the duration of
867                        // animation playing just before).
868                        // Note: Cannot user Animation.setDuration() directly. Have to set it
869                        // on the LayoutTransition.
870                        transition.setDuration(LayoutTransition.DISAPPEARING, 100);
871                        // CHANGE_DISAPPEARING plays after DISAPPEARING
872                        transition.setStartDelay(LayoutTransition.CHANGE_DISAPPEARING, 100);
873
874                        transition.setDuration(LayoutTransition.CHANGE_DISAPPEARING, 100);
875
876                        transition.setDuration(LayoutTransition.CHANGE_APPEARING, 100);
877                        // CHANGE_APPEARING plays after CHANGE_APPEARING
878                        transition.setStartDelay(LayoutTransition.APPEARING, 100);
879
880                        transition.setDuration(LayoutTransition.APPEARING, 100);
881
882                        return moveView(previousParent, newParentView, childView, index, params);
883                    }
884
885                    @Override
886                    public void postAnimation() {
887                        previousParent.setLayoutTransition(null);
888                        newParentView.setLayoutTransition(null);
889                    }
890                }.start();
891            }
892
893            // always return success since the real status will come through the listener.
894            return SUCCESS.createResult(layoutParams);
895        }
896
897        Result result = moveView(previousParent, newParentView, childView, index, layoutParams);
898        if (!result.isSuccess()) {
899            return result;
900        }
901
902        result = render(false /*freshRender*/);
903        if (layoutParams != null && result.isSuccess()) {
904            result = result.getCopyWithData(layoutParams);
905        }
906
907        return result;
908    }
909
910    /**
911     * Moves a View from its current parent to a new given parent at a new given location, with
912     * an optional new {@link LayoutParams} instance
913     *
914     * @param previousParent the previous parent, still owning the child at the time of the call.
915     * @param newParent the new parent
916     * @param movedView the view to move
917     * @param index the new location in the new parent
918     * @param params an option (can be null) {@link LayoutParams} instance.
919     *
920     * @return a Result with {@link Status#SUCCESS} or
921     *     {@link Status#ERROR_VIEWGROUP_NO_CHILDREN} if the given parent doesn't support
922     *     adding views.
923     */
924    private Result moveView(ViewGroup previousParent, final ViewGroup newParent,
925            final View movedView, final int index, final LayoutParams params) {
926        try {
927            // check if there is a transition on the previousParent.
928            LayoutTransition previousTransition = previousParent.getLayoutTransition();
929            if (previousTransition != null) {
930                // in this case there is an animation. This means we have to wait for the child's
931                // parent reference to be null'ed out so that we can add it to the new parent.
932                // It is technically removed right before the DISAPPEARING animation is done (if
933                // the animation of this type is not null, otherwise it's after which is impossible
934                // to handle).
935                // Because there is no move animation, if the new parent is the same as the old
936                // parent, we need to wait until the CHANGE_DISAPPEARING animation is done before
937                // adding the child or the child will appear in its new location before the
938                // other children have made room for it.
939
940                // add a listener to the transition to be notified of the actual removal.
941                previousTransition.addTransitionListener(new TransitionListener() {
942                    private int mChangeDisappearingCount = 0;
943
944                    @Override
945                    public void startTransition(LayoutTransition transition, ViewGroup container,
946                            View view, int transitionType) {
947                        if (transitionType == LayoutTransition.CHANGE_DISAPPEARING) {
948                            mChangeDisappearingCount++;
949                        }
950                    }
951
952                    @Override
953                    public void endTransition(LayoutTransition transition, ViewGroup container,
954                            View view, int transitionType) {
955                        if (transitionType == LayoutTransition.CHANGE_DISAPPEARING) {
956                            mChangeDisappearingCount--;
957                        }
958
959                        if (transitionType == LayoutTransition.CHANGE_DISAPPEARING &&
960                                mChangeDisappearingCount == 0) {
961                            // add it to the parentView in the correct location
962                            if (params != null) {
963                                newParent.addView(movedView, index, params);
964                            } else {
965                                newParent.addView(movedView, index);
966                            }
967                        }
968                    }
969                });
970
971                // remove the view from the current parent.
972                previousParent.removeView(movedView);
973
974                // and return since adding the view to the new parent is done in the listener.
975                return SUCCESS.createResult();
976            } else {
977                // standard code with no animation. pretty simple.
978                previousParent.removeView(movedView);
979
980                // add it to the parentView in the correct location
981                if (params != null) {
982                    newParent.addView(movedView, index, params);
983                } else {
984                    newParent.addView(movedView, index);
985                }
986
987                return SUCCESS.createResult();
988            }
989        } catch (UnsupportedOperationException e) {
990            // looks like this is a view class that doesn't support children manipulation!
991            return ERROR_VIEWGROUP_NO_CHILDREN.createResult();
992        }
993    }
994
995    /**
996     * Removes a child from its current parent.
997     * <p>
998     * {@link #acquire(long)} must have been called before this.
999     *
1000     * @throws IllegalStateException if the current context is different than the one owned by
1001     *      the scene, or if {@link #acquire(long)} was not called.
1002     *
1003     * @see RenderSession#removeChild(Object, IAnimationListener)
1004     */
1005    public Result removeChild(final View childView, IAnimationListener listener) {
1006        checkLock();
1007
1008        invalidateRenderingSize();
1009
1010        final ViewGroup parent = (ViewGroup) childView.getParent();
1011
1012        if (listener != null) {
1013            new AnimationThread(this, "moveChild", listener) {
1014
1015                @Override
1016                public Result preAnimation() {
1017                    parent.setLayoutTransition(new LayoutTransition());
1018                    return removeView(parent, childView);
1019                }
1020
1021                @Override
1022                public void postAnimation() {
1023                    parent.setLayoutTransition(null);
1024                }
1025            }.start();
1026
1027            // always return success since the real status will come through the listener.
1028            return SUCCESS.createResult();
1029        }
1030
1031        Result result = removeView(parent, childView);
1032        if (!result.isSuccess()) {
1033            return result;
1034        }
1035
1036        return render(false /*freshRender*/);
1037    }
1038
1039    /**
1040     * Removes a given view from its current parent.
1041     *
1042     * @param view the view to remove from its parent
1043     *
1044     * @return a Result with {@link Status#SUCCESS} or
1045     *     {@link Status#ERROR_VIEWGROUP_NO_CHILDREN} if the given parent doesn't support
1046     *     adding views.
1047     */
1048    private Result removeView(ViewGroup parent, View view) {
1049        try {
1050            parent.removeView(view);
1051            return SUCCESS.createResult();
1052        } catch (UnsupportedOperationException e) {
1053            // looks like this is a view class that doesn't support children manipulation!
1054            return ERROR_VIEWGROUP_NO_CHILDREN.createResult();
1055        }
1056    }
1057
1058
1059    private void findBackground(RenderResources resources) {
1060        if (!getParams().isBgColorOverridden()) {
1061            mWindowBackground = resources.findItemInTheme("windowBackground",
1062                    true /*isFrameworkAttr*/);
1063            if (mWindowBackground != null) {
1064                mWindowBackground = resources.resolveResValue(mWindowBackground);
1065            }
1066        }
1067    }
1068
1069    private boolean hasSoftwareButtons() {
1070        return getParams().getHardwareConfig().hasSoftwareButtons();
1071    }
1072
1073    private void findStatusBar(RenderResources resources, DisplayMetrics metrics) {
1074        boolean windowFullscreen = getBooleanThemeValue(resources,
1075                "windowFullscreen", false, !isThemeAppCompat(resources));
1076
1077        if (!windowFullscreen && !mWindowIsFloating) {
1078            // default value
1079            mStatusBarSize = DEFAULT_STATUS_BAR_HEIGHT;
1080
1081            // get the real value
1082            ResourceValue value = resources.getFrameworkResource(ResourceType.DIMEN,
1083                    "status_bar_height");
1084
1085            if (value != null) {
1086                TypedValue typedValue = ResourceHelper.getValue("status_bar_height",
1087                        value.getValue(), true /*requireUnit*/);
1088                if (typedValue != null) {
1089                    // compute the pixel value based on the display metrics
1090                    mStatusBarSize = (int)typedValue.getDimension(metrics);
1091                }
1092            }
1093        }
1094    }
1095
1096    private void findActionBar(RenderResources resources, DisplayMetrics metrics) {
1097        if (mWindowIsFloating) {
1098            return;
1099        }
1100
1101        boolean windowActionBar = getBooleanThemeValue(resources,
1102                "windowActionBar", true, !isThemeAppCompat(resources));
1103
1104        // if there's a value and it's false (default is true)
1105        if (windowActionBar) {
1106
1107            // default size of the window title bar
1108            mActionBarSize = DEFAULT_TITLE_BAR_HEIGHT;
1109
1110            // get value from the theme.
1111            ResourceValue value = resources.findItemInTheme("actionBarSize",
1112                    true /*isFrameworkAttr*/);
1113
1114            // resolve it
1115            value = resources.resolveResValue(value);
1116
1117            if (value != null) {
1118                // get the numerical value, if available
1119                TypedValue typedValue = ResourceHelper.getValue("actionBarSize", value.getValue(),
1120                        true /*requireUnit*/);
1121                if (typedValue != null) {
1122                    // compute the pixel value based on the display metrics
1123                    mActionBarSize = (int)typedValue.getDimension(metrics);
1124                }
1125            }
1126        } else {
1127            // action bar overrides title bar so only look for this one if action bar is hidden
1128            boolean windowNoTitle = getBooleanThemeValue(resources,
1129                    "windowNoTitle", false, !isThemeAppCompat(resources));
1130
1131            if (!windowNoTitle) {
1132
1133                // default size of the window title bar
1134                mTitleBarSize = DEFAULT_TITLE_BAR_HEIGHT;
1135
1136                // get value from the theme.
1137                ResourceValue value = resources.findItemInTheme("windowTitleSize",
1138                        true /*isFrameworkAttr*/);
1139
1140                // resolve it
1141                value = resources.resolveResValue(value);
1142
1143                if (value != null) {
1144                    // get the numerical value, if available
1145                    TypedValue typedValue = ResourceHelper.getValue("windowTitleSize",
1146                            value.getValue(), true /*requireUnit*/);
1147                    if (typedValue != null) {
1148                        // compute the pixel value based on the display metrics
1149                        mTitleBarSize = (int)typedValue.getDimension(metrics);
1150                    }
1151                }
1152            }
1153
1154        }
1155    }
1156
1157    private void findNavigationBar(RenderResources resources, DisplayMetrics metrics) {
1158        if (hasSoftwareButtons() && !mWindowIsFloating) {
1159
1160            // default value
1161            mNavigationBarSize = 48; // ??
1162
1163            HardwareConfig hardwareConfig = getParams().getHardwareConfig();
1164
1165            boolean barOnBottom = true;
1166
1167            if (hardwareConfig.getOrientation() == ScreenOrientation.LANDSCAPE) {
1168                // compute the dp of the screen.
1169                int shortSize = hardwareConfig.getScreenHeight();
1170
1171                // compute in dp
1172                int shortSizeDp = shortSize * DisplayMetrics.DENSITY_DEFAULT /
1173                        hardwareConfig.getDensity().getDpiValue();
1174
1175                // 0-599dp: "phone" UI with bar on the side
1176                // 600+dp: "tablet" UI with bar on the bottom
1177                barOnBottom = shortSizeDp >= 600;
1178            }
1179
1180            if (barOnBottom) {
1181                mNavigationBarOrientation = LinearLayout.HORIZONTAL;
1182            } else {
1183                mNavigationBarOrientation = LinearLayout.VERTICAL;
1184            }
1185
1186            // get the real value
1187            ResourceValue value = resources.getFrameworkResource(ResourceType.DIMEN,
1188                    barOnBottom ? "navigation_bar_height" : "navigation_bar_width");
1189
1190            if (value != null) {
1191                TypedValue typedValue = ResourceHelper.getValue("navigation_bar_height",
1192                        value.getValue(), true /*requireUnit*/);
1193                if (typedValue != null) {
1194                    // compute the pixel value based on the display metrics
1195                    mNavigationBarSize = (int)typedValue.getDimension(metrics);
1196                }
1197            }
1198        }
1199    }
1200
1201    private boolean isThemeAppCompat(RenderResources resources) {
1202        // Ideally, we should check if the corresponding activity extends
1203        // android.support.v7.app.ActionBarActivity, and not care about the theme name at all.
1204        if (mIsThemeAppCompat == null) {
1205            StyleResourceValue defaultTheme = resources.getDefaultTheme();
1206          // We can't simply check for parent using resources.themeIsParentOf() since the
1207          // inheritance structure isn't really what one would expect. The first common parent
1208          // between Theme.AppCompat.Light and Theme.AppCompat is Theme.Material (for v21).
1209            boolean isThemeAppCompat = false;
1210            for (int i = 0; i < 50; i++) {
1211                // for loop ensures that we don't run into cyclic theme inheritance.
1212                if (defaultTheme.getName().startsWith("Theme.AppCompat")) {
1213                    isThemeAppCompat = true;
1214                    break;
1215                }
1216                defaultTheme = resources.getParent(defaultTheme);
1217                if (defaultTheme == null) {
1218                    break;
1219                }
1220            }
1221            mIsThemeAppCompat = isThemeAppCompat;
1222        }
1223        return mIsThemeAppCompat;
1224    }
1225
1226    /**
1227     * Looks for an attribute in the current theme.
1228     *
1229     * @param resources the render resources
1230     * @param name the name of the attribute
1231     * @param defaultValue the default value.
1232     * @param isFrameworkAttr if the attribute is in android namespace
1233     * @return the value of the attribute or the default one if not found.
1234     */
1235    private boolean getBooleanThemeValue(RenderResources resources,
1236            String name, boolean defaultValue, boolean isFrameworkAttr) {
1237
1238        ResourceValue value = resources.findItemInTheme(name, isFrameworkAttr);
1239
1240        // because it may reference something else, we resolve it.
1241        value = resources.resolveResValue(value);
1242
1243        // if there's no value, return the default.
1244        if (value == null || value.getValue() == null) {
1245            return defaultValue;
1246        }
1247
1248        return XmlUtils.convertValueToBoolean(value.getValue(), defaultValue);
1249    }
1250
1251    /**
1252     * Post process on a view hierarchy that was just inflated.
1253     * <p/>
1254     * At the moment this only supports TabHost: If {@link TabHost} is detected, look for the
1255     * {@link TabWidget}, and the corresponding {@link FrameLayout} and make new tabs automatically
1256     * based on the content of the {@link FrameLayout}.
1257     * @param view the root view to process.
1258     * @param projectCallback callback to the project.
1259     * @param skip the view and it's children are not processed.
1260     */
1261    @SuppressWarnings("deprecation")  // For the use of Pair
1262    private void postInflateProcess(View view, IProjectCallback projectCallback, View skip)
1263            throws PostInflateException {
1264        if (view == skip) {
1265            return;
1266        }
1267        if (view instanceof TabHost) {
1268            setupTabHost((TabHost) view, projectCallback);
1269        } else if (view instanceof QuickContactBadge) {
1270            QuickContactBadge badge = (QuickContactBadge) view;
1271            badge.setImageToDefault();
1272        } else if (view instanceof AdapterView<?>) {
1273            // get the view ID.
1274            int id = view.getId();
1275
1276            BridgeContext context = getContext();
1277
1278            // get a ResourceReference from the integer ID.
1279            ResourceReference listRef = context.resolveId(id);
1280
1281            if (listRef != null) {
1282                SessionParams params = getParams();
1283                AdapterBinding binding = params.getAdapterBindings().get(listRef);
1284
1285                // if there was no adapter binding, trying to get it from the call back.
1286                if (binding == null) {
1287                    binding = params.getProjectCallback().getAdapterBinding(listRef,
1288                            context.getViewKey(view), view);
1289                }
1290
1291                if (binding != null) {
1292
1293                    if (view instanceof AbsListView) {
1294                        if ((binding.getFooterCount() > 0 || binding.getHeaderCount() > 0) &&
1295                                view instanceof ListView) {
1296                            ListView list = (ListView) view;
1297
1298                            boolean skipCallbackParser = false;
1299
1300                            int count = binding.getHeaderCount();
1301                            for (int i = 0; i < count; i++) {
1302                                Pair<View, Boolean> pair = context.inflateView(
1303                                        binding.getHeaderAt(i),
1304                                        list, false /*attachToRoot*/, skipCallbackParser);
1305                                if (pair.getFirst() != null) {
1306                                    list.addHeaderView(pair.getFirst());
1307                                }
1308
1309                                skipCallbackParser |= pair.getSecond();
1310                            }
1311
1312                            count = binding.getFooterCount();
1313                            for (int i = 0; i < count; i++) {
1314                                Pair<View, Boolean> pair = context.inflateView(
1315                                        binding.getFooterAt(i),
1316                                        list, false /*attachToRoot*/, skipCallbackParser);
1317                                if (pair.getFirst() != null) {
1318                                    list.addFooterView(pair.getFirst());
1319                                }
1320
1321                                skipCallbackParser |= pair.getSecond();
1322                            }
1323                        }
1324
1325                        if (view instanceof ExpandableListView) {
1326                            ((ExpandableListView) view).setAdapter(
1327                                    new FakeExpandableAdapter(
1328                                            listRef, binding, params.getProjectCallback()));
1329                        } else {
1330                            ((AbsListView) view).setAdapter(
1331                                    new FakeAdapter(
1332                                            listRef, binding, params.getProjectCallback()));
1333                        }
1334                    } else if (view instanceof AbsSpinner) {
1335                        ((AbsSpinner) view).setAdapter(
1336                                new FakeAdapter(
1337                                        listRef, binding, params.getProjectCallback()));
1338                    }
1339                }
1340            }
1341        } else if (view instanceof ViewGroup) {
1342            ViewGroup group = (ViewGroup) view;
1343            final int count = group.getChildCount();
1344            for (int c = 0; c < count; c++) {
1345                View child = group.getChildAt(c);
1346                postInflateProcess(child, projectCallback, skip);
1347            }
1348        }
1349    }
1350
1351    /**
1352     * Sets up a {@link TabHost} object.
1353     * @param tabHost the TabHost to setup.
1354     * @param projectCallback The project callback object to access the project R class.
1355     * @throws PostInflateException
1356     */
1357    private void setupTabHost(TabHost tabHost, IProjectCallback projectCallback)
1358            throws PostInflateException {
1359        // look for the TabWidget, and the FrameLayout. They have their own specific names
1360        View v = tabHost.findViewById(android.R.id.tabs);
1361
1362        if (v == null) {
1363            throw new PostInflateException(
1364                    "TabHost requires a TabWidget with id \"android:id/tabs\".\n");
1365        }
1366
1367        if (!(v instanceof TabWidget)) {
1368            throw new PostInflateException(String.format(
1369                    "TabHost requires a TabWidget with id \"android:id/tabs\".\n" +
1370                    "View found with id 'tabs' is '%s'", v.getClass().getCanonicalName()));
1371        }
1372
1373        v = tabHost.findViewById(android.R.id.tabcontent);
1374
1375        if (v == null) {
1376            // TODO: see if we can fake tabs even without the FrameLayout (same below when the frameLayout is empty)
1377            //noinspection SpellCheckingInspection
1378            throw new PostInflateException(
1379                    "TabHost requires a FrameLayout with id \"android:id/tabcontent\".");
1380        }
1381
1382        if (!(v instanceof FrameLayout)) {
1383            //noinspection SpellCheckingInspection
1384            throw new PostInflateException(String.format(
1385                    "TabHost requires a FrameLayout with id \"android:id/tabcontent\".\n" +
1386                    "View found with id 'tabcontent' is '%s'", v.getClass().getCanonicalName()));
1387        }
1388
1389        FrameLayout content = (FrameLayout)v;
1390
1391        // now process the content of the frameLayout and dynamically create tabs for it.
1392        final int count = content.getChildCount();
1393
1394        // this must be called before addTab() so that the TabHost searches its TabWidget
1395        // and FrameLayout.
1396        tabHost.setup();
1397
1398        if (count == 0) {
1399            // Create a dummy child to get a single tab
1400            TabSpec spec = tabHost.newTabSpec("tag").setIndicator("Tab Label",
1401                    tabHost.getResources().getDrawable(android.R.drawable.ic_menu_info_details))
1402                    .setContent(new TabHost.TabContentFactory() {
1403                        @Override
1404                        public View createTabContent(String tag) {
1405                            return new LinearLayout(getContext());
1406                        }
1407                    });
1408            tabHost.addTab(spec);
1409        } else {
1410            // for each child of the frameLayout, add a new TabSpec
1411            for (int i = 0 ; i < count ; i++) {
1412                View child = content.getChildAt(i);
1413                String tabSpec = String.format("tab_spec%d", i+1);
1414                @SuppressWarnings("ConstantConditions")  // child cannot be null.
1415                int id = child.getId();
1416                @SuppressWarnings("deprecation")
1417                Pair<ResourceType, String> resource = projectCallback.resolveResourceId(id);
1418                String name;
1419                if (resource != null) {
1420                    name = resource.getSecond();
1421                } else {
1422                    name = String.format("Tab %d", i+1); // default name if id is unresolved.
1423                }
1424                tabHost.addTab(tabHost.newTabSpec(tabSpec).setIndicator(name).setContent(id));
1425            }
1426        }
1427    }
1428
1429    /**
1430     * Visits a {@link View} and its children and generate a {@link ViewInfo} containing the
1431     * bounds of all the views.
1432     *
1433     * @param view the root View
1434     * @param offset an offset for the view bounds.
1435     * @param setExtendedInfo whether to set the extended view info in the {@link ViewInfo} object.
1436     * @param isContentFrame {@code true} if the {@code ViewInfo} to be created is part of the
1437     *                       content frame.
1438     *
1439     * @return {@code ViewInfo} containing the bounds of the view and it children otherwise.
1440     */
1441    private ViewInfo visit(View view, int offset, boolean setExtendedInfo,
1442            boolean isContentFrame) {
1443        ViewInfo result = createViewInfo(view, offset, setExtendedInfo, isContentFrame);
1444
1445        if (view instanceof ViewGroup) {
1446            ViewGroup group = ((ViewGroup) view);
1447            result.setChildren(visitAllChildren(group, isContentFrame ? 0 : offset,
1448                    setExtendedInfo, isContentFrame));
1449        }
1450        return result;
1451    }
1452
1453    /**
1454     * Visits all the children of a given ViewGroup and generates a list of {@link ViewInfo}
1455     * containing the bounds of all the views. It also initializes the {@link #mViewInfoList} with
1456     * the children of the {@code mContentRoot}.
1457     *
1458     * @param viewGroup the root View
1459     * @param offset an offset from the top for the content view frame.
1460     * @param setExtendedInfo whether to set the extended view info in the {@link ViewInfo} object.
1461     * @param isContentFrame {@code true} if the {@code ViewInfo} to be created is part of the
1462     *                       content frame. {@code false} if the {@code ViewInfo} to be created is
1463     *                       part of the system decor.
1464     */
1465    private List<ViewInfo> visitAllChildren(ViewGroup viewGroup, int offset,
1466            boolean setExtendedInfo, boolean isContentFrame) {
1467        if (viewGroup == null) {
1468            return null;
1469        }
1470
1471        if (!isContentFrame) {
1472            offset += viewGroup.getTop();
1473        }
1474
1475        int childCount = viewGroup.getChildCount();
1476        if (viewGroup == mContentRoot) {
1477            List<ViewInfo> childrenWithoutOffset = new ArrayList<ViewInfo>(childCount);
1478            List<ViewInfo> childrenWithOffset = new ArrayList<ViewInfo>(childCount);
1479            for (int i = 0; i < childCount; i++) {
1480                ViewInfo[] childViewInfo = visitContentRoot(viewGroup.getChildAt(i), offset,
1481                        setExtendedInfo);
1482                childrenWithoutOffset.add(childViewInfo[0]);
1483                childrenWithOffset.add(childViewInfo[1]);
1484            }
1485            mViewInfoList = childrenWithOffset;
1486            return childrenWithoutOffset;
1487        } else {
1488            List<ViewInfo> children = new ArrayList<ViewInfo>(childCount);
1489            for (int i = 0; i < childCount; i++) {
1490                children.add(visit(viewGroup.getChildAt(i), offset, setExtendedInfo,
1491                        isContentFrame));
1492            }
1493            return children;
1494        }
1495    }
1496
1497    /**
1498     * Visits the children of {@link #mContentRoot} and generates {@link ViewInfo} containing the
1499     * bounds of all the views. It returns two {@code ViewInfo} objects with the same children,
1500     * one with the {@code offset} and other without the {@code offset}. The offset is needed to
1501     * get the right bounds if the {@code ViewInfo} hierarchy is accessed from
1502     * {@code mViewInfoList}. When the hierarchy is accessed via {@code mSystemViewInfoList}, the
1503     * offset is not needed.
1504     *
1505     * @return an array of length two, with ViewInfo at index 0 is without offset and ViewInfo at
1506     *         index 1 is with the offset.
1507     */
1508    private ViewInfo[] visitContentRoot(View view, int offset, boolean setExtendedInfo) {
1509        ViewInfo[] result = new ViewInfo[2];
1510        if (view == null) {
1511            return result;
1512        }
1513
1514        result[0] = createViewInfo(view, 0, setExtendedInfo, true);
1515        result[1] = createViewInfo(view, offset, setExtendedInfo, true);
1516        if (view instanceof ViewGroup) {
1517            List<ViewInfo> children = visitAllChildren((ViewGroup) view, 0, setExtendedInfo, true);
1518            result[0].setChildren(children);
1519            result[1].setChildren(children);
1520        }
1521        return result;
1522    }
1523
1524    /**
1525     * Creates a {@link ViewInfo} for the view. The {@code ViewInfo} corresponding to the children
1526     * of the {@code view} are not created. Consequently, the children of {@code ViewInfo} is not
1527     * set.
1528     * @param offset an offset for the view bounds. Used only if view is part of the content frame.
1529     */
1530    private ViewInfo createViewInfo(View view, int offset, boolean setExtendedInfo,
1531            boolean isContentFrame) {
1532        if (view == null) {
1533            return null;
1534        }
1535
1536        ViewInfo result;
1537        if (isContentFrame) {
1538            // The view is part of the layout added by the user. Hence,
1539            // the ViewCookie may be obtained only through the Context.
1540            result = new ViewInfo(view.getClass().getName(),
1541                    getContext().getViewKey(view),
1542                    view.getLeft(), view.getTop() + offset, view.getRight(),
1543                    view.getBottom() + offset, view, view.getLayoutParams());
1544        } else {
1545            // We are part of the system decor.
1546            SystemViewInfo r = new SystemViewInfo(view.getClass().getName(),
1547                    getViewKey(view),
1548                    view.getLeft(), view.getTop(), view.getRight(),
1549                    view.getBottom(), view, view.getLayoutParams());
1550            result = r;
1551            // We currently mark three kinds of views:
1552            // 1. Menus in the Action Bar
1553            // 2. Menus in the Overflow popup.
1554            // 3. The overflow popup button.
1555            if (view instanceof ListMenuItemView) {
1556                // Mark 2.
1557                // All menus in the popup are of type ListMenuItemView.
1558                r.setViewType(ViewType.ACTION_BAR_OVERFLOW_MENU);
1559            } else {
1560                // Mark 3.
1561                ViewGroup.LayoutParams lp = view.getLayoutParams();
1562                if (lp instanceof ActionMenuView.LayoutParams &&
1563                        ((ActionMenuView.LayoutParams) lp).isOverflowButton) {
1564                    r.setViewType(ViewType.ACTION_BAR_OVERFLOW);
1565                } else {
1566                    // Mark 1.
1567                    // A view is a menu in the Action Bar is it is not the overflow button and of
1568                    // its parent is of type ActionMenuView. We can also check if the view is
1569                    // instanceof ActionMenuItemView but that will fail for menus using
1570                    // actionProviderClass.
1571                    ViewParent parent = view.getParent();
1572                    while (parent != mViewRoot && parent instanceof ViewGroup) {
1573                        if (parent instanceof ActionMenuView) {
1574                            r.setViewType(ViewType.ACTION_BAR_MENU);
1575                            break;
1576                        }
1577                        parent = parent.getParent();
1578                    }
1579                }
1580            }
1581        }
1582
1583        if (setExtendedInfo) {
1584            MarginLayoutParams marginParams = null;
1585            LayoutParams params = view.getLayoutParams();
1586            if (params instanceof MarginLayoutParams) {
1587                marginParams = (MarginLayoutParams) params;
1588            }
1589            result.setExtendedInfo(view.getBaseline(),
1590                    marginParams != null ? marginParams.leftMargin : 0,
1591                    marginParams != null ? marginParams.topMargin : 0,
1592                    marginParams != null ? marginParams.rightMargin : 0,
1593                    marginParams != null ? marginParams.bottomMargin : 0);
1594        }
1595
1596        return result;
1597    }
1598
1599    /* (non-Javadoc)
1600     * The cookie for menu items are stored in menu item and not in the map from View stored in
1601     * BridgeContext.
1602     */
1603    private Object getViewKey(View view) {
1604        BridgeContext context = getContext();
1605        if (!(view instanceof MenuView.ItemView)) {
1606            return context.getViewKey(view);
1607        }
1608        MenuItemImpl menuItem;
1609        if (view instanceof ActionMenuItemView) {
1610            menuItem = ((ActionMenuItemView) view).getItemData();
1611        } else if (view instanceof ListMenuItemView) {
1612            menuItem = ((ListMenuItemView) view).getItemData();
1613        } else if (view instanceof IconMenuItemView) {
1614            menuItem = ((IconMenuItemView) view).getItemData();
1615        } else {
1616            menuItem = null;
1617        }
1618        if (menuItem instanceof BridgeMenuItemImpl) {
1619            return ((BridgeMenuItemImpl) menuItem).getViewCookie();
1620        }
1621
1622        return null;
1623    }
1624
1625    public void invalidateRenderingSize() {
1626        mMeasuredScreenWidth = mMeasuredScreenHeight = -1;
1627    }
1628
1629    /**
1630     * Creates the status bar with wifi and battery icons.
1631     */
1632    private StatusBar createStatusBar(BridgeContext context, Density density, int direction,
1633            boolean isRtlSupported, int platformVersion) throws XmlPullParserException {
1634        StatusBar statusBar = new StatusBar(context, density,
1635                direction, isRtlSupported, platformVersion);
1636        statusBar.setLayoutParams(
1637                new LinearLayout.LayoutParams(
1638                        LayoutParams.MATCH_PARENT, mStatusBarSize));
1639        return statusBar;
1640    }
1641
1642    /**
1643     * Creates the navigation bar with back, home and recent buttons.
1644     *
1645     * @param isRtl true if the current locale is right-to-left
1646     * @param isRtlSupported true is the project manifest declares that the application
1647     *        is RTL aware.
1648     */
1649    private NavigationBar createNavigationBar(BridgeContext context, Density density,
1650            boolean isRtl, boolean isRtlSupported, int simulatedPlatformVersion)
1651            throws XmlPullParserException {
1652        NavigationBar navigationBar = new NavigationBar(context,
1653                density, mNavigationBarOrientation, isRtl,
1654                isRtlSupported, simulatedPlatformVersion);
1655        if (mNavigationBarOrientation == LinearLayout.VERTICAL) {
1656            navigationBar.setLayoutParams(new LinearLayout.LayoutParams(mNavigationBarSize,
1657                    LayoutParams.MATCH_PARENT));
1658        } else {
1659            navigationBar.setLayoutParams(new LinearLayout.LayoutParams(LayoutParams.MATCH_PARENT,
1660                    mNavigationBarSize));
1661        }
1662        return navigationBar;
1663    }
1664
1665    private TitleBar createTitleBar(BridgeContext context, String title,
1666            int simulatedPlatformVersion)
1667            throws XmlPullParserException {
1668        TitleBar titleBar = new TitleBar(context, title, simulatedPlatformVersion);
1669        titleBar.setLayoutParams(
1670                new LinearLayout.LayoutParams(LayoutParams.MATCH_PARENT, mTitleBarSize));
1671        return titleBar;
1672    }
1673
1674    /**
1675     * Creates the action bar. Also queries the project callback for missing information.
1676     */
1677    private BridgeActionBar createActionBar(BridgeContext context, SessionParams params,
1678            ViewGroup parentView) {
1679        if (mIsThemeAppCompat == Boolean.TRUE) {
1680            return new AppCompatActionBar(context, params, parentView);
1681        } else {
1682            return new FrameworkActionBar(context, params, parentView);
1683        }
1684    }
1685
1686    public BufferedImage getImage() {
1687        return mImage;
1688    }
1689
1690    public boolean isAlphaChannelImage() {
1691        return mIsAlphaChannelImage;
1692    }
1693
1694    public List<ViewInfo> getViewInfos() {
1695        return mViewInfoList;
1696    }
1697
1698    public List<ViewInfo> getSystemViewInfos() {
1699        return mSystemViewInfoList;
1700    }
1701
1702    public Map<String, String> getDefaultProperties(Object viewObject) {
1703        return getContext().getDefaultPropMap(viewObject);
1704    }
1705
1706    public void setScene(RenderSession session) {
1707        mScene = session;
1708    }
1709
1710    public RenderSession getSession() {
1711        return mScene;
1712    }
1713}
1714