From 847eb5aba868661b258c8b59cd70ded5264c49fd Mon Sep 17 00:00:00 2001 From: Geoffrey Pitsch Date: Wed, 2 Nov 2016 14:00:02 -0400 Subject: [PATCH] LayoutInflaterBuilder helper in systemui util Useful for tests that want to inflate production layouts but with some classes replaced with Testable/Mock instances. Located in SystemUI until more clients can be found. Test: See ag/1630107 Change-Id: I4addb4cb4adfdf8c3e8f065dd3926574dc352e84 --- .../systemui/util/LayoutInflaterBuilder.java | 162 ++++++++++++++++++ 1 file changed, 162 insertions(+) create mode 100644 packages/SystemUI/src/com/android/systemui/util/LayoutInflaterBuilder.java diff --git a/packages/SystemUI/src/com/android/systemui/util/LayoutInflaterBuilder.java b/packages/SystemUI/src/com/android/systemui/util/LayoutInflaterBuilder.java new file mode 100644 index 0000000000000..f42092130af1e --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/util/LayoutInflaterBuilder.java @@ -0,0 +1,162 @@ +/* + * Copyright (C) 2016 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 com.android.systemui.util; + +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) { + assertIfAlreadyBuilt(); + if (mReplaceMap == null) { + mReplaceMap = new ArrayMap(); + } + mReplaceMap.put(from.getName(), 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); + } + }); + } +}