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