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