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