BridgeInflater.java revision 54b6cfa9a9e5b861a9930af873580d6dc20f773c
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 android.view;
18
19import com.android.layoutlib.api.IProjectCallback;
20import com.android.layoutlib.api.IResourceValue;
21import com.android.layoutlib.bridge.Bridge;
22import com.android.layoutlib.bridge.BridgeConstants;
23import com.android.layoutlib.bridge.BridgeContext;
24import com.android.layoutlib.bridge.BridgeXmlBlockParser;
25
26import org.kxml2.io.KXmlParser;
27import org.xmlpull.v1.XmlPullParser;
28
29import android.content.Context;
30import android.util.AttributeSet;
31
32import java.io.File;
33import java.io.FileReader;
34
35/**
36 * Custom implementation of {@link LayoutInflater} to handle custom views.
37 */
38public final class BridgeInflater extends LayoutInflater {
39
40    private final IProjectCallback mProjectCallback;
41
42    /**
43     * List of class prefixes which are tried first by default.
44     * <p/>
45     * This should match the list in com.android.internal.policy.impl.PhoneLayoutInflater.
46     */
47    private static final String[] sClassPrefixList = {
48        "android.widget.",
49        "android.webkit."
50    };
51
52    protected BridgeInflater(LayoutInflater original, Context newContext) {
53        super(original, newContext);
54        mProjectCallback = null;
55    }
56
57    /**
58     * Instantiate a new BridgeInflater with an {@link IProjectCallback} object.
59     *
60     * @param context The Android application context.
61     * @param projectCallback the {@link IProjectCallback} object.
62     */
63    public BridgeInflater(Context context, IProjectCallback projectCallback) {
64        super(context);
65        mProjectCallback = projectCallback;
66        mConstructorArgs[0] = context;
67    }
68
69    @SuppressWarnings("unchecked")
70    @Override
71    public View onCreateView(String name, AttributeSet attrs) throws ClassNotFoundException {
72        View view = null;
73
74        try {
75            // First try to find a class using the default Android prefixes
76            for (String prefix : sClassPrefixList) {
77                try {
78                    view = createView(name, prefix, attrs);
79                    if (view != null) {
80                        break;
81                    }
82                } catch (ClassNotFoundException e) {
83                    // Ignore. We'll try again using the base class below.
84                }
85            }
86
87            // Next try using the parent loader. This will most likely only work for
88            // fully-qualified class names.
89            try {
90                if (view == null) {
91                    view = super.onCreateView(name, attrs);
92                }
93            } catch (ClassNotFoundException e) {
94                // Ignore. We'll try again using the custom view loader below.
95            }
96
97            // Finally try again using the custom view loader
98            try {
99                if (view == null) {
100                    view = loadCustomView(name, attrs);
101                }
102            } catch (ClassNotFoundException e) {
103                // If the class was not found, we throw the exception directly, because this
104                // method is already expected to throw it.
105                throw e;
106            }
107        } catch (Exception e) {
108            // Wrap the real exception in a ClassNotFoundException, so that the calling method
109            // can deal with it.
110            ClassNotFoundException exception = new ClassNotFoundException("onCreateView", e);
111            throw exception;
112        }
113
114        setupViewInContext(view, attrs);
115
116        return view;
117    }
118
119    @Override
120    public View createViewFromTag(String name, AttributeSet attrs) {
121        View view = null;
122        try {
123            view = super.createViewFromTag(name, attrs);
124        } catch (InflateException e) {
125            // try to load the class from using the custom view loader
126            try {
127                view = loadCustomView(name, attrs);
128            } catch (Exception e2) {
129                // Wrap the real exception in an InflateException so that the calling
130                // method can deal with it.
131                InflateException exception = new InflateException();
132                if (e2.getClass().equals(ClassNotFoundException.class) == false) {
133                    exception.initCause(e2);
134                } else {
135                    exception.initCause(e);
136                }
137                throw exception;
138            }
139        }
140
141        setupViewInContext(view, attrs);
142
143        return view;
144    }
145
146    @Override
147    public View inflate(int resource, ViewGroup root) {
148        Context context = getContext();
149        if (context instanceof BridgeContext) {
150            BridgeContext bridgeContext = (BridgeContext)context;
151
152            IResourceValue value = null;
153
154            String[] layoutInfo = Bridge.resolveResourceValue(resource);
155            if (layoutInfo != null) {
156                value = bridgeContext.getFrameworkResource(BridgeConstants.RES_LAYOUT,
157                        layoutInfo[0]);
158            } else {
159                layoutInfo = mProjectCallback.resolveResourceValue(resource);
160
161                if (layoutInfo != null) {
162                    value = bridgeContext.getProjectResource(BridgeConstants.RES_LAYOUT,
163                            layoutInfo[0]);
164                }
165            }
166
167            if (value != null) {
168                File f = new File(value.getValue());
169                if (f.isFile()) {
170                    try {
171                        KXmlParser parser = new KXmlParser();
172                        parser.setFeature(XmlPullParser.FEATURE_PROCESS_NAMESPACES, true);
173                        parser.setInput(new FileReader(f));
174
175                        BridgeXmlBlockParser bridgeParser = new BridgeXmlBlockParser(
176                                parser, bridgeContext, false);
177
178                        return inflate(bridgeParser, root);
179                    } catch (Exception e) {
180                        bridgeContext.getLogger().error(e);
181                        // return null below.
182                    }
183                }
184            }
185        }
186        return null;
187    }
188
189    private View loadCustomView(String name, AttributeSet attrs) throws ClassNotFoundException,
190            Exception{
191        if (mProjectCallback != null) {
192            // first get the classname in case it's not the node name
193            if (name.equals("view")) {
194                name = attrs.getAttributeValue(null, "class");
195            }
196
197            mConstructorArgs[1] = attrs;
198
199            Object customView = mProjectCallback.loadView(name, mConstructorSignature,
200                    mConstructorArgs);
201
202            if (customView instanceof View) {
203                return (View)customView;
204            }
205        }
206
207        return null;
208    }
209
210
211
212    private void setupViewInContext(View view, AttributeSet attrs) {
213        if (getContext() instanceof BridgeContext) {
214            BridgeContext bc = (BridgeContext) getContext();
215            if (attrs instanceof BridgeXmlBlockParser) {
216                Object viewKey = ((BridgeXmlBlockParser) attrs).getViewKey();
217                if (viewKey != null) {
218                    bc.addViewKey(view, viewKey);
219                }
220            }
221        }
222    }
223
224    @Override
225    public LayoutInflater cloneInContext(Context newContext) {
226        return new BridgeInflater(this, newContext);
227    }
228}
229