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