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