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