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