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 
17 package com.android.layoutlib.bridge.impl;
18 
19 import com.android.ide.common.rendering.api.AdapterBinding;
20 import com.android.ide.common.rendering.api.HardwareConfig;
21 import com.android.ide.common.rendering.api.ILayoutLog;
22 import com.android.ide.common.rendering.api.ILayoutPullParser;
23 import com.android.ide.common.rendering.api.LayoutlibCallback;
24 import com.android.ide.common.rendering.api.RenderSession;
25 import com.android.ide.common.rendering.api.ResourceReference;
26 import com.android.ide.common.rendering.api.ResourceValue;
27 import com.android.ide.common.rendering.api.Result;
28 import com.android.ide.common.rendering.api.SessionParams;
29 import com.android.ide.common.rendering.api.SessionParams.RenderingMode;
30 import com.android.ide.common.rendering.api.SessionParams.RenderingMode.SizeAction;
31 import com.android.ide.common.rendering.api.ViewInfo;
32 import com.android.ide.common.rendering.api.ViewType;
33 import com.android.internal.view.menu.ActionMenuItemView;
34 import com.android.internal.view.menu.BridgeMenuItemImpl;
35 import com.android.internal.view.menu.IconMenuItemView;
36 import com.android.internal.view.menu.ListMenuItemView;
37 import com.android.internal.view.menu.MenuItemImpl;
38 import com.android.internal.view.menu.MenuView;
39 import com.android.layoutlib.bridge.Bridge;
40 import com.android.layoutlib.bridge.android.BridgeContext;
41 import com.android.layoutlib.bridge.android.BridgeXmlBlockParser;
42 import com.android.layoutlib.bridge.android.RenderParamsFlags;
43 import com.android.layoutlib.bridge.android.graphics.NopCanvas;
44 import com.android.layoutlib.bridge.android.support.DesignLibUtil;
45 import com.android.layoutlib.bridge.android.support.FragmentTabHostUtil;
46 import com.android.layoutlib.bridge.android.support.SupportPreferencesUtil;
47 import com.android.layoutlib.bridge.impl.binding.FakeAdapter;
48 import com.android.layoutlib.bridge.impl.binding.FakeExpandableAdapter;
49 import com.android.tools.idea.validator.LayoutValidator;
50 import com.android.tools.idea.validator.ValidatorResult;
51 import com.android.tools.idea.validator.ValidatorResult.Builder;
52 import com.android.tools.layoutlib.java.System_Delegate;
53 import com.android.utils.Pair;
54 
55 import android.annotation.NonNull;
56 import android.annotation.Nullable;
57 import android.graphics.Bitmap;
58 import android.graphics.Bitmap_Delegate;
59 import android.graphics.Canvas;
60 import android.graphics.NinePatch_Delegate;
61 import android.os.Looper;
62 import android.preference.Preference_Delegate;
63 import android.view.AttachInfo_Accessor;
64 import android.view.BridgeInflater;
65 import android.view.Choreographer_Delegate;
66 import android.view.View;
67 import android.view.View.MeasureSpec;
68 import android.view.ViewGroup;
69 import android.view.ViewGroup.LayoutParams;
70 import android.view.ViewGroup.MarginLayoutParams;
71 import android.view.ViewParent;
72 import android.widget.AbsListView;
73 import android.widget.AbsSpinner;
74 import android.widget.ActionMenuView;
75 import android.widget.AdapterView;
76 import android.widget.ExpandableListView;
77 import android.widget.FrameLayout;
78 import android.widget.LinearLayout;
79 import android.widget.ListView;
80 import android.widget.QuickContactBadge;
81 import android.widget.TabHost;
82 import android.widget.TabHost.TabSpec;
83 import android.widget.TabWidget;
84 
85 import java.awt.AlphaComposite;
86 import java.awt.Color;
87 import java.awt.Graphics2D;
88 import java.awt.image.BufferedImage;
89 import java.util.ArrayList;
90 import java.util.IdentityHashMap;
91 import java.util.List;
92 import java.util.Map;
93 
94 import com.google.android.apps.common.testing.accessibility.framework.uielement.AccessibilityHierarchyAndroid_ViewElementClassNamesAndroid_Delegate;
95 
96 import static com.android.ide.common.rendering.api.Result.Status.ERROR_INFLATION;
97 import static com.android.ide.common.rendering.api.Result.Status.ERROR_NOT_INFLATED;
98 import static com.android.ide.common.rendering.api.Result.Status.ERROR_UNKNOWN;
99 import static com.android.ide.common.rendering.api.Result.Status.SUCCESS;
100 import static com.android.layoutlib.bridge.util.ReflectionUtils.isInstanceOf;
101 
102 /**
103  * Class implementing the render session.
104  * <p/>
105  * A session is a stateful representation of a layout file. It is initialized with data coming
106  * through the {@link Bridge} API to inflate the layout. Further actions and rendering can then
107  * be done on the layout.
108  */
109 public class RenderSessionImpl extends RenderAction<SessionParams> {
110 
111     private static final Canvas NOP_CANVAS = new NopCanvas();
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     /** If >= 0, a frame will be executed */
123     private long mElapsedFrameTimeNanos = -1;
124     /** True if one frame has been already executed to start the animations */
125     private boolean mFirstFrameExecuted = false;
126 
127     // information being returned through the API
128     private BufferedImage mImage;
129     private List<ViewInfo> mViewInfoList;
130     private List<ViewInfo> mSystemViewInfoList;
131     private Layout.Builder mLayoutBuilder;
132     private boolean mNewRenderSize;
133     @Nullable private ValidatorResult mValidatorResult = null;
134 
135     private static final class PostInflateException extends Exception {
136         private static final long serialVersionUID = 1L;
137 
PostInflateException(String message)138         private PostInflateException(String message) {
139             super(message);
140         }
141     }
142 
143     /**
144      * Creates a layout scene with all the information coming from the layout bridge API.
145      * <p>
146      * This <b>must</b> be followed by a call to {@link RenderSessionImpl#init(long)},
147      * which act as a
148      * call to {@link RenderSessionImpl#acquire(long)}
149      *
150      * @see Bridge#createSession(SessionParams)
151      */
RenderSessionImpl(SessionParams params)152     public RenderSessionImpl(SessionParams params) {
153         super(new SessionParams(params));
154     }
155 
156     /**
157      * Initializes and acquires the scene, creating various Android objects such as context,
158      * inflater, and parser.
159      *
160      * @param timeout the time to wait if another rendering is happening.
161      *
162      * @return whether the scene was prepared
163      *
164      * @see #acquire(long)
165      * @see #release()
166      */
167     @Override
init(long timeout)168     public Result init(long timeout) {
169         Result result = super.init(timeout);
170         if (!result.isSuccess()) {
171             return result;
172         }
173 
174         SessionParams params = getParams();
175         BridgeContext context = getContext();
176 
177         mLayoutBuilder = new Layout.Builder(params, context);
178 
179         // build the inflater and parser.
180         mInflater = new BridgeInflater(context, params.getLayoutlibCallback());
181         context.setBridgeInflater(mInflater);
182 
183         ILayoutPullParser layoutParser = params.getLayoutDescription();
184         mBlockParser = new BridgeXmlBlockParser(layoutParser, context, layoutParser.getLayoutNamespace());
185 
186         return SUCCESS.createResult();
187     }
188 
189     /**
190      * Measures the the current layout if needed (see {@link #invalidateRenderingSize}).
191      */
measureLayout(@onNull SessionParams params)192     private void measureLayout(@NonNull SessionParams params) {
193         // only do the screen measure when needed.
194         if (mMeasuredScreenWidth != -1) {
195             return;
196         }
197 
198         RenderingMode renderingMode = params.getRenderingMode();
199         HardwareConfig hardwareConfig = params.getHardwareConfig();
200 
201         mNewRenderSize = true;
202         mMeasuredScreenWidth = hardwareConfig.getScreenWidth();
203         mMeasuredScreenHeight = hardwareConfig.getScreenHeight();
204 
205         if (renderingMode != RenderingMode.NORMAL) {
206             int widthMeasureSpecMode = renderingMode.getHorizAction() == SizeAction.EXPAND ?
207                     MeasureSpec.UNSPECIFIED // this lets us know the actual needed size
208                     : MeasureSpec.EXACTLY;
209             int heightMeasureSpecMode = renderingMode.getVertAction() == SizeAction.EXPAND ?
210                     MeasureSpec.UNSPECIFIED // this lets us know the actual needed size
211                     : MeasureSpec.EXACTLY;
212 
213             // We used to compare the measured size of the content to the screen size but
214             // this does not work anymore due to the 2 following issues:
215             // - If the content is in a decor (system bar, title/action bar), the root view
216             //   will not resize even with the UNSPECIFIED because of the embedded layout.
217             // - If there is no decor, but a dialog frame, then the dialog padding prevents
218             //   comparing the size of the content to the screen frame (as it would not
219             //   take into account the dialog padding).
220 
221             // The solution is to first get the content size in a normal rendering, inside
222             // the decor or the dialog padding.
223             // Then measure only the content with UNSPECIFIED to see the size difference
224             // and apply this to the screen size.
225 
226             View measuredView = mContentRoot.getChildAt(0);
227 
228             // first measure the full layout, with EXACTLY to get the size of the
229             // content as it is inside the decor/dialog
230             Pair<Integer, Integer> exactMeasure = measureView(
231                     mViewRoot, measuredView,
232                     mMeasuredScreenWidth, MeasureSpec.EXACTLY,
233                     mMeasuredScreenHeight, MeasureSpec.EXACTLY);
234 
235             // now measure the content only using UNSPECIFIED (where applicable, based on
236             // the rendering mode). This will give us the size the content needs.
237             Pair<Integer, Integer> neededMeasure = measureView(
238                     mContentRoot, mContentRoot.getChildAt(0),
239                     mMeasuredScreenWidth, widthMeasureSpecMode,
240                     mMeasuredScreenHeight, heightMeasureSpecMode);
241             int neededWidth = neededMeasure.getFirst();
242             int neededHeight = neededMeasure.getSecond();
243 
244             // If measuredView is not null, exactMeasure nor result will be null.
245             assert (exactMeasure != null && neededMeasure != null) || measuredView == null;
246 
247             // now look at the difference and add what is needed.
248             if (renderingMode.getHorizAction() == SizeAction.EXPAND) {
249                 int measuredWidth = exactMeasure.getFirst();
250                 if (neededWidth > measuredWidth) {
251                     mMeasuredScreenWidth += neededWidth - measuredWidth;
252                 }
253                 if (mMeasuredScreenWidth < measuredWidth) {
254                     // If the screen width is less than the exact measured width,
255                     // expand to match.
256                     mMeasuredScreenWidth = measuredWidth;
257                 }
258             } else if (renderingMode.getHorizAction() == SizeAction.SHRINK) {
259                 mMeasuredScreenWidth = neededWidth;
260             }
261 
262             if (renderingMode.getVertAction() == SizeAction.EXPAND) {
263                 int measuredHeight = exactMeasure.getSecond();
264                 if (neededHeight > measuredHeight) {
265                     mMeasuredScreenHeight += neededHeight - measuredHeight;
266                 }
267                 if (mMeasuredScreenHeight < measuredHeight) {
268                     // If the screen height is less than the exact measured height,
269                     // expand to match.
270                     mMeasuredScreenHeight = measuredHeight;
271                 }
272             } else if (renderingMode.getVertAction() == SizeAction.SHRINK) {
273                 mMeasuredScreenHeight = neededHeight;
274             }
275         }
276     }
277 
278     /**
279      * Inflates the layout.
280      * <p>
281      * {@link #acquire(long)} must have been called before this.
282      *
283      * @throws IllegalStateException if the current context is different than the one owned by
284      *      the scene, or if {@link #init(long)} was not called.
285      */
inflate()286     public Result inflate() {
287         checkLock();
288 
289         try {
290             mViewRoot = new Layout(mLayoutBuilder);
291             mLayoutBuilder = null;  // Done with the builder.
292             mContentRoot = ((Layout) mViewRoot).getContentRoot();
293             SessionParams params = getParams();
294             BridgeContext context = getContext();
295 
296             if (Bridge.isLocaleRtl(params.getLocale())) {
297                 if (!params.isRtlSupported()) {
298                     Bridge.getLog().warning(ILayoutLog.TAG_RTL_NOT_ENABLED,
299                             "You are using a right-to-left " +
300                                     "(RTL) locale but RTL is not enabled", null, null);
301                 } else if (params.getSimulatedPlatformVersion() !=0 &&
302                         params.getSimulatedPlatformVersion() < 17) {
303                     // This will render ok because we are using the latest layoutlib but at least
304                     // warn the user that this might fail in a real device.
305                     Bridge.getLog().warning(ILayoutLog.TAG_RTL_NOT_SUPPORTED, "You are using a " +
306                             "right-to-left " +
307                             "(RTL) locale but RTL is not supported for API level < 17", null, null);
308                 }
309             }
310 
311             String rootTag = params.getFlag(RenderParamsFlags.FLAG_KEY_ROOT_TAG);
312             boolean isPreference = "PreferenceScreen".equals(rootTag) ||
313                     SupportPreferencesUtil.isSupportRootTag(rootTag);
314             View view;
315             if (isPreference) {
316                 // First try to use the support library inflater. If something fails, fallback
317                 // to the system preference inflater.
318                 view = SupportPreferencesUtil.inflatePreference(context, mBlockParser,
319                         mContentRoot);
320                 if (view == null) {
321                     view = Preference_Delegate.inflatePreference(context, mBlockParser,
322                             mContentRoot);
323                 }
324             } else {
325                 view = mInflater.inflate(mBlockParser, mContentRoot);
326             }
327 
328             // done with the parser, pop it.
329             context.popParser();
330 
331             // set the AttachInfo on the root view.
332             AttachInfo_Accessor.setAttachInfo(mViewRoot);
333 
334             // post-inflate process. For now this supports TabHost/TabWidget
335             postInflateProcess(view, params.getLayoutlibCallback(), isPreference ? view : null);
336             mInflater.onDoneInflation();
337 
338             setActiveToolbar(view, context, params);
339 
340             measureLayout(params);
341             measureView(mViewRoot, null /*measuredView*/,
342                     mMeasuredScreenWidth, MeasureSpec.EXACTLY,
343                     mMeasuredScreenHeight, MeasureSpec.EXACTLY);
344             mViewRoot.layout(0, 0, mMeasuredScreenWidth, mMeasuredScreenHeight);
345             mSystemViewInfoList =
346                     visitAllChildren(mViewRoot, 0, 0, params.getExtendedViewInfoMode(),
347                     false);
348 
349             Choreographer_Delegate.clearFrames();
350 
351             return SUCCESS.createResult();
352         } catch (PostInflateException e) {
353             return ERROR_INFLATION.createResult(e.getMessage(), e);
354         } catch (Throwable e) {
355             // get the real cause of the exception.
356             Throwable t = e;
357             while (t.getCause() != null) {
358                 t = t.getCause();
359             }
360 
361             return ERROR_INFLATION.createResult(t.getMessage(), t);
362         }
363     }
364 
365     /**
366      * Sets the time for which the next frame will be selected. The time is the elapsed time from
367      * the current system nanos time. You
368      */
setElapsedFrameTimeNanos(long nanos)369     public void setElapsedFrameTimeNanos(long nanos) {
370         mElapsedFrameTimeNanos = nanos;
371     }
372 
373     /**
374      * Runs a layout pass for the given view root
375      */
doLayout(@onNull BridgeContext context, @NonNull ViewGroup viewRoot, int width, int height)376     private static void doLayout(@NonNull BridgeContext context, @NonNull ViewGroup viewRoot,
377             int width, int height) {
378         // measure again with the size we need
379         // This must always be done before the call to layout
380         measureView(viewRoot, null /*measuredView*/,
381                 width, MeasureSpec.EXACTLY,
382                 height, MeasureSpec.EXACTLY);
383 
384         // now do the layout.
385         viewRoot.layout(0, 0, width, height);
386         handleScrolling(context, viewRoot);
387     }
388 
389     /**
390      * Renders the given view hierarchy to the passed canvas and returns the result of the render
391      * operation.
392      * @param canvas an optional canvas to render the views to. If null, only the measure and
393      * layout steps will be executed.
394      */
renderAndBuildResult(@onNull ViewGroup viewRoot, @Nullable Canvas canvas)395     private static Result renderAndBuildResult(@NonNull ViewGroup viewRoot, @Nullable Canvas canvas) {
396         if (canvas == null) {
397             return SUCCESS.createResult();
398         }
399 
400         AttachInfo_Accessor.dispatchOnPreDraw(viewRoot);
401         viewRoot.draw(canvas);
402 
403         return SUCCESS.createResult();
404     }
405 
406     /**
407      * Renders the scene.
408      * <p>
409      * {@link #acquire(long)} must have been called before this.
410      *
411      * @param freshRender whether the render is a new one and should erase the existing bitmap (in
412      *      the case where bitmaps are reused). This is typically needed when not playing
413      *      animations.)
414      *
415      * @throws IllegalStateException if the current context is different than the one owned by
416      *      the scene, or if {@link #acquire(long)} was not called.
417      *
418      * @see SessionParams#getRenderingMode()
419      * @see RenderSession#render(long)
420      */
render(boolean freshRender)421     public Result render(boolean freshRender) {
422         return renderAndBuildResult(freshRender, false);
423     }
424 
425     /**
426      * Measures the layout
427      * <p>
428      * {@link #acquire(long)} must have been called before this.
429      *
430      * @throws IllegalStateException if the current context is different than the one owned by
431      *      the scene, or if {@link #acquire(long)} was not called.
432      *
433      * @see SessionParams#getRenderingMode()
434      * @see RenderSession#render(long)
435      */
measure()436     public Result measure() {
437         return renderAndBuildResult(false, true);
438     }
439 
440     /**
441      * Renders the scene.
442      * <p>
443      * {@link #acquire(long)} must have been called before this.
444      *
445      * @param freshRender whether the render is a new one and should erase the existing bitmap (in
446      *      the case where bitmaps are reused). This is typically needed when not playing
447      *      animations.)
448      *
449      * @throws IllegalStateException if the current context is different than the one owned by
450      *      the scene, or if {@link #acquire(long)} was not called.
451      *
452      * @see SessionParams#getRenderingMode()
453      * @see RenderSession#render(long)
454      */
renderAndBuildResult(boolean freshRender, boolean onlyMeasure)455     private Result renderAndBuildResult(boolean freshRender, boolean onlyMeasure) {
456         checkLock();
457 
458         SessionParams params = getParams();
459 
460         try {
461             if (mViewRoot == null) {
462                 return ERROR_NOT_INFLATED.createResult();
463             }
464 
465             measureLayout(params);
466 
467             HardwareConfig hardwareConfig = params.getHardwareConfig();
468             Result renderResult = SUCCESS.createResult();
469             if (onlyMeasure) {
470                 // delete the canvas and image to reset them on the next full rendering
471                 mImage = null;
472                 mCanvas = null;
473                 doLayout(getContext(), mViewRoot, mMeasuredScreenWidth, mMeasuredScreenHeight);
474             } else {
475                 // draw the views
476                 // create the BufferedImage into which the layout will be rendered.
477                 boolean newImage = false;
478 
479                 // When disableBitmapCaching is true, we do not reuse mImage and
480                 // we create a new one in every render.
481                 // This is useful when mImage is just a wrapper of Graphics2D so
482                 // it doesn't get cached.
483                 boolean disableBitmapCaching = Boolean.TRUE.equals(params.getFlag(
484                     RenderParamsFlags.FLAG_KEY_DISABLE_BITMAP_CACHING));
485 
486                 if (mNewRenderSize || mCanvas == null || disableBitmapCaching) {
487                     mNewRenderSize = false;
488                     if (params.getImageFactory() != null) {
489                         mImage = params.getImageFactory().getImage(
490                                 mMeasuredScreenWidth,
491                                 mMeasuredScreenHeight);
492                     } else {
493                         mImage = new BufferedImage(
494                                 mMeasuredScreenWidth,
495                                 mMeasuredScreenHeight,
496                                 BufferedImage.TYPE_INT_ARGB);
497                         newImage = true;
498                     }
499 
500                     if (params.isTransparentBackground()) {
501                         // since we override the content, it's the same as if it was a new image.
502                         newImage = true;
503                         Graphics2D gc = mImage.createGraphics();
504                         gc.setColor(new Color(0, true));
505                         gc.setComposite(AlphaComposite.Src);
506                         gc.fillRect(0, 0, mMeasuredScreenWidth, mMeasuredScreenHeight);
507                         gc.dispose();
508                     }
509 
510                     // create an Android bitmap around the BufferedImage
511                     Bitmap bitmap = Bitmap_Delegate.createBitmap(mImage,
512                             true /*isMutable*/, hardwareConfig.getDensity());
513 
514                     if (mCanvas == null) {
515                         // create a Canvas around the Android bitmap
516                         mCanvas = new Canvas(bitmap);
517                     } else {
518                         mCanvas.setBitmap(bitmap);
519                     }
520 
521                     boolean enableImageResizing =
522                             mImage.getWidth() != mMeasuredScreenWidth &&
523                             mImage.getHeight() != mMeasuredScreenHeight &&
524                             Boolean.TRUE.equals(params.getFlag(
525                                     RenderParamsFlags.FLAG_KEY_RESULT_IMAGE_AUTO_SCALE));
526 
527                     if (enableImageResizing) {
528                         float scaleX = (float)mImage.getWidth() / mMeasuredScreenWidth;
529                         float scaleY = (float)mImage.getHeight() / mMeasuredScreenHeight;
530                         mCanvas.scale(scaleX, scaleY);
531                     }
532 
533                     mCanvas.setDensity(hardwareConfig.getDensity().getDpiValue());
534                 }
535 
536                 if (freshRender && !newImage) {
537                     Graphics2D gc = mImage.createGraphics();
538                     gc.setComposite(AlphaComposite.Src);
539 
540                     gc.setColor(new Color(0x00000000, true));
541                     gc.fillRect(0, 0,
542                             mMeasuredScreenWidth, mMeasuredScreenHeight);
543 
544                     // done
545                     gc.dispose();
546                 }
547 
548                 doLayout(getContext(), mViewRoot, mMeasuredScreenWidth, mMeasuredScreenHeight);
549                 if (mElapsedFrameTimeNanos >= 0) {
550                     long initialTime = System_Delegate.nanoTime();
551                     if (!mFirstFrameExecuted) {
552                         // We need to run an initial draw call to initialize the animations
553                         renderAndBuildResult(mViewRoot, NOP_CANVAS);
554 
555                         // The first frame will initialize the animations
556                         Choreographer_Delegate.doFrame(initialTime);
557                         mFirstFrameExecuted = true;
558                     }
559                     // Second frame will move the animations
560                     Choreographer_Delegate.doFrame(initialTime + mElapsedFrameTimeNanos);
561                 }
562                 renderResult = renderAndBuildResult(mViewRoot, mCanvas);
563             }
564 
565             mSystemViewInfoList =
566                     visitAllChildren(mViewRoot, 0, 0, params.getExtendedViewInfoMode(),
567                     false);
568 
569             try {
570                 boolean enableLayoutValidation = Boolean.TRUE.equals(params.getFlag(RenderParamsFlags.FLAG_ENABLE_LAYOUT_VALIDATOR));
571                 boolean enableLayoutValidationImageCheck = Boolean.TRUE.equals(
572                          params.getFlag(RenderParamsFlags.FLAG_ENABLE_LAYOUT_VALIDATOR_IMAGE_CHECK));
573 
574                 if (enableLayoutValidation && !getViewInfos().isEmpty()) {
575                     AccessibilityHierarchyAndroid_ViewElementClassNamesAndroid_Delegate.sLayoutlibCallback =
576                             getContext().getLayoutlibCallback();
577 
578                     BufferedImage imageToPass =
579                             enableLayoutValidationImageCheck ? getImage() : null;
580                     ValidatorResult validatorResult =
581                             LayoutValidator.validate(((View) getViewInfos().get(0).getViewObject()), imageToPass);
582                     setValidatorResult(validatorResult);
583                 }
584             } catch (Throwable e) {
585                 ValidatorResult.Builder builder = new Builder();
586                 builder.mMetric.mErrorMessage = e.getMessage();
587                 setValidatorResult(builder.build());
588             } finally {
589                 AccessibilityHierarchyAndroid_ViewElementClassNamesAndroid_Delegate.sLayoutlibCallback = null;
590             }
591 
592             // success!
593             return renderResult;
594         } catch (Throwable e) {
595             // get the real cause of the exception.
596             Throwable t = e;
597             while (t.getCause() != null) {
598                 t = t.getCause();
599             }
600 
601             return ERROR_UNKNOWN.createResult(t.getMessage(), t);
602         }
603     }
604 
605     /**
606      * Executes {@link View#measure(int, int)} on a given view with the given parameters (used
607      * to create measure specs with {@link MeasureSpec#makeMeasureSpec(int, int)}.
608      *
609      * if <var>measuredView</var> is non null, the method returns a {@link Pair} of (width, height)
610      * for the view (using {@link View#getMeasuredWidth()} and {@link View#getMeasuredHeight()}).
611      *
612      * @param viewToMeasure the view on which to execute measure().
613      * @param measuredView if non null, the view to query for its measured width/height.
614      * @param width the width to use in the MeasureSpec.
615      * @param widthMode the MeasureSpec mode to use for the width.
616      * @param height the height to use in the MeasureSpec.
617      * @param heightMode the MeasureSpec mode to use for the height.
618      * @return the measured width/height if measuredView is non-null, null otherwise.
619      */
measureView(ViewGroup viewToMeasure, View measuredView, int width, int widthMode, int height, int heightMode)620     private static Pair<Integer, Integer> measureView(ViewGroup viewToMeasure, View measuredView,
621             int width, int widthMode, int height, int heightMode) {
622         int w_spec = MeasureSpec.makeMeasureSpec(width, widthMode);
623         int h_spec = MeasureSpec.makeMeasureSpec(height, heightMode);
624         viewToMeasure.measure(w_spec, h_spec);
625 
626         if (measuredView != null) {
627             return Pair.of(measuredView.getMeasuredWidth(), measuredView.getMeasuredHeight());
628         }
629 
630         return null;
631     }
632 
633     /**
634      * Post process on a view hierarchy that was just inflated.
635      * <p/>
636      * At the moment this only supports TabHost: If {@link TabHost} is detected, look for the
637      * {@link TabWidget}, and the corresponding {@link FrameLayout} and make new tabs automatically
638      * based on the content of the {@link FrameLayout}.
639      * @param view the root view to process.
640      * @param layoutlibCallback callback to the project.
641      * @param skip the view and it's children are not processed.
642      */
postInflateProcess(View view, LayoutlibCallback layoutlibCallback, View skip)643     private void postInflateProcess(View view, LayoutlibCallback layoutlibCallback, View skip)
644             throws PostInflateException {
645         if (view == skip) {
646             return;
647         }
648         if (view instanceof TabHost) {
649             setupTabHost((TabHost) view, layoutlibCallback);
650         } else if (view instanceof QuickContactBadge) {
651             QuickContactBadge badge = (QuickContactBadge) view;
652             badge.setImageToDefault();
653         } else if (view instanceof AdapterView<?>) {
654             // get the view ID.
655             int id = view.getId();
656 
657             BridgeContext context = getContext();
658 
659             // get a ResourceReference from the integer ID.
660             ResourceReference listRef = context.resolveId(id);
661 
662             if (listRef != null) {
663                 SessionParams params = getParams();
664                 AdapterBinding binding = params.getAdapterBindings().get(listRef);
665 
666                 // if there was no adapter binding, trying to get it from the call back.
667                 if (binding == null) {
668                     binding = layoutlibCallback.getAdapterBinding(
669                             listRef, context.getViewKey(view), view);
670                 }
671 
672                 if (binding != null) {
673 
674                     if (view instanceof AbsListView) {
675                         if ((binding.getFooterCount() > 0 || binding.getHeaderCount() > 0) &&
676                                 view instanceof ListView) {
677                             ListView list = (ListView) view;
678 
679                             boolean skipCallbackParser = false;
680 
681                             int count = binding.getHeaderCount();
682                             for (int i = 0; i < count; i++) {
683                                 Pair<View, Boolean> pair = context.inflateView(
684                                         binding.getHeaderAt(i),
685                                         list, false, skipCallbackParser);
686                                 if (pair.getFirst() != null) {
687                                     list.addHeaderView(pair.getFirst());
688                                 }
689 
690                                 skipCallbackParser |= pair.getSecond();
691                             }
692 
693                             count = binding.getFooterCount();
694                             for (int i = 0; i < count; i++) {
695                                 Pair<View, Boolean> pair = context.inflateView(
696                                         binding.getFooterAt(i),
697                                         list, false, skipCallbackParser);
698                                 if (pair.getFirst() != null) {
699                                     list.addFooterView(pair.getFirst());
700                                 }
701 
702                                 skipCallbackParser |= pair.getSecond();
703                             }
704                         }
705 
706                         if (view instanceof ExpandableListView) {
707                             ((ExpandableListView) view).setAdapter(
708                                     new FakeExpandableAdapter(listRef, binding, layoutlibCallback));
709                         } else {
710                             ((AbsListView) view).setAdapter(
711                                     new FakeAdapter(listRef, binding, layoutlibCallback));
712                         }
713                     } else if (view instanceof AbsSpinner) {
714                         ((AbsSpinner) view).setAdapter(
715                                 new FakeAdapter(listRef, binding, layoutlibCallback));
716                     }
717                 }
718             }
719         } else if (view instanceof ViewGroup) {
720             mInflater.postInflateProcess(view);
721             ViewGroup group = (ViewGroup) view;
722             final int count = group.getChildCount();
723             for (int c = 0; c < count; c++) {
724                 View child = group.getChildAt(c);
725                 postInflateProcess(child, layoutlibCallback, skip);
726             }
727         }
728     }
729 
730     /**
731      * If the root layout is a CoordinatorLayout with an AppBar:
732      * Set the title of the AppBar to the title of the activity context.
733      */
setActiveToolbar(View view, BridgeContext context, SessionParams params)734     private void setActiveToolbar(View view, BridgeContext context, SessionParams params) {
735         View coordinatorLayout = findChildView(view, DesignLibUtil.CN_COORDINATOR_LAYOUT);
736         if (coordinatorLayout == null) {
737             return;
738         }
739         View appBar = findChildView(coordinatorLayout, DesignLibUtil.CN_APPBAR_LAYOUT);
740         if (appBar == null) {
741             return;
742         }
743         ViewGroup collapsingToolbar =
744                 (ViewGroup) findChildView(appBar, DesignLibUtil.CN_COLLAPSING_TOOLBAR_LAYOUT);
745         if (collapsingToolbar == null) {
746             return;
747         }
748         if (!hasToolbar(collapsingToolbar)) {
749             return;
750         }
751         String title = params.getAppLabel();
752         DesignLibUtil.setTitle(collapsingToolbar, title);
753     }
754 
findChildView(View view, String[] className)755     private View findChildView(View view, String[] className) {
756         if (!(view instanceof ViewGroup)) {
757             return null;
758         }
759         ViewGroup group = (ViewGroup) view;
760         for (int i = 0; i < group.getChildCount(); i++) {
761             if (isInstanceOf(group.getChildAt(i), className)) {
762                 return group.getChildAt(i);
763             }
764         }
765         return null;
766     }
767 
hasToolbar(View collapsingToolbar)768     private boolean hasToolbar(View collapsingToolbar) {
769         if (!(collapsingToolbar instanceof ViewGroup)) {
770             return false;
771         }
772         ViewGroup group = (ViewGroup) collapsingToolbar;
773         for (int i = 0; i < group.getChildCount(); i++) {
774             if (isInstanceOf(group.getChildAt(i), DesignLibUtil.CN_TOOLBAR)) {
775                 return true;
776             }
777         }
778         return false;
779     }
780 
781     /**
782      * Set the scroll position on all the components with the "scrollX" and "scrollY" attribute. If
783      * the component supports nested scrolling attempt that first, then use the unconsumed scroll
784      * part to scroll the content in the component.
785      */
handleScrolling(BridgeContext context, View view)786     private static void handleScrolling(BridgeContext context, View view) {
787         int scrollPosX = context.getScrollXPos(view);
788         int scrollPosY = context.getScrollYPos(view);
789         if (scrollPosX != 0 || scrollPosY != 0) {
790             if (view.isNestedScrollingEnabled()) {
791                 int[] consumed = new int[2];
792                 int axis = scrollPosX != 0 ? View.SCROLL_AXIS_HORIZONTAL : 0;
793                 axis |= scrollPosY != 0 ? View.SCROLL_AXIS_VERTICAL : 0;
794                 if (view.startNestedScroll(axis)) {
795                     view.dispatchNestedPreScroll(scrollPosX, scrollPosY, consumed, null);
796                     view.dispatchNestedScroll(consumed[0], consumed[1], scrollPosX, scrollPosY,
797                             null);
798                     view.stopNestedScroll();
799                     scrollPosX -= consumed[0];
800                     scrollPosY -= consumed[1];
801                 }
802             }
803             if (scrollPosX != 0 || scrollPosY != 0) {
804                 view.scrollTo(scrollPosX, scrollPosY);
805             }
806         }
807 
808         if (!(view instanceof ViewGroup)) {
809             return;
810         }
811         ViewGroup group = (ViewGroup) view;
812         for (int i = 0; i < group.getChildCount(); i++) {
813             View child = group.getChildAt(i);
814             handleScrolling(context, child);
815         }
816     }
817 
818     /**
819      * Sets up a {@link TabHost} object.
820      * @param tabHost the TabHost to setup.
821      * @param layoutlibCallback The project callback object to access the project R class.
822      * @throws PostInflateException if TabHost is missing the required ids for TabHost
823      */
setupTabHost(TabHost tabHost, LayoutlibCallback layoutlibCallback)824     private void setupTabHost(TabHost tabHost, LayoutlibCallback layoutlibCallback)
825             throws PostInflateException {
826         // look for the TabWidget, and the FrameLayout. They have their own specific names
827         View v = tabHost.findViewById(android.R.id.tabs);
828 
829         if (v == null) {
830             throw new PostInflateException(
831                     "TabHost requires a TabWidget with id \"android:id/tabs\".\n");
832         }
833 
834         if (!(v instanceof TabWidget)) {
835             throw new PostInflateException(String.format(
836                     "TabHost requires a TabWidget with id \"android:id/tabs\".\n" +
837                     "View found with id 'tabs' is '%s'", v.getClass().getCanonicalName()));
838         }
839 
840         v = tabHost.findViewById(android.R.id.tabcontent);
841 
842         if (v == null) {
843             // TODO: see if we can fake tabs even without the FrameLayout (same below when the frameLayout is empty)
844             //noinspection SpellCheckingInspection
845             throw new PostInflateException(
846                     "TabHost requires a FrameLayout with id \"android:id/tabcontent\".");
847         }
848 
849         if (!(v instanceof FrameLayout)) {
850             //noinspection SpellCheckingInspection
851             throw new PostInflateException(String.format(
852                     "TabHost requires a FrameLayout with id \"android:id/tabcontent\".\n" +
853                     "View found with id 'tabcontent' is '%s'", v.getClass().getCanonicalName()));
854         }
855 
856         FrameLayout content = (FrameLayout)v;
857 
858         // now process the content of the frameLayout and dynamically create tabs for it.
859         final int count = content.getChildCount();
860 
861         // this must be called before addTab() so that the TabHost searches its TabWidget
862         // and FrameLayout.
863         if (isInstanceOf(tabHost, FragmentTabHostUtil.CN_FRAGMENT_TAB_HOST)) {
864             FragmentTabHostUtil.setup(tabHost, getContext());
865         } else {
866             tabHost.setup();
867         }
868 
869         if (count == 0) {
870             // Create a placeholder child to get a single tab
871             TabSpec spec = tabHost.newTabSpec("tag")
872                     .setIndicator("Tab Label", tabHost.getResources()
873                             .getDrawable(android.R.drawable.ic_menu_info_details, null))
874                     .setContent(tag -> new LinearLayout(getContext()));
875             tabHost.addTab(spec);
876         } else {
877             // for each child of the frameLayout, add a new TabSpec
878             for (int i = 0 ; i < count ; i++) {
879                 View child = content.getChildAt(i);
880                 String tabSpec = String.format("tab_spec%d", i+1);
881                 int id = child.getId();
882                 ResourceReference resource = layoutlibCallback.resolveResourceId(id);
883                 String name;
884                 if (resource != null) {
885                     name = resource.getName();
886                 } else {
887                     name = String.format("Tab %d", i+1); // default name if id is unresolved.
888                 }
889                 tabHost.addTab(tabHost.newTabSpec(tabSpec).setIndicator(name).setContent(id));
890             }
891         }
892     }
893 
894     /**
895      * Visits a {@link View} and its children and generate a {@link ViewInfo} containing the
896      * bounds of all the views.
897      *
898      * @param view the root View
899      * @param hOffset horizontal offset for the view bounds.
900      * @param vOffset vertical offset for the view bounds.
901      * @param setExtendedInfo whether to set the extended view info in the {@link ViewInfo} object.
902      * @param isContentFrame {@code true} if the {@code ViewInfo} to be created is part of the
903      *                       content frame.
904      *
905      * @return {@code ViewInfo} containing the bounds of the view and it children otherwise.
906      */
visit(View view, int hOffset, int vOffset, boolean setExtendedInfo, boolean isContentFrame)907     private ViewInfo visit(View view, int hOffset, int vOffset, boolean setExtendedInfo,
908             boolean isContentFrame) {
909         ViewInfo result = createViewInfo(view, hOffset, vOffset, setExtendedInfo, isContentFrame);
910 
911         if (view instanceof ViewGroup) {
912             ViewGroup group = ((ViewGroup) view);
913             result.setChildren(visitAllChildren(group, isContentFrame ? 0 : hOffset,
914                     isContentFrame ? 0 : vOffset,
915                     setExtendedInfo, isContentFrame));
916         }
917         return result;
918     }
919 
920     /**
921      * Visits all the children of a given ViewGroup and generates a list of {@link ViewInfo}
922      * containing the bounds of all the views. It also initializes the {@link #mViewInfoList} with
923      * the children of the {@code mContentRoot}.
924      *
925      * @param viewGroup the root View
926      * @param hOffset horizontal offset from the top for the content view frame.
927      * @param vOffset vertical offset from the top for the content view frame.
928      * @param setExtendedInfo whether to set the extended view info in the {@link ViewInfo} object.
929      * @param isContentFrame {@code true} if the {@code ViewInfo} to be created is part of the
930      *                       content frame. {@code false} if the {@code ViewInfo} to be created is
931      *                       part of the system decor.
932      */
visitAllChildren(ViewGroup viewGroup, int hOffset, int vOffset, boolean setExtendedInfo, boolean isContentFrame)933     private List<ViewInfo> visitAllChildren(ViewGroup viewGroup, int hOffset, int vOffset,
934             boolean setExtendedInfo, boolean isContentFrame) {
935         if (viewGroup == null) {
936             return null;
937         }
938 
939         if (!isContentFrame) {
940             vOffset += viewGroup.getTop();
941             hOffset += viewGroup.getLeft();
942         }
943 
944         int childCount = viewGroup.getChildCount();
945         if (viewGroup == mContentRoot) {
946             List<ViewInfo> childrenWithoutOffset = new ArrayList<>(childCount);
947             List<ViewInfo> childrenWithOffset = new ArrayList<>(childCount);
948             for (int i = 0; i < childCount; i++) {
949                 ViewInfo[] childViewInfo =
950                         visitContentRoot(viewGroup.getChildAt(i), hOffset, vOffset,
951                         setExtendedInfo);
952                 childrenWithoutOffset.add(childViewInfo[0]);
953                 childrenWithOffset.add(childViewInfo[1]);
954             }
955             mViewInfoList = childrenWithOffset;
956             return childrenWithoutOffset;
957         } else {
958             List<ViewInfo> children = new ArrayList<>(childCount);
959             for (int i = 0; i < childCount; i++) {
960                 children.add(visit(viewGroup.getChildAt(i), hOffset, vOffset, setExtendedInfo,
961                         isContentFrame));
962             }
963             return children;
964         }
965     }
966 
967     /**
968      * Visits the children of {@link #mContentRoot} and generates {@link ViewInfo} containing the
969      * bounds of all the views. It returns two {@code ViewInfo} objects with the same children,
970      * one with the {@code offset} and other without the {@code offset}. The offset is needed to
971      * get the right bounds if the {@code ViewInfo} hierarchy is accessed from
972      * {@code mViewInfoList}. When the hierarchy is accessed via {@code mSystemViewInfoList}, the
973      * offset is not needed.
974      *
975      * @return an array of length two, with ViewInfo at index 0 is without offset and ViewInfo at
976      *         index 1 is with the offset.
977      */
978     @NonNull
visitContentRoot(View view, int hOffset, int vOffset, boolean setExtendedInfo)979     private ViewInfo[] visitContentRoot(View view, int hOffset, int vOffset,
980             boolean setExtendedInfo) {
981         ViewInfo[] result = new ViewInfo[2];
982         if (view == null) {
983             return result;
984         }
985 
986         result[0] = createViewInfo(view, 0, 0, setExtendedInfo, true);
987         result[1] = createViewInfo(view, hOffset, vOffset, setExtendedInfo, true);
988         if (view instanceof ViewGroup) {
989             List<ViewInfo> children =
990                     visitAllChildren((ViewGroup) view, 0, 0, setExtendedInfo, true);
991             result[0].setChildren(children);
992             result[1].setChildren(children);
993         }
994         return result;
995     }
996 
997     /**
998      * Creates a {@link ViewInfo} for the view. The {@code ViewInfo} corresponding to the children
999      * of the {@code view} are not created. Consequently, the children of {@code ViewInfo} is not
1000      * set.
1001      * @param hOffset horizontal offset for the view bounds. Used only if view is part of the
1002      * content frame.
1003      * @param vOffset vertial an offset for the view bounds. Used only if view is part of the
1004      * content frame.
1005      */
createViewInfo(View view, int hOffset, int vOffset, boolean setExtendedInfo, boolean isContentFrame)1006     private ViewInfo createViewInfo(View view, int hOffset, int vOffset, boolean setExtendedInfo,
1007             boolean isContentFrame) {
1008         if (view == null) {
1009             return null;
1010         }
1011 
1012         ViewParent parent = view.getParent();
1013         ViewInfo result;
1014         if (isContentFrame) {
1015             // Account for parent scroll values when calculating the bounding box
1016             int scrollX = parent != null ? ((View)parent).getScrollX() : 0;
1017             int scrollY = parent != null ? ((View)parent).getScrollY() : 0;
1018 
1019             // The view is part of the layout added by the user. Hence,
1020             // the ViewCookie may be obtained only through the Context.
1021             int shiftX = -scrollX + Math.round(view.getTranslationX()) + hOffset;
1022             int shiftY = -scrollY + Math.round(view.getTranslationY()) + vOffset;
1023             result = new ViewInfo(view.getClass().getName(),
1024                     getContext().getViewKey(view),
1025                     shiftX + view.getLeft(),
1026                     shiftY + view.getTop(),
1027                     shiftX + view.getRight(),
1028                     shiftY + view.getBottom(),
1029                     view, view.getLayoutParams());
1030         } else {
1031             // We are part of the system decor.
1032             SystemViewInfo r = new SystemViewInfo(view.getClass().getName(),
1033                     getViewKey(view),
1034                     view.getLeft(), view.getTop(), view.getRight(),
1035                     view.getBottom(), view, view.getLayoutParams());
1036             result = r;
1037             // We currently mark three kinds of views:
1038             // 1. Menus in the Action Bar
1039             // 2. Menus in the Overflow popup.
1040             // 3. The overflow popup button.
1041             if (view instanceof ListMenuItemView) {
1042                 // Mark 2.
1043                 // All menus in the popup are of type ListMenuItemView.
1044                 r.setViewType(ViewType.ACTION_BAR_OVERFLOW_MENU);
1045             } else {
1046                 // Mark 3.
1047                 ViewGroup.LayoutParams lp = view.getLayoutParams();
1048                 if (lp instanceof ActionMenuView.LayoutParams &&
1049                         ((ActionMenuView.LayoutParams) lp).isOverflowButton) {
1050                     r.setViewType(ViewType.ACTION_BAR_OVERFLOW);
1051                 } else {
1052                     // Mark 1.
1053                     // A view is a menu in the Action Bar is it is not the overflow button and of
1054                     // its parent is of type ActionMenuView. We can also check if the view is
1055                     // instanceof ActionMenuItemView but that will fail for menus using
1056                     // actionProviderClass.
1057                     while (parent != mViewRoot && parent instanceof ViewGroup) {
1058                         if (parent instanceof ActionMenuView) {
1059                             r.setViewType(ViewType.ACTION_BAR_MENU);
1060                             break;
1061                         }
1062                         parent = parent.getParent();
1063                     }
1064                 }
1065             }
1066         }
1067 
1068         if (setExtendedInfo) {
1069             MarginLayoutParams marginParams = null;
1070             LayoutParams params = view.getLayoutParams();
1071             if (params instanceof MarginLayoutParams) {
1072                 marginParams = (MarginLayoutParams) params;
1073             }
1074             result.setExtendedInfo(view.getBaseline(),
1075                     marginParams != null ? marginParams.leftMargin : 0,
1076                     marginParams != null ? marginParams.topMargin : 0,
1077                     marginParams != null ? marginParams.rightMargin : 0,
1078                     marginParams != null ? marginParams.bottomMargin : 0);
1079         }
1080 
1081         return result;
1082     }
1083 
1084     /* (non-Javadoc)
1085      * The cookie for menu items are stored in menu item and not in the map from View stored in
1086      * BridgeContext.
1087      */
1088     @Nullable
getViewKey(View view)1089     private Object getViewKey(View view) {
1090         BridgeContext context = getContext();
1091         if (!(view instanceof MenuView.ItemView)) {
1092             return context.getViewKey(view);
1093         }
1094         MenuItemImpl menuItem;
1095         if (view instanceof ActionMenuItemView) {
1096             menuItem = ((ActionMenuItemView) view).getItemData();
1097         } else if (view instanceof ListMenuItemView) {
1098             menuItem = ((ListMenuItemView) view).getItemData();
1099         } else if (view instanceof IconMenuItemView) {
1100             menuItem = ((IconMenuItemView) view).getItemData();
1101         } else {
1102             menuItem = null;
1103         }
1104         if (menuItem instanceof BridgeMenuItemImpl) {
1105             return ((BridgeMenuItemImpl) menuItem).getViewCookie();
1106         }
1107 
1108         return null;
1109     }
1110 
invalidateRenderingSize()1111     public void invalidateRenderingSize() {
1112         mMeasuredScreenWidth = mMeasuredScreenHeight = -1;
1113     }
1114 
getImage()1115     public BufferedImage getImage() {
1116         return mImage;
1117     }
1118 
getViewInfos()1119     public List<ViewInfo> getViewInfos() {
1120         return mViewInfoList;
1121     }
1122 
getSystemViewInfos()1123     public List<ViewInfo> getSystemViewInfos() {
1124         return mSystemViewInfoList;
1125     }
1126 
getDefaultNamespacedProperties()1127     public Map<Object, Map<ResourceReference, ResourceValue>> getDefaultNamespacedProperties() {
1128         return getContext().getDefaultProperties();
1129     }
1130 
getDefaultStyles()1131     public Map<Object, String> getDefaultStyles() {
1132         Map<Object, String> defaultStyles = new IdentityHashMap<>();
1133         Map<Object, ResourceReference> namespacedStyles = getDefaultNamespacedStyles();
1134         for (Object key : namespacedStyles.keySet()) {
1135             ResourceReference style = namespacedStyles.get(key);
1136             defaultStyles.put(key, style.getQualifiedName());
1137         }
1138         return defaultStyles;
1139     }
1140 
getDefaultNamespacedStyles()1141     public Map<Object, ResourceReference> getDefaultNamespacedStyles() {
1142         return getContext().getDefaultNamespacedStyles();
1143     }
1144 
1145     @Nullable
getValidatorResult()1146     public ValidatorResult getValidatorResult() {
1147         return mValidatorResult;
1148     }
1149 
setValidatorResult(ValidatorResult result)1150     public void setValidatorResult(ValidatorResult result) {
1151         mValidatorResult = result;
1152     }
1153 
setScene(RenderSession session)1154     public void setScene(RenderSession session) {
1155         mScene = session;
1156     }
1157 
getSession()1158     public RenderSession getSession() {
1159         return mScene;
1160     }
1161 
dispose()1162     public void dispose() {
1163         try {
1164             boolean createdLooper = false;
1165             if (Looper.myLooper() == null) {
1166                 // Detaching the root view from the window will try to stop any running animations.
1167                 // The stop method checks that it can run in the looper so, if there is no current
1168                 // looper, we create a temporary one to complete the shutdown.
1169                 Bridge.prepareThread();
1170                 createdLooper = true;
1171             }
1172             AttachInfo_Accessor.detachFromWindow(mViewRoot);
1173             if (mCanvas != null) {
1174                 mCanvas.release();
1175                 mCanvas = null;
1176             }
1177             if (mViewInfoList != null) {
1178                 mViewInfoList.clear();
1179             }
1180             if (mSystemViewInfoList != null) {
1181                 mSystemViewInfoList.clear();
1182             }
1183             mImage = null;
1184             mViewRoot = null;
1185             mContentRoot = null;
1186             NinePatch_Delegate.clearCache();
1187 
1188             if (createdLooper) {
1189                 Choreographer_Delegate.dispose();
1190                 Bridge.cleanupThread();
1191             }
1192         } catch (Throwable t) {
1193             getContext().error("Error while disposing a RenderSession", t);
1194         }
1195     }
1196 }
1197