blob: 098302e1a0a1cbfeca455ff6bacb8c600a3e5a6b [file] [log] [blame]
Geoffrey Pitsch847eb5a2016-11-02 14:00:02 -04001/*
Jason Monk340b0e52017-03-08 14:57:56 -05002 * Copyright (C) 2017 The Android Open Source Project
Geoffrey Pitsch847eb5a2016-11-02 14:00:02 -04003 *
Jason Monk340b0e52017-03-08 14:57:56 -05004 * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file
5 * except in compliance with the License. You may obtain a copy of the License at
Geoffrey Pitsch847eb5a2016-11-02 14:00:02 -04006 *
7 * http://www.apache.org/licenses/LICENSE-2.0
8 *
Jason Monk340b0e52017-03-08 14:57:56 -05009 * Unless required by applicable law or agreed to in writing, software distributed under the
10 * License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
11 * KIND, either express or implied. See the License for the specific language governing
12 * permissions and limitations under the License.
Geoffrey Pitsch847eb5a2016-11-02 14:00:02 -040013 */
14
Jason Monk340b0e52017-03-08 14:57:56 -050015package android.testing;
Geoffrey Pitsch847eb5a2016-11-02 14:00:02 -040016
17import android.annotation.NonNull;
18import android.content.Context;
19import android.util.ArrayMap;
20import android.util.ArraySet;
21import android.util.AttributeSet;
22import android.util.Log;
23import android.view.LayoutInflater;
24import android.view.View;
25import java.util.Map;
26import java.util.Set;
27
28/**
29 * Builder class to create a {@link LayoutInflater} with various properties.
30 *
31 * Call any desired configuration methods on the Builder and then use
32 * {@link Builder#build} to create the LayoutInflater. This is an alternative to directly using
33 * {@link LayoutInflater#setFilter} and {@link LayoutInflater#setFactory}.
34 * @hide for use by framework
35 */
36public class LayoutInflaterBuilder {
37 private static final String TAG = "LayoutInflaterBuilder";
38
39 private Context mFromContext;
40 private Context mTargetContext;
41 private Map<String, String> mReplaceMap;
42 private Set<Class> mDisallowedClasses;
43 private LayoutInflater mBuiltInflater;
44
45 /**
46 * Creates a new Builder which will construct a LayoutInflater.
47 *
48 * @param fromContext This context's LayoutInflater will be cloned by the Builder using
49 * {@link LayoutInflater#cloneInContext}. By default, the new LayoutInflater will point at
50 * this same Context.
51 */
52 public LayoutInflaterBuilder(@NonNull Context fromContext) {
53 mFromContext = fromContext;
54 mTargetContext = fromContext;
55 mReplaceMap = null;
56 mDisallowedClasses = null;
57 mBuiltInflater = null;
58 }
59
60 /**
61 * Instructs the Builder to point the LayoutInflater at a different Context.
62 *
63 * @param targetContext Context to be provided to
64 * {@link LayoutInflater#cloneInContext(Context)}.
65 * @return Builder object post-modification.
66 */
67 public LayoutInflaterBuilder target(@NonNull Context targetContext) {
68 assertIfAlreadyBuilt();
69 mTargetContext = targetContext;
70 return this;
71 }
72
73 /**
74 * Instructs the Builder to configure the LayoutInflater such that all instances
75 * of one {@link View} will be replaced with instances of another during inflation.
76 *
77 * @param from Instances of this class will be replaced during inflation.
78 * @param to Instances of this class will be inflated as replacements.
79 * @return Builder object post-modification.
80 */
81 public LayoutInflaterBuilder replace(@NonNull Class from, @NonNull Class to) {
Jason Monk3cfedd72016-12-09 09:31:37 -050082 return replace(from.getName(), to);
83 }
84
85 /**
86 * Instructs the Builder to configure the LayoutInflater such that all instances
87 * of one {@link View} will be replaced with instances of another during inflation.
88 *
89 * @param tag Instances of this tag will be replaced during inflation.
90 * @param to Instances of this class will be inflated as replacements.
91 * @return Builder object post-modification.
92 */
93 public LayoutInflaterBuilder replace(@NonNull String tag, @NonNull Class to) {
Geoffrey Pitsch847eb5a2016-11-02 14:00:02 -040094 assertIfAlreadyBuilt();
95 if (mReplaceMap == null) {
96 mReplaceMap = new ArrayMap<String, String>();
97 }
Jason Monk3cfedd72016-12-09 09:31:37 -050098 mReplaceMap.put(tag, to.getName());
Geoffrey Pitsch847eb5a2016-11-02 14:00:02 -040099 return this;
100 }
101
102 /**
103 * Instructs the Builder to configure the LayoutInflater such that any attempt to inflate
104 * a {@link View} of a given type will throw a {@link InflateException}.
105 *
106 * @param disallowedClass The Class type that will be disallowed.
107 * @return Builder object post-modification.
108 */
109 public LayoutInflaterBuilder disallow(@NonNull Class disallowedClass) {
110 assertIfAlreadyBuilt();
111 if (mDisallowedClasses == null) {
112 mDisallowedClasses = new ArraySet<Class>();
113 }
114 mDisallowedClasses.add(disallowedClass);
115 return this;
116 }
117
118 /**
119 * Builds and returns the LayoutInflater. Afterwards, this Builder can no longer can be
120 * used, all future calls on the Builder will throw {@link AssertionError}.
121 */
122 public LayoutInflater build() {
123 assertIfAlreadyBuilt();
124 mBuiltInflater =
125 LayoutInflater.from(mFromContext).cloneInContext(mTargetContext);
126 setFactoryIfNeeded(mBuiltInflater);
127 setFilterIfNeeded(mBuiltInflater);
128 return mBuiltInflater;
129 }
130
131 private void assertIfAlreadyBuilt() {
132 if (mBuiltInflater != null) {
133 throw new AssertionError("Cannot use this Builder after build() has been called.");
134 }
135 }
136
137 private void setFactoryIfNeeded(LayoutInflater inflater) {
138 if (mReplaceMap == null) {
139 return;
140 }
141 inflater.setFactory(
142 new LayoutInflater.Factory() {
143 @Override
144 public View onCreateView(String name, Context context, AttributeSet attrs) {
145 String replacingClassName = mReplaceMap.get(name);
146 if (replacingClassName != null) {
147 try {
148 return inflater.createView(replacingClassName, null, attrs);
149 } catch (ClassNotFoundException e) {
150 Log.e(TAG, "Could not replace " + name
151 + " with " + replacingClassName
152 + ", Exception: ", e);
153 }
154 }
155 return null;
156 }
157 });
158 }
159
160 private void setFilterIfNeeded(LayoutInflater inflater) {
161 if (mDisallowedClasses == null) {
162 return;
163 }
164 inflater.setFilter(
165 new LayoutInflater.Filter() {
166 @Override
167 public boolean onLoadClass(Class clazz) {
168 return !mDisallowedClasses.contains(clazz);
169 }
170 });
171 }
172}