Make `AppInitializer` a singleton.

* Makes testing a lot simpler.

Test: Updated unit test. Ran integration tests.
Change-Id: I7afe54671796e299c93be3526c91657c768ffe70
diff --git a/startup/startup-runtime/api/1.0.0-alpha01.txt b/startup/startup-runtime/api/1.0.0-alpha01.txt
index ce81b08..da9dd9d 100644
--- a/startup/startup-runtime/api/1.0.0-alpha01.txt
+++ b/startup/startup-runtime/api/1.0.0-alpha01.txt
@@ -2,8 +2,9 @@
 package androidx.startup {
 
   public final class AppInitializer {
-    method public static void initialize(android.content.Context);
-    method public static void initialize(android.content.Context, java.util.List<java.lang.Class<?>!>);
+    method public static androidx.startup.AppInitializer getInstance(android.content.Context);
+    method public void initializeAllComponents();
+    method public void initializeComponents(java.util.List<java.lang.Class<?>!>);
   }
 
   public interface ComponentInitializer<T> {
diff --git a/startup/startup-runtime/api/current.txt b/startup/startup-runtime/api/current.txt
index ce81b08..da9dd9d 100644
--- a/startup/startup-runtime/api/current.txt
+++ b/startup/startup-runtime/api/current.txt
@@ -2,8 +2,9 @@
 package androidx.startup {
 
   public final class AppInitializer {
-    method public static void initialize(android.content.Context);
-    method public static void initialize(android.content.Context, java.util.List<java.lang.Class<?>!>);
+    method public static androidx.startup.AppInitializer getInstance(android.content.Context);
+    method public void initializeAllComponents();
+    method public void initializeComponents(java.util.List<java.lang.Class<?>!>);
   }
 
   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 ce81b08..da9dd9d 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
@@ -2,8 +2,9 @@
 package androidx.startup {
 
   public final class AppInitializer {
-    method public static void initialize(android.content.Context);
-    method public static void initialize(android.content.Context, java.util.List<java.lang.Class<?>!>);
+    method public static androidx.startup.AppInitializer getInstance(android.content.Context);
+    method public void initializeAllComponents();
+    method public void initializeComponents(java.util.List<java.lang.Class<?>!>);
   }
 
   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 ce81b08..da9dd9d 100644
--- a/startup/startup-runtime/api/public_plus_experimental_current.txt
+++ b/startup/startup-runtime/api/public_plus_experimental_current.txt
@@ -2,8 +2,9 @@
 package androidx.startup {
 
   public final class AppInitializer {
-    method public static void initialize(android.content.Context);
-    method public static void initialize(android.content.Context, java.util.List<java.lang.Class<?>!>);
+    method public static androidx.startup.AppInitializer getInstance(android.content.Context);
+    method public void initializeAllComponents();
+    method public void initializeComponents(java.util.List<java.lang.Class<?>!>);
   }
 
   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 ce81b08..da9dd9d 100644
--- a/startup/startup-runtime/api/restricted_1.0.0-alpha01.txt
+++ b/startup/startup-runtime/api/restricted_1.0.0-alpha01.txt
@@ -2,8 +2,9 @@
 package androidx.startup {
 
   public final class AppInitializer {
-    method public static void initialize(android.content.Context);
-    method public static void initialize(android.content.Context, java.util.List<java.lang.Class<?>!>);
+    method public static androidx.startup.AppInitializer getInstance(android.content.Context);
+    method public void initializeAllComponents();
+    method public void initializeComponents(java.util.List<java.lang.Class<?>!>);
   }
 
   public interface ComponentInitializer<T> {
diff --git a/startup/startup-runtime/api/restricted_current.txt b/startup/startup-runtime/api/restricted_current.txt
index ce81b08..da9dd9d 100644
--- a/startup/startup-runtime/api/restricted_current.txt
+++ b/startup/startup-runtime/api/restricted_current.txt
@@ -2,8 +2,9 @@
 package androidx.startup {
 
   public final class AppInitializer {
-    method public static void initialize(android.content.Context);
-    method public static void initialize(android.content.Context, java.util.List<java.lang.Class<?>!>);
+    method public static androidx.startup.AppInitializer getInstance(android.content.Context);
+    method public void initializeAllComponents();
+    method public void initializeComponents(java.util.List<java.lang.Class<?>!>);
   }
 
   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 46f93ac..6b1172e 100644
--- a/startup/startup-runtime/src/androidTest/java/androidx/startup/AppInitializerTest.kt
+++ b/startup/startup-runtime/src/androidTest/java/androidx/startup/AppInitializerTest.kt
@@ -19,71 +19,64 @@
 import android.content.Context
 import androidx.test.core.app.ApplicationProvider
 import androidx.test.ext.junit.runners.AndroidJUnit4
-import androidx.test.filters.SmallTest
+import androidx.test.filters.MediumTest
 import org.hamcrest.CoreMatchers.`is`
 import org.hamcrest.CoreMatchers.containsString
-import org.hamcrest.Matchers.containsInAnyOrder
 import org.junit.Assert.assertThat
+import org.junit.Assert.assertTrue
 import org.junit.Assert.fail
 import org.junit.Before
 import org.junit.Test
 import org.junit.runner.RunWith
 
 @RunWith(AndroidJUnit4::class)
-@SmallTest
+@MediumTest
 class AppInitializerTest {
 
     private lateinit var context: Context
+    private lateinit var appInitializer: AppInitializer
 
     @Before
     fun setUp() {
         context = ApplicationProvider.getApplicationContext()
+        appInitializer = AppInitializer(context)
     }
 
     @Test
     fun basicUsageTest() {
-        AppInitializer.initialize(context, listOf<Class<*>>(InitializerNoDependencies::class.java))
-        assertThat(AppInitializer.sInitialized.get(), `is`(true))
+        appInitializer.initializeComponents(listOf<Class<*>>(InitializerNoDependencies::class.java))
+        assertTrue(appInitializer.mInitialized.containsKey(InitializerNoDependencies::class.java))
     }
 
     @Test
     fun basicInitializationTest() {
         val initializing = mutableSetOf<Class<*>>()
-        val initialized = mutableSetOf<Class<*>>()
         val components = listOf<Class<*>>(InitializerNoDependencies::class.java)
-        AppInitializer.doInitialize(context, components, initializing, initialized)
+        appInitializer.doInitialize(components, initializing)
         assertThat(initializing.size, `is`(0))
-        assertThat(initialized.size, `is`(1))
-        assertThat<Collection<Class<*>>>(
-            initialized,
-            containsInAnyOrder<Class<*>>(*components.toTypedArray())
-        )
+        assertThat(appInitializer.mInitialized.size, `is`(1))
+        for (component in components) {
+            assertTrue(appInitializer.mInitialized.containsKey(component))
+        }
     }
 
     @Test
     fun initializationWithDependencies() {
         val initializing = mutableSetOf<Class<*>>()
-        val initialized = mutableSetOf<Class<*>>()
         val components = listOf<Class<*>>(InitializerWithDependency::class.java)
-        AppInitializer.doInitialize(context, components, initializing, initialized)
+        appInitializer.doInitialize(components, initializing)
         assertThat(initializing.size, `is`(0))
-        assertThat(initialized.size, `is`(2))
-        assertThat<Collection<Class<*>>>(
-            initialized,
-            containsInAnyOrder<Class<*>>(
-                InitializerNoDependencies::class.java,
-                InitializerWithDependency::class.java
-            )
-        )
+        assertThat(appInitializer.mInitialized.size, `is`(2))
+        assertTrue(appInitializer.mInitialized.containsKey(InitializerNoDependencies::class.java))
+        assertTrue(appInitializer.mInitialized.containsKey(InitializerWithDependency::class.java))
     }
 
     @Test
     fun initializationWithCyclicDependencies() {
         val initializing = mutableSetOf<Class<*>>()
-        val initialized = mutableSetOf<Class<*>>()
         val components = listOf<Class<*>>(CyclicDependencyInitializer::class.java)
         try {
-            AppInitializer.doInitialize(context, components, initializing, initialized)
+            appInitializer.doInitialize(components, initializing)
             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 1e2b023..2b49ab9 100644
--- a/startup/startup-runtime/src/main/java/androidx/startup/AppInitializer.java
+++ b/startup/startup-runtime/src/main/java/androidx/startup/AppInitializer.java
@@ -28,10 +28,11 @@
 
 import java.util.ArrayList;
 import java.util.Collections;
+import java.util.HashMap;
 import java.util.HashSet;
 import java.util.List;
+import java.util.Map;
 import java.util.Set;
-import java.util.concurrent.atomic.AtomicBoolean;
 
 /**
  * An {@link AppInitializer} can be used to initialize all discovered [ComponentInitializer]s.
@@ -42,49 +43,67 @@
 public final class AppInitializer {
 
     /**
+     * The {@link AppInitializer} instance.
+     */
+    private static AppInitializer sInstance;
+
+    /**
      * Guards app initialization.
      */
     private static final Object sLock = new Object();
 
+    @NonNull
+    final List<Class<?>> mDiscovered;
+
+    @NonNull
+    final Map<Class<?>, Object> mInitialized;
+
+    @NonNull
+    final Context mContext;
+
     /**
-     * Keeps track of whether components were initialized.
+     * Creates an instance of {@link AppInitializer}
+     *
+     * @param context The application context
+     */
+    AppInitializer(@NonNull Context context) {
+        mContext = context.getApplicationContext();
+        mInitialized = new HashMap<>();
+        mDiscovered = discoverComponents();
+    }
+
+    /**
+     * @param context The Application {@link Context}
+     * @return The instance of {@link AppInitializer} after initialization.
      */
     @NonNull
-    static final AtomicBoolean sInitialized = new AtomicBoolean(false);
-
-    private AppInitializer() {
-        // Does nothing.
+    @SuppressWarnings("UnusedReturnValue")
+    public static AppInitializer getInstance(@NonNull Context context) {
+        synchronized (sLock) {
+            if (sInstance == null) {
+                sInstance = new AppInitializer(context);
+            }
+            return sInstance;
+        }
     }
 
     /**
      * Discovers an initializes all available {@link ComponentInitializer} classes based on the
      * merged manifest `<meta-data>` entries in the `AndroidManifest.xml`.
-     *
-     * @param context The Application context
      */
-    public static void initialize(@NonNull Context context) {
-        Context applicationContext = context.getApplicationContext();
-        List<Class<?>> components = discoverComponents(applicationContext);
-        initialize(applicationContext, components);
+    public void initializeAllComponents() {
+        initializeComponents(mDiscovered);
     }
 
     /**
      * Initializes a {@link List} of {@link ComponentInitializer} class types.
      *
-     * @param context    The Application context
      * @param components The {@link List} of {@link Class}es that represent all discovered
      *                   {@link ComponentInitializer}s
      */
-    public static void initialize(@NonNull Context context, @NonNull List<Class<?>> components) {
+    public void initializeComponents(@NonNull List<Class<?>> components) {
         synchronized (sLock) {
-            Context applicationContext = context.getApplicationContext();
-            if (sInitialized.compareAndSet(false, true)) {
-                doInitialize(applicationContext, components, new HashSet<>(), new HashSet<>());
-            } else {
-                if (StartupLogger.DEBUG) {
-                    StartupLogger.i("Already initialized");
-                }
-            }
+            doInitialize(components, new HashSet<>());
         }
     }
 
@@ -92,13 +111,10 @@
      * @hide
      */
     @RestrictTo(RestrictTo.Scope.LIBRARY)
-    public static void doInitialize(
-            @NonNull Context context,
+    public void doInitialize(
             @NonNull List<Class<?>> components,
-            @NonNull Set<Class<?>> initializing,
-            @NonNull Set<Class<?>> initialized) {
+            @NonNull Set<Class<?>> initializing) {
 
-        Context applicationContext = context.getApplicationContext();
         for (Class<?> component : components) {
             if (initializing.contains(component)) {
                 String message = String.format(
@@ -106,7 +122,7 @@
                 );
                 throw new IllegalStateException(message);
             }
-            if (!initialized.contains(component)) {
+            if (!mInitialized.containsKey(component)) {
                 initializing.add(component);
                 try {
                     Object instance = component.getDeclaredConstructor().newInstance();
@@ -121,22 +137,22 @@
                             initializer.dependencies();
                     List<Class<?>> filtered = new ArrayList<>(dependencies.size());
                     for (Class<? extends ComponentInitializer<?>> clazz : dependencies) {
-                        if (!initialized.contains(clazz)) {
+                        if (!mInitialized.containsKey(clazz)) {
                             filtered.add(clazz);
                         }
                     }
                     if (!filtered.isEmpty()) {
-                        doInitialize(applicationContext, filtered, initializing, initialized);
+                        doInitialize(filtered, initializing);
                     }
                     if (StartupLogger.DEBUG) {
                         StartupLogger.i(String.format("Initializing %s", component.getName()));
                     }
-                    initializer.create(applicationContext);
+                    Object result = initializer.create(mContext);
                     if (StartupLogger.DEBUG) {
                         StartupLogger.i(String.format("Initialized %s", component.getName()));
                     }
                     initializing.remove(component);
-                    initialized.add(component);
+                    mInitialized.put(component, result);
                 } catch (Throwable throwable) {
                     throw new StartupException(throwable);
                 }
@@ -149,15 +165,14 @@
      */
     @NonNull
     @RestrictTo(RestrictTo.Scope.LIBRARY)
-    public static List<Class<?>> discoverComponents(@NonNull Context context) {
+    public List<Class<?>> discoverComponents() {
         try {
-            Context applicationContext = context.getApplicationContext();
             ApplicationInfo applicationInfo =
-                    applicationContext.getPackageManager()
-                            .getApplicationInfo(applicationContext.getPackageName(), GET_META_DATA);
+                    mContext.getPackageManager()
+                            .getApplicationInfo(mContext.getPackageName(), GET_META_DATA);
 
             Bundle metadata = applicationInfo.metaData;
-            String startup = applicationContext.getString(R.string.androidx_startup);
+            String startup = mContext.getString(R.string.androidx_startup);
             if (metadata != null) {
                 List<Class<?>> components = new ArrayList<>(metadata.size());
                 Set<String> keys = metadata.keySet();
diff --git a/startup/startup-runtime/src/main/java/androidx/startup/ComponentInitializer.java b/startup/startup-runtime/src/main/java/androidx/startup/ComponentInitializer.java
index ca10fd1..280a3ad 100644
--- a/startup/startup-runtime/src/main/java/androidx/startup/ComponentInitializer.java
+++ b/startup/startup-runtime/src/main/java/androidx/startup/ComponentInitializer.java
@@ -36,7 +36,6 @@
      * @param context The application context.
      */
     @NonNull
-    @SuppressWarnings("UnusedReturnValue")
     T create(@NonNull Context context);
 
     /**
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 6e5978d..24873746 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.initialize(context);
+            AppInitializer.getInstance(context).initializeAllComponents();
         } else {
             throw new StartupException("Context cannot be null");
         }