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