RenderSessionImpl.java revision 4eb298a941c3f465944b63f1a06518e911681c89
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                        if (mMeasuredScreenWidth < measuredWidth) {
334                            // If the screen width is less than the exact measured width,
335                            // expand to match.
336                            mMeasuredScreenWidth = measuredWidth;
337                        }
338                    }
339
340                    if (renderingMode.isVertExpand()) {
341                        int measuredHeight = exactMeasure.getSecond();
342                        int neededHeight = result.getSecond();
343                        if (neededHeight > measuredHeight) {
344                            mMeasuredScreenHeight += neededHeight - measuredHeight;
345                        }
346                        if (mMeasuredScreenHeight < measuredHeight) {
347                            // If the screen height is less than the exact measured height,
348                            // expand to match.
349                            mMeasuredScreenHeight = measuredHeight;
350                        }
351                    }
352                }
353            }
354
355            // measure again with the size we need
356            // This must always be done before the call to layout
357            measureView(mViewRoot, null /*measuredView*/,
358                    mMeasuredScreenWidth, MeasureSpec.EXACTLY,
359                    mMeasuredScreenHeight, MeasureSpec.EXACTLY);
360
361            // now do the layout.
362            mViewRoot.layout(0, 0, mMeasuredScreenWidth, mMeasuredScreenHeight);
363
364            handleScrolling(mViewRoot);
365
366            if (params.isLayoutOnly()) {
367                // delete the canvas and image to reset them on the next full rendering
368                mImage = null;
369                mCanvas = null;
370            } else {
371                AttachInfo_Accessor.dispatchOnPreDraw(mViewRoot);
372
373                // draw the views
374                // create the BufferedImage into which the layout will be rendered.
375                boolean newImage = false;
376
377                // When disableBitmapCaching is true, we do not reuse mImage and
378                // we create a new one in every render.
379                // This is useful when mImage is just a wrapper of Graphics2D so
380                // it doesn't get cached.
381                boolean disableBitmapCaching = Boolean.TRUE.equals(params.getFlag(
382                    RenderParamsFlags.FLAG_KEY_DISABLE_BITMAP_CACHING));
383                if (newRenderSize || mCanvas == null || disableBitmapCaching) {
384                    if (params.getImageFactory() != null) {
385                        mImage = params.getImageFactory().getImage(
386                                mMeasuredScreenWidth,
387                                mMeasuredScreenHeight);
388                    } else {
389                        mImage = new BufferedImage(
390                                mMeasuredScreenWidth,
391                                mMeasuredScreenHeight,
392                                BufferedImage.TYPE_INT_ARGB);
393                        newImage = true;
394                    }
395
396                    if (params.isBgColorOverridden()) {
397                        // since we override the content, it's the same as if it was a new image.
398                        newImage = true;
399                        Graphics2D gc = mImage.createGraphics();
400                        gc.setColor(new Color(params.getOverrideBgColor(), true));
401                        gc.setComposite(AlphaComposite.Src);
402                        gc.fillRect(0, 0, mMeasuredScreenWidth, mMeasuredScreenHeight);
403                        gc.dispose();
404                    }
405
406                    // create an Android bitmap around the BufferedImage
407                    Bitmap bitmap = Bitmap_Delegate.createBitmap(mImage,
408                            true /*isMutable*/, hardwareConfig.getDensity());
409
410                    if (mCanvas == null) {
411                        // create a Canvas around the Android bitmap
412                        mCanvas = new Canvas(bitmap);
413                    } else {
414                        mCanvas.setBitmap(bitmap);
415                    }
416                    mCanvas.setDensity(hardwareConfig.getDensity().getDpiValue());
417                }
418
419                if (freshRender && !newImage) {
420                    Graphics2D gc = mImage.createGraphics();
421                    gc.setComposite(AlphaComposite.Src);
422
423                    gc.setColor(new Color(0x00000000, true));
424                    gc.fillRect(0, 0, mMeasuredScreenWidth, mMeasuredScreenHeight);
425
426                    // done
427                    gc.dispose();
428                }
429
430                mViewRoot.draw(mCanvas);
431            }
432
433            mSystemViewInfoList = visitAllChildren(mViewRoot, 0, params.getExtendedViewInfoMode(),
434                    false);
435
436            // success!
437            return SUCCESS.createResult();
438        } catch (Throwable e) {
439            // get the real cause of the exception.
440            Throwable t = e;
441            while (t.getCause() != null) {
442                t = t.getCause();
443            }
444
445            return ERROR_UNKNOWN.createResult(t.getMessage(), t);
446        }
447    }
448
449    /**
450     * Executes {@link View#measure(int, int)} on a given view with the given parameters (used
451     * to create measure specs with {@link MeasureSpec#makeMeasureSpec(int, int)}.
452     *
453     * if <var>measuredView</var> is non null, the method returns a {@link Pair} of (width, height)
454     * for the view (using {@link View#getMeasuredWidth()} and {@link View#getMeasuredHeight()}).
455     *
456     * @param viewToMeasure the view on which to execute measure().
457     * @param measuredView if non null, the view to query for its measured width/height.
458     * @param width the width to use in the MeasureSpec.
459     * @param widthMode the MeasureSpec mode to use for the width.
460     * @param height the height to use in the MeasureSpec.
461     * @param heightMode the MeasureSpec mode to use for the height.
462     * @return the measured width/height if measuredView is non-null, null otherwise.
463     */
464    @SuppressWarnings("deprecation")  // For the use of Pair
465    private Pair<Integer, Integer> measureView(ViewGroup viewToMeasure, View measuredView,
466            int width, int widthMode, int height, int heightMode) {
467        int w_spec = MeasureSpec.makeMeasureSpec(width, widthMode);
468        int h_spec = MeasureSpec.makeMeasureSpec(height, heightMode);
469        viewToMeasure.measure(w_spec, h_spec);
470
471        if (measuredView != null) {
472            return Pair.of(measuredView.getMeasuredWidth(), measuredView.getMeasuredHeight());
473        }
474
475        return null;
476    }
477
478    /**
479     * Animate an object
480     * <p>
481     * {@link #acquire(long)} must have been called before this.
482     *
483     * @throws IllegalStateException if the current context is different than the one owned by
484     *      the scene, or if {@link #acquire(long)} was not called.
485     *
486     * @see RenderSession#animate(Object, String, boolean, IAnimationListener)
487     */
488    public Result animate(Object targetObject, String animationName,
489            boolean isFrameworkAnimation, IAnimationListener listener) {
490        checkLock();
491
492        BridgeContext context = getContext();
493
494        // find the animation file.
495        ResourceValue animationResource;
496        int animationId = 0;
497        if (isFrameworkAnimation) {
498            animationResource = context.getRenderResources().getFrameworkResource(
499                    ResourceType.ANIMATOR, animationName);
500            if (animationResource != null) {
501                animationId = Bridge.getResourceId(ResourceType.ANIMATOR, animationName);
502            }
503        } else {
504            animationResource = context.getRenderResources().getProjectResource(
505                    ResourceType.ANIMATOR, animationName);
506            if (animationResource != null) {
507                animationId = context.getLayoutlibCallback().getResourceId(
508                        ResourceType.ANIMATOR, animationName);
509            }
510        }
511
512        if (animationResource != null) {
513            try {
514                Animator anim = AnimatorInflater.loadAnimator(context, animationId);
515                if (anim != null) {
516                    anim.setTarget(targetObject);
517
518                    new PlayAnimationThread(anim, this, animationName, listener).start();
519
520                    return SUCCESS.createResult();
521                }
522            } catch (Exception e) {
523                // get the real cause of the exception.
524                Throwable t = e;
525                while (t.getCause() != null) {
526                    t = t.getCause();
527                }
528
529                return ERROR_UNKNOWN.createResult(t.getMessage(), t);
530            }
531        }
532
533        return ERROR_ANIM_NOT_FOUND.createResult();
534    }
535
536    /**
537     * Insert a new child into an existing parent.
538     * <p>
539     * {@link #acquire(long)} must have been called before this.
540     *
541     * @throws IllegalStateException if the current context is different than the one owned by
542     *      the scene, or if {@link #acquire(long)} was not called.
543     *
544     * @see RenderSession#insertChild(Object, ILayoutPullParser, int, IAnimationListener)
545     */
546    public Result insertChild(final ViewGroup parentView, ILayoutPullParser childXml,
547            final int index, IAnimationListener listener) {
548        checkLock();
549
550        BridgeContext context = getContext();
551
552        // create a block parser for the XML
553        BridgeXmlBlockParser blockParser = new BridgeXmlBlockParser(
554                childXml, context, false /* platformResourceFlag */);
555
556        // inflate the child without adding it to the root since we want to control where it'll
557        // get added. We do pass the parentView however to ensure that the layoutParams will
558        // be created correctly.
559        final View child = mInflater.inflate(blockParser, parentView, false /*attachToRoot*/);
560        blockParser.ensurePopped();
561
562        invalidateRenderingSize();
563
564        if (listener != null) {
565            new AnimationThread(this, "insertChild", listener) {
566
567                @Override
568                public Result preAnimation() {
569                    parentView.setLayoutTransition(new LayoutTransition());
570                    return addView(parentView, child, index);
571                }
572
573                @Override
574                public void postAnimation() {
575                    parentView.setLayoutTransition(null);
576                }
577            }.start();
578
579            // always return success since the real status will come through the listener.
580            return SUCCESS.createResult(child);
581        }
582
583        // add it to the parentView in the correct location
584        Result result = addView(parentView, child, index);
585        if (!result.isSuccess()) {
586            return result;
587        }
588
589        result = render(false /*freshRender*/);
590        if (result.isSuccess()) {
591            result = result.getCopyWithData(child);
592        }
593
594        return result;
595    }
596
597    /**
598     * Adds a given view to a given parent at a given index.
599     *
600     * @param parent the parent to receive the view
601     * @param view the view to add to the parent
602     * @param index the index where to do the add.
603     *
604     * @return a Result with {@link Status#SUCCESS} or
605     *     {@link Status#ERROR_VIEWGROUP_NO_CHILDREN} if the given parent doesn't support
606     *     adding views.
607     */
608    private Result addView(ViewGroup parent, View view, int index) {
609        try {
610            parent.addView(view, index);
611            return SUCCESS.createResult();
612        } catch (UnsupportedOperationException e) {
613            // looks like this is a view class that doesn't support children manipulation!
614            return ERROR_VIEWGROUP_NO_CHILDREN.createResult();
615        }
616    }
617
618    /**
619     * Moves a view to a new parent at a given location
620     * <p>
621     * {@link #acquire(long)} must have been called before this.
622     *
623     * @throws IllegalStateException if the current context is different than the one owned by
624     *      the scene, or if {@link #acquire(long)} was not called.
625     *
626     * @see RenderSession#moveChild(Object, Object, int, Map, IAnimationListener)
627     */
628    public Result moveChild(final ViewGroup newParentView, final View childView, final int index,
629            Map<String, String> layoutParamsMap, final IAnimationListener listener) {
630        checkLock();
631
632        invalidateRenderingSize();
633
634        LayoutParams layoutParams = null;
635        if (layoutParamsMap != null) {
636            // need to create a new LayoutParams object for the new parent.
637            layoutParams = newParentView.generateLayoutParams(
638                    new BridgeLayoutParamsMapAttributes(layoutParamsMap));
639        }
640
641        // get the current parent of the view that needs to be moved.
642        final ViewGroup previousParent = (ViewGroup) childView.getParent();
643
644        if (listener != null) {
645            final LayoutParams params = layoutParams;
646
647            // there is no support for animating views across layouts, so in case the new and old
648            // parent views are different we fake the animation through a no animation thread.
649            if (previousParent != newParentView) {
650                new Thread("not animated moveChild") {
651                    @Override
652                    public void run() {
653                        Result result = moveView(previousParent, newParentView, childView, index,
654                                params);
655                        if (!result.isSuccess()) {
656                            listener.done(result);
657                        }
658
659                        // ready to do the work, acquire the scene.
660                        result = acquire(250);
661                        if (!result.isSuccess()) {
662                            listener.done(result);
663                            return;
664                        }
665
666                        try {
667                            result = render(false /*freshRender*/);
668                            if (result.isSuccess()) {
669                                listener.onNewFrame(RenderSessionImpl.this.getSession());
670                            }
671                        } finally {
672                            release();
673                        }
674
675                        listener.done(result);
676                    }
677                }.start();
678            } else {
679                new AnimationThread(this, "moveChild", listener) {
680
681                    @Override
682                    public Result preAnimation() {
683                        // set up the transition for the parent.
684                        LayoutTransition transition = new LayoutTransition();
685                        previousParent.setLayoutTransition(transition);
686
687                        // tweak the animation durations and start delays (to match the duration of
688                        // animation playing just before).
689                        // Note: Cannot user Animation.setDuration() directly. Have to set it
690                        // on the LayoutTransition.
691                        transition.setDuration(LayoutTransition.DISAPPEARING, 100);
692                        // CHANGE_DISAPPEARING plays after DISAPPEARING
693                        transition.setStartDelay(LayoutTransition.CHANGE_DISAPPEARING, 100);
694
695                        transition.setDuration(LayoutTransition.CHANGE_DISAPPEARING, 100);
696
697                        transition.setDuration(LayoutTransition.CHANGE_APPEARING, 100);
698                        // CHANGE_APPEARING plays after CHANGE_APPEARING
699                        transition.setStartDelay(LayoutTransition.APPEARING, 100);
700
701                        transition.setDuration(LayoutTransition.APPEARING, 100);
702
703                        return moveView(previousParent, newParentView, childView, index, params);
704                    }
705
706                    @Override
707                    public void postAnimation() {
708                        previousParent.setLayoutTransition(null);
709                        newParentView.setLayoutTransition(null);
710                    }
711                }.start();
712            }
713
714            // always return success since the real status will come through the listener.
715            return SUCCESS.createResult(layoutParams);
716        }
717
718        Result result = moveView(previousParent, newParentView, childView, index, layoutParams);
719        if (!result.isSuccess()) {
720            return result;
721        }
722
723        result = render(false /*freshRender*/);
724        if (layoutParams != null && result.isSuccess()) {
725            result = result.getCopyWithData(layoutParams);
726        }
727
728        return result;
729    }
730
731    /**
732     * Moves a View from its current parent to a new given parent at a new given location, with
733     * an optional new {@link LayoutParams} instance
734     *
735     * @param previousParent the previous parent, still owning the child at the time of the call.
736     * @param newParent the new parent
737     * @param movedView the view to move
738     * @param index the new location in the new parent
739     * @param params an option (can be null) {@link LayoutParams} instance.
740     *
741     * @return a Result with {@link Status#SUCCESS} or
742     *     {@link Status#ERROR_VIEWGROUP_NO_CHILDREN} if the given parent doesn't support
743     *     adding views.
744     */
745    private Result moveView(ViewGroup previousParent, final ViewGroup newParent,
746            final View movedView, final int index, final LayoutParams params) {
747        try {
748            // check if there is a transition on the previousParent.
749            LayoutTransition previousTransition = previousParent.getLayoutTransition();
750            if (previousTransition != null) {
751                // in this case there is an animation. This means we have to wait for the child's
752                // parent reference to be null'ed out so that we can add it to the new parent.
753                // It is technically removed right before the DISAPPEARING animation is done (if
754                // the animation of this type is not null, otherwise it's after which is impossible
755                // to handle).
756                // Because there is no move animation, if the new parent is the same as the old
757                // parent, we need to wait until the CHANGE_DISAPPEARING animation is done before
758                // adding the child or the child will appear in its new location before the
759                // other children have made room for it.
760
761                // add a listener to the transition to be notified of the actual removal.
762                previousTransition.addTransitionListener(new TransitionListener() {
763                    private int mChangeDisappearingCount = 0;
764
765                    @Override
766                    public void startTransition(LayoutTransition transition, ViewGroup container,
767                            View view, int transitionType) {
768                        if (transitionType == LayoutTransition.CHANGE_DISAPPEARING) {
769                            mChangeDisappearingCount++;
770                        }
771                    }
772
773                    @Override
774                    public void endTransition(LayoutTransition transition, ViewGroup container,
775                            View view, int transitionType) {
776                        if (transitionType == LayoutTransition.CHANGE_DISAPPEARING) {
777                            mChangeDisappearingCount--;
778                        }
779
780                        if (transitionType == LayoutTransition.CHANGE_DISAPPEARING &&
781                                mChangeDisappearingCount == 0) {
782                            // add it to the parentView in the correct location
783                            if (params != null) {
784                                newParent.addView(movedView, index, params);
785                            } else {
786                                newParent.addView(movedView, index);
787                            }
788                        }
789                    }
790                });
791
792                // remove the view from the current parent.
793                previousParent.removeView(movedView);
794
795                // and return since adding the view to the new parent is done in the listener.
796                return SUCCESS.createResult();
797            } else {
798                // standard code with no animation. pretty simple.
799                previousParent.removeView(movedView);
800
801                // add it to the parentView in the correct location
802                if (params != null) {
803                    newParent.addView(movedView, index, params);
804                } else {
805                    newParent.addView(movedView, index);
806                }
807
808                return SUCCESS.createResult();
809            }
810        } catch (UnsupportedOperationException e) {
811            // looks like this is a view class that doesn't support children manipulation!
812            return ERROR_VIEWGROUP_NO_CHILDREN.createResult();
813        }
814    }
815
816    /**
817     * Removes a child from its current parent.
818     * <p>
819     * {@link #acquire(long)} must have been called before this.
820     *
821     * @throws IllegalStateException if the current context is different than the one owned by
822     *      the scene, or if {@link #acquire(long)} was not called.
823     *
824     * @see RenderSession#removeChild(Object, IAnimationListener)
825     */
826    public Result removeChild(final View childView, IAnimationListener listener) {
827        checkLock();
828
829        invalidateRenderingSize();
830
831        final ViewGroup parent = (ViewGroup) childView.getParent();
832
833        if (listener != null) {
834            new AnimationThread(this, "moveChild", listener) {
835
836                @Override
837                public Result preAnimation() {
838                    parent.setLayoutTransition(new LayoutTransition());
839                    return removeView(parent, childView);
840                }
841
842                @Override
843                public void postAnimation() {
844                    parent.setLayoutTransition(null);
845                }
846            }.start();
847
848            // always return success since the real status will come through the listener.
849            return SUCCESS.createResult();
850        }
851
852        Result result = removeView(parent, childView);
853        if (!result.isSuccess()) {
854            return result;
855        }
856
857        return render(false /*freshRender*/);
858    }
859
860    /**
861     * Removes a given view from its current parent.
862     *
863     * @param view the view to remove from its parent
864     *
865     * @return a Result with {@link Status#SUCCESS} or
866     *     {@link Status#ERROR_VIEWGROUP_NO_CHILDREN} if the given parent doesn't support
867     *     adding views.
868     */
869    private Result removeView(ViewGroup parent, View view) {
870        try {
871            parent.removeView(view);
872            return SUCCESS.createResult();
873        } catch (UnsupportedOperationException e) {
874            // looks like this is a view class that doesn't support children manipulation!
875            return ERROR_VIEWGROUP_NO_CHILDREN.createResult();
876        }
877    }
878
879    /**
880     * Post process on a view hierarchy that was just inflated.
881     * <p/>
882     * At the moment this only supports TabHost: If {@link TabHost} is detected, look for the
883     * {@link TabWidget}, and the corresponding {@link FrameLayout} and make new tabs automatically
884     * based on the content of the {@link FrameLayout}.
885     * @param view the root view to process.
886     * @param layoutlibCallback callback to the project.
887     * @param skip the view and it's children are not processed.
888     */
889    @SuppressWarnings("deprecation")  // For the use of Pair
890    private void postInflateProcess(View view, LayoutlibCallback layoutlibCallback, View skip)
891            throws PostInflateException {
892        if (view == skip) {
893            return;
894        }
895        if (view instanceof TabHost) {
896            setupTabHost((TabHost) view, layoutlibCallback);
897        } else if (view instanceof QuickContactBadge) {
898            QuickContactBadge badge = (QuickContactBadge) view;
899            badge.setImageToDefault();
900        } else if (view instanceof AdapterView<?>) {
901            // get the view ID.
902            int id = view.getId();
903
904            BridgeContext context = getContext();
905
906            // get a ResourceReference from the integer ID.
907            ResourceReference listRef = context.resolveId(id);
908
909            if (listRef != null) {
910                SessionParams params = getParams();
911                AdapterBinding binding = params.getAdapterBindings().get(listRef);
912
913                // if there was no adapter binding, trying to get it from the call back.
914                if (binding == null) {
915                    binding = layoutlibCallback.getAdapterBinding(
916                            listRef, context.getViewKey(view), view);
917                }
918
919                if (binding != null) {
920
921                    if (view instanceof AbsListView) {
922                        if ((binding.getFooterCount() > 0 || binding.getHeaderCount() > 0) &&
923                                view instanceof ListView) {
924                            ListView list = (ListView) view;
925
926                            boolean skipCallbackParser = false;
927
928                            int count = binding.getHeaderCount();
929                            for (int i = 0; i < count; i++) {
930                                Pair<View, Boolean> pair = context.inflateView(
931                                        binding.getHeaderAt(i),
932                                        list, false, skipCallbackParser);
933                                if (pair.getFirst() != null) {
934                                    list.addHeaderView(pair.getFirst());
935                                }
936
937                                skipCallbackParser |= pair.getSecond();
938                            }
939
940                            count = binding.getFooterCount();
941                            for (int i = 0; i < count; i++) {
942                                Pair<View, Boolean> pair = context.inflateView(
943                                        binding.getFooterAt(i),
944                                        list, false, skipCallbackParser);
945                                if (pair.getFirst() != null) {
946                                    list.addFooterView(pair.getFirst());
947                                }
948
949                                skipCallbackParser |= pair.getSecond();
950                            }
951                        }
952
953                        if (view instanceof ExpandableListView) {
954                            ((ExpandableListView) view).setAdapter(
955                                    new FakeExpandableAdapter(listRef, binding, layoutlibCallback));
956                        } else {
957                            ((AbsListView) view).setAdapter(
958                                    new FakeAdapter(listRef, binding, layoutlibCallback));
959                        }
960                    } else if (view instanceof AbsSpinner) {
961                        ((AbsSpinner) view).setAdapter(
962                                new FakeAdapter(listRef, binding, layoutlibCallback));
963                    }
964                }
965            }
966        } else if (view instanceof ViewGroup) {
967            mInflater.postInflateProcess(view);
968            ViewGroup group = (ViewGroup) view;
969            final int count = group.getChildCount();
970            for (int c = 0; c < count; c++) {
971                View child = group.getChildAt(c);
972                postInflateProcess(child, layoutlibCallback, skip);
973            }
974        }
975    }
976
977    /**
978     * If the root layout is a CoordinatorLayout with an AppBar:
979     * Set the title of the AppBar to the title of the activity context.
980     */
981    private void setActiveToolbar(View view, BridgeContext context, SessionParams params) {
982        View coordinatorLayout = findChildView(view, DesignLibUtil.CN_COORDINATOR_LAYOUT);
983        if (coordinatorLayout == null) {
984            return;
985        }
986        View appBar = findChildView(coordinatorLayout, DesignLibUtil.CN_APPBAR_LAYOUT);
987        if (appBar == null) {
988            return;
989        }
990        ViewGroup collapsingToolbar =
991                (ViewGroup) findChildView(appBar, DesignLibUtil.CN_COLLAPSING_TOOLBAR_LAYOUT);
992        if (collapsingToolbar == null) {
993            return;
994        }
995        if (!hasToolbar(collapsingToolbar)) {
996            return;
997        }
998        RenderResources res = context.getRenderResources();
999        String title = params.getAppLabel();
1000        ResourceValue titleValue = res.findResValue(title, false);
1001        if (titleValue != null && titleValue.getValue() != null) {
1002            title = titleValue.getValue();
1003        }
1004        DesignLibUtil.setTitle(collapsingToolbar, title);
1005    }
1006
1007    private View findChildView(View view, String className) {
1008        if (!(view instanceof ViewGroup)) {
1009            return null;
1010        }
1011        ViewGroup group = (ViewGroup) view;
1012        for (int i = 0; i < group.getChildCount(); i++) {
1013            if (isInstanceOf(group.getChildAt(i), className)) {
1014                return group.getChildAt(i);
1015            }
1016        }
1017        return null;
1018    }
1019
1020    private boolean hasToolbar(View collapsingToolbar) {
1021        if (!(collapsingToolbar instanceof ViewGroup)) {
1022            return false;
1023        }
1024        ViewGroup group = (ViewGroup) collapsingToolbar;
1025        for (int i = 0; i < group.getChildCount(); i++) {
1026            if (isInstanceOf(group.getChildAt(i), DesignLibUtil.CN_TOOLBAR)) {
1027                return true;
1028            }
1029        }
1030        return false;
1031    }
1032
1033    /**
1034     * Set the vertical scroll position on all the components with the "scrollY" attribute. If the
1035     * component supports nested scrolling attempt that first, then use the unconsumed scroll part
1036     * to scroll the content in the component.
1037     */
1038    private void handleScrolling(View view) {
1039        BridgeContext context = getContext();
1040        int scrollPos = context.getScrollYPos(view);
1041        if (scrollPos != 0) {
1042            if (view.isNestedScrollingEnabled()) {
1043                int[] consumed = new int[2];
1044                if (view.startNestedScroll(DesignLibUtil.SCROLL_AXIS_VERTICAL)) {
1045                    view.dispatchNestedPreScroll(0, scrollPos, consumed, null);
1046                    view.dispatchNestedScroll(consumed[0], consumed[1], 0, scrollPos, null);
1047                    view.stopNestedScroll();
1048                    scrollPos -= consumed[1];
1049                }
1050            }
1051            if (scrollPos != 0) {
1052                view.scrollBy(0, scrollPos);
1053            } else {
1054                view.scrollBy(0, scrollPos);
1055            }
1056        } else {
1057            view.scrollBy(0, scrollPos);
1058        }
1059
1060        if (!(view instanceof ViewGroup)) {
1061            return;
1062        }
1063        ViewGroup group = (ViewGroup) view;
1064        for (int i = 0; i < group.getChildCount(); i++) {
1065            View child = group.getChildAt(i);
1066            handleScrolling(child);
1067        }
1068    }
1069
1070    /**
1071     * Sets up a {@link TabHost} object.
1072     * @param tabHost the TabHost to setup.
1073     * @param layoutlibCallback The project callback object to access the project R class.
1074     * @throws PostInflateException
1075     */
1076    private void setupTabHost(TabHost tabHost, LayoutlibCallback layoutlibCallback)
1077            throws PostInflateException {
1078        // look for the TabWidget, and the FrameLayout. They have their own specific names
1079        View v = tabHost.findViewById(android.R.id.tabs);
1080
1081        if (v == null) {
1082            throw new PostInflateException(
1083                    "TabHost requires a TabWidget with id \"android:id/tabs\".\n");
1084        }
1085
1086        if (!(v instanceof TabWidget)) {
1087            throw new PostInflateException(String.format(
1088                    "TabHost requires a TabWidget with id \"android:id/tabs\".\n" +
1089                    "View found with id 'tabs' is '%s'", v.getClass().getCanonicalName()));
1090        }
1091
1092        v = tabHost.findViewById(android.R.id.tabcontent);
1093
1094        if (v == null) {
1095            // TODO: see if we can fake tabs even without the FrameLayout (same below when the frameLayout is empty)
1096            //noinspection SpellCheckingInspection
1097            throw new PostInflateException(
1098                    "TabHost requires a FrameLayout with id \"android:id/tabcontent\".");
1099        }
1100
1101        if (!(v instanceof FrameLayout)) {
1102            //noinspection SpellCheckingInspection
1103            throw new PostInflateException(String.format(
1104                    "TabHost requires a FrameLayout with id \"android:id/tabcontent\".\n" +
1105                    "View found with id 'tabcontent' is '%s'", v.getClass().getCanonicalName()));
1106        }
1107
1108        FrameLayout content = (FrameLayout)v;
1109
1110        // now process the content of the frameLayout and dynamically create tabs for it.
1111        final int count = content.getChildCount();
1112
1113        // this must be called before addTab() so that the TabHost searches its TabWidget
1114        // and FrameLayout.
1115        tabHost.setup();
1116
1117        if (count == 0) {
1118            // Create a dummy child to get a single tab
1119            TabSpec spec = tabHost.newTabSpec("tag")
1120                    .setIndicator("Tab Label", tabHost.getResources()
1121                            .getDrawable(android.R.drawable.ic_menu_info_details, null))
1122                    .setContent(new TabHost.TabContentFactory() {
1123                        @Override
1124                        public View createTabContent(String tag) {
1125                            return new LinearLayout(getContext());
1126                        }
1127                    });
1128            tabHost.addTab(spec);
1129        } else {
1130            // for each child of the frameLayout, add a new TabSpec
1131            for (int i = 0 ; i < count ; i++) {
1132                View child = content.getChildAt(i);
1133                String tabSpec = String.format("tab_spec%d", i+1);
1134                @SuppressWarnings("ConstantConditions")  // child cannot be null.
1135                int id = child.getId();
1136                @SuppressWarnings("deprecation")
1137                Pair<ResourceType, String> resource = layoutlibCallback.resolveResourceId(id);
1138                String name;
1139                if (resource != null) {
1140                    name = resource.getSecond();
1141                } else {
1142                    name = String.format("Tab %d", i+1); // default name if id is unresolved.
1143                }
1144                tabHost.addTab(tabHost.newTabSpec(tabSpec).setIndicator(name).setContent(id));
1145            }
1146        }
1147    }
1148
1149    /**
1150     * Visits a {@link View} and its children and generate a {@link ViewInfo} containing the
1151     * bounds of all the views.
1152     *
1153     * @param view the root View
1154     * @param offset an offset for the view bounds.
1155     * @param setExtendedInfo whether to set the extended view info in the {@link ViewInfo} object.
1156     * @param isContentFrame {@code true} if the {@code ViewInfo} to be created is part of the
1157     *                       content frame.
1158     *
1159     * @return {@code ViewInfo} containing the bounds of the view and it children otherwise.
1160     */
1161    private ViewInfo visit(View view, int offset, boolean setExtendedInfo,
1162            boolean isContentFrame) {
1163        ViewInfo result = createViewInfo(view, offset, setExtendedInfo, isContentFrame);
1164
1165        if (view instanceof ViewGroup) {
1166            ViewGroup group = ((ViewGroup) view);
1167            result.setChildren(visitAllChildren(group, isContentFrame ? 0 : offset,
1168                    setExtendedInfo, isContentFrame));
1169        }
1170        return result;
1171    }
1172
1173    /**
1174     * Visits all the children of a given ViewGroup and generates a list of {@link ViewInfo}
1175     * containing the bounds of all the views. It also initializes the {@link #mViewInfoList} with
1176     * the children of the {@code mContentRoot}.
1177     *
1178     * @param viewGroup the root View
1179     * @param offset an offset from the top for the content view frame.
1180     * @param setExtendedInfo whether to set the extended view info in the {@link ViewInfo} object.
1181     * @param isContentFrame {@code true} if the {@code ViewInfo} to be created is part of the
1182     *                       content frame. {@code false} if the {@code ViewInfo} to be created is
1183     *                       part of the system decor.
1184     */
1185    private List<ViewInfo> visitAllChildren(ViewGroup viewGroup, int offset,
1186            boolean setExtendedInfo, boolean isContentFrame) {
1187        if (viewGroup == null) {
1188            return null;
1189        }
1190
1191        if (!isContentFrame) {
1192            offset += viewGroup.getTop();
1193        }
1194
1195        int childCount = viewGroup.getChildCount();
1196        if (viewGroup == mContentRoot) {
1197            List<ViewInfo> childrenWithoutOffset = new ArrayList<ViewInfo>(childCount);
1198            List<ViewInfo> childrenWithOffset = new ArrayList<ViewInfo>(childCount);
1199            for (int i = 0; i < childCount; i++) {
1200                ViewInfo[] childViewInfo = visitContentRoot(viewGroup.getChildAt(i), offset,
1201                        setExtendedInfo);
1202                childrenWithoutOffset.add(childViewInfo[0]);
1203                childrenWithOffset.add(childViewInfo[1]);
1204            }
1205            mViewInfoList = childrenWithOffset;
1206            return childrenWithoutOffset;
1207        } else {
1208            List<ViewInfo> children = new ArrayList<ViewInfo>(childCount);
1209            for (int i = 0; i < childCount; i++) {
1210                children.add(visit(viewGroup.getChildAt(i), offset, setExtendedInfo,
1211                        isContentFrame));
1212            }
1213            return children;
1214        }
1215    }
1216
1217    /**
1218     * Visits the children of {@link #mContentRoot} and generates {@link ViewInfo} containing the
1219     * bounds of all the views. It returns two {@code ViewInfo} objects with the same children,
1220     * one with the {@code offset} and other without the {@code offset}. The offset is needed to
1221     * get the right bounds if the {@code ViewInfo} hierarchy is accessed from
1222     * {@code mViewInfoList}. When the hierarchy is accessed via {@code mSystemViewInfoList}, the
1223     * offset is not needed.
1224     *
1225     * @return an array of length two, with ViewInfo at index 0 is without offset and ViewInfo at
1226     *         index 1 is with the offset.
1227     */
1228    @NonNull
1229    private ViewInfo[] visitContentRoot(View view, int offset, boolean setExtendedInfo) {
1230        ViewInfo[] result = new ViewInfo[2];
1231        if (view == null) {
1232            return result;
1233        }
1234
1235        result[0] = createViewInfo(view, 0, setExtendedInfo, true);
1236        result[1] = createViewInfo(view, offset, setExtendedInfo, true);
1237        if (view instanceof ViewGroup) {
1238            List<ViewInfo> children = visitAllChildren((ViewGroup) view, 0, setExtendedInfo, true);
1239            result[0].setChildren(children);
1240            result[1].setChildren(children);
1241        }
1242        return result;
1243    }
1244
1245    /**
1246     * Creates a {@link ViewInfo} for the view. The {@code ViewInfo} corresponding to the children
1247     * of the {@code view} are not created. Consequently, the children of {@code ViewInfo} is not
1248     * set.
1249     * @param offset an offset for the view bounds. Used only if view is part of the content frame.
1250     */
1251    private ViewInfo createViewInfo(View view, int offset, boolean setExtendedInfo,
1252            boolean isContentFrame) {
1253        if (view == null) {
1254            return null;
1255        }
1256
1257        ViewInfo result;
1258        if (isContentFrame) {
1259            // The view is part of the layout added by the user. Hence,
1260            // the ViewCookie may be obtained only through the Context.
1261            result = new ViewInfo(view.getClass().getName(),
1262                    getContext().getViewKey(view),
1263                    view.getLeft(), view.getTop() + offset, view.getRight(),
1264                    view.getBottom() + offset, view, view.getLayoutParams());
1265        } else {
1266            // We are part of the system decor.
1267            SystemViewInfo r = new SystemViewInfo(view.getClass().getName(),
1268                    getViewKey(view),
1269                    view.getLeft(), view.getTop(), view.getRight(),
1270                    view.getBottom(), view, view.getLayoutParams());
1271            result = r;
1272            // We currently mark three kinds of views:
1273            // 1. Menus in the Action Bar
1274            // 2. Menus in the Overflow popup.
1275            // 3. The overflow popup button.
1276            if (view instanceof ListMenuItemView) {
1277                // Mark 2.
1278                // All menus in the popup are of type ListMenuItemView.
1279                r.setViewType(ViewType.ACTION_BAR_OVERFLOW_MENU);
1280            } else {
1281                // Mark 3.
1282                ViewGroup.LayoutParams lp = view.getLayoutParams();
1283                if (lp instanceof ActionMenuView.LayoutParams &&
1284                        ((ActionMenuView.LayoutParams) lp).isOverflowButton) {
1285                    r.setViewType(ViewType.ACTION_BAR_OVERFLOW);
1286                } else {
1287                    // Mark 1.
1288                    // A view is a menu in the Action Bar is it is not the overflow button and of
1289                    // its parent is of type ActionMenuView. We can also check if the view is
1290                    // instanceof ActionMenuItemView but that will fail for menus using
1291                    // actionProviderClass.
1292                    ViewParent parent = view.getParent();
1293                    while (parent != mViewRoot && parent instanceof ViewGroup) {
1294                        if (parent instanceof ActionMenuView) {
1295                            r.setViewType(ViewType.ACTION_BAR_MENU);
1296                            break;
1297                        }
1298                        parent = parent.getParent();
1299                    }
1300                }
1301            }
1302        }
1303
1304        if (setExtendedInfo) {
1305            MarginLayoutParams marginParams = null;
1306            LayoutParams params = view.getLayoutParams();
1307            if (params instanceof MarginLayoutParams) {
1308                marginParams = (MarginLayoutParams) params;
1309            }
1310            result.setExtendedInfo(view.getBaseline(),
1311                    marginParams != null ? marginParams.leftMargin : 0,
1312                    marginParams != null ? marginParams.topMargin : 0,
1313                    marginParams != null ? marginParams.rightMargin : 0,
1314                    marginParams != null ? marginParams.bottomMargin : 0);
1315        }
1316
1317        return result;
1318    }
1319
1320    /* (non-Javadoc)
1321     * The cookie for menu items are stored in menu item and not in the map from View stored in
1322     * BridgeContext.
1323     */
1324    @Nullable
1325    private Object getViewKey(View view) {
1326        BridgeContext context = getContext();
1327        if (!(view instanceof MenuView.ItemView)) {
1328            return context.getViewKey(view);
1329        }
1330        MenuItemImpl menuItem;
1331        if (view instanceof ActionMenuItemView) {
1332            menuItem = ((ActionMenuItemView) view).getItemData();
1333        } else if (view instanceof ListMenuItemView) {
1334            menuItem = ((ListMenuItemView) view).getItemData();
1335        } else if (view instanceof IconMenuItemView) {
1336            menuItem = ((IconMenuItemView) view).getItemData();
1337        } else {
1338            menuItem = null;
1339        }
1340        if (menuItem instanceof BridgeMenuItemImpl) {
1341            return ((BridgeMenuItemImpl) menuItem).getViewCookie();
1342        }
1343
1344        return null;
1345    }
1346
1347    public void invalidateRenderingSize() {
1348        mMeasuredScreenWidth = mMeasuredScreenHeight = -1;
1349    }
1350
1351    public BufferedImage getImage() {
1352        return mImage;
1353    }
1354
1355    public boolean isAlphaChannelImage() {
1356        return mIsAlphaChannelImage;
1357    }
1358
1359    public List<ViewInfo> getViewInfos() {
1360        return mViewInfoList;
1361    }
1362
1363    public List<ViewInfo> getSystemViewInfos() {
1364        return mSystemViewInfoList;
1365    }
1366
1367    public Map<String, String> getDefaultProperties(Object viewObject) {
1368        return getContext().getDefaultPropMap(viewObject);
1369    }
1370
1371    public void setScene(RenderSession session) {
1372        mScene = session;
1373    }
1374
1375    public RenderSession getSession() {
1376        return mScene;
1377    }
1378}
1379