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