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