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