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.ide.common.rendering.api.IProjectCallback;
20import com.android.ide.common.rendering.api.LayoutLog;
21import com.android.ide.common.rendering.api.MergeCookie;
22import com.android.ide.common.rendering.api.ResourceReference;
23import com.android.ide.common.rendering.api.ResourceValue;
24import com.android.layoutlib.bridge.Bridge;
25import com.android.layoutlib.bridge.android.BridgeContext;
26import com.android.layoutlib.bridge.android.BridgeXmlBlockParser;
27import com.android.layoutlib.bridge.impl.ParserFactory;
28import com.android.resources.ResourceType;
29import com.android.util.Pair;
30
31import org.xmlpull.v1.XmlPullParser;
32
33import android.content.Context;
34import android.util.AttributeSet;
35import android.view.InflateException;
36import android.view.LayoutInflater;
37import android.view.View;
38import android.view.ViewGroup;
39
40import java.io.File;
41
42/**
43 * Custom implementation of {@link LayoutInflater} to handle custom views.
44 */
45public final class BridgeInflater extends LayoutInflater {
46
47    private final IProjectCallback mProjectCallback;
48    private boolean mIsInMerge = false;
49    private ResourceReference mResourceReference;
50
51    /**
52     * List of class prefixes which are tried first by default.
53     * <p/>
54     * This should match the list in com.android.internal.policy.impl.PhoneLayoutInflater.
55     */
56    private static final String[] sClassPrefixList = {
57        "android.widget.",
58        "android.webkit."
59    };
60
61    protected BridgeInflater(LayoutInflater original, Context newContext) {
62        super(original, newContext);
63        mProjectCallback = null;
64    }
65
66    /**
67     * Instantiate a new BridgeInflater with an {@link IProjectCallback} object.
68     *
69     * @param context The Android application context.
70     * @param projectCallback the {@link IProjectCallback} object.
71     */
72    public BridgeInflater(Context context, IProjectCallback projectCallback) {
73        super(context);
74        mProjectCallback = projectCallback;
75        mConstructorArgs[0] = context;
76    }
77
78    @Override
79    public View onCreateView(String name, AttributeSet attrs) throws ClassNotFoundException {
80        View view = null;
81
82        try {
83            // First try to find a class using the default Android prefixes
84            for (String prefix : sClassPrefixList) {
85                try {
86                    view = createView(name, prefix, attrs);
87                    if (view != null) {
88                        break;
89                    }
90                } catch (ClassNotFoundException e) {
91                    // Ignore. We'll try again using the base class below.
92                }
93            }
94
95            // Next try using the parent loader. This will most likely only work for
96            // fully-qualified class names.
97            try {
98                if (view == null) {
99                    view = super.onCreateView(name, attrs);
100                }
101            } catch (ClassNotFoundException e) {
102                // Ignore. We'll try again using the custom view loader below.
103            }
104
105            // Finally try again using the custom view loader
106            try {
107                if (view == null) {
108                    view = loadCustomView(name, attrs);
109                }
110            } catch (ClassNotFoundException e) {
111                // If the class was not found, we throw the exception directly, because this
112                // method is already expected to throw it.
113                throw e;
114            }
115        } catch (Exception e) {
116            // Wrap the real exception in a ClassNotFoundException, so that the calling method
117            // can deal with it.
118            ClassNotFoundException exception = new ClassNotFoundException("onCreateView", e);
119            throw exception;
120        }
121
122        setupViewInContext(view, attrs);
123
124        return view;
125    }
126
127    @Override
128    public View createViewFromTag(View parent, String name, AttributeSet attrs) {
129        View view = null;
130        try {
131            view = super.createViewFromTag(parent, name, attrs);
132        } catch (InflateException e) {
133            // try to load the class from using the custom view loader
134            try {
135                view = loadCustomView(name, attrs);
136            } catch (Exception e2) {
137                // Wrap the real exception in an InflateException so that the calling
138                // method can deal with it.
139                InflateException exception = new InflateException();
140                if (e2.getClass().equals(ClassNotFoundException.class) == false) {
141                    exception.initCause(e2);
142                } else {
143                    exception.initCause(e);
144                }
145                throw exception;
146            }
147        }
148
149        setupViewInContext(view, attrs);
150
151        return view;
152    }
153
154    @Override
155    public View inflate(int resource, ViewGroup root) {
156        Context context = getContext();
157        if (context instanceof BridgeContext) {
158            BridgeContext bridgeContext = (BridgeContext)context;
159
160            ResourceValue value = null;
161
162            Pair<ResourceType, String> layoutInfo = Bridge.resolveResourceId(resource);
163            if (layoutInfo != null) {
164                value = bridgeContext.getRenderResources().getFrameworkResource(
165                        ResourceType.LAYOUT, layoutInfo.getSecond());
166            } else {
167                layoutInfo = mProjectCallback.resolveResourceId(resource);
168
169                if (layoutInfo != null) {
170                    value = bridgeContext.getRenderResources().getProjectResource(
171                            ResourceType.LAYOUT, layoutInfo.getSecond());
172                }
173            }
174
175            if (value != null) {
176                File f = new File(value.getValue());
177                if (f.isFile()) {
178                    try {
179                        XmlPullParser parser = ParserFactory.create(f);
180
181                        BridgeXmlBlockParser bridgeParser = new BridgeXmlBlockParser(
182                                parser, bridgeContext, false);
183
184                        return inflate(bridgeParser, root);
185                    } catch (Exception e) {
186                        Bridge.getLog().error(LayoutLog.TAG_RESOURCES_READ,
187                                "Failed to parse file " + f.getAbsolutePath(), e, null /*data*/);
188
189                        return null;
190                    }
191                }
192            }
193        }
194        return null;
195    }
196
197    private View loadCustomView(String name, AttributeSet attrs) throws ClassNotFoundException,
198            Exception{
199        if (mProjectCallback != null) {
200            // first get the classname in case it's not the node name
201            if (name.equals("view")) {
202                name = attrs.getAttributeValue(null, "class");
203            }
204
205            mConstructorArgs[1] = attrs;
206
207            Object customView = mProjectCallback.loadView(name, mConstructorSignature,
208                    mConstructorArgs);
209
210            if (customView instanceof View) {
211                return (View)customView;
212            }
213        }
214
215        return null;
216    }
217
218    private void setupViewInContext(View view, AttributeSet attrs) {
219        if (getContext() instanceof BridgeContext) {
220            BridgeContext bc = (BridgeContext) getContext();
221            if (attrs instanceof BridgeXmlBlockParser) {
222                BridgeXmlBlockParser parser = (BridgeXmlBlockParser) attrs;
223
224                // get the view key
225                Object viewKey = parser.getViewCookie();
226
227                if (viewKey == null) {
228                    int currentDepth = parser.getDepth();
229
230                    // test whether we are in an included file or in a adapter binding view.
231                    BridgeXmlBlockParser previousParser = bc.getPreviousParser();
232                    if (previousParser != null) {
233                        // looks like we inside an embedded layout.
234                        // only apply the cookie of the calling node (<include>) if we are at the
235                        // top level of the embedded layout. If there is a merge tag, then
236                        // skip it and look for the 2nd level
237                        int testDepth = mIsInMerge ? 2 : 1;
238                        if (currentDepth == testDepth) {
239                            viewKey = previousParser.getViewCookie();
240                            // if we are in a merge, wrap the cookie in a MergeCookie.
241                            if (viewKey != null && mIsInMerge) {
242                                viewKey = new MergeCookie(viewKey);
243                            }
244                        }
245                    } else if (mResourceReference != null && currentDepth == 1) {
246                        // else if there's a resource reference, this means we are in an adapter
247                        // binding case. Set the resource ref as the view cookie only for the top
248                        // level view.
249                        viewKey = mResourceReference;
250                    }
251                }
252
253                if (viewKey != null) {
254                    bc.addViewKey(view, viewKey);
255                }
256            }
257        }
258    }
259
260    public void setIsInMerge(boolean isInMerge) {
261        mIsInMerge = isInMerge;
262    }
263
264    public void setResourceReference(ResourceReference reference) {
265        mResourceReference = reference;
266    }
267
268    @Override
269    public LayoutInflater cloneInContext(Context newContext) {
270        return new BridgeInflater(this, newContext);
271    }
272}
273