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