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