RenderSessionImpl.java revision 92d9c3fa220868d6641b5831b24a0a72af7c6751
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.annotations.NonNull; 20import com.android.annotations.Nullable; 21import com.android.ide.common.rendering.api.AdapterBinding; 22import com.android.ide.common.rendering.api.HardwareConfig; 23import com.android.ide.common.rendering.api.IAnimationListener; 24import com.android.ide.common.rendering.api.ILayoutPullParser; 25import com.android.ide.common.rendering.api.LayoutlibCallback; 26import com.android.ide.common.rendering.api.RenderResources; 27import com.android.ide.common.rendering.api.RenderSession; 28import com.android.ide.common.rendering.api.ResourceReference; 29import com.android.ide.common.rendering.api.ResourceValue; 30import com.android.ide.common.rendering.api.Result; 31import com.android.ide.common.rendering.api.Result.Status; 32import com.android.ide.common.rendering.api.SessionParams; 33import com.android.ide.common.rendering.api.SessionParams.RenderingMode; 34import com.android.ide.common.rendering.api.StyleResourceValue; 35import com.android.ide.common.rendering.api.ViewInfo; 36import com.android.ide.common.rendering.api.ViewType; 37import com.android.internal.util.XmlUtils; 38import com.android.internal.view.menu.ActionMenuItemView; 39import com.android.internal.view.menu.BridgeMenuItemImpl; 40import com.android.internal.view.menu.IconMenuItemView; 41import com.android.internal.view.menu.ListMenuItemView; 42import com.android.internal.view.menu.MenuItemImpl; 43import com.android.internal.view.menu.MenuView; 44import com.android.layoutlib.bridge.Bridge; 45import com.android.layoutlib.bridge.android.BridgeContext; 46import com.android.layoutlib.bridge.android.BridgeLayoutParamsMapAttributes; 47import com.android.layoutlib.bridge.android.BridgeXmlBlockParser; 48import com.android.layoutlib.bridge.android.RenderParamsFlags; 49import com.android.layoutlib.bridge.android.support.RecyclerViewUtil; 50import com.android.layoutlib.bridge.bars.AppCompatActionBar; 51import com.android.layoutlib.bridge.bars.BridgeActionBar; 52import com.android.layoutlib.bridge.bars.Config; 53import com.android.layoutlib.bridge.bars.FrameworkActionBar; 54import com.android.layoutlib.bridge.bars.NavigationBar; 55import com.android.layoutlib.bridge.bars.StatusBar; 56import com.android.layoutlib.bridge.bars.TitleBar; 57import com.android.layoutlib.bridge.impl.binding.FakeAdapter; 58import com.android.layoutlib.bridge.impl.binding.FakeExpandableAdapter; 59import com.android.resources.Density; 60import com.android.resources.ResourceType; 61import com.android.resources.ScreenOrientation; 62import com.android.util.Pair; 63 64import org.xmlpull.v1.XmlPullParserException; 65 66import android.animation.AnimationThread; 67import android.animation.Animator; 68import android.animation.AnimatorInflater; 69import android.animation.LayoutTransition; 70import android.animation.LayoutTransition.TransitionListener; 71import android.app.Fragment_Delegate; 72import android.graphics.Bitmap; 73import android.graphics.Bitmap_Delegate; 74import android.graphics.Canvas; 75import android.graphics.drawable.Drawable; 76import android.preference.Preference_Delegate; 77import android.util.DisplayMetrics; 78import android.util.TypedValue; 79import android.view.AttachInfo_Accessor; 80import android.view.BridgeInflater; 81import android.view.IWindowManager; 82import android.view.IWindowManagerImpl; 83import android.view.Surface; 84import android.view.View; 85import android.view.View.MeasureSpec; 86import android.view.ViewGroup; 87import android.view.ViewGroup.LayoutParams; 88import android.view.ViewGroup.MarginLayoutParams; 89import android.view.ViewParent; 90import android.view.WindowManagerGlobal_Delegate; 91import android.widget.AbsListView; 92import android.widget.AbsSpinner; 93import android.widget.ActionMenuView; 94import android.widget.AdapterView; 95import android.widget.ExpandableListView; 96import android.widget.FrameLayout; 97import android.widget.LinearLayout; 98import android.widget.ListView; 99import android.widget.QuickContactBadge; 100import android.widget.TabHost; 101import android.widget.TabHost.TabSpec; 102import android.widget.TabWidget; 103 104import java.awt.AlphaComposite; 105import java.awt.Color; 106import java.awt.Graphics2D; 107import java.awt.image.BufferedImage; 108import java.util.ArrayList; 109import java.util.List; 110import java.util.Map; 111 112import static com.android.ide.common.rendering.api.Result.Status.ERROR_ANIM_NOT_FOUND; 113import static com.android.ide.common.rendering.api.Result.Status.ERROR_INFLATION; 114import static com.android.ide.common.rendering.api.Result.Status.ERROR_NOT_INFLATED; 115import static com.android.ide.common.rendering.api.Result.Status.ERROR_UNKNOWN; 116import static com.android.ide.common.rendering.api.Result.Status.ERROR_VIEWGROUP_NO_CHILDREN; 117import static com.android.ide.common.rendering.api.Result.Status.SUCCESS; 118 119/** 120 * Class implementing the render session. 121 * <p/> 122 * A session is a stateful representation of a layout file. It is initialized with data coming 123 * through the {@link Bridge} API to inflate the layout. Further actions and rendering can then 124 * be done on the layout. 125 */ 126public class RenderSessionImpl extends RenderAction<SessionParams> { 127 128 private static final int DEFAULT_TITLE_BAR_HEIGHT = 25; 129 private static final int DEFAULT_STATUS_BAR_HEIGHT = 25; 130 131 // scene state 132 private RenderSession mScene; 133 private BridgeXmlBlockParser mBlockParser; 134 private BridgeInflater mInflater; 135 private ResourceValue mWindowBackground; 136 private ViewGroup mViewRoot; 137 private FrameLayout mContentRoot; 138 private Canvas mCanvas; 139 private int mMeasuredScreenWidth = -1; 140 private int mMeasuredScreenHeight = -1; 141 private boolean mIsAlphaChannelImage; 142 private boolean mWindowIsFloating; 143 private Boolean mIsThemeAppCompat; 144 145 private int mStatusBarSize; 146 private int mNavigationBarSize; 147 private int mNavigationBarOrientation = LinearLayout.HORIZONTAL; 148 private int mTitleBarSize; 149 private int mActionBarSize; 150 151 152 // information being returned through the API 153 private BufferedImage mImage; 154 private List<ViewInfo> mViewInfoList; 155 private List<ViewInfo> mSystemViewInfoList; 156 157 private static final class PostInflateException extends Exception { 158 private static final long serialVersionUID = 1L; 159 160 public PostInflateException(String message) { 161 super(message); 162 } 163 } 164 165 /** 166 * Creates a layout scene with all the information coming from the layout bridge API. 167 * <p> 168 * This <b>must</b> be followed by a call to {@link RenderSessionImpl#init(long)}, 169 * which act as a 170 * call to {@link RenderSessionImpl#acquire(long)} 171 * 172 * @see Bridge#createSession(SessionParams) 173 */ 174 public RenderSessionImpl(SessionParams params) { 175 super(new SessionParams(params)); 176 } 177 178 /** 179 * Initializes and acquires the scene, creating various Android objects such as context, 180 * inflater, and parser. 181 * 182 * @param timeout the time to wait if another rendering is happening. 183 * 184 * @return whether the scene was prepared 185 * 186 * @see #acquire(long) 187 * @see #release() 188 */ 189 @Override 190 public Result init(long timeout) { 191 Result result = super.init(timeout); 192 if (!result.isSuccess()) { 193 return result; 194 } 195 196 SessionParams params = getParams(); 197 BridgeContext context = getContext(); 198 199 200 RenderResources resources = getParams().getResources(); 201 DisplayMetrics metrics = getContext().getMetrics(); 202 203 // use default of true in case it's not found to use alpha by default 204 mIsAlphaChannelImage = getBooleanThemeValue(resources, "windowIsFloating", true, true); 205 // FIXME: Find out why both variables are taking the same value. 206 mWindowIsFloating = getBooleanThemeValue(resources, "windowIsFloating", true, true); 207 208 findBackground(resources); 209 findStatusBar(resources, metrics); 210 findActionBar(resources, metrics); 211 findNavigationBar(resources, metrics); 212 213 // FIXME: find those out, and possibly add them to the render params 214 boolean hasNavigationBar = true; 215 //noinspection ConstantConditions 216 IWindowManager iwm = new IWindowManagerImpl(getContext().getConfiguration(), 217 metrics, Surface.ROTATION_0, 218 hasNavigationBar); 219 WindowManagerGlobal_Delegate.setWindowManagerService(iwm); 220 221 // build the inflater and parser. 222 mInflater = new BridgeInflater(context, params.getLayoutlibCallback()); 223 context.setBridgeInflater(mInflater); 224 225 mBlockParser = new BridgeXmlBlockParser( 226 params.getLayoutDescription(), context, false /* platformResourceFlag */); 227 228 return SUCCESS.createResult(); 229 } 230 231 /** 232 * Inflates the layout. 233 * <p> 234 * {@link #acquire(long)} must have been called before this. 235 * 236 * @throws IllegalStateException if the current context is different than the one owned by 237 * the scene, or if {@link #init(long)} was not called. 238 */ 239 public Result inflate() { 240 checkLock(); 241 242 try { 243 244 SessionParams params = getParams(); 245 HardwareConfig hardwareConfig = params.getHardwareConfig(); 246 BridgeContext context = getContext(); 247 boolean isRtl = Bridge.isLocaleRtl(params.getLocale()); 248 int layoutDirection = isRtl ? View.LAYOUT_DIRECTION_RTL : View.LAYOUT_DIRECTION_LTR; 249 250 // the view group that receives the window background. 251 ViewGroup backgroundView; 252 253 if (mWindowIsFloating || params.isForceNoDecor()) { 254 backgroundView = mViewRoot = mContentRoot = new FrameLayout(context); 255 mViewRoot.setLayoutDirection(layoutDirection); 256 } else { 257 int simulatedPlatformVersion = params.getSimulatedPlatformVersion(); 258 if (hasSoftwareButtons() && mNavigationBarOrientation == LinearLayout.VERTICAL) { 259 /* 260 * This is a special case where the navigation bar is on the right. 261 +-------------------------------------------------+---+ 262 | Status bar (always) | | 263 +-------------------------------------------------+ | 264 | (Layout with background drawable) | | 265 | +---------------------------------------------+ | | 266 | | Title/Action bar (optional) | | | 267 | +---------------------------------------------+ | | 268 | | Content, vertical extending | | | 269 | | | | | 270 | +---------------------------------------------+ | | 271 +-------------------------------------------------+---+ 272 273 So we create a horizontal layout, with the nav bar on the right, 274 and the left part is the normal layout below without the nav bar at 275 the bottom 276 */ 277 LinearLayout topLayout = new LinearLayout(context); 278 topLayout.setLayoutDirection(layoutDirection); 279 mViewRoot = topLayout; 280 topLayout.setOrientation(LinearLayout.HORIZONTAL); 281 282 if (Config.showOnScreenNavBar(simulatedPlatformVersion)) { 283 try { 284 NavigationBar navigationBar = createNavigationBar(context, 285 hardwareConfig.getDensity(), isRtl, params.isRtlSupported(), 286 simulatedPlatformVersion); 287 topLayout.addView(navigationBar); 288 } catch (XmlPullParserException ignored) { 289 } 290 } 291 } 292 293 /* 294 * we're creating the following layout 295 * 296 +-------------------------------------------------+ 297 | Status bar (always) | 298 +-------------------------------------------------+ 299 | (Layout with background drawable) | 300 | +---------------------------------------------+ | 301 | | Title/Action bar (optional) | | 302 | +---------------------------------------------+ | 303 | | Content, vertical extending | | 304 | | | | 305 | +---------------------------------------------+ | 306 +-------------------------------------------------+ 307 | Navigation bar for soft buttons, maybe see above| 308 +-------------------------------------------------+ 309 310 */ 311 312 LinearLayout topLayout = new LinearLayout(context); 313 topLayout.setOrientation(LinearLayout.VERTICAL); 314 topLayout.setLayoutDirection(layoutDirection); 315 // if we don't already have a view root this is it 316 if (mViewRoot == null) { 317 mViewRoot = topLayout; 318 } else { 319 int topLayoutWidth = 320 params.getHardwareConfig().getScreenWidth() - mNavigationBarSize; 321 LinearLayout.LayoutParams layoutParams = new LinearLayout.LayoutParams( 322 topLayoutWidth, LayoutParams.MATCH_PARENT); 323 topLayout.setLayoutParams(layoutParams); 324 325 // this is the case of soft buttons + vertical bar. 326 // this top layout is the first layout in the horizontal layout. see above) 327 if (isRtl && params.isRtlSupported()) { 328 // If RTL is enabled, layoutlib will mirror the layouts. So, add the 329 // topLayout to the right of Navigation Bar and layoutlib will draw it 330 // to the left. 331 mViewRoot.addView(topLayout); 332 } else { 333 // Add the top layout to the left of the Navigation Bar. 334 mViewRoot.addView(topLayout, 0); 335 } 336 } 337 338 if (mStatusBarSize > 0) { 339 // system bar 340 try { 341 StatusBar statusBar = createStatusBar(context, hardwareConfig.getDensity(), 342 layoutDirection, params.isRtlSupported(), 343 simulatedPlatformVersion); 344 topLayout.addView(statusBar); 345 } catch (XmlPullParserException ignored) { 346 347 } 348 } 349 350 LinearLayout backgroundLayout = new LinearLayout(context); 351 backgroundView = backgroundLayout; 352 backgroundLayout.setOrientation(LinearLayout.VERTICAL); 353 LinearLayout.LayoutParams layoutParams = new LinearLayout.LayoutParams( 354 LayoutParams.MATCH_PARENT, 0); 355 layoutParams.weight = 1; 356 backgroundLayout.setLayoutParams(layoutParams); 357 topLayout.addView(backgroundLayout); 358 359 360 // if the theme says no title/action bar, then the size will be 0 361 if (mActionBarSize > 0) { 362 BridgeActionBar actionBar = createActionBar(context, params, backgroundLayout); 363 actionBar.createMenuPopup(); 364 mContentRoot = actionBar.getContentRoot(); 365 } else if (mTitleBarSize > 0) { 366 try { 367 TitleBar titleBar = createTitleBar(context, 368 params.getAppLabel(), 369 simulatedPlatformVersion); 370 backgroundLayout.addView(titleBar); 371 } catch (XmlPullParserException ignored) { 372 373 } 374 } 375 376 // content frame 377 if (mContentRoot == null) { 378 mContentRoot = new FrameLayout(context); 379 layoutParams = new LinearLayout.LayoutParams( 380 LayoutParams.MATCH_PARENT, 0); 381 layoutParams.weight = 1; 382 mContentRoot.setLayoutParams(layoutParams); 383 backgroundLayout.addView(mContentRoot); 384 } 385 386 if (Config.showOnScreenNavBar(simulatedPlatformVersion) && 387 mNavigationBarOrientation == LinearLayout.HORIZONTAL && 388 mNavigationBarSize > 0) { 389 // system bar 390 try { 391 NavigationBar navigationBar = createNavigationBar(context, 392 hardwareConfig.getDensity(), isRtl, params.isRtlSupported(), 393 simulatedPlatformVersion); 394 topLayout.addView(navigationBar); 395 } catch (XmlPullParserException ignored) { 396 397 } 398 } 399 } 400 401 402 // Sets the project callback (custom view loader) to the fragment delegate so that 403 // it can instantiate the custom Fragment. 404 Fragment_Delegate.setLayoutlibCallback(params.getLayoutlibCallback()); 405 406 String rootTag = params.getFlag(RenderParamsFlags.FLAG_KEY_ROOT_TAG); 407 boolean isPreference = "PreferenceScreen".equals(rootTag); 408 View view; 409 if (isPreference) { 410 view = Preference_Delegate.inflatePreference(getContext(), mBlockParser, 411 mContentRoot); 412 } else { 413 view = mInflater.inflate(mBlockParser, mContentRoot); 414 } 415 416 // done with the parser, pop it. 417 context.popParser(); 418 419 Fragment_Delegate.setLayoutlibCallback(null); 420 421 // set the AttachInfo on the root view. 422 AttachInfo_Accessor.setAttachInfo(mViewRoot); 423 424 // post-inflate process. For now this supports TabHost/TabWidget 425 postInflateProcess(view, params.getLayoutlibCallback(), isPreference ? view : null); 426 427 // get the background drawable 428 if (mWindowBackground != null) { 429 Drawable d = ResourceHelper.getDrawable(mWindowBackground, context); 430 backgroundView.setBackground(d); 431 } 432 433 return SUCCESS.createResult(); 434 } catch (PostInflateException e) { 435 return ERROR_INFLATION.createResult(e.getMessage(), e); 436 } catch (Throwable e) { 437 // get the real cause of the exception. 438 Throwable t = e; 439 while (t.getCause() != null) { 440 t = t.getCause(); 441 } 442 443 return ERROR_INFLATION.createResult(t.getMessage(), t); 444 } 445 } 446 447 /** 448 * Renders the scene. 449 * <p> 450 * {@link #acquire(long)} must have been called before this. 451 * 452 * @param freshRender whether the render is a new one and should erase the existing bitmap (in 453 * the case where bitmaps are reused). This is typically needed when not playing 454 * animations.) 455 * 456 * @throws IllegalStateException if the current context is different than the one owned by 457 * the scene, or if {@link #acquire(long)} was not called. 458 * 459 * @see SessionParams#getRenderingMode() 460 * @see RenderSession#render(long) 461 */ 462 public Result render(boolean freshRender) { 463 checkLock(); 464 465 SessionParams params = getParams(); 466 467 try { 468 if (mViewRoot == null) { 469 return ERROR_NOT_INFLATED.createResult(); 470 } 471 472 RenderingMode renderingMode = params.getRenderingMode(); 473 HardwareConfig hardwareConfig = params.getHardwareConfig(); 474 475 // only do the screen measure when needed. 476 boolean newRenderSize = false; 477 if (mMeasuredScreenWidth == -1) { 478 newRenderSize = true; 479 mMeasuredScreenWidth = hardwareConfig.getScreenWidth(); 480 mMeasuredScreenHeight = hardwareConfig.getScreenHeight(); 481 482 if (renderingMode != RenderingMode.NORMAL) { 483 int widthMeasureSpecMode = renderingMode.isHorizExpand() ? 484 MeasureSpec.UNSPECIFIED // this lets us know the actual needed size 485 : MeasureSpec.EXACTLY; 486 int heightMeasureSpecMode = renderingMode.isVertExpand() ? 487 MeasureSpec.UNSPECIFIED // this lets us know the actual needed size 488 : MeasureSpec.EXACTLY; 489 490 // We used to compare the measured size of the content to the screen size but 491 // this does not work anymore due to the 2 following issues: 492 // - If the content is in a decor (system bar, title/action bar), the root view 493 // will not resize even with the UNSPECIFIED because of the embedded layout. 494 // - If there is no decor, but a dialog frame, then the dialog padding prevents 495 // comparing the size of the content to the screen frame (as it would not 496 // take into account the dialog padding). 497 498 // The solution is to first get the content size in a normal rendering, inside 499 // the decor or the dialog padding. 500 // Then measure only the content with UNSPECIFIED to see the size difference 501 // and apply this to the screen size. 502 503 // first measure the full layout, with EXACTLY to get the size of the 504 // content as it is inside the decor/dialog 505 @SuppressWarnings("deprecation") 506 Pair<Integer, Integer> exactMeasure = measureView( 507 mViewRoot, mContentRoot.getChildAt(0), 508 mMeasuredScreenWidth, MeasureSpec.EXACTLY, 509 mMeasuredScreenHeight, MeasureSpec.EXACTLY); 510 511 // now measure the content only using UNSPECIFIED (where applicable, based on 512 // the rendering mode). This will give us the size the content needs. 513 @SuppressWarnings("deprecation") 514 Pair<Integer, Integer> result = measureView( 515 mContentRoot, mContentRoot.getChildAt(0), 516 mMeasuredScreenWidth, widthMeasureSpecMode, 517 mMeasuredScreenHeight, heightMeasureSpecMode); 518 519 // now look at the difference and add what is needed. 520 if (renderingMode.isHorizExpand()) { 521 int measuredWidth = exactMeasure.getFirst(); 522 int neededWidth = result.getFirst(); 523 if (neededWidth > measuredWidth) { 524 mMeasuredScreenWidth += neededWidth - measuredWidth; 525 } 526 if (mMeasuredScreenWidth < measuredWidth) { 527 // If the screen width is less than the exact measured width, 528 // expand to match. 529 mMeasuredScreenWidth = measuredWidth; 530 } 531 } 532 533 if (renderingMode.isVertExpand()) { 534 int measuredHeight = exactMeasure.getSecond(); 535 int neededHeight = result.getSecond(); 536 if (neededHeight > measuredHeight) { 537 mMeasuredScreenHeight += neededHeight - measuredHeight; 538 } 539 if (mMeasuredScreenHeight < measuredHeight) { 540 // If the screen height is less than the exact measured height, 541 // expand to match. 542 mMeasuredScreenHeight = measuredHeight; 543 } 544 } 545 } 546 } 547 548 // measure again with the size we need 549 // This must always be done before the call to layout 550 measureView(mViewRoot, null /*measuredView*/, 551 mMeasuredScreenWidth, MeasureSpec.EXACTLY, 552 mMeasuredScreenHeight, MeasureSpec.EXACTLY); 553 554 // now do the layout. 555 mViewRoot.layout(0, 0, mMeasuredScreenWidth, mMeasuredScreenHeight); 556 557 if (params.isLayoutOnly()) { 558 // delete the canvas and image to reset them on the next full rendering 559 mImage = null; 560 mCanvas = null; 561 } else { 562 AttachInfo_Accessor.dispatchOnPreDraw(mViewRoot); 563 564 // draw the views 565 // create the BufferedImage into which the layout will be rendered. 566 boolean newImage = false; 567 568 // When disableBitmapCaching is true, we do not reuse mImage and 569 // we create a new one in every render. 570 // This is useful when mImage is just a wrapper of Graphics2D so 571 // it doesn't get cached. 572 boolean disableBitmapCaching = Boolean.TRUE.equals(params.getFlag( 573 RenderParamsFlags.FLAG_KEY_DISABLE_BITMAP_CACHING)); 574 if (newRenderSize || mCanvas == null || disableBitmapCaching) { 575 if (params.getImageFactory() != null) { 576 mImage = params.getImageFactory().getImage( 577 mMeasuredScreenWidth, 578 mMeasuredScreenHeight); 579 } else { 580 mImage = new BufferedImage( 581 mMeasuredScreenWidth, 582 mMeasuredScreenHeight, 583 BufferedImage.TYPE_INT_ARGB); 584 newImage = true; 585 } 586 587 if (params.isBgColorOverridden()) { 588 // since we override the content, it's the same as if it was a new image. 589 newImage = true; 590 Graphics2D gc = mImage.createGraphics(); 591 gc.setColor(new Color(params.getOverrideBgColor(), true)); 592 gc.setComposite(AlphaComposite.Src); 593 gc.fillRect(0, 0, mMeasuredScreenWidth, mMeasuredScreenHeight); 594 gc.dispose(); 595 } 596 597 // create an Android bitmap around the BufferedImage 598 Bitmap bitmap = Bitmap_Delegate.createBitmap(mImage, 599 true /*isMutable*/, hardwareConfig.getDensity()); 600 601 if (mCanvas == null) { 602 // create a Canvas around the Android bitmap 603 mCanvas = new Canvas(bitmap); 604 } else { 605 mCanvas.setBitmap(bitmap); 606 } 607 mCanvas.setDensity(hardwareConfig.getDensity().getDpiValue()); 608 } 609 610 if (freshRender && !newImage) { 611 Graphics2D gc = mImage.createGraphics(); 612 gc.setComposite(AlphaComposite.Src); 613 614 gc.setColor(new Color(0x00000000, true)); 615 gc.fillRect(0, 0, 616 mMeasuredScreenWidth, mMeasuredScreenHeight); 617 618 // done 619 gc.dispose(); 620 } 621 622 mViewRoot.draw(mCanvas); 623 } 624 625 mSystemViewInfoList = visitAllChildren(mViewRoot, 0, params.getExtendedViewInfoMode(), 626 false); 627 628 // success! 629 return SUCCESS.createResult(); 630 } catch (Throwable e) { 631 // get the real cause of the exception. 632 Throwable t = e; 633 while (t.getCause() != null) { 634 t = t.getCause(); 635 } 636 637 return ERROR_UNKNOWN.createResult(t.getMessage(), t); 638 } 639 } 640 641 /** 642 * Executes {@link View#measure(int, int)} on a given view with the given parameters (used 643 * to create measure specs with {@link MeasureSpec#makeMeasureSpec(int, int)}. 644 * 645 * if <var>measuredView</var> is non null, the method returns a {@link Pair} of (width, height) 646 * for the view (using {@link View#getMeasuredWidth()} and {@link View#getMeasuredHeight()}). 647 * 648 * @param viewToMeasure the view on which to execute measure(). 649 * @param measuredView if non null, the view to query for its measured width/height. 650 * @param width the width to use in the MeasureSpec. 651 * @param widthMode the MeasureSpec mode to use for the width. 652 * @param height the height to use in the MeasureSpec. 653 * @param heightMode the MeasureSpec mode to use for the height. 654 * @return the measured width/height if measuredView is non-null, null otherwise. 655 */ 656 @SuppressWarnings("deprecation") // For the use of Pair 657 private Pair<Integer, Integer> measureView(ViewGroup viewToMeasure, View measuredView, 658 int width, int widthMode, int height, int heightMode) { 659 int w_spec = MeasureSpec.makeMeasureSpec(width, widthMode); 660 int h_spec = MeasureSpec.makeMeasureSpec(height, heightMode); 661 viewToMeasure.measure(w_spec, h_spec); 662 663 if (measuredView != null) { 664 return Pair.of(measuredView.getMeasuredWidth(), measuredView.getMeasuredHeight()); 665 } 666 667 return null; 668 } 669 670 /** 671 * Animate an object 672 * <p> 673 * {@link #acquire(long)} must have been called before this. 674 * 675 * @throws IllegalStateException if the current context is different than the one owned by 676 * the scene, or if {@link #acquire(long)} was not called. 677 * 678 * @see RenderSession#animate(Object, String, boolean, IAnimationListener) 679 */ 680 public Result animate(Object targetObject, String animationName, 681 boolean isFrameworkAnimation, IAnimationListener listener) { 682 checkLock(); 683 684 BridgeContext context = getContext(); 685 686 // find the animation file. 687 ResourceValue animationResource; 688 int animationId = 0; 689 if (isFrameworkAnimation) { 690 animationResource = context.getRenderResources().getFrameworkResource( 691 ResourceType.ANIMATOR, animationName); 692 if (animationResource != null) { 693 animationId = Bridge.getResourceId(ResourceType.ANIMATOR, animationName); 694 } 695 } else { 696 animationResource = context.getRenderResources().getProjectResource( 697 ResourceType.ANIMATOR, animationName); 698 if (animationResource != null) { 699 animationId = context.getLayoutlibCallback().getResourceId( 700 ResourceType.ANIMATOR, animationName); 701 } 702 } 703 704 if (animationResource != null) { 705 try { 706 Animator anim = AnimatorInflater.loadAnimator(context, animationId); 707 if (anim != null) { 708 anim.setTarget(targetObject); 709 710 new PlayAnimationThread(anim, this, animationName, listener).start(); 711 712 return SUCCESS.createResult(); 713 } 714 } catch (Exception e) { 715 // get the real cause of the exception. 716 Throwable t = e; 717 while (t.getCause() != null) { 718 t = t.getCause(); 719 } 720 721 return ERROR_UNKNOWN.createResult(t.getMessage(), t); 722 } 723 } 724 725 return ERROR_ANIM_NOT_FOUND.createResult(); 726 } 727 728 /** 729 * Insert a new child into an existing parent. 730 * <p> 731 * {@link #acquire(long)} must have been called before this. 732 * 733 * @throws IllegalStateException if the current context is different than the one owned by 734 * the scene, or if {@link #acquire(long)} was not called. 735 * 736 * @see RenderSession#insertChild(Object, ILayoutPullParser, int, IAnimationListener) 737 */ 738 public Result insertChild(final ViewGroup parentView, ILayoutPullParser childXml, 739 final int index, IAnimationListener listener) { 740 checkLock(); 741 742 BridgeContext context = getContext(); 743 744 // create a block parser for the XML 745 BridgeXmlBlockParser blockParser = new BridgeXmlBlockParser( 746 childXml, context, false /* platformResourceFlag */); 747 748 // inflate the child without adding it to the root since we want to control where it'll 749 // get added. We do pass the parentView however to ensure that the layoutParams will 750 // be created correctly. 751 final View child = mInflater.inflate(blockParser, parentView, false /*attachToRoot*/); 752 blockParser.ensurePopped(); 753 754 invalidateRenderingSize(); 755 756 if (listener != null) { 757 new AnimationThread(this, "insertChild", listener) { 758 759 @Override 760 public Result preAnimation() { 761 parentView.setLayoutTransition(new LayoutTransition()); 762 return addView(parentView, child, index); 763 } 764 765 @Override 766 public void postAnimation() { 767 parentView.setLayoutTransition(null); 768 } 769 }.start(); 770 771 // always return success since the real status will come through the listener. 772 return SUCCESS.createResult(child); 773 } 774 775 // add it to the parentView in the correct location 776 Result result = addView(parentView, child, index); 777 if (!result.isSuccess()) { 778 return result; 779 } 780 781 result = render(false /*freshRender*/); 782 if (result.isSuccess()) { 783 result = result.getCopyWithData(child); 784 } 785 786 return result; 787 } 788 789 /** 790 * Adds a given view to a given parent at a given index. 791 * 792 * @param parent the parent to receive the view 793 * @param view the view to add to the parent 794 * @param index the index where to do the add. 795 * 796 * @return a Result with {@link Status#SUCCESS} or 797 * {@link Status#ERROR_VIEWGROUP_NO_CHILDREN} if the given parent doesn't support 798 * adding views. 799 */ 800 private Result addView(ViewGroup parent, View view, int index) { 801 try { 802 parent.addView(view, index); 803 return SUCCESS.createResult(); 804 } catch (UnsupportedOperationException e) { 805 // looks like this is a view class that doesn't support children manipulation! 806 return ERROR_VIEWGROUP_NO_CHILDREN.createResult(); 807 } 808 } 809 810 /** 811 * Moves a view to a new parent at a given location 812 * <p> 813 * {@link #acquire(long)} must have been called before this. 814 * 815 * @throws IllegalStateException if the current context is different than the one owned by 816 * the scene, or if {@link #acquire(long)} was not called. 817 * 818 * @see RenderSession#moveChild(Object, Object, int, Map, IAnimationListener) 819 */ 820 public Result moveChild(final ViewGroup newParentView, final View childView, final int index, 821 Map<String, String> layoutParamsMap, final IAnimationListener listener) { 822 checkLock(); 823 824 invalidateRenderingSize(); 825 826 LayoutParams layoutParams = null; 827 if (layoutParamsMap != null) { 828 // need to create a new LayoutParams object for the new parent. 829 layoutParams = newParentView.generateLayoutParams( 830 new BridgeLayoutParamsMapAttributes(layoutParamsMap)); 831 } 832 833 // get the current parent of the view that needs to be moved. 834 final ViewGroup previousParent = (ViewGroup) childView.getParent(); 835 836 if (listener != null) { 837 final LayoutParams params = layoutParams; 838 839 // there is no support for animating views across layouts, so in case the new and old 840 // parent views are different we fake the animation through a no animation thread. 841 if (previousParent != newParentView) { 842 new Thread("not animated moveChild") { 843 @Override 844 public void run() { 845 Result result = moveView(previousParent, newParentView, childView, index, 846 params); 847 if (!result.isSuccess()) { 848 listener.done(result); 849 } 850 851 // ready to do the work, acquire the scene. 852 result = acquire(250); 853 if (!result.isSuccess()) { 854 listener.done(result); 855 return; 856 } 857 858 try { 859 result = render(false /*freshRender*/); 860 if (result.isSuccess()) { 861 listener.onNewFrame(RenderSessionImpl.this.getSession()); 862 } 863 } finally { 864 release(); 865 } 866 867 listener.done(result); 868 } 869 }.start(); 870 } else { 871 new AnimationThread(this, "moveChild", listener) { 872 873 @Override 874 public Result preAnimation() { 875 // set up the transition for the parent. 876 LayoutTransition transition = new LayoutTransition(); 877 previousParent.setLayoutTransition(transition); 878 879 // tweak the animation durations and start delays (to match the duration of 880 // animation playing just before). 881 // Note: Cannot user Animation.setDuration() directly. Have to set it 882 // on the LayoutTransition. 883 transition.setDuration(LayoutTransition.DISAPPEARING, 100); 884 // CHANGE_DISAPPEARING plays after DISAPPEARING 885 transition.setStartDelay(LayoutTransition.CHANGE_DISAPPEARING, 100); 886 887 transition.setDuration(LayoutTransition.CHANGE_DISAPPEARING, 100); 888 889 transition.setDuration(LayoutTransition.CHANGE_APPEARING, 100); 890 // CHANGE_APPEARING plays after CHANGE_APPEARING 891 transition.setStartDelay(LayoutTransition.APPEARING, 100); 892 893 transition.setDuration(LayoutTransition.APPEARING, 100); 894 895 return moveView(previousParent, newParentView, childView, index, params); 896 } 897 898 @Override 899 public void postAnimation() { 900 previousParent.setLayoutTransition(null); 901 newParentView.setLayoutTransition(null); 902 } 903 }.start(); 904 } 905 906 // always return success since the real status will come through the listener. 907 return SUCCESS.createResult(layoutParams); 908 } 909 910 Result result = moveView(previousParent, newParentView, childView, index, layoutParams); 911 if (!result.isSuccess()) { 912 return result; 913 } 914 915 result = render(false /*freshRender*/); 916 if (layoutParams != null && result.isSuccess()) { 917 result = result.getCopyWithData(layoutParams); 918 } 919 920 return result; 921 } 922 923 /** 924 * Moves a View from its current parent to a new given parent at a new given location, with 925 * an optional new {@link LayoutParams} instance 926 * 927 * @param previousParent the previous parent, still owning the child at the time of the call. 928 * @param newParent the new parent 929 * @param movedView the view to move 930 * @param index the new location in the new parent 931 * @param params an option (can be null) {@link LayoutParams} instance. 932 * 933 * @return a Result with {@link Status#SUCCESS} or 934 * {@link Status#ERROR_VIEWGROUP_NO_CHILDREN} if the given parent doesn't support 935 * adding views. 936 */ 937 private Result moveView(ViewGroup previousParent, final ViewGroup newParent, 938 final View movedView, final int index, final LayoutParams params) { 939 try { 940 // check if there is a transition on the previousParent. 941 LayoutTransition previousTransition = previousParent.getLayoutTransition(); 942 if (previousTransition != null) { 943 // in this case there is an animation. This means we have to wait for the child's 944 // parent reference to be null'ed out so that we can add it to the new parent. 945 // It is technically removed right before the DISAPPEARING animation is done (if 946 // the animation of this type is not null, otherwise it's after which is impossible 947 // to handle). 948 // Because there is no move animation, if the new parent is the same as the old 949 // parent, we need to wait until the CHANGE_DISAPPEARING animation is done before 950 // adding the child or the child will appear in its new location before the 951 // other children have made room for it. 952 953 // add a listener to the transition to be notified of the actual removal. 954 previousTransition.addTransitionListener(new TransitionListener() { 955 private int mChangeDisappearingCount = 0; 956 957 @Override 958 public void startTransition(LayoutTransition transition, ViewGroup container, 959 View view, int transitionType) { 960 if (transitionType == LayoutTransition.CHANGE_DISAPPEARING) { 961 mChangeDisappearingCount++; 962 } 963 } 964 965 @Override 966 public void endTransition(LayoutTransition transition, ViewGroup container, 967 View view, int transitionType) { 968 if (transitionType == LayoutTransition.CHANGE_DISAPPEARING) { 969 mChangeDisappearingCount--; 970 } 971 972 if (transitionType == LayoutTransition.CHANGE_DISAPPEARING && 973 mChangeDisappearingCount == 0) { 974 // add it to the parentView in the correct location 975 if (params != null) { 976 newParent.addView(movedView, index, params); 977 } else { 978 newParent.addView(movedView, index); 979 } 980 } 981 } 982 }); 983 984 // remove the view from the current parent. 985 previousParent.removeView(movedView); 986 987 // and return since adding the view to the new parent is done in the listener. 988 return SUCCESS.createResult(); 989 } else { 990 // standard code with no animation. pretty simple. 991 previousParent.removeView(movedView); 992 993 // add it to the parentView in the correct location 994 if (params != null) { 995 newParent.addView(movedView, index, params); 996 } else { 997 newParent.addView(movedView, index); 998 } 999 1000 return SUCCESS.createResult(); 1001 } 1002 } catch (UnsupportedOperationException e) { 1003 // looks like this is a view class that doesn't support children manipulation! 1004 return ERROR_VIEWGROUP_NO_CHILDREN.createResult(); 1005 } 1006 } 1007 1008 /** 1009 * Removes a child from its current parent. 1010 * <p> 1011 * {@link #acquire(long)} must have been called before this. 1012 * 1013 * @throws IllegalStateException if the current context is different than the one owned by 1014 * the scene, or if {@link #acquire(long)} was not called. 1015 * 1016 * @see RenderSession#removeChild(Object, IAnimationListener) 1017 */ 1018 public Result removeChild(final View childView, IAnimationListener listener) { 1019 checkLock(); 1020 1021 invalidateRenderingSize(); 1022 1023 final ViewGroup parent = (ViewGroup) childView.getParent(); 1024 1025 if (listener != null) { 1026 new AnimationThread(this, "moveChild", listener) { 1027 1028 @Override 1029 public Result preAnimation() { 1030 parent.setLayoutTransition(new LayoutTransition()); 1031 return removeView(parent, childView); 1032 } 1033 1034 @Override 1035 public void postAnimation() { 1036 parent.setLayoutTransition(null); 1037 } 1038 }.start(); 1039 1040 // always return success since the real status will come through the listener. 1041 return SUCCESS.createResult(); 1042 } 1043 1044 Result result = removeView(parent, childView); 1045 if (!result.isSuccess()) { 1046 return result; 1047 } 1048 1049 return render(false /*freshRender*/); 1050 } 1051 1052 /** 1053 * Removes a given view from its current parent. 1054 * 1055 * @param view the view to remove from its parent 1056 * 1057 * @return a Result with {@link Status#SUCCESS} or 1058 * {@link Status#ERROR_VIEWGROUP_NO_CHILDREN} if the given parent doesn't support 1059 * adding views. 1060 */ 1061 private Result removeView(ViewGroup parent, View view) { 1062 try { 1063 parent.removeView(view); 1064 return SUCCESS.createResult(); 1065 } catch (UnsupportedOperationException e) { 1066 // looks like this is a view class that doesn't support children manipulation! 1067 return ERROR_VIEWGROUP_NO_CHILDREN.createResult(); 1068 } 1069 } 1070 1071 1072 private void findBackground(RenderResources resources) { 1073 if (!getParams().isBgColorOverridden()) { 1074 mWindowBackground = resources.findItemInTheme("windowBackground", 1075 true /*isFrameworkAttr*/); 1076 if (mWindowBackground != null) { 1077 mWindowBackground = resources.resolveResValue(mWindowBackground); 1078 } 1079 } 1080 } 1081 1082 private boolean hasSoftwareButtons() { 1083 return getParams().getHardwareConfig().hasSoftwareButtons(); 1084 } 1085 1086 private void findStatusBar(RenderResources resources, DisplayMetrics metrics) { 1087 boolean windowFullscreen = getBooleanThemeValue(resources, 1088 "windowFullscreen", false, true); 1089 1090 if (!windowFullscreen && !mWindowIsFloating) { 1091 // default value 1092 mStatusBarSize = DEFAULT_STATUS_BAR_HEIGHT; 1093 1094 // get the real value 1095 ResourceValue value = resources.getFrameworkResource(ResourceType.DIMEN, 1096 "status_bar_height"); 1097 1098 if (value != null) { 1099 TypedValue typedValue = ResourceHelper.getValue("status_bar_height", 1100 value.getValue(), true /*requireUnit*/); 1101 if (typedValue != null) { 1102 // compute the pixel value based on the display metrics 1103 mStatusBarSize = (int)typedValue.getDimension(metrics); 1104 } 1105 } 1106 } 1107 } 1108 1109 private void findActionBar(RenderResources resources, DisplayMetrics metrics) { 1110 if (mWindowIsFloating) { 1111 return; 1112 } 1113 1114 boolean windowActionBar = getBooleanThemeValue(resources, 1115 "windowActionBar", true, !isThemeAppCompat(resources)); 1116 1117 // if there's a value and it's false (default is true) 1118 if (windowActionBar) { 1119 1120 // default size of the window title bar 1121 mActionBarSize = DEFAULT_TITLE_BAR_HEIGHT; 1122 1123 // get value from the theme. 1124 ResourceValue value = resources.findItemInTheme("actionBarSize", 1125 true /*isFrameworkAttr*/); 1126 1127 // resolve it 1128 value = resources.resolveResValue(value); 1129 1130 if (value != null) { 1131 // get the numerical value, if available 1132 TypedValue typedValue = ResourceHelper.getValue("actionBarSize", value.getValue(), 1133 true /*requireUnit*/); 1134 if (typedValue != null) { 1135 // compute the pixel value based on the display metrics 1136 mActionBarSize = (int)typedValue.getDimension(metrics); 1137 } 1138 } 1139 } else { 1140 // action bar overrides title bar so only look for this one if action bar is hidden 1141 boolean windowNoTitle = getBooleanThemeValue(resources, "windowNoTitle", false, true); 1142 1143 if (!windowNoTitle) { 1144 1145 // default size of the window title bar 1146 mTitleBarSize = DEFAULT_TITLE_BAR_HEIGHT; 1147 1148 // get value from the theme. 1149 ResourceValue value = resources.findItemInTheme("windowTitleSize", 1150 true /*isFrameworkAttr*/); 1151 1152 // resolve it 1153 value = resources.resolveResValue(value); 1154 1155 if (value != null) { 1156 // get the numerical value, if available 1157 TypedValue typedValue = ResourceHelper.getValue("windowTitleSize", 1158 value.getValue(), true /*requireUnit*/); 1159 if (typedValue != null) { 1160 // compute the pixel value based on the display metrics 1161 mTitleBarSize = (int)typedValue.getDimension(metrics); 1162 } 1163 } 1164 } 1165 1166 } 1167 } 1168 1169 private void findNavigationBar(RenderResources resources, DisplayMetrics metrics) { 1170 if (hasSoftwareButtons() && !mWindowIsFloating) { 1171 1172 // default value 1173 mNavigationBarSize = 48; // ?? 1174 1175 HardwareConfig hardwareConfig = getParams().getHardwareConfig(); 1176 1177 boolean barOnBottom = true; 1178 1179 if (hardwareConfig.getOrientation() == ScreenOrientation.LANDSCAPE) { 1180 // compute the dp of the screen. 1181 int shortSize = hardwareConfig.getScreenHeight(); 1182 1183 // compute in dp 1184 int shortSizeDp = shortSize * DisplayMetrics.DENSITY_DEFAULT / 1185 hardwareConfig.getDensity().getDpiValue(); 1186 1187 // 0-599dp: "phone" UI with bar on the side 1188 // 600+dp: "tablet" UI with bar on the bottom 1189 barOnBottom = shortSizeDp >= 600; 1190 } 1191 1192 if (barOnBottom) { 1193 mNavigationBarOrientation = LinearLayout.HORIZONTAL; 1194 } else { 1195 mNavigationBarOrientation = LinearLayout.VERTICAL; 1196 } 1197 1198 // get the real value 1199 ResourceValue value = resources.getFrameworkResource(ResourceType.DIMEN, 1200 barOnBottom ? "navigation_bar_height" : "navigation_bar_width"); 1201 1202 if (value != null) { 1203 TypedValue typedValue = ResourceHelper.getValue("navigation_bar_height", 1204 value.getValue(), true /*requireUnit*/); 1205 if (typedValue != null) { 1206 // compute the pixel value based on the display metrics 1207 mNavigationBarSize = (int)typedValue.getDimension(metrics); 1208 } 1209 } 1210 } 1211 } 1212 1213 private boolean isThemeAppCompat(RenderResources resources) { 1214 // Ideally, we should check if the corresponding activity extends 1215 // android.support.v7.app.ActionBarActivity, and not care about the theme name at all. 1216 if (mIsThemeAppCompat == null) { 1217 StyleResourceValue defaultTheme = resources.getDefaultTheme(); 1218 // We can't simply check for parent using resources.themeIsParentOf() since the 1219 // inheritance structure isn't really what one would expect. The first common parent 1220 // between Theme.AppCompat.Light and Theme.AppCompat is Theme.Material (for v21). 1221 boolean isThemeAppCompat = false; 1222 for (int i = 0; i < 50; i++) { 1223 if (defaultTheme == null) { 1224 break; 1225 } 1226 // for loop ensures that we don't run into cyclic theme inheritance. 1227 if (defaultTheme.getName().startsWith("Theme.AppCompat")) { 1228 isThemeAppCompat = true; 1229 break; 1230 } 1231 defaultTheme = resources.getParent(defaultTheme); 1232 } 1233 mIsThemeAppCompat = isThemeAppCompat; 1234 } 1235 return mIsThemeAppCompat; 1236 } 1237 1238 /** 1239 * Looks for an attribute in the current theme. 1240 * 1241 * @param resources the render resources 1242 * @param name the name of the attribute 1243 * @param defaultValue the default value. 1244 * @param isFrameworkAttr if the attribute is in android namespace 1245 * @return the value of the attribute or the default one if not found. 1246 */ 1247 private boolean getBooleanThemeValue(RenderResources resources, 1248 String name, boolean defaultValue, boolean isFrameworkAttr) { 1249 1250 ResourceValue value = resources.findItemInTheme(name, isFrameworkAttr); 1251 1252 // because it may reference something else, we resolve it. 1253 value = resources.resolveResValue(value); 1254 1255 // if there's no value, return the default. 1256 if (value == null || value.getValue() == null) { 1257 return defaultValue; 1258 } 1259 1260 return XmlUtils.convertValueToBoolean(value.getValue(), defaultValue); 1261 } 1262 1263 /** 1264 * Post process on a view hierarchy that was just inflated. 1265 * <p/> 1266 * At the moment this only supports TabHost: If {@link TabHost} is detected, look for the 1267 * {@link TabWidget}, and the corresponding {@link FrameLayout} and make new tabs automatically 1268 * based on the content of the {@link FrameLayout}. 1269 * @param view the root view to process. 1270 * @param layoutlibCallback callback to the project. 1271 * @param skip the view and it's children are not processed. 1272 */ 1273 @SuppressWarnings("deprecation") // For the use of Pair 1274 private void postInflateProcess(View view, LayoutlibCallback layoutlibCallback, View skip) 1275 throws PostInflateException { 1276 if (view == skip) { 1277 return; 1278 } 1279 if (view instanceof TabHost) { 1280 setupTabHost((TabHost) view, layoutlibCallback); 1281 } else if (view instanceof QuickContactBadge) { 1282 QuickContactBadge badge = (QuickContactBadge) view; 1283 badge.setImageToDefault(); 1284 } else if (view instanceof AdapterView<?>) { 1285 // get the view ID. 1286 int id = view.getId(); 1287 1288 BridgeContext context = getContext(); 1289 1290 // get a ResourceReference from the integer ID. 1291 ResourceReference listRef = context.resolveId(id); 1292 1293 if (listRef != null) { 1294 SessionParams params = getParams(); 1295 AdapterBinding binding = params.getAdapterBindings().get(listRef); 1296 1297 // if there was no adapter binding, trying to get it from the call back. 1298 if (binding == null) { 1299 binding = layoutlibCallback.getAdapterBinding( 1300 listRef, context.getViewKey(view), view); 1301 } 1302 1303 if (binding != null) { 1304 1305 if (view instanceof AbsListView) { 1306 if ((binding.getFooterCount() > 0 || binding.getHeaderCount() > 0) && 1307 view instanceof ListView) { 1308 ListView list = (ListView) view; 1309 1310 boolean skipCallbackParser = false; 1311 1312 int count = binding.getHeaderCount(); 1313 for (int i = 0; i < count; i++) { 1314 Pair<View, Boolean> pair = context.inflateView( 1315 binding.getHeaderAt(i), 1316 list, false, skipCallbackParser); 1317 if (pair.getFirst() != null) { 1318 list.addHeaderView(pair.getFirst()); 1319 } 1320 1321 skipCallbackParser |= pair.getSecond(); 1322 } 1323 1324 count = binding.getFooterCount(); 1325 for (int i = 0; i < count; i++) { 1326 Pair<View, Boolean> pair = context.inflateView( 1327 binding.getFooterAt(i), 1328 list, false, skipCallbackParser); 1329 if (pair.getFirst() != null) { 1330 list.addFooterView(pair.getFirst()); 1331 } 1332 1333 skipCallbackParser |= pair.getSecond(); 1334 } 1335 } 1336 1337 if (view instanceof ExpandableListView) { 1338 ((ExpandableListView) view).setAdapter( 1339 new FakeExpandableAdapter(listRef, binding, layoutlibCallback)); 1340 } else { 1341 ((AbsListView) view).setAdapter( 1342 new FakeAdapter(listRef, binding, layoutlibCallback)); 1343 } 1344 } else if (view instanceof AbsSpinner) { 1345 ((AbsSpinner) view).setAdapter( 1346 new FakeAdapter(listRef, binding, layoutlibCallback)); 1347 } 1348 } 1349 } 1350 } else if (isInstanceOf(view, RecyclerViewUtil.CN_RECYCLER_VIEW)) { 1351 RecyclerViewUtil.setAdapter(view, getContext(), getParams()); 1352 } else if (view instanceof ViewGroup) { 1353 ViewGroup group = (ViewGroup) view; 1354 final int count = group.getChildCount(); 1355 for (int c = 0; c < count; c++) { 1356 View child = group.getChildAt(c); 1357 postInflateProcess(child, layoutlibCallback, skip); 1358 } 1359 } 1360 } 1361 1362 /** 1363 * Check if the object is an instance of a class named {@code className}. This doesn't work 1364 * for interfaces. 1365 */ 1366 public static boolean isInstanceOf(Object object, String className) { 1367 Class superClass = object.getClass(); 1368 while (superClass != null) { 1369 String name = superClass.getName(); 1370 if (name.equals(className)) { 1371 return true; 1372 } 1373 superClass = superClass.getSuperclass(); 1374 } 1375 return false; 1376 } 1377 1378 /** 1379 * Sets up a {@link TabHost} object. 1380 * @param tabHost the TabHost to setup. 1381 * @param layoutlibCallback The project callback object to access the project R class. 1382 * @throws PostInflateException 1383 */ 1384 private void setupTabHost(TabHost tabHost, LayoutlibCallback layoutlibCallback) 1385 throws PostInflateException { 1386 // look for the TabWidget, and the FrameLayout. They have their own specific names 1387 View v = tabHost.findViewById(android.R.id.tabs); 1388 1389 if (v == null) { 1390 throw new PostInflateException( 1391 "TabHost requires a TabWidget with id \"android:id/tabs\".\n"); 1392 } 1393 1394 if (!(v instanceof TabWidget)) { 1395 throw new PostInflateException(String.format( 1396 "TabHost requires a TabWidget with id \"android:id/tabs\".\n" + 1397 "View found with id 'tabs' is '%s'", v.getClass().getCanonicalName())); 1398 } 1399 1400 v = tabHost.findViewById(android.R.id.tabcontent); 1401 1402 if (v == null) { 1403 // TODO: see if we can fake tabs even without the FrameLayout (same below when the frameLayout is empty) 1404 //noinspection SpellCheckingInspection 1405 throw new PostInflateException( 1406 "TabHost requires a FrameLayout with id \"android:id/tabcontent\"."); 1407 } 1408 1409 if (!(v instanceof FrameLayout)) { 1410 //noinspection SpellCheckingInspection 1411 throw new PostInflateException(String.format( 1412 "TabHost requires a FrameLayout with id \"android:id/tabcontent\".\n" + 1413 "View found with id 'tabcontent' is '%s'", v.getClass().getCanonicalName())); 1414 } 1415 1416 FrameLayout content = (FrameLayout)v; 1417 1418 // now process the content of the frameLayout and dynamically create tabs for it. 1419 final int count = content.getChildCount(); 1420 1421 // this must be called before addTab() so that the TabHost searches its TabWidget 1422 // and FrameLayout. 1423 tabHost.setup(); 1424 1425 if (count == 0) { 1426 // Create a dummy child to get a single tab 1427 TabSpec spec = tabHost.newTabSpec("tag") 1428 .setIndicator("Tab Label", tabHost.getResources() 1429 .getDrawable(android.R.drawable.ic_menu_info_details, null)) 1430 .setContent(new TabHost.TabContentFactory() { 1431 @Override 1432 public View createTabContent(String tag) { 1433 return new LinearLayout(getContext()); 1434 } 1435 }); 1436 tabHost.addTab(spec); 1437 } else { 1438 // for each child of the frameLayout, add a new TabSpec 1439 for (int i = 0 ; i < count ; i++) { 1440 View child = content.getChildAt(i); 1441 String tabSpec = String.format("tab_spec%d", i+1); 1442 @SuppressWarnings("ConstantConditions") // child cannot be null. 1443 int id = child.getId(); 1444 @SuppressWarnings("deprecation") 1445 Pair<ResourceType, String> resource = layoutlibCallback.resolveResourceId(id); 1446 String name; 1447 if (resource != null) { 1448 name = resource.getSecond(); 1449 } else { 1450 name = String.format("Tab %d", i+1); // default name if id is unresolved. 1451 } 1452 tabHost.addTab(tabHost.newTabSpec(tabSpec).setIndicator(name).setContent(id)); 1453 } 1454 } 1455 } 1456 1457 /** 1458 * Visits a {@link View} and its children and generate a {@link ViewInfo} containing the 1459 * bounds of all the views. 1460 * 1461 * @param view the root View 1462 * @param offset an offset for the view bounds. 1463 * @param setExtendedInfo whether to set the extended view info in the {@link ViewInfo} object. 1464 * @param isContentFrame {@code true} if the {@code ViewInfo} to be created is part of the 1465 * content frame. 1466 * 1467 * @return {@code ViewInfo} containing the bounds of the view and it children otherwise. 1468 */ 1469 private ViewInfo visit(View view, int offset, boolean setExtendedInfo, 1470 boolean isContentFrame) { 1471 ViewInfo result = createViewInfo(view, offset, setExtendedInfo, isContentFrame); 1472 1473 if (view instanceof ViewGroup) { 1474 ViewGroup group = ((ViewGroup) view); 1475 result.setChildren(visitAllChildren(group, isContentFrame ? 0 : offset, 1476 setExtendedInfo, isContentFrame)); 1477 } 1478 return result; 1479 } 1480 1481 /** 1482 * Visits all the children of a given ViewGroup and generates a list of {@link ViewInfo} 1483 * containing the bounds of all the views. It also initializes the {@link #mViewInfoList} with 1484 * the children of the {@code mContentRoot}. 1485 * 1486 * @param viewGroup the root View 1487 * @param offset an offset from the top for the content view frame. 1488 * @param setExtendedInfo whether to set the extended view info in the {@link ViewInfo} object. 1489 * @param isContentFrame {@code true} if the {@code ViewInfo} to be created is part of the 1490 * content frame. {@code false} if the {@code ViewInfo} to be created is 1491 * part of the system decor. 1492 */ 1493 private List<ViewInfo> visitAllChildren(ViewGroup viewGroup, int offset, 1494 boolean setExtendedInfo, boolean isContentFrame) { 1495 if (viewGroup == null) { 1496 return null; 1497 } 1498 1499 if (!isContentFrame) { 1500 offset += viewGroup.getTop(); 1501 } 1502 1503 int childCount = viewGroup.getChildCount(); 1504 if (viewGroup == mContentRoot) { 1505 List<ViewInfo> childrenWithoutOffset = new ArrayList<ViewInfo>(childCount); 1506 List<ViewInfo> childrenWithOffset = new ArrayList<ViewInfo>(childCount); 1507 for (int i = 0; i < childCount; i++) { 1508 ViewInfo[] childViewInfo = visitContentRoot(viewGroup.getChildAt(i), offset, 1509 setExtendedInfo); 1510 childrenWithoutOffset.add(childViewInfo[0]); 1511 childrenWithOffset.add(childViewInfo[1]); 1512 } 1513 mViewInfoList = childrenWithOffset; 1514 return childrenWithoutOffset; 1515 } else { 1516 List<ViewInfo> children = new ArrayList<ViewInfo>(childCount); 1517 for (int i = 0; i < childCount; i++) { 1518 children.add(visit(viewGroup.getChildAt(i), offset, setExtendedInfo, 1519 isContentFrame)); 1520 } 1521 return children; 1522 } 1523 } 1524 1525 /** 1526 * Visits the children of {@link #mContentRoot} and generates {@link ViewInfo} containing the 1527 * bounds of all the views. It returns two {@code ViewInfo} objects with the same children, 1528 * one with the {@code offset} and other without the {@code offset}. The offset is needed to 1529 * get the right bounds if the {@code ViewInfo} hierarchy is accessed from 1530 * {@code mViewInfoList}. When the hierarchy is accessed via {@code mSystemViewInfoList}, the 1531 * offset is not needed. 1532 * 1533 * @return an array of length two, with ViewInfo at index 0 is without offset and ViewInfo at 1534 * index 1 is with the offset. 1535 */ 1536 @NonNull 1537 private ViewInfo[] visitContentRoot(View view, int offset, boolean setExtendedInfo) { 1538 ViewInfo[] result = new ViewInfo[2]; 1539 if (view == null) { 1540 return result; 1541 } 1542 1543 result[0] = createViewInfo(view, 0, setExtendedInfo, true); 1544 result[1] = createViewInfo(view, offset, setExtendedInfo, true); 1545 if (view instanceof ViewGroup) { 1546 List<ViewInfo> children = visitAllChildren((ViewGroup) view, 0, setExtendedInfo, true); 1547 result[0].setChildren(children); 1548 result[1].setChildren(children); 1549 } 1550 return result; 1551 } 1552 1553 /** 1554 * Creates a {@link ViewInfo} for the view. The {@code ViewInfo} corresponding to the children 1555 * of the {@code view} are not created. Consequently, the children of {@code ViewInfo} is not 1556 * set. 1557 * @param offset an offset for the view bounds. Used only if view is part of the content frame. 1558 */ 1559 private ViewInfo createViewInfo(View view, int offset, boolean setExtendedInfo, 1560 boolean isContentFrame) { 1561 if (view == null) { 1562 return null; 1563 } 1564 1565 ViewInfo result; 1566 if (isContentFrame) { 1567 // The view is part of the layout added by the user. Hence, 1568 // the ViewCookie may be obtained only through the Context. 1569 result = new ViewInfo(view.getClass().getName(), 1570 getContext().getViewKey(view), 1571 view.getLeft(), view.getTop() + offset, view.getRight(), 1572 view.getBottom() + offset, view, view.getLayoutParams()); 1573 } else { 1574 // We are part of the system decor. 1575 SystemViewInfo r = new SystemViewInfo(view.getClass().getName(), 1576 getViewKey(view), 1577 view.getLeft(), view.getTop(), view.getRight(), 1578 view.getBottom(), view, view.getLayoutParams()); 1579 result = r; 1580 // We currently mark three kinds of views: 1581 // 1. Menus in the Action Bar 1582 // 2. Menus in the Overflow popup. 1583 // 3. The overflow popup button. 1584 if (view instanceof ListMenuItemView) { 1585 // Mark 2. 1586 // All menus in the popup are of type ListMenuItemView. 1587 r.setViewType(ViewType.ACTION_BAR_OVERFLOW_MENU); 1588 } else { 1589 // Mark 3. 1590 ViewGroup.LayoutParams lp = view.getLayoutParams(); 1591 if (lp instanceof ActionMenuView.LayoutParams && 1592 ((ActionMenuView.LayoutParams) lp).isOverflowButton) { 1593 r.setViewType(ViewType.ACTION_BAR_OVERFLOW); 1594 } else { 1595 // Mark 1. 1596 // A view is a menu in the Action Bar is it is not the overflow button and of 1597 // its parent is of type ActionMenuView. We can also check if the view is 1598 // instanceof ActionMenuItemView but that will fail for menus using 1599 // actionProviderClass. 1600 ViewParent parent = view.getParent(); 1601 while (parent != mViewRoot && parent instanceof ViewGroup) { 1602 if (parent instanceof ActionMenuView) { 1603 r.setViewType(ViewType.ACTION_BAR_MENU); 1604 break; 1605 } 1606 parent = parent.getParent(); 1607 } 1608 } 1609 } 1610 } 1611 1612 if (setExtendedInfo) { 1613 MarginLayoutParams marginParams = null; 1614 LayoutParams params = view.getLayoutParams(); 1615 if (params instanceof MarginLayoutParams) { 1616 marginParams = (MarginLayoutParams) params; 1617 } 1618 result.setExtendedInfo(view.getBaseline(), 1619 marginParams != null ? marginParams.leftMargin : 0, 1620 marginParams != null ? marginParams.topMargin : 0, 1621 marginParams != null ? marginParams.rightMargin : 0, 1622 marginParams != null ? marginParams.bottomMargin : 0); 1623 } 1624 1625 return result; 1626 } 1627 1628 /* (non-Javadoc) 1629 * The cookie for menu items are stored in menu item and not in the map from View stored in 1630 * BridgeContext. 1631 */ 1632 @Nullable 1633 private Object getViewKey(View view) { 1634 BridgeContext context = getContext(); 1635 if (!(view instanceof MenuView.ItemView)) { 1636 return context.getViewKey(view); 1637 } 1638 MenuItemImpl menuItem; 1639 if (view instanceof ActionMenuItemView) { 1640 menuItem = ((ActionMenuItemView) view).getItemData(); 1641 } else if (view instanceof ListMenuItemView) { 1642 menuItem = ((ListMenuItemView) view).getItemData(); 1643 } else if (view instanceof IconMenuItemView) { 1644 menuItem = ((IconMenuItemView) view).getItemData(); 1645 } else { 1646 menuItem = null; 1647 } 1648 if (menuItem instanceof BridgeMenuItemImpl) { 1649 return ((BridgeMenuItemImpl) menuItem).getViewCookie(); 1650 } 1651 1652 return null; 1653 } 1654 1655 public void invalidateRenderingSize() { 1656 mMeasuredScreenWidth = mMeasuredScreenHeight = -1; 1657 } 1658 1659 /** 1660 * Creates the status bar with wifi and battery icons. 1661 */ 1662 private StatusBar createStatusBar(BridgeContext context, Density density, int direction, 1663 boolean isRtlSupported, int platformVersion) throws XmlPullParserException { 1664 StatusBar statusBar = new StatusBar(context, density, 1665 direction, isRtlSupported, platformVersion); 1666 statusBar.setLayoutParams( 1667 new LinearLayout.LayoutParams( 1668 LayoutParams.MATCH_PARENT, mStatusBarSize)); 1669 return statusBar; 1670 } 1671 1672 /** 1673 * Creates the navigation bar with back, home and recent buttons. 1674 * 1675 * @param isRtl true if the current locale is right-to-left 1676 * @param isRtlSupported true is the project manifest declares that the application 1677 * is RTL aware. 1678 */ 1679 private NavigationBar createNavigationBar(BridgeContext context, Density density, 1680 boolean isRtl, boolean isRtlSupported, int simulatedPlatformVersion) 1681 throws XmlPullParserException { 1682 NavigationBar navigationBar = new NavigationBar(context, 1683 density, mNavigationBarOrientation, isRtl, 1684 isRtlSupported, simulatedPlatformVersion); 1685 if (mNavigationBarOrientation == LinearLayout.VERTICAL) { 1686 navigationBar.setLayoutParams(new LinearLayout.LayoutParams(mNavigationBarSize, 1687 LayoutParams.MATCH_PARENT)); 1688 } else { 1689 navigationBar.setLayoutParams(new LinearLayout.LayoutParams(LayoutParams.MATCH_PARENT, 1690 mNavigationBarSize)); 1691 } 1692 return navigationBar; 1693 } 1694 1695 private TitleBar createTitleBar(BridgeContext context, String title, 1696 int simulatedPlatformVersion) 1697 throws XmlPullParserException { 1698 TitleBar titleBar = new TitleBar(context, title, simulatedPlatformVersion); 1699 titleBar.setLayoutParams( 1700 new LinearLayout.LayoutParams(LayoutParams.MATCH_PARENT, mTitleBarSize)); 1701 return titleBar; 1702 } 1703 1704 /** 1705 * Creates the action bar. Also queries the project callback for missing information. 1706 */ 1707 private BridgeActionBar createActionBar(BridgeContext context, SessionParams params, 1708 ViewGroup parentView) { 1709 if (mIsThemeAppCompat == Boolean.TRUE) { 1710 return new AppCompatActionBar(context, params, parentView); 1711 } else { 1712 return new FrameworkActionBar(context, params, parentView); 1713 } 1714 } 1715 1716 public BufferedImage getImage() { 1717 return mImage; 1718 } 1719 1720 public boolean isAlphaChannelImage() { 1721 return mIsAlphaChannelImage; 1722 } 1723 1724 public List<ViewInfo> getViewInfos() { 1725 return mViewInfoList; 1726 } 1727 1728 public List<ViewInfo> getSystemViewInfos() { 1729 return mSystemViewInfoList; 1730 } 1731 1732 public Map<String, String> getDefaultProperties(Object viewObject) { 1733 return getContext().getDefaultPropMap(viewObject); 1734 } 1735 1736 public void setScene(RenderSession session) { 1737 mScene = session; 1738 } 1739 1740 public RenderSession getSession() { 1741 return mScene; 1742 } 1743} 1744