/* * 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 mReplaceMap; private Set 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(); } 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(); } 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); } }); } }