Bridge.java revision ffa424800d0338b8b894aef2ea1e3e3344cbda7a
1/*
2 * Copyright (C) 2008 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;
18
19import com.android.internal.util.XmlUtils;
20import com.android.layoutlib.api.ILayoutBridge;
21import com.android.layoutlib.api.ILayoutLog;
22import com.android.layoutlib.api.ILayoutResult;
23import com.android.layoutlib.api.IProjectCallback;
24import com.android.layoutlib.api.IResourceValue;
25import com.android.layoutlib.api.IStyleResourceValue;
26import com.android.layoutlib.api.IXmlPullParser;
27import com.android.layoutlib.api.ILayoutResult.ILayoutViewInfo;
28import com.android.layoutlib.bridge.LayoutResult.LayoutViewInfo;
29import com.android.ninepatch.NinePatch;
30import com.android.tools.layoutlib.create.MethodAdapter;
31import com.android.tools.layoutlib.create.OverrideMethod;
32
33import android.graphics.Bitmap;
34import android.graphics.Canvas;
35import android.graphics.Rect;
36import android.graphics.Region;
37import android.graphics.Typeface;
38import android.graphics.drawable.Drawable;
39import android.os.Handler;
40import android.os.IBinder;
41import android.os.Looper;
42import android.os.ParcelFileDescriptor;
43import android.os.RemoteException;
44import android.util.DisplayMetrics;
45import android.util.TypedValue;
46import android.view.BridgeInflater;
47import android.view.IWindow;
48import android.view.IWindowSession;
49import android.view.KeyEvent;
50import android.view.MotionEvent;
51import android.view.Surface;
52import android.view.SurfaceView;
53import android.view.View;
54import android.view.ViewGroup;
55import android.view.View.AttachInfo;
56import android.view.View.MeasureSpec;
57import android.view.WindowManager.LayoutParams;
58import android.widget.FrameLayout;
59import android.widget.TabHost;
60import android.widget.TabWidget;
61
62import java.lang.ref.SoftReference;
63import java.lang.reflect.Field;
64import java.lang.reflect.Modifier;
65import java.util.Collection;
66import java.util.HashMap;
67import java.util.Map;
68
69/**
70 * Main entry point of the LayoutLib Bridge.
71 * <p/>To use this bridge, simply instantiate an object of type {@link Bridge} and call
72 * {@link #computeLayout(IXmlPullParser, Object, int, int, String, boolean, Map, Map, IProjectCallback, ILayoutLog)}.
73 */
74public final class Bridge implements ILayoutBridge {
75
76    private static final int DEFAULT_TITLE_BAR_HEIGHT = 25;
77    private static final int DEFAULT_STATUS_BAR_HEIGHT = 25;
78
79    public static class StaticMethodNotImplementedException extends RuntimeException {
80        private static final long serialVersionUID = 1L;
81
82        public StaticMethodNotImplementedException(String msg) {
83            super(msg);
84        }
85    }
86
87    /**
88     * Maps from id to resource name/type. This is for android.R only.
89     */
90    private final static Map<Integer, String[]> sRMap = new HashMap<Integer, String[]>();
91    /**
92     * Same as sRMap except for int[] instead of int resources. This is for android.R only.
93     */
94    private final static Map<int[], String> sRArrayMap = new HashMap<int[], String>();
95    /**
96     * Reverse map compared to sRMap, resource type -> (resource name -> id).
97     * This is for android.R only.
98     */
99    private final static Map<String, Map<String, Integer>> sRFullMap =
100        new HashMap<String, Map<String,Integer>>();
101
102    private final static Map<Object, Map<String, SoftReference<Bitmap>>> sProjectBitmapCache =
103        new HashMap<Object, Map<String, SoftReference<Bitmap>>>();
104    private final static Map<Object, Map<String, SoftReference<NinePatch>>> sProject9PatchCache =
105        new HashMap<Object, Map<String, SoftReference<NinePatch>>>();
106
107    private final static Map<String, SoftReference<Bitmap>> sFrameworkBitmapCache =
108        new HashMap<String, SoftReference<Bitmap>>();
109    private final static Map<String, SoftReference<NinePatch>> sFramework9PatchCache =
110        new HashMap<String, SoftReference<NinePatch>>();
111
112    private static Map<String, Map<String, Integer>> sEnumValueMap;
113
114    /**
115     * A default logger than prints to stdout/stderr.
116     */
117    private final static ILayoutLog sDefaultLogger = new ILayoutLog() {
118        public void error(String message) {
119            System.err.println(message);
120        }
121
122        public void error(Throwable t) {
123            String message = t.getMessage();
124            if (message == null) {
125                message = t.getClass().getName();
126            }
127
128            System.err.println(message);
129        }
130
131        public void warning(String message) {
132            System.out.println(message);
133        }
134    };
135
136    /**
137     * Logger defined during a compute layout operation.
138     * <p/>
139     * This logger is generally set to {@link #sDefaultLogger} except during rendering
140     * operations when it might be set to a specific provided logger.
141     * <p/>
142     * To change this value, use a block synchronized on {@link #sDefaultLogger}.
143     */
144    private static ILayoutLog sLogger = sDefaultLogger;
145
146    /*
147     * (non-Javadoc)
148     * @see com.android.layoutlib.api.ILayoutBridge#getApiLevel()
149     */
150    public int getApiLevel() {
151        return API_CURRENT;
152    }
153
154    /*
155     * (non-Javadoc)
156     * @see com.android.layoutlib.api.ILayoutLibBridge#init(java.lang.String, java.util.Map)
157     */
158    public boolean init(
159            String fontOsLocation, Map<String, Map<String, Integer>> enumValueMap) {
160
161        return sinit(fontOsLocation, enumValueMap);
162    }
163
164    private static synchronized boolean sinit(String fontOsLocation,
165            Map<String, Map<String, Integer>> enumValueMap) {
166
167        // When DEBUG_LAYOUT is set and is not 0 or false, setup a default listener
168        // on static (native) methods which prints the signature on the console and
169        // throws an exception.
170        // This is useful when testing the rendering in ADT to identify static native
171        // methods that are ignored -- layoutlib_create makes them returns 0/false/null
172        // which is generally OK yet might be a problem, so this is how you'd find out.
173        //
174        // Currently layoutlib_create only overrides static native method.
175        // Static non-natives are not overridden and thus do not get here.
176        final String debug = System.getenv("DEBUG_LAYOUT");
177        if (debug != null && !debug.equals("0") && !debug.equals("false")) {
178
179            OverrideMethod.setDefaultListener(new MethodAdapter() {
180                @Override
181                public void onInvokeV(String signature, boolean isNative, Object caller) {
182                    if (sLogger != null) {
183                        synchronized (sDefaultLogger) {
184                            sLogger.error("Missing Stub: " + signature +
185                                    (isNative ? " (native)" : ""));
186                        }
187                    }
188
189                    if (debug.equalsIgnoreCase("throw")) {
190                        // Throwing this exception doesn't seem that useful. It breaks
191                        // the layout editor yet doesn't display anything meaningful to the
192                        // user. Having the error in the console is just as useful. We'll
193                        // throw it only if the environment variable is "throw" or "THROW".
194                        throw new StaticMethodNotImplementedException(signature);
195                    }
196                }
197            });
198        }
199
200        // Override View.isInEditMode to return true.
201        //
202        // This allows custom views that are drawn in the Graphical Layout Editor to adapt their
203        // rendering for preview. Most important this let custom views know that they can't expect
204        // the rest of their activities to be alive.
205        OverrideMethod.setMethodListener("android.view.View#isInEditMode()Z",
206            new MethodAdapter() {
207                @Override
208                public int onInvokeI(String signature, boolean isNative, Object caller) {
209                    return 1;
210                }
211            }
212        );
213
214        // load the fonts.
215        FontLoader fontLoader = FontLoader.create(fontOsLocation);
216        if (fontLoader != null) {
217            Typeface.init(fontLoader);
218        } else {
219            return false;
220        }
221
222        sEnumValueMap = enumValueMap;
223
224        // now parse com.android.internal.R (and only this one as android.R is a subset of
225        // the internal version), and put the content in the maps.
226        try {
227            // WARNING: this only works because the class is already loaded, and therefore
228            // the objects returned by Field.get() are the same as the ones used by
229            // the code accessing the R class.
230            // int[] does not implement equals/hashCode, and if the parsing used a different class
231            // loader for the R class, this would NOT work.
232            Class<?> r = com.android.internal.R.class;
233
234            for (Class<?> inner : r.getDeclaredClasses()) {
235                String resType = inner.getSimpleName();
236
237                Map<String, Integer> fullMap = new HashMap<String, Integer>();
238                sRFullMap.put(resType, fullMap);
239
240                for (Field f : inner.getDeclaredFields()) {
241                    // only process static final fields. Since the final attribute may have
242                    // been altered by layoutlib_create, we only check static
243                    int modifiers = f.getModifiers();
244                    if (Modifier.isStatic(modifiers)) {
245                        Class<?> type = f.getType();
246                        if (type.isArray() && type.getComponentType() == int.class) {
247                            // if the object is an int[] we put it in sRArrayMap
248                            sRArrayMap.put((int[]) f.get(null), f.getName());
249                        } else if (type == int.class) {
250                            Integer value = (Integer) f.get(null);
251                            sRMap.put(value, new String[] { f.getName(), resType });
252                            fullMap.put(f.getName(), value);
253                        } else {
254                            assert false;
255                        }
256                    }
257                }
258            }
259        } catch (IllegalArgumentException e) {
260            // FIXME: log/return the error (there's no logger object at this point!)
261            e.printStackTrace();
262            return false;
263        } catch (IllegalAccessException e) {
264            e.printStackTrace();
265            return false;
266        }
267
268        return true;
269    }
270
271    /*
272     * For compatilibty purposes, we implement the old deprecated version of computeLayout.
273     * (non-Javadoc)
274     * @see com.android.layoutlib.api.ILayoutBridge#computeLayout(com.android.layoutlib.api.IXmlPullParser, java.lang.Object, int, int, java.lang.String, java.util.Map, java.util.Map, com.android.layoutlib.api.IProjectCallback, com.android.layoutlib.api.ILayoutLog)
275     */
276    @Deprecated
277    public ILayoutResult computeLayout(IXmlPullParser layoutDescription,
278            Object projectKey,
279            int screenWidth, int screenHeight, String themeName,
280            Map<String, Map<String, IResourceValue>> projectResources,
281            Map<String, Map<String, IResourceValue>> frameworkResources,
282            IProjectCallback customViewLoader, ILayoutLog logger) {
283        boolean isProjectTheme = false;
284        if (themeName.charAt(0) == '*') {
285            themeName = themeName.substring(1);
286            isProjectTheme = true;
287        }
288
289        return computeLayout(layoutDescription, projectKey,
290                screenWidth, screenHeight, DisplayMetrics.DENSITY_DEFAULT,
291                DisplayMetrics.DENSITY_DEFAULT, DisplayMetrics.DENSITY_DEFAULT,
292                themeName, isProjectTheme,
293                projectResources, frameworkResources, customViewLoader, logger);
294    }
295
296    /*
297     * For compatilibty purposes, we implement the old deprecated version of computeLayout.
298     * (non-Javadoc)
299     * @see com.android.layoutlib.api.ILayoutBridge#computeLayout(com.android.layoutlib.api.IXmlPullParser, java.lang.Object, int, int, java.lang.String, boolean, java.util.Map, java.util.Map, com.android.layoutlib.api.IProjectCallback, com.android.layoutlib.api.ILayoutLog)
300     */
301    @Deprecated
302    public ILayoutResult computeLayout(IXmlPullParser layoutDescription, Object projectKey,
303            int screenWidth, int screenHeight, String themeName, boolean isProjectTheme,
304            Map<String, Map<String, IResourceValue>> projectResources,
305            Map<String, Map<String, IResourceValue>> frameworkResources,
306            IProjectCallback customViewLoader, ILayoutLog logger) {
307        return computeLayout(layoutDescription, projectKey,
308                screenWidth, screenHeight, DisplayMetrics.DENSITY_DEFAULT,
309                DisplayMetrics.DENSITY_DEFAULT, DisplayMetrics.DENSITY_DEFAULT,
310                themeName, isProjectTheme,
311                projectResources, frameworkResources, customViewLoader, logger);
312    }
313
314    /*
315     * For compatilibty purposes, we implement the old deprecated version of computeLayout.
316     * (non-Javadoc)
317     * @see com.android.layoutlib.api.ILayoutBridge#computeLayout(com.android.layoutlib.api.IXmlPullParser, java.lang.Object, int, int, int, float, float, java.lang.String, boolean, java.util.Map, java.util.Map, com.android.layoutlib.api.IProjectCallback, com.android.layoutlib.api.ILayoutLog)
318     */
319    public ILayoutResult computeLayout(IXmlPullParser layoutDescription, Object projectKey,
320            int screenWidth, int screenHeight, int density, float xdpi, float ydpi,
321            String themeName, boolean isProjectTheme,
322            Map<String, Map<String, IResourceValue>> projectResources,
323            Map<String, Map<String, IResourceValue>> frameworkResources,
324            IProjectCallback customViewLoader, ILayoutLog logger) {
325        return computeLayout(layoutDescription, projectKey,
326                screenWidth, screenHeight, false /* renderFullSize */,
327                density, xdpi, ydpi, themeName, isProjectTheme,
328                projectResources, frameworkResources, customViewLoader, logger);
329    }
330
331    /*
332     * (non-Javadoc)
333     * @see com.android.layoutlib.api.ILayoutBridge#computeLayout(com.android.layoutlib.api.IXmlPullParser, java.lang.Object, int, int, boolean, int, float, float, java.lang.String, boolean, java.util.Map, java.util.Map, com.android.layoutlib.api.IProjectCallback, com.android.layoutlib.api.ILayoutLog)
334     */
335    public ILayoutResult computeLayout(IXmlPullParser layoutDescription, Object projectKey,
336            int screenWidth, int screenHeight, boolean renderFullSize,
337            int density, float xdpi, float ydpi,
338            String themeName, boolean isProjectTheme,
339            Map<String, Map<String, IResourceValue>> projectResources,
340            Map<String, Map<String, IResourceValue>> frameworkResources,
341            IProjectCallback customViewLoader, ILayoutLog logger) {
342        if (logger == null) {
343            logger = sDefaultLogger;
344        }
345
346        synchronized (sDefaultLogger) {
347            sLogger = logger;
348        }
349
350        // find the current theme and compute the style inheritance map
351        Map<IStyleResourceValue, IStyleResourceValue> styleParentMap =
352            new HashMap<IStyleResourceValue, IStyleResourceValue>();
353
354        IStyleResourceValue currentTheme = computeStyleMaps(themeName, isProjectTheme,
355                projectResources.get(BridgeConstants.RES_STYLE),
356                frameworkResources.get(BridgeConstants.RES_STYLE), styleParentMap);
357
358        BridgeContext context = null;
359        try {
360            // setup the display Metrics.
361            DisplayMetrics metrics = new DisplayMetrics();
362            metrics.densityDpi = density;
363            metrics.density = density / (float) DisplayMetrics.DENSITY_DEFAULT;
364            metrics.scaledDensity = metrics.density;
365            metrics.widthPixels = screenWidth;
366            metrics.heightPixels = screenHeight;
367            metrics.xdpi = xdpi;
368            metrics.ydpi = ydpi;
369
370            context = new BridgeContext(projectKey, metrics, currentTheme, projectResources,
371                    frameworkResources, styleParentMap, customViewLoader, logger);
372            BridgeInflater inflater = new BridgeInflater(context, customViewLoader);
373            context.setBridgeInflater(inflater);
374
375            IResourceValue windowBackground = null;
376            int screenOffset = 0;
377            if (currentTheme != null) {
378                windowBackground = context.findItemInStyle(currentTheme, "windowBackground");
379                windowBackground = context.resolveResValue(windowBackground);
380
381                screenOffset = getScreenOffset(frameworkResources, currentTheme, context);
382            }
383
384            // we need to make sure the Looper has been initialized for this thread.
385            // this is required for View that creates Handler objects.
386            if (Looper.myLooper() == null) {
387                Looper.prepare();
388            }
389
390            BridgeXmlBlockParser parser = new BridgeXmlBlockParser(layoutDescription,
391                    context, false /* platformResourceFlag */);
392
393            ViewGroup root = new FrameLayout(context);
394
395            View view = inflater.inflate(parser, root);
396
397            // post-inflate process. For now this supports TabHost/TabWidget
398            postInflateProcess(view, customViewLoader);
399
400            // set the AttachInfo on the root view.
401            AttachInfo info = new AttachInfo(new WindowSession(), new Window(),
402                    new Handler(), null);
403            info.mHasWindowFocus = true;
404            info.mWindowVisibility = View.VISIBLE;
405            info.mInTouchMode = false; // this is so that we can display selections.
406            root.dispatchAttachedToWindow(info, 0);
407
408            // get the background drawable
409            if (windowBackground != null) {
410                Drawable d = ResourceHelper.getDrawable(windowBackground,
411                        context, true /* isFramework */);
412                root.setBackgroundDrawable(d);
413            }
414
415            // measure the views
416            int w_spec, h_spec;
417
418            if (renderFullSize) {
419                // measure the full size needed by the layout.
420                w_spec = MeasureSpec.makeMeasureSpec(screenWidth,
421                        MeasureSpec.UNSPECIFIED); // this lets us know the actual needed size
422                h_spec = MeasureSpec.makeMeasureSpec(screenHeight - screenOffset,
423                        MeasureSpec.UNSPECIFIED); // this lets us know the actual needed size
424                view.measure(w_spec, h_spec);
425
426                int neededWidth = root.getChildAt(0).getMeasuredWidth();
427                if (neededWidth > screenWidth) {
428                    screenWidth = neededWidth;
429                }
430
431                int neededHeight = root.getChildAt(0).getMeasuredHeight();
432                if (neededHeight > screenHeight - screenOffset) {
433                    screenHeight = neededHeight + screenOffset;
434                }
435            }
436
437            // remeasure with only the size we need
438            // This must always be done before the call to layout
439            w_spec = MeasureSpec.makeMeasureSpec(screenWidth, MeasureSpec.EXACTLY);
440            h_spec = MeasureSpec.makeMeasureSpec(screenHeight - screenOffset,
441                    MeasureSpec.EXACTLY);
442            view.measure(w_spec, h_spec);
443
444            // now do the layout.
445            view.layout(0, screenOffset, screenWidth, screenHeight);
446
447            // draw the views
448            Canvas canvas = new Canvas(screenWidth, screenHeight - screenOffset, logger);
449
450            root.draw(canvas);
451            canvas.dispose();
452
453            return new LayoutResult(visit(((ViewGroup)view).getChildAt(0), context),
454                    canvas.getImage());
455        } catch (PostInflateException e) {
456            return new LayoutResult(ILayoutResult.ERROR, "Error during post inflation process:\n"
457                    + e.getMessage());
458        } catch (Throwable e) {
459            // get the real cause of the exception.
460            Throwable t = e;
461            while (t.getCause() != null) {
462                t = t.getCause();
463            }
464
465            // log it
466            logger.error(t);
467
468            // then return with an ERROR status and the message from the real exception
469            return new LayoutResult(ILayoutResult.ERROR,
470                    t.getClass().getSimpleName() + ": " + t.getMessage());
471        } finally {
472            // Make sure to remove static references, otherwise we could not unload the lib
473            BridgeResources.clearSystem();
474            BridgeAssetManager.clearSystem();
475
476            // Remove the global logger
477            synchronized (sDefaultLogger) {
478                sLogger = sDefaultLogger;
479            }
480        }
481    }
482
483    /*
484     * (non-Javadoc)
485     * @see com.android.layoutlib.api.ILayoutLibBridge#clearCaches(java.lang.Object)
486     */
487    public void clearCaches(Object projectKey) {
488        if (projectKey != null) {
489            sProjectBitmapCache.remove(projectKey);
490            sProject9PatchCache.remove(projectKey);
491        }
492    }
493
494    /**
495     * Returns details of a framework resource from its integer value.
496     * @param value the integer value
497     * @return an array of 2 strings containing the resource name and type, or null if the id
498     * does not match any resource.
499     */
500    public static String[] resolveResourceValue(int value) {
501        return sRMap.get(value);
502
503    }
504
505    /**
506     * Returns the name of a framework resource whose value is an int array.
507     * @param array
508     */
509    public static String resolveResourceValue(int[] array) {
510        return sRArrayMap.get(array);
511    }
512
513    /**
514     * Returns the integer id of a framework resource, from a given resource type and resource name.
515     * @param type the type of the resource
516     * @param name the name of the resource.
517     * @return an {@link Integer} containing the resource id, or null if no resource were found.
518     */
519    public static Integer getResourceValue(String type, String name) {
520        Map<String, Integer> map = sRFullMap.get(type);
521        if (map != null) {
522            return map.get(name);
523        }
524
525        return null;
526    }
527
528    static Map<String, Integer> getEnumValues(String attributeName) {
529        if (sEnumValueMap != null) {
530            return sEnumValueMap.get(attributeName);
531        }
532
533        return null;
534    }
535
536    /**
537     * Visits a View and its children and generate a {@link ILayoutViewInfo} containing the
538     * bounds of all the views.
539     * @param view the root View
540     * @param context the context.
541     */
542    private ILayoutViewInfo visit(View view, BridgeContext context) {
543        if (view == null) {
544            return null;
545        }
546
547        LayoutViewInfo result = new LayoutViewInfo(view.getClass().getName(),
548                context.getViewKey(view),
549                view.getLeft(), view.getTop(), view.getRight(), view.getBottom());
550
551        if (view instanceof ViewGroup) {
552            ViewGroup group = ((ViewGroup) view);
553            int n = group.getChildCount();
554            ILayoutViewInfo[] children = new ILayoutViewInfo[n];
555            for (int i = 0; i < group.getChildCount(); i++) {
556                children[i] = visit(group.getChildAt(i), context);
557            }
558            result.setChildren(children);
559        }
560
561        return result;
562    }
563
564    /**
565     * Compute style information from the given list of style for the project and framework.
566     * @param themeName the name of the current theme.  In order to differentiate project and
567     * platform themes sharing the same name, all project themes must be prepended with
568     * a '*' character.
569     * @param isProjectTheme Is this a project theme
570     * @param inProjectStyleMap the project style map
571     * @param inFrameworkStyleMap the framework style map
572     * @param outInheritanceMap the map of style inheritance. This is filled by the method
573     * @return the {@link IStyleResourceValue} matching <var>themeName</var>
574     */
575    private IStyleResourceValue computeStyleMaps(
576            String themeName, boolean isProjectTheme, Map<String,
577            IResourceValue> inProjectStyleMap, Map<String, IResourceValue> inFrameworkStyleMap,
578            Map<IStyleResourceValue, IStyleResourceValue> outInheritanceMap) {
579
580        if (inProjectStyleMap != null && inFrameworkStyleMap != null) {
581            // first, get the theme
582            IResourceValue theme = null;
583
584            // project theme names have been prepended with a *
585            if (isProjectTheme) {
586                theme = inProjectStyleMap.get(themeName);
587            } else {
588                theme = inFrameworkStyleMap.get(themeName);
589            }
590
591            if (theme instanceof IStyleResourceValue) {
592                // compute the inheritance map for both the project and framework styles
593                computeStyleInheritance(inProjectStyleMap.values(), inProjectStyleMap,
594                        inFrameworkStyleMap, outInheritanceMap);
595
596                // Compute the style inheritance for the framework styles/themes.
597                // Since, for those, the style parent values do not contain 'android:'
598                // we want to force looking in the framework style only to avoid using
599                // similarly named styles from the project.
600                // To do this, we pass null in lieu of the project style map.
601                computeStyleInheritance(inFrameworkStyleMap.values(), null /*inProjectStyleMap */,
602                        inFrameworkStyleMap, outInheritanceMap);
603
604                return (IStyleResourceValue)theme;
605            }
606        }
607
608        return null;
609    }
610
611    /**
612     * Compute the parent style for all the styles in a given list.
613     * @param styles the styles for which we compute the parent.
614     * @param inProjectStyleMap the map of project styles.
615     * @param inFrameworkStyleMap the map of framework styles.
616     * @param outInheritanceMap the map of style inheritance. This is filled by the method.
617     */
618    private void computeStyleInheritance(Collection<IResourceValue> styles,
619            Map<String, IResourceValue> inProjectStyleMap,
620            Map<String, IResourceValue> inFrameworkStyleMap,
621            Map<IStyleResourceValue, IStyleResourceValue> outInheritanceMap) {
622        for (IResourceValue value : styles) {
623            if (value instanceof IStyleResourceValue) {
624                IStyleResourceValue style = (IStyleResourceValue)value;
625                IStyleResourceValue parentStyle = null;
626
627                // first look for a specified parent.
628                String parentName = style.getParentStyle();
629
630                // no specified parent? try to infer it from the name of the style.
631                if (parentName == null) {
632                    parentName = getParentName(value.getName());
633                }
634
635                if (parentName != null) {
636                    parentStyle = getStyle(parentName, inProjectStyleMap, inFrameworkStyleMap);
637
638                    if (parentStyle != null) {
639                        outInheritanceMap.put(style, parentStyle);
640                    }
641                }
642            }
643        }
644    }
645
646    /**
647     * Searches for and returns the {@link IStyleResourceValue} from a given name.
648     * <p/>The format of the name can be:
649     * <ul>
650     * <li>[android:]&lt;name&gt;</li>
651     * <li>[android:]style/&lt;name&gt;</li>
652     * <li>@[android:]style/&lt;name&gt;</li>
653     * </ul>
654     * @param parentName the name of the style.
655     * @param inProjectStyleMap the project style map. Can be <code>null</code>
656     * @param inFrameworkStyleMap the framework style map.
657     * @return The matching {@link IStyleResourceValue} object or <code>null</code> if not found.
658     */
659    private IStyleResourceValue getStyle(String parentName,
660            Map<String, IResourceValue> inProjectStyleMap,
661            Map<String, IResourceValue> inFrameworkStyleMap) {
662        boolean frameworkOnly = false;
663
664        String name = parentName;
665
666        // remove the useless @ if it's there
667        if (name.startsWith(BridgeConstants.PREFIX_RESOURCE_REF)) {
668            name = name.substring(BridgeConstants.PREFIX_RESOURCE_REF.length());
669        }
670
671        // check for framework identifier.
672        if (name.startsWith(BridgeConstants.PREFIX_ANDROID)) {
673            frameworkOnly = true;
674            name = name.substring(BridgeConstants.PREFIX_ANDROID.length());
675        }
676
677        // at this point we could have the format style/<name>. we want only the name
678        if (name.startsWith(BridgeConstants.REFERENCE_STYLE)) {
679            name = name.substring(BridgeConstants.REFERENCE_STYLE.length());
680        }
681
682        IResourceValue parent = null;
683
684        // if allowed, search in the project resources.
685        if (frameworkOnly == false && inProjectStyleMap != null) {
686            parent = inProjectStyleMap.get(name);
687        }
688
689        // if not found, then look in the framework resources.
690        if (parent == null) {
691            parent = inFrameworkStyleMap.get(name);
692        }
693
694        // make sure the result is the proper class type and return it.
695        if (parent instanceof IStyleResourceValue) {
696            return (IStyleResourceValue)parent;
697        }
698
699        sLogger.error(String.format("Unable to resolve parent style name: ", parentName));
700
701        return null;
702    }
703
704    /**
705     * Computes the name of the parent style, or <code>null</code> if the style is a root style.
706     */
707    private String getParentName(String styleName) {
708        int index = styleName.lastIndexOf('.');
709        if (index != -1) {
710            return styleName.substring(0, index);
711        }
712
713        return null;
714    }
715
716    /**
717     * Returns the top screen offset. This depends on whether the current theme defines the user
718     * of the title and status bars.
719     * @param frameworkResources The framework resources
720     * @param currentTheme The current theme
721     * @param context The context
722     * @return the pixel height offset
723     */
724    private int getScreenOffset(Map<String, Map<String, IResourceValue>> frameworkResources,
725            IStyleResourceValue currentTheme, BridgeContext context) {
726        int offset = 0;
727
728        // get the title bar flag from the current theme.
729        IResourceValue value = context.findItemInStyle(currentTheme, "windowNoTitle");
730
731        // because it may reference something else, we resolve it.
732        value = context.resolveResValue(value);
733
734        // if there's a value and it's true (default is false)
735        if (value == null || value.getValue() == null ||
736                XmlUtils.convertValueToBoolean(value.getValue(), false /* defValue */) == false) {
737            // default size of the window title bar
738            int defaultOffset = DEFAULT_TITLE_BAR_HEIGHT;
739
740            // get value from the theme.
741            value = context.findItemInStyle(currentTheme, "windowTitleSize");
742
743            // resolve it
744            value = context.resolveResValue(value);
745
746            if (value != null) {
747                // get the numerical value, if available
748                TypedValue typedValue = ResourceHelper.getValue(value.getValue());
749                if (typedValue != null) {
750                    // compute the pixel value based on the display metrics
751                    defaultOffset = (int)typedValue.getDimension(context.getResources().mMetrics);
752                }
753            }
754
755            offset += defaultOffset;
756        }
757
758        // get the fullscreen flag from the current theme.
759        value = context.findItemInStyle(currentTheme, "windowFullscreen");
760
761        // because it may reference something else, we resolve it.
762        value = context.resolveResValue(value);
763
764        if (value == null || value.getValue() == null ||
765                XmlUtils.convertValueToBoolean(value.getValue(), false /* defValue */) == false) {
766
767            // default value
768            int defaultOffset = DEFAULT_STATUS_BAR_HEIGHT;
769
770            // get the real value, first the list of Dimensions from the framework map
771            Map<String, IResourceValue> dimens = frameworkResources.get(BridgeConstants.RES_DIMEN);
772
773            // now get the value
774            value = dimens.get("status_bar_height");
775            if (value != null) {
776                TypedValue typedValue = ResourceHelper.getValue(value.getValue());
777                if (typedValue != null) {
778                    // compute the pixel value based on the display metrics
779                    defaultOffset = (int)typedValue.getDimension(context.getResources().mMetrics);
780                }
781            }
782
783            // add the computed offset.
784            offset += defaultOffset;
785        }
786
787        return offset;
788    }
789
790    /**
791     * Post process on a view hierachy that was just inflated.
792     * <p/>At the moment this only support TabHost: If {@link TabHost} is detected, look for the
793     * {@link TabWidget}, and the corresponding {@link FrameLayout} and make new tabs automatically
794     * based on the content of the {@link FrameLayout}.
795     * @param view the root view to process.
796     * @param projectCallback callback to the project.
797     */
798    private void postInflateProcess(View view, IProjectCallback projectCallback)
799            throws PostInflateException {
800        if (view instanceof TabHost) {
801            setupTabHost((TabHost)view, projectCallback);
802        } else if (view instanceof ViewGroup) {
803            ViewGroup group = (ViewGroup)view;
804            final int count = group.getChildCount();
805            for (int c = 0 ; c < count ; c++) {
806                View child = group.getChildAt(c);
807                postInflateProcess(child, projectCallback);
808            }
809        }
810    }
811
812    /**
813     * Sets up a {@link TabHost} object.
814     * @param tabHost the TabHost to setup.
815     * @param projectCallback The project callback object to access the project R class.
816     * @throws PostInflateException
817     */
818    private void setupTabHost(TabHost tabHost, IProjectCallback projectCallback)
819            throws PostInflateException {
820        // look for the TabWidget, and the FrameLayout. They have their own specific names
821        View v = tabHost.findViewById(android.R.id.tabs);
822
823        if (v == null) {
824            throw new PostInflateException(
825                    "TabHost requires a TabWidget with id \"android:id/tabs\".\n");
826        }
827
828        if ((v instanceof TabWidget) == false) {
829            throw new PostInflateException(String.format(
830                    "TabHost requires a TabWidget with id \"android:id/tabs\".\n" +
831                    "View found with id 'tabs' is '%s'", v.getClass().getCanonicalName()));
832        }
833
834        v = tabHost.findViewById(android.R.id.tabcontent);
835
836        if (v == null) {
837            // TODO: see if we can fake tabs even without the FrameLayout (same below when the framelayout is empty)
838            throw new PostInflateException(
839                    "TabHost requires a FrameLayout with id \"android:id/tabcontent\".");
840        }
841
842        if ((v instanceof FrameLayout) == false) {
843            throw new PostInflateException(String.format(
844                    "TabHost requires a FrameLayout with id \"android:id/tabcontent\".\n" +
845                    "View found with id 'tabcontent' is '%s'", v.getClass().getCanonicalName()));
846        }
847
848        FrameLayout content = (FrameLayout)v;
849
850        // now process the content of the framelayout and dynamically create tabs for it.
851        final int count = content.getChildCount();
852
853        if (count == 0) {
854            throw new PostInflateException(
855                    "The FrameLayout for the TabHost has no content. Rendering failed.\n");
856        }
857
858        // this must be called before addTab() so that the TabHost searches its TabWidget
859        // and FrameLayout.
860        tabHost.setup();
861
862        // for each child of the framelayout, add a new TabSpec
863        for (int i = 0 ; i < count ; i++) {
864            View child = content.getChildAt(i);
865            String tabSpec = String.format("tab_spec%d", i+1);
866            int id = child.getId();
867            String[] resource = projectCallback.resolveResourceValue(id);
868            String name;
869            if (resource != null) {
870                name = resource[0]; // 0 is resource name, 1 is resource type.
871            } else {
872                name = String.format("Tab %d", i+1); // default name if id is unresolved.
873            }
874            tabHost.addTab(tabHost.newTabSpec(tabSpec).setIndicator(name).setContent(id));
875        }
876    }
877
878    /**
879     * Returns the bitmap for a specific path, from a specific project cache, or from the
880     * framework cache.
881     * @param value the path of the bitmap
882     * @param projectKey the key of the project, or null to query the framework cache.
883     * @return the cached Bitmap or null if not found.
884     */
885    static Bitmap getCachedBitmap(String value, Object projectKey) {
886        if (projectKey != null) {
887            Map<String, SoftReference<Bitmap>> map = sProjectBitmapCache.get(projectKey);
888            if (map != null) {
889                SoftReference<Bitmap> ref = map.get(value);
890                if (ref != null) {
891                    return ref.get();
892                }
893            }
894        } else {
895            SoftReference<Bitmap> ref = sFrameworkBitmapCache.get(value);
896            if (ref != null) {
897                return ref.get();
898            }
899        }
900
901        return null;
902    }
903
904    /**
905     * Sets a bitmap in a project cache or in the framework cache.
906     * @param value the path of the bitmap
907     * @param bmp the Bitmap object
908     * @param projectKey the key of the project, or null to put the bitmap in the framework cache.
909     */
910    static void setCachedBitmap(String value, Bitmap bmp, Object projectKey) {
911        if (projectKey != null) {
912            Map<String, SoftReference<Bitmap>> map = sProjectBitmapCache.get(projectKey);
913
914            if (map == null) {
915                map = new HashMap<String, SoftReference<Bitmap>>();
916                sProjectBitmapCache.put(projectKey, map);
917            }
918
919            map.put(value, new SoftReference<Bitmap>(bmp));
920        } else {
921            sFrameworkBitmapCache.put(value, new SoftReference<Bitmap>(bmp));
922        }
923    }
924
925    /**
926     * Returns the 9 patch for a specific path, from a specific project cache, or from the
927     * framework cache.
928     * @param value the path of the 9 patch
929     * @param projectKey the key of the project, or null to query the framework cache.
930     * @return the cached 9 patch or null if not found.
931     */
932    static NinePatch getCached9Patch(String value, Object projectKey) {
933        if (projectKey != null) {
934            Map<String, SoftReference<NinePatch>> map = sProject9PatchCache.get(projectKey);
935
936            if (map != null) {
937                SoftReference<NinePatch> ref = map.get(value);
938                if (ref != null) {
939                    return ref.get();
940                }
941            }
942        } else {
943            SoftReference<NinePatch> ref = sFramework9PatchCache.get(value);
944            if (ref != null) {
945                return ref.get();
946            }
947        }
948
949        return null;
950    }
951
952    /**
953     * Sets a 9 patch in a project cache or in the framework cache.
954     * @param value the path of the 9 patch
955     * @param ninePatch the 9 patch object
956     * @param projectKey the key of the project, or null to put the bitmap in the framework cache.
957     */
958    static void setCached9Patch(String value, NinePatch ninePatch, Object projectKey) {
959        if (projectKey != null) {
960            Map<String, SoftReference<NinePatch>> map = sProject9PatchCache.get(projectKey);
961
962            if (map == null) {
963                map = new HashMap<String, SoftReference<NinePatch>>();
964                sProject9PatchCache.put(projectKey, map);
965            }
966
967            map.put(value, new SoftReference<NinePatch>(ninePatch));
968        } else {
969            sFramework9PatchCache.put(value, new SoftReference<NinePatch>(ninePatch));
970        }
971    }
972
973    private static final class PostInflateException extends Exception {
974        private static final long serialVersionUID = 1L;
975
976        public PostInflateException(String message) {
977            super(message);
978        }
979    }
980
981    /**
982     * Implementation of {@link IWindowSession} so that mSession is not null in
983     * the {@link SurfaceView}.
984     */
985    private static final class WindowSession implements IWindowSession {
986
987        @SuppressWarnings("unused")
988        public int add(IWindow arg0, LayoutParams arg1, int arg2, Rect arg3)
989                throws RemoteException {
990            // pass for now.
991            return 0;
992        }
993
994        @SuppressWarnings("unused")
995        public void finishDrawing(IWindow arg0) throws RemoteException {
996            // pass for now.
997        }
998
999        @SuppressWarnings("unused")
1000        public void finishKey(IWindow arg0) throws RemoteException {
1001            // pass for now.
1002        }
1003
1004        @SuppressWarnings("unused")
1005        public boolean getInTouchMode() throws RemoteException {
1006            // pass for now.
1007            return false;
1008        }
1009
1010        @SuppressWarnings("unused")
1011        public boolean performHapticFeedback(IWindow window, int effectId, boolean always) {
1012            // pass for now.
1013            return false;
1014        }
1015
1016        @SuppressWarnings("unused")
1017        public MotionEvent getPendingPointerMove(IWindow arg0) throws RemoteException {
1018            // pass for now.
1019            return null;
1020        }
1021
1022        @SuppressWarnings("unused")
1023        public MotionEvent getPendingTrackballMove(IWindow arg0) throws RemoteException {
1024            // pass for now.
1025            return null;
1026        }
1027
1028        @SuppressWarnings("unused")
1029        public int relayout(IWindow arg0, LayoutParams arg1, int arg2, int arg3, int arg4,
1030                boolean arg4_5, Rect arg5, Rect arg6, Rect arg7, Surface arg8)
1031                throws RemoteException {
1032            // pass for now.
1033            return 0;
1034        }
1035
1036        public void getDisplayFrame(IWindow window, Rect outDisplayFrame) {
1037            // pass for now.
1038        }
1039
1040        @SuppressWarnings("unused")
1041        public void remove(IWindow arg0) throws RemoteException {
1042            // pass for now.
1043        }
1044
1045        @SuppressWarnings("unused")
1046        public void setInTouchMode(boolean arg0) throws RemoteException {
1047            // pass for now.
1048        }
1049
1050        @SuppressWarnings("unused")
1051        public void setTransparentRegion(IWindow arg0, Region arg1) throws RemoteException {
1052            // pass for now.
1053        }
1054
1055        @SuppressWarnings("unused")
1056        public void setInsets(IWindow window, int touchable, Rect contentInsets,
1057                Rect visibleInsets) {
1058            // pass for now.
1059        }
1060
1061        @SuppressWarnings("unused")
1062        public void setWallpaperPosition(IBinder window, float x, float y) {
1063            // pass for now.
1064        }
1065
1066        @SuppressWarnings("unused")
1067        public void wallpaperOffsetsComplete(IBinder window) {
1068            // pass for now.
1069        }
1070
1071        @SuppressWarnings("unused")
1072        public void closeSystemDialogs(String reason) {
1073            // pass for now.
1074        }
1075
1076        public IBinder asBinder() {
1077            // pass for now.
1078            return null;
1079        }
1080    }
1081
1082    /**
1083     * Implementation of {@link IWindow} to pass to the {@link AttachInfo}.
1084     */
1085    private static final class Window implements IWindow {
1086
1087        @SuppressWarnings("unused")
1088        public void dispatchAppVisibility(boolean arg0) throws RemoteException {
1089            // pass for now.
1090        }
1091
1092        @SuppressWarnings("unused")
1093        public void dispatchGetNewSurface() throws RemoteException {
1094            // pass for now.
1095        }
1096
1097        @SuppressWarnings("unused")
1098        public void dispatchKey(KeyEvent arg0) throws RemoteException {
1099            // pass for now.
1100        }
1101
1102        @SuppressWarnings("unused")
1103        public void dispatchPointer(MotionEvent arg0, long arg1, boolean arg2) throws RemoteException {
1104            // pass for now.
1105        }
1106
1107        @SuppressWarnings("unused")
1108        public void dispatchTrackball(MotionEvent arg0, long arg1, boolean arg2) throws RemoteException {
1109            // pass for now.
1110        }
1111
1112        @SuppressWarnings("unused")
1113        public void executeCommand(String arg0, String arg1, ParcelFileDescriptor arg2)
1114                throws RemoteException {
1115            // pass for now.
1116        }
1117
1118        @SuppressWarnings("unused")
1119        public void resized(int arg0, int arg1, Rect arg2, Rect arg3, boolean arg4)
1120                throws RemoteException {
1121            // pass for now.
1122        }
1123
1124        @SuppressWarnings("unused")
1125        public void windowFocusChanged(boolean arg0, boolean arg1) throws RemoteException {
1126            // pass for now.
1127        }
1128
1129        @SuppressWarnings("unused")
1130        public void dispatchWallpaperOffsets(float x, float y, boolean sync) {
1131            // pass for now.
1132        }
1133
1134        @SuppressWarnings("unused")
1135        public void closeSystemDialogs(String reason) {
1136            // pass for now.
1137        }
1138
1139        public IBinder asBinder() {
1140            // pass for now.
1141            return null;
1142        }
1143    }
1144
1145}
1146