Internal change

PiperOrigin-RevId: 230609148
diff --git a/pluginapi/src/main/java/org/robolectric/pluginapi/ConfigMerger.java b/pluginapi/src/main/java/org/robolectric/pluginapi/ConfigMerger.java
new file mode 100644
index 0000000..15e72ee
--- /dev/null
+++ b/pluginapi/src/main/java/org/robolectric/pluginapi/ConfigMerger.java
@@ -0,0 +1,10 @@
+package org.robolectric.pluginapi;
+
+import java.lang.reflect.Method;
+import org.robolectric.annotation.Config;
+
+public interface ConfigMerger {
+
+  Config getConfig(Class<?> testClass, Method method, Config globalConfig);
+
+}
diff --git a/pluginapi/src/main/java/org/robolectric/pluginapi/ConfigurationStrategy.java b/pluginapi/src/main/java/org/robolectric/pluginapi/ConfigurationStrategy.java
deleted file mode 100644
index b2e8402..0000000
--- a/pluginapi/src/main/java/org/robolectric/pluginapi/ConfigurationStrategy.java
+++ /dev/null
@@ -1,39 +0,0 @@
-package org.robolectric.pluginapi;
-
-import java.lang.reflect.Method;
-import java.util.Collection;
-
-/**
- * Strategy for configuring individual tests.
- *
- * @since 4.2
- */
-public interface ConfigurationStrategy {
-
-  /**
-   * Determine the configuration for the given test class and method.
-   *
-   * Since a method may be run on multiple test subclasses, {@param testClass} indicates which
-   * test case is currently being evaluated.
-   *
-   * @param testClass the test class being evaluated; this might be a subclass of the method's
-   *     declaring class.
-   * @param method the test method to be evaluated
-   * @return the set of configs
-   */
-  Configuration getConfig(Class<?> testClass, Method method);
-
-  /**
-   * Heterogeneous typesafe collection of configuration objects managed by their {@link Configurer}.
-   *
-   * @since 4.2
-   */
-  interface Configuration {
-
-    /** Returns the configuration instance of the specified class for the current test. */
-    <T> T get(Class<T> configClass);
-
-    /** Returns the set of known configuration classes. */
-    Collection<Class<?>> keySet();
-  }
-}
diff --git a/pluginapi/src/main/java/org/robolectric/pluginapi/Configurer.java b/pluginapi/src/main/java/org/robolectric/pluginapi/Configurer.java
deleted file mode 100644
index 860c6e2..0000000
--- a/pluginapi/src/main/java/org/robolectric/pluginapi/Configurer.java
+++ /dev/null
@@ -1,90 +0,0 @@
-package org.robolectric.pluginapi;
-
-import java.lang.reflect.Method;
-import javax.annotation.Nonnull;
-
-/**
- * Provides configuration data for tests.
- *
- * The test author can apply configuration data at a package, class, or method level, or any
- * combination of those. See [Configuring Robolectric](http://robolectric.org/configuring/) for
- * more details.
- *
- * The implementation of the configurer determines how config information is collected and merged
- * for each test.
- *
- * For a test:
- * ```java
- * class com.foo.MyTest extends com.foo.BaseTest {
- *   {@literal @}Test void testMethod() {}
- * }
- * ```
- * the configuration is applied in the following order:
- *
- * * the {@link #defaultConfig()}.
- * * as specified in /robolectric.properties
- * * as specified in /com/robolectric.properties
- * * as specified in /com/foo/robolectric.properties
- * * as specified in BaseTest
- * * as specified in MyTest
- * * as specified in MyTest.testMethod
- *
- * Configuration objects can be accessed by shadows or tests via
- * {@link org.robolectric.config.ConfigRegistry.get(Class)}.
- *
- * @param <T> the configuration object's type
- */
-public interface Configurer<T> {
-
-  /** Retrieve the class type for this Configurer */
-  Class<T> getConfigClass();
-
-  /**
-   * Returns the default configuration for tests that do not specify a configuration of this type.
-   */
-  @Nonnull T defaultConfig();
-
-  /**
-   * Returns the configuration for a given package.
-   *
-   * This method will be called once for package in the hierarchy leading to the test class being
-   * configured. For example, for `com.example.FooTest`, this method will be called three times
-   * with `"com.example"`, `"com"`, and `""` (representing the top level package).
-   *
-   * @param packageName the name of the package, or the empty string representing the top level
-   *     unnamed package
-   * @return a configuration object, or `null` if the given properties has no relevant data for this
-   *     configuration
-   */
-  T getConfigFor(@Nonnull String packageName);
-
-  /**
-   * Returns the configuration for the given class.
-   *
-   * This method will be called for each class in the test's class inheritance hierarchy.
-   *
-   * @return a configuration object, or `null` if the given class has no relevant data for this
-   *     configuration
-   */
-  T getConfigFor(@Nonnull Class<?> testClass);
-
-  /**
-   * Returns the configuration for the given method.
-   *
-   * @return a configuration object, or `null` if the given method has no relevant data for this
-   *     configuration
-   */
-  T getConfigFor(@Nonnull Method method);
-
-  /**
-   * Merges two configurations.
-   *
-   * This method will called whenever {@link #getConfigFor} returns a non-null configuration object.
-   *
-   * @param parentConfig a less specific configuration object
-   * @param childConfig a more specific configuration object
-   * @return the new configuration with merged parent and child data.
-   */
-  @Nonnull T merge(@Nonnull T parentConfig, @Nonnull T childConfig);
-
-}
diff --git a/pluginapi/src/main/java/org/robolectric/pluginapi/SdkPicker.java b/pluginapi/src/main/java/org/robolectric/pluginapi/SdkPicker.java
index 7e95a84..7954626 100644
--- a/pluginapi/src/main/java/org/robolectric/pluginapi/SdkPicker.java
+++ b/pluginapi/src/main/java/org/robolectric/pluginapi/SdkPicker.java
@@ -2,10 +2,10 @@
 
 import java.util.List;
 import javax.annotation.Nonnull;
-import org.robolectric.pluginapi.ConfigurationStrategy.Configuration;
+import org.robolectric.annotation.Config;
 
 public interface SdkPicker {
 
   @Nonnull
-  List<Sdk> selectSdks(Configuration configuration, UsesSdk usesSdk);
+  List<Sdk> selectSdks(Config config, UsesSdk usesSdk);
 }
diff --git a/robolectric/src/main/java/org/robolectric/ConfigMerger.java b/robolectric/src/main/java/org/robolectric/ConfigMerger.java
index a90427c..0eeacf2 100644
--- a/robolectric/src/main/java/org/robolectric/ConfigMerger.java
+++ b/robolectric/src/main/java/org/robolectric/ConfigMerger.java
@@ -1,151 +1,7 @@
 package org.robolectric;
 
-import static com.google.common.collect.Lists.reverse;
+import org.robolectric.plugins.DefaultConfigMerger;
 
-import com.google.common.annotations.VisibleForTesting;
-import java.io.IOException;
-import java.io.InputStream;
-import java.lang.reflect.Method;
-import java.util.ArrayList;
-import java.util.Arrays;
-import java.util.LinkedHashMap;
-import java.util.List;
-import java.util.Map;
-import java.util.Properties;
-import javax.annotation.Nonnull;
-import javax.annotation.Nullable;
-import org.robolectric.annotation.Config;
-import org.robolectric.util.Join;
-
-/**
- * Computes the effective Robolectric configuration for a given test method.
- *
- * This class is no longer used directly by Robolectric, but is left for convenience during
- * migration.
- *
- * @deprecated Provide an implementation of {@link javax.inject.Provider<Config>} instead. See
- *     [Migration Notes](http://robolectric.org/migrating/#migrating-to-40) for details. This
- *     class will be removed in Robolectric 4.3.
- */
+/** @deprecated use {@link org.robolectric.plugins.DefaultConfigMerger} instead. */
 @Deprecated
-public class ConfigMerger {
-  private final Map<String, Config> packageConfigCache = new LinkedHashMap<String, Config>() {
-    @Override
-    protected boolean removeEldestEntry(Map.Entry eldest) {
-      return size() > 10;
-    }
-  };
-
-  /**
-   * Calculate the {@link Config} for the given test.
-   *
-   * @param testClass the class containing the test
-   * @param method the test method
-   * @param globalConfig global configuration values
-   * @return the effective configuration
-   * @since 3.2
-   */
-  public Config getConfig(Class<?> testClass, Method method, Config globalConfig) {
-    Config config = Config.Builder.defaults().build();
-    config = override(config, globalConfig);
-
-    for (String packageName : reverse(packageHierarchyOf(testClass))) {
-      Config packageConfig = cachedPackageConfig(packageName);
-      config = override(config, packageConfig);
-    }
-
-    for (Class clazz : reverse(parentClassesFor(testClass))) {
-      Config classConfig = (Config) clazz.getAnnotation(Config.class);
-      config = override(config, classConfig);
-    }
-
-    Config methodConfig = method.getAnnotation(Config.class);
-    config = override(config, methodConfig);
-
-    return config;
-  }
-
-  /**
-   * Generate {@link Config} for the specified package.
-   *
-   * More specific packages, test classes, and test method configurations
-   * will override values provided here.
-   *
-   * The default implementation uses properties provided by {@link #getConfigProperties(String)}.
-   *
-   * The returned object is likely to be reused for many tests.
-   *
-   * @param packageName the name of the package, or empty string ({@code ""}) for the top level package
-   * @return {@link Config} object for the specified package
-   * @since 3.2
-   */
-  @Nullable
-  private Config buildPackageConfig(String packageName) {
-    return Config.Implementation.fromProperties(getConfigProperties(packageName));
-  }
-
-  /**
-   * Return a {@link Properties} file for the given package name, or {@code null} if none is available.
-   *
-   * @since 3.2
-   */
-  protected Properties getConfigProperties(String packageName) {
-    List<String> packageParts = new ArrayList<>(Arrays.asList(packageName.split("\\.")));
-    packageParts.add(RobolectricTestRunner.CONFIG_PROPERTIES);
-    final String resourceName = Join.join("/", packageParts);
-    try (InputStream resourceAsStream = getResourceAsStream(resourceName)) {
-      if (resourceAsStream == null) return null;
-      Properties properties = new Properties();
-      properties.load(resourceAsStream);
-      return properties;
-    } catch (IOException e) {
-      throw new RuntimeException(e);
-    }
-  }
-
-  @Nonnull @VisibleForTesting
-  List<String> packageHierarchyOf(Class<?> javaClass) {
-    Package aPackage = javaClass.getPackage();
-    String testPackageName = aPackage == null ? "" : aPackage.getName();
-    List<String> packageHierarchy = new ArrayList<>();
-    while (!testPackageName.isEmpty()) {
-      packageHierarchy.add(testPackageName);
-      int lastDot = testPackageName.lastIndexOf('.');
-      testPackageName = lastDot > 1 ? testPackageName.substring(0, lastDot) : "";
-    }
-    packageHierarchy.add("");
-    return packageHierarchy;
-  }
-
-  @Nonnull
-  private List<Class> parentClassesFor(Class testClass) {
-    List<Class> testClassHierarchy = new ArrayList<>();
-    while (testClass != null && !testClass.equals(Object.class)) {
-      testClassHierarchy.add(testClass);
-      testClass = testClass.getSuperclass();
-    }
-    return testClassHierarchy;
-  }
-
-  private Config override(Config config, Config classConfig) {
-    return classConfig != null ? new Config.Builder(config).overlay(classConfig).build() : config;
-  }
-
-  @Nullable
-  private Config cachedPackageConfig(String packageName) {
-    synchronized (packageConfigCache) {
-      Config config = packageConfigCache.get(packageName);
-      if (config == null && !packageConfigCache.containsKey(packageName)) {
-        config = buildPackageConfig(packageName);
-        packageConfigCache.put(packageName, config);
-      }
-      return config;
-    }
-  }
-
-  // visible for testing
-  @SuppressWarnings("WeakerAccess")
-  InputStream getResourceAsStream(String resourceName) {
-    return getClass().getClassLoader().getResourceAsStream(resourceName);
-  }
-}
+public class ConfigMerger extends DefaultConfigMerger {}
diff --git a/robolectric/src/main/java/org/robolectric/RobolectricTestRunner.java b/robolectric/src/main/java/org/robolectric/RobolectricTestRunner.java
index 347a457..b472ad4 100644
--- a/robolectric/src/main/java/org/robolectric/RobolectricTestRunner.java
+++ b/robolectric/src/main/java/org/robolectric/RobolectricTestRunner.java
@@ -2,7 +2,6 @@
 
 
 import android.os.Build;
