blob: 6a290b7fe0457e23cfbfa6621618c33a0d6f51df [file] [log] [blame]
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001/*
2 * Copyright (C) 2007 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
Tor Norbye7b9c9122013-05-30 16:48:33 -070019import android.annotation.LayoutRes;
Ian Lake0c807f42018-09-12 14:03:27 -070020import android.annotation.NonNull;
Alan Viverettee8489cd2015-02-03 14:40:45 -080021import android.annotation.Nullable;
Jeff Sharkeyd86b8fe2017-06-02 17:36:26 -060022import android.annotation.SystemService;
Eric Holk25aa4ed2019-01-18 11:49:55 -080023import android.annotation.TestApi;
Mathew Inwooda570dee2018-08-17 14:56:00 +010024import android.annotation.UnsupportedAppUsage;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080025import android.content.Context;
Eric Holk987c7a92019-01-17 10:51:17 -080026import android.content.pm.ApplicationInfo;
Alan Viverette0810b632014-05-01 14:42:56 -070027import android.content.res.Resources;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080028import android.content.res.TypedArray;
29import android.content.res.XmlResourceParser;
Alan Viverettee8489cd2015-02-03 14:40:45 -080030import android.graphics.Canvas;
Cătălin Tudor0c0e82a2018-10-18 18:32:49 +010031import android.os.Build;
Alan Viverettee8489cd2015-02-03 14:40:45 -080032import android.os.Handler;
33import android.os.Message;
Eric Holk928bbac2019-01-02 10:38:54 -080034import android.os.SystemProperties;
Alan Viverettee8489cd2015-02-03 14:40:45 -080035import android.os.Trace;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080036import android.util.AttributeSet;
Alan Viverette0810b632014-05-01 14:42:56 -070037import android.util.Log;
Alan Viverettee8489cd2015-02-03 14:40:45 -080038import android.util.TypedValue;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080039import android.util.Xml;
Alan Viverettee8489cd2015-02-03 14:40:45 -080040import android.widget.FrameLayout;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080041
Aurimas Liutikas67e2ae82016-10-11 18:17:42 -070042import com.android.internal.R;
43
Eric Holk928bbac2019-01-02 10:38:54 -080044import dalvik.system.PathClassLoader;
45import java.io.File;
46import java.lang.reflect.Method;
Aurimas Liutikas67e2ae82016-10-11 18:17:42 -070047import org.xmlpull.v1.XmlPullParser;
48import org.xmlpull.v1.XmlPullParserException;
49
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080050import java.io.IOException;
51import java.lang.reflect.Constructor;
52import java.util.HashMap;
53
54/**
Scott Main93dc6422012-02-24 12:04:06 -080055 * Instantiates a layout XML file into its corresponding {@link android.view.View}
56 * objects. It is never used directly. Instead, use
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080057 * {@link android.app.Activity#getLayoutInflater()} or
58 * {@link Context#getSystemService} to retrieve a standard LayoutInflater instance
59 * that is already hooked up to the current context and correctly configured
Jeff Sharkeyd86b8fe2017-06-02 17:36:26 -060060 * for the device you are running on.
Aurimas Liutikas67e2ae82016-10-11 18:17:42 -070061 *
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080062 * <p>
63 * To create a new LayoutInflater with an additional {@link Factory} for your
64 * own views, you can use {@link #cloneInContext} to clone an existing
65 * ViewFactory, and then call {@link #setFactory} on it to include your
66 * Factory.
Aurimas Liutikas67e2ae82016-10-11 18:17:42 -070067 *
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080068 * <p>
69 * For performance reasons, view inflation relies heavily on pre-processing of
70 * XML files that is done at build time. Therefore, it is not currently possible
71 * to use LayoutInflater with an XmlPullParser over a plain XML file at runtime;
72 * it only works with an XmlPullParser returned from a compiled resource
73 * (R.<em>something</em> file.)
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080074 */
Jeff Sharkeyd86b8fe2017-06-02 17:36:26 -060075@SystemService(Context.LAYOUT_INFLATER_SERVICE)
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080076public abstract class LayoutInflater {
Alan Viverette33e3cda2014-12-17 15:43:29 -080077
Alan Viverette0810b632014-05-01 14:42:56 -070078 private static final String TAG = LayoutInflater.class.getSimpleName();
79 private static final boolean DEBUG = false;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080080
Eric Holk928bbac2019-01-02 10:38:54 -080081 private static final String USE_PRECOMPILED_LAYOUT_SYSTEM_PROPERTY
82 = "view.precompiled_layout_enabled";
83 private static final String COMPILED_VIEW_DEX_FILE_NAME = "/compiled_view.dex";
84
Alan Viverette8f124812015-09-25 15:17:05 -040085 /** Empty stack trace used to avoid log spam in re-throw exceptions. */
86 private static final StackTraceElement[] EMPTY_STACK_TRACE = new StackTraceElement[0];
87
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080088 /**
89 * This field should be made private, so it is hidden from the SDK.
90 * {@hide}
91 */
Cătălin Tudor0c0e82a2018-10-18 18:32:49 +010092 @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P)
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080093 protected final Context mContext;
94
95 // these are optional, set by the caller
Mathew Inwooda570dee2018-08-17 14:56:00 +010096 @UnsupportedAppUsage
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080097 private boolean mFactorySet;
Mathew Inwooda570dee2018-08-17 14:56:00 +010098 @UnsupportedAppUsage
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080099 private Factory mFactory;
Mathew Inwooda570dee2018-08-17 14:56:00 +0100100 @UnsupportedAppUsage
Dianne Hackborn625ac272010-09-17 18:29:22 -0700101 private Factory2 mFactory2;
Mathew Inwooda570dee2018-08-17 14:56:00 +0100102 @UnsupportedAppUsage
Dianne Hackborn420829e2011-01-28 11:30:35 -0800103 private Factory2 mPrivateFactory;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800104 private Filter mFilter;
105
Eric Holk928bbac2019-01-02 10:38:54 -0800106 // Indicates whether we should try to inflate layouts using a precompiled layout instead of
107 // inflating from the XML resource.
108 private boolean mUseCompiledView;
109 // This variable holds the classloader that will be used to look for precompiled layouts. The
110 // The classloader includes the generated compiled_view.dex file.
111 private ClassLoader mPrecompiledClassLoader;
112
Mathew Inwooda570dee2018-08-17 14:56:00 +0100113 @UnsupportedAppUsage
Xavier Ducrohet7f9f99ea2011-08-11 10:16:17 -0700114 final Object[] mConstructorArgs = new Object[2];
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800115
Mathew Inwooda570dee2018-08-17 14:56:00 +0100116 @UnsupportedAppUsage
Xavier Ducrohet7f9f99ea2011-08-11 10:16:17 -0700117 static final Class<?>[] mConstructorSignature = new Class[] {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800118 Context.class, AttributeSet.class};
119
Mathew Inwooda570dee2018-08-17 14:56:00 +0100120 @UnsupportedAppUsage
Gilles Debunne30301932010-06-16 18:32:00 -0700121 private static final HashMap<String, Constructor<? extends View>> sConstructorMap =
122 new HashMap<String, Constructor<? extends View>>();
Aurimas Liutikas67e2ae82016-10-11 18:17:42 -0700123
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800124 private HashMap<String, Boolean> mFilterMap;
125
Alan Viverette33e3cda2014-12-17 15:43:29 -0800126 private TypedValue mTempValue;
127
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800128 private static final String TAG_MERGE = "merge";
129 private static final String TAG_INCLUDE = "include";
Romain Guy9c1223a2011-05-17 14:25:49 -0700130 private static final String TAG_1995 = "blink";
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800131 private static final String TAG_REQUEST_FOCUS = "requestFocus";
Alan Viverette451a3412014-02-11 18:08:46 -0800132 private static final String TAG_TAG = "tag";
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800133
Alan Viverette33e3cda2014-12-17 15:43:29 -0800134 private static final String ATTR_LAYOUT = "layout";
135
Mathew Inwooda570dee2018-08-17 14:56:00 +0100136 @UnsupportedAppUsage
Alan Viveretteef259e42014-01-24 17:20:12 -0800137 private static final int[] ATTRS_THEME = new int[] {
138 com.android.internal.R.attr.theme };
Alan Viverette24927f22014-01-07 17:28:48 -0800139
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800140 /**
141 * Hook to allow clients of the LayoutInflater to restrict the set of Views that are allowed
142 * to be inflated.
Aurimas Liutikas67e2ae82016-10-11 18:17:42 -0700143 *
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800144 */
145 public interface Filter {
146 /**
Aurimas Liutikas67e2ae82016-10-11 18:17:42 -0700147 * Hook to allow clients of the LayoutInflater to restrict the set of Views
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800148 * that are allowed to be inflated.
Aurimas Liutikas67e2ae82016-10-11 18:17:42 -0700149 *
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800150 * @param clazz The class object for the View that is about to be inflated
Aurimas Liutikas67e2ae82016-10-11 18:17:42 -0700151 *
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800152 * @return True if this class is allowed to be inflated, or false otherwise
153 */
Gilles Debunnee6ac8b92010-06-17 10:55:04 -0700154 @SuppressWarnings("unchecked")
155 boolean onLoadClass(Class clazz);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800156 }
Aurimas Liutikas67e2ae82016-10-11 18:17:42 -0700157
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800158 public interface Factory {
159 /**
160 * Hook you can supply that is called when inflating from a LayoutInflater.
161 * You can use this to customize the tag names available in your XML
162 * layout files.
Aurimas Liutikas67e2ae82016-10-11 18:17:42 -0700163 *
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800164 * <p>
165 * Note that it is good practice to prefix these custom names with your
166 * package (i.e., com.coolcompany.apps) to avoid conflicts with system
167 * names.
Aurimas Liutikas67e2ae82016-10-11 18:17:42 -0700168 *
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800169 * @param name Tag name to be inflated.
170 * @param context The context the view is being created in.
171 * @param attrs Inflation attributes as specified in XML file.
Aurimas Liutikas67e2ae82016-10-11 18:17:42 -0700172 *
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800173 * @return View Newly created view. Return null for the default
174 * behavior.
175 */
Ian Lake0c807f42018-09-12 14:03:27 -0700176 @Nullable
177 View onCreateView(@NonNull String name, @NonNull Context context,
178 @NonNull AttributeSet attrs);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800179 }
180
Dianne Hackborn625ac272010-09-17 18:29:22 -0700181 public interface Factory2 extends Factory {
182 /**
183 * Version of {@link #onCreateView(String, Context, AttributeSet)}
184 * that also supplies the parent that the view created view will be
185 * placed in.
186 *
187 * @param parent The parent that the created view will be placed
188 * in; <em>note that this may be null</em>.
189 * @param name Tag name to be inflated.
190 * @param context The context the view is being created in.
191 * @param attrs Inflation attributes as specified in XML file.
192 *
193 * @return View Newly created view. Return null for the default
194 * behavior.
195 */
Ian Lake0c807f42018-09-12 14:03:27 -0700196 @Nullable
197 View onCreateView(@Nullable View parent, @NonNull String name,
198 @NonNull Context context, @NonNull AttributeSet attrs);
Dianne Hackborn625ac272010-09-17 18:29:22 -0700199 }
200
201 private static class FactoryMerger implements Factory2 {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800202 private final Factory mF1, mF2;
Dianne Hackborn625ac272010-09-17 18:29:22 -0700203 private final Factory2 mF12, mF22;
Aurimas Liutikas67e2ae82016-10-11 18:17:42 -0700204
Dianne Hackborn625ac272010-09-17 18:29:22 -0700205 FactoryMerger(Factory f1, Factory2 f12, Factory f2, Factory2 f22) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800206 mF1 = f1;
207 mF2 = f2;
Dianne Hackborn625ac272010-09-17 18:29:22 -0700208 mF12 = f12;
209 mF22 = f22;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800210 }
Aurimas Liutikas67e2ae82016-10-11 18:17:42 -0700211
Ian Lake0c807f42018-09-12 14:03:27 -0700212 @Nullable
213 public View onCreateView(@NonNull String name, @NonNull Context context,
214 @NonNull AttributeSet attrs) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800215 View v = mF1.onCreateView(name, context, attrs);
216 if (v != null) return v;
217 return mF2.onCreateView(name, context, attrs);
218 }
Dianne Hackborn625ac272010-09-17 18:29:22 -0700219
Ian Lake0c807f42018-09-12 14:03:27 -0700220 @Nullable
221 public View onCreateView(@Nullable View parent, @NonNull String name,
222 @NonNull Context context, @NonNull AttributeSet attrs) {
Dianne Hackborn625ac272010-09-17 18:29:22 -0700223 View v = mF12 != null ? mF12.onCreateView(parent, name, context, attrs)
224 : mF1.onCreateView(name, context, attrs);
225 if (v != null) return v;
226 return mF22 != null ? mF22.onCreateView(parent, name, context, attrs)
227 : mF2.onCreateView(name, context, attrs);
228 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800229 }
Aurimas Liutikas67e2ae82016-10-11 18:17:42 -0700230
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800231 /**
232 * Create a new LayoutInflater instance associated with a particular Context.
233 * Applications will almost always want to use
234 * {@link Context#getSystemService Context.getSystemService()} to retrieve
235 * the standard {@link Context#LAYOUT_INFLATER_SERVICE Context.INFLATER_SERVICE}.
Aurimas Liutikas67e2ae82016-10-11 18:17:42 -0700236 *
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800237 * @param context The Context in which this LayoutInflater will create its
238 * Views; most importantly, this supplies the theme from which the default
239 * values for their attributes are retrieved.
240 */
241 protected LayoutInflater(Context context) {
242 mContext = context;
Eric Holk928bbac2019-01-02 10:38:54 -0800243 initPrecompiledViews();
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800244 }
245
246 /**
247 * Create a new LayoutInflater instance that is a copy of an existing
248 * LayoutInflater, optionally with its Context changed. For use in
249 * implementing {@link #cloneInContext}.
Aurimas Liutikas67e2ae82016-10-11 18:17:42 -0700250 *
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800251 * @param original The original LayoutInflater to copy.
252 * @param newContext The new Context to use.
253 */
254 protected LayoutInflater(LayoutInflater original, Context newContext) {
255 mContext = newContext;
256 mFactory = original.mFactory;
Dianne Hackborn625ac272010-09-17 18:29:22 -0700257 mFactory2 = original.mFactory2;
Dianne Hackborn420829e2011-01-28 11:30:35 -0800258 mPrivateFactory = original.mPrivateFactory;
Dan Sandler0c7bb332014-09-18 22:11:18 -0400259 setFilter(original.mFilter);
Eric Holk928bbac2019-01-02 10:38:54 -0800260 initPrecompiledViews();
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800261 }
Aurimas Liutikas67e2ae82016-10-11 18:17:42 -0700262
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800263 /**
264 * Obtains the LayoutInflater from the given context.
265 */
266 public static LayoutInflater from(Context context) {
267 LayoutInflater LayoutInflater =
268 (LayoutInflater) context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
269 if (LayoutInflater == null) {
270 throw new AssertionError("LayoutInflater not found.");
271 }
272 return LayoutInflater;
273 }
274
275 /**
276 * Create a copy of the existing LayoutInflater object, with the copy
277 * pointing to a different Context than the original. This is used by
278 * {@link ContextThemeWrapper} to create a new LayoutInflater to go along
279 * with the new Context theme.
Aurimas Liutikas67e2ae82016-10-11 18:17:42 -0700280 *
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800281 * @param newContext The new Context to associate with the new LayoutInflater.
282 * May be the same as the original Context if desired.
Aurimas Liutikas67e2ae82016-10-11 18:17:42 -0700283 *
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800284 * @return Returns a brand spanking new LayoutInflater object associated with
285 * the given Context.
286 */
287 public abstract LayoutInflater cloneInContext(Context newContext);
Aurimas Liutikas67e2ae82016-10-11 18:17:42 -0700288
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800289 /**
290 * Return the context we are running in, for access to resources, class
291 * loader, etc.
292 */
293 public Context getContext() {
294 return mContext;
295 }
296
297 /**
Dianne Hackborn625ac272010-09-17 18:29:22 -0700298 * Return the current {@link Factory} (or null). This is called on each element
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800299 * name. If the factory returns a View, add that to the hierarchy. If it
300 * returns null, proceed to call onCreateView(name).
301 */
302 public final Factory getFactory() {
303 return mFactory;
304 }
305
306 /**
Dianne Hackborn625ac272010-09-17 18:29:22 -0700307 * Return the current {@link Factory2}. Returns null if no factory is set
308 * or the set factory does not implement the {@link Factory2} interface.
309 * This is called on each element
310 * name. If the factory returns a View, add that to the hierarchy. If it
311 * returns null, proceed to call onCreateView(name).
312 */
313 public final Factory2 getFactory2() {
314 return mFactory2;
315 }
316
317 /**
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800318 * Attach a custom Factory interface for creating views while using
319 * this LayoutInflater. This must not be null, and can only be set once;
320 * after setting, you can not change the factory. This is
321 * called on each element name as the xml is parsed. If the factory returns
322 * a View, that is added to the hierarchy. If it returns null, the next
323 * factory default {@link #onCreateView} method is called.
Aurimas Liutikas67e2ae82016-10-11 18:17:42 -0700324 *
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800325 * <p>If you have an existing
326 * LayoutInflater and want to add your own factory to it, use
327 * {@link #cloneInContext} to clone the existing instance and then you
328 * can use this function (once) on the returned new instance. This will
329 * merge your own factory with whatever factory the original instance is
330 * using.
331 */
332 public void setFactory(Factory factory) {
333 if (mFactorySet) {
334 throw new IllegalStateException("A factory has already been set on this LayoutInflater");
335 }
336 if (factory == null) {
337 throw new NullPointerException("Given factory can not be null");
338 }
339 mFactorySet = true;
340 if (mFactory == null) {
341 mFactory = factory;
342 } else {
Dianne Hackborn625ac272010-09-17 18:29:22 -0700343 mFactory = new FactoryMerger(factory, null, mFactory, mFactory2);
344 }
345 }
346
347 /**
348 * Like {@link #setFactory}, but allows you to set a {@link Factory2}
349 * interface.
350 */
351 public void setFactory2(Factory2 factory) {
352 if (mFactorySet) {
353 throw new IllegalStateException("A factory has already been set on this LayoutInflater");
354 }
355 if (factory == null) {
356 throw new NullPointerException("Given factory can not be null");
357 }
358 mFactorySet = true;
359 if (mFactory == null) {
360 mFactory = mFactory2 = factory;
361 } else {
Adam Powell371a8092014-06-20 12:51:12 -0700362 mFactory = mFactory2 = new FactoryMerger(factory, factory, mFactory, mFactory2);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800363 }
364 }
365
366 /**
Dianne Hackborn420829e2011-01-28 11:30:35 -0800367 * @hide for use by framework
368 */
Mathew Inwooda570dee2018-08-17 14:56:00 +0100369 @UnsupportedAppUsage
Dianne Hackborn420829e2011-01-28 11:30:35 -0800370 public void setPrivateFactory(Factory2 factory) {
Adam Powell371a8092014-06-20 12:51:12 -0700371 if (mPrivateFactory == null) {
372 mPrivateFactory = factory;
373 } else {
374 mPrivateFactory = new FactoryMerger(factory, factory, mPrivateFactory, mPrivateFactory);
375 }
Dianne Hackborn420829e2011-01-28 11:30:35 -0800376 }
377
378 /**
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800379 * @return The {@link Filter} currently used by this LayoutInflater to restrict the set of Views
380 * that are allowed to be inflated.
381 */
382 public Filter getFilter() {
383 return mFilter;
384 }
Aurimas Liutikas67e2ae82016-10-11 18:17:42 -0700385
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800386 /**
387 * Sets the {@link Filter} to by this LayoutInflater. If a view is attempted to be inflated
388 * which is not allowed by the {@link Filter}, the {@link #inflate(int, ViewGroup)} call will
389 * throw an {@link InflateException}. This filter will replace any previous filter set on this
390 * LayoutInflater.
Aurimas Liutikas67e2ae82016-10-11 18:17:42 -0700391 *
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800392 * @param filter The Filter which restricts the set of Views that are allowed to be inflated.
393 * This filter will replace any previous filter set on this LayoutInflater.
394 */
395 public void setFilter(Filter filter) {
396 mFilter = filter;
397 if (filter != null) {
398 mFilterMap = new HashMap<String, Boolean>();
399 }
400 }
401
Eric Holk928bbac2019-01-02 10:38:54 -0800402 private void initPrecompiledViews() {
Eric Holk25aa4ed2019-01-18 11:49:55 -0800403 initPrecompiledViews(
404 SystemProperties.getBoolean(USE_PRECOMPILED_LAYOUT_SYSTEM_PROPERTY, false));
405 }
406
407 private void initPrecompiledViews(boolean enablePrecompiledViews) {
408 mUseCompiledView = enablePrecompiledViews;
409
Eric Holk987c7a92019-01-17 10:51:17 -0800410 if (!mUseCompiledView) {
Eric Holk25aa4ed2019-01-18 11:49:55 -0800411 mPrecompiledClassLoader = null;
Eric Holk987c7a92019-01-17 10:51:17 -0800412 return;
413 }
414
415 // Make sure the application allows code generation
416 ApplicationInfo appInfo = mContext.getApplicationInfo();
Victor Hsiehfa9df0b2019-01-29 12:48:36 -0800417 if (appInfo.isEmbeddedDexUsed() || appInfo.isPrivilegedApp()) {
Eric Holk987c7a92019-01-17 10:51:17 -0800418 mUseCompiledView = false;
419 return;
420 }
421
422 // Try to load the precompiled layout file.
Eric Holk928bbac2019-01-02 10:38:54 -0800423 try {
Eric Holk987c7a92019-01-17 10:51:17 -0800424 mPrecompiledClassLoader = mContext.getClassLoader();
425 String dexFile = mContext.getCodeCacheDir() + COMPILED_VIEW_DEX_FILE_NAME;
426 if (new File(dexFile).exists()) {
427 mPrecompiledClassLoader = new PathClassLoader(dexFile, mPrecompiledClassLoader);
428 } else {
429 // If the precompiled layout file doesn't exist, then disable precompiled
430 // layouts.
431 mUseCompiledView = false;
Eric Holk928bbac2019-01-02 10:38:54 -0800432 }
433 } catch (Throwable e) {
434 if (DEBUG) {
435 Log.e(TAG, "Failed to initialized precompiled views layouts", e);
436 }
437 mUseCompiledView = false;
438 }
Eric Holk25aa4ed2019-01-18 11:49:55 -0800439 if (!mUseCompiledView) {
440 mPrecompiledClassLoader = null;
441 }
442 }
443
444 /**
445 * @hide for use by CTS tests
446 */
447 @TestApi
448 public void setPrecompiledLayoutsEnabledForTesting(boolean enablePrecompiledLayouts) {
449 initPrecompiledViews(enablePrecompiledLayouts);
Eric Holk928bbac2019-01-02 10:38:54 -0800450 }
451
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800452 /**
453 * Inflate a new view hierarchy from the specified xml resource. Throws
454 * {@link InflateException} if there is an error.
Aurimas Liutikas67e2ae82016-10-11 18:17:42 -0700455 *
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800456 * @param resource ID for an XML layout resource to load (e.g.,
457 * <code>R.layout.main_page</code>)
458 * @param root Optional view to be the parent of the generated hierarchy.
459 * @return The root View of the inflated hierarchy. If root was supplied,
460 * this is the root View; otherwise it is the root of the inflated
461 * XML file.
462 */
Tor Norbye7b9c9122013-05-30 16:48:33 -0700463 public View inflate(@LayoutRes int resource, @Nullable ViewGroup root) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800464 return inflate(resource, root, root != null);
465 }
466
467 /**
468 * Inflate a new view hierarchy from the specified xml node. Throws
469 * {@link InflateException} if there is an error. *
470 * <p>
471 * <em><strong>Important</strong></em>&nbsp;&nbsp;&nbsp;For performance
472 * reasons, view inflation relies heavily on pre-processing of XML files
473 * that is done at build time. Therefore, it is not currently possible to
474 * use LayoutInflater with an XmlPullParser over a plain XML file at runtime.
Aurimas Liutikas67e2ae82016-10-11 18:17:42 -0700475 *
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800476 * @param parser XML dom node containing the description of the view
477 * hierarchy.
478 * @param root Optional view to be the parent of the generated hierarchy.
479 * @return The root View of the inflated hierarchy. If root was supplied,
480 * this is the root View; otherwise it is the root of the inflated
481 * XML file.
482 */
Scott Kennedydb5fd422015-01-15 11:56:33 -0800483 public View inflate(XmlPullParser parser, @Nullable ViewGroup root) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800484 return inflate(parser, root, root != null);
485 }
486
487 /**
488 * Inflate a new view hierarchy from the specified xml resource. Throws
489 * {@link InflateException} if there is an error.
Aurimas Liutikas67e2ae82016-10-11 18:17:42 -0700490 *
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800491 * @param resource ID for an XML layout resource to load (e.g.,
492 * <code>R.layout.main_page</code>)
493 * @param root Optional view to be the parent of the generated hierarchy (if
494 * <em>attachToRoot</em> is true), or else simply an object that
495 * provides a set of LayoutParams values for root of the returned
496 * hierarchy (if <em>attachToRoot</em> is false.)
497 * @param attachToRoot Whether the inflated hierarchy should be attached to
498 * the root parameter? If false, root is only used to create the
499 * correct subclass of LayoutParams for the root view in the XML.
500 * @return The root View of the inflated hierarchy. If root was supplied and
501 * attachToRoot is true, this is root; otherwise it is the root of
502 * the inflated XML file.
503 */
Tor Norbye7b9c9122013-05-30 16:48:33 -0700504 public View inflate(@LayoutRes int resource, @Nullable ViewGroup root, boolean attachToRoot) {
Alan Viverette0810b632014-05-01 14:42:56 -0700505 final Resources res = getContext().getResources();
506 if (DEBUG) {
507 Log.d(TAG, "INFLATING from resource: \"" + res.getResourceName(resource) + "\" ("
Eric Holk928bbac2019-01-02 10:38:54 -0800508 + Integer.toHexString(resource) + ")");
Alan Viverette0810b632014-05-01 14:42:56 -0700509 }
510
Eric Holk928bbac2019-01-02 10:38:54 -0800511 View view = tryInflatePrecompiled(resource, res, root, attachToRoot);
512 if (view != null) {
513 return view;
514 }
515 XmlResourceParser parser = res.getLayout(resource);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800516 try {
517 return inflate(parser, root, attachToRoot);
518 } finally {
519 parser.close();
520 }
521 }
522
Eric Holk928bbac2019-01-02 10:38:54 -0800523 private @Nullable
524 View tryInflatePrecompiled(@LayoutRes int resource, Resources res, @Nullable ViewGroup root,
525 boolean attachToRoot) {
526 if (!mUseCompiledView) {
527 return null;
528 }
529
530 Trace.traceBegin(Trace.TRACE_TAG_VIEW, "inflate (precompiled)");
531
532 // Try to inflate using a precompiled layout.
533 String pkg = res.getResourcePackageName(resource);
534 String layout = res.getResourceEntryName(resource);
535
536 try {
537 Class clazz = mPrecompiledClassLoader.loadClass("" + pkg + ".CompiledView");
538 Method inflater = clazz.getMethod(layout, Context.class, int.class);
539 View view = (View) inflater.invoke(null, mContext, resource);
540
541 if (view != null && root != null) {
542 // We were able to use the precompiled inflater, but now we need to do some work to
543 // attach the view to the root correctly.
544 XmlResourceParser parser = res.getLayout(resource);
545 try {
546 AttributeSet attrs = Xml.asAttributeSet(parser);
547 advanceToRootNode(parser);
548 ViewGroup.LayoutParams params = root.generateLayoutParams(attrs);
549
550 if (attachToRoot) {
551 root.addView(view, params);
552 } else {
553 view.setLayoutParams(params);
554 }
555 } finally {
556 parser.close();
557 }
558 }
559
560 return view;
561 } catch (Throwable e) {
562 if (DEBUG) {
563 Log.e(TAG, "Failed to use precompiled view", e);
564 }
565 } finally {
566 Trace.traceEnd(Trace.TRACE_TAG_VIEW);
567 }
568 return null;
569 }
570
571 /**
572 * Advances the given parser to the first START_TAG. Throws InflateException if no start tag is
573 * found.
574 */
575 private void advanceToRootNode(XmlPullParser parser)
576 throws InflateException, IOException, XmlPullParserException {
577 // Look for the root node.
578 int type;
579 while ((type = parser.next()) != XmlPullParser.START_TAG &&
580 type != XmlPullParser.END_DOCUMENT) {
581 // Empty
582 }
583
584 if (type != XmlPullParser.START_TAG) {
585 throw new InflateException(parser.getPositionDescription()
586 + ": No start tag found!");
587 }
588 }
589
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800590 /**
591 * Inflate a new view hierarchy from the specified XML node. Throws
592 * {@link InflateException} if there is an error.
593 * <p>
594 * <em><strong>Important</strong></em>&nbsp;&nbsp;&nbsp;For performance
595 * reasons, view inflation relies heavily on pre-processing of XML files
596 * that is done at build time. Therefore, it is not currently possible to
597 * use LayoutInflater with an XmlPullParser over a plain XML file at runtime.
Aurimas Liutikas67e2ae82016-10-11 18:17:42 -0700598 *
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800599 * @param parser XML dom node containing the description of the view
600 * hierarchy.
601 * @param root Optional view to be the parent of the generated hierarchy (if
602 * <em>attachToRoot</em> is true), or else simply an object that
603 * provides a set of LayoutParams values for root of the returned
604 * hierarchy (if <em>attachToRoot</em> is false.)
605 * @param attachToRoot Whether the inflated hierarchy should be attached to
606 * the root parameter? If false, root is only used to create the
607 * correct subclass of LayoutParams for the root view in the XML.
608 * @return The root View of the inflated hierarchy. If root was supplied and
609 * attachToRoot is true, this is root; otherwise it is the root of
610 * the inflated XML file.
611 */
Scott Kennedydb5fd422015-01-15 11:56:33 -0800612 public View inflate(XmlPullParser parser, @Nullable ViewGroup root, boolean attachToRoot) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800613 synchronized (mConstructorArgs) {
Romain Guy09f7b932013-04-10 11:42:44 -0700614 Trace.traceBegin(Trace.TRACE_TAG_VIEW, "inflate");
615
Alan Viverette6194d722015-03-20 15:49:06 -0700616 final Context inflaterContext = mContext;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800617 final AttributeSet attrs = Xml.asAttributeSet(parser);
Alan Viverette6194d722015-03-20 15:49:06 -0700618 Context lastContext = (Context) mConstructorArgs[0];
619 mConstructorArgs[0] = inflaterContext;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800620 View result = root;
621
622 try {
Eric Holk928bbac2019-01-02 10:38:54 -0800623 advanceToRootNode(parser);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800624 final String name = parser.getName();
Aurimas Liutikas67e2ae82016-10-11 18:17:42 -0700625
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800626 if (DEBUG) {
627 System.out.println("**************************");
628 System.out.println("Creating root view: "
629 + name);
630 System.out.println("**************************");
631 }
632
633 if (TAG_MERGE.equals(name)) {
634 if (root == null || !attachToRoot) {
635 throw new InflateException("<merge /> can be used only with a valid "
636 + "ViewGroup root and attachToRoot=true");
637 }
638
Alan Viverette6194d722015-03-20 15:49:06 -0700639 rInflate(parser, root, inflaterContext, attrs, false);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800640 } else {
641 // Temp is the root view that was found in the xml
Alan Viverette6194d722015-03-20 15:49:06 -0700642 final View temp = createViewFromTag(root, name, inflaterContext, attrs);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800643
644 ViewGroup.LayoutParams params = null;
645
646 if (root != null) {
647 if (DEBUG) {
648 System.out.println("Creating params from root: " +
649 root);
650 }
651 // Create layout params that match root, if supplied
652 params = root.generateLayoutParams(attrs);
653 if (!attachToRoot) {
654 // Set the layout params for temp if we are not
655 // attaching. (If we are, we use addView, below)
656 temp.setLayoutParams(params);
657 }
658 }
659
660 if (DEBUG) {
661 System.out.println("-----> start inflating children");
662 }
Alan Viverette6194d722015-03-20 15:49:06 -0700663
664 // Inflate all children under temp against its context.
665 rInflateChildren(parser, temp, attrs, true);
666
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800667 if (DEBUG) {
668 System.out.println("-----> done inflating children");
669 }
670
671 // We are supposed to attach all the views we found (int temp)
672 // to root. Do that now.
673 if (root != null && attachToRoot) {
674 root.addView(temp, params);
675 }
676
677 // Decide whether to return the root that was passed in or the
678 // top view found in xml.
679 if (root == null || !attachToRoot) {
680 result = temp;
681 }
682 }
683
684 } catch (XmlPullParserException e) {
Alan Viverette8f124812015-09-25 15:17:05 -0400685 final InflateException ie = new InflateException(e.getMessage(), e);
686 ie.setStackTrace(EMPTY_STACK_TRACE);
687 throw ie;
Alan Viverette93795052015-03-09 15:32:50 -0700688 } catch (Exception e) {
Alan Viverette8f124812015-09-25 15:17:05 -0400689 final InflateException ie = new InflateException(parser.getPositionDescription()
690 + ": " + e.getMessage(), e);
691 ie.setStackTrace(EMPTY_STACK_TRACE);
692 throw ie;
Dianne Hackborn9dae48e2010-08-26 10:20:01 -0700693 } finally {
694 // Don't retain static reference on context.
695 mConstructorArgs[0] = lastContext;
696 mConstructorArgs[1] = null;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800697
John Reck465e1a32015-10-19 12:46:09 -0700698 Trace.traceEnd(Trace.TRACE_TAG_VIEW);
699 }
Romain Guy09f7b932013-04-10 11:42:44 -0700700
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800701 return result;
702 }
703 }
704
Mathew Inwood4985c5c2015-12-31 12:28:54 +0000705 private static final ClassLoader BOOT_CLASS_LOADER = LayoutInflater.class.getClassLoader();
706
707 private final boolean verifyClassLoader(Constructor<? extends View> constructor) {
708 final ClassLoader constructorLoader = constructor.getDeclaringClass().getClassLoader();
709 if (constructorLoader == BOOT_CLASS_LOADER) {
710 // fast path for boot class loader (most common case?) - always ok
711 return true;
712 }
713 // in all normal cases (no dynamic code loading), we will exit the following loop on the
714 // first iteration (i.e. when the declaring classloader is the contexts class loader).
715 ClassLoader cl = mContext.getClassLoader();
716 do {
717 if (constructorLoader == cl) {
718 return true;
719 }
720 cl = cl.getParent();
721 } while (cl != null);
722 return false;
723 }
724
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800725 /**
726 * Low-level function for instantiating a view by name. This attempts to
727 * instantiate a view class of the given <var>name</var> found in this
728 * LayoutInflater's ClassLoader.
Aurimas Liutikas67e2ae82016-10-11 18:17:42 -0700729 *
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800730 * <p>
731 * There are two things that can happen in an error case: either the
732 * exception describing the error will be thrown, or a null will be
733 * returned. You must deal with both possibilities -- the former will happen
734 * the first time createView() is called for a class of a particular name,
735 * the latter every time there-after for that class name.
Aurimas Liutikas67e2ae82016-10-11 18:17:42 -0700736 *
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800737 * @param name The full name of the class to be instantiated.
738 * @param attrs The XML attributes supplied for this instance.
Aurimas Liutikas67e2ae82016-10-11 18:17:42 -0700739 *
Gilles Debunne30301932010-06-16 18:32:00 -0700740 * @return View The newly instantiated view, or null.
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800741 */
742 public final View createView(String name, String prefix, AttributeSet attrs)
743 throws ClassNotFoundException, InflateException {
Gilles Debunne30301932010-06-16 18:32:00 -0700744 Constructor<? extends View> constructor = sConstructorMap.get(name);
Mathew Inwood4985c5c2015-12-31 12:28:54 +0000745 if (constructor != null && !verifyClassLoader(constructor)) {
746 constructor = null;
747 sConstructorMap.remove(name);
748 }
Gilles Debunne30301932010-06-16 18:32:00 -0700749 Class<? extends View> clazz = null;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800750
751 try {
Romain Guy09f7b932013-04-10 11:42:44 -0700752 Trace.traceBegin(Trace.TRACE_TAG_VIEW, name);
753
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800754 if (constructor == null) {
755 // Class not found in the cache, see if it's real, and try to add it
Romain Guyd03b8802009-09-16 14:36:16 -0700756 clazz = mContext.getClassLoader().loadClass(
Gilles Debunne30301932010-06-16 18:32:00 -0700757 prefix != null ? (prefix + name) : name).asSubclass(View.class);
Aurimas Liutikas67e2ae82016-10-11 18:17:42 -0700758
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800759 if (mFilter != null && clazz != null) {
760 boolean allowed = mFilter.onLoadClass(clazz);
761 if (!allowed) {
762 failNotAllowed(name, prefix, attrs);
763 }
764 }
765 constructor = clazz.getConstructor(mConstructorSignature);
Alan Viverette904de2e2015-05-04 10:32:57 -0700766 constructor.setAccessible(true);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800767 sConstructorMap.put(name, constructor);
768 } else {
769 // If we have a filter, apply it to cached constructor
770 if (mFilter != null) {
771 // Have we seen this name before?
772 Boolean allowedState = mFilterMap.get(name);
773 if (allowedState == null) {
774 // New class -- remember whether it is allowed
Romain Guyd03b8802009-09-16 14:36:16 -0700775 clazz = mContext.getClassLoader().loadClass(
Gilles Debunne30301932010-06-16 18:32:00 -0700776 prefix != null ? (prefix + name) : name).asSubclass(View.class);
Aurimas Liutikas67e2ae82016-10-11 18:17:42 -0700777
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800778 boolean allowed = clazz != null && mFilter.onLoadClass(clazz);
779 mFilterMap.put(name, allowed);
780 if (!allowed) {
781 failNotAllowed(name, prefix, attrs);
782 }
783 } else if (allowedState.equals(Boolean.FALSE)) {
784 failNotAllowed(name, prefix, attrs);
785 }
786 }
787 }
788
Jason Monk9a376bc2017-05-10 09:52:10 -0400789 Object lastContext = mConstructorArgs[0];
790 if (mConstructorArgs[0] == null) {
791 // Fill in the context if not already within inflation.
792 mConstructorArgs[0] = mContext;
793 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800794 Object[] args = mConstructorArgs;
795 args[1] = attrs;
Jeff Sharkeyb27b7a12012-04-02 21:07:29 -0700796
797 final View view = constructor.newInstance(args);
798 if (view instanceof ViewStub) {
Alan Viverettea9ddb8d2014-09-17 18:14:32 -0700799 // Use the same context when inflating ViewStub later.
Jeff Sharkeyb27b7a12012-04-02 21:07:29 -0700800 final ViewStub viewStub = (ViewStub) view;
Alan Viverettea9ddb8d2014-09-17 18:14:32 -0700801 viewStub.setLayoutInflater(cloneInContext((Context) args[0]));
Jeff Sharkeyb27b7a12012-04-02 21:07:29 -0700802 }
Jason Monk9a376bc2017-05-10 09:52:10 -0400803 mConstructorArgs[0] = lastContext;
Jeff Sharkeyb27b7a12012-04-02 21:07:29 -0700804 return view;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800805
806 } catch (NoSuchMethodException e) {
Alan Viverette8f124812015-09-25 15:17:05 -0400807 final InflateException ie = new InflateException(attrs.getPositionDescription()
808 + ": Error inflating class " + (prefix != null ? (prefix + name) : name), e);
809 ie.setStackTrace(EMPTY_STACK_TRACE);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800810 throw ie;
811
Gilles Debunne30301932010-06-16 18:32:00 -0700812 } catch (ClassCastException e) {
813 // If loaded class is not a View subclass
Alan Viverette8f124812015-09-25 15:17:05 -0400814 final InflateException ie = new InflateException(attrs.getPositionDescription()
815 + ": Class is not a View " + (prefix != null ? (prefix + name) : name), e);
816 ie.setStackTrace(EMPTY_STACK_TRACE);
Gilles Debunne30301932010-06-16 18:32:00 -0700817 throw ie;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800818 } catch (ClassNotFoundException e) {
819 // If loadClass fails, we should propagate the exception.
820 throw e;
821 } catch (Exception e) {
Alan Viverette8f124812015-09-25 15:17:05 -0400822 final InflateException ie = new InflateException(
823 attrs.getPositionDescription() + ": Error inflating class "
824 + (clazz == null ? "<unknown>" : clazz.getName()), e);
825 ie.setStackTrace(EMPTY_STACK_TRACE);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800826 throw ie;
Romain Guy09f7b932013-04-10 11:42:44 -0700827 } finally {
828 Trace.traceEnd(Trace.TRACE_TAG_VIEW);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800829 }
830 }
831
832 /**
Gilles Debunne30301932010-06-16 18:32:00 -0700833 * Throw an exception because the specified class is not allowed to be inflated.
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800834 */
835 private void failNotAllowed(String name, String prefix, AttributeSet attrs) {
Romain Guy9c1223a2011-05-17 14:25:49 -0700836 throw new InflateException(attrs.getPositionDescription()
Alan Viverette8f124812015-09-25 15:17:05 -0400837 + ": Class not allowed to be inflated "+ (prefix != null ? (prefix + name) : name));
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800838 }
839
840 /**
841 * This routine is responsible for creating the correct subclass of View
842 * given the xml element name. Override it to handle custom view objects. If
843 * you override this in your subclass be sure to call through to
844 * super.onCreateView(name) for names you do not recognize.
Aurimas Liutikas67e2ae82016-10-11 18:17:42 -0700845 *
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800846 * @param name The fully qualified class name of the View to be create.
847 * @param attrs An AttributeSet of attributes to apply to the View.
Aurimas Liutikas67e2ae82016-10-11 18:17:42 -0700848 *
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800849 * @return View The View created.
850 */
851 protected View onCreateView(String name, AttributeSet attrs)
852 throws ClassNotFoundException {
853 return createView(name, "android.view.", attrs);
854 }
855
Dianne Hackborn625ac272010-09-17 18:29:22 -0700856 /**
857 * Version of {@link #onCreateView(String, AttributeSet)} that also
Chet Haase430742f2013-04-12 11:18:36 -0700858 * takes the future parent of the view being constructed. The default
Dianne Hackborn625ac272010-09-17 18:29:22 -0700859 * implementation simply calls {@link #onCreateView(String, AttributeSet)}.
860 *
861 * @param parent The future parent of the returned view. <em>Note that
862 * this may be null.</em>
863 * @param name The fully qualified class name of the View to be create.
864 * @param attrs An AttributeSet of attributes to apply to the View.
865 *
866 * @return View The View created.
867 */
868 protected View onCreateView(View parent, String name, AttributeSet attrs)
869 throws ClassNotFoundException {
870 return onCreateView(name, attrs);
871 }
872
Alan Viverette24927f22014-01-07 17:28:48 -0800873 /**
Alan Viverette6194d722015-03-20 15:49:06 -0700874 * Convenience method for calling through to the five-arg createViewFromTag
875 * method. This method passes {@code false} for the {@code ignoreThemeAttr}
876 * argument and should be used for everything except {@code &gt;include>}
877 * tag parsing.
878 */
Mathew Inwooda570dee2018-08-17 14:56:00 +0100879 @UnsupportedAppUsage
Alan Viverette6194d722015-03-20 15:49:06 -0700880 private View createViewFromTag(View parent, String name, Context context, AttributeSet attrs) {
881 return createViewFromTag(parent, name, context, attrs, false);
882 }
883
884 /**
Alan Viverette24927f22014-01-07 17:28:48 -0800885 * Creates a view from a tag name using the supplied attribute set.
886 * <p>
Alan Viverette6194d722015-03-20 15:49:06 -0700887 * <strong>Note:</strong> Default visibility so the BridgeInflater can
888 * override it.
889 *
890 * @param parent the parent view, used to inflate layout params
891 * @param name the name of the XML tag used to define the view
892 * @param context the inflation context for the view, typically the
893 * {@code parent} or base layout inflater context
894 * @param attrs the attribute set for the XML tag used to define the view
895 * @param ignoreThemeAttr {@code true} to ignore the {@code android:theme}
896 * attribute (if set) for the view being inflated,
897 * {@code false} otherwise
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800898 */
Mathew Inwooda570dee2018-08-17 14:56:00 +0100899 @UnsupportedAppUsage
Alan Viverette6194d722015-03-20 15:49:06 -0700900 View createViewFromTag(View parent, String name, Context context, AttributeSet attrs,
901 boolean ignoreThemeAttr) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800902 if (name.equals("view")) {
903 name = attrs.getAttributeValue(null, "class");
904 }
905
Alan Viverette6194d722015-03-20 15:49:06 -0700906 // Apply a theme wrapper, if allowed and one is specified.
907 if (!ignoreThemeAttr) {
908 final TypedArray ta = context.obtainStyledAttributes(attrs, ATTRS_THEME);
909 final int themeResId = ta.getResourceId(0, 0);
910 if (themeResId != 0) {
911 context = new ContextThemeWrapper(context, themeResId);
912 }
913 ta.recycle();
Alan Viverette24927f22014-01-07 17:28:48 -0800914 }
915
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800916 try {
Eric Holk29d0cd62018-12-14 10:27:29 -0800917 View view = tryCreateView(parent, name, context, attrs);
Alan Viverette24927f22014-01-07 17:28:48 -0800918
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800919 if (view == null) {
Alan Viverette24927f22014-01-07 17:28:48 -0800920 final Object lastContext = mConstructorArgs[0];
Alan Viverette6194d722015-03-20 15:49:06 -0700921 mConstructorArgs[0] = context;
Alan Viverette24927f22014-01-07 17:28:48 -0800922 try {
923 if (-1 == name.indexOf('.')) {
924 view = onCreateView(parent, name, attrs);
925 } else {
926 view = createView(name, null, attrs);
927 }
928 } finally {
929 mConstructorArgs[0] = lastContext;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800930 }
931 }
932
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800933 return view;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800934 } catch (InflateException e) {
935 throw e;
936
937 } catch (ClassNotFoundException e) {
Alan Viverette6194d722015-03-20 15:49:06 -0700938 final InflateException ie = new InflateException(attrs.getPositionDescription()
Alan Viverette8f124812015-09-25 15:17:05 -0400939 + ": Error inflating class " + name, e);
940 ie.setStackTrace(EMPTY_STACK_TRACE);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800941 throw ie;
942
943 } catch (Exception e) {
Alan Viverette6194d722015-03-20 15:49:06 -0700944 final InflateException ie = new InflateException(attrs.getPositionDescription()
Alan Viverette8f124812015-09-25 15:17:05 -0400945 + ": Error inflating class " + name, e);
946 ie.setStackTrace(EMPTY_STACK_TRACE);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800947 throw ie;
948 }
949 }
950
951 /**
Eric Holk29d0cd62018-12-14 10:27:29 -0800952 * Tries to create a view from a tag name using the supplied attribute set.
953 *
954 * This method gives the factory provided by {@link LayoutInflater#setFactory} and
955 * {@link LayoutInflater#setFactory2} a chance to create a view. However, it does not apply all
956 * of the general view creation logic, and thus may return {@code null} for some tags. This
957 * method is used by {@link LayoutInflater#inflate} in creating {@code View} objects.
958 *
959 * @hide for use by precompiled layouts.
960 *
961 * @param parent the parent view, used to inflate layout params
962 * @param name the name of the XML tag used to define the view
963 * @param context the inflation context for the view, typically the
964 * {@code parent} or base layout inflater context
965 * @param attrs the attribute set for the XML tag used to define the view
966 */
967 @UnsupportedAppUsage(trackingBug = 122360734)
968 @Nullable
969 public final View tryCreateView(@Nullable View parent, @NonNull String name,
970 @NonNull Context context,
971 @NonNull AttributeSet attrs) {
972 if (name.equals(TAG_1995)) {
973 // Let's party like it's 1995!
974 return new BlinkLayout(context, attrs);
975 }
976
977 View view;
978 if (mFactory2 != null) {
979 view = mFactory2.onCreateView(parent, name, context, attrs);
980 } else if (mFactory != null) {
981 view = mFactory.onCreateView(name, context, attrs);
982 } else {
983 view = null;
984 }
985
986 if (view == null && mPrivateFactory != null) {
987 view = mPrivateFactory.onCreateView(parent, name, context, attrs);
988 }
989
990 return view;
991 }
992
993 /**
Alan Viverette6194d722015-03-20 15:49:06 -0700994 * Recursive method used to inflate internal (non-root) children. This
995 * method calls through to {@link #rInflate} using the parent context as
996 * the inflation context.
997 * <strong>Note:</strong> Default visibility so the BridgeInflater can
998 * call it.
999 */
1000 final void rInflateChildren(XmlPullParser parser, View parent, AttributeSet attrs,
1001 boolean finishInflate) throws XmlPullParserException, IOException {
1002 rInflate(parser, parent, parent.getContext(), attrs, finishInflate);
1003 }
1004
1005 /**
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001006 * Recursive method used to descend down the xml hierarchy and instantiate
1007 * views, instantiate their children, and then call onFinishInflate().
Alan Viverette6194d722015-03-20 15:49:06 -07001008 * <p>
1009 * <strong>Note:</strong> Default visibility so the BridgeInflater can
1010 * override it.
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001011 */
Alan Viverette6194d722015-03-20 15:49:06 -07001012 void rInflate(XmlPullParser parser, View parent, Context context,
1013 AttributeSet attrs, boolean finishInflate) throws XmlPullParserException, IOException {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001014
1015 final int depth = parser.getDepth();
1016 int type;
Evan Rosky37df2db2017-01-24 16:35:52 -08001017 boolean pendingRequestFocus = false;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001018
1019 while (((type = parser.next()) != XmlPullParser.END_TAG ||
1020 parser.getDepth() > depth) && type != XmlPullParser.END_DOCUMENT) {
1021
1022 if (type != XmlPullParser.START_TAG) {
1023 continue;
1024 }
1025
1026 final String name = parser.getName();
Aurimas Liutikas67e2ae82016-10-11 18:17:42 -07001027
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001028 if (TAG_REQUEST_FOCUS.equals(name)) {
Evan Rosky37df2db2017-01-24 16:35:52 -08001029 pendingRequestFocus = true;
1030 consumeChildElements(parser);
Alan Viverette451a3412014-02-11 18:08:46 -08001031 } else if (TAG_TAG.equals(name)) {
1032 parseViewTag(parser, parent, attrs);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001033 } else if (TAG_INCLUDE.equals(name)) {
1034 if (parser.getDepth() == 0) {
1035 throw new InflateException("<include /> cannot be the root element");
1036 }
Alan Viverette6194d722015-03-20 15:49:06 -07001037 parseInclude(parser, context, parent, attrs);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001038 } else if (TAG_MERGE.equals(name)) {
1039 throw new InflateException("<merge /> must be the root element");
1040 } else {
Alan Viverette6194d722015-03-20 15:49:06 -07001041 final View view = createViewFromTag(parent, name, context, attrs);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001042 final ViewGroup viewGroup = (ViewGroup) parent;
1043 final ViewGroup.LayoutParams params = viewGroup.generateLayoutParams(attrs);
Alan Viverette6194d722015-03-20 15:49:06 -07001044 rInflateChildren(parser, view, attrs, true);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001045 viewGroup.addView(view, params);
1046 }
1047 }
1048
Evan Rosky37df2db2017-01-24 16:35:52 -08001049 if (pendingRequestFocus) {
1050 parent.restoreDefaultFocus();
1051 }
1052
Alan Viverette6194d722015-03-20 15:49:06 -07001053 if (finishInflate) {
1054 parent.onFinishInflate();
1055 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001056 }
1057
Alan Viverette451a3412014-02-11 18:08:46 -08001058 /**
Alan Viverette451a3412014-02-11 18:08:46 -08001059 * Parses a <code>&lt;tag&gt;</code> element and sets a keyed tag on the
1060 * containing View.
1061 */
1062 private void parseViewTag(XmlPullParser parser, View view, AttributeSet attrs)
1063 throws XmlPullParserException, IOException {
Alan Viverette6194d722015-03-20 15:49:06 -07001064 final Context context = view.getContext();
1065 final TypedArray ta = context.obtainStyledAttributes(attrs, R.styleable.ViewTag);
1066 final int key = ta.getResourceId(R.styleable.ViewTag_id, 0);
1067 final CharSequence value = ta.getText(R.styleable.ViewTag_value);
Alan Viverette451a3412014-02-11 18:08:46 -08001068 view.setTag(key, value);
1069 ta.recycle();
1070
Alan Viverette6194d722015-03-20 15:49:06 -07001071 consumeChildElements(parser);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001072 }
1073
Mathew Inwooda570dee2018-08-17 14:56:00 +01001074 @UnsupportedAppUsage
Alan Viverette6194d722015-03-20 15:49:06 -07001075 private void parseInclude(XmlPullParser parser, Context context, View parent,
1076 AttributeSet attrs) throws XmlPullParserException, IOException {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001077 int type;
1078
Eric Holkbfc68702019-01-02 11:11:24 -08001079 if (!(parent instanceof ViewGroup)) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001080 throw new InflateException("<include /> can only be used inside of a ViewGroup");
1081 }
1082
Eric Holkbfc68702019-01-02 11:11:24 -08001083 // Apply a theme wrapper, if requested. This is sort of a weird
1084 // edge case, since developers think the <include> overwrites
1085 // values in the AttributeSet of the included View. So, if the
1086 // included View has a theme attribute, we'll need to ignore it.
1087 final TypedArray ta = context.obtainStyledAttributes(attrs, ATTRS_THEME);
1088 final int themeResId = ta.getResourceId(0, 0);
1089 final boolean hasThemeOverride = themeResId != 0;
1090 if (hasThemeOverride) {
1091 context = new ContextThemeWrapper(context, themeResId);
1092 }
1093 ta.recycle();
1094
1095 // If the layout is pointing to a theme attribute, we have to
1096 // massage the value to get a resource identifier out of it.
1097 int layout = attrs.getAttributeResourceValue(null, ATTR_LAYOUT, 0);
1098 if (layout == 0) {
1099 final String value = attrs.getAttributeValue(null, ATTR_LAYOUT);
1100 if (value == null || value.length() <= 0) {
1101 throw new InflateException("You must specify a layout in the"
1102 + " include tag: <include layout=\"@layout/layoutID\" />");
1103 }
1104
1105 // Attempt to resolve the "?attr/name" string to an attribute
1106 // within the default (e.g. application) package.
1107 layout = context.getResources().getIdentifier(
1108 value.substring(1), "attr", context.getPackageName());
1109
1110 }
1111
1112 // The layout might be referencing a theme attribute.
1113 if (mTempValue == null) {
1114 mTempValue = new TypedValue();
1115 }
1116 if (layout != 0 && context.getTheme().resolveAttribute(layout, mTempValue, true)) {
1117 layout = mTempValue.resourceId;
1118 }
1119
1120 if (layout == 0) {
1121 final String value = attrs.getAttributeValue(null, ATTR_LAYOUT);
1122 throw new InflateException("You must specify a valid layout "
1123 + "reference. The layout ID " + value + " is not valid.");
1124 }
1125
Eric Holk928bbac2019-01-02 10:38:54 -08001126 final View precompiled = tryInflatePrecompiled(layout, context.getResources(),
1127 (ViewGroup) parent, /*attachToRoot=*/true);
1128 if (precompiled == null) {
1129 final XmlResourceParser childParser = context.getResources().getLayout(layout);
Eric Holkbfc68702019-01-02 11:11:24 -08001130
Eric Holk928bbac2019-01-02 10:38:54 -08001131 try {
1132 final AttributeSet childAttrs = Xml.asAttributeSet(childParser);
Eric Holkbfc68702019-01-02 11:11:24 -08001133
Eric Holk928bbac2019-01-02 10:38:54 -08001134 while ((type = childParser.next()) != XmlPullParser.START_TAG &&
1135 type != XmlPullParser.END_DOCUMENT) {
1136 // Empty.
1137 }
1138
1139 if (type != XmlPullParser.START_TAG) {
1140 throw new InflateException(childParser.getPositionDescription() +
1141 ": No start tag found!");
1142 }
1143
1144 final String childName = childParser.getName();
1145
1146 if (TAG_MERGE.equals(childName)) {
1147 // The <merge> tag doesn't support android:theme, so
1148 // nothing special to do here.
1149 rInflate(childParser, parent, context, childAttrs, false);
1150 } else {
1151 final View view = createViewFromTag(parent, childName,
1152 context, childAttrs, hasThemeOverride);
1153 final ViewGroup group = (ViewGroup) parent;
1154
1155 final TypedArray a = context.obtainStyledAttributes(
1156 attrs, R.styleable.Include);
1157 final int id = a.getResourceId(R.styleable.Include_id, View.NO_ID);
1158 final int visibility = a.getInt(R.styleable.Include_visibility, -1);
1159 a.recycle();
1160
1161 // We try to load the layout params set in the <include /> tag.
1162 // If the parent can't generate layout params (ex. missing width
1163 // or height for the framework ViewGroups, though this is not
1164 // necessarily true of all ViewGroups) then we expect it to throw
1165 // a runtime exception.
1166 // We catch this exception and set localParams accordingly: true
1167 // means we successfully loaded layout params from the <include>
1168 // tag, false means we need to rely on the included layout params.
1169 ViewGroup.LayoutParams params = null;
1170 try {
1171 params = group.generateLayoutParams(attrs);
1172 } catch (RuntimeException e) {
1173 // Ignore, just fail over to child attrs.
1174 }
1175 if (params == null) {
1176 params = group.generateLayoutParams(childAttrs);
1177 }
1178 view.setLayoutParams(params);
1179
1180 // Inflate all children.
1181 rInflateChildren(childParser, view, childAttrs, true);
1182
1183 if (id != View.NO_ID) {
1184 view.setId(id);
1185 }
1186
1187 switch (visibility) {
1188 case 0:
1189 view.setVisibility(View.VISIBLE);
1190 break;
1191 case 1:
1192 view.setVisibility(View.INVISIBLE);
1193 break;
1194 case 2:
1195 view.setVisibility(View.GONE);
1196 break;
1197 }
1198
1199 group.addView(view);
1200 }
1201 } finally {
1202 childParser.close();
Eric Holkbfc68702019-01-02 11:11:24 -08001203 }
Eric Holkbfc68702019-01-02 11:11:24 -08001204 }
Alan Viverette6194d722015-03-20 15:49:06 -07001205 LayoutInflater.consumeChildElements(parser);
1206 }
1207
1208 /**
1209 * <strong>Note:</strong> default visibility so that
1210 * LayoutInflater_Delegate can call it.
1211 */
1212 final static void consumeChildElements(XmlPullParser parser)
1213 throws XmlPullParserException, IOException {
1214 int type;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001215 final int currentDepth = parser.getDepth();
1216 while (((type = parser.next()) != XmlPullParser.END_TAG ||
1217 parser.getDepth() > currentDepth) && type != XmlPullParser.END_DOCUMENT) {
1218 // Empty
1219 }
Romain Guy9c1223a2011-05-17 14:25:49 -07001220 }
1221
1222 private static class BlinkLayout extends FrameLayout {
1223 private static final int MESSAGE_BLINK = 0x42;
1224 private static final int BLINK_DELAY = 500;
1225
1226 private boolean mBlink;
1227 private boolean mBlinkState;
1228 private final Handler mHandler;
1229
1230 public BlinkLayout(Context context, AttributeSet attrs) {
1231 super(context, attrs);
1232 mHandler = new Handler(new Handler.Callback() {
1233 @Override
1234 public boolean handleMessage(Message msg) {
1235 if (msg.what == MESSAGE_BLINK) {
1236 if (mBlink) {
1237 mBlinkState = !mBlinkState;
1238 makeBlink();
1239 }
1240 invalidate();
1241 return true;
1242 }
1243 return false;
1244 }
1245 });
1246 }
1247
1248 private void makeBlink() {
1249 Message message = mHandler.obtainMessage(MESSAGE_BLINK);
1250 mHandler.sendMessageDelayed(message, BLINK_DELAY);
1251 }
1252
1253 @Override
1254 protected void onAttachedToWindow() {
1255 super.onAttachedToWindow();
1256
1257 mBlink = true;
1258 mBlinkState = true;
1259
1260 makeBlink();
1261 }
1262
1263 @Override
1264 protected void onDetachedFromWindow() {
1265 super.onDetachedFromWindow();
1266
1267 mBlink = false;
1268 mBlinkState = true;
1269
1270 mHandler.removeMessages(MESSAGE_BLINK);
1271 }
1272
1273 @Override
1274 protected void dispatchDraw(Canvas canvas) {
1275 if (mBlinkState) {
1276 super.dispatchDraw(canvas);
1277 }
1278 }
1279 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001280}