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