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