1847eb5aba868661b258c8b59cd70ded5264c49fdGeoffrey Pitsch/*
2340b0e5216b4fcc435e0459b1ca46155a572100dJason Monk * Copyright (C) 2017 The Android Open Source Project
3847eb5aba868661b258c8b59cd70ded5264c49fdGeoffrey Pitsch *
4340b0e5216b4fcc435e0459b1ca46155a572100dJason Monk * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file
5340b0e5216b4fcc435e0459b1ca46155a572100dJason Monk * except in compliance with the License. You may obtain a copy of the License at
6847eb5aba868661b258c8b59cd70ded5264c49fdGeoffrey Pitsch *
7847eb5aba868661b258c8b59cd70ded5264c49fdGeoffrey Pitsch *      http://www.apache.org/licenses/LICENSE-2.0
8847eb5aba868661b258c8b59cd70ded5264c49fdGeoffrey Pitsch *
9340b0e5216b4fcc435e0459b1ca46155a572100dJason Monk * Unless required by applicable law or agreed to in writing, software distributed under the
10340b0e5216b4fcc435e0459b1ca46155a572100dJason Monk * License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
11340b0e5216b4fcc435e0459b1ca46155a572100dJason Monk * KIND, either express or implied. See the License for the specific language governing
12340b0e5216b4fcc435e0459b1ca46155a572100dJason Monk * permissions and limitations under the License.
13847eb5aba868661b258c8b59cd70ded5264c49fdGeoffrey Pitsch */
14847eb5aba868661b258c8b59cd70ded5264c49fdGeoffrey Pitsch
15340b0e5216b4fcc435e0459b1ca46155a572100dJason Monkpackage android.testing;
16847eb5aba868661b258c8b59cd70ded5264c49fdGeoffrey Pitsch
17847eb5aba868661b258c8b59cd70ded5264c49fdGeoffrey Pitschimport android.annotation.NonNull;
18847eb5aba868661b258c8b59cd70ded5264c49fdGeoffrey Pitschimport android.content.Context;
19847eb5aba868661b258c8b59cd70ded5264c49fdGeoffrey Pitschimport android.util.ArrayMap;
20847eb5aba868661b258c8b59cd70ded5264c49fdGeoffrey Pitschimport android.util.ArraySet;
21847eb5aba868661b258c8b59cd70ded5264c49fdGeoffrey Pitschimport android.util.AttributeSet;
22847eb5aba868661b258c8b59cd70ded5264c49fdGeoffrey Pitschimport android.util.Log;
23847eb5aba868661b258c8b59cd70ded5264c49fdGeoffrey Pitschimport android.view.LayoutInflater;
24847eb5aba868661b258c8b59cd70ded5264c49fdGeoffrey Pitschimport android.view.View;
25847eb5aba868661b258c8b59cd70ded5264c49fdGeoffrey Pitschimport java.util.Map;
26847eb5aba868661b258c8b59cd70ded5264c49fdGeoffrey Pitschimport java.util.Set;
27847eb5aba868661b258c8b59cd70ded5264c49fdGeoffrey Pitsch
28847eb5aba868661b258c8b59cd70ded5264c49fdGeoffrey Pitsch/**
29847eb5aba868661b258c8b59cd70ded5264c49fdGeoffrey Pitsch * Builder class to create a {@link LayoutInflater} with various properties.
30847eb5aba868661b258c8b59cd70ded5264c49fdGeoffrey Pitsch *
31847eb5aba868661b258c8b59cd70ded5264c49fdGeoffrey Pitsch * Call any desired configuration methods on the Builder and then use
32847eb5aba868661b258c8b59cd70ded5264c49fdGeoffrey Pitsch * {@link Builder#build} to create the LayoutInflater. This is an alternative to directly using
33847eb5aba868661b258c8b59cd70ded5264c49fdGeoffrey Pitsch * {@link LayoutInflater#setFilter} and {@link LayoutInflater#setFactory}.
34847eb5aba868661b258c8b59cd70ded5264c49fdGeoffrey Pitsch * @hide for use by framework
35847eb5aba868661b258c8b59cd70ded5264c49fdGeoffrey Pitsch */
36847eb5aba868661b258c8b59cd70ded5264c49fdGeoffrey Pitschpublic class LayoutInflaterBuilder {
37847eb5aba868661b258c8b59cd70ded5264c49fdGeoffrey Pitsch    private static final String TAG = "LayoutInflaterBuilder";
38847eb5aba868661b258c8b59cd70ded5264c49fdGeoffrey Pitsch
39847eb5aba868661b258c8b59cd70ded5264c49fdGeoffrey Pitsch    private Context mFromContext;
40847eb5aba868661b258c8b59cd70ded5264c49fdGeoffrey Pitsch    private Context mTargetContext;
41847eb5aba868661b258c8b59cd70ded5264c49fdGeoffrey Pitsch    private Map<String, String> mReplaceMap;
42847eb5aba868661b258c8b59cd70ded5264c49fdGeoffrey Pitsch    private Set<Class> mDisallowedClasses;
43847eb5aba868661b258c8b59cd70ded5264c49fdGeoffrey Pitsch    private LayoutInflater mBuiltInflater;
44847eb5aba868661b258c8b59cd70ded5264c49fdGeoffrey Pitsch
45847eb5aba868661b258c8b59cd70ded5264c49fdGeoffrey Pitsch    /**
46847eb5aba868661b258c8b59cd70ded5264c49fdGeoffrey Pitsch     * Creates a new Builder which will construct a LayoutInflater.
47847eb5aba868661b258c8b59cd70ded5264c49fdGeoffrey Pitsch     *
48847eb5aba868661b258c8b59cd70ded5264c49fdGeoffrey Pitsch     * @param fromContext This context's LayoutInflater will be cloned by the Builder using
49847eb5aba868661b258c8b59cd70ded5264c49fdGeoffrey Pitsch     * {@link LayoutInflater#cloneInContext}. By default, the new LayoutInflater will point at
50847eb5aba868661b258c8b59cd70ded5264c49fdGeoffrey Pitsch     * this same Context.
51847eb5aba868661b258c8b59cd70ded5264c49fdGeoffrey Pitsch     */
52847eb5aba868661b258c8b59cd70ded5264c49fdGeoffrey Pitsch    public LayoutInflaterBuilder(@NonNull Context fromContext) {
53847eb5aba868661b258c8b59cd70ded5264c49fdGeoffrey Pitsch        mFromContext = fromContext;
54847eb5aba868661b258c8b59cd70ded5264c49fdGeoffrey Pitsch        mTargetContext = fromContext;
55847eb5aba868661b258c8b59cd70ded5264c49fdGeoffrey Pitsch        mReplaceMap = null;
56847eb5aba868661b258c8b59cd70ded5264c49fdGeoffrey Pitsch        mDisallowedClasses = null;
57847eb5aba868661b258c8b59cd70ded5264c49fdGeoffrey Pitsch        mBuiltInflater = null;
58847eb5aba868661b258c8b59cd70ded5264c49fdGeoffrey Pitsch    }
59847eb5aba868661b258c8b59cd70ded5264c49fdGeoffrey Pitsch
60847eb5aba868661b258c8b59cd70ded5264c49fdGeoffrey Pitsch    /**
61847eb5aba868661b258c8b59cd70ded5264c49fdGeoffrey Pitsch     * Instructs the Builder to point the LayoutInflater at a different Context.
62847eb5aba868661b258c8b59cd70ded5264c49fdGeoffrey Pitsch     *
63847eb5aba868661b258c8b59cd70ded5264c49fdGeoffrey Pitsch     * @param targetContext Context to be provided to
64847eb5aba868661b258c8b59cd70ded5264c49fdGeoffrey Pitsch     * {@link LayoutInflater#cloneInContext(Context)}.
65847eb5aba868661b258c8b59cd70ded5264c49fdGeoffrey Pitsch     * @return Builder object post-modification.
66847eb5aba868661b258c8b59cd70ded5264c49fdGeoffrey Pitsch     */
67847eb5aba868661b258c8b59cd70ded5264c49fdGeoffrey Pitsch    public LayoutInflaterBuilder target(@NonNull Context targetContext) {
68847eb5aba868661b258c8b59cd70ded5264c49fdGeoffrey Pitsch        assertIfAlreadyBuilt();
69847eb5aba868661b258c8b59cd70ded5264c49fdGeoffrey Pitsch        mTargetContext = targetContext;
70847eb5aba868661b258c8b59cd70ded5264c49fdGeoffrey Pitsch        return this;
71847eb5aba868661b258c8b59cd70ded5264c49fdGeoffrey Pitsch    }
72847eb5aba868661b258c8b59cd70ded5264c49fdGeoffrey Pitsch
73847eb5aba868661b258c8b59cd70ded5264c49fdGeoffrey Pitsch    /**
74847eb5aba868661b258c8b59cd70ded5264c49fdGeoffrey Pitsch     * Instructs the Builder to configure the LayoutInflater such that all instances
75847eb5aba868661b258c8b59cd70ded5264c49fdGeoffrey Pitsch     * of one {@link View} will be replaced with instances of another during inflation.
76847eb5aba868661b258c8b59cd70ded5264c49fdGeoffrey Pitsch     *
77847eb5aba868661b258c8b59cd70ded5264c49fdGeoffrey Pitsch     * @param from Instances of this class will be replaced during inflation.
78847eb5aba868661b258c8b59cd70ded5264c49fdGeoffrey Pitsch     * @param to Instances of this class will be inflated as replacements.
79847eb5aba868661b258c8b59cd70ded5264c49fdGeoffrey Pitsch     * @return Builder object post-modification.
80847eb5aba868661b258c8b59cd70ded5264c49fdGeoffrey Pitsch     */
81847eb5aba868661b258c8b59cd70ded5264c49fdGeoffrey Pitsch    public LayoutInflaterBuilder replace(@NonNull Class from, @NonNull Class to) {
823cfedd78b1e3704481de27d7354cb29b2fb43781Jason Monk        return replace(from.getName(), to);
833cfedd78b1e3704481de27d7354cb29b2fb43781Jason Monk    }
843cfedd78b1e3704481de27d7354cb29b2fb43781Jason Monk
853cfedd78b1e3704481de27d7354cb29b2fb43781Jason Monk    /**
863cfedd78b1e3704481de27d7354cb29b2fb43781Jason Monk     * Instructs the Builder to configure the LayoutInflater such that all instances
873cfedd78b1e3704481de27d7354cb29b2fb43781Jason Monk     * of one {@link View} will be replaced with instances of another during inflation.
883cfedd78b1e3704481de27d7354cb29b2fb43781Jason Monk     *
893cfedd78b1e3704481de27d7354cb29b2fb43781Jason Monk     * @param tag Instances of this tag will be replaced during inflation.
903cfedd78b1e3704481de27d7354cb29b2fb43781Jason Monk     * @param to Instances of this class will be inflated as replacements.
913cfedd78b1e3704481de27d7354cb29b2fb43781Jason Monk     * @return Builder object post-modification.
923cfedd78b1e3704481de27d7354cb29b2fb43781Jason Monk     */
933cfedd78b1e3704481de27d7354cb29b2fb43781Jason Monk    public LayoutInflaterBuilder replace(@NonNull String tag, @NonNull Class to) {
94847eb5aba868661b258c8b59cd70ded5264c49fdGeoffrey Pitsch        assertIfAlreadyBuilt();
95847eb5aba868661b258c8b59cd70ded5264c49fdGeoffrey Pitsch        if (mReplaceMap == null) {
96847eb5aba868661b258c8b59cd70ded5264c49fdGeoffrey Pitsch            mReplaceMap = new ArrayMap<String, String>();
97847eb5aba868661b258c8b59cd70ded5264c49fdGeoffrey Pitsch        }
983cfedd78b1e3704481de27d7354cb29b2fb43781Jason Monk        mReplaceMap.put(tag, to.getName());
99847eb5aba868661b258c8b59cd70ded5264c49fdGeoffrey Pitsch        return this;
100847eb5aba868661b258c8b59cd70ded5264c49fdGeoffrey Pitsch    }
101847eb5aba868661b258c8b59cd70ded5264c49fdGeoffrey Pitsch
102847eb5aba868661b258c8b59cd70ded5264c49fdGeoffrey Pitsch    /**
103847eb5aba868661b258c8b59cd70ded5264c49fdGeoffrey Pitsch     * Instructs the Builder to configure the LayoutInflater such that any attempt to inflate
104847eb5aba868661b258c8b59cd70ded5264c49fdGeoffrey Pitsch     * a {@link View} of a given type will throw a {@link InflateException}.
105847eb5aba868661b258c8b59cd70ded5264c49fdGeoffrey Pitsch     *
106847eb5aba868661b258c8b59cd70ded5264c49fdGeoffrey Pitsch     * @param disallowedClass The Class type that will be disallowed.
107847eb5aba868661b258c8b59cd70ded5264c49fdGeoffrey Pitsch     * @return Builder object post-modification.
108847eb5aba868661b258c8b59cd70ded5264c49fdGeoffrey Pitsch     */
109847eb5aba868661b258c8b59cd70ded5264c49fdGeoffrey Pitsch    public LayoutInflaterBuilder disallow(@NonNull Class disallowedClass) {
110847eb5aba868661b258c8b59cd70ded5264c49fdGeoffrey Pitsch        assertIfAlreadyBuilt();
111847eb5aba868661b258c8b59cd70ded5264c49fdGeoffrey Pitsch        if (mDisallowedClasses == null) {
112847eb5aba868661b258c8b59cd70ded5264c49fdGeoffrey Pitsch            mDisallowedClasses = new ArraySet<Class>();
113847eb5aba868661b258c8b59cd70ded5264c49fdGeoffrey Pitsch        }
114847eb5aba868661b258c8b59cd70ded5264c49fdGeoffrey Pitsch        mDisallowedClasses.add(disallowedClass);
115847eb5aba868661b258c8b59cd70ded5264c49fdGeoffrey Pitsch        return this;
116847eb5aba868661b258c8b59cd70ded5264c49fdGeoffrey Pitsch    }
117847eb5aba868661b258c8b59cd70ded5264c49fdGeoffrey Pitsch
118847eb5aba868661b258c8b59cd70ded5264c49fdGeoffrey Pitsch    /**
119847eb5aba868661b258c8b59cd70ded5264c49fdGeoffrey Pitsch     * Builds and returns the LayoutInflater.  Afterwards, this Builder can no longer can be
120847eb5aba868661b258c8b59cd70ded5264c49fdGeoffrey Pitsch     * used, all future calls on the Builder will throw {@link AssertionError}.
121847eb5aba868661b258c8b59cd70ded5264c49fdGeoffrey Pitsch     */
122847eb5aba868661b258c8b59cd70ded5264c49fdGeoffrey Pitsch    public LayoutInflater build() {
123847eb5aba868661b258c8b59cd70ded5264c49fdGeoffrey Pitsch        assertIfAlreadyBuilt();
124847eb5aba868661b258c8b59cd70ded5264c49fdGeoffrey Pitsch        mBuiltInflater =
125847eb5aba868661b258c8b59cd70ded5264c49fdGeoffrey Pitsch                LayoutInflater.from(mFromContext).cloneInContext(mTargetContext);
126847eb5aba868661b258c8b59cd70ded5264c49fdGeoffrey Pitsch        setFactoryIfNeeded(mBuiltInflater);
127847eb5aba868661b258c8b59cd70ded5264c49fdGeoffrey Pitsch        setFilterIfNeeded(mBuiltInflater);
128847eb5aba868661b258c8b59cd70ded5264c49fdGeoffrey Pitsch        return mBuiltInflater;
129847eb5aba868661b258c8b59cd70ded5264c49fdGeoffrey Pitsch    }
130847eb5aba868661b258c8b59cd70ded5264c49fdGeoffrey Pitsch
131847eb5aba868661b258c8b59cd70ded5264c49fdGeoffrey Pitsch    private void assertIfAlreadyBuilt() {
132847eb5aba868661b258c8b59cd70ded5264c49fdGeoffrey Pitsch        if (mBuiltInflater != null) {
133847eb5aba868661b258c8b59cd70ded5264c49fdGeoffrey Pitsch            throw new AssertionError("Cannot use this Builder after build() has been called.");
134847eb5aba868661b258c8b59cd70ded5264c49fdGeoffrey Pitsch        }
135847eb5aba868661b258c8b59cd70ded5264c49fdGeoffrey Pitsch    }
136847eb5aba868661b258c8b59cd70ded5264c49fdGeoffrey Pitsch
137847eb5aba868661b258c8b59cd70ded5264c49fdGeoffrey Pitsch    private void setFactoryIfNeeded(LayoutInflater inflater) {
138847eb5aba868661b258c8b59cd70ded5264c49fdGeoffrey Pitsch        if (mReplaceMap == null) {
139847eb5aba868661b258c8b59cd70ded5264c49fdGeoffrey Pitsch            return;
140847eb5aba868661b258c8b59cd70ded5264c49fdGeoffrey Pitsch        }
141847eb5aba868661b258c8b59cd70ded5264c49fdGeoffrey Pitsch        inflater.setFactory(
142847eb5aba868661b258c8b59cd70ded5264c49fdGeoffrey Pitsch                new LayoutInflater.Factory() {
143847eb5aba868661b258c8b59cd70ded5264c49fdGeoffrey Pitsch                    @Override
144847eb5aba868661b258c8b59cd70ded5264c49fdGeoffrey Pitsch                    public View onCreateView(String name, Context context, AttributeSet attrs) {
145847eb5aba868661b258c8b59cd70ded5264c49fdGeoffrey Pitsch                        String replacingClassName = mReplaceMap.get(name);
146847eb5aba868661b258c8b59cd70ded5264c49fdGeoffrey Pitsch                        if (replacingClassName != null) {
147847eb5aba868661b258c8b59cd70ded5264c49fdGeoffrey Pitsch                            try {
148847eb5aba868661b258c8b59cd70ded5264c49fdGeoffrey Pitsch                                return inflater.createView(replacingClassName, null, attrs);
149847eb5aba868661b258c8b59cd70ded5264c49fdGeoffrey Pitsch                            } catch (ClassNotFoundException e) {
150847eb5aba868661b258c8b59cd70ded5264c49fdGeoffrey Pitsch                                Log.e(TAG, "Could not replace " + name
151847eb5aba868661b258c8b59cd70ded5264c49fdGeoffrey Pitsch                                        + " with " + replacingClassName
152847eb5aba868661b258c8b59cd70ded5264c49fdGeoffrey Pitsch                                        + ", Exception: ", e);
153847eb5aba868661b258c8b59cd70ded5264c49fdGeoffrey Pitsch                            }
154847eb5aba868661b258c8b59cd70ded5264c49fdGeoffrey Pitsch                        }
155847eb5aba868661b258c8b59cd70ded5264c49fdGeoffrey Pitsch                        return null;
156847eb5aba868661b258c8b59cd70ded5264c49fdGeoffrey Pitsch                    }
157847eb5aba868661b258c8b59cd70ded5264c49fdGeoffrey Pitsch                });
158847eb5aba868661b258c8b59cd70ded5264c49fdGeoffrey Pitsch    }
159847eb5aba868661b258c8b59cd70ded5264c49fdGeoffrey Pitsch
160847eb5aba868661b258c8b59cd70ded5264c49fdGeoffrey Pitsch    private void setFilterIfNeeded(LayoutInflater inflater) {
161847eb5aba868661b258c8b59cd70ded5264c49fdGeoffrey Pitsch        if (mDisallowedClasses == null) {
162847eb5aba868661b258c8b59cd70ded5264c49fdGeoffrey Pitsch            return;
163847eb5aba868661b258c8b59cd70ded5264c49fdGeoffrey Pitsch        }
164847eb5aba868661b258c8b59cd70ded5264c49fdGeoffrey Pitsch        inflater.setFilter(
165847eb5aba868661b258c8b59cd70ded5264c49fdGeoffrey Pitsch                new LayoutInflater.Filter() {
166847eb5aba868661b258c8b59cd70ded5264c49fdGeoffrey Pitsch                    @Override
167847eb5aba868661b258c8b59cd70ded5264c49fdGeoffrey Pitsch                    public boolean onLoadClass(Class clazz) {
168847eb5aba868661b258c8b59cd70ded5264c49fdGeoffrey Pitsch                        return !mDisallowedClasses.contains(clazz);
169847eb5aba868661b258c8b59cd70ded5264c49fdGeoffrey Pitsch                    }
170847eb5aba868661b258c8b59cd70ded5264c49fdGeoffrey Pitsch                });
171847eb5aba868661b258c8b59cd70ded5264c49fdGeoffrey Pitsch    }
172847eb5aba868661b258c8b59cd70ded5264c49fdGeoffrey Pitsch}
173