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