RenderSessionImpl.java revision 300f4893351a9974fa3380c513b29d4a7883cd74
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.ViewInfo;
33import com.android.ide.common.rendering.api.ViewType;
34import com.android.internal.view.menu.ActionMenuItemView;
35import com.android.internal.view.menu.BridgeMenuItemImpl;
36import com.android.internal.view.menu.IconMenuItemView;
37import com.android.internal.view.menu.ListMenuItemView;
38import com.android.internal.view.menu.MenuItemImpl;
39import com.android.internal.view.menu.MenuView;
40import com.android.layoutlib.bridge.Bridge;
41import com.android.layoutlib.bridge.android.BridgeContext;
42import com.android.layoutlib.bridge.android.BridgeLayoutParamsMapAttributes;
43import com.android.layoutlib.bridge.android.BridgeXmlBlockParser;
44import com.android.layoutlib.bridge.android.RenderParamsFlags;
45import com.android.layoutlib.bridge.android.support.DesignLibUtil;
46import com.android.layoutlib.bridge.impl.binding.FakeAdapter;
47import com.android.layoutlib.bridge.impl.binding.FakeExpandableAdapter;
48import com.android.resources.ResourceType;
49import com.android.util.Pair;
50
51import android.animation.AnimationThread;
52import android.animation.Animator;
53import android.animation.AnimatorInflater;
54import android.animation.LayoutTransition;
55import android.animation.LayoutTransition.TransitionListener;
56import android.annotation.NonNull;
57import android.annotation.Nullable;
58import android.app.Fragment_Delegate;
59import android.graphics.Bitmap;
60import android.graphics.Bitmap_Delegate;
61import android.graphics.Canvas;
62import android.preference.Preference_Delegate;
63import android.view.AttachInfo_Accessor;
64import android.view.BridgeInflater;
65import android.view.IWindowManager;
66import android.view.IWindowManagerImpl;
67import android.view.Surface;
68import android.view.View;
69import android.view.View.MeasureSpec;
70import android.view.ViewGroup;
71import android.view.ViewGroup.LayoutParams;
72import android.view.ViewGroup.MarginLayoutParams;
73import android.view.ViewParent;
74import android.view.WindowManagerGlobal_Delegate;
75import android.widget.AbsListView;
76import android.widget.AbsSpinner;
77import android.widget.ActionMenuView;
78import android.widget.AdapterView;
79import android.widget.ExpandableListView;
80import android.widget.FrameLayout;
81import android.widget.LinearLayout;
82import android.widget.ListView;
83import android.widget.QuickContactBadge;
84import android.widget.TabHost;
85import android.widget.TabHost.TabSpec;
86import android.widget.TabWidget;
87
88import java.awt.AlphaComposite;
89import java.awt.Color;
90import java.awt.Graphics2D;
91import java.awt.image.BufferedImage;
92import java.util.ArrayList;
93import java.util.List;
94import java.util.Map;
95
96import static com.android.ide.common.rendering.api.Result.Status.ERROR_ANIM_NOT_FOUND;
97import static com.android.ide.common.rendering.api.Result.Status.ERROR_INFLATION;
98import static com.android.ide.common.rendering.api.Result.Status.ERROR_NOT_INFLATED;
99import static com.android.ide.common.rendering.api.Result.Status.ERROR_UNKNOWN;
100import static com.android.ide.common.rendering.api.Result.Status.ERROR_VIEWGROUP_NO_CHILDREN;
101import static com.android.ide.common.rendering.api.Result.Status.SUCCESS;
102import static com.android.layoutlib.bridge.util.ReflectionUtils.isInstanceOf;
103
104/**
105 * Class implementing the render session.
106 * <p/>
107 * A session is a stateful representation of a layout file. It is initialized with data coming
108 * through the {@link Bridge} API to inflate the layout. Further actions and rendering can then
109 * be done on the layout.
110 */
111public class RenderSessionImpl extends RenderAction<SessionParams> {
112
113    // scene state
114    private RenderSession mScene;
115    private BridgeXmlBlockParser mBlockParser;
116    private BridgeInflater mInflater;
117    private ViewGroup mViewRoot;
118    private FrameLayout mContentRoot;
119    private Canvas mCanvas;
120    private int mMeasuredScreenWidth = -1;
121    private int mMeasuredScreenHeight = -1;
122    private boolean mIsAlphaChannelImage;
123
124    // information being returned through the API
125    private BufferedImage mImage;
126    private List<ViewInfo> mViewInfoList;
127    private List<ViewInfo> mSystemViewInfoList;
128    private Layout.Builder mLayoutBuilder;
129
130    private static final class PostInflateException extends Exception {
131        private static final long serialVersionUID = 1L;
132
133        public PostInflateException(String message) {
134            super(message);
135        }
136    }
137
138    /**
139     * Creates a layout scene with all the information coming from the layout bridge API.
140     * <p>
141     * This <b>must</b> be followed by a call to {@link RenderSessionImpl#init(long)},
142     * which act as a
143     * call to {@link RenderSessionImpl#acquire(long)}
144     *
145     * @see Bridge#createSession(SessionParams)
146     */
147    public RenderSessionImpl(SessionParams params) {
148        super(new SessionParams(params));
149    }
150
151    /**
152     * Initializes and acquires the scene, creating various Android objects such as context,
153     * inflater, and parser.
154     *
155     * @param timeout the time to wait if another rendering is happening.
156     *
157     * @return whether the scene was prepared
158     *
159     * @see #acquire(long)
160     * @see #release()
161     */
162    @Override
163    public Result init(long timeout) {
164        Result result = super.init(timeout);
165        if (!result.isSuccess()) {
166            return result;
167        }
168
169        SessionParams params = getParams();
170        BridgeContext context = getContext();
171
172        // use default of true in case it's not found to use alpha by default
173        mIsAlphaChannelImage = ResourceHelper.getBooleanThemeValue(params.getResources(),
174                "windowIsFloating", true, true);
175
176        mLayoutBuilder = new Layout.Builder(params, context);
177
178        // FIXME: find those out, and possibly add them to the render params
179        boolean hasNavigationBar = true;
180        //noinspection ConstantConditions
181        IWindowManager iwm = new IWindowManagerImpl(getContext().getConfiguration(),
182                context.getMetrics(), Surface.ROTATION_0, hasNavigationBar);
183        WindowManagerGlobal_Delegate.setWindowManagerService(iwm);
184
185        // build the inflater and parser.
186        mInflater = new BridgeInflater(context, params.getLayoutlibCallback());
187        context.setBridgeInflater(mInflater);
188
189        mBlockParser = new BridgeXmlBlockParser(params.getLayoutDescription(), context, false);
190
191        return SUCCESS.createResult();
192    }
193
194    /**
195     * Inflates the layout.
196     * <p>
197     * {@link #acquire(long)} must have been called before this.
198     *
199     * @throws IllegalStateException if the current context is different than the one owned by
200     *      the scene, or if {@link #init(long)} was not called.
201     */
202    public Result inflate() {
203        checkLock();
204
205        try {
206            mViewRoot = new Layout(mLayoutBuilder);
207            mLayoutBuilder = null;  // Done with the builder.
208            mContentRoot = ((Layout) mViewRoot).getContentRoot();
209            SessionParams params = getParams();
210            BridgeContext context = getContext();
211
212            // Sets the project callback (custom view loader) to the fragment delegate so that
213            // it can instantiate the custom Fragment.
214            Fragment_Delegate.setLayoutlibCallback(params.getLayoutlibCallback());
215
216            String rootTag = params.getFlag(RenderParamsFlags.FLAG_KEY_ROOT_TAG);
217            boolean isPreference = "PreferenceScreen".equals(rootTag);
218            View view;
219            if (isPreference) {
220                view = Preference_Delegate.inflatePreference(getContext(), mBlockParser,
221                        mContentRoot);
222            } else {
223                view = mInflater.inflate(mBlockParser, mContentRoot);
224            }
225
226            // done with the parser, pop it.
227            context.popParser();
228
229            Fragment_Delegate.setLayoutlibCallback(null);
230
231            // set the AttachInfo on the root view.
232            AttachInfo_Accessor.setAttachInfo(mViewRoot);
233
234            // post-inflate process. For now this supports TabHost/TabWidget
235            postInflateProcess(view, params.getLayoutlibCallback(), isPreference ? view : null);
236            mInflater.onDoneInflation();
237
238            setActiveToolbar(view, context, params);
239
240            return SUCCESS.createResult();
241        } catch (PostInflateException e) {
242            return ERROR_INFLATION.createResult(e.getMessage(), e);
243        } catch (Throwable e) {
244            // get the real cause of the exception.
245            Throwable t = e;
246            while (t.getCause() != null) {
247                t = t.getCause();
248            }
249
250            return ERROR_INFLATION.createResult(t.getMessage(), t);
251        }
252    }
253
254    /**
255     * Renders the scene.
256     * <p>
257     * {@link #acquire(long)} must have been called before this.
258     *
259     * @param freshRender whether the render is a new one and should erase the existing bitmap (in
260     *      the case where bitmaps are reused). This is typically needed when not playing
261     *      animations.)
262     *
263     * @throws IllegalStateException if the current context is different than the one owned by
264     *      the scene, or if {@link #acquire(long)} was not called.
265     *
266     * @see SessionParams#getRenderingMode()
267     * @see RenderSession#render(long)
268     */
269    public Result render(boolean freshRender) {
270        checkLock();
271
272        SessionParams params = getParams();
273
274        try {
275            if (mViewRoot == null) {
276                return ERROR_NOT_INFLATED.createResult();
277            }
278
279            RenderingMode renderingMode = params.getRenderingMode();
280            HardwareConfig hardwareConfig = params.getHardwareConfig();
281
282            // only do the screen measure when needed.
283            boolean newRenderSize = false;
284            if (mMeasuredScreenWidth == -1) {
285                newRenderSize = true;
286                mMeasuredScreenWidth = hardwareConfig.getScreenWidth();
287                mMeasuredScreenHeight = hardwareConfig.getScreenHeight();
288
289                if (renderingMode != RenderingMode.NORMAL) {
290                    int widthMeasureSpecMode = renderingMode.isHorizExpand() ?
291                            MeasureSpec.UNSPECIFIED // this lets us know the actual needed size
292                            : MeasureSpec.EXACTLY;
293                    int heightMeasureSpecMode = renderingMode.isVertExpand() ?
294                            MeasureSpec.UNSPECIFIED // this lets us know the actual needed size
295                            : MeasureSpec.EXACTLY;
296
297                    // We used to compare the measured size of the content to the screen size but
298                    // this does not work anymore due to the 2 following issues:
299                    // - If the content is in a decor (system bar, title/action bar), the root view
300                    //   will not resize even with the UNSPECIFIED because of the embedded layout.
301                    // - If there is no decor, but a dialog frame, then the dialog padding prevents
302                    //   comparing the size of the content to the screen frame (as it would not
303                    //   take into account the dialog padding).
304
305                    // The solution is to first get the content size in a normal rendering, inside
306                    // the decor or the dialog padding.
307                    // Then measure only the content with UNSPECIFIED to see the size difference
308                    // and apply this to the screen size.
309
310                    // first measure the full layout, with EXACTLY to get the size of the
311                    // content as it is inside the decor/dialog
312                    @SuppressWarnings("deprecation")
313                    Pair<Integer, Integer> exactMeasure = measureView(
314                            mViewRoot, mContentRoot.getChildAt(0),
315                            mMeasuredScreenWidth, MeasureSpec.EXACTLY,
316                            mMeasuredScreenHeight, MeasureSpec.EXACTLY);
317
318                    // now measure the content only using UNSPECIFIED (where applicable, based on
319                    // the rendering mode). This will give us the size the content needs.
320                    @SuppressWarnings("deprecation")
321                    Pair<Integer, Integer> result = measureView(
322                            mContentRoot, mContentRoot.getChildAt(0),
323                            mMeasuredScreenWidth, widthMeasureSpecMode,
324                            mMeasuredScreenHeight, heightMeasureSpecMode);
325
326                    // now look at the difference and add what is needed.
327                    if (renderingMode.isHorizExpand()) {
328                        int measuredWidth = exactMeasure.getFirst();
329                        int neededWidth = result.getFirst();
330                        if (neededWidth > measuredWidth) {
331                            mMeasuredScreenWidth += neededWidth - measuredWidth;
332                        }
333                    }
334
335                    if (renderingMode.isVertExpand()) {
336                        int measuredHeight = exactMeasure.getSecond();
337                        int neededHeight = result.getSecond();
338                        if (neededHeight > measuredHeight) {
339                            mMeasuredScreenHeight += neededHeight - measuredHeight;
340                        }
341                    }
342                }
343            }
344
345            // measure again with the size we need
346            // This must always be done before the call to layout
347            measureView(mViewRoot, null /*measuredView*/,
348                    mMeasuredScreenWidth, MeasureSpec.EXACTLY,
349                    mMeasuredScreenHeight, MeasureSpec.EXACTLY);
350
351            // now do the layout.
352            mViewRoot.layout(0, 0, mMeasuredScreenWidth, mMeasuredScreenHeight);
353
354            handleScrolling(mViewRoot);
355
356            if (params.isLayoutOnly()) {
357                // delete the canvas and image to reset them on the next full rendering
358                mImage = null;
359                mCanvas = null;
360            } else {
361                AttachInfo_Accessor.dispatchOnPreDraw(mViewRoot);
362
363                // draw the views
364                // create the BufferedImage into which the layout will be rendered.
365                boolean newImage = false;
366
367                // When disableBitmapCaching is true, we do not reuse mImage and
368                // we create a new one in every render.
369                // This is useful when mImage is just a wrapper of Graphics2D so
370                // it doesn't get cached.
371                boolean disableBitmapCaching = Boolean.TRUE.equals(params.getFlag(
372                    RenderParamsFlags.FLAG_KEY_DISABLE_BITMAP_CACHING));
373                if (newRenderSize || mCanvas == null || disableBitmapCaching) {
374                    if (params.getImageFactory() != null) {
375                        mImage = params.getImageFactory().getImage(
376                                mMeasuredScreenWidth,
377                                mMeasuredScreenHeight);
378                    } else {
379                        mImage = new BufferedImage(
380                                mMeasuredScreenWidth,
381                                mMeasuredScreenHeight,
382                                BufferedImage.TYPE_INT_ARGB);
383                        newImage = true;
384                    }
385
386                    if (params.isBgColorOverridden()) {
387                        // since we override the content, it's the same as if it was a new image.
388                        newImage = true;
389                        Graphics2D gc = mImage.createGraphics();
390                        gc.setColor(new Color(params.getOverrideBgColor(), true));
391                        gc.setComposite(AlphaComposite.Src);
392                        gc.fillRect(0, 0, mMeasuredScreenWidth, mMeasuredScreenHeight);
393                        gc.dispose();
394                    }
395
396                    // create an Android bitmap around the BufferedImage
397                    Bitmap bitmap = Bitmap_Delegate.createBitmap(mImage,
398                            true /*isMutable*/, hardwareConfig.getDensity());
399
400                    if (mCanvas == null) {
401                        // create a Canvas around the Android bitmap
402                        mCanvas = new Canvas(bitmap);
403                    } else {
404                        mCanvas.setBitmap(bitmap);
405                    }
406                    mCanvas.setDensity(hardwareConfig.getDensity().getDpiValue());
407                }
408
409                if (freshRender && !newImage) {
410                    Graphics2D gc = mImage.createGraphics();
411                    gc.setComposite(AlphaComposite.Src);
412
413                    gc.setColor(new Color(0x00000000, true));
414                    gc.fillRect(0, 0,
415                            mMeasuredScreenWidth, mMeasuredScreenHeight);
416
417                    // done
418                    gc.dispose();
419                }
420
421                mViewRoot.draw(mCanvas);
422            }
423
424            mSystemViewInfoList = visitAllChildren(mViewRoot, 0, params.getExtendedViewInfoMode(),
425                    false);
426
427            // success!
428            return SUCCESS.createResult();
429        } catch (Throwable e) {
430            // get the real cause of the exception.
431            Throwable t = e;
432            while (t.getCause() != null) {
433                t = t.getCause();
434            }
435
436            return ERROR_UNKNOWN.createResult(t.getMessage(), t);
437        }
438    }
439
440    /**
441     * Executes {@link View#measure(int, int)} on a given view with the given parameters (used
442     * to create measure specs with {@link MeasureSpec#makeMeasureSpec(int, int)}.
443     *
444     * if <var>measuredView</var> is non null, the method returns a {@link Pair} of (width, height)
445     * for the view (using {@link View#getMeasuredWidth()} and {@link View#getMeasuredHeight()}).
446     *
447     * @param viewToMeasure the view on which to execute measure().
448     * @param measuredView if non null, the view to query for its measured width/height.
449     * @param width the width to use in the MeasureSpec.
450     * @param widthMode the MeasureSpec mode to use for the width.
451     * @param height the height to use in the MeasureSpec.
452     * @param heightMode the MeasureSpec mode to use for the height.
453     * @return the measured width/height if measuredView is non-null, null otherwise.
454     */
455    @SuppressWarnings("deprecation")  // For the use of Pair
456    private Pair<Integer, Integer> measureView(ViewGroup viewToMeasure, View measuredView,
457            int width, int widthMode, int height, int heightMode) {
458        int w_spec = MeasureSpec.makeMeasureSpec(width, widthMode);
459        int h_spec = MeasureSpec.makeMeasureSpec(height, heightMode);
460        viewToMeasure.measure(w_spec, h_spec);
461
462        if (measuredView != null) {
463            return Pair.of(measuredView.getMeasuredWidth(), measuredView.getMeasuredHeight());
464        }
465
466        return null;
467    }
468
469    /**
470     * Animate an object
471     * <p>
472     * {@link #acquire(long)} must have been called before this.
473     *
474     * @throws IllegalStateException if the current context is different than the one owned by
475     *      the scene, or if {@link #acquire(long)} was not called.
476     *
477     * @see RenderSession#animate(Object, String, boolean, IAnimationListener)
478     */
479    public Result animate(Object targetObject, String animationName,
480            boolean isFrameworkAnimation, IAnimationListener listener) {
481        checkLock();
482
483        BridgeContext context = getContext();
484
485        // find the animation file.
486        ResourceValue animationResource;
487        int animationId = 0;
488        if (isFrameworkAnimation) {
489            animationResource = context.getRenderResources().getFrameworkResource(
490                    ResourceType.ANIMATOR, animationName);
491            if (animationResource != null) {
492                animationId = Bridge.getResourceId(ResourceType.ANIMATOR, animationName);
493            }
494        } else {
495            animationResource = context.getRenderResources().getProjectResource(
496                    ResourceType.ANIMATOR, animationName);
497            if (animationResource != null) {
498                animationId = context.getLayoutlibCallback().getResourceId(
499                        ResourceType.ANIMATOR, animationName);
500            }
501        }
502
503        if (animationResource != null) {
504            try {
505                Animator anim = AnimatorInflater.loadAnimator(context, animationId);
506                if (anim != null) {
507                    anim.setTarget(targetObject);
508
509                    new PlayAnimationThread(anim, this, animationName, listener).start();
510
511                    return SUCCESS.createResult();
512                }
513            } catch (Exception e) {
514                // get the real cause of the exception.
515                Throwable t = e;
516                while (t.getCause() != null) {
517                    t = t.getCause();
518                }
519
520                return ERROR_UNKNOWN.createResult(t.getMessage(), t);
521            }
522        }
523
524        return ERROR_ANIM_NOT_FOUND.createResult();
525    }
526
527    /**
528     * Insert a new child into an existing parent.
529     * <p>
530     * {@link #acquire(long)} must have been called before this.
531     *
532     * @throws IllegalStateException if the current context is different than the one owned by
533     *      the scene, or if {@link #acquire(long)} was not called.
534     *
535     * @see RenderSession#insertChild(Object, ILayoutPullParser, int, IAnimationListener)
536     */
537    public Result insertChild(final ViewGroup parentView, ILayoutPullParser childXml,
538            final int index, IAnimationListener listener) {
539        checkLock();
540
541        BridgeContext context = getContext();
542
543        // create a block parser for the XML
544        BridgeXmlBlockParser blockParser = new BridgeXmlBlockParser(
545                childXml, context, false /* platformResourceFlag */);
546
547        // inflate the child without adding it to the root since we want to control where it'll
548        // get added. We do pass the parentView however to ensure that the layoutParams will
549        // be created correctly.
550        final View child = mInflater.inflate(blockParser, parentView, false /*attachToRoot*/);
551        blockParser.ensurePopped();
552
553        invalidateRenderingSize();
554
555        if (listener != null) {
556            new AnimationThread(this, "insertChild", listener) {
557
558                @Override
559                public Result preAnimation() {
560                    parentView.setLayoutTransition(new LayoutTransition());
561                    return addView(parentView, child, index);
562                }
563
564                @Override
565                public void postAnimation() {
566                    parentView.setLayoutTransition(null);
567                }
568            }.start();
569
570            // always return success since the real status will come through the listener.
571            return SUCCESS.createResult(child);
572        }
573
574        // add it to the parentView in the correct location
575        Result result = addView(parentView, child, index);
576        if (!result.isSuccess()) {
577            return result;
578        }
579
580        result = render(false /*freshRender*/);
581        if (result.isSuccess()) {
582            result = result.getCopyWithData(child);
583        }
584
585        return result;
586    }
587
588    /**
589     * Adds a given view to a given parent at a given index.
590     *
591     * @param parent the parent to receive the view
592     * @param view the view to add to the parent
593     * @param index the index where to do the add.
594     *
595     * @return a Result with {@link Status#SUCCESS} or
596     *     {@link Status#ERROR_VIEWGROUP_NO_CHILDREN} if the given parent doesn't support
597     *     adding views.
598     */
599    private Result addView(ViewGroup parent, View view, int index) {
600        try {
601            parent.addView(view, index);
602            return SUCCESS.createResult();
603        } catch (UnsupportedOperationException e) {
604            // looks like this is a view class that doesn't support children manipulation!
605            return ERROR_VIEWGROUP_NO_CHILDREN.createResult();
606        }
607    }
608
609    /**
610     * Moves a view to a new parent at a given location
611     * <p>
612     * {@link #acquire(long)} must have been called before this.
613     *
614     * @throws IllegalStateException if the current context is different than the one owned by
615     *      the scene, or if {@link #acquire(long)} was not called.
616     *
617     * @see RenderSession#moveChild(Object, Object, int, Map, IAnimationListener)
618     */
619    public Result moveChild(final ViewGroup newParentView, final View childView, final int index,
620            Map<String, String> layoutParamsMap, final IAnimationListener listener) {
621        checkLock();
622
623        invalidateRenderingSize();
624
625        LayoutParams layoutParams = null;
626        if (layoutParamsMap != null) {
627            // need to create a new LayoutParams object for the new parent.
628            layoutParams = newParentView.generateLayoutParams(
629                    new BridgeLayoutParamsMapAttributes(layoutParamsMap));
630        }
631
632        // get the current parent of the view that needs to be moved.
633        final ViewGroup previousParent = (ViewGroup) childView.getParent();
634
635        if (listener != null) {
636            final LayoutParams params = layoutParams;
637
638            // there is no support for animating views across layouts, so in case the new and old
639            // parent views are different we fake the animation through a no animation thread.
640            if (previousParent != newParentView) {
641                new Thread("not animated moveChild") {
642                    @Override
643                    public void run() {
644                        Result result = moveView(previousParent, newParentView, childView, index,
645                                params);
646                        if (!result.isSuccess()) {
647                            listener.done(result);
648                        }
649
650                        // ready to do the work, acquire the scene.
651                        result = acquire(250);
652                        if (!result.isSuccess()) {
653                            listener.done(result);
654                            return;
655                        }
656
657                        try {
658                            result = render(false /*freshRender*/);
659                            if (result.isSuccess()) {
660                                listener.onNewFrame(RenderSessionImpl.this.getSession());
661                            }
662                        } finally {
663                            release();
664                        }
665
666                        listener.done(result);
667                    }
668                }.start();
669            } else {
670                new AnimationThread(this, "moveChild", listener) {
671
672                    @Override
673                    public Result preAnimation() {
674                        // set up the transition for the parent.
675                        LayoutTransition transition = new LayoutTransition();
676                        previousParent.setLayoutTransition(transition);
677
678                        // tweak the animation durations and start delays (to match the duration of
679                        // animation playing just before).
680                        // Note: Cannot user Animation.setDuration() directly. Have to set it
681                        // on the LayoutTransition.
682                        transition.setDuration(LayoutTransition.DISAPPEARING, 100);
683                        // CHANGE_DISAPPEARING plays after DISAPPEARING
684                        transition.setStartDelay(LayoutTransition.CHANGE_DISAPPEARING, 100);
685
686                        transition.setDuration(LayoutTransition.CHANGE_DISAPPEARING, 100);
687
688                        transition.setDuration(LayoutTransition.CHANGE_APPEARING, 100);
689                        // CHANGE_APPEARING plays after CHANGE_APPEARING
690                        transition.setStartDelay(LayoutTransition.APPEARING, 100);
691
692                        transition.setDuration(LayoutTransition.APPEARING, 100);
693
694                        return moveView(previousParent, newParentView, childView, index, params);
695                    }
696
697                    @Override
698                    public void postAnimation() {
699                        previousParent.setLayoutTransition(null);
700                        newParentView.setLayoutTransition(null);
701                    }
702                }.start();
703            }
704
705            // always return success since the real status will come through the listener.
706            return SUCCESS.createResult(layoutParams);
707        }
708
709        Result result = moveView(previousParent, newParentView, childView, index, layoutParams);
710        if (!result.isSuccess()) {
711            return result;
712        }
713
714        result = render(false /*freshRender*/);
715        if (layoutParams != null && result.isSuccess()) {
716            result = result.getCopyWithData(layoutParams);
717        }
718
719        return result;
720    }
721
722    /**
723     * Moves a View from its current parent to a new given parent at a new given location, with
724     * an optional new {@link LayoutParams} instance
725     *
726     * @param previousParent the previous parent, still owning the child at the time of the call.
727     * @param newParent the new parent
728     * @param movedView the view to move
729     * @param index the new location in the new parent
730     * @param params an option (can be null) {@link LayoutParams} instance.
731     *
732     * @return a Result with {@link Status#SUCCESS} or
733     *     {@link Status#ERROR_VIEWGROUP_NO_CHILDREN} if the given parent doesn't support
734     *     adding views.
735     */
736    private Result moveView(ViewGroup previousParent, final ViewGroup newParent,
737            final View movedView, final int index, final LayoutParams params) {
738        try {
739            // check if there is a transition on the previousParent.
740            LayoutTransition previousTransition = previousParent.getLayoutTransition();
741            if (previousTransition != null) {
742                // in this case there is an animation. This means we have to wait for the child's
743                // parent reference to be null'ed out so that we can add it to the new parent.
744                // It is technically removed right before the DISAPPEARING animation is done (if
745                // the animation of this type is not null, otherwise it's after which is impossible
746                // to handle).
747                // Because there is no move animation, if the new parent is the same as the old
748                // parent, we need to wait until the CHANGE_DISAPPEARING animation is done before
749                // adding the child or the child will appear in its new location before the
750                // other children have made room for it.
751
752                // add a listener to the transition to be notified of the actual removal.
753                previousTransition.addTransitionListener(new TransitionListener() {
754                    private int mChangeDisappearingCount = 0;
755
756                    @Override
757                    public void startTransition(LayoutTransition transition, ViewGroup container,
758                            View view, int transitionType) {
759                        if (transitionType == LayoutTransition.CHANGE_DISAPPEARING) {
760                            mChangeDisappearingCount++;
761                        }
762                    }
763
764                    @Override
765                    public void endTransition(LayoutTransition transition, ViewGroup container,
766                            View view, int transitionType) {
767                        if (transitionType == LayoutTransition.CHANGE_DISAPPEARING) {
768                            mChangeDisappearingCount--;
769                        }
770
771                        if (transitionType == LayoutTransition.CHANGE_DISAPPEARING &&
772                                mChangeDisappearingCount == 0) {
773                            // add it to the parentView in the correct location
774                            if (params != null) {
775                                newParent.addView(movedView, index, params);
776                            } else {
777                                newParent.addView(movedView, index);
778                            }
779                        }
780                    }
781                });
782
783                // remove the view from the current parent.
784                previousParent.removeView(movedView);
785
786                // and return since adding the view to the new parent is done in the listener.
787                return SUCCESS.createResult();
788            } else {
789                // standard code with no animation. pretty simple.
790                previousParent.removeView(movedView);
791
792                // add it to the parentView in the correct location
793                if (params != null) {
794                    newParent.addView(movedView, index, params);
795                } else {
796                    newParent.addView(movedView, index);
797                }
798
799                return SUCCESS.createResult();
800            }
801        } catch (UnsupportedOperationException e) {
802            // looks like this is a view class that doesn't support children manipulation!
803            return ERROR_VIEWGROUP_NO_CHILDREN.createResult();
804        }
805    }
806
807    /**
808     * Removes a child from its current parent.
809     * <p>
810     * {@link #acquire(long)} must have been called before this.
811     *
812     * @throws IllegalStateException if the current context is different than the one owned by
813     *      the scene, or if {@link #acquire(long)} was not called.
814     *
815     * @see RenderSession#removeChild(Object, IAnimationListener)
816     */
817    public Result removeChild(final View childView, IAnimationListener listener) {
818        checkLock();
819
820        invalidateRenderingSize();
821
822        final ViewGroup parent = (ViewGroup) childView.getParent();
823
824        if (listener != null) {
825            new AnimationThread(this, "moveChild", listener) {
826
827                @Override
828                public Result preAnimation() {
829                    parent.setLayoutTransition(new LayoutTransition());
830                    return removeView(parent, childView);
831                }
832
833                @Override
834                public void postAnimation() {
835                    parent.setLayoutTransition(null);
836                }
837            }.start();
838
839            // always return success since the real status will come through the listener.
840            return SUCCESS.createResult();
841        }
842
843        Result result = removeView(parent, childView);
844        if (!result.isSuccess()) {
845            return result;
846        }
847
848        return render(false /*freshRender*/);
849    }
850
851    /**
852     * Removes a given view from its current parent.
853     *
854     * @param view the view to remove from its parent
855     *
856     * @return a Result with {@link Status#SUCCESS} or
857     *     {@link Status#ERROR_VIEWGROUP_NO_CHILDREN} if the given parent doesn't support
858     *     adding views.
859     */
860    private Result removeView(ViewGroup parent, View view) {
861        try {
862            parent.removeView(view);
863            return SUCCESS.createResult();
864        } catch (UnsupportedOperationException e) {
865            // looks like this is a view class that doesn't support children manipulation!
866            return ERROR_VIEWGROUP_NO_CHILDREN.createResult();
867        }
868    }
869
870    /**
871     * Post process on a view hierarchy that was just inflated.
872     * <p/>
873     * At the moment this only supports TabHost: If {@link TabHost} is detected, look for the
874     * {@link TabWidget}, and the corresponding {@link FrameLayout} and make new tabs automatically
875     * based on the content of the {@link FrameLayout}.
876     * @param view the root view to process.
877     * @param layoutlibCallback callback to the project.
878     * @param skip the view and it's children are not processed.
879     */
880    @SuppressWarnings("deprecation")  // For the use of Pair
881    private void postInflateProcess(View view, LayoutlibCallback layoutlibCallback, View skip)
882            throws PostInflateException {
883        if (view == skip) {
884            return;
885        }
886        if (view instanceof TabHost) {
887            setupTabHost((TabHost) view, layoutlibCallback);
888        } else if (view instanceof QuickContactBadge) {
889            QuickContactBadge badge = (QuickContactBadge) view;
890            badge.setImageToDefault();
891        } else if (view instanceof AdapterView<?>) {
892            // get the view ID.
893            int id = view.getId();
894
895            BridgeContext context = getContext();
896
897            // get a ResourceReference from the integer ID.
898            ResourceReference listRef = context.resolveId(id);
899
900            if (listRef != null) {
901                SessionParams params = getParams();
902                AdapterBinding binding = params.getAdapterBindings().get(listRef);
903
904                // if there was no adapter binding, trying to get it from the call back.
905                if (binding == null) {
906                    binding = layoutlibCallback.getAdapterBinding(
907                            listRef, context.getViewKey(view), view);
908                }
909
910                if (binding != null) {
911
912                    if (view instanceof AbsListView) {
913                        if ((binding.getFooterCount() > 0 || binding.getHeaderCount() > 0) &&
914                                view instanceof ListView) {
915                            ListView list = (ListView) view;
916
917                            boolean skipCallbackParser = false;
918
919                            int count = binding.getHeaderCount();
920                            for (int i = 0; i < count; i++) {
921                                Pair<View, Boolean> pair = context.inflateView(
922                                        binding.getHeaderAt(i),
923                                        list, false, skipCallbackParser);
924                                if (pair.getFirst() != null) {
925                                    list.addHeaderView(pair.getFirst());
926                                }
927
928                                skipCallbackParser |= pair.getSecond();
929                            }
930
931                            count = binding.getFooterCount();
932                            for (int i = 0; i < count; i++) {
933                                Pair<View, Boolean> pair = context.inflateView(
934                                        binding.getFooterAt(i),
935                                        list, false, skipCallbackParser);
936                                if (pair.getFirst() != null) {
937                                    list.addFooterView(pair.getFirst());
938                                }
939
940                                skipCallbackParser |= pair.getSecond();
941                            }
942                        }
943
944                        if (view instanceof ExpandableListView) {
945                            ((ExpandableListView) view).setAdapter(
946                                    new FakeExpandableAdapter(listRef, binding, layoutlibCallback));
947                        } else {
948                            ((AbsListView) view).setAdapter(
949                                    new FakeAdapter(listRef, binding, layoutlibCallback));
950                        }
951                    } else if (view instanceof AbsSpinner) {
952                        ((AbsSpinner) view).setAdapter(
953                                new FakeAdapter(listRef, binding, layoutlibCallback));
954                    }
955                }
956            }
957        } else if (view instanceof ViewGroup) {
958            mInflater.postInflateProcess(view);
959            ViewGroup group = (ViewGroup) view;
960            final int count = group.getChildCount();
961            for (int c = 0; c < count; c++) {
962                View child = group.getChildAt(c);
963                postInflateProcess(child, layoutlibCallback, skip);
964            }
965        }
966    }
967
968    /**
969     * If the root layout is a CoordinatorLayout with an AppBar:
970     * Set the title of the AppBar to the title of the activity context.
971     */
972    private void setActiveToolbar(View view, BridgeContext context, SessionParams params) {
973        View coordinatorLayout = findChildView(view, DesignLibUtil.CN_COORDINATOR_LAYOUT);
974        if (coordinatorLayout == null) {
975            return;
976        }
977        View appBar = findChildView(coordinatorLayout, DesignLibUtil.CN_APPBAR_LAYOUT);
978        if (appBar == null) {
979            return;
980        }
981        ViewGroup collapsingToolbar =
982                (ViewGroup) findChildView(appBar, DesignLibUtil.CN_COLLAPSING_TOOLBAR_LAYOUT);
983        if (collapsingToolbar == null) {
984            return;
985        }
986        if (!hasToolbar(collapsingToolbar)) {
987            return;
988        }
989        RenderResources res = context.getRenderResources();
990        String title = params.getAppLabel();
991        ResourceValue titleValue = res.findResValue(title, false);
992        if (titleValue != null && titleValue.getValue() != null) {
993            title = titleValue.getValue();
994        }
995        DesignLibUtil.setTitle(collapsingToolbar, title);
996    }
997
998    private View findChildView(View view, String className) {
999        if (!(view instanceof ViewGroup)) {
1000            return null;
1001        }
1002        ViewGroup group = (ViewGroup) view;
1003        for (int i = 0; i < group.getChildCount(); i++) {
1004            if (isInstanceOf(group.getChildAt(i), className)) {
1005                return group.getChildAt(i);
1006            }
1007        }
1008        return null;
1009    }
1010
1011    private boolean hasToolbar(View collapsingToolbar) {
1012        if (!(collapsingToolbar instanceof ViewGroup)) {
1013            return false;
1014        }
1015        ViewGroup group = (ViewGroup) collapsingToolbar;
1016        for (int i = 0; i < group.getChildCount(); i++) {
1017            if (isInstanceOf(group.getChildAt(i), DesignLibUtil.CN_TOOLBAR)) {
1018                return true;
1019            }
1020        }
1021        return false;
1022    }
1023
1024    /**
1025     * Set the vertical scroll position on all the components with the "scrollY" attribute. If the
1026     * component supports nested scrolling attempt that first, then use the unconsumed scroll part
1027     * to scroll the content in the component.
1028     */
1029    private void handleScrolling(View view) {
1030        BridgeContext context = getContext();
1031        int scrollPos = context.getScrollYPos(view);
1032        if (scrollPos != 0) {
1033            if (view.isNestedScrollingEnabled()) {
1034                int[] consumed = new int[2];
1035                if (view.startNestedScroll(DesignLibUtil.SCROLL_AXIS_VERTICAL)) {
1036                    view.dispatchNestedPreScroll(0, scrollPos, consumed, null);
1037                    view.dispatchNestedScroll(consumed[0], consumed[1], 0, scrollPos, null);
1038                    view.stopNestedScroll();
1039                    scrollPos -= consumed[1];
1040                }
1041            }
1042            if (scrollPos != 0) {
1043                view.scrollBy(0, scrollPos);
1044            } else {
1045                view.scrollBy(0, scrollPos);
1046            }
1047        } else {
1048            view.scrollBy(0, scrollPos);
1049        }
1050
1051        if (!(view instanceof ViewGroup)) {
1052            return;
1053        }
1054        ViewGroup group = (ViewGroup) view;
1055        for (int i = 0; i < group.getChildCount(); i++) {
1056            View child = group.getChildAt(i);
1057            handleScrolling(child);
1058        }
1059    }
1060
1061    /**
1062     * Sets up a {@link TabHost} object.
1063     * @param tabHost the TabHost to setup.
1064     * @param layoutlibCallback The project callback object to access the project R class.
1065     * @throws PostInflateException
1066     */
1067    private void setupTabHost(TabHost tabHost, LayoutlibCallback layoutlibCallback)
1068            throws PostInflateException {
1069        // look for the TabWidget, and the FrameLayout. They have their own specific names
1070        View v = tabHost.findViewById(android.R.id.tabs);
1071
1072        if (v == null) {
1073            throw new PostInflateException(
1074                    "TabHost requires a TabWidget with id \"android:id/tabs\".\n");
1075        }
1076
1077        if (!(v instanceof TabWidget)) {
1078            throw new PostInflateException(String.format(
1079                    "TabHost requires a TabWidget with id \"android:id/tabs\".\n" +
1080                    "View found with id 'tabs' is '%s'", v.getClass().getCanonicalName()));
1081        }
1082
1083        v = tabHost.findViewById(android.R.id.tabcontent);
1084
1085        if (v == null) {
1086            // TODO: see if we can fake tabs even without the FrameLayout (same below when the frameLayout is empty)
1087            //noinspection SpellCheckingInspection
1088            throw new PostInflateException(
1089                    "TabHost requires a FrameLayout with id \"android:id/tabcontent\".");
1090        }
1091
1092        if (!(v instanceof FrameLayout)) {
1093            //noinspection SpellCheckingInspection
1094            throw new PostInflateException(String.format(
1095                    "TabHost requires a FrameLayout with id \"android:id/tabcontent\".\n" +
1096                    "View found with id 'tabcontent' is '%s'", v.getClass().getCanonicalName()));
1097        }
1098
1099        FrameLayout content = (FrameLayout)v;
1100
1101        // now process the content of the frameLayout and dynamically create tabs for it.
1102        final int count = content.getChildCount();
1103
1104        // this must be called before addTab() so that the TabHost searches its TabWidget
1105        // and FrameLayout.
1106        tabHost.setup();
1107
1108        if (count == 0) {
1109            // Create a dummy child to get a single tab
1110            TabSpec spec = tabHost.newTabSpec("tag")
1111                    .setIndicator("Tab Label", tabHost.getResources()
1112                            .getDrawable(android.R.drawable.ic_menu_info_details, null))
1113                    .setContent(new TabHost.TabContentFactory() {
1114                        @Override
1115                        public View createTabContent(String tag) {
1116                            return new LinearLayout(getContext());
1117                        }
1118                    });
1119            tabHost.addTab(spec);
1120        } else {
1121            // for each child of the frameLayout, add a new TabSpec
1122            for (int i = 0 ; i < count ; i++) {
1123                View child = content.getChildAt(i);
1124                String tabSpec = String.format("tab_spec%d", i+1);
1125                @SuppressWarnings("ConstantConditions")  // child cannot be null.
1126                int id = child.getId();
1127                @SuppressWarnings("deprecation")
1128                Pair<ResourceType, String> resource = layoutlibCallback.resolveResourceId(id);
1129                String name;
1130                if (resource != null) {
1131                    name = resource.getSecond();
1132                } else {
1133                    name = String.format("Tab %d", i+1); // default name if id is unresolved.
1134                }
1135                tabHost.addTab(tabHost.newTabSpec(tabSpec).setIndicator(name).setContent(id));
1136            }
1137        }
1138    }
1139
1140    /**
1141     * Visits a {@link View} and its children and generate a {@link ViewInfo} containing the
1142     * bounds of all the views.
1143     *
1144     * @param view the root View
1145     * @param offset an offset for the view bounds.
1146     * @param setExtendedInfo whether to set the extended view info in the {@link ViewInfo} object.
1147     * @param isContentFrame {@code true} if the {@code ViewInfo} to be created is part of the
1148     *                       content frame.
1149     *
1150     * @return {@code ViewInfo} containing the bounds of the view and it children otherwise.
1151     */
1152    private ViewInfo visit(View view, int offset, boolean setExtendedInfo,
1153            boolean isContentFrame) {
1154        ViewInfo result = createViewInfo(view, offset, setExtendedInfo, isContentFrame);
1155
1156        if (view instanceof ViewGroup) {
1157            ViewGroup group = ((ViewGroup) view);
1158            result.setChildren(visitAllChildren(group, isContentFrame ? 0 : offset,
1159                    setExtendedInfo, isContentFrame));
1160        }
1161        return result;
1162    }
1163
1164    /**
1165     * Visits all the children of a given ViewGroup and generates a list of {@link ViewInfo}
1166     * containing the bounds of all the views. It also initializes the {@link #mViewInfoList} with
1167     * the children of the {@code mContentRoot}.
1168     *
1169     * @param viewGroup the root View
1170     * @param offset an offset from the top for the content view frame.
1171     * @param setExtendedInfo whether to set the extended view info in the {@link ViewInfo} object.
1172     * @param isContentFrame {@code true} if the {@code ViewInfo} to be created is part of the
1173     *                       content frame. {@code false} if the {@code ViewInfo} to be created is
1174     *                       part of the system decor.
1175     */
1176    private List<ViewInfo> visitAllChildren(ViewGroup viewGroup, int offset,
1177            boolean setExtendedInfo, boolean isContentFrame) {
1178        if (viewGroup == null) {
1179            return null;
1180        }
1181
1182        if (!isContentFrame) {
1183            offset += viewGroup.getTop();
1184        }
1185
1186        int childCount = viewGroup.getChildCount();
1187        if (viewGroup == mContentRoot) {
1188            List<ViewInfo> childrenWithoutOffset = new ArrayList<ViewInfo>(childCount);
1189            List<ViewInfo> childrenWithOffset = new ArrayList<ViewInfo>(childCount);
1190            for (int i = 0; i < childCount; i++) {
1191                ViewInfo[] childViewInfo = visitContentRoot(viewGroup.getChildAt(i), offset,
1192                        setExtendedInfo);
1193                childrenWithoutOffset.add(childViewInfo[0]);
1194                childrenWithOffset.add(childViewInfo[1]);
1195            }
1196            mViewInfoList = childrenWithOffset;
1197            return childrenWithoutOffset;
1198        } else {
1199            List<ViewInfo> children = new ArrayList<ViewInfo>(childCount);
1200            for (int i = 0; i < childCount; i++) {
1201                children.add(visit(viewGroup.getChildAt(i), offset, setExtendedInfo,
1202                        isContentFrame));
1203            }
1204            return children;
1205        }
1206    }
1207
1208    /**
1209     * Visits the children of {@link #mContentRoot} and generates {@link ViewInfo} containing the
1210     * bounds of all the views. It returns two {@code ViewInfo} objects with the same children,
1211     * one with the {@code offset} and other without the {@code offset}. The offset is needed to
1212     * get the right bounds if the {@code ViewInfo} hierarchy is accessed from
1213     * {@code mViewInfoList}. When the hierarchy is accessed via {@code mSystemViewInfoList}, the
1214     * offset is not needed.
1215     *
1216     * @return an array of length two, with ViewInfo at index 0 is without offset and ViewInfo at
1217     *         index 1 is with the offset.
1218     */
1219    @NonNull
1220    private ViewInfo[] visitContentRoot(View view, int offset, boolean setExtendedInfo) {
1221        ViewInfo[] result = new ViewInfo[2];
1222        if (view == null) {
1223            return result;
1224        }
1225
1226        result[0] = createViewInfo(view, 0, setExtendedInfo, true);
1227        result[1] = createViewInfo(view, offset, setExtendedInfo, true);
1228        if (view instanceof ViewGroup) {
1229            List<ViewInfo> children = visitAllChildren((ViewGroup) view, 0, setExtendedInfo, true);
1230            result[0].setChildren(children);
1231            result[1].setChildren(children);
1232        }
1233        return result;
1234    }
1235
1236    /**
1237     * Creates a {@link ViewInfo} for the view. The {@code ViewInfo} corresponding to the children
1238     * of the {@code view} are not created. Consequently, the children of {@code ViewInfo} is not
1239     * set.
1240     * @param offset an offset for the view bounds. Used only if view is part of the content frame.
1241     */
1242    private ViewInfo createViewInfo(View view, int offset, boolean setExtendedInfo,
1243            boolean isContentFrame) {
1244        if (view == null) {
1245            return null;
1246        }
1247
1248        ViewInfo result;
1249        if (isContentFrame) {
1250            // The view is part of the layout added by the user. Hence,
1251            // the ViewCookie may be obtained only through the Context.
1252            result = new ViewInfo(view.getClass().getName(),
1253                    getContext().getViewKey(view),
1254                    view.getLeft(), view.getTop() + offset, view.getRight(),
1255                    view.getBottom() + offset, view, view.getLayoutParams());
1256        } else {
1257            // We are part of the system decor.
1258            SystemViewInfo r = new SystemViewInfo(view.getClass().getName(),
1259                    getViewKey(view),
1260                    view.getLeft(), view.getTop(), view.getRight(),
1261                    view.getBottom(), view, view.getLayoutParams());
1262            result = r;
1263            // We currently mark three kinds of views:
1264            // 1. Menus in the Action Bar
1265            // 2. Menus in the Overflow popup.
1266            // 3. The overflow popup button.
1267            if (view instanceof ListMenuItemView) {
1268                // Mark 2.
1269                // All menus in the popup are of type ListMenuItemView.
1270                r.setViewType(ViewType.ACTION_BAR_OVERFLOW_MENU);
1271            } else {
1272                // Mark 3.
1273                ViewGroup.LayoutParams lp = view.getLayoutParams();
1274                if (lp instanceof ActionMenuView.LayoutParams &&
1275                        ((ActionMenuView.LayoutParams) lp).isOverflowButton) {
1276                    r.setViewType(ViewType.ACTION_BAR_OVERFLOW);
1277                } else {
1278                    // Mark 1.
1279                    // A view is a menu in the Action Bar is it is not the overflow button and of
1280                    // its parent is of type ActionMenuView. We can also check if the view is
1281                    // instanceof ActionMenuItemView but that will fail for menus using
1282                    // actionProviderClass.
1283                    ViewParent parent = view.getParent();
1284                    while (parent != mViewRoot && parent instanceof ViewGroup) {
1285                        if (parent instanceof ActionMenuView) {
1286                            r.setViewType(ViewType.ACTION_BAR_MENU);
1287                            break;
1288                        }
1289                        parent = parent.getParent();
1290                    }
1291                }
1292            }
1293        }
1294
1295        if (setExtendedInfo) {
1296            MarginLayoutParams marginParams = null;
1297            LayoutParams params = view.getLayoutParams();
1298            if (params instanceof MarginLayoutParams) {
1299                marginParams = (MarginLayoutParams) params;
1300            }
1301            result.setExtendedInfo(view.getBaseline(),
1302                    marginParams != null ? marginParams.leftMargin : 0,
1303                    marginParams != null ? marginParams.topMargin : 0,
1304                    marginParams != null ? marginParams.rightMargin : 0,
1305                    marginParams != null ? marginParams.bottomMargin : 0);
1306        }
1307
1308        return result;
1309    }
1310
1311    /* (non-Javadoc)
1312     * The cookie for menu items are stored in menu item and not in the map from View stored in
1313     * BridgeContext.
1314     */
1315    @Nullable
1316    private Object getViewKey(View view) {
1317        BridgeContext context = getContext();
1318        if (!(view instanceof MenuView.ItemView)) {
1319            return context.getViewKey(view);
1320        }
1321        MenuItemImpl menuItem;
1322        if (view instanceof ActionMenuItemView) {
1323            menuItem = ((ActionMenuItemView) view).getItemData();
1324        } else if (view instanceof ListMenuItemView) {
1325            menuItem = ((ListMenuItemView) view).getItemData();
1326        } else if (view instanceof IconMenuItemView) {
1327            menuItem = ((IconMenuItemView) view).getItemData();
1328        } else {
1329            menuItem = null;
1330        }
1331        if (menuItem instanceof BridgeMenuItemImpl) {
1332            return ((BridgeMenuItemImpl) menuItem).getViewCookie();
1333        }
1334
1335        return null;
1336    }
1337
1338    public void invalidateRenderingSize() {
1339        mMeasuredScreenWidth = mMeasuredScreenHeight = -1;
1340    }
1341
1342    public BufferedImage getImage() {
1343        return mImage;
1344    }
1345
1346    public boolean isAlphaChannelImage() {
1347        return mIsAlphaChannelImage;
1348    }
1349
1350    public List<ViewInfo> getViewInfos() {
1351        return mViewInfoList;
1352    }
1353
1354    public List<ViewInfo> getSystemViewInfos() {
1355        return mSystemViewInfoList;
1356    }
1357
1358    public Map<String, String> getDefaultProperties(Object viewObject) {
1359        return getContext().getDefaultPropMap(viewObject);
1360    }
1361
1362    public void setScene(RenderSession session) {
1363        mScene = session;
1364    }
1365
1366    public RenderSession getSession() {
1367        return mScene;
1368    }
1369}
1370