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