-import com.google.auto.service.AutoService;
 import com.google.common.annotations.VisibleForTesting;
 import com.google.common.collect.ImmutableMap;
 import java.io.IOException;
@@ -19,7 +18,6 @@
 import java.util.Properties;
 import java.util.ServiceLoader;
 import javax.annotation.Nonnull;
-import javax.annotation.Priority;
 import javax.inject.Inject;
 import org.junit.AssumptionViolatedException;
 import org.junit.Ignore;
@@ -49,12 +47,8 @@
 import org.robolectric.internal.bytecode.ShadowMap;
 import org.robolectric.internal.bytecode.ShadowWrangler;
 import org.robolectric.manifest.AndroidManifest;
-import org.robolectric.pluginapi.ConfigurationStrategy;
-import org.robolectric.pluginapi.ConfigurationStrategy.Configuration;
 import org.robolectric.pluginapi.Sdk;
 import org.robolectric.pluginapi.SdkPicker;
-import org.robolectric.plugins.ConfigConfigurer.DefaultConfigProvider;
-import org.robolectric.plugins.HierarchicalConfigurationStrategy.ConfigurationImpl;
 import org.robolectric.util.PerfStatsCollector;
 import org.robolectric.util.ReflectionHelpers;
 import org.robolectric.util.inject.Injector;
@@ -95,15 +89,16 @@
     final SandboxFactory sandboxFactory;
     final ApkLoader apkLoader;
     final SdkPicker sdkPicker;
-    final ConfigurationStrategy configurationStrategy;
+    final org.robolectric.pluginapi.ConfigMerger configMerger;
 
     @Inject
-    public Ctx(SandboxFactory sandboxFactory, ApkLoader apkLoader, SdkPicker sdkPicker,
-        ConfigurationStrategy configurationStrategy) {
+    public Ctx(SandboxFactory sandboxFactory, ApkLoader apkLoader,
+        SdkPicker sdkPicker,
+        org.robolectric.pluginapi.ConfigMerger configMerger) {
       this.sandboxFactory = sandboxFactory;
       this.apkLoader = apkLoader;
       this.sdkPicker = sdkPicker;
-      this.configurationStrategy = configurationStrategy;
+      this.configMerger = configMerger;
     }
   }
 
@@ -121,10 +116,6 @@
       throws InitializationError {
     super(testClass);
 
-    if (DeprecatedTestRunnerDefaultConfigProvider.globalConfig == null) {
-      DeprecatedTestRunnerDefaultConfigProvider.globalConfig = buildGlobalConfig();
-    }
-
     ctx = injector.getInstance(Ctx.class);
   }
 
@@ -166,12 +157,9 @@
    */
   @Override @Nonnull
   protected InstrumentationConfiguration createClassLoaderConfig(final FrameworkMethod method) {
-    Configuration configuration = ((RobolectricFrameworkMethod) method).config;
-    Config config = configuration.get(Config.class);
-
     Builder builder = new Builder(super.createClassLoaderConfig(method));
     AndroidConfigurer.configure(builder, getInterceptors());
-    AndroidConfigurer.withConfig(builder, config);
+    AndroidConfigurer.withConfig(builder, ((RobolectricFrameworkMethod) method).config);
     return builder.build();
   }
 
@@ -231,11 +219,10 @@
     List<FrameworkMethod> children = new ArrayList<>();
     for (FrameworkMethod frameworkMethod : super.getChildren()) {
       try {
-        Configuration configuration = getConfiguration(frameworkMethod.getMethod());
+        Config config = getConfig(frameworkMethod.getMethod());
+        AndroidManifest appManifest = getAppManifest(config);
 
-        AndroidManifest appManifest = getAppManifest(configuration);
-
-        List<Sdk> sdksToRun = ctx.sdkPicker.selectSdks(configuration, appManifest);
+        List<Sdk> sdksToRun = ctx.sdkPicker.selectSdks(config, appManifest);
         RobolectricFrameworkMethod last = null;
         for (Sdk sdk : sdksToRun) {
           if (resourcesMode.includeLegacy(appManifest)) {
@@ -245,7 +232,7 @@
                         frameworkMethod.getMethod(),
                         appManifest,
                         sdk,
-                        configuration,
+                        config,
                         ResourcesMode.legacy,
                         resourcesMode,
                         alwaysIncludeVariantMarkersInName));
@@ -257,7 +244,7 @@
                         frameworkMethod.getMethod(),
                         appManifest,
                         sdk,
-                        configuration,
+                        config,
                         ResourcesMode.binary,
                         resourcesMode,
                         alwaysIncludeVariantMarkersInName));
@@ -411,27 +398,27 @@
   }
 
   protected Properties getBuildSystemApiProperties() {
-    return staticGetBuildSystemApiProperties();
-  }
+    InputStream resourceAsStream = getClass().getResourceAsStream("/com/android/tools/test_config.properties");
+    if (resourceAsStream == null) {
+      return null;
+    }
 
