Add `initializeComponent` API

Test: Unit tests pass. Ran instrumentation tests.
Change-Id: I2208803fcf1ff84f0c2c93025b6c343d37a5ba22
diff --git a/startup/startup-runtime/api/1.0.0-alpha01.txt b/startup/startup-runtime/api/1.0.0-alpha01.txt
index da9dd9d..ea4f606 100644
--- a/startup/startup-runtime/api/1.0.0-alpha01.txt
+++ b/startup/startup-runtime/api/1.0.0-alpha01.txt
@@ -3,8 +3,7 @@
 
   public final class AppInitializer {
     method public static androidx.startup.AppInitializer getInstance(android.content.Context);
-    method public void initializeAllComponents();
-    method public void initializeComponents(java.util.List<java.lang.Class<?>!>);
+    method public <T> T initializeComponent(Class<? extends androidx.startup.ComponentInitializer<T!>>);
   }
 
   public interface ComponentInitializer<T> {
diff --git a/startup/startup-runtime/api/current.txt b/startup/startup-runtime/api/current.txt
index da9dd9d..ea4f606 100644
--- a/startup/startup-runtime/api/current.txt
+++ b/startup/startup-runtime/api/current.txt
@@ -3,8 +3,7 @@
 
   public final class AppInitializer {
     method public static androidx.startup.AppInitializer getInstance(android.content.Context);
-    method public void initializeAllComponents();
-    method public void initializeComponents(java.util.List<java.lang.Class<?>!>);
+    method public <T> T initializeComponent(Class<? extends androidx.startup.ComponentInitializer<T!>>);
   }
 
   public interface ComponentInitializer<T> {
diff --git a/startup/startup-runtime/api/public_plus_experimental_1.0.0-alpha01.txt b/startup/startup-runtime/api/public_plus_experimental_1.0.0-alpha01.txt
index da9dd9d..ea4f606 100644
--- a/startup/startup-runtime/api/public_plus_experimental_1.0.0-alpha01.txt
+++ b/startup/startup-runtime/api/public_plus_experimental_1.0.0-alpha01.txt
@@ -3,8 +3,7 @@
 
   public final class AppInitializer {
     method public static androidx.startup.AppInitializer getInstance(android.content.Context);
-    method public void initializeAllComponents();
-    method public void initializeComponents(java.util.List<java.lang.Class<?>!>);
+    method public <T> T initializeComponent(Class<? extends androidx.startup.ComponentInitializer<T!>>);
   }
 
   public interface ComponentInitializer<T> {
diff --git a/startup/startup-runtime/api/public_plus_experimental_current.txt b/startup/startup-runtime/api/public_plus_experimental_current.txt
index da9dd9d..ea4f606 100644
--- a/startup/startup-runtime/api/public_plus_experimental_current.txt
+++ b/startup/startup-runtime/api/public_plus_experimental_current.txt
@@ -3,8 +3,7 @@
 
   public final class AppInitializer {
     method public static androidx.startup.AppInitializer getInstance(android.content.Context);
-    method public void initializeAllComponents();
-    method public void initializeComponents(java.util.List<java.lang.Class<?>!>);
+    method public <T> T initializeComponent(Class<? extends androidx.startup.ComponentInitializer<T!>>);
   }
 
   public interface ComponentInitializer<T> {
diff --git a/startup/startup-runtime/api/restricted_1.0.0-alpha01.txt b/startup/startup-runtime/api/restricted_1.0.0-alpha01.txt
index da9dd9d..ea4f606 100644
--- a/startup/startup-runtime/api/restricted_1.0.0-alpha01.txt
+++ b/startup/startup-runtime/api/restricted_1.0.0-alpha01.txt
@@ -3,8 +3,7 @@
 
   public final class AppInitializer {
     method public static androidx.startup.AppInitializer getInstance(android.content.Context);
-    method public void initializeAllComponents();
-    method public void initializeComponents(java.util.List<java.lang.Class<?>!>);
+    method public <T> T initializeComponent(Class<? extends androidx.startup.ComponentInitializer<T!>>);
   }
 
   public interface ComponentInitializer<T> {
diff --git a/startup/startup-runtime/api/restricted_current.txt b/startup/startup-runtime/api/restricted_current.txt
index da9dd9d..ea4f606 100644
--- a/startup/startup-runtime/api/restricted_current.txt
+++ b/startup/startup-runtime/api/restricted_current.txt
@@ -3,8 +3,7 @@
 
   public final class AppInitializer {
     method public static androidx.startup.AppInitializer getInstance(android.content.Context);
-    method public void initializeAllComponents();
-    method public void initializeComponents(java.util.List<java.lang.Class<?>!>);
+    method public <T> T initializeComponent(Class<? extends androidx.startup.ComponentInitializer<T!>>);
   }
 
   public interface ComponentInitializer<T> {
diff --git a/startup/startup-runtime/src/androidTest/java/androidx/startup/AppInitializerTest.kt b/startup/startup-runtime/src/androidTest/java/androidx/startup/AppInitializerTest.kt
index 6b1172e..899c90e 100644
--- a/startup/startup-runtime/src/androidTest/java/androidx/startup/AppInitializerTest.kt
+++ b/startup/startup-runtime/src/androidTest/java/androidx/startup/AppInitializerTest.kt
@@ -43,29 +43,15 @@
     }
 
     @Test
-    fun basicUsageTest() {
-        appInitializer.initializeComponents(listOf<Class<*>>(InitializerNoDependencies::class.java))
+    fun basicInitializationTest() {
+        appInitializer.initializeComponent(InitializerNoDependencies::class.java)
+        assertThat(appInitializer.mInitialized.size, `is`(1))
         assertTrue(appInitializer.mInitialized.containsKey(InitializerNoDependencies::class.java))
     }
 
     @Test
-    fun basicInitializationTest() {
-        val initializing = mutableSetOf<Class<*>>()
-        val components = listOf<Class<*>>(InitializerNoDependencies::class.java)
-        appInitializer.doInitialize(components, initializing)
-        assertThat(initializing.size, `is`(0))
-        assertThat(appInitializer.mInitialized.size, `is`(1))
-        for (component in components) {
-            assertTrue(appInitializer.mInitialized.containsKey(component))
-        }
-    }
-
-    @Test
     fun initializationWithDependencies() {
-        val initializing = mutableSetOf<Class<*>>()
-        val components = listOf<Class<*>>(InitializerWithDependency::class.java)
-        appInitializer.doInitialize(components, initializing)
-        assertThat(initializing.size, `is`(0))
+        appInitializer.initializeComponent(InitializerWithDependency::class.java)
         assertThat(appInitializer.mInitialized.size, `is`(2))
         assertTrue(appInitializer.mInitialized.containsKey(InitializerNoDependencies::class.java))
         assertTrue(appInitializer.mInitialized.containsKey(InitializerWithDependency::class.java))
@@ -73,10 +59,8 @@
 
     @Test
     fun initializationWithCyclicDependencies() {
-        val initializing = mutableSetOf<Class<*>>()
-        val components = listOf<Class<*>>(CyclicDependencyInitializer::class.java)
         try {
-            appInitializer.doInitialize(components, initializing)
+            appInitializer.initializeComponent(CyclicDependencyInitializer::class.java)
             fail()
         } catch (exception: StartupException) {
             assertThat(exception.localizedMessage, containsString("Cycle detected."))
diff --git a/startup/startup-runtime/src/main/java/androidx/startup/AppInitializer.java b/startup/startup-runtime/src/main/java/androidx/startup/AppInitializer.java
index 4be6352..c9e7ad1 100644
--- a/startup/startup-runtime/src/main/java/androidx/startup/AppInitializer.java
+++ b/startup/startup-runtime/src/main/java/androidx/startup/AppInitializer.java
@@ -24,10 +24,7 @@
 import android.os.Bundle;
 
 import androidx.annotation.NonNull;
-import androidx.annotation.RestrictTo;
 
-import java.util.ArrayList;
-import java.util.Collections;
 import java.util.HashMap;
 import java.util.HashSet;
 import java.util.List;
@@ -53,9 +50,6 @@
     private static final Object sLock = new Object();
 
     @NonNull
-    final List<Class<?>> mDiscovered;
-
-    @NonNull
     final Map<Class<?>, Object> mInitialized;
 
     @NonNull
@@ -69,7 +63,6 @@
     AppInitializer(@NonNull Context context) {
         mContext = context.getApplicationContext();
         mInitialized = new HashMap<>();
-        mDiscovered = discoverComponents();
     }
 
     /**
@@ -88,69 +81,51 @@
     }
 
     /**
-     * Discovers an initializes all available {@link ComponentInitializer} classes based on the
-     * merged manifest `<meta-data>` entries in the `AndroidManifest.xml`.
-     */
-    public void initializeAllComponents() {
-        initializeComponents(mDiscovered);
-    }
-
-    /**
-     * Initializes a {@link List} of {@link ComponentInitializer} class types.
+     * Initializes a {@link ComponentInitializer} class type.
      *
-     * @param components The {@link List} of {@link Class}es that represent all discovered
-     *                   {@link ComponentInitializer}s
+     * @param component The {@link Class} of {@link ComponentInitializer} to initialize.
+     * @param <T>       The instance type being initialized
+     * @return The initialized instance
      */
-    public void initializeComponents(@NonNull List<Class<?>> components) {
-        synchronized (sLock) {
-            doInitialize(components, new HashSet<>());
-        }
+    @NonNull
+    @SuppressWarnings("unused")
+    public <T> T initializeComponent(@NonNull Class<? extends ComponentInitializer<T>> component) {
+        return doInitialize(component, new HashSet<>());
     }
 
-    /**
-     * @hide
-     */
-    @RestrictTo(RestrictTo.Scope.LIBRARY)
-    public void doInitialize(
-            @NonNull List<Class<?>> components,
+    @NonNull
+    @SuppressWarnings({"unchecked", "TypeParameterUnusedInFormals"})
+    <T> T doInitialize(
+            @NonNull Class<? extends ComponentInitializer<?>> component,
             @NonNull Set<Class<?>> initializing) {
-
-        for (Class<?> component : components) {
+        synchronized (sLock) {
             if (initializing.contains(component)) {
                 String message = String.format(
                         "Cannot initialize %s. Cycle detected.", component.getName()
                 );
                 throw new IllegalStateException(message);
             }
+            Object result;
             if (!mInitialized.containsKey(component)) {
                 initializing.add(component);
                 try {
                     Object instance = component.getDeclaredConstructor().newInstance();
-                    if (!(instance instanceof ComponentInitializer<?>)) {
-                        String message = String.format(
-                                "%s is not a subtype of ComponentInitializer", component.getName()
-                        );
-                        throw new IllegalStateException(message);
-                    }
                     ComponentInitializer<?> initializer = (ComponentInitializer<?>) instance;
                     List<Class<? extends ComponentInitializer<?>>> dependencies =
                             initializer.dependencies();
-                    List<Class<?>> filtered = null;
+
                     if (!dependencies.isEmpty()) {
-                        filtered = new ArrayList<>(dependencies.size());
                         for (Class<? extends ComponentInitializer<?>> clazz : dependencies) {
                             if (!mInitialized.containsKey(clazz)) {
-                                filtered.add(clazz);
+                                doInitialize(clazz, initializing);
                             }
                         }
                     }
-                    if (filtered != null && !filtered.isEmpty()) {
-                        doInitialize(filtered, initializing);
-                    }
+
                     if (StartupLogger.DEBUG) {
                         StartupLogger.i(String.format("Initializing %s", component.getName()));
                     }
-                    Object result = initializer.create(mContext);
+                    result = initializer.create(mContext);
                     if (StartupLogger.DEBUG) {
                         StartupLogger.i(String.format("Initialized %s", component.getName()));
                     }
@@ -159,16 +134,15 @@
                 } catch (Throwable throwable) {
                     throw new StartupException(throwable);
                 }
+            } else {
+                result = mInitialized.get(component);
             }
+            return (T) result;
         }
     }
 
-    /**
-     * @hide
-     */
-    @NonNull
-    @RestrictTo(RestrictTo.Scope.LIBRARY)
-    public List<Class<?>> discoverComponents() {
+    @SuppressWarnings("unchecked")
+    void discoverAndInitialize() {
         try {
             ApplicationInfo applicationInfo =
                     mContext.getPackageManager()
@@ -177,21 +151,22 @@
             Bundle metadata = applicationInfo.metaData;
             String startup = mContext.getString(R.string.androidx_startup);
             if (metadata != null) {
-                List<Class<?>> components = new ArrayList<>(metadata.size());
+                Set<Class<?>> initializing = new HashSet<>();
                 Set<String> keys = metadata.keySet();
                 for (String key : keys) {
                     String value = metadata.getString(key, null);
                     if (startup.equals(value)) {
                         Class<?> clazz = Class.forName(key);
-                        if (StartupLogger.DEBUG) {
-                            StartupLogger.i(String.format("Discovered %s", key));
+                        if (ComponentInitializer.class.isAssignableFrom(clazz)) {
+                            Class<? extends ComponentInitializer<?>> component =
+                                    (Class<? extends ComponentInitializer<?>>) clazz;
+                            if (StartupLogger.DEBUG) {
+                                StartupLogger.i(String.format("Discovered %s", key));
+                            }
+                            doInitialize(component, initializing);
                         }
-                        components.add(clazz);
                     }
                 }
-                return components;
-            } else {
-                return Collections.emptyList();
             }
         } catch (PackageManager.NameNotFoundException | ClassNotFoundException exception) {
             throw new StartupException(exception);
diff --git a/startup/startup-runtime/src/main/java/androidx/startup/InitializationProvider.java b/startup/startup-runtime/src/main/java/androidx/startup/InitializationProvider.java
index 24873746..d94aafc 100644
--- a/startup/startup-runtime/src/main/java/androidx/startup/InitializationProvider.java
+++ b/startup/startup-runtime/src/main/java/androidx/startup/InitializationProvider.java
@@ -39,7 +39,7 @@
     public boolean onCreate() {
         Context context = getContext();
         if (context != null) {
-            AppInitializer.getInstance(context).initializeAllComponents();
+            AppInitializer.getInstance(context).discoverAndInitialize();
         } else {
             throw new StartupException("Context cannot be null");
         }