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