-  protected static Properties staticGetBuildSystemApiProperties() {
-    try (InputStream resourceAsStream =
-        RobolectricTestRunner.class.getResourceAsStream(
-            "/com/android/tools/test_config.properties")) {
-      if (resourceAsStream == null) {
-        return null;
-      }
-
+    try {
       Properties properties = new Properties();
       properties.load(resourceAsStream);
       return properties;
     } catch (IOException e) {
       return null;
+    } finally {
+      try {
+        resourceAsStream.close();
+      } catch (IOException e) {
+        // ignore
+      }
     }
   }
 
-  private AndroidManifest getAppManifest(Configuration configuration) {
-    Config config = configuration.get(Config.class);
+  private AndroidManifest getAppManifest(Config config) {
     ManifestFactory manifestFactory = getManifestFactory(config);
     ManifestIdentifier identifier = manifestFactory.identify(config);
 
@@ -481,29 +468,10 @@
    *
    * @param method the test method
    * @return the effective Robolectric configuration for the given test method
-   * @deprecated Provide an implementation of {@link javax.inject.Provider<Config>} instead. See
-   *     [Migration Notes](http://robolectric.org/migrating/#migrating-to-40) for details. This
-   *     method will be removed in Robolectric 4.3.
    * @since 2.0
    */
-  @Deprecated
   public Config getConfig(Method method) {
-    throw new UnsupportedOperationException();
-  }
-
-  private Configuration getConfiguration(Method method) {
-    Configuration configuration =
-        ctx.configurationStrategy.getConfig(getTestClass().getJavaClass(), method);
-
-    // in case #getConfig(Method) has been overridden...
-    try {
-      Config config = getConfig(method);
-      ((ConfigurationImpl) configuration).put(Config.class, config);
-    } catch (UnsupportedOperationException e) {
-      // no problem
-    }
-
-    return configuration;
+    return ctx.configMerger.getConfig(getTestClass().getJavaClass(), method, buildGlobalConfig());
   }
 
   /**
@@ -518,31 +486,15 @@
    * The default implementation has appropriate values for most use cases.
    *
    * @return global {@link Config} object
-   * @deprecated Provide a service implementation of {@link DefaultConfigProvider} instead. See
-   *     [Migration Notes](http://robolectric.org/migrating/#migrating-to-40) for details. This
-   *     method will be removed in Robolectric 4.3.
    * @since 3.1.3
    */
-  @Deprecated
   protected Config buildGlobalConfig() {
     return new Config.Builder().build();
   }
 
-  @AutoService(DefaultConfigProvider.class)
-  @Priority(Integer.MIN_VALUE)
-  @Deprecated
-  public static class DeprecatedTestRunnerDefaultConfigProvider implements DefaultConfigProvider {
-    static Config globalConfig;
-
-    @Override
-    public Config get() {
-      return globalConfig;
-    }
-  }
-
   @Override @Nonnull
   protected Class<?>[] getExtraShadows(FrameworkMethod frameworkMethod) {
-    Config config = ((RobolectricFrameworkMethod) frameworkMethod).config.get(Config.class);
+    Config config = ((RobolectricFrameworkMethod) frameworkMethod).config;
     return config.shadows();
   }
 
@@ -617,7 +569,7 @@
 
     private final @Nonnull AndroidManifest appManifest;
     private final int apiLevel;
-    final @Nonnull Configuration config;
+    final @Nonnull Config config;
     final ResourcesMode resourcesMode;
     private final ResourcesMode defaultResourcesMode;
     private final boolean alwaysIncludeVariantMarkersInName;
@@ -630,7 +582,7 @@
         @Nonnull Method method,
         @Nonnull AndroidManifest appManifest,
         @Nonnull Sdk sdk,
-        @Nonnull Configuration config,
+        @Nonnull Config config,
         ResourcesMode resourcesMode,
         ResourcesMode defaultResourcesMode,
         boolean alwaysIncludeVariantMarkersInName) {
diff --git a/robolectric/src/main/java/org/robolectric/android/internal/ParallelUniverse.java b/robolectric/src/main/java/org/robolectric/android/internal/ParallelUniverse.java
index cd9546a..25cb139 100755
--- a/robolectric/src/main/java/org/robolectric/android/internal/ParallelUniverse.java
+++ b/robolectric/src/main/java/org/robolectric/android/internal/ParallelUniverse.java
@@ -19,6 +19,7 @@
 import android.content.pm.PackageParser;
 import android.content.pm.PackageParser.Package;
 import android.content.res.AssetManager;
+import android.content.res.Configuration;
 import android.content.res.Resources;
 import android.os.Build;
 import android.os.Build.VERSION_CODES;
@@ -40,13 +41,11 @@
 import org.robolectric.android.Bootstrap;
 import org.robolectric.android.fakes.RoboMonitoringInstrumentation;
 import org.robolectric.annotation.Config;
-import org.robolectric.config.ConfigurationRegistry;
 import org.robolectric.internal.ParallelUniverseInterface;
 import org.robolectric.internal.SdkEnvironment;
 import org.robolectric.manifest.AndroidManifest;
 import org.robolectric.manifest.BroadcastReceiverData;
 import org.robolectric.manifest.RoboNotFoundException;
-import org.robolectric.pluginapi.ConfigurationStrategy.Configuration;
 import org.robolectric.pluginapi.Sdk;
 import org.robolectric.res.Fs;
 import org.robolectric.res.PackageResourceTable;
@@ -94,12 +93,8 @@
   }
 
   @Override
-  public void setUpApplicationState(ApkLoader apkLoader, Method method,
-      Configuration configuration, AndroidManifest appManifest, SdkEnvironment sdkEnvironment) {
-    Config config = configuration.get(Config.class);
-
-    ConfigurationRegistry.instance = new ConfigurationRegistry(configuration);
-
+  public void setUpApplicationState(ApkLoader apkLoader, Method method, Config config,
+      AndroidManifest appManifest, SdkEnvironment sdkEnvironment) {
     ReflectionHelpers.setStaticField(RuntimeEnvironment.class, "apiLevel", apiLevel);
 
     RuntimeEnvironment.application = null;
@@ -117,16 +112,15 @@
       Security.insertProviderAt(new BouncyCastleProvider(), 1);
     }
 
-    android.content.res.Configuration androidConfiguration =
-        new android.content.res.Configuration();
+    Configuration configuration = new Configuration();
     DisplayMetrics displayMetrics = new DisplayMetrics();
 
-    Bootstrap.applyQualifiers(config.qualifiers(), apiLevel, androidConfiguration,
+    Bootstrap.applyQualifiers(config.qualifiers(), apiLevel, configuration,
         displayMetrics);
 
     Locale locale = apiLevel >= VERSION_CODES.N
-        ? androidConfiguration.getLocales().get(0)
-        : androidConfiguration.locale;
+        ? configuration.getLocales().get(0)
+        : configuration.locale;
     Locale.setDefault(locale);
 
     // Looper needs to be prepared before the activity thread is created
@@ -159,15 +153,15 @@
     // code in there that can be reusable, e.g: the XxxxIntentResolver code.
     ShadowActivityThread.setApplicationInfo(applicationInfo);
 
-    _activityThread_.setCompatConfiguration(androidConfiguration);
+    _activityThread_.setCompatConfiguration(configuration);
     ReflectionHelpers
         .setStaticField(ActivityThread.class, "sMainThreadHandler", new Handler(Looper.myLooper()));
 
-    Bootstrap.setUpDisplay(androidConfiguration, displayMetrics);
-    activityThread.applyConfigurationToResources(androidConfiguration);
+    Bootstrap.setUpDisplay(configuration, displayMetrics);
+    activityThread.applyConfigurationToResources(configuration);
 
     Resources systemResources = Resources.getSystem();
-    systemResources.updateConfiguration(androidConfiguration, displayMetrics);
+    systemResources.updateConfiguration(configuration, displayMetrics);
 
     Context systemContextImpl = reflector(_ContextImpl_.class).createSystemContext(activityThread);
     RuntimeEnvironment.systemContext = systemContextImpl;
@@ -225,7 +219,7 @@
       }
       registerBroadcastReceivers(application, appManifest);
 
-      appResources.updateConfiguration(androidConfiguration, displayMetrics);
+      appResources.updateConfiguration(configuration, displayMetrics);
 
       if (ShadowAssetManager.useLegacy()) {
         populateAssetPaths(appResources.getAssets(), appManifest);
diff --git a/robolectric/src/main/java/org/robolectric/config/ConfigurationRegistry.java b/robolectric/src/main/java/org/robolectric/config/ConfigurationRegistry.java
deleted file mode 100644
index c7c3b89..0000000
--- a/robolectric/src/main/java/org/robolectric/config/ConfigurationRegistry.java
+++ /dev/null
@@ -1,65 +0,0 @@
-package org.robolectric.config;
-
-import java.io.ByteArrayInputStream;
-import java.io.ByteArrayOutputStream;
-import java.io.IOException;
-import java.io.ObjectInputStream;
-import java.io.ObjectOutputStream;
-import java.util.HashMap;
-import java.util.Map;
-import org.robolectric.pluginapi.ConfigurationStrategy.Configuration;
-
-/**
- * Holds configuration objects for the current test, computed using
- * {@link org.robolectric.pluginapi.Configurer}.
- *
- * Configuration is computed before tests run, outside of their sandboxes. If the configuration
- * is needed from within a sandbox (when a test is executing), we need to transfer it to a class
- * that the SandboxClassLoader recognizes. We do this by serializing and deserializing in
- * {@link #reloadInSandboxClassLoader(Object)}.
- */
-public class ConfigurationRegistry {
-
-  public static ConfigurationRegistry instance;
-
-  /**
-   * Returns the configuration object of the specified class, computed using
-   * {@link org.robolectric.pluginapi.Configurer}.
-   */
-  public static <T> T get(Class<T> configClass) {
-    return instance.getInSandboxClassLoader(configClass);
-  }
-
-  private final Map<String, Object> configurations = new HashMap<>();
-
-  public ConfigurationRegistry(Configuration configuration) {
-    for (Class<?> classInParentLoader : configuration.keySet()) {
-      Object configInParentLoader = configuration.get(classInParentLoader);
-      configurations.put(classInParentLoader.getName(), configInParentLoader);
-    }
-  }
-
-  private <T> T getInSandboxClassLoader(Class<T> someConfigClass) {
-    Object configInParentLoader = configurations.get(someConfigClass.getName());
-    Object configInSandboxLoader = reloadInSandboxClassLoader(configInParentLoader);
-    return someConfigClass.cast(configInSandboxLoader);
-  }
-
-  private static Object reloadInSandboxClassLoader(Object configInParentLoader) {
-    ByteArrayOutputStream buf = new ByteArrayOutputStream();
-    try (ObjectOutputStream out = new ObjectOutputStream(buf)) {
-      out.writeObject(configInParentLoader);
-    } catch (IOException e) {
-      throw new RuntimeException(e);
-    }
-
-    byte[] bytes = buf.toByteArray();
-
-    // ObjectInputStream loads classes in the current classloader by magic
-    try (ObjectInputStream in = new ObjectInputStream(new ByteArrayInputStream(bytes))) {
-      return in.readObject();
-    } catch (IOException | ClassNotFoundException e) {
-      throw new RuntimeException(e);
-    }
-  }
-}
diff --git a/robolectric/src/main/java/org/robolectric/internal/ParallelUniverseInterface.java b/robolectric/src/main/java/org/robolectric/internal/ParallelUniverseInterface.java
index f1167fa..e182401 100644
--- a/robolectric/src/main/java/org/robolectric/internal/ParallelUniverseInterface.java
+++ b/robolectric/src/main/java/org/robolectric/internal/ParallelUniverseInterface.java
@@ -2,8 +2,8 @@
 
 import java.lang.reflect.Method;
 import org.robolectric.ApkLoader;
+import org.robolectric.annotation.Config;
 import org.robolectric.manifest.AndroidManifest;
-import org.robolectric.pluginapi.ConfigurationStrategy.Configuration;
 import org.robolectric.pluginapi.Sdk;
 
 public interface ParallelUniverseInterface {
@@ -14,7 +14,7 @@
 
   void setUpApplicationState(
       ApkLoader apkLoader, Method method,
-      Configuration config, AndroidManifest appManifest,
+      Config config, AndroidManifest appManifest,
       SdkEnvironment sdkEnvironment);
 
   Thread getMainThread();
diff --git a/robolectric/src/main/java/org/robolectric/plugins/ConfigConfigurer.java b/robolectric/src/main/java/org/robolectric/plugins/ConfigConfigurer.java
deleted file mode 100644
index de13373..0000000
--- a/robolectric/src/main/java/org/robolectric/plugins/ConfigConfigurer.java
+++ /dev/null
@@ -1,72 +0,0 @@
-package org.robolectric.plugins;
-
-import com.google.auto.service.AutoService;
-import java.lang.reflect.Method;
-import java.util.Properties;
-import javax.annotation.Nonnull;
-import javax.inject.Provider;
-import org.robolectric.annotation.Config;
-import org.robolectric.pluginapi.ConfigurationStrategy.Configuration;
-import org.robolectric.pluginapi.Configurer;
-
-/** Provides configuration to Robolectric for its `@`{@link Config} annotation. */
-@AutoService(Configurer.class)
-public class ConfigConfigurer implements Configurer<Config> {
-
-  private final PackagePropertiesLoader packagePropertiesLoader;
-  private final Config defaultConfig;
-
-  public static Config get(Configuration testConfig) {
-    return testConfig.get(Config.class);
-  }
-
-  protected ConfigConfigurer(PackagePropertiesLoader packagePropertiesLoader) {
-    this(packagePropertiesLoader, () -> new Config.Builder().build());
-  }
-
-  public ConfigConfigurer(
-      PackagePropertiesLoader packagePropertiesLoader,
-      DefaultConfigProvider defaultConfigProvider) {
-    this.packagePropertiesLoader = packagePropertiesLoader;
-    this.defaultConfig = Config.Builder.defaults().overlay(defaultConfigProvider.get()).build();
-  }
-
-  @Override
-  public Class<Config> getConfigClass() {
-    return Config.class;
-  }
-
-  @Nonnull
-  @Override
-  public Config defaultConfig() {
-    return defaultConfig;
-  }
-
-  @Override
-  public Config getConfigFor(@Nonnull String packageName) {
-    Properties properties = packagePropertiesLoader.getConfigProperties(packageName);
-    return Config.Implementation.fromProperties(properties);
-  }
-
-  @Override
-  public Config getConfigFor(@Nonnull Class<?> testClass) {
-    return testClass.getAnnotation(Config.class);
-  }
-
-  @Override
-  public Config getConfigFor(@Nonnull Method method) {
-    return method.getAnnotation(Config.class);
-  }
-
-  @Nonnull
-  @Override
-  public Config merge(@Nonnull Config parentConfig, @Nonnull Config childConfig) {
-    return new Config.Builder(parentConfig).overlay(childConfig).build();
-  }
-
-  /** Provides the default config for a test. */
-  public interface DefaultConfigProvider extends Provider<Config> {
-    @Override
-    Config get();
-  }
-}
diff --git a/robolectric/src/main/java/org/robolectric/plugins/DefaultConfigMerger.java b/robolectric/src/main/java/org/robolectric/plugins/DefaultConfigMerger.java
index 101fa73..99bf10c 100644
--- a/robolectric/src/main/java/org/robolectric/plugins/DefaultConfigMerger.java
+++ b/robolectric/src/main/java/org/robolectric/plugins/DefaultConfigMerger.java
@@ -1,7 +1,152 @@
 package org.robolectric.plugins;
 
-import org.robolectric.ConfigMerger;
+import static com.google.common.collect.Lists.reverse;
 
-public class DefaultConfigMerger extends ConfigMerger {
+import com.google.auto.service.AutoService;
+import com.google.common.annotations.VisibleForTesting;
+import java.io.IOException;
+import java.io.InputStream;
+import java.lang.reflect.Method;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.LinkedHashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.Properties;
+import javax.annotation.Nonnull;
+import javax.annotation.Nullable;
+import javax.annotation.Priority;
+import org.robolectric.RobolectricTestRunner;
+import org.robolectric.annotation.Config;
+import org.robolectric.pluginapi.ConfigMerger;
+import org.robolectric.util.Join;
 
+/** Robolectric's default {@link ConfigMerger}. */
+@AutoService(ConfigMerger.class)
+@Priority(Integer.MIN_VALUE)
+public class DefaultConfigMerger implements ConfigMerger {
+  private final Map<String, Config> packageConfigCache =
+      new LinkedHashMap<String, Config>() {
+        @Override
+        protected boolean removeEldestEntry(Map.Entry eldest) {
+          return size() > 10;
+        }
+      };
+
+  /**
+   * Calculate the {@link Config} for the given test.
+   *
+   * @param testClass the class containing the test
+   * @param method the test method
+   * @param globalConfig global configuration values
+   * @return the effective configuration
+   * @since 3.2
+   */
+  @Override
+  public Config getConfig(Class<?> testClass, Method method, Config globalConfig) {
+    Config config = Config.Builder.defaults().build();
+    config = override(config, globalConfig);
+
+    for (String packageName : reverse(packageHierarchyOf(testClass))) {
+      Config packageConfig = cachedPackageConfig(packageName);
+      config = override(config, packageConfig);
+    }
+
+    for (Class clazz : reverse(parentClassesFor(testClass))) {
+      Config classConfig = (Config) clazz.getAnnotation(Config.class);
+      config = override(config, classConfig);
+    }
+
+    Config methodConfig = method.getAnnotation(Config.class);
+    config = override(config, methodConfig);
+
+    return config;
+  }
+
+  /**
+   * Generate {@link Config} for the specified package.
+   *
+   * <p>More specific packages, test classes, and test method configurations will override values
+   * provided here.
+   *
+   * <p>The default implementation uses properties provided by {@link #getConfigProperties(String)}.
+   *
+   * <p>The returned object is likely to be reused for many tests.
+   *
+   * @param packageName the name of the package, or empty string ({@code ""}) for the top level
+   *     package
+   * @return {@link Config} object for the specified package
+   * @since 3.2
+   */
+  @Nullable
+  private Config buildPackageConfig(String packageName) {
+    return Config.Implementation.fromProperties(getConfigProperties(packageName));
+  }
+
+  /**
+   * Return a {@link Properties} file for the given package name, or {@code null} if none is
+   * available.
+   *
+   * @since 3.2
+   */
+  protected Properties getConfigProperties(String packageName) {
+    List<String> packageParts = new ArrayList<>(Arrays.asList(packageName.split("\\.")));
+    packageParts.add(RobolectricTestRunner.CONFIG_PROPERTIES);
+    final String resourceName = Join.join("/", packageParts);
+    try (InputStream resourceAsStream = getResourceAsStream(resourceName)) {
+      if (resourceAsStream == null) return null;
+      Properties properties = new Properties();
+      properties.load(resourceAsStream);
+      return properties;
+    } catch (IOException e) {
+      throw new RuntimeException(e);
+    }
+  }
+
+  @Nonnull
+  @VisibleForTesting
+  List<String> packageHierarchyOf(Class<?> javaClass) {
+    Package aPackage = javaClass.getPackage();
+    String testPackageName = aPackage == null ? "" : aPackage.getName();
+    List<String> packageHierarchy = new ArrayList<>();
+    while (!testPackageName.isEmpty()) {
+      packageHierarchy.add(testPackageName);
+      int lastDot = testPackageName.lastIndexOf('.');
+      testPackageName = lastDot > 1 ? testPackageName.substring(0, lastDot) : "";
+    }
+    packageHierarchy.add("");
+    return packageHierarchy;
+  }
+
+  @Nonnull
+  private List<Class> parentClassesFor(Class testClass) {
+    List<Class> testClassHierarchy = new ArrayList<>();
+    while (testClass != null && !testClass.equals(Object.class)) {
+      testClassHierarchy.add(testClass);
+      testClass = testClass.getSuperclass();
+    }
+    return testClassHierarchy;
+  }
+
+  private Config override(Config config, Config classConfig) {
+    return classConfig != null ? new Config.Builder(config).overlay(classConfig).build() : config;
+  }
+
+  @Nullable
+  private Config cachedPackageConfig(String packageName) {
+    synchronized (packageConfigCache) {
+      Config config = packageConfigCache.get(packageName);
+      if (config == null && !packageConfigCache.containsKey(packageName)) {
+        config = buildPackageConfig(packageName);
+        packageConfigCache.put(packageName, config);
+      }
+      return config;
+    }
+  }
+
+  // visible for testing
+  @SuppressWarnings("WeakerAccess")
+  InputStream getResourceAsStream(String resourceName) {
+    return getClass().getClassLoader().getResourceAsStream(resourceName);
+  }
 }
diff --git a/robolectric/src/main/java/org/robolectric/plugins/DefaultSdkPicker.java b/robolectric/src/main/java/org/robolectric/plugins/DefaultSdkPicker.java
index 2447cdb..a96b206 100644
--- a/robolectric/src/main/java/org/robolectric/plugins/DefaultSdkPicker.java
+++ b/robolectric/src/main/java/org/robolectric/plugins/DefaultSdkPicker.java
@@ -18,7 +18,6 @@
 import javax.inject.Inject;
 import org.robolectric.annotation.Config;
 import org.robolectric.annotation.internal.ConfigUtils;
-import org.robolectric.pluginapi.ConfigurationStrategy.Configuration;
 import org.robolectric.pluginapi.Sdk;
 import org.robolectric.pluginapi.SdkPicker;
 import org.robolectric.pluginapi.UsesSdk;
@@ -57,15 +56,14 @@
   /**
    * Enumerate the SDKs to be used for this test.
    *
-   * @param configuration a collection of configuration objects, including {@link Config}
+   * @param config a {@link Config} specifying one or more SDKs
    * @param usesSdk the {@link UsesSdk} for the test
    * @return the list of candidate {@link Sdk}s.
    * @since 3.9
    */
   @Override
   @Nonnull
-  public List<Sdk> selectSdks(Configuration configuration, UsesSdk usesSdk) {
-    Config config = configuration.get(Config.class);
+  public List<Sdk> selectSdks(Config config, UsesSdk usesSdk) {
     Set<Sdk> sdks = new TreeSet<>(configuredSdks(config, usesSdk));
     if (enabledSdks != null) {
       sdks = Sets.intersection(sdks, enabledSdks);
diff --git a/robolectric/src/main/java/org/robolectric/plugins/HierarchicalConfigurationStrategy.java b/robolectric/src/main/java/org/robolectric/plugins/HierarchicalConfigurationStrategy.java
deleted file mode 100644
index 0b8fd03..0000000
--- a/robolectric/src/main/java/org/robolectric/plugins/HierarchicalConfigurationStrategy.java
+++ /dev/null
@@ -1,185 +0,0 @@
-package org.robolectric.plugins;
-
-import com.google.auto.service.AutoService;
-import java.lang.reflect.Method;
-import java.util.HashMap;
-import java.util.LinkedHashMap;
-import java.util.Map;
-import java.util.Set;
-import java.util.function.Function;
-import javax.annotation.Priority;
-import org.robolectric.pluginapi.ConfigurationStrategy;
-import org.robolectric.pluginapi.Configurer;
-
-/**
- * Robolectric's default {@link ConfigurationStrategy}.
- *
- * See [Configuring Robolectric](http://robolectric.org/configuring/).
- */
-@SuppressWarnings({"AndroidJdkLibsChecker", "NewApi"})
-@AutoService(ConfigurationStrategy.class)
-@Priority(Integer.MIN_VALUE)
-public class HierarchicalConfigurationStrategy implements ConfigurationStrategy {
-
-  /** The cache is sized to avoid repeated resolutions for any node. */
-  private int highWaterMark = 0;
-  private final Map<String, Object[]> cache =
-      new LinkedHashMap<String, Object[]>() {
-        @Override
-        protected boolean removeEldestEntry(Map.Entry<String, Object[]> eldest) {
-          return size() > highWaterMark + 1;
-        }
-      };
-
-  private final Configurer<?>[] configurers;
-  private final Object[] defaultConfigs;
-
-  public HierarchicalConfigurationStrategy(Configurer<?>... configurers) {
-    this.configurers = configurers;
-
-    defaultConfigs = new Object[configurers.length];
-    for (int i = 0; i < configurers.length; i++) {
-      Configurer<?> configurer = configurers[i];
-      defaultConfigs[i] = configurer.defaultConfig();
-    }
-  }
-
-  @Override
-  public ConfigurationImpl getConfig(Class<?> testClass, Method method) {
-    final Counter counter = new Counter();
-    Object[] configs = cache(testClass.getName() + "/" + method.getName(), counter, s -> {
-      counter.incr();
-      Object[] methodConfigs = getConfigs(counter,
-          configurer -> configurer.getConfigFor(method));
-      return merge(getFirstClassConfig(testClass, counter), methodConfigs);
-    });
-
-    ConfigurationImpl testConfig = new ConfigurationImpl();
-    for (int i = 0; i < configurers.length; i++) {
-      put(testConfig, configurers[i].getConfigClass(), configs[i]);
-    }
-
-    return testConfig;
-  }
-
-  private Object[] getFirstClassConfig(Class<?> testClass, Counter counter) {
-    // todo: should parent class configs have lower precedence than package configs?
-    return cache("first:" + testClass, counter, s -> {
-          Object[] configsForClass = getClassConfig(testClass, counter);
-      Package pkg = testClass.getPackage();
-      Object[] configsForPackage = getPackageConfig(pkg == null ? "" : pkg.getName(), counter);
-          return merge(configsForPackage, configsForClass);
-        }
-    );
-  }
-
-  private Object[] getPackageConfig(String packageName, Counter counter) {
-    return cache(packageName, counter, s -> {
-          Object[] packageConfigs = getConfigs(counter,
-              configurer -> configurer.getConfigFor(packageName));
-          String parentPackage = parentPackage(packageName);
-          if (parentPackage == null) {
-            return merge(defaultConfigs, packageConfigs);
-          } else {
-            Object[] packageConfig = getPackageConfig(parentPackage, counter);
-            return merge(packageConfig, packageConfigs);
-          }
-        });
-  }
-
-  private String parentPackage(String name) {
-    if (name.isEmpty()) {
-      return null;
-    }
-    int lastDot = name.lastIndexOf('.');
-    return lastDot > -1 ? name.substring(0, lastDot) : "";
-  }
-
-  private Object[] getClassConfig(Class<?> testClass, Counter counter) {
-    return cache(testClass.getName(), counter, s -> {
-      Object[] classConfigs = getConfigs(counter, configurer -> configurer.getConfigFor(testClass));
-
-      Class<?> superclass = testClass.getSuperclass();
-      if (superclass != Object.class) {
-        Object[] superclassConfigs = getClassConfig(superclass, counter);
-        return merge(superclassConfigs, classConfigs);
-      }
-      return classConfigs;
-    });
-  }
-
-  private Object[] cache(String name, Counter counter, Function<String, Object[]> fn) {
-    // make sure the cache is optimally sized this test suite
-    if (counter.depth > highWaterMark) {
-      highWaterMark = counter.depth;
-    }
-
-    Object[] configs = cache.get(name);
-    if (configs == null) {
-      configs = fn.apply(name);
-      cache.put(name, configs);
-    }
-    return configs;
-  }
-
-  interface GetConfig {
-    Object getConfig(Configurer<?> configurer);
-  }
-
-  private Object[] getConfigs(Counter counter, GetConfig getConfig) {
-    counter.incr();
-
-    Object[] objects = new Object[configurers.length];
-    for (int i = 0; i < configurers.length; i++) {
-      objects[i] = getConfig.getConfig(configurers[i]);
-    }
-    return objects;
-  }
-
-  private void put(ConfigurationImpl testConfig, Class<?> configClass, Object config) {
-    testConfig.put((Class) configClass, config);
-  }
-
-  private Object[] merge(Object[] parentConfigs, Object[] childConfigs) {
-    Object[] objects = new Object[configurers.length];
-    for (int i = 0; i < configurers.length; i++) {
-      Configurer configurer = configurers[i];
-      Object childConfig = childConfigs[i];
-      Object parentConfig = parentConfigs[i];
-      objects[i] = childConfig == null
-          ? parentConfig
-          : parentConfig == null
-              ? childConfig
-              : configurer.merge(parentConfig, childConfig);
-    }
-    return objects;
-  }
-
-  public static class ConfigurationImpl implements Configuration {
-
-    private final Map<Class<?>, Object> configs = new HashMap<>();
-
-    public <T> void put(Class<T> klass, T instance) {
-      configs.put(klass, instance);
-    }
-
-    @Override
-    public <T> T get(Class<T> klass) {
-      return klass.cast(configs.get(klass));
-    }
-
-    @Override
-    public Set<Class<?>> keySet() {
-      return configs.keySet();
-    }
-
-  }
-
-  private static class Counter {
-    private int depth = 0;
-
-    void incr() {
-      depth++;
-    }
-  }
-}
diff --git a/robolectric/src/main/java/org/robolectric/plugins/PackagePropertiesLoader.java b/robolectric/src/main/java/org/robolectric/plugins/PackagePropertiesLoader.java
deleted file mode 100644
index 7957737..0000000
--- a/robolectric/src/main/java/org/robolectric/plugins/PackagePropertiesLoader.java
+++ /dev/null
@@ -1,65 +0,0 @@
-package org.robolectric.plugins;
-
-import java.io.IOException;
-import java.io.InputStream;
-import java.util.LinkedHashMap;
-import java.util.Map;
-import java.util.Properties;
-import javax.annotation.Nonnull;
-import org.robolectric.RobolectricTestRunner;
-
-/**
- * Provides cached access to `robolectric-properties` files, for all your configuration needs!
- *
- * Used by {@link ConfigConfigurer} to support package configuration (see [Configuring
- * Robolectric](http://robolectric.org/configuring/) but it may be useful for other
- * {@link org.robolectric.pluginapi.Configurer}s as well.
- */
-@SuppressWarnings({"AndroidJdkLibsChecker", "NewApi"})
-public class PackagePropertiesLoader {
-
-  /**
-   * We should get very high cache hit rates even with a tiny cache if we're called sequentially
-   * by multiple {@link org.robolectric.pluginapi.Configurer}s for the same package.
-   */
-  private final Map<String, Properties> cache = new LinkedHashMap<String, Properties>() {
-    @Override
-    protected boolean removeEldestEntry(Map.Entry<String, Properties> eldest) {
-      return size() > 3;
-    }
-  };
-
-  /**
-   * Return a {@link Properties} file for the given package name, or {@code null} if none is
-   * available.
-   *
-   * @since 3.2
-   */
-  public Properties getConfigProperties(@Nonnull String packageName) {
-    return cache.computeIfAbsent(packageName, s -> {
-      StringBuilder buf = new StringBuilder();
-      if (!packageName.isEmpty()) {
-        buf.append(packageName.replace('.', '/'));
-        buf.append('/');
-      }
-      buf.append(RobolectricTestRunner.CONFIG_PROPERTIES);
-      final String resourceName = buf.toString();
-
-      try (InputStream resourceAsStream = getResourceAsStream(resourceName)) {
-        if (resourceAsStream == null) {
-          return null;
-        }
-        Properties properties = new Properties();
-        properties.load(resourceAsStream);
-        return properties;
-      } catch (IOException e) {
-        throw new RuntimeException(e);
-      }
-    });
-  }
-
-  // visible for testing
-  InputStream getResourceAsStream(String resourceName) {
-    return getClass().getClassLoader().getResourceAsStream(resourceName);
-  }
-}
diff --git a/robolectric/src/test/java/org/robolectric/BootstrapDeferringRobolectricTestRunner.java b/robolectric/src/test/java/org/robolectric/BootstrapDeferringRobolectricTestRunner.java
index 66bb411..29a7243 100644
--- a/robolectric/src/test/java/org/robolectric/BootstrapDeferringRobolectricTestRunner.java
+++ b/robolectric/src/test/java/org/robolectric/BootstrapDeferringRobolectricTestRunner.java
@@ -9,12 +9,12 @@
 import javax.annotation.Nonnull;
 import org.junit.runners.model.FrameworkMethod;
 import org.junit.runners.model.InitializationError;
+import org.robolectric.annotation.Config;
 import org.robolectric.internal.ParallelUniverseInterface;
 import org.robolectric.internal.SdkEnvironment;
 import org.robolectric.internal.bytecode.InstrumentationConfiguration;
 import org.robolectric.internal.bytecode.InstrumentationConfiguration.Builder;
 import org.robolectric.manifest.AndroidManifest;
-import org.robolectric.pluginapi.ConfigurationStrategy.Configuration;
 import org.robolectric.pluginapi.Sdk;
 
 /**
@@ -80,7 +80,7 @@
     public boolean legacyResources;
     public ApkLoader apkLoader;
     public Method method;
-    public Configuration config;
+    public Config config;
     public AndroidManifest appManifest;
     public SdkEnvironment sdkEnvironment;
 
@@ -100,7 +100,7 @@
     }
 
     @Override
-    public void setUpApplicationState(ApkLoader apkLoader, Method method, Configuration config,
+    public void setUpApplicationState(ApkLoader apkLoader, Method method, Config config,
         AndroidManifest appManifest, SdkEnvironment sdkEnvironment) {
       this.apkLoader = apkLoader;
       this.method = method;
diff --git a/robolectric/src/test/java/org/robolectric/RobolectricTestRunnerTest.java b/robolectric/src/test/java/org/robolectric/RobolectricTestRunnerTest.java
index 9add700..3b212a0 100644
--- a/robolectric/src/test/java/org/robolectric/RobolectricTestRunnerTest.java
+++ b/robolectric/src/test/java/org/robolectric/RobolectricTestRunnerTest.java
@@ -38,16 +38,13 @@
 import org.junit.runner.notification.RunNotifier;
 import org.junit.runners.JUnit4;
 import org.junit.runners.MethodSorters;
-import org.junit.runners.model.FrameworkMethod;
 import org.robolectric.RobolectricTestRunner.ResourcesMode;
 import org.robolectric.RobolectricTestRunner.RobolectricFrameworkMethod;
 import org.robolectric.android.internal.ParallelUniverse;
 import org.robolectric.annotation.Config;
-import org.robolectric.annotation.Config.Implementation;
 import org.robolectric.internal.ParallelUniverseInterface;
 import org.robolectric.internal.SdkEnvironment;
 import org.robolectric.manifest.AndroidManifest;
-import org.robolectric.pluginapi.ConfigurationStrategy.Configuration;
 import org.robolectric.pluginapi.SdkProvider;
 import org.robolectric.plugins.DefaultSdkPicker;
 import org.robolectric.plugins.DefaultSdkProvider;
@@ -128,19 +125,6 @@
   }
 
   @Test
-  public void supportsOldGetConfigUntil4dot3() throws Exception {
-    Implementation overriddenConfig = Config.Builder.defaults().build();
-    List<FrameworkMethod> children = new SingleSdkRobolectricTestRunner(TestWithTwoMethods.class) {
-      @Override
-      public Config getConfig(Method method) {
-        return overriddenConfig;
-      }
-    }.getChildren();
-    Config config = ((RobolectricFrameworkMethod) children.get(0)).config.get(Config.class);
-    assertThat(config).isSameAs(overriddenConfig);
-  }
-
-  @Test
   public void failureInResetterDoesntBreakAllTests() throws Exception {
     RobolectricTestRunner runner =
         new SingleSdkRobolectricTestRunner(TestWithTwoMethods.class) {
@@ -200,7 +184,7 @@
             method,
             mock(AndroidManifest.class),
             sdkCollection.getSdk(16),
-            mock(Configuration.class),
+            mock(Config.class),
             ResourcesMode.legacy,
             ResourcesMode.legacy,
             false);
@@ -209,7 +193,7 @@
             method,
             mock(AndroidManifest.class),
             sdkCollection.getSdk(17),
-            mock(Configuration.class),
+            mock(Config.class),
             ResourcesMode.legacy,
             ResourcesMode.legacy,
             false);
@@ -218,7 +202,7 @@
             method,
             mock(AndroidManifest.class),
             sdkCollection.getSdk(16),
-            mock(Configuration.class),
+            mock(Config.class),
             ResourcesMode.legacy,
             ResourcesMode.legacy,
             false);
@@ -227,7 +211,7 @@
             method,
             mock(AndroidManifest.class),
             sdkCollection.getSdk(16),
-            mock(Configuration.class),
+            mock(Config.class),
             ResourcesMode.binary,
             ResourcesMode.legacy,
             false);
@@ -277,7 +261,7 @@
 
     @Override
     public void setUpApplicationState(ApkLoader apkLoader, Method method,
-        Configuration configuration, AndroidManifest appManifest, SdkEnvironment environment) {
+        Config config, AndroidManifest appManifest, SdkEnvironment environment) {
       throw new RuntimeException("fake error in setUpApplicationState");
     }
   }
@@ -300,7 +284,6 @@
 
   @Ignore
   @FixMethodOrder(MethodSorters.NAME_ASCENDING)
-  @Config(qualifiers = "w123dp-h456dp-land-hdpi")
   public static class TestWithTwoMethods {
     @Test
     public void first() throws Exception {
diff --git a/robolectric/src/test/java/org/robolectric/SingleSdkRobolectricTestRunner.java b/robolectric/src/test/java/org/robolectric/SingleSdkRobolectricTestRunner.java
index 0f3a6d1..bf9a010 100644
--- a/robolectric/src/test/java/org/robolectric/SingleSdkRobolectricTestRunner.java
+++ b/robolectric/src/test/java/org/robolectric/SingleSdkRobolectricTestRunner.java
@@ -5,31 +5,22 @@
 import java.util.List;
 import javax.annotation.Nonnull;
 import org.junit.runners.model.InitializationError;
-import org.robolectric.pluginapi.ConfigurationStrategy.Configuration;
+import org.robolectric.annotation.Config;
 import org.robolectric.pluginapi.Sdk;
 import org.robolectric.pluginapi.SdkPicker;
 import org.robolectric.pluginapi.UsesSdk;
 import org.robolectric.util.TestUtil;
 import org.robolectric.util.inject.Injector;
 
-public class SingleSdkRobolectricTestRunner extends RobolectricTestRunner {
+class SingleSdkRobolectricTestRunner extends RobolectricTestRunner {
 
-  private static final Injector INJECTOR = defaultInjector().build();
+  private static final Injector INJECTOR =
+      defaultInjector().bind(SdkPicker.class, SingleSdkPicker.class).build();
 
-  public static Injector.Builder defaultInjector() {
-    return RobolectricTestRunner.defaultInjector()
-        .bind(SdkPicker.class, SingleSdkPicker.class);
-  }
-
-  public SingleSdkRobolectricTestRunner(Class<?> testClass) throws InitializationError {
+  SingleSdkRobolectricTestRunner(Class<?> testClass) throws InitializationError {
     super(testClass, INJECTOR);
   }
 
-  public SingleSdkRobolectricTestRunner(Class<?> testClass, Injector injector)
-      throws InitializationError {
-    super(testClass, injector);
-  }
-
   @Override
   ResourcesMode getResourcesMode() {
     return ResourcesMode.binary;
@@ -49,7 +40,7 @@
 
     @Nonnull
     @Override
-    public List<Sdk> selectSdks(Configuration configuration, UsesSdk usesSdk) {
+    public List<Sdk> selectSdks(Config config, UsesSdk usesSdk) {
       return Collections.singletonList(sdk);
     }
   }
diff --git a/robolectric/src/test/java/org/robolectric/android/internal/ParallelUniverseTest.java b/robolectric/src/test/java/org/robolectric/android/internal/ParallelUniverseTest.java
index d9594fd..b3396cc 100644
--- a/robolectric/src/test/java/org/robolectric/android/internal/ParallelUniverseTest.java
+++ b/robolectric/src/test/java/org/robolectric/android/internal/ParallelUniverseTest.java
@@ -33,7 +33,6 @@
 import org.robolectric.annotation.Config;
 import org.robolectric.manifest.AndroidManifest;
 import org.robolectric.manifest.RoboNotFoundException;
-import org.robolectric.plugins.HierarchicalConfigurationStrategy.ConfigurationImpl;
 import org.robolectric.res.ResourceTable;
 import org.robolectric.shadow.api.Shadow;
 import org.robolectric.shadows.ShadowApplication;
@@ -116,9 +115,7 @@
   @Test
   public void setUpApplicationState_setsVersionQualifierFromSdk() {
     String givenQualifiers = "";
-    ConfigurationImpl config = new ConfigurationImpl();
-    config.put(Config.class, new Config.Builder().setQualifiers(givenQualifiers).build());
-    bootstrapWrapper.config = config;
+    bootstrapWrapper.config = new Config.Builder().setQualifiers(givenQualifiers).build();
     bootstrapWrapper.callSetUpApplicationState();
     assertThat(RuntimeEnvironment.getQualifiers()).contains("v" + Build.VERSION.RESOURCES_SDK_INT);
   }
@@ -126,10 +123,7 @@
   @Test
   public void setUpApplicationState_setsVersionQualifierFromSdkWithOtherQualifiers() {
     String givenQualifiers = "large-land";
-    ConfigurationImpl config = new ConfigurationImpl();
-    config.put(Config.class, new Config.Builder().setQualifiers(givenQualifiers).build());
-    bootstrapWrapper.config = config;
-
+    bootstrapWrapper.config = new Config.Builder().setQualifiers(givenQualifiers).build();
     bootstrapWrapper.callSetUpApplicationState();
 
     String optsForO = RuntimeEnvironment.getApiLevel() >= O
diff --git a/robolectric/src/test/java/org/robolectric/plugins/CustomConfigurerTest.java b/robolectric/src/test/java/org/robolectric/plugins/CustomConfigurerTest.java
deleted file mode 100644
index d64fa15..0000000
--- a/robolectric/src/test/java/org/robolectric/plugins/CustomConfigurerTest.java
+++ /dev/null
@@ -1,126 +0,0 @@
-package org.robolectric.plugins;
-
-import static com.google.common.truth.Truth.assertThat;
-import static java.util.stream.Collectors.toList;
-import static org.junit.Assert.fail;
-
-import java.lang.annotation.Annotation;
-import java.lang.annotation.ElementType;
-import java.lang.annotation.Retention;
-import java.lang.annotation.RetentionPolicy;
-import java.lang.annotation.Target;
-import java.lang.reflect.Method;
-import java.util.List;
-import javax.annotation.Nonnull;
-import org.junit.Ignore;
-import org.junit.Test;
-import org.junit.runner.RunWith;
-import org.junit.runner.notification.Failure;
-import org.junit.runner.notification.RunNotifier;
-import org.junit.runners.JUnit4;
-import org.junit.runners.model.InitializationError;
-import org.robolectric.SingleSdkRobolectricTestRunner;
-import org.robolectric.android.FailureListener;
-import org.robolectric.config.ConfigurationRegistry;
-import org.robolectric.pluginapi.ConfigurationStrategy;
-import org.robolectric.pluginapi.Configurer;
-
-@RunWith(JUnit4.class)
-public class CustomConfigurerTest {
-
-  @Test
-  public void customConfigCanBeAccessedFromWithinSandbox() throws Exception {
-    List<String> failures = runAndGetFailures(TestWithConfig.class);
-    assertThat(failures).containsExactly("someConfig value is the value");
-  }
-
-  /////////////////////
-
-  @Retention(RetentionPolicy.RUNTIME)
-  @Target(value = {ElementType.PACKAGE, ElementType.TYPE, ElementType.METHOD})
-  public @interface SomeConfig {
-    String value();
-  }
-
-  @Ignore
-  public static class TestWithConfig {
-
-    @Test
-    @SomeConfig(value = "the value")
-    public void shouldHaveValue() throws Exception {
-      SomeConfig someConfig = ConfigurationRegistry.get(SomeConfig.class);
-      fail("someConfig value is " + someConfig.value());
-    }
-  }
-
-  static class SomeConfigConfigurer implements Configurer<SomeConfig> {
-
-    @Override
-    public Class<SomeConfig> getConfigClass() {
-      return SomeConfig.class;
-    }
-
-    @Nonnull
-    @Override
-    public SomeConfig defaultConfig() {
-      return new MySomeConfig();
-    }
-
-    @Override
-    public SomeConfig getConfigFor(@Nonnull String packageName) {
-      return null;
-    }
-
-    @Override
-    public SomeConfig getConfigFor(@Nonnull Class<?> testClass) {
-      return testClass.getAnnotation(SomeConfig.class);
-    }
-
-    @Override
-    public SomeConfig getConfigFor(@Nonnull Method method) {
-      return method.getAnnotation(SomeConfig.class);
-    }
-
-    @Nonnull
-    @Override
-    public SomeConfig merge(@Nonnull SomeConfig parentConfig, @Nonnull SomeConfig childConfig) {
-      return childConfig;
-    }
-
-    @SuppressWarnings("BadAnnotationImplementation")
-    private static class MySomeConfig implements SomeConfig {
-
-      @Override
-      public Class<? extends Annotation> annotationType() {
-        return Annotation.class;
-      }
-
-      @Override
-      public String value() {
-        return "default value";
-      }
-    }
-  }
-
-  private List<String> runAndGetFailures(Class<TestWithConfig> testClass)
-      throws InitializationError {
-    RunNotifier notifier = new RunNotifier();
-    FailureListener failureListener = new FailureListener();
-    notifier.addListener(failureListener);
-
-    HierarchicalConfigurationStrategy configurationStrategy =
-        new HierarchicalConfigurationStrategy(
-            new ConfigConfigurer(new PackagePropertiesLoader()),
-            new SomeConfigConfigurer());
-
-    SingleSdkRobolectricTestRunner testRunner = new SingleSdkRobolectricTestRunner(
-        testClass,
-        SingleSdkRobolectricTestRunner.defaultInjector()
-            .bind(ConfigurationStrategy.class, configurationStrategy)
-            .build());
-
-    testRunner.run(notifier);
-    return failureListener.failures.stream().map(Failure::getMessage).collect(toList());
-  }
-
-}
diff --git a/robolectric/src/test/java/org/robolectric/plugins/HierarchicalConfigurationStrategyTest.java b/robolectric/src/test/java/org/robolectric/plugins/DefaultConfigMergerTest.java
similarity index 65%
rename from robolectric/src/test/java/org/robolectric/plugins/HierarchicalConfigurationStrategyTest.java
rename to robolectric/src/test/java/org/robolectric/plugins/DefaultConfigMergerTest.java
index 86ff598..37d1b2c 100644
--- a/robolectric/src/test/java/org/robolectric/plugins/HierarchicalConfigurationStrategyTest.java
+++ b/robolectric/src/test/java/org/robolectric/plugins/DefaultConfigMergerTest.java
@@ -9,29 +9,25 @@
 import java.io.ByteArrayInputStream;
 import java.io.InputStream;
 import java.lang.reflect.Method;
-import java.util.ArrayList;
 import java.util.HashMap;
-import java.util.List;
 import java.util.Map;
-import javax.annotation.Nonnull;
 import org.junit.Ignore;
 import org.junit.Test;
 import org.junit.runner.RunWith;
 import org.junit.runners.JUnit4;
+import org.junit.runners.model.InitializationError;
 import org.robolectric.TestFakeApp;
 import org.robolectric.annotation.Config;
-import org.robolectric.pluginapi.ConfigurationStrategy;
-import org.robolectric.pluginapi.ConfigurationStrategy.Configuration;
-import org.robolectric.pluginapi.Configurer;
 import org.robolectric.shadows.ShadowView;
 import org.robolectric.shadows.ShadowViewGroup;
 import org.robolectric.shadows.testing.TestApplication;
 
 @RunWith(JUnit4.class)
-public class HierarchicalConfigurationStrategyTest {
+public class DefaultConfigMergerTest {
 
   @Test public void defaultValuesAreMerged() throws Exception {
-    assertThat(configFor(Test2.class, "withoutAnnotation").manifest())
+    assertThat(configFor(Test2.class, "withoutAnnotation",
+        new Config.Builder().build()).manifest())
         .isEqualTo("AndroidManifest.xml");
   }
 
@@ -42,8 +38,7 @@
   }
 
   @Test
-  public void whenClassHasConfigAnnotation_getConfig_shouldMergeClassAndMethodConfig()
-      throws Exception {
+  public void whenClassHasConfigAnnotation_getConfig_shouldMergeClassAndMethodConfig() throws Exception {
     assertConfig(
         configFor(Test1.class, "withoutAnnotation"),
         new int[] {1},
@@ -53,7 +48,7 @@
         "from-test",
         "test/res",
         "test/assets",
-        new Class<?>[] {Test1.class},
+        new Class[] {Test1.class},
         new String[] {"com.example.test1"},
         new String[] {"libs/test"});
 
@@ -66,7 +61,7 @@
         "from-test",
         "test/res",
         "test/assets",
-        new Class<?>[] {Test1.class},
+        new Class[] {Test1.class},
         new String[] {"com.example.test1"},
         new String[] {"libs/test"});
 
@@ -79,14 +74,13 @@
         "from-method",
         "method/res",
         "method/assets",
-        new Class<?>[] {Test1.class, Test2.class},
+        new Class[] {Test1.class, Test2.class},
         new String[] {"com.example.test1", "com.example.method1"},
         new String[] {"libs/method", "libs/test"});
   }
 
   @Test
-  public void whenClassDoesntHaveConfigAnnotation_getConfig_shouldUseMethodConfig()
-      throws Exception {
+  public void whenClassDoesntHaveConfigAnnotation_getConfig_shouldUseMethodConfig() throws Exception {
     assertConfig(
         configFor(Test2.class, "withoutAnnotation"),
         new int[0],
@@ -96,7 +90,7 @@
         "",
         "res",
         "assets",
-        new Class<?>[] {},
+        new Class[] {},
         new String[] {},
         new String[] {});
 
@@ -109,7 +103,7 @@
         "",
         "res",
         "assets",
-        new Class<?>[] {},
+        new Class[] {},
         new String[] {},
         new String[] {});
 
@@ -122,16 +116,15 @@
         "from-method",
         "method/res",
         "method/assets",
-        new Class<?>[] {Test1.class},
+        new Class[] {Test1.class},
         new String[] {"com.example.method2"},
         new String[] {"libs/method"});
   }
 
   @Test
-  public void whenClassDoesntHaveConfigAnnotation_getConfig_shouldMergeParentClassAndMethodConfig()
-      throws Exception {
+  public void whenClassDoesntHaveConfigAnnotation_getConfig_shouldMergeParentClassAndMethodConfig() throws Exception {
     assertConfig(
-        configFor(Test1B.class, "withoutAnnotation"),
+        configFor(Test5.class, "withoutAnnotation"),
         new int[] {1},
         "foo",
         TestFakeApp.class,
@@ -139,12 +132,12 @@
         "from-test",
         "test/res",
         "test/assets",
-        new Class<?>[] {Test1.class, Test1.class},
+        new Class[] {Test1.class, Test1.class},
         new String[] {"com.example.test1"},
         new String[] {"libs/test"});
 
     assertConfig(
-        configFor(Test1B.class, "withDefaultsAnnotation"),
+        configFor(Test5.class, "withDefaultsAnnotation"),
         new int[] {1},
         "foo",
         TestFakeApp.class,
@@ -152,12 +145,12 @@
         "from-test",
         "test/res",
         "test/assets",
-        new Class<?>[] {Test1.class, Test1.class},
+        new Class[] {Test1.class, Test1.class},
         new String[] {"com.example.test1"},
         new String[] {"libs/test"});
 
     assertConfig(
-        configFor(Test1B.class, "withOverrideAnnotation"),
+        configFor(Test5.class, "withOverrideAnnotation"),
         new int[] {14},
         "foo",
         TestFakeApp.class,
@@ -165,16 +158,15 @@
         "from-method5",
         "test/res",
         "method5/assets",
-        new Class<?>[] {Test1.class, Test1.class, Test1B.class},
+        new Class[] {Test1.class, Test1.class, Test5.class},
         new String[] {"com.example.test1", "com.example.method5"},
         new String[] {"libs/test"});
   }
 
   @Test
-  public void whenClassAndParentClassHaveConfigAnnotation_getConfig_shouldMergeParentClassAndMethodConfig()
-      throws Exception {
+  public void whenClassAndParentClassHaveConfigAnnotation_getConfig_shouldMergeParentClassAndMethodConfig() throws Exception {
     assertConfig(
-        configFor(Test1C.class, "withoutAnnotation"),
+        configFor(Test6.class, "withoutAnnotation"),
         new int[] {1},
         "foo",
         TestFakeApp.class,
@@ -182,12 +174,12 @@
         "from-class6",
         "class6/res",
         "test/assets",
-        new Class<?>[] {Test1.class, Test1.class, Test1C.class},
+        new Class[] {Test1.class, Test1.class, Test6.class},
         new String[] {"com.example.test1", "com.example.test6"},
         new String[] {"libs/test"});
 
     assertConfig(
-        configFor(Test1C.class, "withDefaultsAnnotation"),
+        configFor(Test6.class, "withDefaultsAnnotation"),
         new int[] {1},
         "foo",
         TestFakeApp.class,
@@ -195,12 +187,12 @@
         "from-class6",
         "class6/res",
         "test/assets",
-        new Class<?>[] {Test1.class, Test1.class, Test1C.class},
+        new Class[] {Test1.class, Test1.class, Test6.class},
         new String[] {"com.example.test1", "com.example.test6"},
         new String[] {"libs/test"});
 
     assertConfig(
-        configFor(Test1C.class, "withOverrideAnnotation"),
+        configFor(Test6.class, "withOverrideAnnotation"),
         new int[] {14},
         "foo",
         TestFakeApp.class,
@@ -208,16 +200,15 @@
         "from-method5",
         "class6/res",
         "method5/assets",
-        new Class<?>[] {Test1.class, Test1.class, Test1C.class, Test1B.class},
+        new Class[] {Test1.class, Test1.class, Test6.class, Test5.class},
         new String[] {"com.example.test1", "com.example.method5", "com.example.test6"},
         new String[] {"libs/test"});
   }
 
   @Test
-  public void whenClassAndSubclassHaveConfigAnnotation_getConfig_shouldMergeClassSubclassAndMethodConfig()
-      throws Exception {
+  public void whenClassAndSubclassHaveConfigAnnotation_getConfig_shouldMergeClassSubclassAndMethodConfig() throws Exception {
     assertConfig(
-        configFor(Test1A.class, "withoutAnnotation"),
+        configFor(Test3.class, "withoutAnnotation"),
         new int[] {1},
         "foo",
         TestFakeApp.class,
@@ -225,12 +216,12 @@
         "from-subclass",
         "test/res",
         "test/assets",
-        new Class<?>[] {Test1.class},
+        new Class[] {Test1.class},
         new String[] {"com.example.test1"},
         new String[] {"libs/test"});
 
     assertConfig(
-        configFor(Test1A.class, "withDefaultsAnnotation"),
+        configFor(Test3.class, "withDefaultsAnnotation"),
         new int[] {1},
         "foo",
         TestFakeApp.class,
@@ -238,12 +229,12 @@
         "from-subclass",
         "test/res",
         "test/assets",
-        new Class<?>[] {Test1.class},
+        new Class[] {Test1.class},
         new String[] {"com.example.test1"},
         new String[] {"libs/test"});
 
     assertConfig(
-        configFor(Test1A.class, "withOverrideAnnotation"),
+        configFor(Test3.class, "withOverrideAnnotation"),
         new int[] {9},
         "furf",
         TestApplication.class,
@@ -251,16 +242,15 @@
         "from-method",
         "method/res",
         "method/assets",
-        new Class<?>[] {Test1.class, Test2.class},
+        new Class[] {Test1.class, Test2.class},
         new String[] {"com.example.test1", "com.example.method1"},
         new String[] {"libs/method", "libs/test"});
   }
 
   @Test
-  public void whenClassDoesntHaveConfigAnnotationButSubclassDoes_getConfig_shouldMergeSubclassAndMethodConfig()
-      throws Exception {
+  public void whenClassDoesntHaveConfigAnnotationButSubclassDoes_getConfig_shouldMergeSubclassAndMethodConfig() throws Exception {
     assertConfig(
-        configFor(Test2A.class, "withoutAnnotation"),
+        configFor(Test4.class, "withoutAnnotation"),
         new int[0],
         "AndroidManifest.xml",
         DEFAULT_APPLICATION,
@@ -268,12 +258,12 @@
         "from-subclass",
         "res",
         "assets",
-        new Class<?>[] {},
+        new Class[] {},
         new String[] {},
         new String[] {});
 
     assertConfig(
-        configFor(Test2A.class, "withDefaultsAnnotation"),
+        configFor(Test4.class, "withDefaultsAnnotation"),
         new int[0],
         "AndroidManifest.xml",
         DEFAULT_APPLICATION,
@@ -281,12 +271,12 @@
         "from-subclass",
         "res",
         "assets",
-        new Class<?>[] {},
+        new Class[] {},
         new String[] {},
         new String[] {});
 
     assertConfig(
-        configFor(Test2A.class, "withOverrideAnnotation"),
+        configFor(Test4.class, "withOverrideAnnotation"),
         new int[] {9},
         "furf",
         TestFakeApp.class,
@@ -294,7 +284,7 @@
         "from-method",
         "method/res",
         "method/assets",
-        new Class<?>[] {Test1.class},
+        new Class[] {Test1.class},
         new String[] {"com.example.method2"},
         new String[] {"libs/method"});
   }
@@ -322,7 +312,7 @@
         "from-properties-file",
         "from/properties/file/res",
         "from/properties/file/assets",
-        new Class<?>[] {ShadowView.class, ShadowViewGroup.class},
+        new Class[] {ShadowView.class, ShadowViewGroup.class},
         new String[] {"com.example.test1", "com.example.test2"},
         new String[] {"libs/test", "libs/test2"});
   }
@@ -341,7 +331,7 @@
         "",
         "res",
         "assets",
-        new Class<?>[] {},
+        new Class[] {},
         new String[] {},
         new String[] {});
   }
@@ -366,7 +356,7 @@
         "from-org-robolectric",
         "res",
         "assets",
-        new Class<?>[] {},
+        new Class[] {},
         new String[] {},
         new String[] {"FromOrgRobolectric", "FromOrg", "FromTopLevel"});
   }
@@ -382,126 +372,40 @@
         "",
         "res",
         "assets",
-        new Class<?>[] {},
+        new Class[] {},
         new String[] {},
         new String[] {});
   }
 
-  @Test public void testPrecedence() throws Exception {
-    SpyConfigurer spyConfigurer = new SpyConfigurer();
-
-    ConfigurationStrategy configStrategy =
-        new HierarchicalConfigurationStrategy(spyConfigurer);
-
-    assertThat(computeConfig(configStrategy, Test1.class, "withoutAnnotation"))
-        .isEqualTo(
-            "default:(top):org:org.robolectric:org.robolectric.plugins"
-                + ":" + Test1.class.getName()
-                + ":withoutAnnotation");
-
-    assertThat(computeConfig(configStrategy, Test1A.class, "withOverrideAnnotation"))
-        .isEqualTo(
-            "default:(top):org:org.robolectric:org.robolectric.plugins"
-                + ":" + Test1.class.getName()
-                + ":" + Test1A.class.getName()
-                + ":withOverrideAnnotation");
-  }
-
-  @Test public void testTestClassMatters() throws Exception {
-    SpyConfigurer spyConfigurer = new SpyConfigurer();
-
-    ConfigurationStrategy configStrategy =
-        new HierarchicalConfigurationStrategy(spyConfigurer);
-
-    assertThat(computeConfig(configStrategy, Test1.class, "withoutAnnotation"))
-        .isEqualTo(
-            "default:(top):org:org.robolectric:org.robolectric.plugins"
-                + ":" + Test1.class.getName()
-                + ":withoutAnnotation");
-
-    assertThat(computeConfig(configStrategy, Test1A.class, "withoutAnnotation"))
-        .isEqualTo(
-            "default:(top):org:org.robolectric:org.robolectric.plugins"
-                + ":" + Test1.class.getName()
-                + ":" + Test1A.class.getName()
-                + ":withoutAnnotation");
-  }
-
-  @Test public void testBigOAndCaching() throws Exception {
-    SpyConfigurer spyConfigurer = new SpyConfigurer();
-    ConfigurationStrategy configStrategy =
-        new HierarchicalConfigurationStrategy(spyConfigurer);
-    computeConfig(configStrategy, Test1A.class, "withoutAnnotation");
-
-    assertThat(spyConfigurer.log).containsExactly(
-        "default",
-        "withoutAnnotation",
-        Test1A.class.getName(),
-        Test1.class.getName(),
-        "org.robolectric.plugins",
-        "org.robolectric",
-        "org",
-        "(top)"
-    ).inOrder();
-
-    spyConfigurer.log.clear();
-    computeConfig(configStrategy, Test1.class, "withoutAnnotation");
-    assertThat(spyConfigurer.log).containsExactly(
-        "withoutAnnotation"
-    ).inOrder();
-
-    spyConfigurer.log.clear();
-    computeConfig(configStrategy, Test2A.class, "withOverrideAnnotation");
-    assertThat(spyConfigurer.log).containsExactly(
-        "withOverrideAnnotation",
-        Test2A.class.getName(),
-        Test2.class.getName()
-    ).inOrder();
+  @Test public void testPackageHierarchyOf() throws Exception {
+    assertThat(new DefaultConfigMerger().packageHierarchyOf(DefaultConfigMergerTest.class))
+        .containsExactly("org.robolectric.plugins", "org.robolectric", "org", "");
   }
 
   /////////////////////////////
 
-
-  private String computeConfig(ConfigurationStrategy configStrategy,
-      Class<?> testClass, String methodName)
-      throws NoSuchMethodException {
-    return configStrategy
-        .getConfig(testClass,
-            testClass.getMethod(methodName))
-        .get(String.class);
+  private Config configFor(Class<?> testClass, String methodName, final Map<String, String> configProperties) throws InitializationError {
+    return configFor(testClass, methodName, configProperties, Config.Builder.defaults().build());
   }
 
-  private Config configFor(Class<?> testClass, String methodName,
-      final Map<String, String> configProperties) {
-    return configFor(testClass, methodName, configProperties, null);
-  }
-
-  private Config configFor(Class<?> testClass, String methodName) {
+  private Config configFor(Class<?> testClass, String methodName) throws InitializationError {
     Config.Implementation globalConfig = Config.Builder.defaults().build();
     return configFor(testClass, methodName, globalConfig);
   }
 
-  private Config configFor(Class<?> testClass, String methodName,
-      Config.Implementation globalConfig) {
+  private Config configFor(Class<?> testClass, String methodName, Config.Implementation globalConfig) throws InitializationError {
     return configFor(testClass, methodName, new HashMap<>(), globalConfig);
   }
 
-  private Config configFor(Class<?> testClass, String methodName,
-      final Map<String, String> configProperties, Config.Implementation globalConfig) {
+  private Config configFor(Class<?> testClass, String methodName, final Map<String, String> configProperties, Config.Implementation globalConfig) throws InitializationError {
     Method info = getMethod(testClass, methodName);
-    PackagePropertiesLoader packagePropertiesLoader = new PackagePropertiesLoader() {
+    return new DefaultConfigMerger() {
       @Override
       InputStream getResourceAsStream(String resourceName) {
         String properties = configProperties.get(resourceName);
         return properties == null ? null : new ByteArrayInputStream(properties.getBytes(UTF_8));
       }
-    };
-    ConfigurationStrategy defaultConfigStrategy =
-        new HierarchicalConfigurationStrategy(
-            new ConfigConfigurer(packagePropertiesLoader, () ->
-                globalConfig == null ? Config.Builder.defaults().build() : globalConfig));
-    Configuration config = defaultConfigStrategy.getConfig(testClass, info);
-    return config.get(Config.class);
+    }.getConfig(testClass, info, globalConfig);
   }
 
   private static Method getMethod(Class<?> testClass, String methodName) {
@@ -601,16 +505,16 @@
 
   @Ignore
   @Config(qualifiers = "from-subclass")
-  public static class Test1A extends Test1 {
+  public static class Test3 extends Test1 {
   }
 
   @Ignore
   @Config(qualifiers = "from-subclass")
-  public static class Test2A extends Test2 {
+  public static class Test4 extends Test2 {
   }
 
   @Ignore
-  public static class Test1B extends Test1 {
+  public static class Test5 extends Test1 {
     @Override
     @Test
     public void withoutAnnotation() throws Exception {
@@ -626,7 +530,7 @@
     @Test
     @Config(
         sdk = 14,
-        shadows = Test1B.class,
+        shadows = Test5.class,
         instrumentedPackages = "com.example.method5",
         packageName = "com.example.test",
         qualifiers = "from-method5",
@@ -637,50 +541,8 @@
   @Ignore
   @Config(
       qualifiers = "from-class6",
-      shadows = Test1C.class,
+      shadows = Test6.class,
       instrumentedPackages = "com.example.test6",
       resourceDir = "class6/res")
-  public static class Test1C extends Test1B {}
-
-  private static class SpyConfigurer implements Configurer<String> {
-
-    final List<String> log = new ArrayList<>();
-
-    @Override
-    public Class<String> getConfigClass() {
-      return String.class;
-    }
-
-    @Nonnull
-    @Override
-    public String defaultConfig() {
-      return log("default");
-    }
-
-    @Override
-    public String getConfigFor(@Nonnull String packageName) {
-      return log(packageName.isEmpty() ? "(top)" : packageName);
-    }
-
-    @Override
-    public String getConfigFor(@Nonnull Class<?> testClass) {
-      return log(testClass.getName());
-    }
-
-    @Override
-    public String getConfigFor(@Nonnull Method method) {
-      return log(method.getName());
-    }
-
-    @Nonnull
-    @Override
-    public String merge(@Nonnull String parentConfig, @Nonnull String childConfig) {
-      return parentConfig + ":" + childConfig;
-    }
-
-    private String log(String s) {
-      log.add(s);
-      return s;
-    }
-  }
+  public static class Test6 extends Test5 {}
 }
diff --git a/robolectric/src/test/java/org/robolectric/plugins/DefaultSdkPickerTest.java b/robolectric/src/test/java/org/robolectric/plugins/DefaultSdkPickerTest.java
index 556c81a..c002107 100644
--- a/robolectric/src/test/java/org/robolectric/plugins/DefaultSdkPickerTest.java
+++ b/robolectric/src/test/java/org/robolectric/plugins/DefaultSdkPickerTest.java
@@ -15,11 +15,9 @@
 import org.junit.runners.JUnit4;
 import org.robolectric.annotation.Config;
 import org.robolectric.annotation.internal.ConfigUtils;
-import org.robolectric.pluginapi.ConfigurationStrategy.Configuration;
 import org.robolectric.pluginapi.Sdk;
 import org.robolectric.pluginapi.SdkPicker;
 import org.robolectric.pluginapi.UsesSdk;
-import org.robolectric.plugins.HierarchicalConfigurationStrategy.ConfigurationImpl;
 
 @RunWith(JUnit4.class)
 public class DefaultSdkPickerTest {
@@ -38,7 +36,7 @@
   @Test
   public void withDefaultSdk_shouldUseTargetSdkFromAndroidManifest() throws Exception {
     when(usesSdk.getTargetSdkVersion()).thenReturn(22);
-    assertThat(sdkPicker.selectSdks(buildConfig(new Config.Builder()), usesSdk))
+    assertThat(sdkPicker.selectSdks(new Config.Builder().build(), usesSdk))
         .containsExactly(sdkCollection.getSdk(22));
   }
 
@@ -47,7 +45,7 @@
     when(usesSdk.getTargetSdkVersion()).thenReturn(22);
     when(usesSdk.getMinSdkVersion()).thenReturn(19);
     when(usesSdk.getMaxSdkVersion()).thenReturn(23);
-    assertThat(sdkPicker.selectSdks(buildConfig(new Config.Builder().setSdk(Config.ALL_SDKS)), usesSdk))
+    assertThat(sdkPicker.selectSdks(new Config.Builder().setSdk(Config.ALL_SDKS).build(), usesSdk))
         .containsExactly(sdkCollection.getSdk(19), sdkCollection.getSdk(21),
             sdkCollection.getSdk(22), sdkCollection.getSdk(23));
   }
@@ -57,7 +55,7 @@
     when(usesSdk.getTargetSdkVersion()).thenReturn(22);
     when(usesSdk.getMinSdkVersion()).thenReturn(1);
     when(usesSdk.getMaxSdkVersion()).thenReturn(22);
-    assertThat(sdkPicker.selectSdks(buildConfig(new Config.Builder().setSdk(Config.ALL_SDKS)), usesSdk))
+    assertThat(sdkPicker.selectSdks(new Config.Builder().setSdk(Config.ALL_SDKS).build(), usesSdk))
         .containsExactly(sdkCollection.getSdk(16), sdkCollection.getSdk(17),
             sdkCollection.getSdk(18), sdkCollection.getSdk(19),
             sdkCollection.getSdk(21), sdkCollection.getSdk(22));
@@ -68,7 +66,7 @@
     when(usesSdk.getTargetSdkVersion()).thenReturn(22);
     when(usesSdk.getMinSdkVersion()).thenReturn(19);
     when(usesSdk.getMaxSdkVersion()).thenReturn(null);
-    assertThat(sdkPicker.selectSdks(buildConfig(new Config.Builder().setSdk(Config.ALL_SDKS)), usesSdk))
+    assertThat(sdkPicker.selectSdks(new Config.Builder().setSdk(Config.ALL_SDKS).build(), usesSdk))
         .containsExactly(sdkCollection.getSdk(19), sdkCollection.getSdk(21),
             sdkCollection.getSdk(22), sdkCollection.getSdk(23));
   }
@@ -78,7 +76,7 @@
     when(usesSdk.getTargetSdkVersion()).thenReturn(23);
     when(usesSdk.getMinSdkVersion()).thenReturn(1);
     when(usesSdk.getMaxSdkVersion()).thenReturn(null);
-    assertThat(sdkPicker.selectSdks(buildConfig(new Config.Builder().setMinSdk(24)), usesSdk))
+    assertThat(sdkPicker.selectSdks(new Config.Builder().setMinSdk(24).build(), usesSdk))
         .isEmpty();
   }
 
@@ -90,7 +88,7 @@
 
     try {
       sdkPicker.selectSdks(
-          buildConfig(new Config.Builder().setMinSdk(22).setMaxSdk(21)), usesSdk);
+          new Config.Builder().setMinSdk(22).setMaxSdk(21).build(), usesSdk);
       fail();
     } catch (IllegalArgumentException e) {
       assertThat(e).hasMessageThat().contains("minSdk may not be larger than maxSdk (minSdk=22, maxSdk=21)");
@@ -103,7 +101,7 @@
     when(usesSdk.getTargetSdkVersion()).thenReturn(22);
 
     try {
-      sdkPicker.selectSdks(buildConfig(new Config.Builder()), usesSdk);
+      sdkPicker.selectSdks(new Config.Builder().build(), usesSdk);
       fail();
     } catch (IllegalArgumentException e) {
       assertThat(e).hasMessageThat().contains("Package targetSdkVersion=22 < minSdkVersion=23");
@@ -115,7 +113,7 @@
     when(usesSdk.getMaxSdkVersion()).thenReturn(21);
     when(usesSdk.getTargetSdkVersion()).thenReturn(22);
     try {
-      sdkPicker.selectSdks(buildConfig(new Config.Builder()), usesSdk);
+      sdkPicker.selectSdks(new Config.Builder().build(), usesSdk);
       fail();
     } catch (IllegalArgumentException e) {
       assertThat(e).hasMessageThat().contains("Package targetSdkVersion=22 > maxSdkVersion=21");
@@ -127,7 +125,7 @@
     when(usesSdk.getTargetSdkVersion()).thenReturn(1);
     when(usesSdk.getMinSdkVersion()).thenReturn(1);
     when(usesSdk.getMaxSdkVersion()).thenReturn(null);
-    assertThat(sdkPicker.selectSdks(buildConfig(new Config.Builder()), usesSdk))
+    assertThat(sdkPicker.selectSdks(new Config.Builder().build(), usesSdk))
         .containsExactly(sdkCollection.getSdk(16));
   }
 
@@ -136,7 +134,7 @@
     when(usesSdk.getTargetSdkVersion()).thenReturn(22);
     when(usesSdk.getMinSdkVersion()).thenReturn(19);
     when(usesSdk.getMaxSdkVersion()).thenReturn(23);
-    assertThat(sdkPicker.selectSdks(buildConfig(new Config.Builder().setMinSdk(21)), usesSdk))
+    assertThat(sdkPicker.selectSdks(new Config.Builder().setMinSdk(21).build(), usesSdk))
         .containsExactly(sdkCollection.getSdk(21), sdkCollection.getSdk(22),
             sdkCollection.getSdk(23));
   }
@@ -146,7 +144,7 @@
     when(usesSdk.getTargetSdkVersion()).thenReturn(22);
     when(usesSdk.getMinSdkVersion()).thenReturn(19);
     when(usesSdk.getMaxSdkVersion()).thenReturn(23);
-    assertThat(sdkPicker.selectSdks(buildConfig(new Config.Builder().setMaxSdk(21)), usesSdk))
+    assertThat(sdkPicker.selectSdks(new Config.Builder().setMaxSdk(21).build(), usesSdk))
         .containsExactly(sdkCollection.getSdk(19), sdkCollection.getSdk(21));
   }
 
@@ -156,18 +154,18 @@
     when(usesSdk.getMinSdkVersion()).thenReturn(19);
     when(usesSdk.getMaxSdkVersion()).thenReturn(22);
 
-    assertThat(sdkPicker.selectSdks(buildConfig(new Config.Builder().setSdk(21)), usesSdk))
+    assertThat(sdkPicker.selectSdks(new Config.Builder().setSdk(21).build(), usesSdk))
         .containsExactly(sdkCollection.getSdk(21));
-    assertThat(sdkPicker.selectSdks(buildConfig(new Config.Builder().setSdk(Config.OLDEST_SDK)), usesSdk))
+    assertThat(sdkPicker.selectSdks(new Config.Builder().setSdk(Config.OLDEST_SDK).build(), usesSdk))
         .containsExactly(sdkCollection.getSdk(19));
-    assertThat(sdkPicker.selectSdks(buildConfig(new Config.Builder().setSdk(Config.TARGET_SDK)), usesSdk))
+    assertThat(sdkPicker.selectSdks(new Config.Builder().setSdk(Config.TARGET_SDK).build(), usesSdk))
         .containsExactly(sdkCollection.getSdk(21));
-    assertThat(sdkPicker.selectSdks(buildConfig(new Config.Builder().setSdk(Config.NEWEST_SDK)), usesSdk))
+    assertThat(sdkPicker.selectSdks(new Config.Builder().setSdk(Config.NEWEST_SDK).build(), usesSdk))
         .containsExactly(sdkCollection.getSdk(22));
 
-    assertThat(sdkPicker.selectSdks(buildConfig(new Config.Builder().setSdk(16)), usesSdk))
+    assertThat(sdkPicker.selectSdks(new Config.Builder().setSdk(16).build(), usesSdk))
         .containsExactly(sdkCollection.getSdk(16));
-    assertThat(sdkPicker.selectSdks(buildConfig(new Config.Builder().setSdk(23)), usesSdk))
+    assertThat(sdkPicker.selectSdks(new Config.Builder().setSdk(23).build(), usesSdk))
         .containsExactly(sdkCollection.getSdk(23));
   }
 
@@ -176,7 +174,7 @@
     when(usesSdk.getMinSdkVersion()).thenReturn(16);
     when(usesSdk.getMaxSdkVersion()).thenReturn(23);
     sdkPicker = new DefaultSdkPicker(sdkCollection, "17,18");
-    assertThat(sdkPicker.selectSdks(buildConfig(new Config.Builder().setSdk(Config.ALL_SDKS)), usesSdk))
+    assertThat(sdkPicker.selectSdks(new Config.Builder().setSdk(Config.ALL_SDKS).build(), usesSdk))
         .containsExactly(sdkCollection.getSdk(17), sdkCollection.getSdk(18));
   }
 
@@ -190,12 +188,6 @@
         .containsExactly(VERSION_CODES.KITKAT, VERSION_CODES.LOLLIPOP);
   }
 
-  private Configuration buildConfig(Config.Builder builder) {
-    ConfigurationImpl testConfig = new ConfigurationImpl();
-    testConfig.put(Config.class, builder.build());
-    return testConfig;
-  }
-
   private List<Sdk> map(int... sdkInts) {
     SdkCollection allSdks = new SdkCollection(new DefaultSdkProvider(null));
     return Arrays.stream(sdkInts).mapToObj(allSdks::getSdk).collect(Collectors.toList());
diff --git a/utils/src/main/java/org/robolectric/util/inject/Injector.java b/utils/src/main/java/org/robolectric/util/inject/Injector.java
index aa96952..554971f 100644
--- a/utils/src/main/java/org/robolectric/util/inject/Injector.java
+++ b/utils/src/main/java/org/robolectric/util/inject/Injector.java
@@ -5,7 +5,6 @@
 import java.lang.reflect.AnnotatedType;
 import java.lang.reflect.Array;
 import java.lang.reflect.Constructor;
-import java.lang.reflect.GenericArrayType;
 import java.lang.reflect.InvocationTargetException;
 import java.lang.reflect.Method;
 import java.lang.reflect.Modifier;
@@ -239,8 +238,7 @@
       }
 
       return ctor.newInstance(params);
-    } catch (InstantiationException | IllegalAccessException
-        | InvocationTargetException | IllegalArgumentException e) {
+    } catch (InstantiationException | IllegalAccessException | InvocationTargetException e) {
       throw new InjectionException(implementingClass, e);
     }
   }
@@ -385,8 +383,7 @@
     }
 
     public boolean isArray() {
-      return (theInterface instanceof Class && ((Class) theInterface).isArray())
-          || theInterface instanceof GenericArrayType;
+      return theInterface instanceof Class && ((Class) theInterface).isArray();
     }
 
     public boolean isCollection() {
@@ -399,14 +396,7 @@
 
     Class<?> getComponentType() {
       if (isArray()) {
-        if (theInterface instanceof Class) {
-          return ((Class) theInterface).getComponentType();
-        } else if (theInterface instanceof GenericArrayType) {
-          Type genericComponentType = ((GenericArrayType) theInterface).getGenericComponentType();
-          return (Class<?>) ((ParameterizedType) genericComponentType).getRawType();
-        } else {
-          throw new InjectionException(this, new IllegalArgumentException());
-        }
+        return ((Class) theInterface).getComponentType();
       } else if (isCollection() && theInterface instanceof ParameterizedType) {
         return (Class) ((ParameterizedType) theInterface).getActualTypeArguments()[0];
       } else {