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