Merge sc-mainline-prod to master.

Change-Id: Idb3bf0f987f6b32516a25482a278d0e26f1fd546
diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml
index 328d4b1..6c818cc 100644
--- a/.github/workflows/ci.yml
+++ b/.github/workflows/ci.yml
@@ -5,12 +5,43 @@
     branches:
       - master
   pull_request:
+    branches:
+      - master
+
+env:
+  # Our Bazel builds currently rely on JDK 8.
+  USE_JAVA_VERSION: '8'
+  # Our Bazel builds currently rely on 3.7.1. The version is set via
+  # baselisk by USE_BAZEL_VERSION: https://github.com/bazelbuild/bazelisk.
+  USE_BAZEL_VERSION: '3.7.1'
 
 jobs:
-  bazel-test:
-    name: 'Bazel tests'
+  validate-latest-dagger-version:
+    name: 'Validate Dagger version'
     runs-on: ubuntu-latest
     steps:
+      # Cancel previous runs on the same branch to avoid unnecessary parallel
+      # runs of the same job. See https://github.com/google/go-github/pull/1821
+      - name: Cancel previous
+        uses: styfle/cancel-workflow-action@0.8.0
+        with:
+          access_token: ${{ github.token }}
+      - name: 'Check out gh-pages repository'
+        uses: actions/checkout@v2
+        with:
+          ref: 'refs/heads/gh-pages'
+          path: gh-pages
+      - name: 'Validate latest Dagger version'
+        run: ./gh-pages/.github/scripts/validate-latest-dagger-version.sh gh-pages/_config.yml
+  bazel-test:
+    name: 'Bazel tests'
+    needs: validate-latest-dagger-version
+    runs-on: ubuntu-latest
+    steps:
+      - name: 'Install Java ${{ env.USE_JAVA_VERSION }}'
+        uses: actions/setup-java@v1
+        with:
+          java-version: '${{ env.USE_JAVA_VERSION }}'
       - name: 'Check out repository'
         uses: actions/checkout@v2
       - name: 'Cache local Maven repository'
@@ -39,7 +70,7 @@
           restore-keys: |
             ${{ runner.os }}-gradle-
       - name: 'Run Bazel tests'
-        run: bazel test --test_output=errors //...
+        run: bazel test --test_output=errors //... --keep_going
         shell: bash
       - name: 'Install local snapshot'
         run: ./util/install-local-snapshot.sh
@@ -100,6 +131,12 @@
       - name: 'Gradle Android local tests (AGP ${{ matrix.agp }})'
         run: ./util/run-local-gradle-android-tests.sh "${{ matrix.agp }}"
         shell: bash
+      - name: 'Upload test reports (AGP ${{ matrix.agp }})'
+        if: ${{ always() }}
+        uses: actions/upload-artifact@v2
+        with:
+          name: tests-reports-agp-${{ matrix.agp }}
+          path: ${{ github.workspace }}/**/build/reports/tests/*
   artifact-android-emulator-tests:
     name: 'Artifact Android emulator tests (API 30)'
     needs: bazel-test
@@ -130,6 +167,12 @@
           api-level: 30
           target: google_apis
           script: ./util/run-local-emulator-tests.sh
+      - name: 'Upload test reports (API 30)'
+        if: ${{ always() }}
+        uses: actions/upload-artifact@v2
+        with:
+          name: androidTests-reports-api-30
+          path: ${{ github.workspace }}/**/build/reports/androidTests/connected/*
   publish-snapshot:
     name: 'Publish snapshot'
     # TODO(bcorso): Consider also waiting on artifact-android-emulator-tests
@@ -138,6 +181,10 @@
     if: github.event_name == 'push' && github.repository == 'google/dagger' && github.ref == 'refs/heads/master'
     runs-on: ubuntu-latest
     steps:
+      - name: 'Install Java ${{ env.USE_JAVA_VERSION }}'
+        uses: actions/setup-java@v1
+        with:
+          java-version: '${{ env.USE_JAVA_VERSION }}'
       - name: 'Check out repository'
         uses: actions/checkout@v2
       - name: 'Cache local Maven repository'
@@ -186,8 +233,8 @@
     # See https://github.com/marketplace/actions/android-emulator-runner
     runs-on: macos-latest
     strategy:
-      matrix:
-        api-level: [16, 17, 18, 19, 21, 22, 23, 24, 25, 26, 27, 29]
+      matrix: # Run on 16 (PreL), 21 (L), and 26 (O).
+        api-level: [16, 21, 26]
     steps:
       - name: 'Check out repository'
         uses: actions/checkout@v2
@@ -212,3 +259,9 @@
           api-level: ${{ matrix.api-level }}
           target: google_apis
           script: ./util/run-local-emulator-tests.sh
+      - name: 'Upload test reports (API ${{ matrix.api-level }})'
+        if: ${{ always() }}
+        uses: actions/upload-artifact@v2
+        with:
+          name: androidTests-report-api-${{ matrix.api-level }}
+          path: ${{ github.workspace }}/**/build/reports/androidTests/connected/*
diff --git a/Android.bp b/Android.bp
index 1bdbd1b..7573ab7 100644
--- a/Android.bp
+++ b/Android.bp
@@ -236,11 +236,17 @@
         "java/dagger/hilt/android/internal/migration/*.java",
         "java/dagger/hilt/android/internal/modules/*.java",
         "java/dagger/hilt/android/lifecycle/*.java",
+        "java/dagger/hilt/internal/aggregatedroot/*.java",
+        "java/dagger/hilt/internal/processedrootsentinel/*.java",
     ],
     manifest: "java/dagger/hilt/android/AndroidManifest.xml",
     static_libs: [
+        "androidx.activity_activity",
         "androidx.annotation_annotation",
         "androidx.fragment_fragment",
+        "androidx.lifecycle_lifecycle-common",
+        "androidx.lifecycle_lifecycle-viewmodel",
+        "androidx.lifecycle_lifecycle-viewmodel-savedstate",
         "jsr305",
         "jsr330",
         "dagger2",
@@ -383,7 +389,7 @@
 java_plugin {
     name: "hilt_uninstall_modules_processor",
     generates_api: true,
-    processor_class: "dagger.hilt.android.processor.internal.uninstallmodules.UninstallModulesProcessor",
+    processor_class: "dagger.hilt.processor.internal.uninstallmodules.UninstallModulesProcessor",
 }
 
 java_library_host {
diff --git a/BUILD b/BUILD
index 015e849..b2495d7 100644
--- a/BUILD
+++ b/BUILD
@@ -97,6 +97,22 @@
     ],
 )
 
+android_library(
+    name = "android_local_test_exports",
+    exports = [
+        # TODO(bcorso): see if we can remove jsr250 dep from autovalue to prevent this.
+        "@javax_annotation_jsr250_api",  # For @Generated
+        "@maven//:org_robolectric_shadows_framework",  # For ActivityController
+        "@maven//:androidx_lifecycle_lifecycle_common",  # For Lifecycle.State
+        "@maven//:androidx_activity_activity",  # For ComponentActivity
+        "@maven//:androidx_test_core",  # For ApplicationProvider
+        "@maven//:androidx_test_ext_junit",
+        "@maven//:org_robolectric_annotations",
+        "@maven//:org_robolectric_robolectric",
+        "@robolectric//bazel:android-all",
+    ],
+)
+
 # coalesced javadocs used for the gh-pages site
 javadoc_library(
     name = "user-docs",
diff --git a/WORKSPACE b/WORKSPACE
index 6c2b6b0..d49c0e2 100644
--- a/WORKSPACE
+++ b/WORKSPACE
@@ -12,6 +12,12 @@
 # See the License for the specific language governing permissions and
 # limitations under the License.
 
+load("@bazel_tools//tools/build_defs/repo:http.bzl", "http_archive")
+
+#############################
+# Load nested repository
+#############################
+
 # Declare the nested workspace so that the top-level workspace doesn't try to
 # traverse it when calling `bazel build //...`
 local_repository(
@@ -19,7 +25,9 @@
     path = "examples/bazel",
 )
 
-load("@bazel_tools//tools/build_defs/repo:http.bzl", "http_archive")
+#############################
+# Load Bazel-Common repository
+#############################
 
 http_archive(
     name = "google_bazel_common",
@@ -32,16 +40,9 @@
 
 google_common_workspace_rules()
 
-RULES_JVM_EXTERNAL_TAG = "2.7"
-
-RULES_JVM_EXTERNAL_SHA = "f04b1466a00a2845106801e0c5cec96841f49ea4e7d1df88dc8e4bf31523df74"
-
-http_archive(
-    name = "rules_jvm_external",
-    sha256 = RULES_JVM_EXTERNAL_SHA,
-    strip_prefix = "rules_jvm_external-%s" % RULES_JVM_EXTERNAL_TAG,
-    url = "https://github.com/bazelbuild/rules_jvm_external/archive/%s.zip" % RULES_JVM_EXTERNAL_TAG,
-)
+#############################
+# Load Protobuf dependencies
+#############################
 
 # rules_python and zlib are required by protobuf.
 # TODO(ronshapiro): Figure out if zlib is in fact necessary, or if proto can depend on the
@@ -65,6 +66,27 @@
     urls = ["https://github.com/madler/zlib/archive/v1.2.11.tar.gz"],
 )
 
+#############################
+# Load Robolectric repository
+#############################
+
+ROBOLECTRIC_VERSION = "4.4"
+
+http_archive(
+    name = "robolectric",
+    sha256 = "d4f2eb078a51f4e534ebf5e18b6cd4646d05eae9b362ac40b93831bdf46112c7",
+    strip_prefix = "robolectric-bazel-%s" % ROBOLECTRIC_VERSION,
+    urls = ["https://github.com/robolectric/robolectric-bazel/archive/%s.tar.gz" % ROBOLECTRIC_VERSION],
+)
+
+load("@robolectric//bazel:robolectric.bzl", "robolectric_repositories")
+
+robolectric_repositories()
+
+#############################
+# Load Kotlin repository
+#############################
+
 RULES_KOTLIN_COMMIT = "2c283821911439e244285b5bfec39148e7d90e21"
 
 RULES_KOTLIN_SHA = "b04cd539e7e3571745179da95069586b6fa76a64306b24bb286154e652010608"
@@ -96,6 +118,21 @@
 
 register_toolchains("//:kotlin_toolchain")
 
+#############################
+# Load Maven dependencies
+#############################
+
+RULES_JVM_EXTERNAL_TAG = "2.7"
+
+RULES_JVM_EXTERNAL_SHA = "f04b1466a00a2845106801e0c5cec96841f49ea4e7d1df88dc8e4bf31523df74"
+
+http_archive(
+    name = "rules_jvm_external",
+    sha256 = RULES_JVM_EXTERNAL_SHA,
+    strip_prefix = "rules_jvm_external-%s" % RULES_JVM_EXTERNAL_TAG,
+    url = "https://github.com/bazelbuild/rules_jvm_external/archive/%s.zip" % RULES_JVM_EXTERNAL_TAG,
+)
+
 load("@rules_jvm_external//:defs.bzl", "maven_install")
 
 ANDROID_LINT_VERSION = "26.6.2"
@@ -104,14 +141,16 @@
     artifacts = [
         "androidx.annotation:annotation:1.1.0",
         "androidx.appcompat:appcompat:1.2.0",
-        "androidx.activity:activity:1.1.0",
-        "androidx.fragment:fragment:1.2.5",
-        "androidx.lifecycle:lifecycle-viewmodel:2.2.0",
-        "androidx.lifecycle:lifecycle-viewmodel-savedstate:2.2.0",
+        "androidx.activity:activity:1.2.2",
+        "androidx.fragment:fragment:1.3.2",
+        "androidx.lifecycle:lifecycle-common:2.3.1",
+        "androidx.lifecycle:lifecycle-viewmodel:2.3.1",
+        "androidx.lifecycle:lifecycle-viewmodel-savedstate:2.3.1",
         "androidx.multidex:multidex:2.0.1",
         "androidx.savedstate:savedstate:1.0.0",
         "androidx.test:monitor:1.1.1",
         "androidx.test:core:1.1.0",
+        "androidx.test.ext:junit:1.1.2",
         "com.google.auto:auto-common:0.11",
         "com.android.support:appcompat-v7:25.0.0",
         "com.android.support:support-annotations:25.0.0",
@@ -126,9 +165,11 @@
         "com.android.tools:testutils:%s" % ANDROID_LINT_VERSION,
         "com.github.tschuchortdev:kotlin-compile-testing:1.2.8",
         "com.google.guava:guava:27.1-android",
+        "junit:junit:4.13",
         "org.jetbrains.kotlin:kotlin-stdlib:%s" % KOTLIN_VERSION,
-        "org.jetbrains.kotlinx:kotlinx-metadata-jvm:0.1.0",
-        "org.robolectric:robolectric:4.3.1",
+        "org.jetbrains.kotlinx:kotlinx-metadata-jvm:0.2.0",
+        "org.robolectric:robolectric:4.4",
+        "org.robolectric:shadows-framework:4.4",  # For ActivityController
     ],
     repositories = [
         "https://repo1.maven.org/maven2",
@@ -137,6 +178,10 @@
     ],
 )
 
+#############################
+# Load Bazel Skylib rules
+#############################
+
 BAZEL_SKYLIB_VERSION = "1.0.2"
 
 BAZEL_SKYLIB_SHA = "97e70364e9249702246c0e9444bccdc4b847bed1eb03c5a3ece4f83dfe6abc44"
diff --git a/build_defs.bzl b/build_defs.bzl
index 932ca08..915f0a0 100644
--- a/build_defs.bzl
+++ b/build_defs.bzl
@@ -25,7 +25,4 @@
     "1.7",
 ]
 
-POM_VERSION = "2.29.1"
-
-# DO NOT remove the comment on the next line. It's used in deploy-to-maven-central.sh
-POM_VERSION_ALPHA = POM_VERSION  + "-alpha"
+POM_VERSION = "${project.version}"
diff --git a/java/dagger/android/support/BUILD b/java/dagger/android/support/BUILD
index 03846b5..8afa703 100644
--- a/java/dagger/android/support/BUILD
+++ b/java/dagger/android/support/BUILD
@@ -41,9 +41,13 @@
         "//:dagger_with_compiler",
         "//java/dagger/android",
         "@google_bazel_common//third_party/java/error_prone:annotations",
+        "@maven//:androidx_activity_activity",
         "@maven//:androidx_annotation_annotation",
         "@maven//:androidx_appcompat_appcompat",
         "@maven//:androidx_fragment_fragment",
+        "@maven//:androidx_lifecycle_lifecycle_common",
+        "@maven//:androidx_lifecycle_lifecycle_viewmodel",
+        "@maven//:androidx_lifecycle_lifecycle_viewmodel_savedstate",
     ],
 )
 
diff --git a/java/dagger/android/support/DaggerAppCompatActivity.java b/java/dagger/android/support/DaggerAppCompatActivity.java
index da5b173..3f0469e 100644
--- a/java/dagger/android/support/DaggerAppCompatActivity.java
+++ b/java/dagger/android/support/DaggerAppCompatActivity.java
@@ -17,10 +17,10 @@
 package dagger.android.support;
 
 import android.os.Bundle;
-import androidx.annotation.ContentView;
-import androidx.annotation.LayoutRes;
 import androidx.annotation.Nullable;
 import androidx.appcompat.app.AppCompatActivity;
+import androidx.annotation.ContentView;
+import androidx.annotation.LayoutRes;
 import dagger.android.AndroidInjection;
 import dagger.android.AndroidInjector;
 import dagger.android.DispatchingAndroidInjector;
diff --git a/java/dagger/android/support/DaggerFragment.java b/java/dagger/android/support/DaggerFragment.java
index 8874511..e705b3c 100644
--- a/java/dagger/android/support/DaggerFragment.java
+++ b/java/dagger/android/support/DaggerFragment.java
@@ -17,9 +17,9 @@
 package dagger.android.support;
 
 import android.content.Context;
+import androidx.fragment.app.Fragment;
 import androidx.annotation.ContentView;
 import androidx.annotation.LayoutRes;
-import androidx.fragment.app.Fragment;
 import dagger.android.AndroidInjector;
 import dagger.android.DispatchingAndroidInjector;
 import dagger.android.HasAndroidInjector;
diff --git a/java/dagger/hilt/BUILD b/java/dagger/hilt/BUILD
index 94af18a..3d12c91 100644
--- a/java/dagger/hilt/BUILD
+++ b/java/dagger/hilt/BUILD
@@ -13,7 +13,7 @@
 # limitations under the License.
 
 load("//tools:maven.bzl", "gen_maven_artifact")
-load("//:build_defs.bzl", "POM_VERSION_ALPHA")
+load("//:build_defs.bzl", "POM_VERSION")
 
 # Description:
 #   A library that wraps the Dagger API to make DI usage and testing easier.
@@ -46,11 +46,14 @@
         # TODO(bcorso): Consider using a separate processor to validate @EntryPoint.
         "//java/dagger/hilt/processor/internal/aggregateddeps:plugin",
     ],
+    proguard_specs = ["proguard-rules.pro"],
     deps = [
         ":generates_root_input",
         ":package_info",
         "//java/dagger/hilt/internal:component_manager",
         "//java/dagger/hilt/internal:generated_component",
+        "//java/dagger/hilt/internal:preconditions",
+        "//java/dagger/hilt/internal:test_singleton_component",
         "@google_bazel_common//third_party/java/jsr305_annotations",
     ],
 )
@@ -162,7 +165,6 @@
         "//java/dagger/hilt/android/processor/internal/androidentrypoint:srcs_filegroup",
         "//java/dagger/hilt/android/processor/internal/bindvalue:srcs_filegroup",
         "//java/dagger/hilt/android/processor/internal/customtestapplication:srcs_filegroup",
-        "//java/dagger/hilt/android/processor/internal/uninstallmodules:srcs_filegroup",
         "//java/dagger/hilt/android/processor/internal/viewmodel:srcs_filegroup",
         "//java/dagger/hilt/processor:srcs_filegroup",
         "//java/dagger/hilt/processor/internal:srcs_filegroup",
@@ -173,12 +175,13 @@
         "//java/dagger/hilt/processor/internal/generatesrootinput:srcs_filegroup",
         "//java/dagger/hilt/processor/internal/originatingelement:srcs_filegroup",
         "//java/dagger/hilt/processor/internal/root:srcs_filegroup",
+        "//java/dagger/hilt/processor/internal/uninstallmodules:srcs_filegroup",
     ],
 )
 
 java_library(
     name = "artifact-core-lib",
-    tags = ["maven_coordinates=com.google.dagger:hilt-core:" + POM_VERSION_ALPHA],
+    tags = ["maven_coordinates=com.google.dagger:hilt-core:" + POM_VERSION],
     exports = [
         ":define_component",
         ":entry_point",
@@ -194,7 +197,7 @@
 
 gen_maven_artifact(
     name = "artifact-core",
-    artifact_coordinates = "com.google.dagger:hilt-core:" + POM_VERSION_ALPHA,
+    artifact_coordinates = "com.google.dagger:hilt-core:" + POM_VERSION,
     artifact_name = "Hilt Core",
     artifact_target = ":artifact-core-lib",
     artifact_target_libs = [
@@ -210,6 +213,7 @@
         "//java/dagger/hilt/internal:component_manager",
         "//java/dagger/hilt/internal:generated_component",
         "//java/dagger/hilt/internal:preconditions",
+        "//java/dagger/hilt/internal:test_singleton_component",
         "//java/dagger/hilt/internal:unsafe_casts",
         "//java/dagger/hilt/internal/aliasof",
         "//java/dagger/hilt/internal/definecomponent",
diff --git a/java/dagger/hilt/EntryPoints.java b/java/dagger/hilt/EntryPoints.java
index 32e2312..3db77f9 100644
--- a/java/dagger/hilt/EntryPoints.java
+++ b/java/dagger/hilt/EntryPoints.java
@@ -18,10 +18,14 @@
 
 import dagger.hilt.internal.GeneratedComponent;
 import dagger.hilt.internal.GeneratedComponentManager;
+import dagger.hilt.internal.Preconditions;
+import dagger.hilt.internal.TestSingletonComponent;
+import java.lang.annotation.Annotation;
 import javax.annotation.Nonnull;
 
 /** Static utility methods for accessing objects through entry points. */
 public final class EntryPoints {
+  private static final String EARLY_ENTRY_POINT = "dagger.hilt.android.EarlyEntryPoint";
 
   /**
    * Returns the entry point interface given a component or component manager. Note that this
@@ -39,11 +43,20 @@
   @Nonnull
   public static <T> T get(Object component, Class<T> entryPoint) {
     if (component instanceof GeneratedComponent) {
+      if (component instanceof TestSingletonComponent) {
+        // @EarlyEntryPoint only has an effect in test environment, so we shouldn't fail in
+        // non-test cases. In addition, some of the validation requires the use of reflection, which
+        // we don't want to do in non-test cases anyway.
+        Preconditions.checkState(
+            !hasAnnotationReflection(entryPoint, EARLY_ENTRY_POINT),
+            "Interface, %s, annotated with @EarlyEntryPoint should be called with "
+                + "EarlyEntryPoints.get() rather than EntryPoints.get()",
+            entryPoint.getCanonicalName());
+      }
       // Unsafe cast. There is no way for this method to know that the correct component was used.
       return entryPoint.cast(component);
     } else if (component instanceof GeneratedComponentManager) {
-      // Unsafe cast. There is no way for this method to know that the correct component was used.
-      return entryPoint.cast(((GeneratedComponentManager<?>) component).generatedComponent());
+      return get(((GeneratedComponentManager<?>) component).generatedComponent(), entryPoint);
     } else {
       throw new IllegalStateException(
           String.format(
@@ -52,5 +65,15 @@
     }
   }
 
+  // Note: This method uses reflection but it should only be called in test environments.
+  private static boolean hasAnnotationReflection(Class<?> clazz, String annotationName) {
+    for (Annotation annotation : clazz.getAnnotations()) {
+      if (annotation.annotationType().getCanonicalName().contentEquals(annotationName)) {
+        return true;
+      }
+    }
+    return false;
+  }
+
   private EntryPoints() {}
 }
diff --git a/java/dagger/hilt/android/BUILD b/java/dagger/hilt/android/BUILD
index 47d7e67..d8833e7 100644
--- a/java/dagger/hilt/android/BUILD
+++ b/java/dagger/hilt/android/BUILD
@@ -14,7 +14,7 @@
 
 # Description:
 #   A library based on Hilt that provides standard components and automated injection for Android.
-load("//:build_defs.bzl", "POM_VERSION_ALPHA")
+load("//:build_defs.bzl", "POM_VERSION")
 load("//tools:maven.bzl", "gen_maven_artifact")
 
 package(default_visibility = ["//:src"])
@@ -41,8 +41,12 @@
         "//java/dagger/hilt/internal:component_manager",
         "//java/dagger/hilt/internal:generated_entry_point",
         "//java/dagger/hilt/internal:preconditions",
+        "@maven//:androidx_activity_activity",
         "@maven//:androidx_annotation_annotation",
         "@maven//:androidx_fragment_fragment",
+        "@maven//:androidx_lifecycle_lifecycle_common",
+        "@maven//:androidx_lifecycle_lifecycle_viewmodel",
+        "@maven//:androidx_lifecycle_lifecycle_viewmodel_savedstate",
     ],
     deps = [
         ":package_info",
@@ -59,18 +63,28 @@
         "//java/dagger/hilt/processor/internal/root:plugin",
     ],
     exports = [
+        ":activity_retained_lifecycle",
         "//:dagger_with_compiler",
         "//java/dagger/hilt:install_in",
         "//java/dagger/hilt/android/components",
+        "//java/dagger/hilt/android/internal/builders",
         "//java/dagger/hilt/android/internal/managers",
         "//java/dagger/hilt/android/internal/managers:component_supplier",
         "//java/dagger/hilt/android/internal/modules",
+        "//java/dagger/hilt/android/scopes",
         "//java/dagger/hilt/codegen:originating_element",
         "//java/dagger/hilt/internal:component_manager",
         "//java/dagger/hilt/internal:generated_component",
         "//java/dagger/hilt/internal:generated_entry_point",
+        "//java/dagger/hilt/internal/aggregatedroot",
+        "//java/dagger/hilt/internal/processedrootsentinel",
         "//java/dagger/hilt/migration:disable_install_in_check",
+        "@maven//:androidx_activity_activity",
         "@maven//:androidx_annotation_annotation",
+        "@maven//:androidx_fragment_fragment",
+        "@maven//:androidx_lifecycle_lifecycle_common",
+        "@maven//:androidx_lifecycle_lifecycle_viewmodel",
+        "@maven//:androidx_lifecycle_lifecycle_viewmodel_savedstate",
     ],
     deps = [
         ":package_info",
@@ -85,7 +99,11 @@
         ":package_info",
         "//java/dagger/hilt:entry_point",
         "@google_bazel_common//third_party/java/jsr305_annotations",
+        "@maven//:androidx_activity_activity",
         "@maven//:androidx_fragment_fragment",
+        "@maven//:androidx_lifecycle_lifecycle_common",
+        "@maven//:androidx_lifecycle_lifecycle_viewmodel",
+        "@maven//:androidx_lifecycle_lifecycle_viewmodel_savedstate",
     ],
 )
 
@@ -99,16 +117,27 @@
 )
 
 android_library(
-    name = "artifact-lib",
-    tags = ["maven_coordinates=com.google.dagger:hilt-android:" + POM_VERSION_ALPHA],
+    name = "early_entry_point",
+    srcs = [
+        "EarlyEntryPoint.java",
+        "EarlyEntryPoints.java",
+    ],
+    exported_plugins = [
+        "//java/dagger/hilt/processor/internal/aggregateddeps:plugin",
+        "//java/dagger/hilt/processor/internal/earlyentrypoint:processor",
+    ],
+    proguard_specs = ["proguard-rules.pro"],
     exports = [
-        ":android_entry_point",
-        ":entry_point_accessors",
-        ":hilt_android_app",
+        "//java/dagger/hilt/android/internal/earlyentrypoint",
+    ],
+    deps = [
         ":package_info",
-        "//java/dagger/hilt:artifact-core-lib",
-        "//java/dagger/hilt/android/migration:optional_inject",
-        "//java/dagger/lint:lint-android-artifact-lib",
+        "//:dagger_with_compiler",
+        "//java/dagger/hilt:entry_point",
+        "//java/dagger/hilt/internal:component_manager",
+        "//java/dagger/hilt/internal:preconditions",
+        "//java/dagger/hilt/internal:test_singleton_component_manager",
+        "@google_bazel_common//third_party/java/jsr305_annotations",
     ],
 )
 
@@ -120,9 +149,24 @@
     ],
 )
 
+android_library(
+    name = "artifact-lib",
+    tags = ["maven_coordinates=com.google.dagger:hilt-android:" + POM_VERSION],
+    exports = [
+        ":android_entry_point",
+        ":early_entry_point",
+        ":entry_point_accessors",
+        ":hilt_android_app",
+        ":package_info",
+        "//java/dagger/hilt:artifact-core-lib",
+        "//java/dagger/hilt/android/migration:optional_inject",
+        "//java/dagger/lint:lint-android-artifact-lib",
+    ],
+)
+
 gen_maven_artifact(
     name = "artifact",
-    artifact_coordinates = "com.google.dagger:hilt-android:" + POM_VERSION_ALPHA,
+    artifact_coordinates = "com.google.dagger:hilt-android:" + POM_VERSION,
     artifact_name = "Hilt Android",
     artifact_target = ":artifact-lib",
     artifact_target_libs = [
@@ -130,12 +174,13 @@
         "//java/dagger/hilt/android:activity_retained_lifecycle",
         "//java/dagger/hilt/android:android_entry_point",
         "//java/dagger/hilt/android:hilt_android_app",
+        "//java/dagger/hilt/android:early_entry_point",
         "//java/dagger/hilt/android:package_info",
         "//java/dagger/hilt/android/components",
-        "//java/dagger/hilt/android/components:view_model_component",
         "//java/dagger/hilt/android/components:package_info",
         "//java/dagger/hilt/android/internal",
         "//java/dagger/hilt/android/internal/builders",
+        "//java/dagger/hilt/android/internal/earlyentrypoint",
         "//java/dagger/hilt/android/internal/lifecycle",
         "//java/dagger/hilt/android/internal/managers",
         "//java/dagger/hilt/android/internal/managers:component_supplier",
@@ -147,16 +192,18 @@
         "//java/dagger/hilt/android/qualifiers",
         "//java/dagger/hilt/android/qualifiers:package_info",
         "//java/dagger/hilt/android/scopes",
-        "//java/dagger/hilt/android/scopes:activity_retained_scoped",
-        "//java/dagger/hilt/android/scopes:view_model_scoped",
         "//java/dagger/hilt/android/scopes:package_info",
         "//java/dagger/hilt/internal:component_entry_point",
         "//java/dagger/hilt/internal:generated_entry_point",
+        "//java/dagger/hilt/internal:test_singleton_component_manager",
+        "//java/dagger/hilt/internal/aggregatedroot:aggregatedroot",
+        "//java/dagger/hilt/internal/processedrootsentinel:processedrootsentinel",
     ],
     artifact_target_maven_deps = [
         "androidx.activity:activity",
         "androidx.annotation:annotation",
         "androidx.fragment:fragment",
+        "androidx.lifecycle:lifecycle-common",
         "androidx.lifecycle:lifecycle-viewmodel",
         "androidx.lifecycle:lifecycle-viewmodel-savedstate",
         "androidx.savedstate:savedstate",
@@ -183,7 +230,10 @@
     manifest = "AndroidManifest.xml",
     packaging = "aar",
     proguard_specs = [
+        "//java/dagger/hilt:proguard-rules.pro",
+        ":proguard-rules.pro",
         "//java/dagger/hilt/android/lifecycle:proguard-rules.pro",
+        "//java/dagger/hilt/internal:proguard-rules.pro",
     ],
 )
 
diff --git a/java/dagger/hilt/android/EarlyEntryPoint.java b/java/dagger/hilt/android/EarlyEntryPoint.java
new file mode 100644
index 0000000..2a9845e
--- /dev/null
+++ b/java/dagger/hilt/android/EarlyEntryPoint.java
@@ -0,0 +1,60 @@
+/*
+ * Copyright (C) 2021 The Dagger Authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package dagger.hilt.android;
+
+import static java.lang.annotation.RetentionPolicy.RUNTIME;
+
+import dagger.internal.Beta;
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Retention;
+import java.lang.annotation.Target;
+
+/**
+ * An escape hatch for when a Hilt entry point usage needs to be called before the singleton
+ * component is available in a Hilt test.
+ *
+ * <p>Warning: Please see documentation for more details:
+ * https://dagger.dev/hilt/early-entry-point
+ *
+ * <p>Usage:
+ *
+ * <p>To enable an existing entry point to be called early in a Hilt test, replace its
+ * {@link dagger.hilt.EntryPoint} annotation with {@link EarlyEntryPoint}. (Note that,
+ * {@link EarlyEntryPoint} is only allowed on entry points installed in the
+ * {@link dagger.hilt.components.SingletonComponent}).
+ *
+ * <pre><code>
+ * @EarlyEntryPoint  // <- This replaces @EntryPoint
+ * @InstallIn(SingletonComponent.class)
+ * interface FooEntryPoint {
+ *   Foo getFoo();
+ * }
+ * </code></pre>
+ *
+ * <p>Then, replace any of the corresponding usages of {@link dagger.hilt.EntryPoints} with
+ * {@link EarlyEntryPoints}, as shown below:
+ *
+ * <pre><code>
+ * // EarlyEntryPoints.get() must be used with entry points annotated with @EarlyEntryPoint
+ * // This entry point can now be called at any point during a test, e.g. in Application.onCreate().
+ * Foo foo = EarlyEntryPoints.get(appContext, FooEntryPoint.class).getFoo();
+ * </code></pre>
+ */
+@Beta
+@Retention(RUNTIME) // Needs to be runtime for checks in EntryPoints and EarlyEntryPoints.
+@Target(ElementType.TYPE)
+public @interface EarlyEntryPoint {}
diff --git a/java/dagger/hilt/android/EarlyEntryPoints.java b/java/dagger/hilt/android/EarlyEntryPoints.java
new file mode 100644
index 0000000..e714019
--- /dev/null
+++ b/java/dagger/hilt/android/EarlyEntryPoints.java
@@ -0,0 +1,80 @@
+/*
+ * Copyright (C) 2021 The Dagger Authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package dagger.hilt.android;
+
+import android.content.Context;
+import dagger.hilt.EntryPoints;
+import dagger.hilt.internal.GeneratedComponentManagerHolder;
+import dagger.hilt.internal.Preconditions;
+import dagger.hilt.internal.TestSingletonComponentManager;
+import dagger.internal.Beta;
+import java.lang.annotation.Annotation;
+import javax.annotation.Nonnull;
+
+/** Static utility methods for accessing entry points annotated with {@link EarlyEntryPoint}. */
+@Beta
+public final class EarlyEntryPoints {
+
+  /**
+   * Returns the early entry point interface given a component manager holder. Note that this
+   * performs an unsafe cast and so callers should be sure that the given component/component
+   * manager matches the early entry point interface that is given.
+   *
+   * @param applicationContext The application context.
+   * @param entryPoint The interface marked with {@link EarlyEntryPoint}. The {@link
+   *     dagger.hilt.InstallIn} annotation on this entry point should match the component argument
+   *     above.
+   */
+  // Note that the input is not statically declared to be a Component or ComponentManager to make
+  // this method easier to use, since most code will use this with an Application or Context type.
+  @Nonnull
+  public static <T> T get(Context applicationContext, Class<T> entryPoint) {
+    applicationContext = applicationContext.getApplicationContext();
+    Preconditions.checkState(
+        applicationContext instanceof GeneratedComponentManagerHolder,
+        "Expected application context to implement GeneratedComponentManagerHolder. "
+            + "Check that you're passing in an application context that uses Hilt.");
+    Object componentManager =
+        ((GeneratedComponentManagerHolder) applicationContext).componentManager();
+    if (componentManager instanceof TestSingletonComponentManager) {
+      Preconditions.checkState(
+          hasAnnotationReflection(entryPoint, EarlyEntryPoint.class),
+          "%s should be called with EntryPoints.get() rather than EarlyEntryPoints.get()",
+          entryPoint.getCanonicalName());
+      Object earlyComponent =
+          ((TestSingletonComponentManager) componentManager).earlySingletonComponent();
+      return entryPoint.cast(earlyComponent);
+    }
+
+    // @EarlyEntryPoint only has an effect in test environment, so if this is not a test we
+    // delegate to EntryPoints.
+    return EntryPoints.get(applicationContext, entryPoint);
+  }
+
+  // Note: This method uses reflection but it should only be called in test environments.
+  private static boolean hasAnnotationReflection(
+      Class<?> clazz, Class<? extends Annotation> annotationClazz) {
+    for (Annotation annotation : clazz.getAnnotations()) {
+      if (annotation.annotationType().equals(annotationClazz)) {
+        return true;
+      }
+    }
+    return false;
+  }
+
+  private EarlyEntryPoints() {}
+}
diff --git a/java/dagger/hilt/android/components/BUILD b/java/dagger/hilt/android/components/BUILD
index 3a307d1..655ccd1 100644
--- a/java/dagger/hilt/android/components/BUILD
+++ b/java/dagger/hilt/android/components/BUILD
@@ -25,6 +25,7 @@
         "FragmentComponent.java",
         "ServiceComponent.java",
         "ViewComponent.java",
+        "ViewModelComponent.java",
         "ViewWithFragmentComponent.java",
     ],
     exports = [
@@ -34,20 +35,7 @@
         ":package_info",
         "//java/dagger/hilt:define_component",
         "//java/dagger/hilt/android/scopes",
-        "//java/dagger/hilt/android/scopes:activity_retained_scoped",
         "//java/dagger/hilt/components",
-        "@google_bazel_common//third_party/java/jsr330_inject",
-    ],
-)
-
-android_library(
-    name = "view_model_component",
-    srcs = ["ViewModelComponent.java"],
-    deps = [
-        ":components",
-        ":package_info",
-        "//java/dagger/hilt:define_component",
-        "//java/dagger/hilt/android/scopes:view_model_scoped",
     ],
 )
 
diff --git a/java/dagger/hilt/android/internal/builders/BUILD b/java/dagger/hilt/android/internal/builders/BUILD
index 0e282be..9693f79 100644
--- a/java/dagger/hilt/android/internal/builders/BUILD
+++ b/java/dagger/hilt/android/internal/builders/BUILD
@@ -24,9 +24,10 @@
         "//:dagger_with_compiler",
         "//java/dagger/hilt:define_component",
         "//java/dagger/hilt/android/components",
-        "//java/dagger/hilt/android/components:view_model_component",
         "@maven//:androidx_activity_activity",
         "@maven//:androidx_fragment_fragment",
+        "@maven//:androidx_lifecycle_lifecycle_common",
+        "@maven//:androidx_lifecycle_lifecycle_viewmodel",
         "@maven//:androidx_lifecycle_lifecycle_viewmodel_savedstate",
     ],
 )
diff --git a/java/dagger/hilt/android/internal/earlyentrypoint/AggregatedEarlyEntryPoint.java b/java/dagger/hilt/android/internal/earlyentrypoint/AggregatedEarlyEntryPoint.java
new file mode 100644
index 0000000..124f831
--- /dev/null
+++ b/java/dagger/hilt/android/internal/earlyentrypoint/AggregatedEarlyEntryPoint.java
@@ -0,0 +1,29 @@
+/*
+ * Copyright (C) 2021 The Dagger Authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package dagger.hilt.android.internal.earlyentrypoint;
+
+import static java.lang.annotation.RetentionPolicy.CLASS;
+
+import java.lang.annotation.Retention;
+
+/** Holds aggregated data about {@link dagger.hilt.android.EarlyEntryPoint} elements. */
+@Retention(CLASS)
+public @interface AggregatedEarlyEntryPoint {
+
+  /** Returns the entry point annotated with {@link dagger.hilt.android.EarlyEntryPoint}. */
+  String earlyEntryPoint();
+}
diff --git a/java/dagger/hilt/android/internal/earlyentrypoint/BUILD b/java/dagger/hilt/android/internal/earlyentrypoint/BUILD
new file mode 100644
index 0000000..ab1478a
--- /dev/null
+++ b/java/dagger/hilt/android/internal/earlyentrypoint/BUILD
@@ -0,0 +1,23 @@
+# Copyright (C) 2021 The Dagger Authors.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+# Description:
+#   A processor that aggregates metadata about Hilt @EarlyEntryPoint annotations
+
+package(default_visibility = ["//:src"])
+
+java_library(
+    name = "earlyentrypoint",
+    srcs = ["AggregatedEarlyEntryPoint.java"],
+)
diff --git a/java/dagger/hilt/android/internal/lifecycle/BUILD b/java/dagger/hilt/android/internal/lifecycle/BUILD
index b3ecfc2..17812c9 100644
--- a/java/dagger/hilt/android/internal/lifecycle/BUILD
+++ b/java/dagger/hilt/android/internal/lifecycle/BUILD
@@ -25,12 +25,12 @@
         "//java/dagger/hilt:entry_point",
         "//java/dagger/hilt:install_in",
         "//java/dagger/hilt/android/components",
-        "//java/dagger/hilt/android/components:view_model_component",
         "//java/dagger/hilt/android/internal/builders",
         "//java/dagger/hilt/android/qualifiers",
         "@maven//:androidx_activity_activity",
         "@maven//:androidx_annotation_annotation",
         "@maven//:androidx_fragment_fragment",
+        "@maven//:androidx_lifecycle_lifecycle_common",
         "@maven//:androidx_lifecycle_lifecycle_viewmodel",
         "@maven//:androidx_lifecycle_lifecycle_viewmodel_savedstate",
         "@maven//:androidx_savedstate_savedstate",
diff --git a/java/dagger/hilt/android/internal/lifecycle/DefaultActivityViewModelFactory.java b/java/dagger/hilt/android/internal/lifecycle/DefaultActivityViewModelFactory.java
deleted file mode 100644
index becce8c..0000000
--- a/java/dagger/hilt/android/internal/lifecycle/DefaultActivityViewModelFactory.java
+++ /dev/null
@@ -1,29 +0,0 @@
-/*
- * Copyright (C) 2020 The Dagger Authors.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package dagger.hilt.android.internal.lifecycle;
-
-import java.lang.annotation.ElementType;
-import java.lang.annotation.Retention;
-import java.lang.annotation.RetentionPolicy;
-import java.lang.annotation.Target;
-import javax.inject.Qualifier;
-
-/** Qualifier for the default view model factory used by @AndroidEntryPoint annotated activities. */
-@Qualifier
-@Retention(RetentionPolicy.CLASS)
-@Target({ElementType.TYPE, ElementType.METHOD, ElementType.PARAMETER})
-public @interface DefaultActivityViewModelFactory {}
diff --git a/java/dagger/hilt/android/internal/lifecycle/DefaultViewModelFactories.java b/java/dagger/hilt/android/internal/lifecycle/DefaultViewModelFactories.java
index 427822d..448847c 100644
--- a/java/dagger/hilt/android/internal/lifecycle/DefaultViewModelFactories.java
+++ b/java/dagger/hilt/android/internal/lifecycle/DefaultViewModelFactories.java
@@ -50,10 +50,11 @@
    *
    * <p>Do not use except in Hilt generated code!
    */
-  public static ViewModelProvider.Factory getActivityFactory(ComponentActivity activity) {
+  public static ViewModelProvider.Factory getActivityFactory(ComponentActivity activity,
+      ViewModelProvider.Factory delegateFactory) {
     return EntryPoints.get(activity, ActivityEntryPoint.class)
         .getHiltInternalFactoryFactory()
-        .fromActivity(activity);
+        .fromActivity(activity, delegateFactory);
   }
 
   /**
@@ -61,10 +62,11 @@
    *
    * <p>Do not use except in Hilt generated code!
    */
-  public static ViewModelProvider.Factory getFragmentFactory(Fragment fragment) {
+  public static ViewModelProvider.Factory getFragmentFactory(
+      Fragment fragment, ViewModelProvider.Factory delegateFactory) {
     return EntryPoints.get(fragment, FragmentEntryPoint.class)
         .getHiltInternalFactoryFactory()
-        .fromFragment(fragment);
+        .fromFragment(fragment, delegateFactory);
   }
 
   /** Internal factory for the Hilt ViewModel Factory. */
@@ -73,33 +75,28 @@
     private final Application application;
     private final Set<String> keySet;
     private final ViewModelComponentBuilder viewModelComponentBuilder;
-    @Nullable private final ViewModelProvider.Factory defaultActivityFactory;
-    @Nullable private final ViewModelProvider.Factory defaultFragmentFactory;
 
     @Inject
     InternalFactoryFactory(
             Application application,
         @HiltViewModelMap.KeySet Set<String> keySet,
-        ViewModelComponentBuilder viewModelComponentBuilder,
-        // These default factory bindings are temporary for the transition of deprecating
-        // the Hilt ViewModel extension for the built-in support
-        @DefaultActivityViewModelFactory Set<ViewModelProvider.Factory> defaultActivityFactorySet,
-        @DefaultFragmentViewModelFactory Set<ViewModelProvider.Factory> defaultFragmentFactorySet) {
+        ViewModelComponentBuilder viewModelComponentBuilder) {
       this.application = application;
       this.keySet = keySet;
       this.viewModelComponentBuilder = viewModelComponentBuilder;
-      this.defaultActivityFactory = getFactoryFromSet(defaultActivityFactorySet);
-      this.defaultFragmentFactory = getFactoryFromSet(defaultFragmentFactorySet);
     }
 
-    ViewModelProvider.Factory fromActivity(ComponentActivity activity) {
-      return getHiltViewModelFactory(activity,
+    ViewModelProvider.Factory fromActivity(
+        ComponentActivity activity, ViewModelProvider.Factory delegateFactory) {
+      return getHiltViewModelFactory(
+          activity,
           activity.getIntent() != null ? activity.getIntent().getExtras() : null,
-          defaultActivityFactory);
+          delegateFactory);
     }
 
-    ViewModelProvider.Factory fromFragment(Fragment fragment) {
-      return getHiltViewModelFactory(fragment, fragment.getArguments(), defaultFragmentFactory);
+    ViewModelProvider.Factory fromFragment(
+        Fragment fragment, ViewModelProvider.Factory delegateFactory) {
+      return getHiltViewModelFactory(fragment, fragment.getArguments(), delegateFactory);
     }
 
     private ViewModelProvider.Factory getHiltViewModelFactory(
@@ -112,24 +109,6 @@
       return new HiltViewModelFactory(
           owner, defaultArgs, keySet, delegate, viewModelComponentBuilder);
     }
-
-    @Nullable
-    private static ViewModelProvider.Factory getFactoryFromSet(Set<ViewModelProvider.Factory> set) {
-      // A multibinding set is used instead of BindsOptionalOf because Optional is not available in
-      // Android until API 24 and we don't want to have Guava as a transitive dependency.
-      if (set.isEmpty()) {
-        return null;
-      }
-      if (set.size() > 1) {
-        throw new IllegalStateException(
-            "At most one default view model factory is expected. Found " + set);
-      }
-      ViewModelProvider.Factory factory = set.iterator().next();
-      if (factory == null) {
-        throw new IllegalStateException("Default view model factory must not be null.");
-      }
-      return factory;
-    }
   }
 
   /** The activity module to declare the optional factories. */
@@ -139,14 +118,6 @@
     @Multibinds
     @HiltViewModelMap.KeySet
     abstract Set<String> viewModelKeys();
-
-    @Multibinds
-    @DefaultActivityViewModelFactory
-    Set<ViewModelProvider.Factory> defaultActivityViewModelFactory();
-
-    @Multibinds
-    @DefaultFragmentViewModelFactory
-    Set<ViewModelProvider.Factory> defaultFragmentViewModelFactory();
   }
 
   /** The activity entry point to retrieve the factory. */
diff --git a/java/dagger/hilt/android/internal/lifecycle/HiltViewModelFactory.java b/java/dagger/hilt/android/internal/lifecycle/HiltViewModelFactory.java
index 3b0d3bc..4438941 100644
--- a/java/dagger/hilt/android/internal/lifecycle/HiltViewModelFactory.java
+++ b/java/dagger/hilt/android/internal/lifecycle/HiltViewModelFactory.java
@@ -16,6 +16,7 @@
 
 package dagger.hilt.android.internal.lifecycle;
 
+import android.app.Activity;
 import androidx.lifecycle.AbstractSavedStateViewModelFactory;
 import androidx.lifecycle.SavedStateHandle;
 import androidx.lifecycle.ViewModel;
@@ -28,6 +29,7 @@
 import dagger.hilt.EntryPoint;
 import dagger.hilt.EntryPoints;
 import dagger.hilt.InstallIn;
+import dagger.hilt.android.components.ActivityComponent;
 import dagger.hilt.android.components.ViewModelComponent;
 import dagger.hilt.android.internal.builders.ViewModelComponentBuilder;
 import dagger.multibindings.Multibinds;
@@ -109,4 +111,28 @@
       return delegateFactory.create(modelClass);
     }
   }
+
+  @EntryPoint
+  @InstallIn(ActivityComponent.class)
+  interface ActivityCreatorEntryPoint {
+    @HiltViewModelMap.KeySet
+    Set<String> getViewModelKeys();
+    ViewModelComponentBuilder getViewModelComponentBuilder();
+  }
+
+  public static ViewModelProvider.Factory createInternal(
+      @NonNull Activity activity,
+      @NonNull SavedStateRegistryOwner owner,
+      @Nullable Bundle defaultArgs,
+      @NonNull ViewModelProvider.Factory delegateFactory) {
+    ActivityCreatorEntryPoint entryPoint =
+        EntryPoints.get(activity, ActivityCreatorEntryPoint.class);
+    return new HiltViewModelFactory(
+        owner,
+        defaultArgs,
+        entryPoint.getViewModelKeys(),
+        delegateFactory,
+        entryPoint.getViewModelComponentBuilder()
+    );
+  }
 }
diff --git a/java/dagger/hilt/android/internal/managers/ActivityRetainedComponentManager.java b/java/dagger/hilt/android/internal/managers/ActivityRetainedComponentManager.java
index 2d4a72b..0af3dcd 100644
--- a/java/dagger/hilt/android/internal/managers/ActivityRetainedComponentManager.java
+++ b/java/dagger/hilt/android/internal/managers/ActivityRetainedComponentManager.java
@@ -18,6 +18,8 @@
 
 import androidx.lifecycle.ViewModel;
 import androidx.lifecycle.ViewModelProvider;
+import androidx.lifecycle.ViewModelStoreOwner;
+import android.content.Context;
 import androidx.annotation.NonNull;
 import androidx.annotation.Nullable;
 import androidx.activity.ComponentActivity;
@@ -82,23 +84,26 @@
   private final Object componentLock = new Object();
 
   ActivityRetainedComponentManager(ComponentActivity activity) {
-    this.viewModelProvider =
-        new ViewModelProvider(
-            activity,
-            new ViewModelProvider.Factory() {
-              @NonNull
-              @Override
-              @SuppressWarnings("unchecked")
-              public <T extends ViewModel> T create(@NonNull Class<T> aClass) {
-                ActivityRetainedComponent component =
-                    EntryPoints.get(
-                            activity.getApplication(),
-                            ActivityRetainedComponentBuilderEntryPoint.class)
-                        .retainedComponentBuilder()
-                        .build();
-                return (T) new ActivityRetainedComponentViewModel(component);
-              }
-            });
+    this.viewModelProvider = getViewModelProvider(activity, activity.getApplication());
+  }
+
+  private ViewModelProvider getViewModelProvider(
+      ViewModelStoreOwner owner, Context applicationContext) {
+    return new ViewModelProvider(
+        owner,
+        new ViewModelProvider.Factory() {
+          @NonNull
+          @Override
+          @SuppressWarnings("unchecked")
+          public <T extends ViewModel> T create(@NonNull Class<T> aClass) {
+            ActivityRetainedComponent component =
+                EntryPoints.get(
+                        applicationContext, ActivityRetainedComponentBuilderEntryPoint.class)
+                    .retainedComponentBuilder()
+                    .build();
+            return (T) new ActivityRetainedComponentViewModel(component);
+          }
+        });
   }
 
   @Override
diff --git a/java/dagger/hilt/android/internal/managers/BUILD b/java/dagger/hilt/android/internal/managers/BUILD
index 3bc8df1..7661310 100644
--- a/java/dagger/hilt/android/internal/managers/BUILD
+++ b/java/dagger/hilt/android/internal/managers/BUILD
@@ -40,17 +40,17 @@
         "//java/dagger/hilt:install_in",
         "//java/dagger/hilt/android:activity_retained_lifecycle",
         "//java/dagger/hilt/android/components",
-        "//java/dagger/hilt/android/components:view_model_component",
         "//java/dagger/hilt/android/internal",
         "//java/dagger/hilt/android/internal/builders",
-        "//java/dagger/hilt/android/scopes:activity_retained_scoped",
-        "//java/dagger/hilt/android/scopes:view_model_scoped",
+        "//java/dagger/hilt/android/scopes",
         "//java/dagger/hilt/internal:component_manager",
         "//java/dagger/hilt/internal:preconditions",
         "@maven//:androidx_activity_activity",
         "@maven//:androidx_annotation_annotation",
         "@maven//:androidx_fragment_fragment",
+        "@maven//:androidx_lifecycle_lifecycle_common",
         "@maven//:androidx_lifecycle_lifecycle_viewmodel",
+        "@maven//:androidx_lifecycle_lifecycle_viewmodel_savedstate",
     ],
 )
 
diff --git a/java/dagger/hilt/android/internal/managers/ViewComponentManager.java b/java/dagger/hilt/android/internal/managers/ViewComponentManager.java
index cb2ece0..7861495 100644
--- a/java/dagger/hilt/android/internal/managers/ViewComponentManager.java
+++ b/java/dagger/hilt/android/internal/managers/ViewComponentManager.java
@@ -16,6 +16,9 @@
 
 package dagger.hilt.android.internal.managers;
 
+import androidx.lifecycle.Lifecycle;
+import androidx.lifecycle.LifecycleEventObserver;
+import androidx.lifecycle.LifecycleOwner;
 import android.content.Context;
 import android.content.ContextWrapper;
 import androidx.fragment.app.Fragment;
@@ -104,7 +107,7 @@
       if (context instanceof FragmentContextWrapper) {
 
         FragmentContextWrapper fragmentContextWrapper = (FragmentContextWrapper) context;
-        return (GeneratedComponentManager<?>) fragmentContextWrapper.fragment;
+        return (GeneratedComponentManager<?>) fragmentContextWrapper.getFragment();
       } else if (allowMissing) {
         // We didn't find anything, so return null if we're not supposed to fail.
         // The rest of the logic is just about getting a good error message.
@@ -165,22 +168,41 @@
    *
    * <p>A wrapper class to expose the {@link Fragment} to the views they're inflating.
    */
-  // This is only non-final for the account override
   public static final class FragmentContextWrapper extends ContextWrapper {
+    private Fragment fragment;
     private LayoutInflater baseInflater;
     private LayoutInflater inflater;
-    public final Fragment fragment;
+    private final LifecycleEventObserver fragmentLifecycleObserver =
+        new LifecycleEventObserver() {
+          @Override
+          public void onStateChanged(LifecycleOwner source, Lifecycle.Event event) {
+            if (event == Lifecycle.Event.ON_DESTROY) {
+              // Prevent the fragment from leaking if the view outlives the fragment.
+              // See https://github.com/google/dagger/issues/2070
+              FragmentContextWrapper.this.fragment = null;
+              FragmentContextWrapper.this.baseInflater = null;
+              FragmentContextWrapper.this.inflater = null;
+            }
+          }
+        };
 
-    public FragmentContextWrapper(Context base, Fragment fragment) {
+    FragmentContextWrapper(Context base, Fragment fragment) {
       super(Preconditions.checkNotNull(base));
       this.baseInflater = null;
       this.fragment = Preconditions.checkNotNull(fragment);
+      this.fragment.getLifecycle().addObserver(fragmentLifecycleObserver);
     }
 
-    public FragmentContextWrapper(LayoutInflater baseInflater, Fragment fragment) {
+    FragmentContextWrapper(LayoutInflater baseInflater, Fragment fragment) {
       super(Preconditions.checkNotNull(Preconditions.checkNotNull(baseInflater).getContext()));
       this.baseInflater = baseInflater;
       this.fragment = Preconditions.checkNotNull(fragment);
+      this.fragment.getLifecycle().addObserver(fragmentLifecycleObserver);
+    }
+
+    Fragment getFragment() {
+      Preconditions.checkNotNull(fragment, "The fragment has already been destroyed.");
+      return fragment;
     }
 
     @Override
diff --git a/java/dagger/hilt/android/internal/modules/BUILD b/java/dagger/hilt/android/internal/modules/BUILD
index a36e237..c1b639d 100644
--- a/java/dagger/hilt/android/internal/modules/BUILD
+++ b/java/dagger/hilt/android/internal/modules/BUILD
@@ -28,6 +28,9 @@
         "@maven//:androidx_activity_activity",
         "@maven//:androidx_annotation_annotation",
         "@maven//:androidx_fragment_fragment",
+        "@maven//:androidx_lifecycle_lifecycle_common",
+        "@maven//:androidx_lifecycle_lifecycle_viewmodel",
+        "@maven//:androidx_lifecycle_lifecycle_viewmodel_savedstate",
     ],
 )
 
diff --git a/java/dagger/hilt/android/internal/testing/BUILD b/java/dagger/hilt/android/internal/testing/BUILD
index b0bc361..98a273d 100644
--- a/java/dagger/hilt/android/internal/testing/BUILD
+++ b/java/dagger/hilt/android/internal/testing/BUILD
@@ -21,7 +21,6 @@
     name = "test_injector",
     testonly = 1,
     srcs = [
-        "TestApplicationInjector.java",
         "TestInjector.java",
     ],
 )
@@ -37,15 +36,23 @@
 )
 
 android_library(
+    name = "early_test_singleton_component_creator",
+    testonly = 1,
+    srcs = ["EarlySingletonComponentCreator.java"],
+)
+
+android_library(
     name = "test_application_component_manager",
     testonly = 1,
     srcs = ["TestApplicationComponentManager.java"],
     deps = [
+        ":early_test_singleton_component_creator",
         ":test_component_data",
         ":test_injector",
         "//java/dagger/hilt/android/testing:on_component_ready_runner",
         "//java/dagger/hilt/internal:component_manager",
         "//java/dagger/hilt/internal:preconditions",
+        "//java/dagger/hilt/internal:test_singleton_component_manager",
         "@maven//:junit_junit",
     ],
 )
@@ -67,6 +74,9 @@
     name = "test_application_component_manager_holder",
     testonly = 1,
     srcs = ["TestApplicationComponentManagerHolder.java"],
+    deps = [
+        "//java/dagger/hilt/internal:component_manager",
+    ],
 )
 
 android_library(
diff --git a/java/dagger/hilt/android/internal/testing/EarlySingletonComponentCreator.java b/java/dagger/hilt/android/internal/testing/EarlySingletonComponentCreator.java
new file mode 100644
index 0000000..8e61e4a
--- /dev/null
+++ b/java/dagger/hilt/android/internal/testing/EarlySingletonComponentCreator.java
@@ -0,0 +1,48 @@
+/*
+ * Copyright (C) 2021 The Dagger Authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package dagger.hilt.android.internal.testing;
+
+import java.lang.reflect.InvocationTargetException;
+
+/** Creates a test's early component. */
+public abstract class EarlySingletonComponentCreator {
+  private static final String EARLY_SINGLETON_COMPONENT_CREATOR_IMPL =
+      "dagger.hilt.android.internal.testing.EarlySingletonComponentCreatorImpl";
+
+  static Object createComponent() {
+    try {
+      return Class.forName(EARLY_SINGLETON_COMPONENT_CREATOR_IMPL)
+          .asSubclass(EarlySingletonComponentCreator.class)
+          .getDeclaredConstructor()
+          .newInstance()
+          .create();
+    } catch (ClassNotFoundException
+        | NoSuchMethodException
+        | IllegalAccessException
+        | InstantiationException
+        | InvocationTargetException e) {
+      throw new RuntimeException(
+          "The EarlyComponent was requested but does not exist. Check that you have annotated "
+              + "your test class with @HiltAndroidTest and that the processor is running over your "
+              + "test.",
+          e);
+    }
+  }
+
+  /** Creates the early test component. */
+  abstract Object create();
+}
diff --git a/java/dagger/hilt/android/internal/testing/TestApplicationComponentManager.java b/java/dagger/hilt/android/internal/testing/TestApplicationComponentManager.java
index 1878870..c205117 100644
--- a/java/dagger/hilt/android/internal/testing/TestApplicationComponentManager.java
+++ b/java/dagger/hilt/android/internal/testing/TestApplicationComponentManager.java
@@ -21,7 +21,7 @@
 import dagger.hilt.android.testing.OnComponentReadyRunner.OnComponentReadyRunnerHolder;
 import dagger.hilt.internal.GeneratedComponentManager;
 import dagger.hilt.internal.Preconditions;
-import java.lang.reflect.InvocationTargetException;
+import dagger.hilt.internal.TestSingletonComponentManager;
 import java.util.HashSet;
 import java.util.Map;
 import java.util.Set;
@@ -35,17 +35,18 @@
  * <p>A manager for the creation of components that live in the test Application.
  */
 public final class TestApplicationComponentManager
-    implements GeneratedComponentManager<Object>, OnComponentReadyRunnerHolder {
+    implements TestSingletonComponentManager, OnComponentReadyRunnerHolder {
 
-  // This is a generated class that we always generate in a known location.
-  private static final String TEST_COMPONENT_DATA_SUPPLIER_IMPL =
-      "dagger.hilt.android.internal.testing.TestComponentDataSupplierImpl";
+  private final Object earlyComponentLock = new Object();
+  private volatile Object earlyComponent = null;
+
+  private final Object testComponentDataLock = new Object();
+  private volatile TestComponentData testComponentData;
 
   private final Application application;
-  private final Map<Class<?>, TestComponentData> testComponentDataSupplier;
-
   private final AtomicReference<Object> component = new AtomicReference<>();
   private final AtomicReference<Description> hasHiltTestRule = new AtomicReference<>();
+  // TODO(bcorso): Consider using a lock here rather than ConcurrentHashMap to avoid b/37042460.
   private final Map<Class<?>, Object> registeredModules = new ConcurrentHashMap<>();
   private final AtomicReference<Boolean> autoAddModuleEnabled = new AtomicReference<>();
   private final AtomicReference<DelayedComponentState> delayedComponentState =
@@ -75,24 +76,18 @@
 
   public TestApplicationComponentManager(Application application) {
     this.application = application;
-    try {
-      this.testComponentDataSupplier =
-          Class.forName(TEST_COMPONENT_DATA_SUPPLIER_IMPL)
-              .asSubclass(TestComponentDataSupplier.class)
-              .getDeclaredConstructor()
-              .newInstance()
-              .get();
-    } catch (ClassNotFoundException
-        | NoSuchMethodException
-        | IllegalAccessException
-        | InstantiationException
-        | InvocationTargetException e) {
-      throw new RuntimeException(
-          "Hilt classes generated from @HiltAndroidTest are missing. Check that you have annotated "
-              + "your test class with @HiltAndroidTest and that the processor is running over your "
-              + "test",
-          e);
+  }
+
+  @Override
+  public Object earlySingletonComponent() {
+    if (earlyComponent == null) {
+      synchronized (earlyComponentLock) {
+        if (earlyComponent == null) {
+          earlyComponent = EarlySingletonComponentCreator.createComponent();
+        }
+      }
     }
+    return earlyComponent;
   }
 
   @Override
@@ -149,6 +144,9 @@
         testInstance == null,
         "The Hilt BindValue instance cannot be set before Hilt's test rule has run.");
     Preconditions.checkState(
+        testComponentData == null,
+        "The testComponentData instance cannot be set before Hilt's test rule has run.");
+    Preconditions.checkState(
         registeredModules.isEmpty(),
         "The Hilt registered modules cannot be set before Hilt's test rule has run.");
     Preconditions.checkState(
@@ -171,6 +169,7 @@
     component.set(null);
     hasHiltTestRule.set(null);
     testInstance = null;
+    testComponentData = null;
     registeredModules.clear();
     autoAddModuleEnabled.set(null);
     delayedComponentState.set(DelayedComponentState.NOT_DELAYED);
@@ -308,7 +307,14 @@
   }
 
   private TestComponentData testComponentData() {
-    return testComponentDataSupplier.get(testClass());
+    if (testComponentData == null) {
+      synchronized (testComponentDataLock) {
+        if (testComponentData == null) {
+          testComponentData = TestComponentDataSupplier.get(testClass());
+        }
+      }
+    }
+    return testComponentData;
   }
 
   private Class<?> testClass() {
diff --git a/java/dagger/hilt/android/internal/testing/TestApplicationComponentManagerHolder.java b/java/dagger/hilt/android/internal/testing/TestApplicationComponentManagerHolder.java
index a8695c4..4be5829 100644
--- a/java/dagger/hilt/android/internal/testing/TestApplicationComponentManagerHolder.java
+++ b/java/dagger/hilt/android/internal/testing/TestApplicationComponentManagerHolder.java
@@ -16,9 +16,8 @@
 
 package dagger.hilt.android.internal.testing;
 
+import dagger.hilt.internal.GeneratedComponentManagerHolder;
+
 /** For use by Hilt internally only! Returns the component manager. */
-public interface TestApplicationComponentManagerHolder {
-  // Returns {@link Object} so that we do not expose {@code TestApplicationComponentManager} to
-  // clients. Framework code should explicitly cast to {@code TestApplicationComponentManager}.
-  Object componentManager();
-}
+// TODO(bcorso):Consider deleting this interface and just using GeneratedComponentManagerHolder
+public interface TestApplicationComponentManagerHolder extends GeneratedComponentManagerHolder {}
diff --git a/java/dagger/hilt/android/internal/testing/TestComponentDataSupplier.java b/java/dagger/hilt/android/internal/testing/TestComponentDataSupplier.java
index e39073f..b6f8b0b 100644
--- a/java/dagger/hilt/android/internal/testing/TestComponentDataSupplier.java
+++ b/java/dagger/hilt/android/internal/testing/TestComponentDataSupplier.java
@@ -16,11 +16,50 @@
 
 package dagger.hilt.android.internal.testing;
 
-import java.util.Map;
+import java.lang.reflect.InvocationTargetException;
 
-/** Stores the {@link TestComponentData} for all Hilt test classes. */
+/** Stores the {@link TestComponentData} for a Hilt test class. */
 public abstract class TestComponentDataSupplier {
 
-  /** Returns a map of {@link TestComponentData} keyed by test class. */
-  protected abstract Map<Class<?>, TestComponentData> get();
+  /** Returns a {@link TestComponentData}. */
+  protected abstract TestComponentData get();
+
+  static TestComponentData get(Class<?> testClass) {
+    String generatedClassName = getEnclosedClassName(testClass) + "_TestComponentDataSupplier";
+    try {
+      return Class.forName(generatedClassName)
+          .asSubclass(TestComponentDataSupplier.class)
+          .getDeclaredConstructor()
+          .newInstance()
+          .get();
+    } catch (ClassNotFoundException
+        | NoSuchMethodException
+        | IllegalAccessException
+        | InstantiationException
+        | InvocationTargetException e) {
+      throw new RuntimeException(
+          String.format(
+              "Hilt test, %s, is missing generated file: %s. Check that the test class is "
+                  + " annotated with @HiltAndroidTest and that the processor is running over your"
+                  + " test.",
+              testClass.getSimpleName(),
+              generatedClassName),
+          e);
+    }
+  }
+
+  private static String getEnclosedClassName(Class<?> testClass) {
+    StringBuilder sb = new StringBuilder();
+    Class<?> currClass = testClass;
+    while (currClass != null) {
+      Class<?> enclosingClass = currClass.getEnclosingClass();
+      if (enclosingClass != null) {
+        sb.insert(0, "_" + currClass.getSimpleName());
+      } else {
+        sb.insert(0, currClass.getCanonicalName());
+      }
+      currClass = enclosingClass;
+    }
+    return sb.toString();
+  }
 }
diff --git a/java/dagger/hilt/android/internal/testing/root/BUILD b/java/dagger/hilt/android/internal/testing/root/BUILD
new file mode 100644
index 0000000..96b161e
--- /dev/null
+++ b/java/dagger/hilt/android/internal/testing/root/BUILD
@@ -0,0 +1,23 @@
+# Copyright (C) 2021 The Dagger Authors.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+# Description:
+#   Internal Hilt android testing root
+
+package(default_visibility = ["//:src"])
+
+android_library(
+    name = "default",
+    srcs = ["Default.java"],
+)
diff --git a/java/dagger/hilt/android/internal/testing/TestApplicationInjector.java b/java/dagger/hilt/android/internal/testing/root/Default.java
similarity index 64%
copy from java/dagger/hilt/android/internal/testing/TestApplicationInjector.java
copy to java/dagger/hilt/android/internal/testing/root/Default.java
index c7ff5c9..0aa1b16 100644
--- a/java/dagger/hilt/android/internal/testing/TestApplicationInjector.java
+++ b/java/dagger/hilt/android/internal/testing/root/Default.java
@@ -1,5 +1,5 @@
 /*
- * Copyright (C) 2020 The Dagger Authors.
+ * Copyright (C) 2021 The Dagger Authors.
  *
  * Licensed under the Apache License, Version 2.0 (the "License");
  * you may not use this file except in compliance with the License.
@@ -14,11 +14,12 @@
  * limitations under the License.
  */
 
-package dagger.hilt.android.internal.testing;
+package dagger.hilt.android.internal.testing.root;
 
 /**
- * Interface to expose a method for members injection for use in tests.
+ * This is internal code. Do not depend on this class directly.
+ *
+ * <p>This is a "default" root (used only in tests) that generates a component without any test
+ * specific dependencies.
  */
-public interface TestApplicationInjector<T> {
-  void injectApp(T t);
-}
+final class Default {}
diff --git a/java/dagger/hilt/android/internal/uninstallmodules/AggregatedUninstallModules.java b/java/dagger/hilt/android/internal/uninstallmodules/AggregatedUninstallModules.java
new file mode 100644
index 0000000..0ab1f31
--- /dev/null
+++ b/java/dagger/hilt/android/internal/uninstallmodules/AggregatedUninstallModules.java
@@ -0,0 +1,33 @@
+/*
+ * Copyright (C) 2021 The Dagger Authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+
+package dagger.hilt.android.internal.uninstallmodules;
+
+import static java.lang.annotation.RetentionPolicy.CLASS;
+
+import java.lang.annotation.Retention;
+
+/** Holds aggregated data about {@link dagger.hilt.android.testing.UninstallModules} elements. */
+@Retention(CLASS)
+public @interface AggregatedUninstallModules {
+
+  /** Returns the test annotated with {@link dagger.hilt.android.testing.UninstallModules}. */
+  String test();
+
+  /** Returns the list of modules to uninstall. */
+  String[] uninstallModules();
+}
diff --git a/java/dagger/hilt/android/internal/uninstallmodules/BUILD b/java/dagger/hilt/android/internal/uninstallmodules/BUILD
new file mode 100644
index 0000000..583964b
--- /dev/null
+++ b/java/dagger/hilt/android/internal/uninstallmodules/BUILD
@@ -0,0 +1,24 @@
+# Copyright (C) 2021 The Dagger Authors.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+# Description:
+#   A processor that aggregates metadata about Hilt @UninstallModules annotations
+
+package(default_visibility = ["//:src"])
+
+java_library(
+    name = "uninstallmodules",
+    testonly = 1,
+    srcs = ["AggregatedUninstallModules.java"],
+)
diff --git a/java/dagger/hilt/android/lifecycle/BUILD b/java/dagger/hilt/android/lifecycle/BUILD
index fd80510..25b3e5e 100644
--- a/java/dagger/hilt/android/lifecycle/BUILD
+++ b/java/dagger/hilt/android/lifecycle/BUILD
@@ -25,7 +25,7 @@
     ],
     proguard_specs = ["proguard-rules.pro"],
     exports = [
-        "//java/dagger/hilt/android/components:view_model_component",
+        "//java/dagger/hilt/android/components",
         "//java/dagger/hilt/android/internal/lifecycle",
     ],
     deps = [
diff --git a/java/dagger/hilt/android/migration/BUILD b/java/dagger/hilt/android/migration/BUILD
index ade47c3..a42f0ac 100644
--- a/java/dagger/hilt/android/migration/BUILD
+++ b/java/dagger/hilt/android/migration/BUILD
@@ -33,6 +33,9 @@
         "@maven//:androidx_activity_activity",
         "@maven//:androidx_annotation_annotation",
         "@maven//:androidx_fragment_fragment",
+        "@maven//:androidx_lifecycle_lifecycle_common",
+        "@maven//:androidx_lifecycle_lifecycle_viewmodel",
+        "@maven//:androidx_lifecycle_lifecycle_viewmodel_savedstate",
     ],
 )
 
diff --git a/java/dagger/hilt/android/plugin/build.gradle b/java/dagger/hilt/android/plugin/build.gradle
index 274ecbe..480c6f0 100644
--- a/java/dagger/hilt/android/plugin/build.gradle
+++ b/java/dagger/hilt/android/plugin/build.gradle
@@ -17,6 +17,7 @@
 buildscript {
   repositories {
     google()
+    mavenCentral()
     jcenter()
   }
 }
@@ -29,6 +30,7 @@
 
 repositories {
   google()
+  mavenCentral()
   jcenter()
 }
 
diff --git a/java/dagger/hilt/android/plugin/src/main/kotlin/dagger/hilt/android/plugin/AndroidEntryPointClassTransformer.kt b/java/dagger/hilt/android/plugin/src/main/kotlin/dagger/hilt/android/plugin/AndroidEntryPointClassTransformer.kt
index e066f48..3bbf1e1 100644
--- a/java/dagger/hilt/android/plugin/src/main/kotlin/dagger/hilt/android/plugin/AndroidEntryPointClassTransformer.kt
+++ b/java/dagger/hilt/android/plugin/src/main/kotlin/dagger/hilt/android/plugin/AndroidEntryPointClassTransformer.kt
@@ -81,6 +81,7 @@
         if (entry.isClassFile()) {
           val clazz = classPool.makeClass(input, false)
           transformed = transformClassToOutput(clazz) || transformed
+          clazz.detach()
         }
         entry = input.nextEntry
       }
@@ -99,7 +100,9 @@
       "Invalid file, '$inputFile' is not a class."
     }
     val clazz = inputFile.inputStream().use { classPool.makeClass(it, false) }
-    return transformClassToOutput(clazz)
+    val transformed = transformClassToOutput(clazz)
+    clazz.detach()
+    return transformed
   }
 
   private fun transformClassToOutput(clazz: CtClass): Boolean {
diff --git a/java/dagger/hilt/android/plugin/src/main/kotlin/dagger/hilt/android/plugin/HiltGradlePlugin.kt b/java/dagger/hilt/android/plugin/src/main/kotlin/dagger/hilt/android/plugin/HiltGradlePlugin.kt
index e26edb5..a82826e 100644
--- a/java/dagger/hilt/android/plugin/src/main/kotlin/dagger/hilt/android/plugin/HiltGradlePlugin.kt
+++ b/java/dagger/hilt/android/plugin/src/main/kotlin/dagger/hilt/android/plugin/HiltGradlePlugin.kt
@@ -32,10 +32,12 @@
 import dagger.hilt.android.plugin.util.CopyTransform
 import dagger.hilt.android.plugin.util.SimpleAGPVersion
 import java.io.File
+import javax.inject.Inject
 import org.gradle.api.Plugin
 import org.gradle.api.Project
 import org.gradle.api.artifacts.component.ProjectComponentIdentifier
 import org.gradle.api.attributes.Attribute
+import org.gradle.api.provider.ProviderFactory
 
 /**
  * A Gradle plugin that checks if the project is an Android project and if so, registers a
@@ -45,7 +47,9 @@
  * classes annotated with `@AndroidEntryPoint` since the registered transform by this plugin will
  * update the superclass.
  */
-class HiltGradlePlugin : Plugin<Project> {
+class HiltGradlePlugin @Inject constructor(
+  val providers: ProviderFactory
+) : Plugin<Project> {
   override fun apply(project: Project) {
     var configured = false
     project.plugins.withType(AndroidBasePlugin::class.java) {
@@ -127,6 +131,7 @@
     }
   }
 
+  @Suppress("UnstableApiUsage")
   private fun configureVariantCompileClasspath(
     project: Project,
     hiltExtension: HiltExtension,
@@ -160,7 +165,7 @@
           "android.injected.build.model.only.versioned", // Sent by AS 2.4+
           "android.injected.build.model.feature.full.dependencies", // Sent by AS 2.4+
           "android.injected.build.model.v2", // Sent by AS 4.2+
-        ).any { project.properties.containsKey(it) }
+        ).any { providers.gradleProperty(it).forUseAtConfigurationTime().isPresent }
     ) {
       // Do not configure compile classpath when AndroidStudio is building the model (syncing)
       // otherwise it will cause a freeze.
diff --git a/java/dagger/hilt/android/plugin/src/main/kotlin/dagger/hilt/android/plugin/util/SimpleAGPVersion.kt b/java/dagger/hilt/android/plugin/src/main/kotlin/dagger/hilt/android/plugin/util/SimpleAGPVersion.kt
index 1580431..339c83e 100644
--- a/java/dagger/hilt/android/plugin/src/main/kotlin/dagger/hilt/android/plugin/util/SimpleAGPVersion.kt
+++ b/java/dagger/hilt/android/plugin/src/main/kotlin/dagger/hilt/android/plugin/util/SimpleAGPVersion.kt
@@ -1,7 +1,5 @@
 package dagger.hilt.android.plugin.util
 
-import com.android.Version
-
 /**
  * Simple Android Gradle Plugin version class since there is no public API one. b/175816217
  */
@@ -20,7 +18,18 @@
 
   companion object {
 
-    val ANDROID_GRADLE_PLUGIN_VERSION by lazy { parse(Version.ANDROID_GRADLE_PLUGIN_VERSION) }
+    // TODO(danysantiago): Migrate to AndroidPluginVersion once it is available (b/175816217)
+    val ANDROID_GRADLE_PLUGIN_VERSION by lazy {
+      val clazz =
+        findClass("com.android.Version")
+          ?: findClass("com.android.builder.model.Version")
+      if (clazz != null) {
+        return@lazy parse(clazz.getField("ANDROID_GRADLE_PLUGIN_VERSION").get(null) as String)
+      }
+      error(
+        "Unable to obtain AGP version. It is likely that the AGP version being used is too old."
+      )
+    }
 
     fun parse(version: String?) =
       tryParse(version) ?: error("Unable to parse AGP version: $version")
@@ -37,5 +46,11 @@
 
       return SimpleAGPVersion(parts[0].toInt(), parts[1].toInt())
     }
+
+    private fun findClass(fqName: String) = try {
+      Class.forName(fqName)
+    } catch (ex: ClassNotFoundException) {
+      null
+    }
   }
 }
diff --git a/java/dagger/hilt/android/plugin/src/test/kotlin/IncrementalProcessorTest.kt b/java/dagger/hilt/android/plugin/src/test/kotlin/IncrementalProcessorTest.kt
index a003ab5..cab50aa 100644
--- a/java/dagger/hilt/android/plugin/src/test/kotlin/IncrementalProcessorTest.kt
+++ b/java/dagger/hilt/android/plugin/src/test/kotlin/IncrementalProcessorTest.kt
@@ -157,28 +157,22 @@
     genActivityInjector2 = File(projectRoot, "$GEN_SRC_DIR/simple/Activity2_GeneratedInjector.java")
     genAppInjectorDeps = File(
       projectRoot,
-      "$GEN_SRC_DIR/hilt_aggregated_deps/simple_SimpleApp_GeneratedInjectorModuleDeps.java"
+      "$GEN_SRC_DIR/hilt_aggregated_deps/_simple_SimpleApp_GeneratedInjector.java"
     )
     genActivityInjectorDeps1 = File(
       projectRoot,
-      "$GEN_SRC_DIR/hilt_aggregated_deps/simple_Activity1_GeneratedInjectorModuleDeps.java"
+      "$GEN_SRC_DIR/hilt_aggregated_deps/_simple_Activity1_GeneratedInjector.java"
     )
     genActivityInjectorDeps2 = File(
       projectRoot,
-      "$GEN_SRC_DIR/hilt_aggregated_deps/simple_Activity2_GeneratedInjectorModuleDeps.java"
+      "$GEN_SRC_DIR/hilt_aggregated_deps/_simple_Activity2_GeneratedInjector.java"
     )
     genModuleDeps1 = File(
       projectRoot,
-      "$GEN_SRC_DIR/hilt_aggregated_deps/simple_Module1ModuleDeps.java"
+      "$GEN_SRC_DIR/hilt_aggregated_deps/_simple_Module1.java"
     )
-    genModuleDeps2 = File(
-      projectRoot,
-      "$GEN_SRC_DIR/hilt_aggregated_deps/simple_Module2ModuleDeps.java"
-    )
-    genHiltComponents = File(
-      projectRoot,
-      "$GEN_SRC_DIR/simple/SimpleApp_HiltComponents.java"
-    )
+    genModuleDeps2 = File(projectRoot, "$GEN_SRC_DIR/hilt_aggregated_deps/_simple_Module2.java")
+    genHiltComponents = File(projectRoot, "$GEN_SRC_DIR/simple/SimpleApp_HiltComponents.java")
     genDaggerHiltApplicationComponent = File(
       projectRoot,
       "$GEN_SRC_DIR/simple/DaggerSimpleApp_HiltComponents_SingletonC.java"
@@ -203,24 +197,18 @@
     )
     classGenAppInjectorDeps = File(
       projectRoot,
-      "$CLASS_DIR/hilt_aggregated_deps/simple_SimpleApp_GeneratedInjectorModuleDeps.class"
+      "$CLASS_DIR/hilt_aggregated_deps/_simple_SimpleApp_GeneratedInjector.class"
     )
     classGenActivityInjectorDeps1 = File(
       projectRoot,
-      "$CLASS_DIR/hilt_aggregated_deps/simple_Activity1_GeneratedInjectorModuleDeps.class"
+      "$CLASS_DIR/hilt_aggregated_deps/_simple_Activity1_GeneratedInjector.class"
     )
     classGenActivityInjectorDeps2 = File(
       projectRoot,
-      "$CLASS_DIR/hilt_aggregated_deps/simple_Activity2_GeneratedInjectorModuleDeps.class"
+      "$CLASS_DIR/hilt_aggregated_deps/_simple_Activity2_GeneratedInjector.class"
     )
-    classGenModuleDeps1 = File(
-      projectRoot,
-      "$CLASS_DIR/hilt_aggregated_deps/simple_Module1ModuleDeps.class"
-    )
-    classGenModuleDeps2 = File(
-      projectRoot,
-      "$CLASS_DIR/hilt_aggregated_deps/simple_Module2ModuleDeps.class"
-    )
+    classGenModuleDeps1 = File(projectRoot, "$CLASS_DIR/hilt_aggregated_deps/_simple_Module1.class")
+    classGenModuleDeps2 = File(projectRoot, "$CLASS_DIR/hilt_aggregated_deps/_simple_Module2.class")
     classGenHiltComponents = File(
       projectRoot,
       "$CLASS_DIR/simple/SimpleApp_HiltComponents.class"
diff --git a/java/dagger/hilt/android/processor/BUILD b/java/dagger/hilt/android/processor/BUILD
index e116c44..ccf2103 100644
--- a/java/dagger/hilt/android/processor/BUILD
+++ b/java/dagger/hilt/android/processor/BUILD
@@ -15,14 +15,14 @@
 # Description:
 #   Hilt android processors.
 
-load("//:build_defs.bzl", "POM_VERSION_ALPHA")
+load("//:build_defs.bzl", "POM_VERSION")
 load("//tools:maven.bzl", "gen_maven_artifact")
 
 package(default_visibility = ["//:src"])
 
 java_library(
     name = "artifact-lib",
-    tags = ["maven_coordinates=com.google.dagger:hilt-android-compiler:" + POM_VERSION_ALPHA],
+    tags = ["maven_coordinates=com.google.dagger:hilt-android-compiler:" + POM_VERSION],
     visibility = ["//visibility:private"],
     exports = [
         "//java/dagger/hilt/processor:artifact-lib-shared",
@@ -31,23 +31,23 @@
 
 gen_maven_artifact(
     name = "artifact",
-    artifact_coordinates = "com.google.dagger:hilt-android-compiler:" + POM_VERSION_ALPHA,
+    artifact_coordinates = "com.google.dagger:hilt-android-compiler:" + POM_VERSION,
     artifact_name = "Hilt Android Processor",
     artifact_target = ":artifact-lib",
     artifact_target_libs = [
         "//java/dagger/hilt/android/processor/internal:android_classnames",
         "//java/dagger/hilt/android/processor/internal:utils",
         "//java/dagger/hilt/android/processor/internal/androidentrypoint:android_generators",
-        "//java/dagger/hilt/android/processor/internal/androidentrypoint:compiler_options",
         "//java/dagger/hilt/android/processor/internal/androidentrypoint:metadata",
         "//java/dagger/hilt/android/processor/internal/androidentrypoint:processor_lib",
         "//java/dagger/hilt/android/processor/internal/bindvalue:bind_value_processor_lib",
         "//java/dagger/hilt/android/processor/internal/customtestapplication:processor_lib",
         "//java/dagger/hilt/android/processor/internal/viewmodel:validation_plugin_lib",
-        "//java/dagger/hilt/android/processor/internal/uninstallmodules:processor_lib",
         "//java/dagger/hilt/android/processor/internal/viewmodel:processor_lib",
+        "//java/dagger/hilt/processor/internal:aggregated_elements",
         "//java/dagger/hilt/processor/internal:base_processor",
         "//java/dagger/hilt/processor/internal:classnames",
+        "//java/dagger/hilt/processor/internal:compiler_options",
         "//java/dagger/hilt/processor/internal:component_descriptor",
         "//java/dagger/hilt/processor/internal:component_names",
         "//java/dagger/hilt/processor/internal:components",
@@ -55,17 +55,22 @@
         "//java/dagger/hilt/processor/internal:processor_errors",
         "//java/dagger/hilt/processor/internal:processors",
         "//java/dagger/hilt/processor/internal/aggregateddeps:component_dependencies",
+        "//java/dagger/hilt/processor/internal/aggregateddeps:pkg_private_metadata",
         "//java/dagger/hilt/processor/internal/aggregateddeps:processor_lib",
         "//java/dagger/hilt/processor/internal/aliasof:alias_ofs",
         "//java/dagger/hilt/processor/internal/aliasof:processor_lib",
         "//java/dagger/hilt/processor/internal/definecomponent:define_components",
         "//java/dagger/hilt/processor/internal/definecomponent:processor_lib",
+        "//java/dagger/hilt/processor/internal/earlyentrypoint:aggregated_early_entry_point_metadata",
+        "//java/dagger/hilt/processor/internal/earlyentrypoint:processor_lib",
         "//java/dagger/hilt/processor/internal/generatesrootinput:generates_root_inputs",
         "//java/dagger/hilt/processor/internal/generatesrootinput:processor_lib",
         "//java/dagger/hilt/processor/internal/originatingelement:processor_lib",
         "//java/dagger/hilt/processor/internal/root:processor_lib",
         "//java/dagger/hilt/processor/internal/root:root_metadata",
         "//java/dagger/hilt/processor/internal/root:root_type",
+        "//java/dagger/hilt/processor/internal/uninstallmodules:processor_lib",
+        "//java/dagger/hilt/processor/internal/uninstallmodules:aggregated_uninstall_modules_metadata",
     ],
     artifact_target_maven_deps = [
         "com.google.auto:auto-common",
diff --git a/java/dagger/hilt/android/processor/internal/AndroidClassNames.java b/java/dagger/hilt/android/processor/internal/AndroidClassNames.java
index 915ae51..d370920 100644
--- a/java/dagger/hilt/android/processor/internal/AndroidClassNames.java
+++ b/java/dagger/hilt/android/processor/internal/AndroidClassNames.java
@@ -113,5 +113,10 @@
   public static final ClassName SAVED_STATE_HANDLE =
       get("androidx.lifecycle", "SavedStateHandle");
 
+  public static final ClassName ON_CONTEXT_AVAILABLE_LISTENER =
+      get("androidx.activity.contextaware", "OnContextAvailableListener");
+  public static final ClassName INJECT_VIA_ON_CONTEXT_AVAILABLE_LISTENER =
+      get("dagger.hilt.android", "InjectViaOnContextAvailableListener");
+
   private AndroidClassNames() {}
 }
diff --git a/java/dagger/hilt/android/processor/internal/androidentrypoint/ActivityGenerator.java b/java/dagger/hilt/android/processor/internal/androidentrypoint/ActivityGenerator.java
index ce9ad14..86fbaa7 100644
--- a/java/dagger/hilt/android/processor/internal/androidentrypoint/ActivityGenerator.java
+++ b/java/dagger/hilt/android/processor/internal/androidentrypoint/ActivityGenerator.java
@@ -17,9 +17,9 @@
 package dagger.hilt.android.processor.internal.androidentrypoint;
 
 import com.squareup.javapoet.ClassName;
+import com.squareup.javapoet.CodeBlock;
 import com.squareup.javapoet.JavaFile;
 import com.squareup.javapoet.MethodSpec;
-import com.squareup.javapoet.ParameterSpec;
 import com.squareup.javapoet.TypeSpec;
 import com.squareup.javapoet.TypeVariableName;
 import dagger.hilt.android.processor.internal.AndroidClassNames;
@@ -30,7 +30,6 @@
 
 /** Generates an Hilt Activity class for the @AndroidEntryPoint annotated class. */
 public final class ActivityGenerator {
-
   private final ProcessingEnvironment env;
   private final AndroidEntryPointMetadata metadata;
   private final ClassName generatedClassName;
@@ -56,9 +55,11 @@
     Generators.addGeneratedBaseClassJavadoc(builder, AndroidClassNames.ANDROID_ENTRY_POINT);
     Processors.addGeneratedAnnotation(builder, env, getClass());
 
-      Generators.copyConstructors(metadata.baseElement(), builder);
-      builder.addMethod(onCreate());
-
+      Generators.copyConstructors(
+          metadata.baseElement(),
+          CodeBlock.builder().addStatement("_initHiltInternal()").build(),
+          builder);
+      builder.addMethod(init());
 
     metadata.baseElement().getTypeParameters().stream()
         .map(TypeVariableName::get)
@@ -79,29 +80,36 @@
         .writeTo(env.getFiler());
   }
 
-  // @CallSuper
-  // @Override
-  // protected void onCreate(@Nullable Bundle savedInstanceState) {
-  //   inject();
-  //   super.onCreate(savedInstanceState);
+  // private void init() {
+  //   addOnContextAvailableListener(new OnContextAvailableListener() {
+  //     @Override
+  //     public void onContextAvailable(Context context) {
+  //       inject();
+  //     }
+  //   });
   // }
-  private MethodSpec onCreate() {
-    return MethodSpec.methodBuilder("onCreate")
-        .addAnnotation(AndroidClassNames.CALL_SUPER)
-        .addAnnotation(Override.class)
-        .addModifiers(Modifier.PROTECTED)
-        .addParameter(
-            ParameterSpec.builder(AndroidClassNames.BUNDLE, "savedInstanceState")
-                .addAnnotation(AndroidClassNames.NULLABLE)
+  private MethodSpec init() {
+    return MethodSpec.methodBuilder("_initHiltInternal")
+        .addModifiers(Modifier.PRIVATE)
+        .addStatement(
+            "addOnContextAvailableListener($L)",
+            TypeSpec.anonymousClassBuilder("")
+                .addSuperinterface(AndroidClassNames.ON_CONTEXT_AVAILABLE_LISTENER)
+                .addMethod(
+                    MethodSpec.methodBuilder("onContextAvailable")
+                        .addAnnotation(Override.class)
+                        .addModifiers(Modifier.PUBLIC)
+                        .addParameter(AndroidClassNames.CONTEXT, "context")
+                        .addStatement("inject()")
+                        .build())
                 .build())
-        .addStatement("inject()")
-        .addStatement("super.onCreate(savedInstanceState)")
         .build();
   }
 
   // @Override
   // public ViewModelProvider.Factory getDefaultViewModelProviderFactory() {
-  //   return DefaultViewModelFactories.getActivityFactory(this);
+  //   return DefaultViewModelFactories.getActivityFactory(
+  //       this, super.getDefaultViewModelProviderFactory());
   // }
   private MethodSpec getDefaultViewModelProviderFactory() {
     return MethodSpec.methodBuilder("getDefaultViewModelProviderFactory")
@@ -109,7 +117,7 @@
         .addModifiers(Modifier.PUBLIC)
         .returns(AndroidClassNames.VIEW_MODEL_PROVIDER_FACTORY)
         .addStatement(
-            "return $T.getActivityFactory(this)",
+            "return $T.getActivityFactory(this, super.getDefaultViewModelProviderFactory())",
             AndroidClassNames.DEFAULT_VIEW_MODEL_FACTORIES)
         .build();
   }
diff --git a/java/dagger/hilt/android/processor/internal/androidentrypoint/AndroidEntryPointMetadata.java b/java/dagger/hilt/android/processor/internal/androidentrypoint/AndroidEntryPointMetadata.java
index e5868f8..c94f6a9 100644
--- a/java/dagger/hilt/android/processor/internal/androidentrypoint/AndroidEntryPointMetadata.java
+++ b/java/dagger/hilt/android/processor/internal/androidentrypoint/AndroidEntryPointMetadata.java
@@ -16,7 +16,7 @@
 
 package dagger.hilt.android.processor.internal.androidentrypoint;
 
-import static dagger.hilt.android.processor.internal.androidentrypoint.HiltCompilerOptions.BooleanOption.DISABLE_ANDROID_SUPERCLASS_VALIDATION;
+import static dagger.hilt.processor.internal.HiltCompilerOptions.isAndroidSuperclassValidationDisabled;
 import static dagger.internal.codegen.extension.DaggerStreams.toImmutableSet;
 
 import com.google.auto.common.MoreElements;
@@ -245,7 +245,7 @@
     final TypeElement baseElement;
     final ClassName generatedClassName;
     boolean requiresBytecodeInjection =
-        DISABLE_ANDROID_SUPERCLASS_VALIDATION.get(env)
+        isAndroidSuperclassValidationDisabled(androidEntryPointElement, env)
             && MoreTypes.isTypeOf(Void.class, androidEntryPointClassValue.asType());
     if (requiresBytecodeInjection) {
       baseElement = MoreElements.asType(env.getTypeUtils().asElement(androidEntryPointElement.getSuperclass()));
diff --git a/java/dagger/hilt/android/processor/internal/androidentrypoint/AndroidEntryPointProcessor.java b/java/dagger/hilt/android/processor/internal/androidentrypoint/AndroidEntryPointProcessor.java
index 7bb9b9a..cd3290b 100644
--- a/java/dagger/hilt/android/processor/internal/androidentrypoint/AndroidEntryPointProcessor.java
+++ b/java/dagger/hilt/android/processor/internal/androidentrypoint/AndroidEntryPointProcessor.java
@@ -44,11 +44,6 @@
   }
 
   @Override
-  public Set<String> getSupportedOptions() {
-    return HiltCompilerOptions.getProcessorOptions();
-  }
-
-  @Override
   public boolean delayErrors() {
     return true;
   }
diff --git a/java/dagger/hilt/android/processor/internal/androidentrypoint/ApplicationGenerator.java b/java/dagger/hilt/android/processor/internal/androidentrypoint/ApplicationGenerator.java
index f16e06d..8d63cdb 100644
--- a/java/dagger/hilt/android/processor/internal/androidentrypoint/ApplicationGenerator.java
+++ b/java/dagger/hilt/android/processor/internal/androidentrypoint/ApplicationGenerator.java
@@ -38,11 +38,13 @@
   private final ProcessingEnvironment env;
   private final AndroidEntryPointMetadata metadata;
   private final ClassName wrapperClassName;
+  private final ComponentNames componentNames;
 
   public ApplicationGenerator(ProcessingEnvironment env, AndroidEntryPointMetadata metadata) {
     this.env = env;
     this.metadata = metadata;
-    wrapperClassName = metadata.generatedClassName();
+    this.wrapperClassName = metadata.generatedClassName();
+    this.componentNames = ComponentNames.withoutRenaming();
   }
 
   // @Generated("ApplicationGenerator")
@@ -107,7 +109,7 @@
   // }
   private TypeSpec creatorType() {
     ClassName component =
-        ComponentNames.generatedComponent(
+        componentNames.generatedComponent(
             metadata.elementClassName(), AndroidClassNames.SINGLETON_COMPONENT);
     return TypeSpec.anonymousClassBuilder("")
         .addSuperinterface(AndroidClassNames.COMPONENT_SUPPLIER)
diff --git a/java/dagger/hilt/android/processor/internal/androidentrypoint/BUILD b/java/dagger/hilt/android/processor/internal/androidentrypoint/BUILD
index 55e9ddc..efbf9e4 100644
--- a/java/dagger/hilt/android/processor/internal/androidentrypoint/BUILD
+++ b/java/dagger/hilt/android/processor/internal/androidentrypoint/BUILD
@@ -35,11 +35,9 @@
     srcs = ["AndroidEntryPointProcessor.java"],
     deps = [
         ":android_generators",
-        ":compiler_options",
         ":metadata",
         "//java/dagger/hilt/android/processor/internal:android_classnames",
         "//java/dagger/hilt/processor/internal:base_processor",
-        "//java/dagger/hilt/processor/internal:processors",
         "//java/dagger/internal/guava:collect",
         "@google_bazel_common//third_party/java/auto:service",
         "@google_bazel_common//third_party/java/incap",
@@ -81,9 +79,9 @@
         "AndroidEntryPointMetadata.java",
     ],
     deps = [
-        ":compiler_options",
         "//java/dagger/hilt/android/processor/internal:android_classnames",
         "//java/dagger/hilt/processor/internal:classnames",
+        "//java/dagger/hilt/processor/internal:compiler_options",
         "//java/dagger/hilt/processor/internal:components",
         "//java/dagger/hilt/processor/internal:kotlin",
         "//java/dagger/hilt/processor/internal:processor_errors",
@@ -98,11 +96,6 @@
     ],
 )
 
-java_library(
-    name = "compiler_options",
-    srcs = ["HiltCompilerOptions.java"],
-)
-
 filegroup(
     name = "srcs_filegroup",
     srcs = glob(["*"]),
diff --git a/java/dagger/hilt/android/processor/internal/androidentrypoint/FragmentGenerator.java b/java/dagger/hilt/android/processor/internal/androidentrypoint/FragmentGenerator.java
index 4ef479f..81b2b61 100644
--- a/java/dagger/hilt/android/processor/internal/androidentrypoint/FragmentGenerator.java
+++ b/java/dagger/hilt/android/processor/internal/androidentrypoint/FragmentGenerator.java
@@ -202,7 +202,8 @@
 
   // @Override
   // public ViewModelProvider.Factory getDefaultViewModelProviderFactory() {
-  //   return DefaultViewModelFactories.getFragmentFactory(this);
+  //   return DefaultViewModelFactories.getFragmentFactory(
+  //       this, super.getDefaultViewModelProviderFactory());
   // }
   private MethodSpec getDefaultViewModelProviderFactory() {
     return MethodSpec.methodBuilder("getDefaultViewModelProviderFactory")
@@ -210,7 +211,7 @@
         .addModifiers(Modifier.PUBLIC)
         .returns(AndroidClassNames.VIEW_MODEL_PROVIDER_FACTORY)
         .addStatement(
-            "return $T.getFragmentFactory(this)",
+            "return $T.getFragmentFactory(this, super.getDefaultViewModelProviderFactory())",
             AndroidClassNames.DEFAULT_VIEW_MODEL_FACTORIES)
         .build();
   }
diff --git a/java/dagger/hilt/android/processor/internal/androidentrypoint/Generators.java b/java/dagger/hilt/android/processor/internal/androidentrypoint/Generators.java
index daadd03..91df537 100644
--- a/java/dagger/hilt/android/processor/internal/androidentrypoint/Generators.java
+++ b/java/dagger/hilt/android/processor/internal/androidentrypoint/Generators.java
@@ -319,6 +319,12 @@
           .endControlFlow();
     }
 
+    // Only add @Override if an ancestor extends a generated Hilt class.
+    // When using bytecode injection, this isn't always guaranteed.
+    if (metadata.overridesAndroidEntryPointClass()
+        && ancestorExtendsGeneratedHiltClass(metadata)) {
+      methodSpecBuilder.addAnnotation(Override.class);
+    }
     typeSpecBuilder.addField(injectedField(metadata));
 
     switch (metadata.androidType()) {
@@ -326,12 +332,6 @@
       case FRAGMENT:
       case VIEW:
       case SERVICE:
-        // Only add @Override if an ancestor extends a generated Hilt class.
-        // When using bytecode injection, this isn't always guaranteed.
-        if (metadata.overridesAndroidEntryPointClass()
-            && ancestorExtendsGeneratedHiltClass(metadata)) {
-          methodSpecBuilder.addAnnotation(Override.class);
-        }
         methodSpecBuilder
             .beginControlFlow("if (!injected)")
             .addStatement("injected = true")
diff --git a/java/dagger/hilt/android/processor/internal/androidentrypoint/HiltCompilerOptions.java b/java/dagger/hilt/android/processor/internal/androidentrypoint/HiltCompilerOptions.java
deleted file mode 100644
index 577410d..0000000
--- a/java/dagger/hilt/android/processor/internal/androidentrypoint/HiltCompilerOptions.java
+++ /dev/null
@@ -1,71 +0,0 @@
-/*
- * Copyright (C) 2020 The Dagger Authors.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package dagger.hilt.android.processor.internal.androidentrypoint;
-
-import java.util.Arrays;
-import java.util.Set;
-import java.util.stream.Collectors;
-import javax.annotation.processing.ProcessingEnvironment;
-
-/** Hilt annotation processor options. */
-// TODO(danysantiago): Consider consolidating with Dagger compiler options logic.
-// TODO(user): Move this class to dagger/hilt/processor/internal
-public final class HiltCompilerOptions {
-
-  /** Processor options which can have true or false values. */
-  public enum BooleanOption {
-    /**
-     * Flag that disables validating the superclass of @AndroidEntryPoint are Hilt_ generated,
-     * classes. This flag is to be used internally by the Gradle plugin, enabling the bytecode
-     * transformation to change the superclass.
-     */
-    DISABLE_ANDROID_SUPERCLASS_VALIDATION(
-        "android.internal.disableAndroidSuperclassValidation", false),
-
-    /** Flag that disables check on modules to be annotated with @InstallIn. */
-    DISABLE_MODULES_HAVE_INSTALL_IN_CHECK("disableModulesHaveInstallInCheck", false);
-
-    private final String name;
-    private final boolean defaultValue;
-
-    BooleanOption(String name, boolean defaultValue) {
-      this.name = name;
-      this.defaultValue = defaultValue;
-    }
-
-    public boolean get(ProcessingEnvironment env) {
-      String value = env.getOptions().get(getQualifiedName());
-      if (value == null) {
-        return defaultValue;
-      }
-      // TODO(danysantiago): Strictly verify input, either 'true' or 'false' and nothing else.
-      return Boolean.parseBoolean(value);
-    }
-
-    public String getQualifiedName() {
-      return "dagger.hilt." + name;
-    }
-  }
-
-  public static Set<String> getProcessorOptions() {
-    return Arrays.stream(BooleanOption.values())
-        .map(BooleanOption::getQualifiedName)
-        .collect(Collectors.toSet());
-  }
-
-  private HiltCompilerOptions() {}
-}
diff --git a/java/dagger/hilt/android/processor/internal/customtestapplication/CustomTestApplicationGenerator.java b/java/dagger/hilt/android/processor/internal/customtestapplication/CustomTestApplicationGenerator.java
index 51b7ef4..4f7f1bd 100644
--- a/java/dagger/hilt/android/processor/internal/customtestapplication/CustomTestApplicationGenerator.java
+++ b/java/dagger/hilt/android/processor/internal/customtestapplication/CustomTestApplicationGenerator.java
@@ -109,7 +109,7 @@
     return MethodSpec.methodBuilder("componentManager")
         .addAnnotation(Override.class)
         .addModifiers(Modifier.PUBLIC, Modifier.FINAL)
-        .returns(TypeName.OBJECT)
+        .returns(ParameterizedTypeName.get(ClassNames.GENERATED_COMPONENT_MANAGER, TypeName.OBJECT))
         .addStatement("return $N", COMPONENT_MANAGER)
         .build();
   }
diff --git a/java/dagger/hilt/android/processor/internal/viewmodel/ViewModelModuleGenerator.kt b/java/dagger/hilt/android/processor/internal/viewmodel/ViewModelModuleGenerator.kt
index 846f7d2..1bc2e93 100644
--- a/java/dagger/hilt/android/processor/internal/viewmodel/ViewModelModuleGenerator.kt
+++ b/java/dagger/hilt/android/processor/internal/viewmodel/ViewModelModuleGenerator.kt
@@ -93,6 +93,11 @@
     component = AndroidClassNames.VIEW_MODEL_COMPONENT
   )
     .addModifiers(Modifier.ABSTRACT)
+    .addMethod(
+      MethodSpec.constructorBuilder()
+        .addModifiers(Modifier.PRIVATE)
+        .build()
+    )
     .addMethod(getViewModelBindsMethod())
     .build()
 
diff --git a/java/dagger/hilt/android/proguard-rules.pro b/java/dagger/hilt/android/proguard-rules.pro
new file mode 100644
index 0000000..6fd3a82
--- /dev/null
+++ b/java/dagger/hilt/android/proguard-rules.pro
@@ -0,0 +1,3 @@
+# Keep for the reflective cast done in EntryPoints.
+# See b/183070411#comment4 for more info.
+-keep,allowobfuscation,allowshrinking @dagger.hilt.android.EarlyEntryPoint class *
\ No newline at end of file
diff --git a/java/dagger/hilt/android/scopes/BUILD b/java/dagger/hilt/android/scopes/BUILD
index e74ac9e..5abc27e 100644
--- a/java/dagger/hilt/android/scopes/BUILD
+++ b/java/dagger/hilt/android/scopes/BUILD
@@ -20,9 +20,11 @@
 android_library(
     name = "scopes",
     srcs = [
+        "ActivityRetainedScoped.java",
         "ActivityScoped.java",
         "FragmentScoped.java",
         "ServiceScoped.java",
+        "ViewModelScoped.java",
         "ViewScoped.java",
     ],
     deps = [
@@ -31,24 +33,6 @@
     ],
 )
 
-android_library(
-    name = "activity_retained_scoped",
-    srcs = ["ActivityRetainedScoped.java"],
-    deps = [
-        ":package_info",
-        "@google_bazel_common//third_party/java/jsr330_inject",
-    ],
-)
-
-android_library(
-    name = "view_model_scoped",
-    srcs = ["ViewModelScoped.java"],
-    deps = [
-        ":package_info",
-        "@google_bazel_common//third_party/java/jsr330_inject",
-    ],
-)
-
 java_library(
     name = "package_info",
     srcs = ["package-info.java"],
diff --git a/java/dagger/hilt/android/testing/BUILD b/java/dagger/hilt/android/testing/BUILD
index 47db287..93d4ceb 100644
--- a/java/dagger/hilt/android/testing/BUILD
+++ b/java/dagger/hilt/android/testing/BUILD
@@ -14,7 +14,7 @@
 # Description:
 #   Testing libraries for Hilt Android.
 
-load("//:build_defs.bzl", "POM_VERSION_ALPHA")
+load("//:build_defs.bzl", "POM_VERSION")
 load("//tools:maven.bzl", "gen_maven_artifact")
 
 package(default_visibility = ["//:src"])
@@ -57,14 +57,21 @@
         "//java/dagger/hilt/android/internal/builders",
         "//java/dagger/hilt/android/internal/managers",
         "//java/dagger/hilt/android/internal/modules",
+        "//java/dagger/hilt/android/internal/testing:early_test_singleton_component_creator",
         "//java/dagger/hilt/android/internal/testing:test_application_component_manager",
         "//java/dagger/hilt/android/internal/testing:test_application_component_manager_holder",
+        "//java/dagger/hilt/android/internal/testing:test_component_data",
         "//java/dagger/hilt/android/internal/testing:test_injector",
+        "//java/dagger/hilt/android/internal/testing/root:default",
         "//java/dagger/hilt/android/scopes",
         "//java/dagger/hilt/internal:component_entry_point",
         "//java/dagger/hilt/internal:component_manager",
+        "//java/dagger/hilt/internal:generated_component",
         "//java/dagger/hilt/internal:generated_entry_point",
         "//java/dagger/hilt/internal:preconditions",
+        "//java/dagger/hilt/internal:test_singleton_component",
+        "//java/dagger/hilt/internal/aggregatedroot",
+        "//java/dagger/hilt/internal/processedrootsentinel",
         "//java/dagger/hilt/migration:disable_install_in_check",
         "@maven//:androidx_annotation_annotation",
         "@maven//:androidx_multidex_multidex",
@@ -123,10 +130,14 @@
     testonly = 1,
     srcs = ["UninstallModules.java"],
     exported_plugins = [
-        "//java/dagger/hilt/android/processor/internal/uninstallmodules:processor",
+        "//java/dagger/hilt/processor/internal/uninstallmodules:processor",
+    ],
+    exports = [
+        "//java/dagger/hilt/android/internal/uninstallmodules",
     ],
     deps = [
         ":package_info",
+        "//java/dagger/hilt:generates_root_input",
     ],
 )
 
@@ -163,7 +174,7 @@
 android_library(
     name = "artifact-lib",
     testonly = 1,
-    tags = ["maven_coordinates=com.google.dagger:hilt-android-testing:" + POM_VERSION_ALPHA],
+    tags = ["maven_coordinates=com.google.dagger:hilt-android-testing:" + POM_VERSION],
     exports = [
         ":bind_value",
         ":custom_test_application",
@@ -178,15 +189,18 @@
 gen_maven_artifact(
     name = "artifact",
     testonly = 1,
-    artifact_coordinates = "com.google.dagger:hilt-android-testing:" + POM_VERSION_ALPHA,
+    artifact_coordinates = "com.google.dagger:hilt-android-testing:" + POM_VERSION,
     artifact_name = "Hilt Android Testing",
     artifact_target = ":artifact-lib",
     artifact_target_libs = [
+        "//java/dagger/hilt/android/internal/testing:early_test_singleton_component_creator",
         "//java/dagger/hilt/android/internal/testing:mark_that_rules_ran_rule",
         "//java/dagger/hilt/android/internal/testing:test_application_component_manager",
         "//java/dagger/hilt/android/internal/testing:test_application_component_manager_holder",
         "//java/dagger/hilt/android/internal/testing:test_component_data",
         "//java/dagger/hilt/android/internal/testing:test_injector",
+        "//java/dagger/hilt/android/internal/testing/root:default",
+        "//java/dagger/hilt/android/internal/uninstallmodules:uninstallmodules",
         "//java/dagger/hilt/android/testing:bind_value",
         "//java/dagger/hilt/android/testing:custom_test_application",
         "//java/dagger/hilt/android/testing:hilt_android_rule",
@@ -202,6 +216,7 @@
         "androidx.activity:activity",
         "androidx.annotation:annotation",
         "androidx.fragment:fragment",
+        "androidx.lifecycle:lifecycle-common",
         "androidx.lifecycle:lifecycle-viewmodel",
         "androidx.lifecycle:lifecycle-viewmodel-savedstate",
         "androidx.multidex:multidex",
diff --git a/java/dagger/hilt/android/testing/HiltTestApplication.java b/java/dagger/hilt/android/testing/HiltTestApplication.java
index 97eb4cb..293bfda 100644
--- a/java/dagger/hilt/android/testing/HiltTestApplication.java
+++ b/java/dagger/hilt/android/testing/HiltTestApplication.java
@@ -40,7 +40,7 @@
   }
 
   @Override
-  public final Object componentManager() {
+  public final GeneratedComponentManager<Object> componentManager() {
     return componentManager;
   }
 
diff --git a/java/dagger/hilt/android/testing/UninstallModules.java b/java/dagger/hilt/android/testing/UninstallModules.java
index 6480c10..607d76a 100644
--- a/java/dagger/hilt/android/testing/UninstallModules.java
+++ b/java/dagger/hilt/android/testing/UninstallModules.java
@@ -16,6 +16,7 @@
 
 package dagger.hilt.android.testing;
 
+import dagger.hilt.GeneratesRootInput;
 import java.lang.annotation.ElementType;
 import java.lang.annotation.Target;
 
@@ -43,6 +44,7 @@
  *   }
  * </code></pre>
  */
+@GeneratesRootInput
 @Target({ElementType.TYPE})
 public @interface UninstallModules {
 
diff --git a/java/dagger/hilt/internal/BUILD b/java/dagger/hilt/internal/BUILD
index dc245d1..ad4dbb5 100644
--- a/java/dagger/hilt/internal/BUILD
+++ b/java/dagger/hilt/internal/BUILD
@@ -18,10 +18,20 @@
 package(default_visibility = ["//:src"])
 
 java_library(
+    name = "test_singleton_component",
+    srcs = ["TestSingletonComponent.java"],
+    deps = [":generated_component"],
+)
+
+java_library(
     name = "generated_component",
-    srcs = [
-        "GeneratedComponent.java",
-    ],
+    srcs = ["GeneratedComponent.java"],
+)
+
+java_library(
+    name = "test_singleton_component_manager",
+    srcs = ["TestSingletonComponentManager.java"],
+    deps = [":component_manager"],
 )
 
 java_library(
@@ -53,12 +63,14 @@
 java_library(
     name = "component_entry_point",
     srcs = ["ComponentEntryPoint.java"],
+    proguard_specs = ["proguard-rules.pro"],
     deps = ["//java/dagger/hilt:generates_root_input"],
 )
 
 java_library(
     name = "generated_entry_point",
     srcs = ["GeneratedEntryPoint.java"],
+    proguard_specs = ["proguard-rules.pro"],
     deps = ["//java/dagger/hilt:generates_root_input"],
 )
 
diff --git a/java/dagger/hilt/android/internal/testing/TestApplicationInjector.java b/java/dagger/hilt/internal/TestSingletonComponent.java
similarity index 69%
rename from java/dagger/hilt/android/internal/testing/TestApplicationInjector.java
rename to java/dagger/hilt/internal/TestSingletonComponent.java
index c7ff5c9..730b70b 100644
--- a/java/dagger/hilt/android/internal/testing/TestApplicationInjector.java
+++ b/java/dagger/hilt/internal/TestSingletonComponent.java
@@ -1,5 +1,5 @@
 /*
- * Copyright (C) 2020 The Dagger Authors.
+ * Copyright (C) 2021 The Dagger Authors.
  *
  * Licensed under the Apache License, Version 2.0 (the "License");
  * you may not use this file except in compliance with the License.
@@ -14,11 +14,7 @@
  * limitations under the License.
  */
 
-package dagger.hilt.android.internal.testing;
+package dagger.hilt.internal;
 
-/**
- * Interface to expose a method for members injection for use in tests.
- */
-public interface TestApplicationInjector<T> {
-  void injectApp(T t);
-}
+/** A marker that the given component is a test {@code SingletonComponent}. */
+public interface TestSingletonComponent extends GeneratedComponent {}
diff --git a/java/dagger/hilt/android/internal/testing/TestApplicationInjector.java b/java/dagger/hilt/internal/TestSingletonComponentManager.java
similarity index 65%
copy from java/dagger/hilt/android/internal/testing/TestApplicationInjector.java
copy to java/dagger/hilt/internal/TestSingletonComponentManager.java
index c7ff5c9..316a008 100644
--- a/java/dagger/hilt/android/internal/testing/TestApplicationInjector.java
+++ b/java/dagger/hilt/internal/TestSingletonComponentManager.java
@@ -1,5 +1,5 @@
 /*
- * Copyright (C) 2020 The Dagger Authors.
+ * Copyright (C) 2021 The Dagger Authors.
  *
  * Licensed under the Apache License, Version 2.0 (the "License");
  * you may not use this file except in compliance with the License.
@@ -14,11 +14,9 @@
  * limitations under the License.
  */
 
-package dagger.hilt.android.internal.testing;
+package dagger.hilt.internal;
 
-/**
- * Interface to expose a method for members injection for use in tests.
- */
-public interface TestApplicationInjector<T> {
-  void injectApp(T t);
+/** A marker that the given component manager is for an {@link TestSingletonComponent}. */
+public interface TestSingletonComponentManager extends GeneratedComponentManager<Object> {
+  Object earlySingletonComponent();
 }
diff --git a/java/dagger/hilt/android/internal/lifecycle/DefaultFragmentViewModelFactory.java b/java/dagger/hilt/internal/aggregatedroot/AggregatedRoot.java
similarity index 67%
rename from java/dagger/hilt/android/internal/lifecycle/DefaultFragmentViewModelFactory.java
rename to java/dagger/hilt/internal/aggregatedroot/AggregatedRoot.java
index 9967053..b53ee72 100644
--- a/java/dagger/hilt/android/internal/lifecycle/DefaultFragmentViewModelFactory.java
+++ b/java/dagger/hilt/internal/aggregatedroot/AggregatedRoot.java
@@ -1,5 +1,5 @@
 /*
- * Copyright (C) 2020 The Dagger Authors.
+ * Copyright (C) 2021 The Dagger Authors.
  *
  * Licensed under the Apache License, Version 2.0 (the "License");
  * you may not use this file except in compliance with the License.
@@ -14,16 +14,21 @@
  * limitations under the License.
  */
 
-package dagger.hilt.android.internal.lifecycle;
+package dagger.hilt.internal.aggregatedroot;
 
 import java.lang.annotation.ElementType;
 import java.lang.annotation.Retention;
 import java.lang.annotation.RetentionPolicy;
 import java.lang.annotation.Target;
-import javax.inject.Qualifier;
 
-/** Qualifier for the default view model factory used by @AndroidEntryPoint annotated fragments. */
-@Qualifier
+/**
+ * An annotation used to aggregate {@link dagger.hilt.android.HiltAndroidApp} and {@link
+ * dagger.hilt.android.testing.HiltAndroidTest} roots.
+ */
+@Target(ElementType.TYPE)
 @Retention(RetentionPolicy.CLASS)
-@Target({ElementType.TYPE, ElementType.METHOD, ElementType.PARAMETER})
-public @interface DefaultFragmentViewModelFactory {}
+public @interface AggregatedRoot {
+  String root();
+
+  Class<?> rootAnnotation();
+}
diff --git a/java/dagger/hilt/internal/aggregatedroot/BUILD b/java/dagger/hilt/internal/aggregatedroot/BUILD
new file mode 100644
index 0000000..0a7263a
--- /dev/null
+++ b/java/dagger/hilt/internal/aggregatedroot/BUILD
@@ -0,0 +1,28 @@
+# Copyright (C) 2021 The Dagger Authors.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+# Description:
+#   The annotation for aggregating information about Hilt roots.
+
+package(default_visibility = ["//:src"])
+
+java_library(
+    name = "aggregatedroot",
+    srcs = ["AggregatedRoot.java"],
+)
+
+filegroup(
+    name = "srcs_filegroup",
+    srcs = glob(["*"]),
+)
diff --git a/java/dagger/hilt/internal/aliasof/BUILD b/java/dagger/hilt/internal/aliasof/BUILD
index 3e96ed4..13d4364 100644
--- a/java/dagger/hilt/internal/aliasof/BUILD
+++ b/java/dagger/hilt/internal/aliasof/BUILD
@@ -17,7 +17,7 @@
 
 package(default_visibility = ["//:src"])
 
-android_library(
+java_library(
     name = "aliasof",
     srcs = ["AliasOfPropagatedData.java"],
 )
diff --git a/java/dagger/hilt/internal/processedrootsentinel/BUILD b/java/dagger/hilt/internal/processedrootsentinel/BUILD
new file mode 100644
index 0000000..70b72a6
--- /dev/null
+++ b/java/dagger/hilt/internal/processedrootsentinel/BUILD
@@ -0,0 +1,28 @@
+# Copyright (C) 2021 The Dagger Authors.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+# Description:
+#   The annotation for aggregating information about processed Hilt roots.
+
+package(default_visibility = ["//:src"])
+
+java_library(
+    name = "processedrootsentinel",
+    srcs = ["ProcessedRootSentinel.java"],
+)
+
+filegroup(
+    name = "srcs_filegroup",
+    srcs = glob(["*"]),
+)
diff --git a/java/dagger/hilt/android/internal/lifecycle/DefaultFragmentViewModelFactory.java b/java/dagger/hilt/internal/processedrootsentinel/ProcessedRootSentinel.java
similarity index 67%
copy from java/dagger/hilt/android/internal/lifecycle/DefaultFragmentViewModelFactory.java
copy to java/dagger/hilt/internal/processedrootsentinel/ProcessedRootSentinel.java
index 9967053..e5e0b1b 100644
--- a/java/dagger/hilt/android/internal/lifecycle/DefaultFragmentViewModelFactory.java
+++ b/java/dagger/hilt/internal/processedrootsentinel/ProcessedRootSentinel.java
@@ -1,5 +1,5 @@
 /*
- * Copyright (C) 2020 The Dagger Authors.
+ * Copyright (C) 2021 The Dagger Authors.
  *
  * Licensed under the Apache License, Version 2.0 (the "License");
  * you may not use this file except in compliance with the License.
@@ -14,16 +14,17 @@
  * limitations under the License.
  */
 
-package dagger.hilt.android.internal.lifecycle;
+package dagger.hilt.internal.processedrootsentinel;
 
 import java.lang.annotation.ElementType;
 import java.lang.annotation.Retention;
 import java.lang.annotation.RetentionPolicy;
 import java.lang.annotation.Target;
-import javax.inject.Qualifier;
 
-/** Qualifier for the default view model factory used by @AndroidEntryPoint annotated fragments. */
-@Qualifier
+/** An annotation used to aggregate sentinels for processed roots. */
+@Target(ElementType.TYPE)
 @Retention(RetentionPolicy.CLASS)
-@Target({ElementType.TYPE, ElementType.METHOD, ElementType.PARAMETER})
-public @interface DefaultFragmentViewModelFactory {}
+public @interface ProcessedRootSentinel {
+  /** Returns the set of roots processed in a previous build. */
+  String[] roots();
+}
diff --git a/java/dagger/hilt/internal/proguard-rules.pro b/java/dagger/hilt/internal/proguard-rules.pro
new file mode 100644
index 0000000..2607ba1
--- /dev/null
+++ b/java/dagger/hilt/internal/proguard-rules.pro
@@ -0,0 +1,4 @@
+# Keep for the reflective cast done in EntryPoints.
+# See b/183070411#comment4 for more info.
+-keep,allowobfuscation,allowshrinking @dagger.hilt.internal.ComponentEntryPoint class *
+-keep,allowobfuscation,allowshrinking @dagger.hilt.internal.GeneratedEntryPoint class *
\ No newline at end of file
diff --git a/java/dagger/hilt/processor/BUILD b/java/dagger/hilt/processor/BUILD
index 87adcf3..1f75d0a 100644
--- a/java/dagger/hilt/processor/BUILD
+++ b/java/dagger/hilt/processor/BUILD
@@ -15,7 +15,7 @@
 # Description:
 #   Hilt android processors.
 
-load("//:build_defs.bzl", "POM_VERSION_ALPHA")
+load("//:build_defs.bzl", "POM_VERSION")
 load("//tools:maven.bzl", "gen_maven_artifact")
 
 package(default_visibility = ["//:src"])
@@ -27,22 +27,23 @@
         "//java/dagger/hilt/android/processor/internal/androidentrypoint:processor_lib",
         "//java/dagger/hilt/android/processor/internal/bindvalue:bind_value_processor_lib",
         "//java/dagger/hilt/android/processor/internal/customtestapplication:processor_lib",
-        "//java/dagger/hilt/android/processor/internal/uninstallmodules:processor_lib",
         "//java/dagger/hilt/android/processor/internal/viewmodel:processor_lib",
         "//java/dagger/hilt/android/processor/internal/viewmodel:validation_plugin_lib",
         "//java/dagger/hilt/processor/internal/aggregateddeps:processor_lib",
         "//java/dagger/hilt/processor/internal/aliasof:processor_lib",
         "//java/dagger/hilt/processor/internal/definecomponent:processor_lib",
+        "//java/dagger/hilt/processor/internal/earlyentrypoint:processor_lib",
         "//java/dagger/hilt/processor/internal/generatesrootinput:processor_lib",
         "//java/dagger/hilt/processor/internal/originatingelement:processor_lib",
         "//java/dagger/hilt/processor/internal/root:processor_lib",
+        "//java/dagger/hilt/processor/internal/uninstallmodules:processor_lib",
         "//java/dagger/internal/codegen:processor",
     ],
 )
 
 java_library(
     name = "artifact-lib",
-    tags = ["maven_coordinates=com.google.dagger:hilt-compiler:" + POM_VERSION_ALPHA],
+    tags = ["maven_coordinates=com.google.dagger:hilt-compiler:" + POM_VERSION],
     visibility = ["//visibility:private"],
     exports = [
         ":artifact-lib-shared",
@@ -51,23 +52,23 @@
 
 gen_maven_artifact(
     name = "artifact",
-    artifact_coordinates = "com.google.dagger:hilt-compiler:" + POM_VERSION_ALPHA,
+    artifact_coordinates = "com.google.dagger:hilt-compiler:" + POM_VERSION,
     artifact_name = "Hilt Processor",
     artifact_target = ":artifact-lib",
     artifact_target_libs = [
         "//java/dagger/hilt/android/processor/internal:android_classnames",
         "//java/dagger/hilt/android/processor/internal:utils",
         "//java/dagger/hilt/android/processor/internal/androidentrypoint:android_generators",
-        "//java/dagger/hilt/android/processor/internal/androidentrypoint:compiler_options",
         "//java/dagger/hilt/android/processor/internal/androidentrypoint:metadata",
         "//java/dagger/hilt/android/processor/internal/androidentrypoint:processor_lib",
         "//java/dagger/hilt/android/processor/internal/bindvalue:bind_value_processor_lib",
         "//java/dagger/hilt/android/processor/internal/customtestapplication:processor_lib",
         "//java/dagger/hilt/android/processor/internal/viewmodel:validation_plugin_lib",
-        "//java/dagger/hilt/android/processor/internal/uninstallmodules:processor_lib",
         "//java/dagger/hilt/android/processor/internal/viewmodel:processor_lib",
+        "//java/dagger/hilt/processor/internal:aggregated_elements",
         "//java/dagger/hilt/processor/internal:base_processor",
         "//java/dagger/hilt/processor/internal:classnames",
+        "//java/dagger/hilt/processor/internal:compiler_options",
         "//java/dagger/hilt/processor/internal:component_descriptor",
         "//java/dagger/hilt/processor/internal:component_names",
         "//java/dagger/hilt/processor/internal:components",
@@ -75,17 +76,22 @@
         "//java/dagger/hilt/processor/internal:processor_errors",
         "//java/dagger/hilt/processor/internal:processors",
         "//java/dagger/hilt/processor/internal/aggregateddeps:component_dependencies",
+        "//java/dagger/hilt/processor/internal/aggregateddeps:pkg_private_metadata",
         "//java/dagger/hilt/processor/internal/aggregateddeps:processor_lib",
         "//java/dagger/hilt/processor/internal/aliasof:alias_ofs",
         "//java/dagger/hilt/processor/internal/aliasof:processor_lib",
         "//java/dagger/hilt/processor/internal/definecomponent:define_components",
         "//java/dagger/hilt/processor/internal/definecomponent:processor_lib",
+        "//java/dagger/hilt/processor/internal/earlyentrypoint:aggregated_early_entry_point_metadata",
+        "//java/dagger/hilt/processor/internal/earlyentrypoint:processor_lib",
         "//java/dagger/hilt/processor/internal/generatesrootinput:generates_root_inputs",
         "//java/dagger/hilt/processor/internal/generatesrootinput:processor_lib",
         "//java/dagger/hilt/processor/internal/originatingelement:processor_lib",
         "//java/dagger/hilt/processor/internal/root:processor_lib",
         "//java/dagger/hilt/processor/internal/root:root_metadata",
         "//java/dagger/hilt/processor/internal/root:root_type",
+        "//java/dagger/hilt/processor/internal/uninstallmodules:processor_lib",
+        "//java/dagger/hilt/processor/internal/uninstallmodules:aggregated_uninstall_modules_metadata",
     ],
     artifact_target_maven_deps = [
         "com.google.auto:auto-common",
diff --git a/java/dagger/hilt/processor/internal/AggregatedElements.java b/java/dagger/hilt/processor/internal/AggregatedElements.java
new file mode 100644
index 0000000..8ee820f
--- /dev/null
+++ b/java/dagger/hilt/processor/internal/AggregatedElements.java
@@ -0,0 +1,65 @@
+/*
+ * Copyright (C) 2021 The Dagger Authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package dagger.hilt.processor.internal;
+
+import static dagger.internal.codegen.extension.DaggerStreams.toImmutableSet;
+
+import com.google.auto.common.MoreElements;
+import com.google.common.collect.ImmutableSet;
+import com.squareup.javapoet.ClassName;
+import javax.lang.model.element.PackageElement;
+import javax.lang.model.element.TypeElement;
+import javax.lang.model.util.Elements;
+
+/** Utility class for aggregating metadata. */
+public final class AggregatedElements {
+
+  /** Returns all aggregated elements in the aggregating package after validating them. */
+  public static ImmutableSet<TypeElement> from(
+      String aggregatingPackage, ClassName aggregatingAnnotation, Elements elements) {
+    PackageElement packageElement = elements.getPackageElement(aggregatingPackage);
+
+    if (packageElement == null) {
+      return ImmutableSet.of();
+    }
+
+    ImmutableSet<TypeElement> aggregatedElements =
+        packageElement.getEnclosedElements().stream()
+            .map(MoreElements::asType)
+            .collect(toImmutableSet());
+
+    ProcessorErrors.checkState(
+        !aggregatedElements.isEmpty(),
+        packageElement,
+        "No dependencies found. Did you remove code in package %s?",
+        packageElement);
+
+    for (TypeElement aggregatedElement : aggregatedElements) {
+      ProcessorErrors.checkState(
+          Processors.hasAnnotation(aggregatedElement, aggregatingAnnotation),
+          aggregatedElement,
+          "Expected element, %s, to be annotated with @%s, but only found: %s.",
+          aggregatedElement.getSimpleName(),
+          aggregatingAnnotation,
+          aggregatedElement.getAnnotationMirrors());
+    }
+
+    return aggregatedElements;
+  }
+
+  private AggregatedElements() {}
+}
diff --git a/java/dagger/hilt/processor/internal/AnnotationValues.java b/java/dagger/hilt/processor/internal/AnnotationValues.java
index 584d8f9..9ebeeeb 100644
--- a/java/dagger/hilt/processor/internal/AnnotationValues.java
+++ b/java/dagger/hilt/processor/internal/AnnotationValues.java
@@ -18,15 +18,19 @@
 
 import static com.google.auto.common.AnnotationMirrors.getAnnotationValue;
 import static com.google.auto.common.AnnotationMirrors.getAnnotationValuesWithDefaults;
+import static com.google.auto.common.MoreTypes.asTypeElement;
 import static com.google.common.base.Preconditions.checkNotNull;
+import static dagger.internal.codegen.extension.DaggerStreams.toImmutableSet;
 
 import com.google.auto.common.MoreTypes;
 import com.google.common.collect.ImmutableList;
+import com.google.common.collect.ImmutableSet;
 import java.util.List;
 import java.util.Optional;
 import javax.lang.model.element.AnnotationMirror;
 import javax.lang.model.element.AnnotationValue;
 import javax.lang.model.element.AnnotationValueVisitor;
+import javax.lang.model.element.TypeElement;
 import javax.lang.model.element.VariableElement;
 import javax.lang.model.type.DeclaredType;
 import javax.lang.model.type.TypeMirror;
@@ -87,6 +91,18 @@
     }
   }
 
+  /** Returns a class array value as a set of {@link TypeElement}. */
+  public static ImmutableSet<TypeElement> getTypeElements(AnnotationValue value) {
+    return getAnnotationValues(value).stream()
+        .map(AnnotationValues::getTypeElement)
+        .collect(toImmutableSet());
+  }
+
+  /** Returns a class value as a {@link TypeElement}. */
+  public static TypeElement getTypeElement(AnnotationValue value) {
+    return asTypeElement(getTypeMirror(value));
+  }
+
   /**
    * Returns the value as a VariableElement.
    *
@@ -96,6 +112,13 @@
     return EnumVisitor.INSTANCE.visit(value);
   }
 
+  /** Returns a string array value as a set of strings. */
+  public static ImmutableSet<String> getStrings(AnnotationValue value) {
+    return getAnnotationValues(value).stream()
+        .map(AnnotationValues::getString)
+        .collect(toImmutableSet());
+  }
+
   /**
    * Returns the value as a string.
    *
@@ -105,6 +128,15 @@
     return valueOfType(value, String.class);
   }
 
+  /**
+   * Returns the value as a boolean.
+   *
+   * @throws IllegalArgumentException if the value is not a boolean.
+   */
+  public static boolean getBoolean(AnnotationValue value) {
+    return valueOfType(value, Boolean.class);
+  }
+
   private static <T> T valueOfType(AnnotationValue annotationValue, Class<T> type) {
     Object value = annotationValue.getValue();
     if (!type.isInstance(value)) {
diff --git a/java/dagger/hilt/processor/internal/BUILD b/java/dagger/hilt/processor/internal/BUILD
index baff1d8..978655d 100644
--- a/java/dagger/hilt/processor/internal/BUILD
+++ b/java/dagger/hilt/processor/internal/BUILD
@@ -24,6 +24,7 @@
         "ProcessorErrorHandler.java",
     ],
     deps = [
+        ":compiler_options",
         ":processor_errors",
         ":processors",
         "//java/dagger/internal/guava:base",
@@ -62,11 +63,9 @@
         ":processor_errors",
         "//java/dagger/internal/codegen/extension",
         "//java/dagger/internal/codegen/kotlin",
-        "//java/dagger/internal/codegen/langmodel",
         "//java/dagger/internal/guava:base",
         "//java/dagger/internal/guava:collect",
         "@google_bazel_common//third_party/java/javapoet",
-        "@google_bazel_common//third_party/java/jsr305_annotations",
         "@google_bazel_common//third_party/java/jsr330_inject",
         "@maven//:com_google_auto_auto_common",
         "@maven//:org_jetbrains_kotlin_kotlin_stdlib",
@@ -90,25 +89,34 @@
         "ComponentNames.java",
     ],
     deps = [
+        ":classnames",
         ":processors",
+        "//java/dagger/internal/guava:base",
+        "//java/dagger/internal/guava:collect",
         "@google_bazel_common//third_party/java/javapoet",
     ],
 )
 
 java_library(
-    name = "component_descriptor",
+    name = "aggregated_elements",
     srcs = [
-        "ComponentDescriptor.java",
-        "ComponentGenerator.java",
-        "ComponentTree.java",
+        "AggregatedElements.java",
     ],
     deps = [
-        ":classnames",
+        ":processor_errors",
         ":processors",
         "//java/dagger/internal/codegen/extension",
-        "//java/dagger/internal/guava:base",
         "//java/dagger/internal/guava:collect",
-        "//java/dagger/internal/guava:graph",
+        "@google_bazel_common//third_party/java/javapoet",
+        "@maven//:com_google_auto_auto_common",
+    ],
+)
+
+java_library(
+    name = "component_descriptor",
+    srcs = ["ComponentDescriptor.java"],
+    deps = [
+        "//java/dagger/internal/guava:collect",
         "@google_bazel_common//third_party/java/auto:value",
         "@google_bazel_common//third_party/java/javapoet",
     ],
@@ -143,6 +151,16 @@
     ],
 )
 
+java_library(
+    name = "compiler_options",
+    srcs = ["HiltCompilerOptions.java"],
+    deps = [
+        ":processor_errors",
+        "//java/dagger/internal/guava:collect",
+        "@google_bazel_common//third_party/java/javapoet",
+    ],
+)
+
 filegroup(
     name = "srcs_filegroup",
     srcs = glob(["*"]),
diff --git a/java/dagger/hilt/processor/internal/BadInputException.java b/java/dagger/hilt/processor/internal/BadInputException.java
index d961768..f57a34a 100644
--- a/java/dagger/hilt/processor/internal/BadInputException.java
+++ b/java/dagger/hilt/processor/internal/BadInputException.java
@@ -36,6 +36,11 @@
     this.badElements = ImmutableList.copyOf(badElements);
   }
 
+  public BadInputException(String message) {
+    super(message);
+    this.badElements = ImmutableList.of();
+  }
+
   public ImmutableList<Element> getBadElements() {
     return badElements;
   }
diff --git a/java/dagger/hilt/processor/internal/BaseProcessor.java b/java/dagger/hilt/processor/internal/BaseProcessor.java
index 4961cd5..1a63f8b 100644
--- a/java/dagger/hilt/processor/internal/BaseProcessor.java
+++ b/java/dagger/hilt/processor/internal/BaseProcessor.java
@@ -96,6 +96,15 @@
   private Messager messager;
   private ProcessorErrorHandler errorHandler;
 
+  @Override
+  public final Set<String> getSupportedOptions() {
+    // This is declared here rather than in the actual processors because KAPT will issue a
+    // warning if any used option is not unsupported. This can happen when there is a module
+    // which uses Hilt but lacks any @AndroidEntryPoint annotations.
+    // See: https://github.com/google/dagger/issues/2040
+    return HiltCompilerOptions.getProcessorOptions();
+  }
+
   /** Used to perform initialization before each round of processing. */
   protected void preRoundProcess(RoundEnvironment roundEnv) {};
 
diff --git a/java/dagger/hilt/processor/internal/ClassNames.java b/java/dagger/hilt/processor/internal/ClassNames.java
index 234ea7b..093e1b3 100644
--- a/java/dagger/hilt/processor/internal/ClassNames.java
+++ b/java/dagger/hilt/processor/internal/ClassNames.java
@@ -22,6 +22,26 @@
 
 /** Holder for commonly used class names. */
 public final class ClassNames {
+  public static final String AGGREGATED_ROOT_PACKAGE =
+      "dagger.hilt.internal.aggregatedroot.codegen";
+  public static final ClassName AGGREGATED_ROOT =
+      get("dagger.hilt.internal.aggregatedroot", "AggregatedRoot");
+  public static final String PROCESSED_ROOT_SENTINEL_PACKAGE =
+      "dagger.hilt.internal.processedrootsentinel.codegen";
+  public static final ClassName PROCESSED_ROOT_SENTINEL =
+      get("dagger.hilt.internal.processedrootsentinel", "ProcessedRootSentinel");
+
+  public static final String AGGREGATED_EARLY_ENTRY_POINT_PACKAGE =
+      "dagger.hilt.android.internal.earlyentrypoint.codegen";
+  public static final ClassName AGGREGATED_EARLY_ENTRY_POINT =
+      get("dagger.hilt.android.internal.earlyentrypoint", "AggregatedEarlyEntryPoint");
+  public static final ClassName EARLY_ENTRY_POINT = get("dagger.hilt.android", "EarlyEntryPoint");
+
+  public static final String AGGREGATED_UNINSTALL_MODULES_PACKAGE =
+      "dagger.hilt.android.internal.uninstallmodules.codegen";
+  public static final ClassName AGGREGATED_UNINSTALL_MODULES =
+      get("dagger.hilt.android.internal.uninstallmodules", "AggregatedUninstallModules");
+
   public static final ClassName ORIGINATING_ELEMENT =
       get("dagger.hilt.codegen", "OriginatingElement");
   public static final ClassName AGGREGATED_DEPS =
@@ -32,9 +52,11 @@
       get("dagger.hilt.internal", "GeneratedComponentManager");
   public static final ClassName GENERATED_COMPONENT_MANAGER_HOLDER =
       get("dagger.hilt.internal", "GeneratedComponentManagerHolder");
-  public static final ClassName IGNORE_MODULES =
+  public static final ClassName UNINSTALL_MODULES =
       get("dagger.hilt.android.testing", "UninstallModules");
 
+  public static final String DEFINE_COMPONENT_CLASSES_PACKAGE =
+      "dagger.hilt.processor.internal.definecomponent.codegen";
   public static final ClassName DEFINE_COMPONENT = get("dagger.hilt", "DefineComponent");
   public static final ClassName DEFINE_COMPONENT_BUILDER =
       get("dagger.hilt", "DefineComponent", "Builder");
@@ -79,6 +101,8 @@
   public static final ClassName ALIAS_OF = get("dagger.hilt.migration", "AliasOf");
   public static final ClassName ALIAS_OF_PROPAGATED_DATA =
       get("dagger.hilt.internal.aliasof", "AliasOfPropagatedData");
+  public static final String ALIAS_OF_PROPAGATED_DATA_PACKAGE =
+      "dagger.hilt.processor.internal.aliasof.codegen";
 
   public static final ClassName GENERATES_ROOT_INPUT = get("dagger.hilt", "GeneratesRootInput");
   public static final ClassName GENERATES_ROOT_INPUT_PROPAGATED_DATA =
@@ -122,12 +146,12 @@
       get("dagger.hilt.android.internal.managers", "ComponentSupplier");
   public static final ClassName APPLICATION_CONTEXT_MODULE =
       get("dagger.hilt.android.internal.modules", "ApplicationContextModule");
+  public static final ClassName DEFAULT_ROOT =
+      ClassName.get("dagger.hilt.android.internal.testing.root", "Default");
   public static final ClassName INTERNAL_TEST_ROOT =
       get("dagger.hilt.android.internal.testing", "InternalTestRoot");
   public static final ClassName TEST_INJECTOR =
       get("dagger.hilt.android.internal.testing", "TestInjector");
-  public static final ClassName TEST_APPLICATION_INJECTOR =
-      get("dagger.hilt.android.internal.testing", "TestApplicationInjector");
   public static final ClassName TEST_APPLICATION_COMPONENT_MANAGER =
       get("dagger.hilt.android.internal.testing", "TestApplicationComponentManager");
   public static final ClassName TEST_APPLICATION_COMPONENT_MANAGER_HOLDER =
@@ -152,6 +176,8 @@
       get("dagger.hilt.android.testing", "BindValueIntoSet");
   public static final ClassName APPLICATION_CONTEXT =
       get("dagger.hilt.android.qualifiers", "ApplicationContext");
+  public static final ClassName TEST_SINGLETON_COMPONENT =
+      get("dagger.hilt.internal", "TestSingletonComponent");
   public static final ClassName TEST_COMPONENT_DATA =
       get("dagger.hilt.android.internal.testing", "TestComponentData");
   public static final ClassName TEST_COMPONENT_DATA_SUPPLIER =
diff --git a/java/dagger/hilt/processor/internal/ComponentNames.java b/java/dagger/hilt/processor/internal/ComponentNames.java
index fab4d19..eeaa5f4 100644
--- a/java/dagger/hilt/processor/internal/ComponentNames.java
+++ b/java/dagger/hilt/processor/internal/ComponentNames.java
@@ -16,7 +16,25 @@
 
 package dagger.hilt.processor.internal;
 
+import static java.lang.Character.isUpperCase;
+import static java.lang.String.format;
+import static java.util.Comparator.comparing;
+
+import com.google.common.base.CharMatcher;
+import com.google.common.base.Preconditions;
+import com.google.common.base.Splitter;
+import com.google.common.collect.ImmutableList;
+import com.google.common.collect.ImmutableListMultimap;
+import com.google.common.collect.ImmutableMap;
+import com.google.common.collect.Iterables;
+import com.google.common.collect.Multimaps;
 import com.squareup.javapoet.ClassName;
+import java.util.Collection;
+import java.util.HashSet;
+import java.util.Iterator;
+import java.util.Set;
+import javax.lang.model.element.Name;
+import javax.lang.model.element.TypeElement;
 
 /**
  * Utility class for getting the generated component name.
@@ -24,15 +42,45 @@
  * <p>This should not be used externally.
  */
 public final class ComponentNames {
-  private ComponentNames() {}
+  private static final Splitter QUALIFIED_NAME_SPLITTER = Splitter.on('.');
+
+  private final boolean renameTestComponents;
+  private final String destinationPackage;
+  private final ImmutableMap<ClassName, String> simpleNameByClassName;
+
+  public static ComponentNames withoutRenaming() {
+    return new ComponentNames(
+        /*renameTestComponents=*/ false, /*destinationPackage=*/ null, ImmutableMap.of());
+  }
+
+  public static ComponentNames withRenamingIntoPackage(
+      String destinationPackage, ImmutableList<TypeElement> roots) {
+    ImmutableMap.Builder<ClassName, String> builder = ImmutableMap.builder();
+    ImmutableListMultimap<String, TypeElement> rootsBySimpleName =
+        Multimaps.index(roots, typeElement -> typeElement.getSimpleName().toString());
+    rootsBySimpleName.asMap().values().stream()
+        .map(ComponentNames::disambiguateConflictingSimpleNames)
+        .forEach(builder::putAll);
+    return new ComponentNames(/*renameTestComponents=*/ true, destinationPackage, builder.build());
+  }
+
+  private ComponentNames(
+      boolean renameTestComponents,
+      String destinationPackage,
+      ImmutableMap<ClassName, String> simpleNameByClassName) {
+    this.renameTestComponents = renameTestComponents;
+    this.destinationPackage = destinationPackage;
+    this.simpleNameByClassName = simpleNameByClassName;
+  }
 
   /** Returns the name of the generated component wrapper. */
-  public static ClassName generatedComponentsWrapper(ClassName root) {
-    return Processors.append(Processors.getEnclosedClassName(root), "_HiltComponents");
+  public ClassName generatedComponentsWrapper(ClassName root) {
+    return Processors.append(
+        Processors.getEnclosedClassName(maybeRenameComponent(root)), "_HiltComponents");
   }
 
   /** Returns the name of the generated component. */
-  public static ClassName generatedComponent(ClassName root, ClassName component) {
+  public ClassName generatedComponent(ClassName root, ClassName component) {
     return generatedComponentsWrapper(root).nestedClass(componentName(component));
   }
 
@@ -50,4 +98,82 @@
     // Note: This uses regex matching so we only match if the name ends in "Component"
     return Processors.getEnclosedName(component).replaceAll("Component$", "C");
   }
+
+  /**
+   * Rewrites the provided HiltAndroidTest-annotated class name using the shared component
+   * directory.
+   */
+  private ClassName maybeRenameComponent(ClassName className) {
+    return (renameTestComponents && !className.equals(ClassNames.DEFAULT_ROOT))
+        ? ClassName.get(destinationPackage, dedupeSimpleName(className))
+        : className;
+  }
+
+  /**
+   * Derives a new generated component base name, should the simple names of two roots have
+   * conflicting simple names.
+   *
+   * <p>This is lifted nearly verbatim (albeit with new different struct types) from {@link
+   * dagger.internal.codegen.writing.SubcomponentNames}.
+   */
+  private String dedupeSimpleName(ClassName className) {
+    Preconditions.checkState(
+        simpleNameByClassName.containsKey(className),
+        "Class name %s not found in simple name map",
+        className.canonicalName());
+    return simpleNameByClassName.get(className);
+  }
+
+  private static ImmutableMap<ClassName, String> disambiguateConflictingSimpleNames(
+      Collection<TypeElement> rootsWithConflictingNames) {
+    // If there's only 1 root there's nothing to disambiguate so return the simple name.
+    if (rootsWithConflictingNames.size() == 1) {
+      TypeElement root = Iterables.getOnlyElement(rootsWithConflictingNames);
+      return ImmutableMap.of(ClassName.get(root), root.getSimpleName().toString());
+    }
+
+    // There are conflicting simple names, so disambiguate them with a unique prefix.
+    // We keep them small to fix https://github.com/google/dagger/issues/421.
+    // Sorted in order to guarantee determinism if this is invoked by different processors.
+    ImmutableList<TypeElement> sortedRootsWithConflictingNames =
+        ImmutableList.sortedCopyOf(
+            comparing(typeElement -> typeElement.getQualifiedName().toString()),
+            rootsWithConflictingNames);
+    Set<String> usedNames = new HashSet<>();
+    ImmutableMap.Builder<ClassName, String> uniqueNames = ImmutableMap.builder();
+    for (TypeElement root : sortedRootsWithConflictingNames) {
+      String basePrefix = uniquingPrefix(root);
+      String uniqueName = basePrefix;
+      for (int differentiator = 2; !usedNames.add(uniqueName); differentiator++) {
+        uniqueName = basePrefix + differentiator;
+      }
+      uniqueNames.put(ClassName.get(root), format("%s_%s", uniqueName, root.getSimpleName()));
+    }
+    return uniqueNames.build();
+  }
+
+  /** Returns a prefix that could make the component's simple name more unique. */
+  private static String uniquingPrefix(TypeElement typeElement) {
+    String containerName = typeElement.getEnclosingElement().getSimpleName().toString();
+
+    // If parent element looks like a class, use its initials as a prefix.
+    if (!containerName.isEmpty() && isUpperCase(containerName.charAt(0))) {
+      return CharMatcher.javaLowerCase().removeFrom(containerName);
+    }
+
+    // Not in a normally named class. Prefix with the initials of the elements leading here.
+    Name qualifiedName = typeElement.getQualifiedName();
+    Iterator<String> pieces = QUALIFIED_NAME_SPLITTER.split(qualifiedName).iterator();
+    StringBuilder b = new StringBuilder();
+
+    while (pieces.hasNext()) {
+      String next = pieces.next();
+      if (pieces.hasNext()) {
+        b.append(next.charAt(0));
+      }
+    }
+
+    // Note that a top level class in the root package will be prefixed "$_".
+    return b.length() > 0 ? b.toString() : "$";
+  }
 }
diff --git a/java/dagger/hilt/processor/internal/HiltCompilerOptions.java b/java/dagger/hilt/processor/internal/HiltCompilerOptions.java
new file mode 100644
index 0000000..0d24823
--- /dev/null
+++ b/java/dagger/hilt/processor/internal/HiltCompilerOptions.java
@@ -0,0 +1,116 @@
+/*
+ * Copyright (C) 2020 The Dagger Authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package dagger.hilt.processor.internal;
+
+import com.google.common.collect.ImmutableSet;
+import java.util.Arrays;
+import java.util.Set;
+import java.util.stream.Collectors;
+import javax.annotation.processing.ProcessingEnvironment;
+import javax.lang.model.element.TypeElement;
+
+/** Hilt annotation processor options. */
+// TODO(danysantiago): Consider consolidating with Dagger compiler options logic.
+public final class HiltCompilerOptions {
+
+  /**
+   * Returns {@code true} if the superclass validation is disabled for
+   * {@link dagger.hilt.android.AndroidEntryPoint}-annotated classes.
+   *
+   * This flag is for internal use only! The superclass validation checks that the super class is a
+   * generated {@code Hilt_} class. This flag is disabled by the Hilt Gradle plugin to enable
+   * bytecode transformation to change the superclass.
+   */
+  public static boolean isAndroidSuperclassValidationDisabled(
+      TypeElement element, ProcessingEnvironment env) {
+    BooleanOption option = BooleanOption.DISABLE_ANDROID_SUPERCLASS_VALIDATION;
+    return option.get(env);
+  }
+
+  /**
+   * Returns {@code true} if cross-compilation root validation is disabled.
+   *
+   * <p>This flag should rarely be needed, but may be used for legacy/migration purposes if
+   * tests require the use of {@link dagger.hilt.android.HiltAndroidApp} rather than
+   * {@link dagger.hilt.android.testing.HiltAndroidTest}.
+   *
+   * <p>Note that Hilt still does validation within a single compilation unit. In particular,
+   * a compilation unit that contains a {@code HiltAndroidApp} usage cannot have other
+   * {@code HiltAndroidApp} or {@code HiltAndroidTest} usages in the same compilation unit.
+   */
+  public static boolean isCrossCompilationRootValidationDisabled(
+      ImmutableSet<TypeElement> rootElements, ProcessingEnvironment env) {
+    BooleanOption option = BooleanOption.DISABLE_CROSS_COMPILATION_ROOT_VALIDATION;
+    return option.get(env);
+  }
+
+  /** Returns {@code true} if the check for {@link dagger.hilt.InstallIn} is disabled. */
+  public static boolean isModuleInstallInCheckDisabled(ProcessingEnvironment env) {
+    return BooleanOption.DISABLE_MODULES_HAVE_INSTALL_IN_CHECK.get(env);
+  }
+
+  /**
+   * Returns {@code true} of unit tests should try to share generated components, rather than using
+   * separate generated components per Hilt test root.
+   *
+   * <p>Tests that provide their own test bindings (e.g. using {@link
+   * dagger.hilt.android.testing.BindValue} or a test {@link dagger.Module}) cannot use the shared
+   * component. In these cases, a component will be generated for the test.
+   */
+  public static boolean isSharedTestComponentsEnabled(ProcessingEnvironment env) {
+    return BooleanOption.SHARE_TEST_COMPONENTS.get(env);
+  }
+
+  /** Processor options which can have true or false values. */
+  private enum BooleanOption {
+    DISABLE_ANDROID_SUPERCLASS_VALIDATION(
+        "android.internal.disableAndroidSuperclassValidation", false),
+
+    DISABLE_CROSS_COMPILATION_ROOT_VALIDATION("disableCrossCompilationRootValidation", false),
+
+    DISABLE_MODULES_HAVE_INSTALL_IN_CHECK("disableModulesHaveInstallInCheck", false),
+
+    SHARE_TEST_COMPONENTS("shareTestComponents", false);
+
+    private final String name;
+    private final boolean defaultValue;
+
+    BooleanOption(String name, boolean defaultValue) {
+      this.name = name;
+      this.defaultValue = defaultValue;
+    }
+
+    boolean get(ProcessingEnvironment env) {
+      String value = env.getOptions().get(getQualifiedName());
+      if (value == null) {
+        return defaultValue;
+      }
+      // TODO(danysantiago): Strictly verify input, either 'true' or 'false' and nothing else.
+      return Boolean.parseBoolean(value);
+    }
+
+    String getQualifiedName() {
+      return "dagger.hilt." + name;
+    }
+  }
+
+  public static Set<String> getProcessorOptions() {
+    return Arrays.stream(BooleanOption.values())
+        .map(BooleanOption::getQualifiedName)
+        .collect(Collectors.toSet());
+  }
+}
diff --git a/java/dagger/hilt/processor/internal/KotlinMetadataUtils.java b/java/dagger/hilt/processor/internal/KotlinMetadataUtils.java
index ec5ddf8..64d5892 100644
--- a/java/dagger/hilt/processor/internal/KotlinMetadataUtils.java
+++ b/java/dagger/hilt/processor/internal/KotlinMetadataUtils.java
@@ -20,7 +20,11 @@
 import dagger.internal.codegen.kotlin.KotlinMetadataUtil;
 import javax.inject.Singleton;
 
-/** A single-use provider of {@link KotlinMetadataUtil}. */
+/**
+ * A single-use provider of {@link KotlinMetadataUtil}. Since the returned util has a cache, it is
+ * better to reuse the same instance as much as possible, except for going across processor rounds
+ * because the cache contains elements.
+ */
 // TODO(erichang):  Revert this, should be wrapped with a Dagger module.
 public final class KotlinMetadataUtils {
 
diff --git a/java/dagger/hilt/processor/internal/ProcessorErrorHandler.java b/java/dagger/hilt/processor/internal/ProcessorErrorHandler.java
index 460e8a9..2ecc0f0 100644
--- a/java/dagger/hilt/processor/internal/ProcessorErrorHandler.java
+++ b/java/dagger/hilt/processor/internal/ProcessorErrorHandler.java
@@ -62,6 +62,9 @@
 
     if (t instanceof BadInputException) {
       BadInputException badInput = (BadInputException) t;
+      if (badInput.getBadElements().isEmpty()) {
+        hiltErrors.add(HiltError.of(badInput.getMessage()));
+      }
       for (Element element : badInput.getBadElements()) {
         hiltErrors.add(HiltError.of(badInput.getMessage(), element));
       }
diff --git a/java/dagger/hilt/processor/internal/ProcessorErrors.java b/java/dagger/hilt/processor/internal/ProcessorErrors.java
index b1578da..d75bb62 100644
--- a/java/dagger/hilt/processor/internal/ProcessorErrors.java
+++ b/java/dagger/hilt/processor/internal/ProcessorErrors.java
@@ -36,6 +36,49 @@
    * involving any parameters to the calling method.
    *
    * @param expression a boolean expression
+   * @param errorMessage the exception message to use if the check fails; will be converted to a
+   *     string using {@link String#valueOf(Object)}
+   * @throws BadInputException if {@code expression} is false
+   */
+  public static void checkState(
+      boolean expression,
+      @Nullable Object errorMessage) {
+    if (!expression) {
+      throw new BadInputException(String.valueOf(errorMessage));
+    }
+  }
+
+  /**
+   * Ensures the truth of an expression involving the state of the calling instance, but not
+   * involving any parameters to the calling method.
+   *
+   * @param expression a boolean expression
+   * @param errorMessageTemplate a template for the exception message should the check fail. The
+   *     message is formed by replacing each {@code %s} placeholder in the template with an
+   *     argument. These are matched by position - the first {@code %s} gets {@code
+   *     errorMessageArgs[0]}, etc. Unmatched arguments will be appended to the formatted message in
+   *     square braces. Unmatched placeholders will be left as-is.
+   * @param errorMessageArgs the arguments to be substituted into the message template. Arguments
+   *     are converted to strings using {@link String#valueOf(Object)}.
+   * @throws BadInputException if {@code expression} is false
+   * @throws NullPointerException if the check fails and either {@code errorMessageTemplate} or
+   *     {@code errorMessageArgs} is null (don't let this happen)
+   */
+  @FormatMethod
+  public static void checkState(
+      boolean expression,
+      @Nullable @FormatString String errorMessageTemplate,
+      @Nullable Object... errorMessageArgs) {
+    if (!expression) {
+      throw new BadInputException(String.format(errorMessageTemplate, errorMessageArgs));
+    }
+  }
+
+  /**
+   * Ensures the truth of an expression involving the state of the calling instance, but not
+   * involving any parameters to the calling method.
+   *
+   * @param expression a boolean expression
    * @param badElement the element that was at fault
    * @param errorMessage the exception message to use if the check fails; will be converted to a
    *     string using {@link String#valueOf(Object)}
diff --git a/java/dagger/hilt/processor/internal/Processors.java b/java/dagger/hilt/processor/internal/Processors.java
index b33c19d..8740bd6 100644
--- a/java/dagger/hilt/processor/internal/Processors.java
+++ b/java/dagger/hilt/processor/internal/Processors.java
@@ -17,10 +17,12 @@
 package dagger.hilt.processor.internal;
 
 import static com.google.auto.common.MoreElements.asPackage;
+import static com.google.auto.common.MoreElements.asType;
 import static com.google.auto.common.MoreElements.asVariable;
 import static com.google.common.base.Preconditions.checkNotNull;
 import static dagger.internal.codegen.extension.DaggerCollectors.toOptional;
 import static javax.lang.model.element.Modifier.ABSTRACT;
+import static javax.lang.model.element.Modifier.PUBLIC;
 import static javax.lang.model.element.Modifier.STATIC;
 
 import com.google.auto.common.AnnotationMirrors;
@@ -41,6 +43,7 @@
 import com.google.common.collect.SetMultimap;
 import com.squareup.javapoet.AnnotationSpec;
 import com.squareup.javapoet.ClassName;
+import com.squareup.javapoet.JavaFile;
 import com.squareup.javapoet.MethodSpec;
 import com.squareup.javapoet.ParameterSpec;
 import com.squareup.javapoet.ParameterizedTypeName;
@@ -48,6 +51,7 @@
 import com.squareup.javapoet.TypeSpec;
 import dagger.internal.codegen.extension.DaggerStreams;
 import dagger.internal.codegen.kotlin.KotlinMetadataUtil;
+import java.io.IOException;
 import java.lang.annotation.Annotation;
 import java.util.LinkedHashSet;
 import java.util.List;
@@ -88,6 +92,26 @@
 
   private static final String JAVA_CLASS = "java.lang.Class";
 
+  public static void generateAggregatingClass(
+      String aggregatingPackage,
+      AnnotationSpec aggregatingAnnotation,
+      TypeElement element,
+      Class<?> generatedAnnotationClass,
+      ProcessingEnvironment env) throws IOException {
+    ClassName name = ClassName.get(aggregatingPackage, "_" + getFullEnclosedName(element));
+    TypeSpec.Builder builder =
+        TypeSpec.classBuilder(name)
+            .addModifiers(PUBLIC)
+            .addOriginatingElement(element)
+            .addAnnotation(aggregatingAnnotation)
+            .addJavadoc("This class should only be referenced by generated code!")
+            .addJavadoc("This class aggregates information across multiple compilations.\n");;
+
+    addGeneratedAnnotation(builder, env, generatedAnnotationClass);
+
+    JavaFile.builder(name.packageName(), builder.build()).build().writeTo(env.getFiler());
+  }
+
   /** Returns a map from {@link AnnotationMirror} attribute name to {@link AnnotationValue}s */
   public static ImmutableMap<String, AnnotationValue> getAnnotationValues(Elements elements,
       AnnotationMirror annotation) {
@@ -511,6 +535,14 @@
     return ClassName.get(className.packageName(), getEnclosedName(className));
   }
 
+  /**
+   * Returns an equivalent class name with the {@code .} (dots) used for inner classes replaced with
+   * {@code _}.
+   */
+  public static ClassName getEnclosedClassName(TypeElement typeElement) {
+    return getEnclosedClassName(ClassName.get(typeElement));
+  }
+
   /** Returns the fully qualified class name, with _ instead of . */
   public static String getFullyQualifiedEnclosedClassName(ClassName className) {
     return className.packageName().replace('.', '_') + getEnclosedName(className);
@@ -893,7 +925,10 @@
     return ElementFilter.methodsIn(elements.getAllMembers(module)).stream()
         .filter(Processors::isBindingMethod)
         .map(ExecutableElement::getModifiers)
-        .anyMatch(modifiers -> !modifiers.contains(ABSTRACT) && !modifiers.contains(STATIC));
+        .anyMatch(modifiers -> !modifiers.contains(ABSTRACT) && !modifiers.contains(STATIC))
+        // TODO(erichang): Getting a new KotlinMetadataUtil each time isn't great here, but until
+        // we have some sort of dependency management it will be difficult to share the instance.
+        && !KotlinMetadataUtils.getMetadataUtil().isObjectOrCompanionObjectClass(module);
   }
 
   private static boolean isBindingMethod(ExecutableElement method) {
@@ -936,5 +971,27 @@
         : typeName;
   }
 
+  public static Optional<TypeElement> getOriginatingTestElement(
+      Element element, Elements elements) {
+    TypeElement topLevelType = getOriginatingTopLevelType(element, elements);
+    return hasAnnotation(topLevelType, ClassNames.HILT_ANDROID_TEST)
+        ? Optional.of(asType(topLevelType))
+        : Optional.empty();
+  }
+
+  private static TypeElement getOriginatingTopLevelType(Element element, Elements elements) {
+    TypeElement topLevelType = getTopLevelType(element);
+    if (hasAnnotation(topLevelType, ClassNames.ORIGINATING_ELEMENT)) {
+      return getOriginatingTopLevelType(
+          getAnnotationClassValue(
+              elements,
+              getAnnotationMirror(topLevelType, ClassNames.ORIGINATING_ELEMENT),
+              "topLevelClass"),
+          elements);
+    }
+
+    return topLevelType;
+  }
+
   private Processors() {}
 }
diff --git a/java/dagger/hilt/processor/internal/aggregateddeps/AggregatedDepsGenerator.java b/java/dagger/hilt/processor/internal/aggregateddeps/AggregatedDepsGenerator.java
index bcc2dfe..84fb5a1 100644
--- a/java/dagger/hilt/processor/internal/aggregateddeps/AggregatedDepsGenerator.java
+++ b/java/dagger/hilt/processor/internal/aggregateddeps/AggregatedDepsGenerator.java
@@ -19,8 +19,6 @@
 import com.google.common.collect.ImmutableSet;
 import com.squareup.javapoet.AnnotationSpec;
 import com.squareup.javapoet.ClassName;
-import com.squareup.javapoet.JavaFile;
-import com.squareup.javapoet.TypeSpec;
 import dagger.hilt.processor.internal.Processors;
 import java.io.IOException;
 import java.util.Optional;
@@ -59,20 +57,8 @@
   }
 
   void generate() throws IOException {
-    ClassName name =
-        ClassName.get(
-            AGGREGATING_PACKAGE, Processors.getFullEnclosedName(dependency) + "ModuleDeps");
-    TypeSpec.Builder generator =
-        TypeSpec.classBuilder(name.simpleName())
-            .addOriginatingElement(dependency)
-            .addAnnotation(aggregatedDepsAnnotation())
-            .addJavadoc("Generated class to pass information through multiple javac runs.\n");
-
-    Processors.addGeneratedAnnotation(generator, processingEnv, getClass());
-
-    JavaFile.builder(name.packageName(), generator.build())
-        .build()
-        .writeTo(processingEnv.getFiler());
+    Processors.generateAggregatingClass(
+        AGGREGATING_PACKAGE, aggregatedDepsAnnotation(), dependency, getClass(), processingEnv);
   }
 
   private AnnotationSpec aggregatedDepsAnnotation() {
diff --git a/java/dagger/hilt/processor/internal/aggregateddeps/AggregatedDepsMetadata.java b/java/dagger/hilt/processor/internal/aggregateddeps/AggregatedDepsMetadata.java
new file mode 100644
index 0000000..09e1651
--- /dev/null
+++ b/java/dagger/hilt/processor/internal/aggregateddeps/AggregatedDepsMetadata.java
@@ -0,0 +1,178 @@
+/*
+ * Copyright (C) 2021 The Dagger Authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package dagger.hilt.processor.internal.aggregateddeps;
+
+import static com.google.common.base.Preconditions.checkNotNull;
+import static com.google.common.base.Preconditions.checkState;
+import static com.google.common.collect.Iterables.getOnlyElement;
+import static dagger.internal.codegen.extension.DaggerStreams.toImmutableSet;
+
+import com.google.auto.value.AutoValue;
+import com.google.common.collect.ImmutableMap;
+import com.google.common.collect.ImmutableSet;
+import dagger.hilt.processor.internal.AggregatedElements;
+import dagger.hilt.processor.internal.AnnotationValues;
+import dagger.hilt.processor.internal.ClassNames;
+import dagger.hilt.processor.internal.Processors;
+import java.util.Optional;
+import javax.lang.model.element.AnnotationMirror;
+import javax.lang.model.element.AnnotationValue;
+import javax.lang.model.element.TypeElement;
+import javax.lang.model.util.Elements;
+
+/**
+ * A class that represents the values stored in an {@link
+ * dagger.hilt.processor.internal.aggregateddeps.AggregatedDeps} annotation.
+ */
+@AutoValue
+abstract class AggregatedDepsMetadata {
+  private static final String AGGREGATED_DEPS_PACKAGE = "hilt_aggregated_deps";
+
+  enum DependencyType {
+    MODULE,
+    ENTRY_POINT,
+    COMPONENT_ENTRY_POINT
+  }
+
+  abstract Optional<TypeElement> testElement();
+
+  abstract ImmutableSet<TypeElement> componentElements();
+
+  abstract DependencyType dependencyType();
+
+  abstract TypeElement dependency();
+
+  abstract ImmutableSet<TypeElement> replacedDependencies();
+
+  /** Returns all aggregated deps in the aggregating package. */
+  public static ImmutableSet<AggregatedDepsMetadata> from(Elements elements) {
+    return AggregatedElements.from(AGGREGATED_DEPS_PACKAGE, ClassNames.AGGREGATED_DEPS, elements)
+        .stream()
+        .map(aggregatedElement -> create(aggregatedElement, elements))
+        .collect(toImmutableSet());
+  }
+
+  private static AggregatedDepsMetadata create(TypeElement element, Elements elements) {
+    AnnotationMirror annotationMirror =
+        Processors.getAnnotationMirror(element, ClassNames.AGGREGATED_DEPS);
+
+    ImmutableMap<String, AnnotationValue> values =
+        Processors.getAnnotationValues(elements, annotationMirror);
+
+    return new AutoValue_AggregatedDepsMetadata(
+        getTestElement(values.get("test"), elements),
+        getComponents(values.get("components"), elements),
+        getDependencyType(
+            values.get("modules"),
+            values.get("entryPoints"),
+            values.get("componentEntryPoints")),
+        getDependency(
+            values.get("modules"),
+            values.get("entryPoints"),
+            values.get("componentEntryPoints"),
+            elements),
+        getReplacedDependencies(values.get("replaces"), elements));
+  }
+
+  private static Optional<TypeElement> getTestElement(
+      AnnotationValue testValue, Elements elements) {
+    checkNotNull(testValue);
+    String test = AnnotationValues.getString(testValue);
+    return test.isEmpty() ? Optional.empty() : Optional.of(elements.getTypeElement(test));
+  }
+
+  private static ImmutableSet<TypeElement> getComponents(
+      AnnotationValue componentsValue, Elements elements) {
+    checkNotNull(componentsValue);
+    ImmutableSet<TypeElement> componentNames =
+        AnnotationValues.getAnnotationValues(componentsValue).stream()
+            .map(AnnotationValues::getString)
+            .map(
+                // This is a temporary hack to map the old ApplicationComponent to the new
+                // SingletonComponent. Technically, this is only needed for backwards compatibility
+                // with libraries using the old processor since new processors should convert to the
+                // new SingletonComponent when generating the metadata class.
+                componentName ->
+                    componentName.contentEquals(
+                            "dagger.hilt.android.components.ApplicationComponent")
+                        ? ClassNames.SINGLETON_COMPONENT.canonicalName()
+                        : componentName)
+            .map(elements::getTypeElement)
+            .collect(toImmutableSet());
+    checkState(!componentNames.isEmpty());
+    return componentNames;
+  }
+
+  private static DependencyType getDependencyType(
+      AnnotationValue modulesValue,
+      AnnotationValue entryPointsValue,
+      AnnotationValue componentEntryPointsValue) {
+    checkNotNull(modulesValue);
+    checkNotNull(entryPointsValue);
+    checkNotNull(componentEntryPointsValue);
+
+    ImmutableSet.Builder<DependencyType> dependencyTypes = ImmutableSet.builder();
+    if (!AnnotationValues.getAnnotationValues(modulesValue).isEmpty()) {
+      dependencyTypes.add(DependencyType.MODULE);
+    }
+    if (!AnnotationValues.getAnnotationValues(entryPointsValue).isEmpty()) {
+      dependencyTypes.add(DependencyType.ENTRY_POINT);
+    }
+    if (!AnnotationValues.getAnnotationValues(componentEntryPointsValue).isEmpty()) {
+      dependencyTypes.add(DependencyType.COMPONENT_ENTRY_POINT);
+    }
+    return getOnlyElement(dependencyTypes.build());
+  }
+
+  private static TypeElement getDependency(
+      AnnotationValue modulesValue,
+      AnnotationValue entryPointsValue,
+      AnnotationValue componentEntryPointsValue,
+      Elements elements) {
+    checkNotNull(modulesValue);
+    checkNotNull(entryPointsValue);
+    checkNotNull(componentEntryPointsValue);
+
+    return elements.getTypeElement(
+        AnnotationValues.getString(
+            getOnlyElement(
+                ImmutableSet.<AnnotationValue>builder()
+                    .addAll(AnnotationValues.getAnnotationValues(modulesValue))
+                    .addAll(AnnotationValues.getAnnotationValues(entryPointsValue))
+                    .addAll(AnnotationValues.getAnnotationValues(componentEntryPointsValue))
+                    .build())));
+  }
+
+  private static ImmutableSet<TypeElement> getReplacedDependencies(
+      AnnotationValue replacedDependenciesValue, Elements elements) {
+    // Allow null values to support libraries using a Hilt version before @TestInstallIn was added
+    return replacedDependenciesValue == null
+        ? ImmutableSet.of()
+        : AnnotationValues.getAnnotationValues(replacedDependenciesValue).stream()
+            .map(AnnotationValues::getString)
+            .map(elements::getTypeElement)
+            .map(replacedDep -> getPublicDependency(replacedDep, elements))
+            .collect(toImmutableSet());
+  }
+
+  /** Returns the public Hilt wrapper module, or the module itself if its already public. */
+  private static TypeElement getPublicDependency(TypeElement dependency, Elements elements) {
+    return PkgPrivateMetadata.of(elements, dependency, ClassNames.MODULE)
+        .map(metadata -> elements.getTypeElement(metadata.generatedClassName().toString()))
+        .orElse(dependency);
+  }
+}
diff --git a/java/dagger/hilt/processor/internal/aggregateddeps/AggregatedDepsProcessor.java b/java/dagger/hilt/processor/internal/aggregateddeps/AggregatedDepsProcessor.java
index 58c938f..151401e 100644
--- a/java/dagger/hilt/processor/internal/aggregateddeps/AggregatedDepsProcessor.java
+++ b/java/dagger/hilt/processor/internal/aggregateddeps/AggregatedDepsProcessor.java
@@ -20,7 +20,7 @@
 import static com.google.auto.common.MoreElements.asType;
 import static com.google.auto.common.MoreElements.getPackage;
 import static com.google.common.collect.Iterables.getOnlyElement;
-import static dagger.hilt.android.processor.internal.androidentrypoint.HiltCompilerOptions.BooleanOption.DISABLE_MODULES_HAVE_INSTALL_IN_CHECK;
+import static dagger.hilt.processor.internal.HiltCompilerOptions.isModuleInstallInCheckDisabled;
 import static dagger.internal.codegen.extension.DaggerStreams.toImmutableList;
 import static dagger.internal.codegen.extension.DaggerStreams.toImmutableSet;
 import static javax.lang.model.element.ElementKind.CLASS;
@@ -37,10 +37,8 @@
 import dagger.hilt.processor.internal.BaseProcessor;
 import dagger.hilt.processor.internal.ClassNames;
 import dagger.hilt.processor.internal.Components;
-import dagger.hilt.processor.internal.KotlinMetadataUtils;
 import dagger.hilt.processor.internal.ProcessorErrors;
 import dagger.hilt.processor.internal.Processors;
-import dagger.internal.codegen.kotlin.KotlinMetadataUtil;
 import java.util.HashSet;
 import java.util.List;
 import java.util.Optional;
@@ -65,6 +63,7 @@
   private static final ImmutableSet<ClassName> ENTRY_POINT_ANNOTATIONS =
       ImmutableSet.of(
           ClassNames.ENTRY_POINT,
+          ClassNames.EARLY_ENTRY_POINT,
           ClassNames.GENERATED_ENTRY_POINT,
           ClassNames.COMPONENT_ENTRY_POINT);
 
@@ -166,7 +165,10 @@
     // Check that if Dagger needs an instance of the module, Hilt can provide it automatically by
     // calling a visible empty constructor.
     ProcessorErrors.checkState(
-        !daggerRequiresModuleInstance(module) || hasVisibleEmptyConstructor(module),
+        // Skip ApplicationContextModule, since Hilt manages this module internally.
+        ClassNames.APPLICATION_CONTEXT_MODULE.equals(ClassName.get(module))
+        || !Processors.requiresModuleInstance(getElementUtils(), module)
+        || hasVisibleEmptyConstructor(module),
         module,
         "Modules that need to be instantiated by Hilt must have a visible, empty constructor.");
 
@@ -186,13 +188,14 @@
 
     ImmutableList<TypeElement> replacedModules = ImmutableList.of();
     if (Processors.hasAnnotation(module, ClassNames.TEST_INSTALL_IN)) {
-      Optional<TypeElement> originatingTestElement = getOriginatingTestElement(module);
+      Optional<TypeElement> originatingTestElement =
+          Processors.getOriginatingTestElement(module, getElementUtils());
       ProcessorErrors.checkState(
           !originatingTestElement.isPresent(),
           // TODO(b/152801981): this should really error on the annotation value
           module,
           "@TestInstallIn modules cannot be nested in (or originate from) a "
-                + "@HiltAndroidTest-annotated class:  %s",
+              + "@HiltAndroidTest-annotated class:  %s",
           originatingTestElement
               .map(testElement -> testElement.getQualifiedName().toString())
               .orElse(""));
@@ -262,7 +265,10 @@
       // Prevent users from uninstalling test-specific @InstallIn modules.
       ImmutableList<TypeElement> replacedTestSpecificInstallIn =
           replacedModules.stream()
-              .filter(replacedModule -> getOriginatingTestElement(replacedModule).isPresent())
+              .filter(
+                  replacedModule ->
+                      Processors.getOriginatingTestElement(replacedModule, getElementUtils())
+                          .isPresent())
               .collect(toImmutableList());
 
       ProcessorErrors.checkState(
@@ -304,6 +310,28 @@
         element);
     TypeElement entryPoint = asType(element);
 
+    if (entryPointAnnotation.equals(ClassNames.EARLY_ENTRY_POINT)) {
+      ImmutableSet<ClassName> components = Components.getComponents(getElementUtils(), element);
+      ProcessorErrors.checkState(
+          components.equals(ImmutableSet.of(ClassNames.SINGLETON_COMPONENT)),
+          element,
+          "@EarlyEntryPoint can only be installed into the SingletonComponent. Found: %s",
+          components);
+
+      Optional<TypeElement> optionalTestElement =
+          Processors.getOriginatingTestElement(element, getElementUtils());
+      ProcessorErrors.checkState(
+          !optionalTestElement.isPresent(),
+          element,
+          "@EarlyEntryPoint-annotated entry point, %s, cannot be nested in (or originate from) "
+              + "a @HiltAndroidTest-annotated class, %s. This requirement is to avoid confusion "
+              + "with other, test-specific entry points.",
+          asType(element).getQualifiedName().toString(),
+          optionalTestElement
+              .map(testElement -> testElement.getQualifiedName().toString())
+              .orElse(""));
+    }
+
     generateAggregatedDeps(
         entryPointAnnotation.equals(ClassNames.COMPONENT_ENTRY_POINT)
             ? "componentEntryPoints"
@@ -333,7 +361,8 @@
               .generate();
         }
       } else {
-        Optional<ClassName> testName = getOriginatingTestElement(element).map(ClassName::get);
+        Optional<ClassName> testName =
+            Processors.getOriginatingTestElement(element, getElementUtils()).map(ClassName::get);
         new AggregatedDepsGenerator(
                 key, element, testName, components, replacedModules, getProcessingEnv())
             .generate();
@@ -362,25 +391,6 @@
     return Optional.of(getOnlyElement(usedAnnotations));
   }
 
-  private Optional<TypeElement> getOriginatingTestElement(Element element) {
-    TypeElement topLevelType = getOriginatingTopLevelType(element);
-    return Processors.hasAnnotation(topLevelType, ClassNames.HILT_ANDROID_TEST)
-        ? Optional.of(asType(topLevelType))
-        : Optional.empty();
-  }
-
-  private TypeElement getOriginatingTopLevelType(Element element) {
-    TypeElement topLevelType = Processors.getTopLevelType(element);
-    if (Processors.hasAnnotation(topLevelType, ClassNames.ORIGINATING_ELEMENT)) {
-      return getOriginatingTopLevelType(
-          Processors.getAnnotationClassValue(
-              getElementUtils(),
-              Processors.getAnnotationMirror(topLevelType, ClassNames.ORIGINATING_ELEMENT),
-              "topLevelClass"));
-    }
-    return topLevelType;
-  }
-
   private static boolean isValidKind(Element element) {
     // don't go down the rabbit hole of analyzing undefined types. N.B. we don't issue
     // an error here because javac already has and we don't want to spam the user.
@@ -388,7 +398,7 @@
   }
 
   private boolean installInCheckDisabled(Element element) {
-    return DISABLE_MODULES_HAVE_INSTALL_IN_CHECK.get(getProcessingEnv())
+    return isModuleInstallInCheckDisabled(getProcessingEnv())
         || Processors.hasAnnotation(element, ClassNames.DISABLE_INSTALL_IN_CHECK);
   }
 
@@ -435,27 +445,6 @@
         || name.contentEquals("javax.annotation.processing.Generated");
   }
 
-  private static boolean daggerRequiresModuleInstance(TypeElement module) {
-    return !module.getModifiers().contains(ABSTRACT)
-        && !hasOnlyStaticProvides(module)
-        // Skip ApplicationContextModule, since Hilt manages this module internally.
-        && !ClassNames.APPLICATION_CONTEXT_MODULE.equals(ClassName.get(module))
-        // Skip Kotlin object modules since all their provision methods are static
-        && !isKotlinObject(module);
-  }
-
-  private static boolean isKotlinObject(TypeElement type) {
-    KotlinMetadataUtil metadataUtil = KotlinMetadataUtils.getMetadataUtil();
-    return metadataUtil.isObjectClass(type) || metadataUtil.isCompanionObjectClass(type);
-  }
-
-  private static boolean hasOnlyStaticProvides(TypeElement module) {
-    // TODO(erichang): Check for @Produces too when we have a producers story
-    return ElementFilter.methodsIn(module.getEnclosedElements()).stream()
-        .filter(method -> Processors.hasAnnotation(method, ClassNames.PROVIDES))
-        .allMatch(method -> method.getModifiers().contains(STATIC));
-  }
-
   private static boolean hasVisibleEmptyConstructor(TypeElement type) {
     List<ExecutableElement> constructors = ElementFilter.constructorsIn(type.getEnclosedElements());
     return constructors.isEmpty()
diff --git a/java/dagger/hilt/processor/internal/aggregateddeps/BUILD b/java/dagger/hilt/processor/internal/aggregateddeps/BUILD
index ebbc941..5da2241 100644
--- a/java/dagger/hilt/processor/internal/aggregateddeps/BUILD
+++ b/java/dagger/hilt/processor/internal/aggregateddeps/BUILD
@@ -39,14 +39,13 @@
         "AggregatedDepsGenerator.java",
         "AggregatedDepsProcessor.java",
         "PkgPrivateEntryPointGenerator.java",
-        "PkgPrivateMetadata.java",
         "PkgPrivateModuleGenerator.java",
     ],
     deps = [
-        "//:dagger_with_compiler",
-        "//java/dagger/hilt/android/processor/internal/androidentrypoint:compiler_options",
+        ":pkg_private_metadata",
         "//java/dagger/hilt/processor/internal:base_processor",
         "//java/dagger/hilt/processor/internal:classnames",
+        "//java/dagger/hilt/processor/internal:compiler_options",
         "//java/dagger/hilt/processor/internal:components",
         "//java/dagger/hilt/processor/internal:kotlin",
         "//java/dagger/hilt/processor/internal:processor_errors",
@@ -55,10 +54,21 @@
         "//java/dagger/internal/codegen/kotlin",
         "//java/dagger/internal/guava:collect",
         "@google_bazel_common//third_party/java/auto:service",
-        "@google_bazel_common//third_party/java/auto:value",
         "@google_bazel_common//third_party/java/incap",
         "@google_bazel_common//third_party/java/javapoet",
-        "@google_bazel_common//third_party/java/jsr250_annotations",
+        "@maven//:com_google_auto_auto_common",
+    ],
+)
+
+java_library(
+    name = "pkg_private_metadata",
+    srcs = ["PkgPrivateMetadata.java"],
+    deps = [
+        "//java/dagger/hilt/processor/internal:classnames",
+        "//java/dagger/hilt/processor/internal:kotlin",
+        "//java/dagger/hilt/processor/internal:processors",
+        "@google_bazel_common//third_party/java/auto:value",
+        "@google_bazel_common//third_party/java/javapoet",
         "@maven//:com_google_auto_auto_common",
     ],
 )
@@ -66,14 +76,17 @@
 java_library(
     name = "component_dependencies",
     srcs = [
+        "AggregatedDepsMetadata.java",
         "ComponentDependencies.java",
     ],
     deps = [
-        ":processor_lib",
+        ":pkg_private_metadata",
+        "//java/dagger/hilt/processor/internal:aggregated_elements",
         "//java/dagger/hilt/processor/internal:classnames",
         "//java/dagger/hilt/processor/internal:component_descriptor",
-        "//java/dagger/hilt/processor/internal:processor_errors",
         "//java/dagger/hilt/processor/internal:processors",
+        "//java/dagger/hilt/processor/internal/earlyentrypoint:aggregated_early_entry_point_metadata",
+        "//java/dagger/hilt/processor/internal/uninstallmodules:aggregated_uninstall_modules_metadata",
         "//java/dagger/internal/codegen/extension",
         "//java/dagger/internal/guava:base",
         "//java/dagger/internal/guava:collect",
diff --git a/java/dagger/hilt/processor/internal/aggregateddeps/ComponentDependencies.java b/java/dagger/hilt/processor/internal/aggregateddeps/ComponentDependencies.java
index 23ed148..897ffd1 100644
--- a/java/dagger/hilt/processor/internal/aggregateddeps/ComponentDependencies.java
+++ b/java/dagger/hilt/processor/internal/aggregateddeps/ComponentDependencies.java
@@ -16,36 +16,17 @@
 
 package dagger.hilt.processor.internal.aggregateddeps;
 
-import static com.google.common.base.Preconditions.checkNotNull;
 import static com.google.common.base.Preconditions.checkState;
-import static com.google.common.collect.Iterables.getOnlyElement;
-import static dagger.hilt.processor.internal.aggregateddeps.AggregatedDepsGenerator.AGGREGATING_PACKAGE;
-import static dagger.internal.codegen.extension.DaggerStreams.toImmutableList;
 import static dagger.internal.codegen.extension.DaggerStreams.toImmutableSet;
 
 import com.google.auto.value.AutoValue;
-import com.google.common.collect.HashMultimap;
-import com.google.common.collect.ImmutableList;
-import com.google.common.collect.ImmutableMap;
 import com.google.common.collect.ImmutableSet;
 import com.google.common.collect.ImmutableSetMultimap;
-import com.google.common.collect.SetMultimap;
 import com.squareup.javapoet.ClassName;
-import dagger.hilt.processor.internal.AnnotationValues;
-import dagger.hilt.processor.internal.BadInputException;
 import dagger.hilt.processor.internal.ClassNames;
 import dagger.hilt.processor.internal.ComponentDescriptor;
-import dagger.hilt.processor.internal.ProcessorErrors;
-import dagger.hilt.processor.internal.Processors;
-import dagger.hilt.processor.internal.aggregateddeps.ComponentDependencies.AggregatedDepMetadata.DependencyType;
-import java.util.List;
-import java.util.Map;
-import java.util.Optional;
-import javax.lang.model.element.AnnotationMirror;
-import javax.lang.model.element.AnnotationValue;
-import javax.lang.model.element.Element;
-import javax.lang.model.element.ElementKind;
-import javax.lang.model.element.PackageElement;
+import dagger.hilt.processor.internal.earlyentrypoint.AggregatedEarlyEntryPointMetadata;
+import dagger.hilt.processor.internal.uninstallmodules.AggregatedUninstallModulesMetadata;
 import javax.lang.model.element.TypeElement;
 import javax.lang.model.util.Elements;
 
@@ -65,6 +46,23 @@
   /** Returns the component entry point associated with the given a component. */
   public abstract Dependencies componentEntryPoints();
 
+  /** Returns the set of early entry points */
+  public abstract ImmutableSet<ClassName> earlyEntryPoints();
+
+  /** Returns {@code true} if any entry points are annotated with {@code EarlyEntryPoints}. */
+  public boolean hasEarlyEntryPoints() {
+    return !earlyEntryPoints().isEmpty();
+  }
+
+  /**
+   * Returns {@code true} if the test binds or uninstalls test-specific bindings that would prevent
+   * it from sharing components with other test roots.
+   */
+  public final boolean includesTestDeps(ClassName root) {
+    return modules().testDeps().keySet().stream().anyMatch((key) -> key.test().equals(root))
+        || modules().uninstalledTestDeps().containsKey(root);
+  }
+
   @AutoValue.Builder
   abstract static class Builder {
     abstract Dependencies.Builder modulesBuilder();
@@ -73,12 +71,9 @@
 
     abstract Dependencies.Builder componentEntryPointsBuilder();
 
-    abstract ComponentDependencies autoBuild();
+    abstract ImmutableSet.Builder<ClassName> earlyEntryPointsBuilder();
 
-    ComponentDependencies build(Elements elements) {
-      validateModules(modulesBuilder().build(), elements);
-      return autoBuild();
-    }
+    abstract ComponentDependencies build();
   }
 
   /** A key used for grouping a test dependency by both its component and test name. */
@@ -123,6 +118,17 @@
     /** Returns the global uninstalled test deps. */
     abstract ImmutableSet<TypeElement> globalUninstalledTestDeps();
 
+    /** Returns the dependencies to be installed in the global singleton component. */
+    ImmutableSet<TypeElement> getGlobalSingletonDeps() {
+      return ImmutableSet.<TypeElement>builder()
+          .addAll(
+              globalDeps().get(ClassNames.SINGLETON_COMPONENT).stream()
+                  .filter(dep -> !globalUninstalledTestDeps().contains(dep))
+                  .collect(toImmutableSet()))
+          .addAll(globalTestDeps().get(ClassNames.SINGLETON_COMPONENT))
+          .build();
+    }
+
     /** Returns the dependencies to be installed in the given component for the given root. */
     public ImmutableSet<TypeElement> get(ClassName component, ClassName root, boolean isTestRoot) {
       if (!isTestRoot) {
@@ -175,14 +181,10 @@
    */
   public static ComponentDependencies from(
       ImmutableSet<ComponentDescriptor> descriptors, Elements elements) {
-    Map<String, ComponentDescriptor> descriptorLookup = descriptorLookupMap(descriptors);
-    ImmutableList<AggregatedDepMetadata> metadatas =
-        getAggregatedDeps(elements).stream()
-            .map(deps -> AggregatedDepMetadata.create(deps, descriptorLookup, elements))
-            .collect(toImmutableList());
-
+    ImmutableSet<ClassName> componentNames =
+        descriptors.stream().map(ComponentDescriptor::component).collect(toImmutableSet());
     ComponentDependencies.Builder componentDependencies = ComponentDependencies.builder();
-    for (AggregatedDepMetadata metadata : metadatas) {
+    for (AggregatedDepsMetadata metadata : AggregatedDepsMetadata.from(elements)) {
       Dependencies.Builder builder = null;
       switch (metadata.dependencyType()) {
         case MODULE:
@@ -195,265 +197,46 @@
           builder = componentDependencies.componentEntryPointsBuilder();
           break;
       }
-
-      for (ComponentDescriptor componentDescriptor : metadata.componentDescriptors()) {
-        ClassName component = componentDescriptor.component();
+      for (TypeElement componentElement : metadata.componentElements()) {
+        ClassName componentName = ClassName.get(componentElement);
+        checkState(
+            componentNames.contains(componentName), "%s is not a valid Component.", componentName);
         if (metadata.testElement().isPresent()) {
           // In this case the @InstallIn or @TestInstallIn applies to only the given test root.
           ClassName test = ClassName.get(metadata.testElement().get());
-          builder.testDepsBuilder().put(TestDepKey.of(component, test), metadata.dependency());
+          builder.testDepsBuilder().put(TestDepKey.of(componentName, test), metadata.dependency());
           builder.uninstalledTestDepsBuilder().putAll(test, metadata.replacedDependencies());
         } else {
           // In this case the @InstallIn or @TestInstallIn applies to all roots
           if (!metadata.replacedDependencies().isEmpty()) {
             // If there are replacedDependencies() it means this is a @TestInstallIn
-            builder.globalTestDepsBuilder().put(component, metadata.dependency());
+            builder.globalTestDepsBuilder().put(componentName, metadata.dependency());
             builder.globalUninstalledTestDepsBuilder().addAll(metadata.replacedDependencies());
           } else {
-            builder.globalDepsBuilder().put(component, metadata.dependency());
+            builder.globalDepsBuilder().put(componentName, metadata.dependency());
           }
         }
       }
     }
 
-    // Collect all @UninstallModules.
-    // TODO(b/176438516): Filter @UninstallModules at the root.
-    metadatas.stream()
-        .filter(metadata -> metadata.testElement().isPresent())
-        .map(metadata -> metadata.testElement().get())
-        .distinct()
-        .filter(testElement -> Processors.hasAnnotation(testElement, ClassNames.IGNORE_MODULES))
+    AggregatedUninstallModulesMetadata.from(elements)
         .forEach(
-            testElement ->
+            metadata ->
                 componentDependencies
                     .modulesBuilder()
                     .uninstalledTestDepsBuilder()
                     .putAll(
-                        ClassName.get(testElement), getUninstalledModules(testElement, elements)));
+                        ClassName.get(metadata.testElement()),
+                        metadata.uninstallModuleElements().stream()
+                            .map(module -> PkgPrivateMetadata.publicModule(module, elements))
+                            .collect(toImmutableSet())));
 
-    return componentDependencies.build(elements);
-  }
+    AggregatedEarlyEntryPointMetadata.from(elements).stream()
+        .map(AggregatedEarlyEntryPointMetadata::earlyEntryPoint)
+        .map(entryPoint -> PkgPrivateMetadata.publicEarlyEntryPoint(entryPoint, elements))
+        .map(ClassName::get)
+        .forEach(componentDependencies.earlyEntryPointsBuilder()::add);
 
-  private static ImmutableMap<String, ComponentDescriptor> descriptorLookupMap(
-      ImmutableSet<ComponentDescriptor> descriptors) {
-    ImmutableMap.Builder<String, ComponentDescriptor> builder = ImmutableMap.builder();
-    for (ComponentDescriptor descriptor : descriptors) {
-      // This is a temporary hack to map the old ApplicationComponent to the new SingletonComponent.
-      // Technically, this is only needed for backwards compatibility with libraries using the old
-      // processor since new processors should convert to the new SingletonComponent when generating
-      // the metadata class.
-      if (descriptor.component().equals(ClassNames.SINGLETON_COMPONENT)) {
-        builder.put("dagger.hilt.android.components.ApplicationComponent", descriptor);
-      }
-      builder.put(descriptor.component().toString(), descriptor);
-    }
-    return builder.build();
-  }
-
-  // Validate that the @UninstallModules doesn't contain any test modules.
-  private static Dependencies validateModules(Dependencies moduleDeps, Elements elements) {
-    SetMultimap<ClassName, TypeElement> invalidTestModules = HashMultimap.create();
-    moduleDeps.testDeps().entries().stream()
-        .filter(
-            e -> moduleDeps.uninstalledTestDeps().containsEntry(e.getKey().test(), e.getValue()))
-        .forEach(e -> invalidTestModules.put(e.getKey().test(), e.getValue()));
-
-    // Currently we don't have a good way to throw an error for all tests, so we sort (to keep the
-    // error reporting order stable) and then choose the first test.
-    // TODO(bcorso): Consider using ProcessorErrorHandler directly to report all errors at once?
-    Optional<ClassName> invalidTest =
-        invalidTestModules.keySet().stream()
-            .min((test1, test2) -> test1.toString().compareTo(test2.toString()));
-    if (invalidTest.isPresent()) {
-      throw new BadInputException(
-          String.format(
-              "@UninstallModules on test, %s, should not containing test modules, "
-                  + "but found: %s",
-              invalidTest.get(),
-              invalidTestModules.get(invalidTest.get()).stream()
-                  // Sort modules to keep stable error messages.
-                  .sorted((test1, test2) -> test1.toString().compareTo(test2.toString()))
-                  .collect(toImmutableList())),
-          elements.getTypeElement(invalidTest.get().toString()));
-    }
-    return moduleDeps;
-  }
-
-  private static ImmutableSet<TypeElement> getUninstalledModules(
-      TypeElement testElement, Elements elements) {
-    ImmutableList<TypeElement> userUninstallModules =
-        Processors.getAnnotationClassValues(
-            elements,
-            Processors.getAnnotationMirror(testElement, ClassNames.IGNORE_MODULES),
-            "value");
-
-    // For pkg-private modules, find the generated wrapper class and uninstall that instead.
-    return userUninstallModules.stream()
-        .map(uninstallModule -> getPublicDependency(uninstallModule, elements))
-        .collect(toImmutableSet());
-  }
-
-  /** Returns the public Hilt wrapper module, or the module itself if its already public. */
-  private static TypeElement getPublicDependency(TypeElement dependency, Elements elements) {
-    return PkgPrivateMetadata.of(elements, dependency, ClassNames.MODULE)
-        .map(metadata -> elements.getTypeElement(metadata.generatedClassName().toString()))
-        .orElse(dependency);
-  }
-
-  /** Returns the top-level elements of the aggregated deps package. */
-  private static ImmutableList<AnnotationMirror> getAggregatedDeps(Elements elements) {
-    PackageElement packageElement = elements.getPackageElement(AGGREGATING_PACKAGE);
-    checkState(
-        packageElement != null,
-        "Couldn't find package %s. Did you mark your @Module classes with @InstallIn annotations?",
-        AGGREGATING_PACKAGE);
-
-    List<? extends Element> aggregatedDepsElements = packageElement.getEnclosedElements();
-    checkState(
-        !aggregatedDepsElements.isEmpty(),
-        "No dependencies found. Did you mark your @Module classes with @InstallIn annotations?");
-
-    ImmutableList.Builder<AnnotationMirror> builder = ImmutableList.builder();
-    for (Element element : aggregatedDepsElements) {
-      ProcessorErrors.checkState(
-          element.getKind() == ElementKind.CLASS,
-          element,
-          "Only classes may be in package %s. Did you add custom code in the package?",
-          AGGREGATING_PACKAGE);
-
-      AnnotationMirror aggregatedDeps =
-          Processors.getAnnotationMirror(element, ClassNames.AGGREGATED_DEPS);
-      ProcessorErrors.checkState(
-          aggregatedDeps != null,
-          element,
-          "Classes in package %s must be annotated with @AggregatedDeps: %s. Found: %s.",
-          AGGREGATING_PACKAGE,
-          element.getSimpleName(),
-          element.getAnnotationMirrors());
-
-      builder.add(aggregatedDeps);
-    }
-    return builder.build();
-  }
-
-  @AutoValue
-  abstract static class AggregatedDepMetadata {
-    static AggregatedDepMetadata create(
-        AnnotationMirror aggregatedDeps,
-        Map<String, ComponentDescriptor> descriptorLookup,
-        Elements elements) {
-      ImmutableMap<String, AnnotationValue> aggregatedDepsValues =
-          Processors.getAnnotationValues(elements, aggregatedDeps);
-
-      return new AutoValue_ComponentDependencies_AggregatedDepMetadata(
-          getTestElement(aggregatedDepsValues.get("test"), elements),
-          getComponents(aggregatedDepsValues.get("components"), descriptorLookup),
-          getDependencyType(
-              aggregatedDepsValues.get("modules"),
-              aggregatedDepsValues.get("entryPoints"),
-              aggregatedDepsValues.get("componentEntryPoints")),
-          getDependency(
-              aggregatedDepsValues.get("modules"),
-              aggregatedDepsValues.get("entryPoints"),
-              aggregatedDepsValues.get("componentEntryPoints"),
-              elements),
-          getReplacedDependencies(aggregatedDepsValues.get("replaces"), elements));
-    }
-
-    enum DependencyType {
-      MODULE,
-      ENTRY_POINT,
-      COMPONENT_ENTRY_POINT
-    }
-
-    abstract Optional<TypeElement> testElement();
-
-    abstract ImmutableList<ComponentDescriptor> componentDescriptors();
-
-    abstract DependencyType dependencyType();
-
-    abstract TypeElement dependency();
-
-    abstract ImmutableSet<TypeElement> replacedDependencies();
-
-    private static Optional<TypeElement> getTestElement(
-        AnnotationValue testValue, Elements elements) {
-      checkNotNull(testValue);
-      String test = AnnotationValues.getString(testValue);
-      return test.isEmpty() ? Optional.empty() : Optional.of(elements.getTypeElement(test));
-    }
-
-    private static ImmutableList<ComponentDescriptor> getComponents(
-        AnnotationValue componentsValue, Map<String, ComponentDescriptor> descriptorLookup) {
-      checkNotNull(componentsValue);
-      ImmutableList<String> componentNames =
-          AnnotationValues.getAnnotationValues(componentsValue).stream()
-              .map(AnnotationValues::getString)
-              .collect(toImmutableList());
-
-      checkState(!componentNames.isEmpty());
-      ImmutableList.Builder<ComponentDescriptor> components = ImmutableList.builder();
-      for (String componentName : componentNames) {
-        checkState(
-            descriptorLookup.containsKey(componentName),
-            "%s is not a valid Component. Did you add or remove code in package %s?",
-            componentName,
-            AGGREGATING_PACKAGE);
-        components.add(descriptorLookup.get(componentName));
-      }
-      return components.build();
-    }
-
-    private static DependencyType getDependencyType(
-        AnnotationValue modulesValue,
-        AnnotationValue entryPointsValue,
-        AnnotationValue componentEntryPointsValue) {
-      checkNotNull(modulesValue);
-      checkNotNull(entryPointsValue);
-      checkNotNull(componentEntryPointsValue);
-
-      ImmutableSet.Builder<DependencyType> dependencyTypes = ImmutableSet.builder();
-      if (!AnnotationValues.getAnnotationValues(modulesValue).isEmpty()) {
-        dependencyTypes.add(DependencyType.MODULE);
-      }
-      if (!AnnotationValues.getAnnotationValues(entryPointsValue).isEmpty()) {
-        dependencyTypes.add(DependencyType.ENTRY_POINT);
-      }
-      if (!AnnotationValues.getAnnotationValues(componentEntryPointsValue).isEmpty()) {
-        dependencyTypes.add(DependencyType.COMPONENT_ENTRY_POINT);
-      }
-      return getOnlyElement(dependencyTypes.build());
-    }
-
-    private static TypeElement getDependency(
-        AnnotationValue modulesValue,
-        AnnotationValue entryPointsValue,
-        AnnotationValue componentEntryPointsValue,
-        Elements elements) {
-      checkNotNull(modulesValue);
-      checkNotNull(entryPointsValue);
-      checkNotNull(componentEntryPointsValue);
-
-      return elements.getTypeElement(
-          AnnotationValues.getString(
-              getOnlyElement(
-                  ImmutableList.<AnnotationValue>builder()
-                      .addAll(AnnotationValues.getAnnotationValues(modulesValue))
-                      .addAll(AnnotationValues.getAnnotationValues(entryPointsValue))
-                      .addAll(AnnotationValues.getAnnotationValues(componentEntryPointsValue))
-                      .build())));
-    }
-
-    private static ImmutableSet<TypeElement> getReplacedDependencies(
-        AnnotationValue replacedDependenciesValue, Elements elements) {
-      // Allow null values to support libraries using a Hilt version before @TestInstallIn was added
-      return replacedDependenciesValue == null
-          ? ImmutableSet.of()
-          : AnnotationValues.getAnnotationValues(replacedDependenciesValue).stream()
-              .map(AnnotationValues::getString)
-              .map(elements::getTypeElement)
-              .map(replacedDep -> getPublicDependency(replacedDep, elements))
-              .collect(toImmutableSet());
-    }
+    return componentDependencies.build();
   }
 }
diff --git a/java/dagger/hilt/processor/internal/aggregateddeps/PkgPrivateMetadata.java b/java/dagger/hilt/processor/internal/aggregateddeps/PkgPrivateMetadata.java
index 66f1751..ff344a6 100644
--- a/java/dagger/hilt/processor/internal/aggregateddeps/PkgPrivateMetadata.java
+++ b/java/dagger/hilt/processor/internal/aggregateddeps/PkgPrivateMetadata.java
@@ -24,16 +24,35 @@
 import com.squareup.javapoet.ClassName;
 import com.squareup.javapoet.TypeName;
 import dagger.hilt.processor.internal.ClassNames;
+import dagger.hilt.processor.internal.KotlinMetadataUtils;
 import dagger.hilt.processor.internal.Processors;
 import java.util.Optional;
 import javax.lang.model.element.AnnotationMirror;
-import javax.lang.model.element.Element;
 import javax.lang.model.element.TypeElement;
 import javax.lang.model.util.Elements;
 
 /** PkgPrivateModuleMetadata contains a set of utilities for processing package private modules. */
 @AutoValue
-abstract class PkgPrivateMetadata {
+public abstract class PkgPrivateMetadata {
+  /** Returns the public Hilt wrapped type or the type itself if it is already public. */
+  public static TypeElement publicModule(TypeElement element, Elements elements) {
+    return publicDep(element, elements, ClassNames.MODULE);
+  }
+
+  /** Returns the public Hilt wrapped type or the type itself if it is already public. */
+  public static TypeElement publicEarlyEntryPoint(TypeElement element, Elements elements) {
+    return publicDep(element, elements, ClassNames.EARLY_ENTRY_POINT);
+  }
+
+  private static TypeElement publicDep(
+      TypeElement element, Elements elements, ClassName annotation) {
+    return of(elements, element, annotation)
+        .map(PkgPrivateMetadata::generatedClassName)
+        .map(ClassName::canonicalName)
+        .map(elements::getTypeElement)
+        .orElse(element);
+  }
+
   private static final String PREFIX = "HiltWrapper_";
 
   /** Returns the base class name of the elemenet. */
@@ -63,9 +82,11 @@
    * Returns an Optional PkgPrivateMetadata requiring Hilt processing, otherwise returns an empty
    * Optional.
    */
-  static Optional<PkgPrivateMetadata> of(Elements elements, Element element, ClassName annotation) {
+  static Optional<PkgPrivateMetadata> of(
+      Elements elements, TypeElement element, ClassName annotation) {
     // If this is a public element no wrapping is needed
-    if (effectiveVisibilityOfElement(element) == Visibility.PUBLIC) {
+    if (effectiveVisibilityOfElement(element) == Visibility.PUBLIC
+        && !KotlinMetadataUtils.getMetadataUtil().isVisibilityInternal(element)) {
       return Optional.empty();
     }
 
diff --git a/java/dagger/hilt/processor/internal/aliasof/AliasOfProcessor.java b/java/dagger/hilt/processor/internal/aliasof/AliasOfProcessor.java
index 6efd643..02e7f5d 100644
--- a/java/dagger/hilt/processor/internal/aliasof/AliasOfProcessor.java
+++ b/java/dagger/hilt/processor/internal/aliasof/AliasOfProcessor.java
@@ -16,6 +16,7 @@
 
 package dagger.hilt.processor.internal.aliasof;
 
+import static com.google.auto.common.MoreElements.asType;
 import static net.ltgt.gradle.incap.IncrementalAnnotationProcessorType.ISOLATING;
 
 import com.google.auto.service.AutoService;
@@ -48,13 +49,14 @@
         "%s should only be used on scopes." + " However, it was found annotating %s",
         annotation,
         element);
+
     AnnotationMirror annotationMirror =
         Processors.getAnnotationMirror(element, ClassNames.ALIAS_OF);
 
-    Element defineComponentScope =
+    TypeElement defineComponentScope =
         Processors.getAnnotationClassValue(getElementUtils(), annotationMirror, "value");
 
-    new AliasOfPropagatedDataGenerator(getProcessingEnv(), element, defineComponentScope)
+    new AliasOfPropagatedDataGenerator(getProcessingEnv(), asType(element), defineComponentScope)
         .generate();
   }
 }
diff --git a/java/dagger/hilt/processor/internal/aliasof/AliasOfPropagatedDataGenerator.java b/java/dagger/hilt/processor/internal/aliasof/AliasOfPropagatedDataGenerator.java
index 75c4c15..1d7edf2 100644
--- a/java/dagger/hilt/processor/internal/aliasof/AliasOfPropagatedDataGenerator.java
+++ b/java/dagger/hilt/processor/internal/aliasof/AliasOfPropagatedDataGenerator.java
@@ -17,43 +17,37 @@
 package dagger.hilt.processor.internal.aliasof;
 
 import com.squareup.javapoet.AnnotationSpec;
-import com.squareup.javapoet.JavaFile;
-import com.squareup.javapoet.TypeSpec;
 import dagger.hilt.processor.internal.ClassNames;
 import dagger.hilt.processor.internal.Processors;
 import java.io.IOException;
 import javax.annotation.processing.ProcessingEnvironment;
-import javax.lang.model.element.Element;
+import javax.lang.model.element.TypeElement;
 
 /** Generates resource files for {@link dagger.hilt.migration.AliasOf}. */
 final class AliasOfPropagatedDataGenerator {
 
   private final ProcessingEnvironment processingEnv;
-  private final Element aliasScope;
-  private final Element defineComponentScope;
+  private final TypeElement aliasScope;
+  private final TypeElement defineComponentScope;
 
   AliasOfPropagatedDataGenerator(
-      ProcessingEnvironment processingEnv, Element aliasScope, Element defineComponentScope) {
+      ProcessingEnvironment processingEnv,
+      TypeElement aliasScope,
+      TypeElement defineComponentScope) {
     this.processingEnv = processingEnv;
     this.aliasScope = aliasScope;
     this.defineComponentScope = defineComponentScope;
   }
 
   void generate() throws IOException {
-    TypeSpec.Builder generator =
-        TypeSpec.classBuilder(Processors.getFullEnclosedName(aliasScope))
-            .addOriginatingElement(aliasScope)
-            .addAnnotation(
-                AnnotationSpec.builder(ClassNames.ALIAS_OF_PROPAGATED_DATA)
+    Processors.generateAggregatingClass(
+        ClassNames.ALIAS_OF_PROPAGATED_DATA_PACKAGE,
+        AnnotationSpec.builder(ClassNames.ALIAS_OF_PROPAGATED_DATA)
                     .addMember("defineComponentScope", "$T.class", defineComponentScope)
                     .addMember("alias", "$T.class", aliasScope)
-                    .build())
-            .addJavadoc("Generated class for aggregating scope aliases. \n");
-
-    Processors.addGeneratedAnnotation(generator, processingEnv, getClass());
-
-    JavaFile.builder(AliasOfs.AGGREGATING_PACKAGE, generator.build())
-        .build()
-        .writeTo(processingEnv.getFiler());
+                    .build(),
+        aliasScope,
+        getClass(),
+        processingEnv);
   }
 }
diff --git a/java/dagger/hilt/processor/internal/aliasof/AliasOfPropagatedDataMetadata.java b/java/dagger/hilt/processor/internal/aliasof/AliasOfPropagatedDataMetadata.java
new file mode 100644
index 0000000..30a2c70
--- /dev/null
+++ b/java/dagger/hilt/processor/internal/aliasof/AliasOfPropagatedDataMetadata.java
@@ -0,0 +1,65 @@
+/*
+ * Copyright (C) 2021 The Dagger Authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package dagger.hilt.processor.internal.aliasof;
+
+import static dagger.internal.codegen.extension.DaggerStreams.toImmutableSet;
+
+import com.google.auto.value.AutoValue;
+import com.google.common.collect.ImmutableMap;
+import com.google.common.collect.ImmutableSet;
+import dagger.hilt.processor.internal.AggregatedElements;
+import dagger.hilt.processor.internal.AnnotationValues;
+import dagger.hilt.processor.internal.ClassNames;
+import dagger.hilt.processor.internal.Processors;
+import javax.lang.model.element.AnnotationMirror;
+import javax.lang.model.element.AnnotationValue;
+import javax.lang.model.element.TypeElement;
+import javax.lang.model.util.Elements;
+
+/**
+ * A class that represents the values stored in an {@link
+ * dagger.hilt.internal.aliasof.AliasOfPropagatedData} annotation.
+ */
+@AutoValue
+abstract class AliasOfPropagatedDataMetadata {
+
+  abstract TypeElement defineComponentScopeElement();
+
+  abstract TypeElement aliasElement();
+
+  static ImmutableSet<AliasOfPropagatedDataMetadata> from(Elements elements) {
+    return AggregatedElements.from(
+            ClassNames.ALIAS_OF_PROPAGATED_DATA_PACKAGE,
+            ClassNames.ALIAS_OF_PROPAGATED_DATA,
+            elements)
+        .stream()
+        .map(aggregatedElement -> create(aggregatedElement, elements))
+        .collect(toImmutableSet());
+  }
+
+  private static AliasOfPropagatedDataMetadata create(TypeElement element, Elements elements) {
+    AnnotationMirror annotationMirror =
+        Processors.getAnnotationMirror(element, ClassNames.ALIAS_OF_PROPAGATED_DATA);
+
+    ImmutableMap<String, AnnotationValue> values =
+        Processors.getAnnotationValues(elements, annotationMirror);
+
+    return new AutoValue_AliasOfPropagatedDataMetadata(
+        AnnotationValues.getTypeElement(values.get("defineComponentScope")),
+        AnnotationValues.getTypeElement(values.get("alias")));
+  }
+}
diff --git a/java/dagger/hilt/processor/internal/aliasof/AliasOfs.java b/java/dagger/hilt/processor/internal/aliasof/AliasOfs.java
index 930a72e..18951bd 100644
--- a/java/dagger/hilt/processor/internal/aliasof/AliasOfs.java
+++ b/java/dagger/hilt/processor/internal/aliasof/AliasOfs.java
@@ -16,23 +16,11 @@
 
 package dagger.hilt.processor.internal.aliasof;
 
-import static com.google.common.base.Suppliers.memoize;
 
-import com.google.common.base.Preconditions;
-import com.google.common.base.Supplier;
-import com.google.common.collect.ImmutableMultimap;
 import com.google.common.collect.ImmutableSet;
+import com.google.common.collect.ImmutableSetMultimap;
 import com.squareup.javapoet.ClassName;
-import dagger.hilt.processor.internal.ClassNames;
 import dagger.hilt.processor.internal.ProcessorErrors;
-import dagger.hilt.processor.internal.Processors;
-import java.util.List;
-import javax.annotation.processing.ProcessingEnvironment;
-import javax.lang.model.element.AnnotationMirror;
-import javax.lang.model.element.Element;
-import javax.lang.model.element.ElementKind;
-import javax.lang.model.element.PackageElement;
-import javax.lang.model.element.TypeElement;
 import javax.lang.model.util.Elements;
 
 /**
@@ -40,69 +28,33 @@
  * to scopes they are alias of.
  */
 public final class AliasOfs {
-  static final String AGGREGATING_PACKAGE = AliasOfs.class.getPackage().getName() + ".codegen";
+  public static AliasOfs create(Elements elements, ImmutableSet<ClassName> defineComponentScopes) {
+    ImmutableSetMultimap.Builder<ClassName, ClassName> builder = ImmutableSetMultimap.builder();
+    AliasOfPropagatedDataMetadata.from(elements)
+        .forEach(
+            metadata -> {
+              ClassName defineComponentScopeName =
+                  ClassName.get(metadata.defineComponentScopeElement());
+              ClassName aliasScopeName = ClassName.get(metadata.aliasElement());
+              ProcessorErrors.checkState(
+                  defineComponentScopes.contains(defineComponentScopeName),
+                  metadata.aliasElement(),
+                  "The scope %s cannot be an alias for %s. You can only have aliases of a scope"
+                      + " defined directly on a @DefineComponent type.",
+                  aliasScopeName,
+                  defineComponentScopeName);
+              builder.put(defineComponentScopeName, aliasScopeName);
+            });
+    return new AliasOfs(builder.build());
+  }
 
-  private final ProcessingEnvironment processingEnvironment;
-  private final ImmutableSet<ClassName> defineComponentScopes;
-  private final Supplier<ImmutableMultimap<ClassName, ClassName>> aliases =
-      memoize(() -> getAliases());
+  private final ImmutableSetMultimap<ClassName, ClassName> defineComponentScopeToAliases;
 
-  public AliasOfs(
-      ProcessingEnvironment processingEnvironment, ImmutableSet<ClassName> defineComponentScopes) {
-    this.defineComponentScopes = defineComponentScopes;
-    this.processingEnvironment = processingEnvironment;
+  private AliasOfs(ImmutableSetMultimap<ClassName, ClassName> defineComponentScopeToAliases) {
+    this.defineComponentScopeToAliases = defineComponentScopeToAliases;
   }
 
   public ImmutableSet<ClassName> getAliasesFor(ClassName defineComponentScope) {
-    return ImmutableSet.copyOf(aliases.get().get(defineComponentScope));
-  }
-
-  private ImmutableMultimap<ClassName, ClassName> getAliases() {
-    Elements elements = processingEnvironment.getElementUtils();
-    PackageElement packageElement = elements.getPackageElement(AGGREGATING_PACKAGE);
-    if (packageElement == null) {
-      return ImmutableMultimap.of();
-    }
-    List<? extends Element> scopeAliasElements = packageElement.getEnclosedElements();
-    Preconditions.checkState(
-        !scopeAliasElements.isEmpty(), "No scope aliases Found in package %s.", packageElement);
-
-    ImmutableMultimap.Builder<ClassName, ClassName> builder = ImmutableMultimap.builder();
-    for (Element element : scopeAliasElements) {
-      ProcessorErrors.checkState(
-          element.getKind() == ElementKind.CLASS,
-          element,
-          "Only classes may be in package %s. Did you add custom code in the package?",
-          packageElement);
-
-      AnnotationMirror annotationMirror =
-          Processors.getAnnotationMirror(element, ClassNames.ALIAS_OF_PROPAGATED_DATA);
-
-      ProcessorErrors.checkState(
-          annotationMirror != null,
-          element,
-          "Classes in package %s must be annotated with @%s: %s."
-              + " Found: %s. Files in this package are generated, did you add custom code in the"
-              + " package? ",
-          packageElement,
-          ClassNames.ALIAS_OF_PROPAGATED_DATA,
-          element.getSimpleName(),
-          element.getAnnotationMirrors());
-
-      TypeElement defineComponentScope =
-          Processors.getAnnotationClassValue(elements, annotationMirror, "defineComponentScope");
-      TypeElement alias = Processors.getAnnotationClassValue(elements, annotationMirror, "alias");
-
-      Preconditions.checkState(
-          defineComponentScopes.contains(ClassName.get(defineComponentScope)),
-          "The scope %s cannot be an alias for %s. You can only have aliases of a scope defined"
-              + " directly on a @DefineComponent type.",
-          ClassName.get(alias),
-          ClassName.get(defineComponentScope));
-
-      builder.put(ClassName.get(defineComponentScope), ClassName.get(alias));
-    }
-
-    return builder.build();
+    return defineComponentScopeToAliases.get(defineComponentScope);
   }
 }
diff --git a/java/dagger/hilt/processor/internal/aliasof/BUILD b/java/dagger/hilt/processor/internal/aliasof/BUILD
index d3f90f2..ffd0c9a 100644
--- a/java/dagger/hilt/processor/internal/aliasof/BUILD
+++ b/java/dagger/hilt/processor/internal/aliasof/BUILD
@@ -31,7 +31,6 @@
         "AliasOfPropagatedDataGenerator.java",
     ],
     deps = [
-        ":alias_ofs",
         "//java/dagger/hilt/processor/internal:base_processor",
         "//java/dagger/hilt/processor/internal:classnames",
         "//java/dagger/hilt/processor/internal:processor_errors",
@@ -40,20 +39,24 @@
         "@google_bazel_common//third_party/java/auto:service",
         "@google_bazel_common//third_party/java/incap",
         "@google_bazel_common//third_party/java/javapoet",
+        "@maven//:com_google_auto_auto_common",
     ],
 )
 
 java_library(
     name = "alias_ofs",
     srcs = [
+        "AliasOfPropagatedDataMetadata.java",
         "AliasOfs.java",
     ],
     deps = [
+        "//java/dagger/hilt/processor/internal:aggregated_elements",
         "//java/dagger/hilt/processor/internal:classnames",
         "//java/dagger/hilt/processor/internal:processor_errors",
         "//java/dagger/hilt/processor/internal:processors",
-        "//java/dagger/internal/guava:base",
+        "//java/dagger/internal/codegen/extension",
         "//java/dagger/internal/guava:collect",
+        "@google_bazel_common//third_party/java/auto:value",
         "@google_bazel_common//third_party/java/javapoet",
     ],
 )
diff --git a/java/dagger/hilt/processor/internal/definecomponent/BUILD b/java/dagger/hilt/processor/internal/definecomponent/BUILD
index 7edec1c..43f4dfd 100644
--- a/java/dagger/hilt/processor/internal/definecomponent/BUILD
+++ b/java/dagger/hilt/processor/internal/definecomponent/BUILD
@@ -45,18 +45,18 @@
     name = "define_components",
     srcs = [
         "DefineComponentBuilderMetadatas.java",
+        "DefineComponentClassesMetadata.java",
         "DefineComponentMetadatas.java",
         "DefineComponents.java",
     ],
     deps = [
+        "//java/dagger/hilt/processor/internal:aggregated_elements",
         "//java/dagger/hilt/processor/internal:classnames",
         "//java/dagger/hilt/processor/internal:component_descriptor",
         "//java/dagger/hilt/processor/internal:processor_errors",
         "//java/dagger/hilt/processor/internal:processors",
         "//java/dagger/internal/codegen/extension",
-        "//java/dagger/internal/guava:base",
         "//java/dagger/internal/guava:collect",
-        "//java/dagger/internal/guava:graph",
         "@google_bazel_common//third_party/java/auto:value",
         "@google_bazel_common//third_party/java/javapoet",
         "@maven//:com_google_auto_auto_common",
diff --git a/java/dagger/hilt/processor/internal/definecomponent/DefineComponentClassesMetadata.java b/java/dagger/hilt/processor/internal/definecomponent/DefineComponentClassesMetadata.java
new file mode 100644
index 0000000..36ac28f
--- /dev/null
+++ b/java/dagger/hilt/processor/internal/definecomponent/DefineComponentClassesMetadata.java
@@ -0,0 +1,97 @@
+/*
+ * Copyright (C) 2021 The Dagger Authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package dagger.hilt.processor.internal.definecomponent;
+
+import static dagger.internal.codegen.extension.DaggerStreams.toImmutableSet;
+
+import com.google.auto.value.AutoValue;
+import com.google.common.collect.ImmutableMap;
+import com.google.common.collect.ImmutableSet;
+import dagger.hilt.processor.internal.AggregatedElements;
+import dagger.hilt.processor.internal.AnnotationValues;
+import dagger.hilt.processor.internal.ClassNames;
+import dagger.hilt.processor.internal.ProcessorErrors;
+import dagger.hilt.processor.internal.Processors;
+import javax.lang.model.element.AnnotationMirror;
+import javax.lang.model.element.AnnotationValue;
+import javax.lang.model.element.TypeElement;
+import javax.lang.model.util.Elements;
+
+/**
+ * A class that represents the values stored in an {@link
+ * dagger.hilt.internal.definecomponent.DefineComponentClasses} annotation.
+ */
+@AutoValue
+abstract class DefineComponentClassesMetadata {
+
+  /**
+   * Returns the element annotated with {@code dagger.hilt.internal.definecomponent.DefineComponent}
+   * or {@code dagger.hilt.internal.definecomponent.DefineComponent.Builder}.
+   */
+  abstract TypeElement element();
+
+  /** Returns {@code true} if this element represents a component. */
+  abstract boolean isComponent();
+
+  /** Returns {@code true} if this element represents a component builder. */
+  boolean isComponentBuilder() {
+    return !isComponent();
+  }
+
+  static ImmutableSet<DefineComponentClassesMetadata> from(Elements elements) {
+    return AggregatedElements.from(
+            ClassNames.DEFINE_COMPONENT_CLASSES_PACKAGE,
+            ClassNames.DEFINE_COMPONENT_CLASSES,
+            elements)
+        .stream()
+        .map(aggregatedElement -> create(aggregatedElement, elements))
+        .collect(toImmutableSet());
+  }
+
+  private static DefineComponentClassesMetadata create(TypeElement element, Elements elements) {
+    AnnotationMirror annotationMirror =
+        Processors.getAnnotationMirror(element, ClassNames.DEFINE_COMPONENT_CLASSES);
+
+    ImmutableMap<String, AnnotationValue> values =
+        Processors.getAnnotationValues(elements, annotationMirror);
+
+    String componentName = AnnotationValues.getString(values.get("component"));
+    String builderName = AnnotationValues.getString(values.get("builder"));
+
+    ProcessorErrors.checkState(
+        !(componentName.isEmpty() && builderName.isEmpty()),
+        element,
+        "@DefineComponentClasses missing both `component` and `builder` members.");
+
+    ProcessorErrors.checkState(
+        componentName.isEmpty() || builderName.isEmpty(),
+        element,
+        "@DefineComponentClasses should not include both `component` and `builder` members.");
+
+    boolean isComponent = !componentName.isEmpty();
+    String componentOrBuilderName = isComponent ? componentName : builderName;
+    TypeElement componentOrBuilderElement = elements.getTypeElement(componentOrBuilderName);
+    ProcessorErrors.checkState(
+        componentOrBuilderElement != null,
+        componentOrBuilderElement,
+        "%s.%s(), has invalid value: `%s`.",
+        ClassNames.DEFINE_COMPONENT_CLASSES.simpleName(),
+        isComponent ? "component" : "builder",
+        componentOrBuilderName);
+    return new AutoValue_DefineComponentClassesMetadata(componentOrBuilderElement, isComponent);
+  }
+}
diff --git a/java/dagger/hilt/processor/internal/definecomponent/DefineComponentMetadatas.java b/java/dagger/hilt/processor/internal/definecomponent/DefineComponentMetadatas.java
index aa69e40..60864c2 100644
--- a/java/dagger/hilt/processor/internal/definecomponent/DefineComponentMetadatas.java
+++ b/java/dagger/hilt/processor/internal/definecomponent/DefineComponentMetadatas.java
@@ -163,15 +163,25 @@
             ? Optional.empty()
             : Optional.of(get(parent, childPath));
 
+    ClassName componentClassName = ClassName.get(component);
+
     ProcessorErrors.checkState(
         parentComponent.isPresent()
-            || ClassName.get(component).equals(ClassNames.SINGLETON_COMPONENT),
+            || componentClassName.equals(ClassNames.SINGLETON_COMPONENT),
         component,
         "@DefineComponent %s is missing a parent declaration.\n"
             + "Please declare the parent, for example: @DefineComponent(parent ="
             + " SingletonComponent.class)",
         component);
 
+    ProcessorErrors.checkState(
+        componentClassName.equals(ClassNames.SINGLETON_COMPONENT)
+        || !componentClassName.simpleName().equals(ClassNames.SINGLETON_COMPONENT.simpleName()),
+        component,
+        "Cannot have a component with the same simple name as the reserved %s: %s",
+        ClassNames.SINGLETON_COMPONENT.simpleName(),
+        componentClassName);
+
     return new AutoValue_DefineComponentMetadatas_DefineComponentMetadata(
         component, scopes, parentComponent);
   }
diff --git a/java/dagger/hilt/processor/internal/definecomponent/DefineComponentProcessor.java b/java/dagger/hilt/processor/internal/definecomponent/DefineComponentProcessor.java
index e0bfca9..f7e54a0 100644
--- a/java/dagger/hilt/processor/internal/definecomponent/DefineComponentProcessor.java
+++ b/java/dagger/hilt/processor/internal/definecomponent/DefineComponentProcessor.java
@@ -22,8 +22,6 @@
 import com.google.common.collect.ImmutableSet;
 import com.squareup.javapoet.AnnotationSpec;
 import com.squareup.javapoet.ClassName;
-import com.squareup.javapoet.JavaFile;
-import com.squareup.javapoet.TypeSpec;
 import dagger.hilt.processor.internal.BaseProcessor;
 import dagger.hilt.processor.internal.ClassNames;
 import dagger.hilt.processor.internal.Processors;
@@ -70,18 +68,13 @@
   }
 
   private void generateFile(String member, TypeElement typeElement) throws IOException {
-    TypeSpec.Builder builder =
-        TypeSpec.interfaceBuilder(Processors.getFullEnclosedName(typeElement))
-            .addOriginatingElement(typeElement)
-            .addAnnotation(
-                AnnotationSpec.builder(ClassNames.DEFINE_COMPONENT_CLASSES)
-                    .addMember(member, "$S", typeElement.getQualifiedName())
-                    .build());
-
-    Processors.addGeneratedAnnotation(builder, processingEnv, getClass());
-
-    JavaFile.builder(DefineComponents.AGGREGATING_PACKAGE, builder.build())
-        .build()
-        .writeTo(processingEnv.getFiler());
+    Processors.generateAggregatingClass(
+        ClassNames.DEFINE_COMPONENT_CLASSES_PACKAGE,
+        AnnotationSpec.builder(ClassNames.DEFINE_COMPONENT_CLASSES)
+            .addMember(member, "$S", typeElement.getQualifiedName())
+            .build(),
+        typeElement,
+        getClass(),
+        getProcessingEnv());
   }
 }
diff --git a/java/dagger/hilt/processor/internal/definecomponent/DefineComponents.java b/java/dagger/hilt/processor/internal/definecomponent/DefineComponents.java
index 0b33011..efa5011 100644
--- a/java/dagger/hilt/processor/internal/definecomponent/DefineComponents.java
+++ b/java/dagger/hilt/processor/internal/definecomponent/DefineComponents.java
@@ -16,32 +16,22 @@
 
 package dagger.hilt.processor.internal.definecomponent;
 
-import static com.google.auto.common.AnnotationMirrors.getAnnotationElementAndValue;
 import static dagger.internal.codegen.extension.DaggerStreams.toImmutableList;
 import static dagger.internal.codegen.extension.DaggerStreams.toImmutableSet;
 
-import com.google.auto.common.MoreElements;
-import com.google.auto.value.AutoValue;
 import com.google.common.collect.ArrayListMultimap;
-import com.google.common.collect.ImmutableList;
+import com.google.common.collect.ImmutableSet;
 import com.google.common.collect.ListMultimap;
 import com.squareup.javapoet.ClassName;
-import dagger.hilt.processor.internal.AnnotationValues;
 import dagger.hilt.processor.internal.ClassNames;
 import dagger.hilt.processor.internal.ComponentDescriptor;
-import dagger.hilt.processor.internal.ComponentTree;
 import dagger.hilt.processor.internal.ProcessorErrors;
-import dagger.hilt.processor.internal.Processors;
 import dagger.hilt.processor.internal.definecomponent.DefineComponentBuilderMetadatas.DefineComponentBuilderMetadata;
 import dagger.hilt.processor.internal.definecomponent.DefineComponentMetadatas.DefineComponentMetadata;
 import java.util.HashMap;
 import java.util.LinkedHashMap;
 import java.util.Map;
-import java.util.Optional;
-import javax.lang.model.element.AnnotationMirror;
-import javax.lang.model.element.AnnotationValue;
 import javax.lang.model.element.Element;
-import javax.lang.model.element.PackageElement;
 import javax.lang.model.element.TypeElement;
 import javax.lang.model.util.Elements;
 
@@ -50,8 +40,6 @@
  * DefineComponentBuilderMetadata}.
  */
 public final class DefineComponents {
-  static final String AGGREGATING_PACKAGE =
-      DefineComponents.class.getPackage().getName() + ".codegen";
 
   public static DefineComponents create() {
     return new DefineComponents();
@@ -89,14 +77,28 @@
     return builder.build();
   }
 
-  /** Returns the {@link ComponentTree} from the aggregated {@link ComponentDescriptor}s. */
-  public ComponentTree getComponentTree(Elements elements) {
-    AggregatedMetadata aggregatedMetadata =
-        AggregatedMetadata.from(elements, componentMetadatas, componentBuilderMetadatas);
+  /** Returns the set of aggregated {@link ComponentDescriptor}s. */
+  public ImmutableSet<ComponentDescriptor> getComponentDescriptors(Elements elements) {
+    ImmutableSet<DefineComponentClassesMetadata> aggregatedMetadatas =
+        DefineComponentClassesMetadata.from(elements);
+
+    ImmutableSet<DefineComponentMetadata> components =
+        aggregatedMetadatas.stream()
+            .filter(DefineComponentClassesMetadata::isComponent)
+            .map(DefineComponentClassesMetadata::element)
+            .map(componentMetadatas::get)
+            .collect(toImmutableSet());
+
+    ImmutableSet<DefineComponentBuilderMetadata> builders =
+        aggregatedMetadatas.stream()
+            .filter(DefineComponentClassesMetadata::isComponentBuilder)
+            .map(DefineComponentClassesMetadata::element)
+            .map(componentBuilderMetadatas::get)
+            .collect(toImmutableSet());
+
     ListMultimap<DefineComponentMetadata, DefineComponentBuilderMetadata> builderMultimap =
         ArrayListMultimap.create();
-    aggregatedMetadata.builders()
-        .forEach(builder -> builderMultimap.put(builder.componentMetadata(), builder));
+    builders.forEach(builder -> builderMultimap.put(builder.componentMetadata(), builder));
 
     // Check that there are not multiple builders per component
     for (DefineComponentMetadata componentMetadata : builderMultimap.keySet()) {
@@ -119,10 +121,9 @@
     Map<DefineComponentMetadata, DefineComponentBuilderMetadata> builderMap = new LinkedHashMap<>();
     builderMultimap.entries().forEach(e -> builderMap.put(e.getKey(), e.getValue()));
 
-
-    return ComponentTree.from(aggregatedMetadata.components().stream()
+    return components.stream()
         .map(componentMetadata -> toComponentDescriptor(componentMetadata, builderMap))
-        .collect(toImmutableSet()));
+        .collect(toImmutableSet());
   }
 
   private static ComponentDescriptor toComponentDescriptor(
@@ -146,80 +147,4 @@
 
     return builder.build();
   }
-
-  @AutoValue
-  abstract static class AggregatedMetadata {
-    /** Returns the aggregated metadata for {@link DefineComponentClasses#component()}. */
-    abstract ImmutableList<DefineComponentMetadata> components();
-
-    /** Returns the aggregated metadata for {@link DefineComponentClasses#builder()}. */
-    abstract ImmutableList<DefineComponentBuilderMetadata> builders();
-
-    static AggregatedMetadata from(
-        Elements elements,
-        DefineComponentMetadatas componentMetadatas,
-        DefineComponentBuilderMetadatas componentBuilderMetadatas) {
-      PackageElement packageElement = elements.getPackageElement(AGGREGATING_PACKAGE);
-
-      if (packageElement == null) {
-        return new AutoValue_DefineComponents_AggregatedMetadata(
-            ImmutableList.of(), ImmutableList.of());
-      }
-
-      ImmutableList.Builder<DefineComponentMetadata> components = ImmutableList.builder();
-      ImmutableList.Builder<DefineComponentBuilderMetadata> builders = ImmutableList.builder();
-      for (Element element : packageElement.getEnclosedElements()) {
-        ProcessorErrors.checkState(
-            MoreElements.isType(element),
-            element,
-            "Only types may be in package %s. Did you add custom code in the package?",
-            packageElement);
-
-        TypeElement typeElement = MoreElements.asType(element);
-        ProcessorErrors.checkState(
-            Processors.hasAnnotation(typeElement, ClassNames.DEFINE_COMPONENT_CLASSES),
-            typeElement,
-            "Class, %s, must be annotated with @%s. Found: %s.",
-            typeElement,
-            ClassNames.DEFINE_COMPONENT_CLASSES.simpleName(),
-            typeElement.getAnnotationMirrors());
-
-        Optional<TypeElement> component = defineComponentClass(elements, typeElement, "component");
-        Optional<TypeElement> builder = defineComponentClass(elements, typeElement, "builder");
-        ProcessorErrors.checkState(
-            component.isPresent() || builder.isPresent(),
-            typeElement,
-            "@DefineComponentClasses missing both `component` and `builder` members.");
-
-        component.map(componentMetadatas::get).ifPresent(components::add);
-        builder.map(componentBuilderMetadatas::get).ifPresent(builders::add);
-      }
-
-      return new AutoValue_DefineComponents_AggregatedMetadata(
-          components.build(), builders.build());
-    }
-
-    private static Optional<TypeElement> defineComponentClass(
-        Elements elements, Element element, String annotationMember) {
-      AnnotationMirror mirror =
-          Processors.getAnnotationMirror(element, ClassNames.DEFINE_COMPONENT_CLASSES);
-      AnnotationValue value = getAnnotationElementAndValue(mirror, annotationMember).getValue();
-      String className = AnnotationValues.getString(value);
-
-      if (className.isEmpty()) { // The default value.
-        return Optional.empty();
-      }
-
-      TypeElement type = elements.getTypeElement(className);
-      ProcessorErrors.checkState(
-          type != null,
-          element,
-          "%s.%s(), has invalid value: `%s`.",
-          ClassNames.DEFINE_COMPONENT_CLASSES.simpleName(),
-          annotationMember,
-          className);
-
-      return Optional.of(type);
-    }
-  }
 }
diff --git a/java/dagger/hilt/processor/internal/earlyentrypoint/AggregatedEarlyEntryPointGenerator.java b/java/dagger/hilt/processor/internal/earlyentrypoint/AggregatedEarlyEntryPointGenerator.java
new file mode 100644
index 0000000..ae34118
--- /dev/null
+++ b/java/dagger/hilt/processor/internal/earlyentrypoint/AggregatedEarlyEntryPointGenerator.java
@@ -0,0 +1,50 @@
+/*
+ * Copyright (C) 2021 The Dagger Authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package dagger.hilt.processor.internal.earlyentrypoint;
+
+import com.squareup.javapoet.AnnotationSpec;
+import dagger.hilt.processor.internal.ClassNames;
+import dagger.hilt.processor.internal.Processors;
+import java.io.IOException;
+import javax.annotation.processing.ProcessingEnvironment;
+import javax.lang.model.element.TypeElement;
+
+/**
+ * Generates an {@link dagger.hilt.android.internal.earlyentrypoint.AggregatedEarlyEntryPoint}
+ * annotation.
+ */
+final class AggregatedEarlyEntryPointGenerator {
+
+  private final ProcessingEnvironment env;
+  private final TypeElement earlyEntryPoint;
+
+  AggregatedEarlyEntryPointGenerator(TypeElement earlyEntryPoint, ProcessingEnvironment env) {
+    this.earlyEntryPoint = earlyEntryPoint;
+    this.env = env;
+  }
+
+  void generate() throws IOException {
+    Processors.generateAggregatingClass(
+        ClassNames.AGGREGATED_EARLY_ENTRY_POINT_PACKAGE,
+        AnnotationSpec.builder(ClassNames.AGGREGATED_EARLY_ENTRY_POINT)
+            .addMember("earlyEntryPoint", "$S", earlyEntryPoint.getQualifiedName())
+            .build(),
+        earlyEntryPoint,
+        getClass(),
+        env);
+  }
+}
diff --git a/java/dagger/hilt/processor/internal/earlyentrypoint/AggregatedEarlyEntryPointMetadata.java b/java/dagger/hilt/processor/internal/earlyentrypoint/AggregatedEarlyEntryPointMetadata.java
new file mode 100644
index 0000000..ed347bd
--- /dev/null
+++ b/java/dagger/hilt/processor/internal/earlyentrypoint/AggregatedEarlyEntryPointMetadata.java
@@ -0,0 +1,64 @@
+/*
+ * Copyright (C) 2021 The Dagger Authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package dagger.hilt.processor.internal.earlyentrypoint;
+
+import static dagger.internal.codegen.extension.DaggerStreams.toImmutableSet;
+
+import com.google.auto.value.AutoValue;
+import com.google.common.collect.ImmutableMap;
+import com.google.common.collect.ImmutableSet;
+import dagger.hilt.processor.internal.AggregatedElements;
+import dagger.hilt.processor.internal.AnnotationValues;
+import dagger.hilt.processor.internal.ClassNames;
+import dagger.hilt.processor.internal.Processors;
+import javax.lang.model.element.AnnotationMirror;
+import javax.lang.model.element.AnnotationValue;
+import javax.lang.model.element.TypeElement;
+import javax.lang.model.util.Elements;
+
+/**
+ * A class that represents the values stored in an {@link
+ * dagger.hilt.android.internal.uninstallmodules.AggregatedUninstallModules} annotation.
+ */
+@AutoValue
+public abstract class AggregatedEarlyEntryPointMetadata {
+
+  /** Returns the element annotated with {@link dagger.hilt.android.EarlyEntryPoint}. */
+  public abstract TypeElement earlyEntryPoint();
+
+  /** Returns all aggregated deps in the aggregating package. */
+  public static ImmutableSet<AggregatedEarlyEntryPointMetadata> from(Elements elements) {
+    return AggregatedElements.from(
+            ClassNames.AGGREGATED_EARLY_ENTRY_POINT_PACKAGE,
+            ClassNames.AGGREGATED_EARLY_ENTRY_POINT,
+            elements)
+        .stream()
+        .map(aggregatedElement -> create(aggregatedElement, elements))
+        .collect(toImmutableSet());
+  }
+
+  private static AggregatedEarlyEntryPointMetadata create(TypeElement element, Elements elements) {
+    AnnotationMirror annotationMirror =
+        Processors.getAnnotationMirror(element, ClassNames.AGGREGATED_EARLY_ENTRY_POINT);
+
+    ImmutableMap<String, AnnotationValue> values =
+        Processors.getAnnotationValues(elements, annotationMirror);
+
+    return new AutoValue_AggregatedEarlyEntryPointMetadata(
+        elements.getTypeElement(AnnotationValues.getString(values.get("earlyEntryPoint"))));
+  }
+}
diff --git a/java/dagger/hilt/android/processor/internal/uninstallmodules/BUILD b/java/dagger/hilt/processor/internal/earlyentrypoint/BUILD
similarity index 63%
copy from java/dagger/hilt/android/processor/internal/uninstallmodules/BUILD
copy to java/dagger/hilt/processor/internal/earlyentrypoint/BUILD
index 73c4606..3573e86 100644
--- a/java/dagger/hilt/android/processor/internal/uninstallmodules/BUILD
+++ b/java/dagger/hilt/processor/internal/earlyentrypoint/BUILD
@@ -13,35 +13,50 @@
 # limitations under the License.
 
 # Description:
-#   A processor for @dagger.hilt.android.testing.UninstallModules.
+#   A processor for @dagger.hilt.android.EarlyEntryPoint.
 
 package(default_visibility = ["//:src"])
 
 java_plugin(
     name = "processor",
     generates_api = 1,
-    processor_class = "dagger.hilt.android.processor.internal.uninstallmodules.UninstallModulesProcessor",
+    processor_class = "dagger.hilt.processor.internal.earlyentrypoint.EarlyEntryPointProcessor",
     deps = [":processor_lib"],
 )
 
 java_library(
     name = "processor_lib",
     srcs = [
-        "UninstallModulesProcessor.java",
+        "AggregatedEarlyEntryPointGenerator.java",
+        "EarlyEntryPointProcessor.java",
     ],
     deps = [
         "//java/dagger/hilt/processor/internal:base_processor",
         "//java/dagger/hilt/processor/internal:classnames",
-        "//java/dagger/hilt/processor/internal:processor_errors",
         "//java/dagger/hilt/processor/internal:processors",
-        "//java/dagger/internal/codegen/extension",
         "//java/dagger/internal/guava:collect",
         "@google_bazel_common//third_party/java/auto:service",
         "@google_bazel_common//third_party/java/incap",
+        "@google_bazel_common//third_party/java/javapoet",
         "@maven//:com_google_auto_auto_common",
     ],
 )
 
+java_library(
+    name = "aggregated_early_entry_point_metadata",
+    srcs = [
+        "AggregatedEarlyEntryPointMetadata.java",
+    ],
+    deps = [
+        "//java/dagger/hilt/processor/internal:aggregated_elements",
+        "//java/dagger/hilt/processor/internal:classnames",
+        "//java/dagger/hilt/processor/internal:processors",
+        "//java/dagger/internal/codegen/extension",
+        "//java/dagger/internal/guava:collect",
+        "@google_bazel_common//third_party/java/auto:value",
+    ],
+)
+
 filegroup(
     name = "srcs_filegroup",
     srcs = glob(["*"]),
diff --git a/java/dagger/hilt/processor/internal/earlyentrypoint/EarlyEntryPointProcessor.java b/java/dagger/hilt/processor/internal/earlyentrypoint/EarlyEntryPointProcessor.java
new file mode 100644
index 0000000..848fa31
--- /dev/null
+++ b/java/dagger/hilt/processor/internal/earlyentrypoint/EarlyEntryPointProcessor.java
@@ -0,0 +1,45 @@
+/*
+ * Copyright (C) 2021 The Dagger Authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package dagger.hilt.processor.internal.earlyentrypoint;
+
+import static com.google.auto.common.MoreElements.asType;
+import static net.ltgt.gradle.incap.IncrementalAnnotationProcessorType.ISOLATING;
+
+import com.google.auto.service.AutoService;
+import com.google.common.collect.ImmutableSet;
+import dagger.hilt.processor.internal.BaseProcessor;
+import dagger.hilt.processor.internal.ClassNames;
+import javax.annotation.processing.Processor;
+import javax.lang.model.element.Element;
+import javax.lang.model.element.TypeElement;
+import net.ltgt.gradle.incap.IncrementalAnnotationProcessor;
+
+/** Validates {@link dagger.hilt.android.EarlyEntryPoint} usages. */
+@IncrementalAnnotationProcessor(ISOLATING)
+@AutoService(Processor.class)
+public final class EarlyEntryPointProcessor extends BaseProcessor {
+
+  @Override
+  public ImmutableSet<String> getSupportedAnnotationTypes() {
+    return ImmutableSet.of(ClassNames.EARLY_ENTRY_POINT.toString());
+  }
+
+  @Override
+  public void processEach(TypeElement annotation, Element element) throws Exception {
+    new AggregatedEarlyEntryPointGenerator(asType(element), getProcessingEnv()).generate();
+  }
+}
diff --git a/java/dagger/hilt/processor/internal/root/AggregatedRootGenerator.java b/java/dagger/hilt/processor/internal/root/AggregatedRootGenerator.java
new file mode 100644
index 0000000..722b99b
--- /dev/null
+++ b/java/dagger/hilt/processor/internal/root/AggregatedRootGenerator.java
@@ -0,0 +1,50 @@
+/*
+ * Copyright (C) 2021 The Dagger Authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package dagger.hilt.processor.internal.root;
+
+import com.squareup.javapoet.AnnotationSpec;
+import dagger.hilt.processor.internal.ClassNames;
+import dagger.hilt.processor.internal.Processors;
+import java.io.IOException;
+import javax.annotation.processing.ProcessingEnvironment;
+import javax.lang.model.element.TypeElement;
+
+/** Generates an {@link dagger.hilt.internal.aggregatedroot.AggregatedRoot}. */
+final class AggregatedRootGenerator {
+  private final TypeElement rootElement;
+  private final TypeElement rootAnnotation;
+  private final ProcessingEnvironment processingEnv;
+
+  AggregatedRootGenerator(
+      TypeElement rootElement, TypeElement rootAnnotation, ProcessingEnvironment processingEnv) {
+    this.rootElement = rootElement;
+    this.rootAnnotation = rootAnnotation;
+    this.processingEnv = processingEnv;
+  }
+
+  void generate() throws IOException {
+    Processors.generateAggregatingClass(
+        ClassNames.AGGREGATED_ROOT_PACKAGE,
+        AnnotationSpec.builder(ClassNames.AGGREGATED_ROOT)
+            .addMember("root", "$S", rootElement.getQualifiedName())
+            .addMember("rootAnnotation", "$T.class", rootAnnotation)
+            .build(),
+        rootElement,
+        getClass(),
+        processingEnv);
+  }
+}
diff --git a/java/dagger/hilt/processor/internal/root/AggregatedRootMetadata.java b/java/dagger/hilt/processor/internal/root/AggregatedRootMetadata.java
new file mode 100644
index 0000000..961689b
--- /dev/null
+++ b/java/dagger/hilt/processor/internal/root/AggregatedRootMetadata.java
@@ -0,0 +1,64 @@
+/*
+ * Copyright (C) 2021 The Dagger Authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package dagger.hilt.processor.internal.root;
+
+import static dagger.internal.codegen.extension.DaggerStreams.toImmutableSet;
+
+import com.google.auto.value.AutoValue;
+import com.google.common.collect.ImmutableMap;
+import com.google.common.collect.ImmutableSet;
+import dagger.hilt.processor.internal.AggregatedElements;
+import dagger.hilt.processor.internal.AnnotationValues;
+import dagger.hilt.processor.internal.ClassNames;
+import dagger.hilt.processor.internal.Processors;
+import javax.lang.model.element.AnnotationMirror;
+import javax.lang.model.element.AnnotationValue;
+import javax.lang.model.element.TypeElement;
+import javax.lang.model.util.Elements;
+
+/**
+ * Represents the values stored in an {@link dagger.hilt.internal.aggregatedroot.AggregatedRoot}.
+ */
+@AutoValue
+abstract class AggregatedRootMetadata {
+
+  /** Returns the element that was annotated with the root annotation. */
+  abstract TypeElement rootElement();
+
+  /** Returns the root annotation as an element. */
+  abstract TypeElement rootAnnotation();
+
+  static ImmutableSet<AggregatedRootMetadata> from(Elements elements) {
+    return AggregatedElements.from(
+            ClassNames.AGGREGATED_ROOT_PACKAGE, ClassNames.AGGREGATED_ROOT, elements)
+        .stream()
+        .map(aggregatedElement -> create(aggregatedElement, elements))
+        .collect(toImmutableSet());
+  }
+
+  private static AggregatedRootMetadata create(TypeElement element, Elements elements) {
+    AnnotationMirror annotationMirror =
+        Processors.getAnnotationMirror(element, ClassNames.AGGREGATED_ROOT);
+
+    ImmutableMap<String, AnnotationValue> values =
+        Processors.getAnnotationValues(elements, annotationMirror);
+
+    return new AutoValue_AggregatedRootMetadata(
+        elements.getTypeElement(AnnotationValues.getString(values.get("root"))),
+        AnnotationValues.getTypeElement(values.get("rootAnnotation")));
+  }
+}
diff --git a/java/dagger/hilt/processor/internal/root/BUILD b/java/dagger/hilt/processor/internal/root/BUILD
index 709eb7f..5ce762f 100644
--- a/java/dagger/hilt/processor/internal/root/BUILD
+++ b/java/dagger/hilt/processor/internal/root/BUILD
@@ -29,11 +29,14 @@
 java_library(
     name = "processor_lib",
     srcs = [
+        "AggregatedRootGenerator.java",
+        "ComponentGenerator.java",
+        "EarlySingletonComponentCreatorGenerator.java",
+        "ProcessedRootSentinelGenerator.java",
         "RootFileFormatter.java",
         "RootGenerator.java",
         "RootProcessor.java",
         "TestComponentDataGenerator.java",
-        "TestComponentDataSupplierGenerator.java",
         "TestInjectorGenerator.java",
     ],
     deps = [
@@ -41,6 +44,7 @@
         ":root_type",
         "//java/dagger/hilt/processor/internal:base_processor",
         "//java/dagger/hilt/processor/internal:classnames",
+        "//java/dagger/hilt/processor/internal:compiler_options",
         "//java/dagger/hilt/processor/internal:component_descriptor",
         "//java/dagger/hilt/processor/internal:component_names",
         "//java/dagger/hilt/processor/internal:processor_errors",
@@ -62,13 +66,18 @@
 java_library(
     name = "root_metadata",
     srcs = [
+        "AggregatedRootMetadata.java",
+        "ComponentTree.java",
+        "ProcessedRootSentinelMetadata.java",
         "Root.java",
         "RootMetadata.java",
         "TestRootMetadata.java",
     ],
     deps = [
         ":root_type",
+        "//java/dagger/hilt/processor/internal:aggregated_elements",
         "//java/dagger/hilt/processor/internal:classnames",
+        "//java/dagger/hilt/processor/internal:compiler_options",
         "//java/dagger/hilt/processor/internal:component_descriptor",
         "//java/dagger/hilt/processor/internal:kotlin",
         "//java/dagger/hilt/processor/internal:processor_errors",
@@ -79,6 +88,7 @@
         "//java/dagger/internal/codegen/kotlin",
         "//java/dagger/internal/guava:base",
         "//java/dagger/internal/guava:collect",
+        "//java/dagger/internal/guava:graph",
         "@google_bazel_common//third_party/java/auto:value",
         "@google_bazel_common//third_party/java/javapoet",
         "@maven//:com_google_auto_auto_common",
@@ -90,7 +100,6 @@
     srcs = ["RootType.java"],
     deps = [
         "//java/dagger/hilt/processor/internal:classnames",
-        "//java/dagger/hilt/processor/internal:processor_errors",
         "//java/dagger/hilt/processor/internal:processors",
         "@google_bazel_common//third_party/java/javapoet",
     ],
diff --git a/java/dagger/hilt/processor/internal/ComponentGenerator.java b/java/dagger/hilt/processor/internal/root/ComponentGenerator.java
similarity index 89%
rename from java/dagger/hilt/processor/internal/ComponentGenerator.java
rename to java/dagger/hilt/processor/internal/root/ComponentGenerator.java
index 3a4bf1e..0e8d0ad 100644
--- a/java/dagger/hilt/processor/internal/ComponentGenerator.java
+++ b/java/dagger/hilt/processor/internal/root/ComponentGenerator.java
@@ -14,7 +14,7 @@
  * limitations under the License.
  */
 
-package dagger.hilt.processor.internal;
+package dagger.hilt.processor.internal.root;
 
 import static dagger.internal.codegen.extension.DaggerStreams.toImmutableList;
 import static java.util.Comparator.comparing;
@@ -28,6 +28,8 @@
 import com.squareup.javapoet.JavaFile;
 import com.squareup.javapoet.TypeName;
 import com.squareup.javapoet.TypeSpec;
+import dagger.hilt.processor.internal.ClassNames;
+import dagger.hilt.processor.internal.Processors;
 import java.io.IOException;
 import java.util.ArrayList;
 import java.util.Comparator;
@@ -36,11 +38,9 @@
 import java.util.Set;
 import javax.annotation.processing.ProcessingEnvironment;
 import javax.lang.model.element.Modifier;
-import javax.lang.model.element.TypeElement;
 
 /** Generates a Dagger component or subcomponent interface. */
-// TODO(bcorso): Make this non-public
-public final class ComponentGenerator {
+final class ComponentGenerator {
   private static final Joiner JOINER = Joiner.on(".");
   private static final Comparator<ClassName> SIMPLE_NAME_SORTER =
       Comparator.comparing((ClassName c) -> JOINER.join(c.simpleNames()))
@@ -49,7 +49,6 @@
 
   private final ProcessingEnvironment processingEnv;
   private final ClassName name;
-  private final TypeElement rootElement;
   private final Optional<ClassName> superclass;
   private final ImmutableList<ClassName> modules;
   private final ImmutableList<TypeName> entryPoints;
@@ -61,7 +60,6 @@
   public ComponentGenerator(
       ProcessingEnvironment processingEnv,
       ClassName name,
-      TypeElement rootElement,
       Optional<ClassName> superclass,
       Set<? extends ClassName> modules,
       Set<? extends TypeName> entryPoints,
@@ -71,7 +69,6 @@
       Optional<TypeSpec> componentBuilder) {
     this.processingEnv = processingEnv;
     this.name = name;
-    this.rootElement = rootElement;
     this.superclass = superclass;
     this.modules = modules.stream().sorted(SIMPLE_NAME_SORTER).collect(toImmutableList());
     this.entryPoints = entryPoints.stream().sorted(TYPE_NAME_SORTER).collect(toImmutableList());
@@ -81,25 +78,24 @@
     this.componentBuilder = componentBuilder;
   }
 
-  public TypeSpec generate() throws IOException {
-    TypeSpec.Builder generator =
+  public TypeSpec.Builder typeSpecBuilder() throws IOException {
+    TypeSpec.Builder builder =
         TypeSpec.classBuilder(name)
             // Public because components from a scope below must reference to create
             .addModifiers(Modifier.PUBLIC, Modifier.ABSTRACT)
-            .addOriginatingElement(rootElement)
             .addAnnotation(getComponentAnnotation());
 
-    componentBuilder.ifPresent(generator::addType);
+    componentBuilder.ifPresent(builder::addType);
 
-    scopes.forEach(generator::addAnnotation);
+    scopes.forEach(builder::addAnnotation);
 
-    addEntryPoints(generator);
+    addEntryPoints(builder);
 
-    superclass.ifPresent(generator::superclass);
+    superclass.ifPresent(builder::superclass);
 
-    generator.addAnnotations(extraAnnotations);
+    builder.addAnnotations(extraAnnotations);
 
-    return generator.build();
+    return builder;
   }
 
   /** Returns the component annotation with the list of modules to install for the component. */
@@ -160,7 +156,6 @@
             Processors.getEnclosedClassName(name), "_EntryPointPartition" + partitionIndex);
     TypeSpec.Builder builder =
         TypeSpec.interfaceBuilder(partitionName)
-            .addOriginatingElement(rootElement)
             .addModifiers(Modifier.ABSTRACT)
             .addSuperinterfaces(partition);
 
diff --git a/java/dagger/hilt/processor/internal/ComponentTree.java b/java/dagger/hilt/processor/internal/root/ComponentTree.java
similarity index 88%
rename from java/dagger/hilt/processor/internal/ComponentTree.java
rename to java/dagger/hilt/processor/internal/root/ComponentTree.java
index 6d2137a..fe3d4b1 100644
--- a/java/dagger/hilt/processor/internal/ComponentTree.java
+++ b/java/dagger/hilt/processor/internal/root/ComponentTree.java
@@ -14,7 +14,7 @@
  * limitations under the License.
  */
 
-package dagger.hilt.processor.internal;
+package dagger.hilt.processor.internal.root;
 
 import static dagger.internal.codegen.extension.DaggerStreams.toImmutableList;
 
@@ -27,17 +27,18 @@
 import com.google.common.graph.ImmutableGraph;
 import com.google.common.graph.MutableGraph;
 import com.squareup.javapoet.ClassName;
+import dagger.hilt.processor.internal.ComponentDescriptor;
 import java.util.HashMap;
 import java.util.Map;
 import java.util.Set;
 
 /** A representation of the full tree of scopes. */
-public final class ComponentTree {
+final class ComponentTree {
   private final ImmutableGraph<ComponentDescriptor> graph;
   private final ComponentDescriptor root;
 
   /** Creates a new tree from a set of descriptors. */
-  public static ComponentTree from(Set<ComponentDescriptor> descriptors) {
+  static ComponentTree from(Set<ComponentDescriptor> descriptors) {
     MutableGraph<ComponentDescriptor> graph =
         GraphBuilder.directed().allowsSelfLoops(false).build();
 
@@ -88,19 +89,19 @@
     root = Iterables.getOnlyElement(roots);
   }
 
-  public ImmutableSet<ComponentDescriptor> getComponentDescriptors() {
+  ImmutableSet<ComponentDescriptor> getComponentDescriptors() {
     return ImmutableSet.copyOf(graph.nodes());
   }
 
-  public ImmutableSet<ComponentDescriptor> childrenOf(ComponentDescriptor componentDescriptor) {
+  ImmutableSet<ComponentDescriptor> childrenOf(ComponentDescriptor componentDescriptor) {
     return ImmutableSet.copyOf(graph.successors(componentDescriptor));
   }
 
-  public ImmutableGraph<ComponentDescriptor> graph() {
+  ImmutableGraph<ComponentDescriptor> graph() {
     return graph;
   }
 
-  public ComponentDescriptor root() {
+  ComponentDescriptor root() {
     return root;
   }
 }
diff --git a/java/dagger/hilt/processor/internal/root/EarlySingletonComponentCreatorGenerator.java b/java/dagger/hilt/processor/internal/root/EarlySingletonComponentCreatorGenerator.java
new file mode 100644
index 0000000..1dbda39
--- /dev/null
+++ b/java/dagger/hilt/processor/internal/root/EarlySingletonComponentCreatorGenerator.java
@@ -0,0 +1,63 @@
+/*
+ * Copyright (C) 2021 The Dagger Authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package dagger.hilt.processor.internal.root;
+
+import com.squareup.javapoet.ClassName;
+import com.squareup.javapoet.JavaFile;
+import com.squareup.javapoet.MethodSpec;
+import com.squareup.javapoet.TypeSpec;
+import dagger.hilt.processor.internal.ClassNames;
+import dagger.hilt.processor.internal.Processors;
+import java.io.IOException;
+import javax.annotation.processing.ProcessingEnvironment;
+
+/** Generator for the {@code EarlySingletonComponentCreator}. */
+final class EarlySingletonComponentCreatorGenerator {
+  private static final ClassName EARLY_SINGLETON_COMPONENT_CREATOR =
+      ClassName.get("dagger.hilt.android.internal.testing", "EarlySingletonComponentCreator");
+  private static final ClassName EARLY_SINGLETON_COMPONENT_CREATOR_IMPL =
+      ClassName.get(
+          "dagger.hilt.android.internal.testing", "EarlySingletonComponentCreatorImpl");
+  private static final ClassName DEFAULT_COMPONENT_IMPL =
+      ClassName.get(
+          "dagger.hilt.android.internal.testing.root", "DaggerDefault_HiltComponents_SingletonC");
+
+  static void generate(ProcessingEnvironment env) throws IOException {
+    TypeSpec.Builder builder =
+        TypeSpec.classBuilder(EARLY_SINGLETON_COMPONENT_CREATOR_IMPL)
+            .superclass(EARLY_SINGLETON_COMPONENT_CREATOR)
+            .addMethod(
+                MethodSpec.methodBuilder("create")
+                    .returns(ClassName.OBJECT)
+                    .addStatement(
+                        "return $T.builder()\n"
+                            + ".applicationContextModule(new $T($T.getApplicationContext()))\n"
+                            + ".build()",
+                        DEFAULT_COMPONENT_IMPL,
+                        ClassNames.APPLICATION_CONTEXT_MODULE,
+                        ClassNames.APPLICATION_PROVIDER)
+                    .build());
+
+    Processors.addGeneratedAnnotation(builder, env, ClassNames.ROOT_PROCESSOR.toString());
+
+    JavaFile.builder(EARLY_SINGLETON_COMPONENT_CREATOR_IMPL.packageName(), builder.build())
+        .build()
+        .writeTo(env.getFiler());
+  }
+
+  private EarlySingletonComponentCreatorGenerator() {}
+}
diff --git a/java/dagger/hilt/processor/internal/root/ProcessedRootSentinelGenerator.java b/java/dagger/hilt/processor/internal/root/ProcessedRootSentinelGenerator.java
new file mode 100644
index 0000000..87efa20
--- /dev/null
+++ b/java/dagger/hilt/processor/internal/root/ProcessedRootSentinelGenerator.java
@@ -0,0 +1,46 @@
+/*
+ * Copyright (C) 2021 The Dagger Authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package dagger.hilt.processor.internal.root;
+
+import com.squareup.javapoet.AnnotationSpec;
+import dagger.hilt.processor.internal.ClassNames;
+import dagger.hilt.processor.internal.Processors;
+import java.io.IOException;
+import javax.annotation.processing.ProcessingEnvironment;
+import javax.lang.model.element.TypeElement;
+
+/** Generates an {@link dagger.hilt.internal.processedrootsentinel.ProcessedRootSentinel}. */
+final class ProcessedRootSentinelGenerator {
+  private final TypeElement processedRoot;
+  private final ProcessingEnvironment processingEnv;
+
+  ProcessedRootSentinelGenerator(TypeElement processedRoot, ProcessingEnvironment processingEnv) {
+    this.processedRoot = processedRoot;
+    this.processingEnv = processingEnv;
+  }
+
+  void generate() throws IOException {
+    Processors.generateAggregatingClass(
+        ClassNames.PROCESSED_ROOT_SENTINEL_PACKAGE,
+        AnnotationSpec.builder(ClassNames.PROCESSED_ROOT_SENTINEL)
+            .addMember("roots", "$S", processedRoot.getQualifiedName())
+            .build(),
+        processedRoot,
+        getClass(),
+        processingEnv);
+  }
+}
diff --git a/java/dagger/hilt/processor/internal/root/ProcessedRootSentinelMetadata.java b/java/dagger/hilt/processor/internal/root/ProcessedRootSentinelMetadata.java
new file mode 100644
index 0000000..d600154
--- /dev/null
+++ b/java/dagger/hilt/processor/internal/root/ProcessedRootSentinelMetadata.java
@@ -0,0 +1,65 @@
+/*
+ * Copyright (C) 2021 The Dagger Authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package dagger.hilt.processor.internal.root;
+
+import static dagger.internal.codegen.extension.DaggerStreams.toImmutableSet;
+
+import com.google.auto.value.AutoValue;
+import com.google.common.collect.ImmutableMap;
+import com.google.common.collect.ImmutableSet;
+import dagger.hilt.processor.internal.AggregatedElements;
+import dagger.hilt.processor.internal.AnnotationValues;
+import dagger.hilt.processor.internal.ClassNames;
+import dagger.hilt.processor.internal.Processors;
+import javax.lang.model.element.AnnotationMirror;
+import javax.lang.model.element.AnnotationValue;
+import javax.lang.model.element.TypeElement;
+import javax.lang.model.util.Elements;
+
+/**
+ * Represents the values stored in an {@link
+ * dagger.hilt.internal.processedrootsentinel.ProcessedRootSentinel}.
+ */
+@AutoValue
+abstract class ProcessedRootSentinelMetadata {
+
+  /** Returns the processed root elements. */
+  abstract ImmutableSet<TypeElement> rootElements();
+
+  static ImmutableSet<ProcessedRootSentinelMetadata> from(Elements elements) {
+    return AggregatedElements.from(
+            ClassNames.PROCESSED_ROOT_SENTINEL_PACKAGE,
+            ClassNames.PROCESSED_ROOT_SENTINEL,
+            elements)
+        .stream()
+        .map(aggregatedElement -> create(aggregatedElement, elements))
+        .collect(toImmutableSet());
+  }
+
+  private static ProcessedRootSentinelMetadata create(TypeElement element, Elements elements) {
+    AnnotationMirror annotationMirror =
+        Processors.getAnnotationMirror(element, ClassNames.PROCESSED_ROOT_SENTINEL);
+
+    ImmutableMap<String, AnnotationValue> values =
+        Processors.getAnnotationValues(elements, annotationMirror);
+
+    return new AutoValue_ProcessedRootSentinelMetadata(
+        AnnotationValues.getStrings(values.get("roots")).stream()
+            .map(elements::getTypeElement)
+            .collect(toImmutableSet()));
+  }
+}
diff --git a/java/dagger/hilt/processor/internal/root/Root.java b/java/dagger/hilt/processor/internal/root/Root.java
index 981636d..24440b0 100644
--- a/java/dagger/hilt/processor/internal/root/Root.java
+++ b/java/dagger/hilt/processor/internal/root/Root.java
@@ -19,6 +19,7 @@
 import com.google.auto.common.MoreElements;
 import com.google.auto.value.AutoValue;
 import com.squareup.javapoet.ClassName;
+import dagger.hilt.processor.internal.ClassNames;
 import javax.annotation.processing.ProcessingEnvironment;
 import javax.lang.model.element.Element;
 import javax.lang.model.element.TypeElement;
@@ -26,18 +27,37 @@
 /** Metadata for a root element that can trigger the {@link RootProcessor}. */
 @AutoValue
 abstract class Root {
+  /**
+   * Creates the default root for this (test) build compilation.
+   *
+   * <p>A default root installs only the global {@code InstallIn} and {@code TestInstallIn}
+   * dependencies. Test-specific dependencies are not installed in the default root.
+   *
+   * <p>The default root is used for two purposes:
+   *
+   * <ul>
+   *   <li>To inject {@code EarlyEntryPoint} annotated interfaces.
+   *   <li>To inject tests that only depend on global dependencies
+   * </ul>
+   */
+  static Root createDefaultRoot(ProcessingEnvironment env) {
+    TypeElement rootElement =
+        env.getElementUtils().getTypeElement(ClassNames.DEFAULT_ROOT.canonicalName());
+    return new AutoValue_Root(rootElement, /*isTestRoot=*/ true);
+  }
+
   /** Creates a {@plainlink Root root} for the given {@plainlink Element element}. */
   static Root create(Element element, ProcessingEnvironment env) {
     TypeElement rootElement = MoreElements.asType(element);
-    return new AutoValue_Root(RootType.of(env, rootElement), rootElement);
+    return new AutoValue_Root(rootElement, RootType.of(rootElement).isTestRoot());
   }
 
-  /** Returns the type of the root {@code element}. */
-  abstract RootType type();
-
   /** Returns the root element that should be used with processing. */
   abstract TypeElement element();
 
+  /** Returns {@code true} if this is a test root. */
+  abstract boolean isTestRoot();
+
   /** Returns the class name of the root element. */
   ClassName classname() {
     return ClassName.get(element());
@@ -48,7 +68,8 @@
     return element().toString();
   }
 
-  boolean isTestRoot() {
-    return type().isTestRoot();
+  /** Returns {@code true} if this uses the default root. */
+  boolean isDefaultRoot() {
+    return classname().equals(ClassNames.DEFAULT_ROOT);
   }
 }
diff --git a/java/dagger/hilt/processor/internal/root/RootGenerator.java b/java/dagger/hilt/processor/internal/root/RootGenerator.java
index c2c55e6..f87fbd9 100644
--- a/java/dagger/hilt/processor/internal/root/RootGenerator.java
+++ b/java/dagger/hilt/processor/internal/root/RootGenerator.java
@@ -16,6 +16,7 @@
 
 package dagger.hilt.processor.internal.root;
 
+import static com.google.common.base.Preconditions.checkState;
 import static dagger.hilt.processor.internal.Processors.toClassNames;
 import static dagger.internal.codegen.extension.DaggerStreams.toImmutableSet;
 import static javax.lang.model.element.Modifier.ABSTRACT;
@@ -35,11 +36,11 @@
 import com.squareup.javapoet.TypeSpec;
 import dagger.hilt.processor.internal.ClassNames;
 import dagger.hilt.processor.internal.ComponentDescriptor;
-import dagger.hilt.processor.internal.ComponentGenerator;
 import dagger.hilt.processor.internal.ComponentNames;
-import dagger.hilt.processor.internal.ComponentTree;
 import dagger.hilt.processor.internal.Processors;
 import java.io.IOException;
+import java.util.HashMap;
+import java.util.Map;
 import java.util.Optional;
 import javax.annotation.processing.ProcessingEnvironment;
 import javax.lang.model.element.Modifier;
@@ -47,20 +48,27 @@
 /** Generates components and any other classes needed for a root. */
 final class RootGenerator {
 
-  static void generate(RootMetadata metadata, ProcessingEnvironment env) throws IOException {
+  static void generate(
+      RootMetadata metadata, ComponentNames componentNames, ProcessingEnvironment env)
+      throws IOException {
     new RootGenerator(
-        RootMetadata.copyWithNewTree(
-            metadata,
-            filterDescriptors(metadata.componentTree())),
-        env).generateComponents();
+            RootMetadata.copyWithNewTree(metadata, filterDescriptors(metadata.componentTree())),
+            componentNames,
+            env)
+        .generateComponents();
   }
 
   private final RootMetadata metadata;
   private final ProcessingEnvironment env;
   private final Root root;
+  private final Map<String, Integer> simpleComponentNamesToDedupeSuffix = new HashMap<>();
+  private final Map<ComponentDescriptor, ClassName> componentNameMap = new HashMap<>();
+  private final ComponentNames componentNames;
 
-  private RootGenerator(RootMetadata metadata, ProcessingEnvironment env) {
+  private RootGenerator(
+      RootMetadata metadata, ComponentNames componentNames, ProcessingEnvironment env) {
     this.metadata = metadata;
+    this.componentNames = componentNames;
     this.env = env;
     this.root = metadata.root();
   }
@@ -68,8 +76,9 @@
   private void generateComponents() throws IOException {
 
     // TODO(bcorso): Consider moving all of this logic into ComponentGenerator?
+    ClassName componentsWrapperClassName = getComponentsWrapperClassName();
     TypeSpec.Builder componentsWrapper =
-        TypeSpec.classBuilder(getComponentsWrapperClassName())
+        TypeSpec.classBuilder(componentsWrapperClassName)
             .addModifiers(Modifier.PUBLIC, Modifier.FINAL)
             .addMethod(MethodSpec.constructorBuilder().addModifiers(Modifier.PRIVATE).build());
 
@@ -93,7 +102,6 @@
           new ComponentGenerator(
                   env,
                   getComponentClassName(componentDescriptor),
-                  root.element(),
                   Optional.empty(),
                   modules,
                   metadata.entryPoints(componentDescriptor.component()),
@@ -101,11 +109,14 @@
                   ImmutableList.of(),
                   componentAnnotation(componentDescriptor),
                   componentBuilder(componentDescriptor))
-              .generate().toBuilder().addModifiers(Modifier.STATIC).build());
+              .typeSpecBuilder()
+              .addModifiers(Modifier.STATIC)
+              .build());
     }
 
     RootFileFormatter.write(
-        JavaFile.builder(root.classname().packageName(), componentsWrapper.build()).build(),
+        JavaFile.builder(componentsWrapperClassName.packageName(), componentsWrapper.build())
+            .build(),
         env.getFiler());
   }
 
@@ -130,7 +141,7 @@
   }
 
   private ImmutableMap<ComponentDescriptor, ClassName> subcomponentBuilderModules(
-      TypeSpec.Builder componentsWrapper) throws IOException {
+      TypeSpec.Builder componentsWrapper) {
     ImmutableMap.Builder<ComponentDescriptor, ClassName> modules = ImmutableMap.builder();
     for (ComponentDescriptor descriptor : metadata.componentTree().getComponentDescriptors()) {
       // Root component builders don't have subcomponent builder modules
@@ -151,7 +162,7 @@
   //   @Binds FooSubcomponentInterfaceBuilder bind(FooSubcomponent.Builder builder);
   // }
   private TypeSpec subcomponentBuilderModule(
-      ClassName componentName, ClassName builderName, ClassName moduleName) throws IOException {
+      ClassName componentName, ClassName builderName, ClassName moduleName) {
     TypeSpec.Builder subcomponentBuilderModule =
         TypeSpec.interfaceBuilder(moduleName)
             .addOriginatingElement(root.element())
@@ -210,10 +221,38 @@
   }
 
   private ClassName getComponentsWrapperClassName() {
-    return ComponentNames.generatedComponentsWrapper(root.classname());
+    return componentNames.generatedComponentsWrapper(root.classname());
   }
 
   private ClassName getComponentClassName(ComponentDescriptor componentDescriptor) {
-    return ComponentNames.generatedComponent(root.classname(), componentDescriptor.component());
+    if (componentNameMap.containsKey(componentDescriptor)) {
+      return componentNameMap.get(componentDescriptor);
+    }
+
+    // Disallow any component names with the same name as our SingletonComponent because we treat
+    // that component specially and things may break.
+    checkState(
+        componentDescriptor.component().equals(ClassNames.SINGLETON_COMPONENT)
+        || !componentDescriptor.component().simpleName().equals(
+            ClassNames.SINGLETON_COMPONENT.simpleName()),
+        "Cannot have a component with the same simple name as the reserved %s: %s",
+        ClassNames.SINGLETON_COMPONENT.simpleName(),
+        componentDescriptor.component());
+
+    ClassName generatedComponent =
+        componentNames.generatedComponent(root.classname(), componentDescriptor.component());
+
+    Integer suffix = simpleComponentNamesToDedupeSuffix.get(generatedComponent.simpleName());
+    if (suffix != null) {
+      // If an entry exists, use the suffix in the map and the replace it with the value incremented
+      generatedComponent = Processors.append(generatedComponent, String.valueOf(suffix));
+      simpleComponentNamesToDedupeSuffix.put(generatedComponent.simpleName(), suffix + 1);
+    } else {
+      // Otherwise, just add an entry for any possible future duplicates
+      simpleComponentNamesToDedupeSuffix.put(generatedComponent.simpleName(), 2);
+    }
+
+    componentNameMap.put(componentDescriptor, generatedComponent);
+    return generatedComponent;
   }
 }
diff --git a/java/dagger/hilt/processor/internal/root/RootMetadata.java b/java/dagger/hilt/processor/internal/root/RootMetadata.java
index 4c43f1d..b39b590 100644
--- a/java/dagger/hilt/processor/internal/root/RootMetadata.java
+++ b/java/dagger/hilt/processor/internal/root/RootMetadata.java
@@ -16,20 +16,22 @@
 
 package dagger.hilt.processor.internal.root;
 
+import static com.google.common.base.Preconditions.checkState;
 import static com.google.common.base.Suppliers.memoize;
+import static dagger.hilt.processor.internal.HiltCompilerOptions.isSharedTestComponentsEnabled;
 import static dagger.internal.codegen.extension.DaggerStreams.toImmutableSet;
 import static javax.lang.model.element.Modifier.ABSTRACT;
 import static javax.lang.model.element.Modifier.PRIVATE;
 import static javax.lang.model.element.Modifier.STATIC;
 
 import com.google.common.base.Supplier;
+import com.google.common.collect.ImmutableList;
 import com.google.common.collect.ImmutableSet;
 import com.google.common.collect.ImmutableSetMultimap;
 import com.squareup.javapoet.ClassName;
 import com.squareup.javapoet.TypeName;
 import dagger.hilt.processor.internal.ClassNames;
 import dagger.hilt.processor.internal.ComponentDescriptor;
-import dagger.hilt.processor.internal.ComponentTree;
 import dagger.hilt.processor.internal.KotlinMetadataUtils;
 import dagger.hilt.processor.internal.Processors;
 import dagger.hilt.processor.internal.aggregateddeps.ComponentDependencies;
@@ -53,15 +55,34 @@
       ComponentTree componentTree,
       ComponentDependencies deps,
       ProcessingEnvironment env) {
-    RootMetadata metadata = new RootMetadata(root, componentTree, deps, env);
-    metadata.validate();
-    return metadata;
+    return createInternal(root, ImmutableList.of(), componentTree, deps, env);
   }
 
-  static RootMetadata copyWithNewTree(
-      RootMetadata other,
-      ComponentTree componentTree) {
-    return create(other.root, componentTree, other.deps, other.env);
+  static RootMetadata createForDefaultRoot(
+      Root root,
+      ImmutableList<RootMetadata> rootsUsingDefaultComponents,
+      ComponentTree componentTree,
+      ComponentDependencies deps,
+      ProcessingEnvironment env) {
+    checkState(root.isDefaultRoot());
+    return createInternal(root, rootsUsingDefaultComponents, componentTree, deps, env);
+  }
+
+  static RootMetadata copyWithNewTree(RootMetadata other, ComponentTree componentTree) {
+    return createInternal(
+        other.root, other.rootsUsingDefaultComponents, componentTree, other.deps, other.env);
+  }
+
+  private static RootMetadata createInternal(
+      Root root,
+      ImmutableList<RootMetadata> rootsUsingDefaultComponents,
+      ComponentTree componentTree,
+      ComponentDependencies deps,
+      ProcessingEnvironment env) {
+    RootMetadata metadata =
+        new RootMetadata(root, componentTree, deps, rootsUsingDefaultComponents, env);
+    metadata.validate();
+    return metadata;
   }
 
   private final Root root;
@@ -69,6 +90,7 @@
   private final Elements elements;
   private final ComponentTree componentTree;
   private final ComponentDependencies deps;
+  private final ImmutableList<RootMetadata> rootsUsingDefaultComponents;
   private final Supplier<ImmutableSetMultimap<ClassName, ClassName>> scopesByComponent =
       memoize(this::getScopesByComponentUncached);
   private final Supplier<TestRootMetadata> testRootMetadata =
@@ -78,12 +100,14 @@
       Root root,
       ComponentTree componentTree,
       ComponentDependencies deps,
+      ImmutableList<RootMetadata> rootsUsingDefaultComponents,
       ProcessingEnvironment env) {
     this.root = root;
     this.env = env;
     this.elements = env.getElementUtils();
     this.componentTree = componentTree;
     this.deps = deps;
+    this.rootsUsingDefaultComponents = rootsUsingDefaultComponents;
   }
 
   public Root root() {
@@ -102,9 +126,25 @@
     return deps.modules().get(componentName, root.classname(), root.isTestRoot());
   }
 
+  /**
+   * Returns {@code true} if this is a test root that provides no test-specific dependencies or sets
+   * other options that would prevent it from sharing components with other test roots.
+   */
+  // TODO(groakley): Allow more tests to share modules, e.g. tests that uninstall the same module.
+  // In that case, this might instead return which shared dep grouping should be used.
+  public boolean canShareTestComponents() {
+    return isSharedTestComponentsEnabled(env)
+        && root.isTestRoot()
+        && !deps.includesTestDeps(root.classname());
+  }
+
   public ImmutableSet<TypeName> entryPoints(ClassName componentName) {
     return ImmutableSet.<TypeName>builder()
         .addAll(getUserDefinedEntryPoints(componentName))
+        .add(
+            root.isTestRoot() && componentName.equals(ClassNames.SINGLETON_COMPONENT)
+                ? ClassNames.TEST_SINGLETON_COMPONENT
+                : ClassNames.GENERATED_COMPONENT)
         .add(componentName)
         .build();
   }
@@ -128,6 +168,7 @@
   }
 
   public TestRootMetadata testRootMetadata() {
+    checkState(!root.isDefaultRoot(), "The default root does not have TestRootMetadata!");
     return testRootMetadata.get();
   }
 
@@ -149,7 +190,7 @@
     for (ComponentDescriptor componentDescriptor : componentTree.getComponentDescriptors()) {
       ClassName componentName = componentDescriptor.component();
       for (TypeElement extraModule : modulesThatDaggerCannotConstruct(componentName)) {
-        if (root.type().isTestRoot() && !componentName.equals(ClassNames.SINGLETON_COMPONENT)) {
+        if (root.isTestRoot() && !componentName.equals(ClassNames.SINGLETON_COMPONENT)) {
           env.getMessager()
               .printMessage(
                   Diagnostic.Kind.ERROR,
@@ -157,7 +198,7 @@
                       + "static provision methods or have a visible, no-arg constructor. Found: "
                       + extraModule.getQualifiedName(),
                   root.element());
-        } else if (!root.type().isTestRoot()) {
+        } else if (!root.isTestRoot()) {
           env.getMessager()
               .printMessage(
                   Diagnostic.Kind.ERROR,
@@ -172,10 +213,19 @@
 
   private ImmutableSet<TypeName> getUserDefinedEntryPoints(ClassName componentName) {
     ImmutableSet.Builder<TypeName> entryPointSet = ImmutableSet.builder();
-    entryPointSet.add(ClassNames.GENERATED_COMPONENT);
-    for (TypeElement element :
-        deps.entryPoints().get(componentName, root.classname(), root.isTestRoot())) {
-      entryPointSet.add(ClassName.get(element));
+    if (root.isDefaultRoot() && !rootsUsingDefaultComponents.isEmpty()) {
+      // Add entry points for shared component
+      rootsUsingDefaultComponents.stream()
+          .flatMap(metadata -> metadata.entryPoints(componentName).stream())
+          .forEach(entryPointSet::add);
+    } else if (root.isDefaultRoot() && componentName.equals(ClassNames.SINGLETON_COMPONENT)) {
+      // We only do this for SingletonComponent because EarlyEntryPoints can only be installed
+      // in the SingletonComponent.
+      deps.earlyEntryPoints().forEach(entryPointSet::add);
+    } else {
+      deps.entryPoints().get(componentName, root.classname(), root.isTestRoot()).stream()
+          .map(ClassName::get)
+          .forEach(entryPointSet::add);
     }
     return entryPointSet.build();
   }
@@ -188,7 +238,7 @@
             .flatMap(descriptor -> descriptor.scopes().stream())
             .collect(toImmutableSet());
 
-    AliasOfs aliasOfs = new AliasOfs(env, defineComponentScopes);
+    AliasOfs aliasOfs = AliasOfs.create(env.getElementUtils(), defineComponentScopes);
 
     for (ComponentDescriptor componentDescriptor : componentTree.getComponentDescriptors()) {
       for (ClassName scope : componentDescriptor.scopes()) {
diff --git a/java/dagger/hilt/processor/internal/root/RootProcessor.java b/java/dagger/hilt/processor/internal/root/RootProcessor.java
index 8f8c948..1ee4446 100644
--- a/java/dagger/hilt/processor/internal/root/RootProcessor.java
+++ b/java/dagger/hilt/processor/internal/root/RootProcessor.java
@@ -17,8 +17,11 @@
 package dagger.hilt.processor.internal.root;
 
 import static com.google.common.base.Preconditions.checkState;
+import static dagger.hilt.processor.internal.HiltCompilerOptions.isCrossCompilationRootValidationDisabled;
+import static dagger.hilt.processor.internal.HiltCompilerOptions.isSharedTestComponentsEnabled;
 import static dagger.internal.codegen.extension.DaggerStreams.toImmutableList;
 import static dagger.internal.codegen.extension.DaggerStreams.toImmutableSet;
+import static java.util.Comparator.comparing;
 import static javax.lang.model.element.Modifier.PUBLIC;
 import static net.ltgt.gradle.incap.IncrementalAnnotationProcessorType.AGGREGATING;
 
@@ -28,16 +31,17 @@
 import com.google.common.collect.ImmutableSet;
 import com.squareup.javapoet.ClassName;
 import dagger.hilt.processor.internal.BaseProcessor;
-import dagger.hilt.processor.internal.ComponentTree;
+import dagger.hilt.processor.internal.ClassNames;
+import dagger.hilt.processor.internal.ComponentDescriptor;
+import dagger.hilt.processor.internal.ComponentNames;
 import dagger.hilt.processor.internal.ProcessorErrors;
 import dagger.hilt.processor.internal.aggregateddeps.ComponentDependencies;
 import dagger.hilt.processor.internal.definecomponent.DefineComponents;
 import dagger.hilt.processor.internal.generatesrootinput.GeneratesRootInputs;
 import java.io.IOException;
-import java.util.ArrayList;
 import java.util.Arrays;
+import java.util.Comparator;
 import java.util.HashSet;
-import java.util.List;
 import java.util.Set;
 import javax.annotation.processing.ProcessingEnvironment;
 import javax.annotation.processing.Processor;
@@ -50,9 +54,10 @@
 @IncrementalAnnotationProcessor(AGGREGATING)
 @AutoService(Processor.class)
 public final class RootProcessor extends BaseProcessor {
-  private final List<ClassName> rootNames = new ArrayList<>();
+  private static final Comparator<TypeElement> QUALIFIED_NAME_COMPARATOR =
+      comparing(TypeElement::getQualifiedName, (n1, n2) -> n1.toString().compareTo(n2.toString()));
+
   private final Set<ClassName> processed = new HashSet<>();
-  private boolean isTestEnv;
   // TODO(bcorso): Consider using a Dagger component to create/scope these objects
   private final DefineComponents defineComponents = DefineComponents.create();
   private GeneratesRootInputs generatesRootInputs;
@@ -76,25 +81,13 @@
   @Override
   public void processEach(TypeElement annotation, Element element) throws Exception {
     TypeElement rootElement = MoreElements.asType(element);
-    boolean isTestRoot = RootType.of(getProcessingEnv(), rootElement).isTestRoot();
-    checkState(
-        rootNames.isEmpty() || isTestEnv == isTestRoot,
-        "Cannot mix test roots with non-test roots:"
-            + "\n\tNon-Test Roots: %s"
-            + "\n\tTest Roots: %s",
-        isTestRoot ? rootNames : rootElement,
-        isTestRoot ? rootElement : rootNames);
-    isTestEnv = isTestRoot;
-
-    rootNames.add(ClassName.get(rootElement));
-    if (isTestEnv) {
+    RootType rootType = RootType.of(rootElement);
+    if (rootType.isTestRoot()) {
       new TestInjectorGenerator(
-          getProcessingEnv(),
-          TestRootMetadata.of(getProcessingEnv(), rootElement)).generate();
-    } else {
-      ProcessorErrors.checkState(
-          rootNames.size() <= 1, element, "More than one root found: %s", rootNames);
+              getProcessingEnv(), TestRootMetadata.of(getProcessingEnv(), rootElement))
+          .generate();
     }
+    new AggregatedRootGenerator(rootElement, annotation, getProcessingEnv()).generate();
   }
 
   @Override
@@ -114,14 +107,22 @@
       return;
     }
 
-    ImmutableList<Root> rootsToProcess =
-        rootNames.stream()
-            .filter(rootName -> !processed.contains(rootName))
-            // We create a new root element each round to avoid the jdk8 bug where
-            // TypeElement.equals does not work for elements across processing rounds.
-            .map(rootName -> getElementUtils().getTypeElement(rootName.toString()))
+    ImmutableSet<Root> allRoots =
+        AggregatedRootMetadata.from(getElementUtils()).stream()
+            .map(metadata -> Root.create(metadata.rootElement(), getProcessingEnv()))
+            .collect(toImmutableSet());
+
+    ImmutableSet<Root> processedRoots =
+        ProcessedRootSentinelMetadata.from(getElementUtils()).stream()
+            .flatMap(metadata -> metadata.rootElements().stream())
             .map(rootElement -> Root.create(rootElement, getProcessingEnv()))
-            .collect(toImmutableList());
+            .collect(toImmutableSet());
+
+    ImmutableSet<Root> rootsToProcess =
+        allRoots.stream()
+            .filter(root -> !processedRoots.contains(root))
+            .filter(root -> !processed.contains(rootName(root)))
+            .collect(toImmutableSet());
 
     if (rootsToProcess.isEmpty()) {
       // Skip further processing since there's no roots that need processing.
@@ -132,40 +133,163 @@
     // all roots. We should consider if it's worth trying to continue processing for other
     // roots. At the moment, I think it's rare that if one root failed the others would not.
     try {
-      ComponentTree tree = defineComponents.getComponentTree(getElementUtils());
-      ComponentDependencies deps = ComponentDependencies.from(
-          tree.getComponentDescriptors(), getElementUtils());
+      validateRoots(allRoots, rootsToProcess);
+
+      boolean isTestEnv = rootsToProcess.stream().anyMatch(Root::isTestRoot);
+      ComponentNames componentNames =
+          isTestEnv && isSharedTestComponentsEnabled(getProcessingEnv())
+              ? ComponentNames.withRenamingIntoPackage(
+                  ClassNames.DEFAULT_ROOT.packageName(),
+                  rootsToProcess.stream().map(Root::element).collect(toImmutableList()))
+              : ComponentNames.withoutRenaming();
+
+      ImmutableSet<ComponentDescriptor> componentDescriptors =
+          defineComponents.getComponentDescriptors(getElementUtils());
+      ComponentTree tree = ComponentTree.from(componentDescriptors);
+      ComponentDependencies deps =
+          ComponentDependencies.from(componentDescriptors, getElementUtils());
       ImmutableList<RootMetadata> rootMetadatas =
           rootsToProcess.stream()
               .map(root -> RootMetadata.create(root, tree, deps, getProcessingEnv()))
               .collect(toImmutableList());
 
       for (RootMetadata rootMetadata : rootMetadatas) {
-        setProcessingState(rootMetadata.root());
-        generateComponents(rootMetadata);
+        if (!rootMetadata.canShareTestComponents()) {
+          generateComponents(rootMetadata, componentNames);
+        }
       }
 
       if (isTestEnv) {
-        generateTestComponentData(rootMetadatas);
+        ImmutableList<RootMetadata> rootsThatCanShareComponents =
+            rootMetadatas.stream()
+                .filter(RootMetadata::canShareTestComponents)
+                .collect(toImmutableList());
+        generateTestComponentData(rootMetadatas, componentNames);
+        if (deps.hasEarlyEntryPoints() || !rootsThatCanShareComponents.isEmpty()) {
+          Root defaultRoot = Root.createDefaultRoot(getProcessingEnv());
+          generateComponents(
+              RootMetadata.createForDefaultRoot(
+                  defaultRoot, rootsThatCanShareComponents, tree, deps, getProcessingEnv()),
+              componentNames);
+          EarlySingletonComponentCreatorGenerator.generate(getProcessingEnv());
+        }
       }
     } catch (Exception e) {
       for (Root root : rootsToProcess) {
-        processed.add(root.classname());
+        processed.add(rootName(root));
       }
       throw e;
+    } finally {
+      rootsToProcess.forEach(this::setProcessingState);
+      // Calculate the roots processed in this round. We do this in the finally-block rather than in
+      // the try-block because the catch-block can change the processing state.
+      ImmutableSet<Root> rootsProcessedInRound =
+          rootsToProcess.stream()
+              // Only add a sentinel for processed roots. Skip preprocessed roots since those will
+              // will be processed in the next round.
+              .filter(root -> processed.contains(rootName(root)))
+              .collect(toImmutableSet());
+      for (Root root : rootsProcessedInRound) {
+        new ProcessedRootSentinelGenerator(rootElement(root), getProcessingEnv()).generate();
+      }
+    }
+  }
+
+  private void validateRoots(ImmutableSet<Root> allRoots, ImmutableSet<Root> rootsToProcess) {
+
+    ImmutableSet<TypeElement> rootElementsToProcess =
+        rootsToProcess.stream()
+            .map(Root::element)
+            .sorted(QUALIFIED_NAME_COMPARATOR)
+            .collect(toImmutableSet());
+
+    ImmutableSet<TypeElement> appRootElementsToProcess =
+        rootsToProcess.stream()
+            .filter(root -> !root.isTestRoot())
+            .map(Root::element)
+            .sorted(QUALIFIED_NAME_COMPARATOR)
+            .collect(toImmutableSet());
+
+    // Perform validation between roots in this compilation unit.
+    if (!appRootElementsToProcess.isEmpty()) {
+      ImmutableSet<TypeElement> testRootElementsToProcess =
+          rootsToProcess.stream()
+              .filter(Root::isTestRoot)
+              .map(Root::element)
+              .sorted(QUALIFIED_NAME_COMPARATOR)
+              .collect(toImmutableSet());
+
+      ProcessorErrors.checkState(
+          testRootElementsToProcess.isEmpty(),
+          "Cannot process test roots and app roots in the same compilation unit:"
+              + "\n\tApp root in this compilation unit: %s"
+              + "\n\tTest roots in this compilation unit: %s",
+          appRootElementsToProcess,
+          testRootElementsToProcess);
+
+      ProcessorErrors.checkState(
+          appRootElementsToProcess.size() == 1,
+          "Cannot process multiple app roots in the same compilation unit: %s",
+          appRootElementsToProcess);
+    }
+
+    // Perform validation across roots previous compilation units.
+    if (!isCrossCompilationRootValidationDisabled(rootElementsToProcess, getProcessingEnv())) {
+      ImmutableSet<TypeElement> processedTestRootElements =
+          allRoots.stream()
+              .filter(Root::isTestRoot)
+              .filter(root -> !rootsToProcess.contains(root))
+              .map(Root::element)
+              .sorted(QUALIFIED_NAME_COMPARATOR)
+              .collect(toImmutableSet());
+
+      // TODO(b/185742783): Add an explanation or link to docs to explain why we're forbidding this.
+      ProcessorErrors.checkState(
+          processedTestRootElements.isEmpty(),
+          "Cannot process new roots when there are test roots from a previous compilation unit:"
+              + "\n\tTest roots from previous compilation unit: %s"
+              + "\n\tAll roots from this compilation unit: %s",
+          processedTestRootElements,
+          rootElementsToProcess);
+
+      ImmutableSet<TypeElement> processedAppRootElements =
+          allRoots.stream()
+              .filter(root -> !root.isTestRoot())
+              .filter(root -> !rootsToProcess.contains(root))
+              .map(Root::element)
+              .sorted(QUALIFIED_NAME_COMPARATOR)
+              .collect(toImmutableSet());
+
+      ProcessorErrors.checkState(
+          processedAppRootElements.isEmpty() || appRootElementsToProcess.isEmpty(),
+          "Cannot process app roots in this compilation unit since there are app roots in a "
+              + "previous compilation unit:"
+              + "\n\tApp roots in previous compilation unit: %s"
+              + "\n\tApp roots in this compilation unit: %s",
+          processedAppRootElements,
+          appRootElementsToProcess);
     }
   }
 
   private void setProcessingState(Root root) {
-    processed.add(root.classname());
+    processed.add(rootName(root));
   }
 
-  private void generateComponents(RootMetadata rootMetadata) throws IOException {
-    RootGenerator.generate(rootMetadata, getProcessingEnv());
+  private ClassName rootName(Root root) {
+    return ClassName.get(rootElement(root));
   }
 
-  private void generateTestComponentData(ImmutableList<RootMetadata> rootMetadatas)
+  private TypeElement rootElement(Root root) {
+    return root.element();
+  }
+
+  private void generateComponents(RootMetadata rootMetadata, ComponentNames componentNames)
       throws IOException {
+    RootGenerator.generate(rootMetadata, componentNames, getProcessingEnv());
+  }
+
+  private void generateTestComponentData(
+      ImmutableList<RootMetadata> rootMetadatas, ComponentNames componentNames) throws IOException {
     for (RootMetadata rootMetadata : rootMetadatas) {
       // TODO(bcorso): Consider moving this check earlier into processEach.
       TypeElement testElement = rootMetadata.testRootMetadata().testElement();
@@ -174,8 +298,7 @@
           testElement,
           "Hilt tests must be public, but found: %s",
           testElement);
-      new TestComponentDataGenerator(getProcessingEnv(), rootMetadata).generate();
+      new TestComponentDataGenerator(getProcessingEnv(), rootMetadata, componentNames).generate();
     }
-    new TestComponentDataSupplierGenerator(getProcessingEnv(), rootMetadatas).generate();
   }
 }
diff --git a/java/dagger/hilt/processor/internal/root/RootType.java b/java/dagger/hilt/processor/internal/root/RootType.java
index 3545231..807f71d 100644
--- a/java/dagger/hilt/processor/internal/root/RootType.java
+++ b/java/dagger/hilt/processor/internal/root/RootType.java
@@ -19,7 +19,6 @@
 import com.squareup.javapoet.ClassName;
 import dagger.hilt.processor.internal.ClassNames;
 import dagger.hilt.processor.internal.Processors;
-import javax.annotation.processing.ProcessingEnvironment;
 import javax.lang.model.element.TypeElement;
 
 /** The valid root types for Hilt applications. */
@@ -47,7 +46,7 @@
     return annotation;
   }
 
-  public static RootType of(ProcessingEnvironment env, TypeElement element) {
+  public static RootType of(TypeElement element) {
     if (Processors.hasAnnotation(element, ClassNames.HILT_ANDROID_APP)) {
       return ROOT;
     } else if (Processors.hasAnnotation(element, ClassNames.HILT_ANDROID_TEST)) {
diff --git a/java/dagger/hilt/processor/internal/root/TestComponentDataGenerator.java b/java/dagger/hilt/processor/internal/root/TestComponentDataGenerator.java
index 284f8cd..101c124 100644
--- a/java/dagger/hilt/processor/internal/root/TestComponentDataGenerator.java
+++ b/java/dagger/hilt/processor/internal/root/TestComponentDataGenerator.java
@@ -20,6 +20,7 @@
 import static java.util.stream.Collectors.joining;
 import static javax.lang.model.element.Modifier.FINAL;
 import static javax.lang.model.element.Modifier.PRIVATE;
+import static javax.lang.model.element.Modifier.PROTECTED;
 import static javax.lang.model.element.Modifier.PUBLIC;
 import static javax.lang.model.element.Modifier.STATIC;
 import static javax.lang.model.util.ElementFilter.constructorsIn;
@@ -45,24 +46,28 @@
   private final ProcessingEnvironment processingEnv;
   private final RootMetadata rootMetadata;
   private final ClassName name;
+  private final ComponentNames componentNames;
 
   public TestComponentDataGenerator(
       ProcessingEnvironment processingEnv,
-      RootMetadata rootMetadata) {
+      RootMetadata rootMetadata,
+      ComponentNames componentNames) {
     this.processingEnv = processingEnv;
     this.rootMetadata = rootMetadata;
+    this.componentNames = componentNames;
     this.name =
         Processors.append(
             Processors.getEnclosedClassName(rootMetadata.testRootMetadata().testName()),
-            "_ComponentDataHolder");
+            "_TestComponentDataSupplier");
   }
 
   /**
    *
    *
    * <pre><code>{@code
-   * public final class FooTest_ComponentDataHolder {
-   *   public static TestComponentData get() {
+   * public final class FooTest_TestComponentDataSupplier extends TestComponentDataSupplier {
+   *   @Override
+   *   protected TestComponentData get() {
    *     return new TestComponentData(
    *         false, // waitForBindValue
    *         testInstance -> injectInternal(($1T) testInstance),
@@ -83,15 +88,15 @@
   public void generate() throws IOException {
     TypeSpec.Builder generator =
         TypeSpec.classBuilder(name)
+            .superclass(ClassNames.TEST_COMPONENT_DATA_SUPPLIER)
             .addModifiers(PUBLIC, FINAL)
-            .addMethod(MethodSpec.constructorBuilder().addModifiers(PRIVATE).build())
             .addMethod(getMethod())
             .addMethod(getTestInjectInternalMethod());
 
     Processors.addGeneratedAnnotation(
         generator, processingEnv, ClassNames.ROOT_PROCESSOR.toString());
 
-    JavaFile.builder(rootMetadata.testRootMetadata().testName().packageName(), generator.build())
+    JavaFile.builder(name.packageName(), generator.build())
         .build()
         .writeTo(processingEnv.getFiler());
   }
@@ -99,8 +104,11 @@
   private MethodSpec getMethod() {
     TypeElement testElement = rootMetadata.testRootMetadata().testElement();
     ClassName component =
-        ComponentNames.generatedComponent(
-            ClassName.get(testElement), ClassNames.SINGLETON_COMPONENT);
+        componentNames.generatedComponent(
+            rootMetadata.canShareTestComponents()
+                ? ClassNames.DEFAULT_ROOT
+                : ClassName.get(testElement),
+            ClassNames.SINGLETON_COMPONENT);
     ImmutableSet<TypeElement> daggerRequiredModules =
         rootMetadata.modulesThatDaggerCannotConstruct(ClassNames.SINGLETON_COMPONENT);
     ImmutableSet<TypeElement> hiltRequiredModules =
@@ -109,7 +117,7 @@
             .collect(toImmutableSet());
 
     return MethodSpec.methodBuilder("get")
-        .addModifiers(PUBLIC, STATIC)
+        .addModifiers(PROTECTED)
         .returns(ClassNames.TEST_COMPONENT_DATA)
         .addStatement(
             "return new $T($L, $L, $L, $L, $L)",
@@ -202,14 +210,14 @@
             AnnotationSpec.builder(SuppressWarnings.class)
                 .addMember("value", "$S", "unchecked")
                 .build())
-        .addStatement("$L.injectTest(testInstance)", getInjector(testElement))
+        .addStatement(callInjectTest(testElement))
         .build();
   }
 
-  private static CodeBlock getInjector(TypeElement testElement) {
+  private CodeBlock callInjectTest(TypeElement testElement) {
     return CodeBlock.of(
-        "(($T) (($T) $T.getApplicationContext()).generatedComponent())",
-        ClassNames.TEST_INJECTOR,
+        "(($T) (($T) $T.getApplicationContext()).generatedComponent()).injectTest(testInstance)",
+        rootMetadata.testRootMetadata().testInjectorName(),
         ClassNames.GENERATED_COMPONENT_MANAGER,
         ClassNames.APPLICATION_PROVIDER);
   }
diff --git a/java/dagger/hilt/processor/internal/root/TestComponentDataSupplierGenerator.java b/java/dagger/hilt/processor/internal/root/TestComponentDataSupplierGenerator.java
deleted file mode 100644
index e5a83b6..0000000
--- a/java/dagger/hilt/processor/internal/root/TestComponentDataSupplierGenerator.java
+++ /dev/null
@@ -1,119 +0,0 @@
-/*
- * Copyright (C) 2020 The Dagger Authors.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package dagger.hilt.processor.internal.root;
-
-import static javax.lang.model.element.Modifier.FINAL;
-import static javax.lang.model.element.Modifier.PRIVATE;
-import static javax.lang.model.element.Modifier.PROTECTED;
-import static javax.lang.model.element.Modifier.PUBLIC;
-
-import com.google.common.collect.ImmutableList;
-import com.squareup.javapoet.ClassName;
-import com.squareup.javapoet.FieldSpec;
-import com.squareup.javapoet.JavaFile;
-import com.squareup.javapoet.MethodSpec;
-import com.squareup.javapoet.ParameterizedTypeName;
-import com.squareup.javapoet.TypeName;
-import com.squareup.javapoet.TypeSpec;
-import com.squareup.javapoet.WildcardTypeName;
-import dagger.hilt.processor.internal.ClassNames;
-import dagger.hilt.processor.internal.Processors;
-import java.io.IOException;
-import javax.annotation.processing.ProcessingEnvironment;
-
-/** Generates an implementation of {@link dagger.hilt.android.internal.TestComponentDataSupplier} */
-public final class TestComponentDataSupplierGenerator {
-  private static final ClassName TEST_COMPONENT_DATA_SUPPLIER_IMPL =
-      ClassName.get("dagger.hilt.android.internal.testing", "TestComponentDataSupplierImpl");
-  private static final ParameterizedTypeName CLASS_TYPE =
-      ParameterizedTypeName.get(ClassNames.CLASS, WildcardTypeName.subtypeOf(TypeName.OBJECT));
-  private static final ParameterizedTypeName TEST_COMPONENT_DATA_MAP_TYPE =
-      ParameterizedTypeName.get(ClassNames.MAP, CLASS_TYPE, ClassNames.TEST_COMPONENT_DATA);
-
-  private final ProcessingEnvironment processingEnv;
-  private final ImmutableList<RootMetadata> rootMetadatas;
-
-  public TestComponentDataSupplierGenerator(
-      ProcessingEnvironment processingEnv,
-      ImmutableList<RootMetadata> rootMetadatas) {
-    this.processingEnv = processingEnv;
-    this.rootMetadatas = rootMetadatas;
-  }
-
-  /**
-   * <pre><code>{@code
-   * public final class TestComponentDataSupplierImpl extends TestComponentDataSupplier {
-   *   private final Map<Class<?>, TestComponentData> testComponentDataMap = new HashMap<>();
-   *
-   *   protected TestComponentDataSupplierImpl() {
-   *     testComponentDataMap.put(FooTest.class, new FooTest_ComponentData());
-   *     testComponentDataMap.put(BarTest.class, new BarTest_ComponentData());
-   *     ...
-   *   }
-   *
-   *   @Override
-   *   protected Map<Class<?>, TestComponentData> get() {
-   *     return testComponentDataMap;
-   *   }
-   * }
-   * }</code></pre>
-   */
-  public void generate() throws IOException {
-    TypeSpec.Builder generator =
-        TypeSpec.classBuilder(TEST_COMPONENT_DATA_SUPPLIER_IMPL)
-            .addModifiers(PUBLIC, FINAL)
-            .superclass(ClassNames.TEST_COMPONENT_DATA_SUPPLIER)
-            .addField(
-                FieldSpec.builder(
-                        TEST_COMPONENT_DATA_MAP_TYPE, "testComponentDataMap", PRIVATE, FINAL)
-                    .initializer("new $T<>($L)", ClassNames.HASH_MAP, rootMetadatas.size())
-                    .build())
-            .addMethod(constructor())
-            .addMethod(getMethod());
-
-    Processors.addGeneratedAnnotation(
-        generator, processingEnv, ClassNames.ROOT_PROCESSOR.toString());
-
-    JavaFile.builder(TEST_COMPONENT_DATA_SUPPLIER_IMPL.packageName(), generator.build())
-        .build()
-        .writeTo(processingEnv.getFiler());
-  }
-
-
-  private MethodSpec constructor() {
-    MethodSpec.Builder constructor = MethodSpec.constructorBuilder();
-    for (RootMetadata rootMetadata : rootMetadatas) {
-      ClassName testName = rootMetadata.testRootMetadata().testName();
-      ClassName testComponentDataHolderName =
-          Processors.append(Processors.getEnclosedClassName(testName), "_ComponentDataHolder");
-      constructor.addStatement(
-          "testComponentDataMap.put($T.class, $T.get())",
-          testName,
-          testComponentDataHolderName);
-    }
-    return constructor.build();
-  }
-
-  private MethodSpec getMethod() {
-    return MethodSpec.methodBuilder("get")
-        .addAnnotation(Override.class)
-        .addModifiers(PROTECTED)
-        .returns(TEST_COMPONENT_DATA_MAP_TYPE)
-        .addStatement("return testComponentDataMap")
-        .build();
-  }
-}
diff --git a/java/dagger/hilt/processor/internal/root/TestInjectorGenerator.java b/java/dagger/hilt/processor/internal/root/TestInjectorGenerator.java
index fc97fed..b7200e7 100644
--- a/java/dagger/hilt/processor/internal/root/TestInjectorGenerator.java
+++ b/java/dagger/hilt/processor/internal/root/TestInjectorGenerator.java
@@ -20,7 +20,6 @@
 import com.squareup.javapoet.ClassName;
 import com.squareup.javapoet.JavaFile;
 import com.squareup.javapoet.MethodSpec;
-import com.squareup.javapoet.ParameterizedTypeName;
 import com.squareup.javapoet.TypeSpec;
 import dagger.hilt.processor.internal.ClassNames;
 import dagger.hilt.processor.internal.Processors;
@@ -41,7 +40,9 @@
 
   // @GeneratedEntryPoint
   // @InstallIn(SingletonComponent.class)
-  // public interface FooTest_GeneratedInjector extends TestInjector<FooTest> {}
+  // public interface FooTest_GeneratedInjector {
+  //   void injectTest(FooTest fooTest);
+  // }
   public void generate() throws IOException {
     TypeSpec.Builder builder =
         TypeSpec.interfaceBuilder(metadata.testInjectorName())
@@ -53,11 +54,8 @@
                     .addMember("value", "$T.class", installInComponent(metadata.testElement()))
                     .build())
             .addModifiers(Modifier.PUBLIC)
-            .addSuperinterface(
-                ParameterizedTypeName.get(ClassNames.TEST_INJECTOR, metadata.testName()))
             .addMethod(
                 MethodSpec.methodBuilder("injectTest")
-                    .addAnnotation(Override.class)
                     .addModifiers(Modifier.PUBLIC, Modifier.ABSTRACT)
                     .addParameter(
                         metadata.testName(),
diff --git a/java/dagger/hilt/processor/internal/uninstallmodules/AggregatedUninstallModulesGenerator.java b/java/dagger/hilt/processor/internal/uninstallmodules/AggregatedUninstallModulesGenerator.java
new file mode 100644
index 0000000..654a690
--- /dev/null
+++ b/java/dagger/hilt/processor/internal/uninstallmodules/AggregatedUninstallModulesGenerator.java
@@ -0,0 +1,64 @@
+/*
+ * Copyright (C) 2021 The Dagger Authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package dagger.hilt.processor.internal.uninstallmodules;
+
+import com.google.common.collect.ImmutableList;
+import com.squareup.javapoet.AnnotationSpec;
+import dagger.hilt.processor.internal.ClassNames;
+import dagger.hilt.processor.internal.Processors;
+import java.io.IOException;
+import javax.annotation.processing.ProcessingEnvironment;
+import javax.lang.model.element.TypeElement;
+
+/**
+ * Generates an {@link dagger.hilt.android.internal.uninstallmodules.AggregatedUninstallModules}
+ * annotation.
+ */
+final class AggregatedUninstallModulesGenerator {
+
+  private final ProcessingEnvironment env;
+  private final TypeElement testElement;
+  private final ImmutableList<TypeElement> uninstallModuleElements;
+
+  AggregatedUninstallModulesGenerator(
+      TypeElement testElement,
+      ImmutableList<TypeElement> uninstallModuleElements,
+      ProcessingEnvironment env) {
+    this.testElement = testElement;
+    this.uninstallModuleElements = uninstallModuleElements;
+    this.env = env;
+  }
+
+  void generate() throws IOException {
+    Processors.generateAggregatingClass(
+        ClassNames.AGGREGATED_UNINSTALL_MODULES_PACKAGE,
+        aggregatedUninstallModulesAnnotation(),
+        testElement,
+        getClass(),
+        env);
+  }
+
+  private AnnotationSpec aggregatedUninstallModulesAnnotation() {
+    AnnotationSpec.Builder builder =
+        AnnotationSpec.builder(ClassNames.AGGREGATED_UNINSTALL_MODULES);
+    builder.addMember("test", "$S", testElement.getQualifiedName());
+    uninstallModuleElements.stream()
+        .map(TypeElement::getQualifiedName)
+        .forEach(uninstallModule -> builder.addMember("uninstallModules", "$S", uninstallModule));
+    return builder.build();
+  }
+}
diff --git a/java/dagger/hilt/processor/internal/uninstallmodules/AggregatedUninstallModulesMetadata.java b/java/dagger/hilt/processor/internal/uninstallmodules/AggregatedUninstallModulesMetadata.java
new file mode 100644
index 0000000..eee7849
--- /dev/null
+++ b/java/dagger/hilt/processor/internal/uninstallmodules/AggregatedUninstallModulesMetadata.java
@@ -0,0 +1,75 @@
+/*
+ * Copyright (C) 2021 The Dagger Authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package dagger.hilt.processor.internal.uninstallmodules;
+
+import static dagger.internal.codegen.extension.DaggerStreams.toImmutableList;
+import static dagger.internal.codegen.extension.DaggerStreams.toImmutableSet;
+
+import com.google.auto.value.AutoValue;
+import com.google.common.collect.ImmutableList;
+import com.google.common.collect.ImmutableMap;
+import com.google.common.collect.ImmutableSet;
+import dagger.hilt.processor.internal.AggregatedElements;
+import dagger.hilt.processor.internal.AnnotationValues;
+import dagger.hilt.processor.internal.ClassNames;
+import dagger.hilt.processor.internal.Processors;
+import javax.lang.model.element.AnnotationMirror;
+import javax.lang.model.element.AnnotationValue;
+import javax.lang.model.element.TypeElement;
+import javax.lang.model.util.Elements;
+
+/**
+ * A class that represents the values stored in an
+ * {@link dagger.hilt.android.internal.uninstallmodules.AggregatedUninstallModules} annotation.
+ */
+@AutoValue
+public abstract class AggregatedUninstallModulesMetadata {
+
+  /** Returns the test annotated with {@link dagger.hilt.android.testing.UninstallModules}. */
+  public abstract TypeElement testElement();
+
+  /**
+   * Returns the list of uninstall modules in {@link dagger.hilt.android.testing.UninstallModules}.
+   */
+  public abstract ImmutableList<TypeElement> uninstallModuleElements();
+
+  /** Returns all aggregated deps in the aggregating package mapped by the top-level element. */
+  public static ImmutableSet<AggregatedUninstallModulesMetadata> from(Elements elements) {
+    return AggregatedElements.from(
+            ClassNames.AGGREGATED_UNINSTALL_MODULES_PACKAGE,
+            ClassNames.AGGREGATED_UNINSTALL_MODULES,
+            elements)
+        .stream()
+        .map(aggregatedElement -> create(aggregatedElement, elements))
+        .collect(toImmutableSet());
+  }
+
+  private static AggregatedUninstallModulesMetadata create(TypeElement element, Elements elements) {
+    AnnotationMirror annotationMirror =
+        Processors.getAnnotationMirror(element, ClassNames.AGGREGATED_UNINSTALL_MODULES);
+
+    ImmutableMap<String, AnnotationValue> values =
+        Processors.getAnnotationValues(elements, annotationMirror);
+
+    return new AutoValue_AggregatedUninstallModulesMetadata(
+        elements.getTypeElement(AnnotationValues.getString(values.get("test"))),
+        AnnotationValues.getAnnotationValues(values.get("uninstallModules")).stream()
+            .map(AnnotationValues::getString)
+            .map(elements::getTypeElement)
+            .collect(toImmutableList()));
+  }
+}
diff --git a/java/dagger/hilt/android/processor/internal/uninstallmodules/BUILD b/java/dagger/hilt/processor/internal/uninstallmodules/BUILD
similarity index 67%
rename from java/dagger/hilt/android/processor/internal/uninstallmodules/BUILD
rename to java/dagger/hilt/processor/internal/uninstallmodules/BUILD
index 73c4606..4f944be 100644
--- a/java/dagger/hilt/android/processor/internal/uninstallmodules/BUILD
+++ b/java/dagger/hilt/processor/internal/uninstallmodules/BUILD
@@ -20,13 +20,14 @@
 java_plugin(
     name = "processor",
     generates_api = 1,
-    processor_class = "dagger.hilt.android.processor.internal.uninstallmodules.UninstallModulesProcessor",
+    processor_class = "dagger.hilt.processor.internal.uninstallmodules.UninstallModulesProcessor",
     deps = [":processor_lib"],
 )
 
 java_library(
     name = "processor_lib",
     srcs = [
+        "AggregatedUninstallModulesGenerator.java",
         "UninstallModulesProcessor.java",
     ],
     deps = [
@@ -38,10 +39,26 @@
         "//java/dagger/internal/guava:collect",
         "@google_bazel_common//third_party/java/auto:service",
         "@google_bazel_common//third_party/java/incap",
+        "@google_bazel_common//third_party/java/javapoet",
         "@maven//:com_google_auto_auto_common",
     ],
 )
 
+java_library(
+    name = "aggregated_uninstall_modules_metadata",
+    srcs = [
+        "AggregatedUninstallModulesMetadata.java",
+    ],
+    deps = [
+        "//java/dagger/hilt/processor/internal:aggregated_elements",
+        "//java/dagger/hilt/processor/internal:classnames",
+        "//java/dagger/hilt/processor/internal:processors",
+        "//java/dagger/internal/codegen/extension",
+        "//java/dagger/internal/guava:collect",
+        "@google_bazel_common//third_party/java/auto:value",
+    ],
+)
+
 filegroup(
     name = "srcs_filegroup",
     srcs = glob(["*"]),
diff --git a/java/dagger/hilt/android/processor/internal/uninstallmodules/UninstallModulesProcessor.java b/java/dagger/hilt/processor/internal/uninstallmodules/UninstallModulesProcessor.java
similarity index 62%
rename from java/dagger/hilt/android/processor/internal/uninstallmodules/UninstallModulesProcessor.java
rename to java/dagger/hilt/processor/internal/uninstallmodules/UninstallModulesProcessor.java
index e92f0f0..c7e528d 100644
--- a/java/dagger/hilt/android/processor/internal/uninstallmodules/UninstallModulesProcessor.java
+++ b/java/dagger/hilt/processor/internal/uninstallmodules/UninstallModulesProcessor.java
@@ -14,7 +14,7 @@
  * limitations under the License.
  */
 
-package dagger.hilt.android.processor.internal.uninstallmodules;
+package dagger.hilt.processor.internal.uninstallmodules;
 
 import static dagger.internal.codegen.extension.DaggerStreams.toImmutableList;
 import static net.ltgt.gradle.incap.IncrementalAnnotationProcessorType.ISOLATING;
@@ -23,6 +23,7 @@
 import com.google.auto.service.AutoService;
 import com.google.common.collect.ImmutableList;
 import com.google.common.collect.ImmutableSet;
+import com.squareup.javapoet.ClassName;
 import dagger.hilt.processor.internal.BaseProcessor;
 import dagger.hilt.processor.internal.ClassNames;
 import dagger.hilt.processor.internal.ProcessorErrors;
@@ -40,7 +41,7 @@
 
   @Override
   public Set<String> getSupportedAnnotationTypes() {
-    return ImmutableSet.of(ClassNames.IGNORE_MODULES.toString());
+    return ImmutableSet.of(ClassNames.UNINSTALL_MODULES.toString());
   }
 
   @Override
@@ -56,12 +57,24 @@
         ClassNames.HILT_ANDROID_TEST.simpleName(),
         element);
 
-    ImmutableList<TypeElement> invalidModules =
+    TypeElement testElement = MoreElements.asType(element);
+    ImmutableList<TypeElement> uninstallModules =
         Processors.getAnnotationClassValues(
-                getElementUtils(),
-                Processors.getAnnotationMirror(element, ClassNames.IGNORE_MODULES),
-                "value")
-            .stream()
+            getElementUtils(),
+            Processors.getAnnotationMirror(testElement, ClassNames.UNINSTALL_MODULES),
+            "value");
+
+    checkModulesHaveInstallIn(testElement, uninstallModules);
+    checkModulesDontOriginateFromTest(testElement, uninstallModules);
+
+    new AggregatedUninstallModulesGenerator(testElement, uninstallModules, getProcessingEnv())
+        .generate();
+  }
+
+  private void checkModulesHaveInstallIn(
+      TypeElement testElement, ImmutableList<TypeElement> uninstallModules) {
+    ImmutableList<TypeElement> invalidModules =
+        uninstallModules.stream()
             .filter(
                 module ->
                     !(Processors.hasAnnotation(module, ClassNames.MODULE)
@@ -71,10 +84,27 @@
     ProcessorErrors.checkState(
         invalidModules.isEmpty(),
         // TODO(b/152801981): Point to the annotation value rather than the annotated element.
-        element,
-        "@%s should only include modules annotated with both @Module and @InstallIn, but found: "
-          + "%s.",
-        annotation.getSimpleName(),
+        testElement,
+        "@UninstallModules should only include modules annotated with both @Module and @InstallIn, "
+            + "but found: %s.",
+        invalidModules);
+  }
+
+  private void checkModulesDontOriginateFromTest(
+      TypeElement testElement, ImmutableList<TypeElement> uninstallModules) {
+    ImmutableList<ClassName> invalidModules =
+        uninstallModules.stream()
+            .filter(
+                module ->
+                    Processors.getOriginatingTestElement(module, getElementUtils()).isPresent())
+            .map(ClassName::get)
+            .collect(toImmutableList());
+
+    ProcessorErrors.checkState(
+        invalidModules.isEmpty(),
+        // TODO(b/152801981): Point to the annotation value rather than the annotated element.
+        testElement,
+        "@UninstallModules should not contain test modules, but found: %s",
         invalidModules);
   }
 }
diff --git a/java/dagger/hilt/proguard-rules.pro b/java/dagger/hilt/proguard-rules.pro
new file mode 100644
index 0000000..3b953df
--- /dev/null
+++ b/java/dagger/hilt/proguard-rules.pro
@@ -0,0 +1,3 @@
+# Keep for the reflective cast done in EntryPoints.
+# See b/183070411#comment4 for more info.
+-keep,allowobfuscation,allowshrinking @dagger.hilt.EntryPoint class *
\ No newline at end of file
diff --git a/java/dagger/internal/DaggerGenerated.java b/java/dagger/internal/DaggerGenerated.java
new file mode 100644
index 0000000..6a872e0
--- /dev/null
+++ b/java/dagger/internal/DaggerGenerated.java
@@ -0,0 +1,30 @@
+/*
+ * Copyright (C) 2021 The Dagger Authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package dagger.internal;
+
+import static java.lang.annotation.ElementType.TYPE;
+import static java.lang.annotation.RetentionPolicy.CLASS;
+
+import java.lang.annotation.Documented;
+import java.lang.annotation.Retention;
+import java.lang.annotation.Target;
+
+/** Annotates the top-level class of each Dagger generated source file. */
+@Documented
+@Retention(CLASS)
+@Target(TYPE)
+public @interface DaggerGenerated {}
diff --git a/java/dagger/internal/codegen/AssistedFactoryProcessingStep.java b/java/dagger/internal/codegen/AssistedFactoryProcessingStep.java
index abc0436..dbd2b33 100644
--- a/java/dagger/internal/codegen/AssistedFactoryProcessingStep.java
+++ b/java/dagger/internal/codegen/AssistedFactoryProcessingStep.java
@@ -34,6 +34,7 @@
 import static javax.lang.model.element.Modifier.STATIC;
 
 import com.google.auto.common.MoreElements;
+import com.google.common.collect.ImmutableList;
 import com.google.common.collect.ImmutableSet;
 import com.squareup.javapoet.ClassName;
 import com.squareup.javapoet.CodeBlock;
@@ -137,7 +138,7 @@
       }
 
       ImmutableSet<ExecutableElement> abstractFactoryMethods =
-          AssistedInjectionAnnotations.assistedFactoryMethods(factory, elements, types);
+          AssistedInjectionAnnotations.assistedFactoryMethods(factory, elements);
 
       if (abstractFactoryMethods.isEmpty()) {
         report.addError(
@@ -226,11 +227,6 @@
     }
 
     @Override
-    public ClassName nameGeneratedType(ProvisionBinding binding) {
-      return generatedClassNameForBinding(binding);
-    }
-
-    @Override
     public Element originatingElement(ProvisionBinding binding) {
       return binding.bindingElement().get();
     }
@@ -268,10 +264,10 @@
     //   }
     // }
     @Override
-    public Optional<TypeSpec.Builder> write(ProvisionBinding binding) {
+    public ImmutableList<TypeSpec.Builder> topLevelTypes(ProvisionBinding binding) {
       TypeElement factory = asType(binding.bindingElement().get());
 
-      ClassName name = nameGeneratedType(binding);
+      ClassName name = generatedClassNameForBinding(binding);
       TypeSpec.Builder builder =
           TypeSpec.classBuilder(name)
               .addModifiers(PUBLIC, FINAL)
@@ -333,7 +329,7 @@
                       name,
                       delegateFactoryParam)
                   .build());
-      return Optional.of(builder);
+      return ImmutableList.of(builder);
     }
 
     /** Returns the generated factory {@link TypeName type} for an @AssistedInject constructor. */
diff --git a/java/dagger/internal/codegen/AssistedInjectProcessingStep.java b/java/dagger/internal/codegen/AssistedInjectProcessingStep.java
index 4f2f5b7..167c4ae 100644
--- a/java/dagger/internal/codegen/AssistedInjectProcessingStep.java
+++ b/java/dagger/internal/codegen/AssistedInjectProcessingStep.java
@@ -76,7 +76,10 @@
       for (AssistedParameter assistedParameter : assistedParameters) {
         if (!uniqueAssistedParameters.add(assistedParameter)) {
           report.addError(
-              "@AssistedInject constructor has duplicate @Assisted type: " + assistedParameter,
+              String.format("@AssistedInject constructor has duplicate @Assisted type: %s. "
+                  + "Consider setting an identifier on the parameter by using "
+                  + "@Assisted(\"identifier\") in both the factory and @AssistedInject constructor",
+                  assistedParameter),
               assistedParameter.variableElement());
         }
       }
diff --git a/java/dagger/internal/codegen/AssistedProcessingStep.java b/java/dagger/internal/codegen/AssistedProcessingStep.java
index 3173987..12a0d34 100644
--- a/java/dagger/internal/codegen/AssistedProcessingStep.java
+++ b/java/dagger/internal/codegen/AssistedProcessingStep.java
@@ -27,7 +27,6 @@
 import dagger.internal.codegen.binding.InjectionAnnotations;
 import dagger.internal.codegen.kotlin.KotlinMetadataUtil;
 import dagger.internal.codegen.langmodel.DaggerElements;
-import dagger.internal.codegen.langmodel.DaggerTypes;
 import dagger.internal.codegen.validation.TypeCheckingProcessingStep;
 import dagger.internal.codegen.validation.ValidationReport;
 import java.lang.annotation.Annotation;
@@ -47,7 +46,6 @@
   private final KotlinMetadataUtil kotlinMetadataUtil;
   private final InjectionAnnotations injectionAnnotations;
   private final DaggerElements elements;
-  private final DaggerTypes types;
   private final Messager messager;
 
   @Inject
@@ -55,13 +53,11 @@
       KotlinMetadataUtil kotlinMetadataUtil,
       InjectionAnnotations injectionAnnotations,
       DaggerElements elements,
-      DaggerTypes types,
       Messager messager) {
     super(MoreElements::asVariable);
     this.kotlinMetadataUtil = kotlinMetadataUtil;
     this.injectionAnnotations = injectionAnnotations;
     this.elements = elements;
-    this.types = types;
     this.messager = messager;
   }
 
@@ -113,7 +109,7 @@
       TypeElement enclosingElement = closestEnclosingTypeElement(element);
       return AssistedInjectionAnnotations.isAssistedFactoryType(enclosingElement)
           // This assumes we've already validated AssistedFactory and that a valid method exists.
-          && AssistedInjectionAnnotations.assistedFactoryMethod(enclosingElement, elements, types)
+          && AssistedInjectionAnnotations.assistedFactoryMethod(enclosingElement, elements)
               .equals(element);
     }
     return false;
diff --git a/java/dagger/internal/codegen/BUILD b/java/dagger/internal/codegen/BUILD
index be970be..e2f74e9 100644
--- a/java/dagger/internal/codegen/BUILD
+++ b/java/dagger/internal/codegen/BUILD
@@ -127,6 +127,8 @@
         "genclass=${package}.Dagger${outerclasses}${classname}",
         "annotation=dagger.producers.ProductionComponent;" +
         "genclass=${package}.Dagger${outerclasses}${classname}",
+        "annotation=dagger.MapKey;" +
+        "genclass=${package}.${outerclasses}${classname}Creator",
     ],
     deps = [":processor"],
 )
diff --git a/java/dagger/internal/codegen/ProcessingEnvironmentModule.java b/java/dagger/internal/codegen/ProcessingEnvironmentModule.java
index 8a535fe..120714b 100644
--- a/java/dagger/internal/codegen/ProcessingEnvironmentModule.java
+++ b/java/dagger/internal/codegen/ProcessingEnvironmentModule.java
@@ -22,15 +22,18 @@
 import dagger.Provides;
 import dagger.Reusable;
 import dagger.internal.codegen.SpiModule.ProcessorClassLoader;
+import dagger.internal.codegen.base.ClearableCache;
 import dagger.internal.codegen.compileroption.CompilerOptions;
 import dagger.internal.codegen.compileroption.ProcessingEnvironmentCompilerOptions;
 import dagger.internal.codegen.compileroption.ProcessingOptions;
 import dagger.internal.codegen.langmodel.DaggerElements;
+import dagger.multibindings.IntoSet;
 import dagger.spi.BindingGraphPlugin;
 import java.util.Map;
 import javax.annotation.processing.Filer;
 import javax.annotation.processing.Messager;
 import javax.annotation.processing.ProcessingEnvironment;
+import javax.inject.Singleton;
 import javax.lang.model.SourceVersion;
 import javax.lang.model.util.Types;
 
@@ -73,10 +76,15 @@
   }
 
   @Provides
+  @Singleton
   static DaggerElements daggerElements(ProcessingEnvironment processingEnvironment) {
     return new DaggerElements(processingEnvironment);
   }
 
+  @Binds
+  @IntoSet
+  ClearableCache daggerElementAsClearableCache(DaggerElements elements);
+
   @Provides
   @ProcessorClassLoader
   static ClassLoader processorClassloader(ProcessingEnvironment processingEnvironment) {
diff --git a/java/dagger/internal/codegen/base/ComponentAnnotation.java b/java/dagger/internal/codegen/base/ComponentAnnotation.java
index c9b3580..b70ef54 100644
--- a/java/dagger/internal/codegen/base/ComponentAnnotation.java
+++ b/java/dagger/internal/codegen/base/ComponentAnnotation.java
@@ -57,6 +57,19 @@
   private static final ImmutableSet<Class<? extends Annotation>> SUBCOMPONENT_ANNOTATIONS =
      ImmutableSet.of(Subcomponent.class, ProductionSubcomponent.class);
 
+  // TODO(erichang): Move ComponentCreatorAnnotation into /base and use that here?
+  /** The component/subcomponent creator annotation types. */
+  private static final ImmutableSet<Class<? extends Annotation>> CREATOR_ANNOTATIONS =
+      ImmutableSet.of(
+          Component.Builder.class,
+          Component.Factory.class,
+          ProductionComponent.Builder.class,
+          ProductionComponent.Factory.class,
+          Subcomponent.Builder.class,
+          Subcomponent.Factory.class,
+          ProductionSubcomponent.Builder.class,
+          ProductionSubcomponent.Factory.class);
+
   /** All component annotation types. */
   private static final ImmutableSet<Class<? extends Annotation>> ALL_COMPONENT_ANNOTATIONS =
      ImmutableSet.<Class<? extends Annotation>>builder()
@@ -64,6 +77,13 @@
          .addAll(SUBCOMPONENT_ANNOTATIONS)
          .build();
 
+  /** All component and creator annotation types. */
+  private static final ImmutableSet<Class<? extends Annotation>>
+      ALL_COMPONENT_AND_CREATOR_ANNOTATIONS = ImmutableSet.<Class<? extends Annotation>>builder()
+         .addAll(ALL_COMPONENT_ANNOTATIONS)
+         .addAll(CREATOR_ANNOTATIONS)
+         .build();
+
   /** The annotation itself. */
   public abstract AnnotationMirror annotation();
 
@@ -206,6 +226,11 @@
     return ALL_COMPONENT_ANNOTATIONS;
   }
 
+  /** All component and creator annotation types. */
+  public static ImmutableSet<Class<? extends Annotation>> allComponentAndCreatorAnnotations() {
+    return ALL_COMPONENT_AND_CREATOR_ANNOTATIONS;
+  }
+
   /**
    * An actual component annotation.
    *
diff --git a/java/dagger/internal/codegen/base/Keys.java b/java/dagger/internal/codegen/base/Keys.java
index a25f996..4d13926 100644
--- a/java/dagger/internal/codegen/base/Keys.java
+++ b/java/dagger/internal/codegen/base/Keys.java
@@ -16,6 +16,10 @@
 
 package dagger.internal.codegen.base;
 
+import static com.google.auto.common.MoreTypes.asTypeElement;
+import static dagger.internal.codegen.base.ComponentAnnotation.allComponentAndCreatorAnnotations;
+import static dagger.internal.codegen.langmodel.DaggerElements.isAnyAnnotationPresent;
+
 import com.google.auto.common.MoreElements;
 import com.google.auto.common.MoreTypes;
 import dagger.internal.codegen.langmodel.DaggerTypes;
@@ -88,4 +92,14 @@
         },
         null);
   }
+
+  /**
+   * Returns {@code true} if the given key is for a component/subcomponent or a creator of a
+   * component/subcomponent.
+   */
+  public static boolean isComponentOrCreator(Key key) {
+    return !key.qualifier().isPresent()
+        && key.type().getKind() == TypeKind.DECLARED
+        && isAnyAnnotationPresent(asTypeElement(key.type()), allComponentAndCreatorAnnotations());
+  }
 }
diff --git a/java/dagger/internal/codegen/base/SourceFileGenerator.java b/java/dagger/internal/codegen/base/SourceFileGenerator.java
index 02348a4..ada67d3 100644
--- a/java/dagger/internal/codegen/base/SourceFileGenerator.java
+++ b/java/dagger/internal/codegen/base/SourceFileGenerator.java
@@ -16,17 +16,20 @@
 
 package dagger.internal.codegen.base;
 
+
 import static com.google.auto.common.GeneratedAnnotations.generatedAnnotation;
 import static com.google.common.base.Preconditions.checkNotNull;
 import static dagger.internal.codegen.javapoet.AnnotationSpecs.Suppression.RAWTYPES;
 import static dagger.internal.codegen.javapoet.AnnotationSpecs.Suppression.UNCHECKED;
 
 import com.google.common.base.Throwables;
+import com.google.common.collect.ImmutableList;
 import com.google.common.collect.ImmutableSet;
 import com.squareup.javapoet.AnnotationSpec;
 import com.squareup.javapoet.ClassName;
 import com.squareup.javapoet.JavaFile;
 import com.squareup.javapoet.TypeSpec;
+import dagger.internal.DaggerGenerated;
 import dagger.internal.codegen.javapoet.AnnotationSpecs;
 import dagger.internal.codegen.javapoet.AnnotationSpecs.Suppression;
 import dagger.internal.codegen.langmodel.DaggerElements;
@@ -74,22 +77,21 @@
 
   /** Generates a source file to be compiled for {@code T}. */
   public void generate(T input) throws SourceFileGenerationException {
-    Optional<TypeSpec.Builder> type = write(input);
-    if (!type.isPresent()) {
-      return;
-    }
-    try {
-      buildJavaFile(input, type.get()).writeTo(filer);
-    } catch (Exception e) {
-      // if the code above threw a SFGE, use that
-      Throwables.propagateIfPossible(e, SourceFileGenerationException.class);
-      // otherwise, throw a new one
-      throw new SourceFileGenerationException(Optional.empty(), e, originatingElement(input));
+    for (TypeSpec.Builder type : topLevelTypes(input)) {
+      try {
+        buildJavaFile(input, type).writeTo(filer);
+      } catch (Exception e) {
+        // if the code above threw a SFGE, use that
+        Throwables.propagateIfPossible(e, SourceFileGenerationException.class);
+        // otherwise, throw a new one
+        throw new SourceFileGenerationException(Optional.empty(), e, originatingElement(input));
+      }
     }
   }
 
   private JavaFile buildJavaFile(T input, TypeSpec.Builder typeSpecBuilder) {
     typeSpecBuilder.addOriginatingElement(originatingElement(input));
+    typeSpecBuilder.addAnnotation(DaggerGenerated.class);
     Optional<AnnotationSpec> generatedAnnotation =
         generatedAnnotation(elements, sourceVersion)
             .map(
@@ -109,7 +111,9 @@
                 .build()));
 
     JavaFile.Builder javaFileBuilder =
-        JavaFile.builder(nameGeneratedType(input).packageName(), typeSpecBuilder.build())
+        JavaFile.builder(
+                elements.getPackageOf(originatingElement(input)).getQualifiedName().toString(),
+                typeSpecBuilder.build())
             .skipJavaLangImports(true);
     if (!generatedAnnotation.isPresent()) {
       javaFileBuilder.addFileComment("Generated by Dagger ($L).", GENERATED_COMMENTS);
@@ -117,19 +121,16 @@
     return javaFileBuilder.build();
   }
 
-  /** Implementations should return the {@link ClassName} for the top-level type to be generated. */
-  public abstract ClassName nameGeneratedType(T input);
-
   /** Returns the originating element of the generating type. */
   public abstract Element originatingElement(T input);
 
   /**
-   * Returns a {@link TypeSpec.Builder type} to be generated for {@code T}, or {@link
-   * Optional#empty()} if no file should be generated.
+   * Returns {@link TypeSpec.Builder types} be generated for {@code T}, or an empty list if no types
+   * should be generated.
+   *
+   * <p>Every type will be generated in its own file.
    */
-  // TODO(ronshapiro): write() makes more sense in JavaWriter where all writers are mutable.
-  // consider renaming to something like typeBuilder() which conveys the mutability of the result
-  public abstract Optional<TypeSpec.Builder> write(T input);
+  public abstract ImmutableList<TypeSpec.Builder> topLevelTypes(T input);
 
   /** Returns {@link Suppression}s that are applied to files generated by this generator. */
   // TODO(b/134590785): When suppressions are removed locally, remove this and inline the usages
diff --git a/java/dagger/internal/codegen/binding/AssistedInjectionAnnotations.java b/java/dagger/internal/codegen/binding/AssistedInjectionAnnotations.java
index 8d6ee5d..f9929ae 100644
--- a/java/dagger/internal/codegen/binding/AssistedInjectionAnnotations.java
+++ b/java/dagger/internal/codegen/binding/AssistedInjectionAnnotations.java
@@ -59,14 +59,14 @@
 public final class AssistedInjectionAnnotations {
   /** Returns the factory method for the given factory {@link TypeElement}. */
   public static ExecutableElement assistedFactoryMethod(
-      TypeElement factory, DaggerElements elements, DaggerTypes types) {
-    return getOnlyElement(assistedFactoryMethods(factory, elements, types));
+      TypeElement factory, DaggerElements elements) {
+    return getOnlyElement(assistedFactoryMethods(factory, elements));
   }
 
   /** Returns the list of abstract factory methods for the given factory {@link TypeElement}. */
   public static ImmutableSet<ExecutableElement> assistedFactoryMethods(
-      TypeElement factory, DaggerElements elements, DaggerTypes types) {
-    return MoreElements.getLocalAndInheritedMethods(factory, types, elements).stream()
+      TypeElement factory, DaggerElements elements) {
+    return elements.getLocalAndInheritedMethods(factory).stream()
         .filter(method -> method.getModifiers().contains(ABSTRACT))
         .filter(method -> !method.isDefault())
         .collect(toImmutableSet());
@@ -170,7 +170,7 @@
         TypeMirror factory, DaggerElements elements, DaggerTypes types) {
       DeclaredType factoryType = asDeclared(factory);
       TypeElement factoryElement = asTypeElement(factoryType);
-      ExecutableElement factoryMethod = assistedFactoryMethod(factoryElement, elements, types);
+      ExecutableElement factoryMethod = assistedFactoryMethod(factoryElement, elements);
       ExecutableType factoryMethodType = asExecutable(types.asMemberOf(factoryType, factoryMethod));
       DeclaredType assistedInjectType = asDeclared(factoryMethodType.getReturnType());
       return new AutoValue_AssistedInjectionAnnotations_AssistedFactoryMetadata(
diff --git a/java/dagger/internal/codegen/binding/BindingFactory.java b/java/dagger/internal/codegen/binding/BindingFactory.java
index 6f2fc80..eb7c132 100644
--- a/java/dagger/internal/codegen/binding/BindingFactory.java
+++ b/java/dagger/internal/codegen/binding/BindingFactory.java
@@ -191,7 +191,7 @@
     }
 
     ExecutableElement factoryMethod =
-        AssistedInjectionAnnotations.assistedFactoryMethod(factory, elements, types);
+        AssistedInjectionAnnotations.assistedFactoryMethod(factory, elements);
     ExecutableType factoryMethodType =
         MoreTypes.asExecutable(types.asMemberOf(factoryType, factoryMethod));
     return ProvisionBinding.builder()
diff --git a/java/dagger/internal/codegen/binding/BindingGraph.java b/java/dagger/internal/codegen/binding/BindingGraph.java
index 4936a05..533d113 100644
--- a/java/dagger/internal/codegen/binding/BindingGraph.java
+++ b/java/dagger/internal/codegen/binding/BindingGraph.java
@@ -16,6 +16,7 @@
 
 package dagger.internal.codegen.binding;
 
+import static com.google.common.collect.Iterables.transform;
 import static dagger.internal.codegen.extension.DaggerCollectors.toOptional;
 import static dagger.internal.codegen.extension.DaggerStreams.presentValues;
 import static dagger.internal.codegen.extension.DaggerStreams.stream;
@@ -26,11 +27,12 @@
 import com.google.auto.value.AutoValue;
 import com.google.auto.value.extension.memoized.Memoized;
 import com.google.common.collect.ImmutableList;
+import com.google.common.collect.ImmutableListMultimap;
 import com.google.common.collect.ImmutableMap;
 import com.google.common.collect.ImmutableSet;
 import com.google.common.collect.ImmutableSetMultimap;
+import com.google.common.collect.Multimaps;
 import com.google.common.collect.Sets;
-import com.google.common.graph.Graphs;
 import com.google.common.graph.ImmutableNetwork;
 import com.google.common.graph.Traverser;
 import dagger.model.BindingGraph.ChildFactoryMethodEdge;
@@ -39,7 +41,9 @@
 import dagger.model.BindingGraph.Node;
 import dagger.model.ComponentPath;
 import dagger.model.Key;
+import java.util.ArrayList;
 import java.util.HashMap;
+import java.util.List;
 import java.util.Map;
 import java.util.Optional;
 import javax.lang.model.element.ExecutableElement;
@@ -104,6 +108,15 @@
     public ImmutableSetMultimap<Class<? extends Node>, ? extends Node> nodesByClass() {
       return super.nodesByClass();
     }
+
+    /**
+     * Returns an index of each {@link BindingNode} by its {@link ComponentPath}. Accessing this for
+     * a component and its parent components is faster than doing a graph traversal.
+     */
+    @Memoized
+    ImmutableListMultimap<ComponentPath, BindingNode> bindingsByComponent() {
+      return Multimaps.index(transform(bindings(), BindingNode.class::cast), Node::componentPath);
+    }
   }
 
   static BindingGraph create(
@@ -115,12 +128,12 @@
       Optional<BindingGraph> parent,
       ComponentNode componentNode,
       TopLevelBindingGraph topLevelBindingGraph) {
-    ImmutableSet<BindingNode> reachableBindingNodes =
-        Graphs.reachableNodes(topLevelBindingGraph.network().asGraph(), componentNode).stream()
-            .filter(node -> isSubpath(componentNode.componentPath(), node.componentPath()))
-            .filter(node -> node instanceof BindingNode)
-            .map(node -> (BindingNode) node)
-            .collect(toImmutableSet());
+    List<BindingNode> reachableBindingNodes = new ArrayList<>();
+    for (ComponentPath path = componentNode.componentPath();
+        !path.components().isEmpty();
+        path = ComponentPath.create(path.components().subList(0, path.components().size() - 1))) {
+      reachableBindingNodes.addAll(topLevelBindingGraph.bindingsByComponent().get(path));
+    }
 
     // Construct the maps of the ContributionBindings and MembersInjectionBindings.
     Map<Key, BindingNode> contributionBindings = new HashMap<>();
@@ -331,17 +344,4 @@
         .addAll(membersInjectionBindings.values())
         .build();
   }
-
-  // TODO(bcorso): Move this to ComponentPath
-  private static boolean isSubpath(ComponentPath path, ComponentPath subpath) {
-    if (path.components().size() < subpath.components().size()) {
-      return false;
-    }
-    for (int i = 0; i < subpath.components().size(); i++) {
-      if (!path.components().get(i).equals(subpath.components().get(i))) {
-        return false;
-      }
-    }
-    return true;
-  }
 }
diff --git a/java/dagger/internal/codegen/binding/BindingGraphFactory.java b/java/dagger/internal/codegen/binding/BindingGraphFactory.java
index 2c15e26..a94f6b0 100644
--- a/java/dagger/internal/codegen/binding/BindingGraphFactory.java
+++ b/java/dagger/internal/codegen/binding/BindingGraphFactory.java
@@ -47,6 +47,7 @@
 import dagger.Reusable;
 import dagger.internal.codegen.base.ClearableCache;
 import dagger.internal.codegen.base.ContributionType;
+import dagger.internal.codegen.base.Keys;
 import dagger.internal.codegen.base.MapType;
 import dagger.internal.codegen.base.OptionalType;
 import dagger.internal.codegen.compileroption.CompilerOptions;
@@ -796,7 +797,7 @@
        * 2. If there are any explicit bindings in this component, they may conflict with those in
        *    the ancestor component, so resolve them here so that conflicts can be caught.
        */
-      if (getPreviouslyResolvedBindings(key).isPresent()) {
+      if (getPreviouslyResolvedBindings(key).isPresent() && !Keys.isComponentOrCreator(key)) {
         /* Resolve in the parent in case there are multibinding contributions or conflicts in some
          * component between this one and the previously-resolved one. */
         parentResolver.get().resolve(key);
diff --git a/java/dagger/internal/codegen/binding/BindsTypeChecker.java b/java/dagger/internal/codegen/binding/BindsTypeChecker.java
index f3e0a1b..d850fd3 100644
--- a/java/dagger/internal/codegen/binding/BindsTypeChecker.java
+++ b/java/dagger/internal/codegen/binding/BindsTypeChecker.java
@@ -18,7 +18,6 @@
 
 import static com.google.common.collect.Iterables.getOnlyElement;
 
-import com.google.auto.common.MoreElements;
 import com.google.auto.common.MoreTypes;
 import com.google.common.collect.ImmutableList;
 import dagger.internal.codegen.base.ContributionType;
@@ -83,8 +82,8 @@
         // type.asElement().getEnclosedElements() is not used because some non-standard JDKs (e.g.
         // J2CL) don't redefine Set.add() (whose only purpose of being redefined in the standard JDK
         // is documentation, and J2CL's implementation doesn't declare docs for JDK types).
-        // MoreElements.getLocalAndInheritedMethods ensures that the method will always be present.
-        MoreElements.getLocalAndInheritedMethods(MoreTypes.asTypeElement(type), types, elements)) {
+        // getLocalAndInheritedMethods ensures that the method will always be present.
+        elements.getLocalAndInheritedMethods(MoreTypes.asTypeElement(type))) {
       if (method.getSimpleName().contentEquals(methodName)) {
         methodsForName.add(method);
       }
diff --git a/java/dagger/internal/codegen/binding/ComponentDescriptor.java b/java/dagger/internal/codegen/binding/ComponentDescriptor.java
index a7e4cc4..f6ea62c 100644
--- a/java/dagger/internal/codegen/binding/ComponentDescriptor.java
+++ b/java/dagger/internal/codegen/binding/ComponentDescriptor.java
@@ -45,6 +45,8 @@
 import dagger.model.Scope;
 import dagger.producers.CancellationPolicy;
 import dagger.producers.ProductionComponent;
+import java.util.HashMap;
+import java.util.Map;
 import java.util.Objects;
 import java.util.Optional;
 import java.util.stream.Stream;
@@ -240,19 +242,17 @@
 
   /** Returns the first component method associated with this binding request, if one exists. */
   public Optional<ComponentMethodDescriptor> firstMatchingComponentMethod(BindingRequest request) {
-    return componentMethods().stream()
-        .filter(method -> doesComponentMethodMatch(method, request))
-        .findFirst();
+    return Optional.ofNullable(firstMatchingComponentMethods().get(request));
   }
 
-  /** Returns true if the component method matches the binding request. */
-  private static boolean doesComponentMethodMatch(
-      ComponentMethodDescriptor componentMethod, BindingRequest request) {
-    return componentMethod
-        .dependencyRequest()
-        .map(BindingRequest::bindingRequest)
-        .filter(request::equals)
-        .isPresent();
+  @Memoized
+  ImmutableMap<BindingRequest, ComponentMethodDescriptor>
+      firstMatchingComponentMethods() {
+    Map<BindingRequest, ComponentMethodDescriptor> methods = new HashMap<>();
+    for (ComponentMethodDescriptor method : entryPointMethods()) {
+      methods.putIfAbsent(BindingRequest.bindingRequest(method.dependencyRequest().get()), method);
+    }
+    return ImmutableMap.copyOf(methods);
   }
 
   /** The entry point methods on the component type. Each has a {@link DependencyRequest}. */
diff --git a/java/dagger/internal/codegen/binding/ComponentRequirement.java b/java/dagger/internal/codegen/binding/ComponentRequirement.java
index fa24b56..9d54f4d 100644
--- a/java/dagger/internal/codegen/binding/ComponentRequirement.java
+++ b/java/dagger/internal/codegen/binding/ComponentRequirement.java
@@ -16,7 +16,6 @@
 
 package dagger.internal.codegen.binding;
 
-import static com.google.auto.common.MoreElements.getLocalAndInheritedMethods;
 import static com.google.common.base.Preconditions.checkArgument;
 import static com.google.common.base.Preconditions.checkNotNull;
 import static dagger.internal.codegen.binding.SourceFiles.simpleVariableName;
@@ -38,7 +37,6 @@
 import dagger.Provides;
 import dagger.internal.codegen.kotlin.KotlinMetadataUtil;
 import dagger.internal.codegen.langmodel.DaggerElements;
-import dagger.internal.codegen.langmodel.DaggerTypes;
 import dagger.model.BindingKind;
 import dagger.model.Key;
 import dagger.multibindings.Multibinds;
@@ -115,14 +113,12 @@
    * of the default behavior in {@link #nullPolicy}.
    *
    * <p>Some implementations' null policy can be determined upon construction (e.g., for binding
-   * instances), but others' require Elements and Types, which must wait until {@link #nullPolicy}
-   * is called.
+   * instances), but others' require Elements which must wait until {@link #nullPolicy} is called.
    */
   abstract Optional<NullPolicy> overrideNullPolicy();
 
   /** The requirement's null policy. */
-  public NullPolicy nullPolicy(
-      DaggerElements elements, DaggerTypes types, KotlinMetadataUtil metadataUtil) {
+  public NullPolicy nullPolicy(DaggerElements elements, KotlinMetadataUtil metadataUtil) {
     if (overrideNullPolicy().isPresent()) {
       return overrideNullPolicy().get();
     }
@@ -130,9 +126,7 @@
       case MODULE:
         return componentCanMakeNewInstances(typeElement(), metadataUtil)
             ? NullPolicy.NEW
-            : requiresAPassedInstance(elements, types, metadataUtil)
-                ? NullPolicy.THROW
-                : NullPolicy.ALLOW;
+            : requiresAPassedInstance(elements, metadataUtil) ? NullPolicy.THROW : NullPolicy.ALLOW;
       case DEPENDENCY:
       case BOUND_INSTANCE:
         return NullPolicy.THROW;
@@ -144,13 +138,12 @@
    * Returns true if the passed {@link ComponentRequirement} requires a passed instance in order to
    * be used within a component.
    */
-  public boolean requiresAPassedInstance(
-      DaggerElements elements, DaggerTypes types, KotlinMetadataUtil metadataUtil) {
+  public boolean requiresAPassedInstance(DaggerElements elements, KotlinMetadataUtil metadataUtil) {
     if (!kind().isModule()) {
       // Bound instances and dependencies always require the user to provide an instance.
       return true;
     }
-    return requiresModuleInstance(elements, types, metadataUtil)
+    return requiresModuleInstance(elements, metadataUtil)
         && !componentCanMakeNewInstances(typeElement(), metadataUtil);
   }
 
@@ -164,8 +157,7 @@
    * <p>Alternatively, if the module is a Kotlin Object then the binding methods are considered
    * {@code static}, requiring no module instance.
    */
-  private boolean requiresModuleInstance(
-      DaggerElements elements, DaggerTypes types, KotlinMetadataUtil metadataUtil) {
+  private boolean requiresModuleInstance(DaggerElements elements, KotlinMetadataUtil metadataUtil) {
     boolean isKotlinObject =
         metadataUtil.isObjectClass(typeElement())
             || metadataUtil.isCompanionObjectClass(typeElement());
@@ -173,8 +165,7 @@
       return false;
     }
 
-    ImmutableSet<ExecutableElement> methods =
-        getLocalAndInheritedMethods(typeElement(), types, elements);
+    ImmutableSet<ExecutableElement> methods = elements.getLocalAndInheritedMethods(typeElement());
     return methods.stream()
         .filter(this::isBindingMethod)
         .map(ExecutableElement::getModifiers)
diff --git a/java/dagger/internal/codegen/componentgenerator/ComponentCreatorImplementationFactory.java b/java/dagger/internal/codegen/componentgenerator/ComponentCreatorImplementationFactory.java
index e1b35da..66270c6 100644
--- a/java/dagger/internal/codegen/componentgenerator/ComponentCreatorImplementationFactory.java
+++ b/java/dagger/internal/codegen/componentgenerator/ComponentCreatorImplementationFactory.java
@@ -237,7 +237,7 @@
       method.addStatement(
           "this.$N = $L",
           fields.get(requirement),
-          requirement.nullPolicy(elements, types, metadataUtil).equals(NullPolicy.ALLOW)
+          requirement.nullPolicy(elements, metadataUtil).equals(NullPolicy.ALLOW)
               ? CodeBlock.of("$N", parameter)
               : CodeBlock.of("$T.checkNotNull($N)", Preconditions.class, parameter));
       return maybeReturnThis(method);
@@ -310,7 +310,7 @@
 
     private void addNullHandlingForField(
         ComponentRequirement requirement, FieldSpec field, MethodSpec.Builder factoryMethod) {
-      switch (requirement.nullPolicy(elements, types, metadataUtil)) {
+      switch (requirement.nullPolicy(elements, metadataUtil)) {
         case NEW:
           checkState(requirement.kind().isModule());
           factoryMethod
@@ -334,7 +334,7 @@
 
     private void addNullHandlingForParameter(
         ComponentRequirement requirement, String parameter, MethodSpec.Builder factoryMethod) {
-      if (!requirement.nullPolicy(elements, types, metadataUtil).equals(NullPolicy.ALLOW)) {
+      if (!requirement.nullPolicy(elements, metadataUtil).equals(NullPolicy.ALLOW)) {
         // Factory method parameters are always required unless they are a nullable
         // binds-instance (i.e. ALLOW)
         factoryMethod.addStatement("$T.checkNotNull($L)", Preconditions.class, parameter);
diff --git a/java/dagger/internal/codegen/componentgenerator/ComponentGenerator.java b/java/dagger/internal/codegen/componentgenerator/ComponentGenerator.java
index e04ee14..098da81 100644
--- a/java/dagger/internal/codegen/componentgenerator/ComponentGenerator.java
+++ b/java/dagger/internal/codegen/componentgenerator/ComponentGenerator.java
@@ -19,6 +19,7 @@
 import static com.google.common.base.Verify.verify;
 import static dagger.internal.codegen.binding.SourceFiles.classFileName;
 
+import com.google.common.collect.ImmutableList;
 import com.squareup.javapoet.ClassName;
 import com.squareup.javapoet.TypeSpec;
 import dagger.Component;
@@ -26,7 +27,6 @@
 import dagger.internal.codegen.binding.BindingGraph;
 import dagger.internal.codegen.langmodel.DaggerElements;
 import dagger.internal.codegen.writing.ComponentImplementation;
-import java.util.Optional;
 import javax.annotation.processing.Filer;
 import javax.inject.Inject;
 import javax.lang.model.SourceVersion;
@@ -47,11 +47,6 @@
     this.componentImplementationFactory = componentImplementationFactory;
   }
 
-  @Override
-  public ClassName nameGeneratedType(BindingGraph input) {
-    return componentName(input.componentTypeElement());
-  }
-
   static ClassName componentName(TypeElement componentDefinitionType) {
     ClassName componentName = ClassName.get(componentDefinitionType);
     return ClassName.get(componentName.packageName(), "Dagger" + classFileName(componentName));
@@ -63,10 +58,11 @@
   }
 
   @Override
-  public Optional<TypeSpec.Builder> write(BindingGraph bindingGraph) {
+  public ImmutableList<TypeSpec.Builder> topLevelTypes(BindingGraph bindingGraph) {
     ComponentImplementation componentImplementation =
         componentImplementationFactory.createComponentImplementation(bindingGraph);
-    verify(componentImplementation.name().equals(nameGeneratedType(bindingGraph)));
-    return Optional.of(componentImplementation.generate());
+    verify(
+        componentImplementation.name().equals(componentName(bindingGraph.componentTypeElement())));
+    return ImmutableList.of(componentImplementation.generate());
   }
 }
diff --git a/java/dagger/internal/codegen/componentgenerator/ComponentGeneratorModule.java b/java/dagger/internal/codegen/componentgenerator/ComponentGeneratorModule.java
index 84179d6..179c411 100644
--- a/java/dagger/internal/codegen/componentgenerator/ComponentGeneratorModule.java
+++ b/java/dagger/internal/codegen/componentgenerator/ComponentGeneratorModule.java
@@ -18,11 +18,9 @@
 
 import dagger.Binds;
 import dagger.Module;
-import dagger.internal.codegen.base.ClearableCache;
 import dagger.internal.codegen.base.SourceFileGenerator;
 import dagger.internal.codegen.binding.BindingGraph;
 import dagger.internal.codegen.binding.ComponentDescriptor;
-import dagger.multibindings.IntoSet;
 
 /** Provides bindings needed to generated the component. */
 @Module(subcomponents = TopLevelImplementationComponent.class)
@@ -41,8 +39,4 @@
   @Binds
   abstract SourceFileGenerator<ComponentDescriptor> componentHjarGenerator(
       ComponentHjarGenerator hjarGenerator);
-
-  @Binds
-  @IntoSet
-  ClearableCache componentImplementationFactory(ComponentImplementationFactory cache);
 }
diff --git a/java/dagger/internal/codegen/componentgenerator/ComponentHjarGenerator.java b/java/dagger/internal/codegen/componentgenerator/ComponentHjarGenerator.java
index b386a4f..c8b8c97 100644
--- a/java/dagger/internal/codegen/componentgenerator/ComponentHjarGenerator.java
+++ b/java/dagger/internal/codegen/componentgenerator/ComponentHjarGenerator.java
@@ -33,6 +33,7 @@
 
 import com.google.auto.common.MoreTypes;
 import com.google.common.base.Ascii;
+import com.google.common.collect.ImmutableList;
 import com.google.common.collect.Sets;
 import com.squareup.javapoet.ClassName;
 import com.squareup.javapoet.MethodSpec;
@@ -48,7 +49,6 @@
 import dagger.internal.codegen.langmodel.DaggerElements;
 import dagger.internal.codegen.langmodel.DaggerTypes;
 import dagger.producers.internal.CancellationListener;
-import java.util.Optional;
 import java.util.Set;
 import java.util.stream.Stream;
 import javax.annotation.processing.Filer;
@@ -91,18 +91,13 @@
   }
 
   @Override
-  public ClassName nameGeneratedType(ComponentDescriptor input) {
-    return componentName(input.typeElement());
-  }
-
-  @Override
   public Element originatingElement(ComponentDescriptor input) {
     return input.typeElement();
   }
 
   @Override
-  public Optional<TypeSpec.Builder> write(ComponentDescriptor componentDescriptor) {
-    ClassName generatedTypeName = nameGeneratedType(componentDescriptor);
+  public ImmutableList<TypeSpec.Builder> topLevelTypes(ComponentDescriptor componentDescriptor) {
+    ClassName generatedTypeName = componentName(componentDescriptor.typeElement());
     TypeSpec.Builder generatedComponent =
         TypeSpec.classBuilder(generatedTypeName)
             .addModifiers(FINAL)
@@ -148,8 +143,7 @@
         && !hasBindsInstanceMethods(componentDescriptor)
         && componentRequirements(componentDescriptor)
             .noneMatch(
-                requirement ->
-                    requirement.requiresAPassedInstance(elements, types, metadataUtil))) {
+                requirement -> requirement.requiresAPassedInstance(elements, metadataUtil))) {
       generatedComponent.addMethod(createMethod(componentDescriptor));
     }
 
@@ -174,7 +168,7 @@
           .addMethod(onProducerFutureCancelledMethod());
     }
 
-    return Optional.of(generatedComponent);
+    return ImmutableList.of(generatedComponent);
   }
 
   private MethodSpec emptyComponentMethod(TypeElement typeElement, ExecutableElement baseMethod) {
diff --git a/java/dagger/internal/codegen/componentgenerator/ComponentImplementationBuilder.java b/java/dagger/internal/codegen/componentgenerator/ComponentImplementationBuilder.java
index 04cb80f..2be7d38 100644
--- a/java/dagger/internal/codegen/componentgenerator/ComponentImplementationBuilder.java
+++ b/java/dagger/internal/codegen/componentgenerator/ComponentImplementationBuilder.java
@@ -16,7 +16,6 @@
 
 package dagger.internal.codegen.componentgenerator;
 
-import static com.google.auto.common.MoreElements.getLocalAndInheritedMethods;
 import static com.google.auto.common.MoreTypes.asDeclared;
 import static com.google.common.base.Preconditions.checkState;
 import static com.squareup.javapoet.MethodSpec.constructorBuilder;
@@ -146,7 +145,8 @@
         .map(ComponentCreatorImplementation::spec)
         .ifPresent(this::addCreatorClass);
 
-    getLocalAndInheritedMethods(graph.componentTypeElement(), types, elements)
+    elements
+        .getLocalAndInheritedMethods(graph.componentTypeElement())
         .forEach(method -> componentImplementation.claimMethodName(method.getSimpleName()));
 
     addFactoryMethods();
@@ -495,7 +495,7 @@
   private boolean canInstantiateAllRequirements() {
     return !Iterables.any(
         graph.componentRequirements(),
-        dependency -> dependency.requiresAPassedInstance(elements, types, metadataUtil));
+        dependency -> dependency.requiresAPassedInstance(elements, metadataUtil));
   }
 
   private void createSubcomponentFactoryMethod(ExecutableElement factoryMethod) {
diff --git a/java/dagger/internal/codegen/componentgenerator/ComponentImplementationFactory.java b/java/dagger/internal/codegen/componentgenerator/ComponentImplementationFactory.java
index fdfcc9d..0d29b86 100644
--- a/java/dagger/internal/codegen/componentgenerator/ComponentImplementationFactory.java
+++ b/java/dagger/internal/codegen/componentgenerator/ComponentImplementationFactory.java
@@ -16,26 +16,20 @@
 
 package dagger.internal.codegen.componentgenerator;
 
-import static dagger.internal.codegen.base.Util.reentrantComputeIfAbsent;
 import static dagger.internal.codegen.componentgenerator.ComponentGenerator.componentName;
 
-import dagger.internal.codegen.base.ClearableCache;
 import dagger.internal.codegen.binding.BindingGraph;
 import dagger.internal.codegen.binding.KeyFactory;
 import dagger.internal.codegen.compileroption.CompilerOptions;
 import dagger.internal.codegen.writing.ComponentImplementation;
 import dagger.internal.codegen.writing.SubcomponentNames;
-import java.util.HashMap;
-import java.util.Map;
 import java.util.Optional;
 import javax.inject.Inject;
 import javax.inject.Singleton;
-import javax.lang.model.element.TypeElement;
 
 /** Factory for {@link ComponentImplementation}s. */
 @Singleton
-final class ComponentImplementationFactory implements ClearableCache {
-  private final Map<TypeElement, ComponentImplementation> topLevelComponentCache = new HashMap<>();
+final class ComponentImplementationFactory {
   private final KeyFactory keyFactory;
   private final CompilerOptions compilerOptions;
   private final TopLevelImplementationComponent.Builder topLevelImplementationComponentBuilder;
@@ -54,13 +48,6 @@
    * Returns a top-level (non-nested) component implementation for a binding graph.
    */
   ComponentImplementation createComponentImplementation(BindingGraph bindingGraph) {
-    return reentrantComputeIfAbsent(
-        topLevelComponentCache,
-        bindingGraph.componentTypeElement(),
-        component -> createComponentImplementationUncached(bindingGraph));
-  }
-
-  private ComponentImplementation createComponentImplementationUncached(BindingGraph bindingGraph) {
     ComponentImplementation componentImplementation =
         ComponentImplementation.topLevelComponentImplementation(
             bindingGraph,
@@ -82,9 +69,4 @@
         .componentImplementationBuilder()
         .build();
   }
-
-  @Override
-  public void clearCache() {
-    topLevelComponentCache.clear();
-  }
 }
diff --git a/java/dagger/internal/codegen/kotlin/KotlinMetadata.java b/java/dagger/internal/codegen/kotlin/KotlinMetadata.java
index 296da44..5fb49f0 100644
--- a/java/dagger/internal/codegen/kotlin/KotlinMetadata.java
+++ b/java/dagger/internal/codegen/kotlin/KotlinMetadata.java
@@ -362,6 +362,7 @@
 
       Builder addConstructor(FunctionMetadata constructor) {
         constructorsBuilder().add(constructor);
+        functionsBySignatureBuilder().put(constructor.signature(), constructor);
         return this;
       }
 
diff --git a/java/dagger/internal/codegen/kotlin/KotlinMetadataUtil.java b/java/dagger/internal/codegen/kotlin/KotlinMetadataUtil.java
index 76d28f0..980ff34 100644
--- a/java/dagger/internal/codegen/kotlin/KotlinMetadataUtil.java
+++ b/java/dagger/internal/codegen/kotlin/KotlinMetadataUtil.java
@@ -100,6 +100,11 @@
         && metadataFactory.create(typeElement).classMetadata().flags(IS_COMPANION_OBJECT);
   }
 
+  /** Returns {@code true} if this type element is a Kotlin object or companion object. */
+  public boolean isObjectOrCompanionObjectClass(TypeElement typeElement) {
+    return isObjectClass(typeElement) || isCompanionObjectClass(typeElement);
+  }
+
   /* Returns {@code true} if this type element has a Kotlin Companion Object. */
   public boolean hasEnclosedCompanionObject(TypeElement typeElement) {
     return hasMetadata(typeElement)
@@ -131,6 +136,15 @@
   }
 
   /**
+   * Returns {@code true} if the given type element was declared {@code internal} in its Kotlin
+   * source.
+   */
+  public boolean isVisibilityInternal(TypeElement type) {
+    return hasMetadata(type)
+        && metadataFactory.create(type).classMetadata().flags(Flag.IS_INTERNAL);
+  }
+
+  /**
    * Returns {@code true} if the given executable element was declared {@code internal} in its
    * Kotlin source.
    */
diff --git a/java/dagger/internal/codegen/langmodel/BUILD b/java/dagger/internal/codegen/langmodel/BUILD
index 670f4aa..25f1e62 100644
--- a/java/dagger/internal/codegen/langmodel/BUILD
+++ b/java/dagger/internal/codegen/langmodel/BUILD
@@ -26,6 +26,7 @@
     tags = ["maven:merged"],
     deps = [
         "//java/dagger:core",
+        "//java/dagger/internal/codegen/base:shared",
         "//java/dagger/internal/guava:base",
         "//java/dagger/internal/guava:collect",
         "//java/dagger/internal/guava:concurrent",
diff --git a/java/dagger/internal/codegen/langmodel/DaggerElements.java b/java/dagger/internal/codegen/langmodel/DaggerElements.java
index 12cec31..51c2a60 100644
--- a/java/dagger/internal/codegen/langmodel/DaggerElements.java
+++ b/java/dagger/internal/codegen/langmodel/DaggerElements.java
@@ -17,7 +17,6 @@
 package dagger.internal.codegen.langmodel;
 
 import static com.google.auto.common.MoreElements.asExecutable;
-import static com.google.auto.common.MoreElements.getLocalAndInheritedMethods;
 import static com.google.auto.common.MoreElements.hasModifiers;
 import static com.google.common.base.Preconditions.checkNotNull;
 import static com.google.common.collect.Lists.asList;
@@ -34,10 +33,12 @@
 import com.google.common.graph.Traverser;
 import com.squareup.javapoet.ClassName;
 import dagger.Reusable;
+import dagger.internal.codegen.base.ClearableCache;
 import java.io.Writer;
 import java.lang.annotation.Annotation;
 import java.util.Collection;
 import java.util.Comparator;
+import java.util.HashMap;
 import java.util.List;
 import java.util.Map;
 import java.util.Optional;
@@ -75,8 +76,9 @@
 
 /** Extension of {@link Elements} that adds Dagger-specific methods. */
 @Reusable
-public final class DaggerElements implements Elements {
-
+public final class DaggerElements implements Elements, ClearableCache {
+  private final Map<TypeElement, ImmutableSet<ExecutableElement>> getLocalAndInheritedMethodsCache =
+      new HashMap<>();
   private final Elements elements;
   private final Types types;
 
@@ -100,8 +102,13 @@
   private static final Traverser<Element> GET_ENCLOSED_ELEMENTS =
       Traverser.forTree(Element::getEnclosedElements);
 
+  public ImmutableSet<ExecutableElement> getLocalAndInheritedMethods(TypeElement type) {
+    return getLocalAndInheritedMethodsCache.computeIfAbsent(
+        type, k -> MoreElements.getLocalAndInheritedMethods(type, types, elements));
+  }
+
   public ImmutableSet<ExecutableElement> getUnimplementedMethods(TypeElement type) {
-    return FluentIterable.from(getLocalAndInheritedMethods(type, types, elements))
+    return FluentIterable.from(getLocalAndInheritedMethods(type))
         .filter(hasModifiers(ABSTRACT))
         .toSet();
   }
@@ -118,7 +125,7 @@
 
   /** Returns the type element for a class name. */
   public TypeElement getTypeElement(ClassName className) {
-    return getTypeElement(className.withoutAnnotations().toString());
+    return getTypeElement(className.canonicalName());
   }
 
   /** Returns the argument or the closest enclosing element that is a {@link TypeElement}. */
@@ -492,4 +499,9 @@
   public boolean isFunctionalInterface(TypeElement type) {
     return elements.isFunctionalInterface(type);
   }
+
+  @Override
+  public void clearCache() {
+    getLocalAndInheritedMethodsCache.clear();
+  }
 }
diff --git a/java/dagger/internal/codegen/validation/BindingElementValidator.java b/java/dagger/internal/codegen/validation/BindingElementValidator.java
index 4557745..b8f9912 100644
--- a/java/dagger/internal/codegen/validation/BindingElementValidator.java
+++ b/java/dagger/internal/codegen/validation/BindingElementValidator.java
@@ -17,7 +17,6 @@
 package dagger.internal.codegen.validation;
 
 import static com.google.auto.common.MoreTypes.asTypeElement;
-import static com.google.common.base.Preconditions.checkState;
 import static com.google.common.base.Verify.verifyNotNull;
 import static dagger.internal.codegen.base.Scopes.scopesOf;
 import static dagger.internal.codegen.base.Util.reentrantComputeIfAbsent;
@@ -142,10 +141,12 @@
   protected abstract class ElementValidator {
     protected final E element;
     protected final ValidationReport.Builder<E> report;
+    private final ImmutableCollection<? extends AnnotationMirror> qualifiers;
 
     protected ElementValidator(E element) {
       this.element = element;
       this.report = ValidationReport.about(element);
+      qualifiers = injectionAnnotations.getQualifiers(element);
     }
 
     /** Checks the element for validity. */
@@ -185,10 +186,15 @@
     protected void checkType() {
       switch (ContributionType.fromBindingElement(element)) {
         case UNIQUE:
-          /* Validate that a unique binding is not attempting to bind a framework type. This
-           * validation is only appropriate for unique bindings because multibindings may collect
-           * framework types.  E.g. Set<Provider<Foo>> is perfectly reasonable. */
+          // Validate that a unique binding is not attempting to bind a framework type. This
+          // validation is only appropriate for unique bindings because multibindings may collect
+          // framework types.  E.g. Set<Provider<Foo>> is perfectly reasonable.
           checkFrameworkType();
+
+          // Validate that a unique binding is not attempting to bind an unqualified assisted type.
+          // This validation is only appropriate for unique bindings because multibindings may
+          // collect assisted types.
+          checkAssistedType();
           // fall through
 
         case SET:
@@ -208,22 +214,26 @@
       TypeKind kind = keyType.getKind();
       if (kind.equals(VOID)) {
         report.addError(bindingElements("must %s a value (not void)", bindingElementTypeVerb()));
-      } else if (kind == DECLARED) {
-        checkNotAssistedInject(keyType);
-      } else if (!(kind.isPrimitive() || kind.equals(ARRAY) || kind.equals(TYPEVAR))) {
+      } else if (!(kind.isPrimitive()
+          || kind.equals(DECLARED)
+          || kind.equals(ARRAY)
+          || kind.equals(TYPEVAR))) {
         report.addError(badTypeMessage());
       }
     }
 
-    /** Adds errors for a method return type. */
-    private void checkNotAssistedInject(TypeMirror keyType) {
-      checkState(keyType.getKind() == TypeKind.DECLARED);
-      TypeElement keyElement = asTypeElement(keyType);
-      if (isAssistedInjectionType(keyElement)) {
-        report.addError("Dagger does not support providing @AssistedInject types.", keyElement);
-      }
-      if (isAssistedFactoryType(keyElement)) {
-        report.addError("Dagger does not support providing @AssistedFactory types.", keyElement);
+    /** Adds errors for unqualified assisted types. */
+    private void checkAssistedType() {
+      if (qualifiers.isEmpty()
+              && bindingElementType().isPresent()
+              && bindingElementType().get().getKind() == DECLARED) {
+        TypeElement keyElement = asTypeElement(bindingElementType().get());
+        if (isAssistedInjectionType(keyElement)) {
+          report.addError("Dagger does not support providing @AssistedInject types.", keyElement);
+        }
+        if (isAssistedFactoryType(keyElement)) {
+          report.addError("Dagger does not support providing @AssistedFactory types.", keyElement);
+        }
       }
     }
 
@@ -254,8 +264,6 @@
      * Adds an error if the element has more than one {@linkplain Qualifier qualifier} annotation.
      */
     private void checkQualifiers() {
-      ImmutableCollection<? extends AnnotationMirror> qualifiers =
-          injectionAnnotations.getQualifiers(element);
       if (qualifiers.size() > 1) {
         for (AnnotationMirror qualifier : qualifiers) {
           report.addError(
diff --git a/java/dagger/internal/codegen/validation/ComponentDescriptorValidator.java b/java/dagger/internal/codegen/validation/ComponentDescriptorValidator.java
index aa20232..28b1c2b 100644
--- a/java/dagger/internal/codegen/validation/ComponentDescriptorValidator.java
+++ b/java/dagger/internal/codegen/validation/ComponentDescriptorValidator.java
@@ -318,7 +318,7 @@
       Set<ComponentRequirement> mustBePassed =
           Sets.filter(
               componentModuleAndDependencyRequirements,
-              input -> input.nullPolicy(elements, types, metadataUtil).equals(NullPolicy.THROW));
+              input -> input.nullPolicy(elements, metadataUtil).equals(NullPolicy.THROW));
       // Component requirements that the creator must be able to set, but can't
       Set<ComponentRequirement> missingRequirements =
           Sets.difference(mustBePassed, creatorModuleAndDependencyRequirements);
diff --git a/java/dagger/internal/codegen/validation/ComponentValidator.java b/java/dagger/internal/codegen/validation/ComponentValidator.java
index b3322e5..72a44f2 100644
--- a/java/dagger/internal/codegen/validation/ComponentValidator.java
+++ b/java/dagger/internal/codegen/validation/ComponentValidator.java
@@ -17,7 +17,6 @@
 package dagger.internal.codegen.validation;
 
 import static com.google.auto.common.MoreElements.asType;
-import static com.google.auto.common.MoreElements.getLocalAndInheritedMethods;
 import static com.google.auto.common.MoreElements.isAnnotationPresent;
 import static com.google.auto.common.MoreTypes.asDeclared;
 import static com.google.auto.common.MoreTypes.asExecutable;
@@ -243,8 +242,7 @@
     }
 
     private void validateComponentMethods() {
-      getLocalAndInheritedMethods(component, types, elements).stream()
-          .filter(method -> method.getModifiers().contains(ABSTRACT))
+      elements.getUnimplementedMethods(component).stream()
           .map(ComponentMethodValidator::new)
           .forEachOrdered(ComponentMethodValidator::validateMethod);
     }
diff --git a/java/dagger/internal/codegen/validation/DependencyRequestValidator.java b/java/dagger/internal/codegen/validation/DependencyRequestValidator.java
index 68c9604..f280497 100644
--- a/java/dagger/internal/codegen/validation/DependencyRequestValidator.java
+++ b/java/dagger/internal/codegen/validation/DependencyRequestValidator.java
@@ -77,11 +77,21 @@
       // Don't validate assisted parameters. These are not dependency requests.
       return;
     }
-    checkQualifiers(report, requestElement);
-    checkType(report, requestElement, requestType);
+    if (missingQualifierMetadata(requestElement)) {
+      report.addError(
+          "Unable to read annotations on an injected Kotlin property. The Dagger compiler must"
+              + " also be applied to any project containing @Inject properties.",
+          requestElement);
+
+      // Skip any further validation if we don't have valid metadata for a type that needs it.
+      return;
+    }
+
+    new Validator(report, requestElement, requestType).validate();
   }
 
-  private void checkQualifiers(ValidationReport.Builder<?> report, Element requestElement) {
+  /** Returns {@code true} if a kotlin inject field is missing metadata about its qualifiers. */
+  private boolean missingQualifierMetadata(Element requestElement) {
     if (requestElement.getKind() == ElementKind.FIELD
         // static injected fields are not supported, no need to get qualifier from kotlin metadata
         && !requestElement.getModifiers().contains(STATIC)
@@ -91,64 +101,80 @@
           Optional.ofNullable(
               elements.getTypeElement(
                   membersInjectorNameForType(asType(requestElement.getEnclosingElement()))));
-      if (!membersInjector.isPresent()) {
-        report.addError(
-            "Unable to read annotations on an injected Kotlin property. The Dagger compiler must"
-                + " also be applied to any project containing @Inject properties.",
-            requestElement);
-        return; // finish checking qualifiers since current information is unreliable.
-      }
+      return !membersInjector.isPresent();
     }
-
-    ImmutableCollection<? extends AnnotationMirror> qualifiers =
-        injectionAnnotations.getQualifiers(requestElement);
-    if (qualifiers.size() > 1) {
-      for (AnnotationMirror qualifier : qualifiers) {
-        report.addError(
-            "A single dependency request may not use more than one @Qualifier",
-            requestElement,
-            qualifier);
-      }
-    }
+    return false;
   }
 
-  private void checkType(
-      ValidationReport.Builder<?> report, Element requestElement, TypeMirror requestType) {
-    TypeMirror keyType = extractKeyType(requestType);
-    RequestKind requestKind = RequestKinds.getRequestKind(requestType);
-    if (keyType.getKind() == TypeKind.DECLARED) {
-      TypeElement typeElement = asTypeElement(keyType);
-      if (isAssistedInjectionType(typeElement)) {
-        report.addError(
-            "Dagger does not support injecting @AssistedInject type, "
-                + requestType
-                + ". Did you mean to inject its assisted factory type instead?",
-            requestElement);
+  private final class Validator {
+    private final ValidationReport.Builder<?> report;
+    private final Element requestElement;
+    private final TypeMirror requestType;
+    private final TypeMirror keyType;
+    private final RequestKind requestKind;
+    private final ImmutableCollection<? extends AnnotationMirror> qualifiers;
+
+
+    Validator(ValidationReport.Builder<?> report, Element requestElement, TypeMirror requestType) {
+      this.report = report;
+      this.requestElement = requestElement;
+      this.requestType = requestType;
+      this.keyType = extractKeyType(requestType);
+      this.requestKind = RequestKinds.getRequestKind(requestType);
+      this.qualifiers = injectionAnnotations.getQualifiers(requestElement);
+    }
+
+    void validate() {
+      checkQualifiers();
+      checkType();
+    }
+
+    private void checkQualifiers() {
+      if (qualifiers.size() > 1) {
+        for (AnnotationMirror qualifier : qualifiers) {
+          report.addError(
+              "A single dependency request may not use more than one @Qualifier",
+              requestElement,
+              qualifier);
+        }
       }
-      if (requestKind != RequestKind.INSTANCE && isAssistedFactoryType(typeElement)) {
+    }
+
+    private void checkType() {
+      if (qualifiers.isEmpty() && keyType.getKind() == TypeKind.DECLARED) {
+        TypeElement typeElement = asTypeElement(keyType);
+        if (isAssistedInjectionType(typeElement)) {
+          report.addError(
+              "Dagger does not support injecting @AssistedInject type, "
+                  + requestType
+                  + ". Did you mean to inject its assisted factory type instead?",
+              requestElement);
+        }
+        if (requestKind != RequestKind.INSTANCE && isAssistedFactoryType(typeElement)) {
+          report.addError(
+              "Dagger does not support injecting Provider<T>, Lazy<T>, Producer<T>, "
+                  + "or Produced<T> when T is an @AssistedFactory-annotated type such as "
+                  + keyType,
+              requestElement);
+        }
+      }
+      if (keyType.getKind().equals(WILDCARD)) {
+        // TODO(ronshapiro): Explore creating this message using RequestKinds.
         report.addError(
             "Dagger does not support injecting Provider<T>, Lazy<T>, Producer<T>, "
-                + "or Produced<T> when T is an @AssistedFactory-annotated type such as "
+                + "or Produced<T> when T is a wildcard type such as "
                 + keyType,
             requestElement);
       }
-    }
-    if (keyType.getKind().equals(WILDCARD)) {
-      // TODO(ronshapiro): Explore creating this message using RequestKinds.
-      report.addError(
-          "Dagger does not support injecting Provider<T>, Lazy<T>, Producer<T>, "
-              + "or Produced<T> when T is a wildcard type such as "
-              + keyType,
-          requestElement);
-    }
-    if (MoreTypes.isType(keyType) && MoreTypes.isTypeOf(MembersInjector.class, keyType)) {
-      DeclaredType membersInjectorType = MoreTypes.asDeclared(keyType);
-      if (membersInjectorType.getTypeArguments().isEmpty()) {
-        report.addError("Cannot inject a raw MembersInjector", requestElement);
-      } else {
-        report.addSubreport(
-            membersInjectionValidator.validateMembersInjectionRequest(
-                requestElement, membersInjectorType.getTypeArguments().get(0)));
+      if (MoreTypes.isType(keyType) && MoreTypes.isTypeOf(MembersInjector.class, keyType)) {
+        DeclaredType membersInjectorType = MoreTypes.asDeclared(keyType);
+        if (membersInjectorType.getTypeArguments().isEmpty()) {
+          report.addError("Cannot inject a raw MembersInjector", requestElement);
+        } else {
+          report.addSubreport(
+              membersInjectionValidator.validateMembersInjectionRequest(
+                  requestElement, membersInjectorType.getTypeArguments().get(0)));
+        }
       }
     }
   }
diff --git a/java/dagger/internal/codegen/validation/InjectBindingRegistryImpl.java b/java/dagger/internal/codegen/validation/InjectBindingRegistryImpl.java
index 06f68f6..9787f4f 100644
--- a/java/dagger/internal/codegen/validation/InjectBindingRegistryImpl.java
+++ b/java/dagger/internal/codegen/validation/InjectBindingRegistryImpl.java
@@ -24,6 +24,7 @@
 import static dagger.internal.codegen.binding.AssistedInjectionAnnotations.assistedInjectedConstructors;
 import static dagger.internal.codegen.binding.InjectionAnnotations.injectedConstructors;
 import static dagger.internal.codegen.binding.SourceFiles.generatedClassNameForBinding;
+import static dagger.internal.codegen.langmodel.DaggerTypes.unwrapType;
 
 import com.google.auto.common.MoreElements;
 import com.google.auto.common.MoreTypes;
@@ -112,7 +113,11 @@
     /** Caches the binding and generates it if it needs generation. */
     void tryRegisterBinding(B binding, boolean warnIfNotAlreadyGenerated) {
       tryToCacheBinding(binding);
-      tryToGenerateBinding(binding, warnIfNotAlreadyGenerated);
+
+      @SuppressWarnings("unchecked")
+      B maybeUnresolved =
+          binding.unresolved().isPresent() ? (B) binding.unresolved().get() : binding;
+      tryToGenerateBinding(maybeUnresolved, warnIfNotAlreadyGenerated);
     }
 
     /**
@@ -199,9 +204,6 @@
    */
   private void registerBinding(ProvisionBinding binding, boolean warnIfNotAlreadyGenerated) {
     provisionBindings.tryRegisterBinding(binding, warnIfNotAlreadyGenerated);
-    if (binding.unresolved().isPresent()) {
-      provisionBindings.tryToGenerateBinding(binding.unresolved().get(), warnIfNotAlreadyGenerated);
-    }
   }
 
   /**
@@ -229,10 +231,6 @@
     }
 
     membersInjectionBindings.tryRegisterBinding(binding, warnIfNotAlreadyGenerated);
-    if (binding.unresolved().isPresent()) {
-      membersInjectionBindings.tryToGenerateBinding(
-          binding.unresolved().get(), warnIfNotAlreadyGenerated);
-    }
   }
 
   @Override
@@ -255,15 +253,16 @@
 
     ValidationReport<TypeElement> report = injectValidator.validateConstructor(constructorElement);
     report.printMessagesTo(messager);
-    if (report.isClean()) {
-      ProvisionBinding binding = bindingFactory.injectionBinding(constructorElement, resolvedType);
-      registerBinding(binding, warnIfNotAlreadyGenerated);
-      if (!binding.injectionSites().isEmpty()) {
-        tryRegisterMembersInjectedType(typeElement, resolvedType, warnIfNotAlreadyGenerated);
-      }
-      return Optional.of(binding);
+    if (!report.isClean()) {
+      return Optional.empty();
     }
-    return Optional.empty();
+
+    ProvisionBinding binding = bindingFactory.injectionBinding(constructorElement, resolvedType);
+    registerBinding(binding, warnIfNotAlreadyGenerated);
+    if (!binding.injectionSites().isEmpty()) {
+      tryRegisterMembersInjectedType(typeElement, resolvedType, warnIfNotAlreadyGenerated);
+    }
+    return Optional.of(binding);
   }
 
   @Override
@@ -286,17 +285,18 @@
     ValidationReport<TypeElement> report =
         injectValidator.validateMembersInjectionType(typeElement);
     report.printMessagesTo(messager);
-    if (report.isClean()) {
-      MembersInjectionBinding binding = bindingFactory.membersInjectionBinding(type, resolvedType);
-      registerBinding(binding, warnIfNotAlreadyGenerated);
-      for (Optional<DeclaredType> supertype = types.nonObjectSuperclass(type);
-          supertype.isPresent();
-          supertype = types.nonObjectSuperclass(supertype.get())) {
-        getOrFindMembersInjectionBinding(keyFactory.forMembersInjectedType(supertype.get()));
-      }
-      return Optional.of(binding);
+    if (!report.isClean()) {
+      return Optional.empty();
     }
-    return Optional.empty();
+
+    MembersInjectionBinding binding = bindingFactory.membersInjectionBinding(type, resolvedType);
+    registerBinding(binding, warnIfNotAlreadyGenerated);
+    for (Optional<DeclaredType> supertype = types.nonObjectSuperclass(type);
+         supertype.isPresent();
+         supertype = types.nonObjectSuperclass(supertype.get())) {
+      getOrFindMembersInjectionBinding(keyFactory.forMembersInjectedType(supertype.get()));
+    }
+    return Optional.of(binding);
   }
 
   @CanIgnoreReturnValue
@@ -352,7 +352,7 @@
     if (!isValidMembersInjectionKey(key)) {
       return Optional.empty();
     }
-    Key membersInjectionKey = keyFactory.forMembersInjectedType(types.unwrapType(key.type()));
+    Key membersInjectionKey = keyFactory.forMembersInjectedType(unwrapType(key.type()));
     return getOrFindMembersInjectionBinding(membersInjectionKey)
         .map(binding -> bindingFactory.membersInjectorBinding(key, binding));
   }
diff --git a/java/dagger/internal/codegen/validation/MonitoringModuleGenerator.java b/java/dagger/internal/codegen/validation/MonitoringModuleGenerator.java
index 2c72e5f..1c2eb1c 100644
--- a/java/dagger/internal/codegen/validation/MonitoringModuleGenerator.java
+++ b/java/dagger/internal/codegen/validation/MonitoringModuleGenerator.java
@@ -26,6 +26,7 @@
 import static javax.lang.model.element.Modifier.PRIVATE;
 import static javax.lang.model.element.Modifier.STATIC;
 
+import com.google.common.collect.ImmutableList;
 import com.squareup.javapoet.ClassName;
 import com.squareup.javapoet.MethodSpec;
 import com.squareup.javapoet.TypeSpec;
@@ -38,7 +39,6 @@
 import dagger.producers.ProductionScope;
 import dagger.producers.monitoring.ProductionComponentMonitor;
 import dagger.producers.monitoring.internal.Monitors;
-import java.util.Optional;
 import javax.annotation.processing.Filer;
 import javax.inject.Inject;
 import javax.lang.model.SourceVersion;
@@ -54,19 +54,14 @@
   }
 
   @Override
-  public ClassName nameGeneratedType(TypeElement componentElement) {
-    return SourceFiles.generatedMonitoringModuleName(componentElement);
-  }
-
-  @Override
   public Element originatingElement(TypeElement componentElement) {
     return componentElement;
   }
 
   @Override
-  public Optional<TypeSpec.Builder> write(TypeElement componentElement) {
-    return Optional.of(
-        classBuilder(nameGeneratedType(componentElement))
+  public ImmutableList<TypeSpec.Builder> topLevelTypes(TypeElement componentElement) {
+    return ImmutableList.of(
+        classBuilder(SourceFiles.generatedMonitoringModuleName(componentElement))
             .addAnnotation(Module.class)
             .addModifiers(ABSTRACT)
             .addMethod(privateConstructor())
diff --git a/java/dagger/internal/codegen/writing/AnnotationCreatorGenerator.java b/java/dagger/internal/codegen/writing/AnnotationCreatorGenerator.java
index 96e6340..fa3a16c 100644
--- a/java/dagger/internal/codegen/writing/AnnotationCreatorGenerator.java
+++ b/java/dagger/internal/codegen/writing/AnnotationCreatorGenerator.java
@@ -39,7 +39,6 @@
 import dagger.internal.codegen.base.SourceFileGenerator;
 import dagger.internal.codegen.langmodel.DaggerElements;
 import java.util.LinkedHashSet;
-import java.util.Optional;
 import java.util.Set;
 import javax.annotation.processing.Filer;
 import javax.inject.Inject;
@@ -88,18 +87,13 @@
   }
 
   @Override
-  public ClassName nameGeneratedType(TypeElement annotationType) {
-    return getAnnotationCreatorClassName(annotationType);
-  }
-
-  @Override
   public Element originatingElement(TypeElement annotationType) {
     return annotationType;
   }
 
   @Override
-  public Optional<TypeSpec.Builder> write(TypeElement annotationType) {
-    ClassName generatedTypeName = nameGeneratedType(annotationType);
+  public ImmutableList<TypeSpec.Builder> topLevelTypes(TypeElement annotationType) {
+    ClassName generatedTypeName = getAnnotationCreatorClassName(annotationType);
     TypeSpec.Builder annotationCreatorBuilder =
         classBuilder(generatedTypeName)
             .addModifiers(PUBLIC, FINAL)
@@ -109,7 +103,7 @@
       annotationCreatorBuilder.addMethod(buildCreateMethod(generatedTypeName, annotationElement));
     }
 
-    return Optional.of(annotationCreatorBuilder);
+    return ImmutableList.of(annotationCreatorBuilder);
   }
 
   private MethodSpec buildCreateMethod(ClassName generatedTypeName, TypeElement annotationElement) {
diff --git a/java/dagger/internal/codegen/writing/AssistedFactoryBindingExpression.java b/java/dagger/internal/codegen/writing/AssistedFactoryBindingExpression.java
index d0c481c..d90ab71 100644
--- a/java/dagger/internal/codegen/writing/AssistedFactoryBindingExpression.java
+++ b/java/dagger/internal/codegen/writing/AssistedFactoryBindingExpression.java
@@ -83,7 +83,7 @@
   private TypeSpec anonymousfactoryImpl(Expression assistedInjectionExpression) {
     TypeElement factory = asType(binding.bindingElement().get());
     DeclaredType factoryType = asDeclared(binding.key().type());
-    ExecutableElement factoryMethod = assistedFactoryMethod(factory, elements, types);
+    ExecutableElement factoryMethod = assistedFactoryMethod(factory, elements);
 
     // We can't use MethodSpec.overriding directly because we need to control the parameter names.
     MethodSpec factoryOverride = MethodSpec.overriding(factoryMethod, factoryType, types).build();
diff --git a/java/dagger/internal/codegen/writing/ComponentBindingExpressions.java b/java/dagger/internal/codegen/writing/ComponentBindingExpressions.java
index 5f1f0bd..dc15c99 100644
--- a/java/dagger/internal/codegen/writing/ComponentBindingExpressions.java
+++ b/java/dagger/internal/codegen/writing/ComponentBindingExpressions.java
@@ -212,19 +212,14 @@
   public MethodSpec getComponentMethod(ComponentMethodDescriptor componentMethod) {
     checkArgument(componentMethod.dependencyRequest().isPresent());
     BindingRequest request = bindingRequest(componentMethod.dependencyRequest().get());
-    MethodSpec.Builder method =
-        MethodSpec.overriding(
+    return MethodSpec.overriding(
             componentMethod.methodElement(),
             MoreTypes.asDeclared(graph.componentTypeElement().asType()),
-            types);
-    // Even though this is not used if the method is abstract, we need to invoke the binding
-    // expression in order for the side of effect of the method being added to the
-    // ComponentImplementation
-    CodeBlock methodBody =
-        getBindingExpression(request)
-            .getComponentMethodImplementation(componentMethod, componentImplementation);
-
-    return method.addCode(methodBody).build();
+            types)
+        .addCode(
+            getBindingExpression(request)
+                .getComponentMethodImplementation(componentMethod, componentImplementation))
+        .build();
   }
 
   /** Returns the {@link BindingExpression} for the given {@link BindingRequest}. */
@@ -460,8 +455,7 @@
   private BindingExpression providerBindingExpression(ContributionBinding binding) {
     if (binding.kind().equals(DELEGATE) && !needsCaching(binding)) {
       return new DelegateBindingExpression(binding, RequestKind.PROVIDER, this, types, elements);
-    } else if (compilerOptions.fastInit(
-            topLevelComponentImplementation.componentDescriptor().typeElement())
+    } else if (isFastInit()
         && frameworkInstanceCreationExpression(binding).useInnerSwitchingProvider()
         && !(instanceBindingExpression(binding)
             instanceof DerivedFromFrameworkInstanceBindingExpression)) {
@@ -493,24 +487,27 @@
 
   /**
    * Returns a binding expression for {@link RequestKind#INSTANCE} requests.
-   *
-   * <p>If there is a direct expression (not calling {@link Provider#get()}) we can use for an
-   * instance of this binding, return it, wrapped in a method if the binding {@linkplain
-   * #needsCaching(ContributionBinding) needs to be cached} or the expression has dependencies.
-   *
-   * <p>In fastInit mode, we can use direct expressions unless the binding needs to be cached.
    */
   private BindingExpression instanceBindingExpression(ContributionBinding binding) {
     Optional<BindingExpression> maybeDirectInstanceExpression =
         unscopedDirectInstanceExpression(binding);
-    if (canUseDirectInstanceExpression(binding) && maybeDirectInstanceExpression.isPresent()) {
-      BindingExpression directInstanceExpression = maybeDirectInstanceExpression.get();
-      return directInstanceExpression.requiresMethodEncapsulation() || needsCaching(binding)
-          ? wrapInMethod(
-              binding,
-              bindingRequest(binding.key(), RequestKind.INSTANCE),
-              directInstanceExpression)
-          : directInstanceExpression;
+    if (maybeDirectInstanceExpression.isPresent()) {
+      // If this is the case where we don't need to use Provider#get() because there's no caching
+      // and it isn't an assisted factory, or because we're in fastInit mode (since fastInit avoids
+      // using Providers), we can try to use the direct expression, possibly wrapped in a method
+      // if necessary (e.g. it has dependencies).
+      if ((!needsCaching(binding) && binding.kind() != BindingKind.ASSISTED_FACTORY)
+          || isFastInit()) {
+        BindingExpression directInstanceExpression = maybeDirectInstanceExpression.get();
+        // While this can't require caching in default mode, if we're in fastInit mode and we need
+        // caching we also need to wrap it in a method.
+        return directInstanceExpression.requiresMethodEncapsulation() || needsCaching(binding)
+            ? wrapInMethod(
+                binding,
+                bindingRequest(binding.key(), RequestKind.INSTANCE),
+                directInstanceExpression)
+            : directInstanceExpression;
+      }
     }
     return new DerivedFromFrameworkInstanceBindingExpression(
         binding.key(), FrameworkType.PROVIDER, RequestKind.INSTANCE, this, types);
@@ -610,27 +607,12 @@
    * MapFactory} or {@code SetFactory}.
    */
   private boolean useStaticFactoryCreation(ContributionBinding binding) {
-    return !compilerOptions.fastInit(
-            topLevelComponentImplementation.componentDescriptor().typeElement())
+    return !isFastInit()
         || binding.kind().equals(MULTIBOUND_MAP)
         || binding.kind().equals(MULTIBOUND_SET);
   }
 
   /**
-   * Returns {@code true} if we can use a direct (not {@code Provider.get()}) expression for this
-   * binding. If the binding doesn't {@linkplain #needsCaching(ContributionBinding) need to be
-   * cached} and the binding is not an {@link BindingKind.ASSISTED_FACTORY}, we can.
-   *
-   * <p>In fastInit mode, we can use a direct expression even if the binding {@linkplain
-   * #needsCaching(ContributionBinding) needs to be cached}.
-   */
-  private boolean canUseDirectInstanceExpression(ContributionBinding binding) {
-    return (!needsCaching(binding) && binding.kind() != BindingKind.ASSISTED_FACTORY)
-        || compilerOptions.fastInit(
-            topLevelComponentImplementation.componentDescriptor().typeElement());
-  }
-
-  /**
    * Returns a binding expression that uses a given one as the body of a method that users call. If
    * a component provision method matches it, it will be the method implemented. If it does not
    * match a component provision method and the binding is modifiable, then a new public modifiable
@@ -690,8 +672,7 @@
 
   private MethodImplementationStrategy methodImplementationStrategy(
       ContributionBinding binding, BindingRequest request) {
-    if (compilerOptions.fastInit(
-        topLevelComponentImplementation.componentDescriptor().typeElement())) {
+    if (isFastInit()) {
       if (request.isRequestKind(RequestKind.PROVIDER)) {
         return MethodImplementationStrategy.SINGLE_CHECK;
       } else if (request.isRequestKind(RequestKind.INSTANCE) && needsCaching(binding)) {
@@ -718,4 +699,9 @@
     }
     return true;
   }
+
+  private boolean isFastInit() {
+    return compilerOptions.fastInit(
+        topLevelComponentImplementation.componentDescriptor().typeElement());
+  }
 }
diff --git a/java/dagger/internal/codegen/writing/ComponentImplementation.java b/java/dagger/internal/codegen/writing/ComponentImplementation.java
index ef69124..a09620e 100644
--- a/java/dagger/internal/codegen/writing/ComponentImplementation.java
+++ b/java/dagger/internal/codegen/writing/ComponentImplementation.java
@@ -276,7 +276,10 @@
         "%s is not a child component of %s",
         childDescriptor.typeElement(),
         componentDescriptor().typeElement());
-    return name.nestedClass(subcomponentNames.get(childDescriptor) + "Impl");
+    // TODO(erichang): Hacky fix to shorten the suffix if we're too deeply
+    // nested to save on file name length. 2 chosen arbitrarily.
+    String suffix = name.simpleNames().size() > 2 ? "I" : "Impl";
+    return name.nestedClass(subcomponentNames.get(childDescriptor) + suffix);
   }
 
   /**
diff --git a/java/dagger/internal/codegen/writing/DependencyMethodProviderCreationExpression.java b/java/dagger/internal/codegen/writing/DependencyMethodProviderCreationExpression.java
index 5a40c02..af5d45e 100644
--- a/java/dagger/internal/codegen/writing/DependencyMethodProviderCreationExpression.java
+++ b/java/dagger/internal/codegen/writing/DependencyMethodProviderCreationExpression.java
@@ -96,7 +96,7 @@
         COMPONENT_PROVISION_FACTORY,
         classBuilder(factoryClassName())
             .addSuperinterface(providerOf(keyType))
-            .addModifiers(PRIVATE, STATIC)
+            .addModifiers(PRIVATE, STATIC, FINAL)
             .addField(dependencyClassName, dependency().variableName(), PRIVATE, FINAL)
             .addMethod(
                 constructorBuilder()
diff --git a/java/dagger/internal/codegen/writing/FactoryGenerator.java b/java/dagger/internal/codegen/writing/FactoryGenerator.java
index 70edd1a..03ad27c 100644
--- a/java/dagger/internal/codegen/writing/FactoryGenerator.java
+++ b/java/dagger/internal/codegen/writing/FactoryGenerator.java
@@ -97,32 +97,27 @@
   }
 
   @Override
-  public ClassName nameGeneratedType(ProvisionBinding binding) {
-    return generatedClassNameForBinding(binding);
-  }
-
-  @Override
   public Element originatingElement(ProvisionBinding binding) {
     // we only create factories for bindings that have a binding element
     return binding.bindingElement().get();
   }
 
   @Override
-  public Optional<TypeSpec.Builder> write(ProvisionBinding binding) {
+  public ImmutableList<TypeSpec.Builder> topLevelTypes(ProvisionBinding binding) {
     // We don't want to write out resolved bindings -- we want to write out the generic version.
     checkArgument(!binding.unresolved().isPresent());
     checkArgument(binding.bindingElement().isPresent());
 
     if (binding.factoryCreationStrategy().equals(DELEGATE)) {
-      return Optional.empty();
+      return ImmutableList.of();
     }
 
-    return Optional.of(factoryBuilder(binding));
+    return ImmutableList.of(factoryBuilder(binding));
   }
 
   private TypeSpec.Builder factoryBuilder(ProvisionBinding binding) {
     TypeSpec.Builder factoryBuilder =
-        classBuilder(nameGeneratedType(binding))
+        classBuilder(generatedClassNameForBinding(binding))
             .addModifiers(PUBLIC, FINAL)
             .addTypeVariables(bindingTypeElementTypeVariableNames(binding));
 
@@ -194,8 +189,9 @@
     switch (binding.factoryCreationStrategy()) {
       case SINGLETON_INSTANCE:
         FieldSpec.Builder instanceFieldBuilder =
-            FieldSpec.builder(nameGeneratedType(binding), "INSTANCE", PRIVATE, STATIC, FINAL)
-                .initializer("new $T()", nameGeneratedType(binding));
+            FieldSpec.builder(
+                    generatedClassNameForBinding(binding), "INSTANCE", PRIVATE, STATIC, FINAL)
+                .initializer("new $T()", generatedClassNameForBinding(binding));
 
         if (!bindingTypeElementTypeVariableNames(binding).isEmpty()) {
           // If the factory has type parameters, ignore them in the field declaration & initializer
@@ -203,7 +199,8 @@
           createMethodBuilder.addAnnotation(suppressWarnings(UNCHECKED));
         }
 
-        ClassName instanceHolderName = nameGeneratedType(binding).nestedClass("InstanceHolder");
+        ClassName instanceHolderName =
+            generatedClassNameForBinding(binding).nestedClass("InstanceHolder");
         createMethodBuilder.addStatement("return $T.INSTANCE", instanceHolderName);
         factoryBuilder.addType(
             TypeSpec.classBuilder(instanceHolderName)
@@ -248,7 +245,7 @@
             request ->
                 frameworkTypeUsageStatement(
                     CodeBlock.of("$N", frameworkFields.get(request)), request.kind()),
-            nameGeneratedType(binding),
+            generatedClassNameForBinding(binding),
             moduleParameter(binding).map(module -> CodeBlock.of("$N", module)),
             compilerOptions,
             metadataUtil);
@@ -265,7 +262,7 @@
           .addCode(
               InjectionSiteMethod.invokeAll(
                   binding.injectionSites(),
-                  nameGeneratedType(binding),
+                  generatedClassNameForBinding(binding),
                   instance,
                   binding.key().type(),
                   frameworkFieldUsages(binding.dependencies(), frameworkFields)::get,
diff --git a/java/dagger/internal/codegen/writing/HjarSourceFileGenerator.java b/java/dagger/internal/codegen/writing/HjarSourceFileGenerator.java
index 5a4b6f1..d4dbde7 100644
--- a/java/dagger/internal/codegen/writing/HjarSourceFileGenerator.java
+++ b/java/dagger/internal/codegen/writing/HjarSourceFileGenerator.java
@@ -19,14 +19,15 @@
 import static com.squareup.javapoet.MethodSpec.constructorBuilder;
 import static com.squareup.javapoet.MethodSpec.methodBuilder;
 import static com.squareup.javapoet.TypeSpec.classBuilder;
+import static dagger.internal.codegen.extension.DaggerStreams.toImmutableList;
 import static javax.lang.model.element.Modifier.PRIVATE;
 
+import com.google.common.collect.ImmutableList;
 import com.squareup.javapoet.ClassName;
 import com.squareup.javapoet.FieldSpec;
 import com.squareup.javapoet.MethodSpec;
 import com.squareup.javapoet.TypeSpec;
 import dagger.internal.codegen.base.SourceFileGenerator;
-import java.util.Optional;
 import javax.lang.model.element.Element;
 import javax.lang.model.element.Modifier;
 
@@ -47,18 +48,15 @@
   }
 
   @Override
-  public ClassName nameGeneratedType(T input) {
-    return delegate.nameGeneratedType(input);
-  }
-
-  @Override
   public Element originatingElement(T input) {
     return delegate.originatingElement(input);
   }
 
   @Override
-  public Optional<TypeSpec.Builder> write(T input) {
-    return delegate.write(input).map(completeType -> skeletonType(completeType.build()));
+  public ImmutableList<TypeSpec.Builder> topLevelTypes(T input) {
+    return delegate.topLevelTypes(input).stream()
+        .map(completeType -> skeletonType(completeType.build()))
+        .collect(toImmutableList());
   }
 
   private TypeSpec.Builder skeletonType(TypeSpec completeType) {
diff --git a/java/dagger/internal/codegen/writing/InaccessibleMapKeyProxyGenerator.java b/java/dagger/internal/codegen/writing/InaccessibleMapKeyProxyGenerator.java
index d527359..889f898 100644
--- a/java/dagger/internal/codegen/writing/InaccessibleMapKeyProxyGenerator.java
+++ b/java/dagger/internal/codegen/writing/InaccessibleMapKeyProxyGenerator.java
@@ -22,14 +22,13 @@
 import static javax.lang.model.element.Modifier.PRIVATE;
 import static javax.lang.model.element.Modifier.PUBLIC;
 
-import com.squareup.javapoet.ClassName;
+import com.google.common.collect.ImmutableList;
 import com.squareup.javapoet.TypeSpec;
 import dagger.internal.codegen.base.SourceFileGenerator;
 import dagger.internal.codegen.binding.ContributionBinding;
 import dagger.internal.codegen.binding.MapKeys;
 import dagger.internal.codegen.langmodel.DaggerElements;
 import dagger.internal.codegen.langmodel.DaggerTypes;
-import java.util.Optional;
 import javax.annotation.processing.Filer;
 import javax.inject.Inject;
 import javax.lang.model.SourceVersion;
@@ -53,24 +52,21 @@
   }
 
   @Override
-  public ClassName nameGeneratedType(ContributionBinding binding) {
-    return MapKeys.mapKeyProxyClassName(binding);
-  }
-
-  @Override
   public Element originatingElement(ContributionBinding binding) {
     // a map key is only ever present on bindings that have a binding element
     return binding.bindingElement().get();
   }
 
   @Override
-  public Optional<TypeSpec.Builder> write(ContributionBinding binding) {
+  public ImmutableList<TypeSpec.Builder> topLevelTypes(ContributionBinding binding) {
     return MapKeys.mapKeyFactoryMethod(binding, types, elements)
         .map(
             method ->
-                classBuilder(nameGeneratedType(binding))
+                classBuilder(MapKeys.mapKeyProxyClassName(binding))
                     .addModifiers(PUBLIC, FINAL)
                     .addMethod(constructorBuilder().addModifiers(PRIVATE).build())
-                    .addMethod(method));
+                    .addMethod(method))
+        .map(ImmutableList::of)
+        .orElse(ImmutableList.of());
   }
 }
diff --git a/java/dagger/internal/codegen/writing/MembersInjectorGenerator.java b/java/dagger/internal/codegen/writing/MembersInjectorGenerator.java
index df618b0..8630ce6 100644
--- a/java/dagger/internal/codegen/writing/MembersInjectorGenerator.java
+++ b/java/dagger/internal/codegen/writing/MembersInjectorGenerator.java
@@ -61,7 +61,6 @@
 import dagger.internal.codegen.writing.InjectionMethods.InjectionSiteMethod;
 import dagger.model.DependencyRequest;
 import java.util.Map.Entry;
-import java.util.Optional;
 import javax.annotation.processing.Filer;
 import javax.inject.Inject;
 import javax.lang.model.SourceVersion;
@@ -87,20 +86,15 @@
   }
 
   @Override
-  public ClassName nameGeneratedType(MembersInjectionBinding binding) {
-    return membersInjectorNameForType(binding.membersInjectedType());
-  }
-
-  @Override
   public Element originatingElement(MembersInjectionBinding binding) {
     return binding.membersInjectedType();
   }
 
   @Override
-  public Optional<TypeSpec.Builder> write(MembersInjectionBinding binding) {
+  public ImmutableList<TypeSpec.Builder> topLevelTypes(MembersInjectionBinding binding) {
     // Empty members injection bindings are special and don't need source files.
     if (binding.injectionSites().isEmpty()) {
-      return Optional.empty();
+      return ImmutableList.of();
     }
 
     // Members injectors for classes with no local injection sites and no @Inject
@@ -108,7 +102,7 @@
     if (!binding.hasLocalInjectionSites()
         && injectedConstructors(binding.membersInjectedType()).isEmpty()
         && assistedInjectedConstructors(binding.membersInjectedType()).isEmpty()) {
-      return Optional.empty();
+      return ImmutableList.of();
     }
 
 
@@ -118,7 +112,7 @@
         "tried to generate a MembersInjector for a binding of a resolved generic type: %s",
         binding);
 
-    ClassName generatedTypeName = nameGeneratedType(binding);
+    ClassName generatedTypeName = membersInjectorNameForType(binding.membersInjectedType());
     ImmutableList<TypeVariableName> typeParameters = bindingTypeElementTypeVariableNames(binding);
     TypeSpec.Builder injectorTypeBuilder =
         classBuilder(generatedTypeName)
@@ -222,6 +216,6 @@
 
     gwtIncompatibleAnnotation(binding).ifPresent(injectorTypeBuilder::addAnnotation);
 
-    return Optional.of(injectorTypeBuilder);
+    return ImmutableList.of(injectorTypeBuilder);
   }
 }
diff --git a/java/dagger/internal/codegen/writing/ModuleProxies.java b/java/dagger/internal/codegen/writing/ModuleProxies.java
index fcd9b56..6d174f8 100644
--- a/java/dagger/internal/codegen/writing/ModuleProxies.java
+++ b/java/dagger/internal/codegen/writing/ModuleProxies.java
@@ -27,6 +27,7 @@
 import static javax.lang.model.element.Modifier.STATIC;
 import static javax.lang.model.util.ElementFilter.constructorsIn;
 
+import com.google.common.collect.ImmutableList;
 import com.squareup.javapoet.ClassName;
 import com.squareup.javapoet.CodeBlock;
 import com.squareup.javapoet.TypeSpec;
@@ -78,25 +79,20 @@
     }
 
     @Override
-    public ClassName nameGeneratedType(TypeElement moduleElement) {
-      return moduleProxies.constructorProxyTypeName(moduleElement);
-    }
-
-    @Override
     public Element originatingElement(TypeElement moduleElement) {
       return moduleElement;
     }
 
     @Override
-    public Optional<TypeSpec.Builder> write(TypeElement moduleElement) {
+    public ImmutableList<TypeSpec.Builder> topLevelTypes(TypeElement moduleElement) {
       ModuleKind.checkIsModule(moduleElement, metadataUtil);
       return moduleProxies.nonPublicNullaryConstructor(moduleElement).isPresent()
-          ? Optional.of(buildProxy(moduleElement))
-          : Optional.empty();
+          ? ImmutableList.of(buildProxy(moduleElement))
+          : ImmutableList.of();
     }
 
     private TypeSpec.Builder buildProxy(TypeElement moduleElement) {
-      return classBuilder(nameGeneratedType(moduleElement))
+      return classBuilder(moduleProxies.constructorProxyTypeName(moduleElement))
           .addModifiers(PUBLIC, FINAL)
           .addMethod(constructorBuilder().addModifiers(PRIVATE).build())
           .addMethod(
diff --git a/java/dagger/internal/codegen/writing/ProducerFactoryGenerator.java b/java/dagger/internal/codegen/writing/ProducerFactoryGenerator.java
index 5e52923..a013be9 100644
--- a/java/dagger/internal/codegen/writing/ProducerFactoryGenerator.java
+++ b/java/dagger/internal/codegen/writing/ProducerFactoryGenerator.java
@@ -103,10 +103,6 @@
     this.keyFactory = keyFactory;
   }
 
-  @Override
-  public ClassName nameGeneratedType(ProductionBinding binding) {
-    return generatedClassNameForBinding(binding);
-  }
 
   @Override
   public Element originatingElement(ProductionBinding binding) {
@@ -115,7 +111,7 @@
   }
 
   @Override
-  public Optional<TypeSpec.Builder> write(ProductionBinding binding) {
+  public ImmutableList<TypeSpec.Builder> topLevelTypes(ProductionBinding binding) {
     // We don't want to write out resolved bindings -- we want to write out the generic version.
     checkArgument(!binding.unresolved().isPresent());
     checkArgument(binding.bindingElement().isPresent());
@@ -123,7 +119,7 @@
     TypeName providedTypeName = TypeName.get(binding.contributedType());
     TypeName futureTypeName = listenableFutureOf(providedTypeName);
 
-    ClassName generatedTypeName = nameGeneratedType(binding);
+    ClassName generatedTypeName = generatedClassNameForBinding(binding);
     TypeSpec.Builder factoryBuilder =
         classBuilder(generatedTypeName)
             .addModifiers(PUBLIC, FINAL)
@@ -233,7 +229,7 @@
     gwtIncompatibleAnnotation(binding).ifPresent(factoryBuilder::addAnnotation);
 
     // TODO(gak): write a sensible toString
-    return Optional.of(factoryBuilder);
+    return ImmutableList.of(factoryBuilder);
   }
 
   private MethodSpec staticFactoryMethod(ProductionBinding binding, MethodSpec constructor) {
diff --git a/javatests/artifacts/dagger-android/simple/build.gradle b/javatests/artifacts/dagger-android/simple/build.gradle
index 8c179e5..d29aa0c 100644
--- a/javatests/artifacts/dagger-android/simple/build.gradle
+++ b/javatests/artifacts/dagger-android/simple/build.gradle
@@ -34,5 +34,20 @@
       mavenCentral()
       mavenLocal()
     }
+    // TODO(bcorso): Consider organizing all projects under a single project
+    // that share a build.gradle configuration to reduce the copy-paste.
+    // Local tests: Adds logs for individual local tests
+    tasks.withType(Test) {
+        testLogging {
+            exceptionFormat "full"
+            showCauses true
+            showExceptions true
+            showStackTraces true
+            showStandardStreams true
+            events = ["passed", "skipped", "failed", "standardOut", "standardError"]
+        }
+    }
 }
 
+// Instrumentation tests: Combines test reports for all modules
+apply plugin: 'android-reporting'
diff --git a/javatests/artifacts/hilt-android/gradleConfigCache/app/build.gradle b/javatests/artifacts/hilt-android/gradleConfigCache/app/build.gradle
index 43ccd1d..4f21d0a 100644
--- a/javatests/artifacts/hilt-android/gradleConfigCache/app/build.gradle
+++ b/javatests/artifacts/hilt-android/gradleConfigCache/app/build.gradle
@@ -38,6 +38,9 @@
     kotlinOptions {
         jvmTarget = '1.8'
     }
+    lintOptions {
+        checkReleaseBuilds = false
+    }
     testOptions {
         unitTests.includeAndroidResources = true
     }
@@ -45,6 +48,7 @@
 
 hilt {
     enableTransformForLocalTests = true
+    enableExperimentalClasspathAggregation = true
 }
 
 dependencies {
diff --git a/javatests/artifacts/hilt-android/gradleConfigCache/build.gradle b/javatests/artifacts/hilt-android/gradleConfigCache/build.gradle
index 3b0cdde..de0566c 100644
--- a/javatests/artifacts/hilt-android/gradleConfigCache/build.gradle
+++ b/javatests/artifacts/hilt-android/gradleConfigCache/build.gradle
@@ -38,4 +38,20 @@
       mavenCentral()
       mavenLocal()
     }
+    // TODO(bcorso): Consider organizing all projects under a single project
+    // that share a build.gradle configuration to reduce the copy-paste.
+    // Local tests: Adds logs for individual local tests
+    tasks.withType(Test) {
+        testLogging {
+            exceptionFormat "full"
+            showCauses true
+            showExceptions true
+            showStackTraces true
+            showStandardStreams true
+            events = ["passed", "skipped", "failed", "standardOut", "standardError"]
+        }
+    }
 }
+
+// Instrumentation tests: Combines test reports for all modules
+apply plugin: 'android-reporting'
\ No newline at end of file
diff --git a/javatests/artifacts/hilt-android/simple/app/build.gradle b/javatests/artifacts/hilt-android/simple/app/build.gradle
index 98b79e1..1d76ec2 100644
--- a/javatests/artifacts/hilt-android/simple/app/build.gradle
+++ b/javatests/artifacts/hilt-android/simple/app/build.gradle
@@ -97,7 +97,6 @@
 
   // To help us catch version skew related issues in hilt extensions.
   // TODO(bcorso): Add examples testing the actual API.
-  implementation 'androidx.hilt:hilt-lifecycle-viewmodel:1.0.0-alpha01'
   implementation 'androidx.hilt:hilt-work:1.0.0-alpha01'
   annotationProcessor 'androidx.hilt:hilt-compiler:1.0.0-alpha01'
   testAnnotationProcessor 'androidx.hilt:hilt-compiler:1.0.0-alpha01'
diff --git a/javatests/artifacts/hilt-android/simple/app/src/androidTest/java/dagger/hilt/android/simple/CustomTestApplicationTest.java b/javatests/artifacts/hilt-android/simple/app/src/androidTest/java/dagger/hilt/android/simple/CustomTestApplicationTest.java
new file mode 100644
index 0000000..de2e37a
--- /dev/null
+++ b/javatests/artifacts/hilt-android/simple/app/src/androidTest/java/dagger/hilt/android/simple/CustomTestApplicationTest.java
@@ -0,0 +1,66 @@
+/*
+ * Copyright (C) 2021 The Dagger Authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package dagger.hilt.android.simple;
+
+import static androidx.test.core.app.ApplicationProvider.getApplicationContext;
+import static com.google.common.truth.Truth.assertThat;
+
+import android.content.Context;
+import androidx.test.ext.junit.runners.AndroidJUnit4;
+import dagger.hilt.android.simple.BaseTestApplication.Foo;
+import dagger.hilt.android.testing.HiltAndroidRule;
+import dagger.hilt.android.testing.HiltAndroidTest;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+/** Tests {@link dagger.hilt.android.testing.CustomTestApplication}. */
+@HiltAndroidTest
+@RunWith(AndroidJUnit4.class)
+public final class CustomTestApplicationTest {
+  @Rule public HiltAndroidRule rule = new HiltAndroidRule(this);
+
+  @Test
+  public void testApplicationBaseClass() throws Exception {
+    assertThat((Context) getApplicationContext()).isInstanceOf(BaseTestApplication.class);
+  }
+
+  @Test
+  public void testEarlyEntryPoint() throws Exception {
+    BaseTestApplication app = (BaseTestApplication) getApplicationContext();
+
+    // Assert that all scoped Foo instances from EarlyEntryPoint are equal
+    Foo earlyFoo = app.earlyFoo();
+    Foo lazyEarlyFoo1 = app.lazyEarlyFoo();
+    Foo lazyEarlyFoo2 = app.lazyEarlyFoo();
+    assertThat(earlyFoo).isNotNull();
+    assertThat(lazyEarlyFoo1).isNotNull();
+    assertThat(lazyEarlyFoo2).isNotNull();
+    assertThat(earlyFoo).isEqualTo(lazyEarlyFoo1);
+    assertThat(earlyFoo).isEqualTo(lazyEarlyFoo2);
+
+    // Assert that all scoped Foo instances from EntryPoint are equal
+    Foo lazyFoo1 = app.lazyFoo();
+    Foo lazyFoo2 = app.lazyFoo();
+    assertThat(lazyFoo1).isNotNull();
+    assertThat(lazyFoo2).isNotNull();
+    assertThat(lazyFoo1).isEqualTo(lazyFoo2);
+
+    // Assert that scoped Foo instances from EarlyEntryPoint and EntryPoint are not equal
+    assertThat(earlyFoo).isNotEqualTo(lazyFoo1);
+  }
+}
diff --git a/javatests/artifacts/hilt-android/simple/app/src/androidTest/java/dagger/hilt/android/simple/SimpleEmulatorTestRunner.java b/javatests/artifacts/hilt-android/simple/app/src/androidTest/java/dagger/hilt/android/simple/SimpleEmulatorTestRunner.java
index 6ee721c..8d4913b 100644
--- a/javatests/artifacts/hilt-android/simple/app/src/androidTest/java/dagger/hilt/android/simple/SimpleEmulatorTestRunner.java
+++ b/javatests/artifacts/hilt-android/simple/app/src/androidTest/java/dagger/hilt/android/simple/SimpleEmulatorTestRunner.java
@@ -19,14 +19,15 @@
 import android.app.Application;
 import android.content.Context;
 import androidx.test.runner.AndroidJUnitRunner;
-import dagger.hilt.android.testing.HiltTestApplication;
+import dagger.hilt.android.testing.CustomTestApplication;
 
 /** A custom runner to setup the emulator application class for tests. */
+@CustomTestApplication(BaseTestApplication.class)
 public final class SimpleEmulatorTestRunner extends AndroidJUnitRunner {
 
   @Override
   public Application newApplication(ClassLoader cl, String className, Context context)
       throws ClassNotFoundException, IllegalAccessException, InstantiationException {
-    return super.newApplication(cl, HiltTestApplication.class.getName(), context);
+    return super.newApplication(cl, SimpleEmulatorTestRunner_Application.class.getName(), context);
   }
 }
diff --git a/javatests/artifacts/hilt-android/simple/app/src/sharedTest/java/dagger/hilt/android/simple/BaseTestApplication.java b/javatests/artifacts/hilt-android/simple/app/src/sharedTest/java/dagger/hilt/android/simple/BaseTestApplication.java
new file mode 100644
index 0000000..7af0cb3
--- /dev/null
+++ b/javatests/artifacts/hilt-android/simple/app/src/sharedTest/java/dagger/hilt/android/simple/BaseTestApplication.java
@@ -0,0 +1,68 @@
+/*
+ * Copyright (C) 2021 The Dagger Authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package dagger.hilt.android.simple;
+
+import android.app.Application;
+import dagger.hilt.EntryPoint;
+import dagger.hilt.EntryPoints;
+import dagger.hilt.InstallIn;
+import dagger.hilt.android.EarlyEntryPoint;
+import dagger.hilt.android.EarlyEntryPoints;
+import dagger.hilt.components.SingletonComponent;
+import javax.inject.Inject;
+import javax.inject.Singleton;
+
+/** A custom application used to test @EarlyEntryPoint */
+public class BaseTestApplication extends Application {
+  @EarlyEntryPoint
+  @InstallIn(SingletonComponent.class)
+  interface EarlyFooEntryPoint {
+    Foo earlyFoo();
+  }
+
+  @EntryPoint
+  @InstallIn(SingletonComponent.class)
+  interface FooEntryPoint {
+    Foo lazyFoo();
+  }
+
+  @Singleton
+  public static final class Foo {
+    @Inject
+    Foo() {}
+  }
+
+  private Foo earlyFoo;
+
+  @Override
+  public void onCreate() {
+    super.onCreate();
+    earlyFoo = EarlyEntryPoints.get(this, EarlyFooEntryPoint.class).earlyFoo();
+  }
+
+  public Foo earlyFoo() {
+    return earlyFoo;
+  }
+
+  public Foo lazyEarlyFoo() {
+    return EarlyEntryPoints.get(this, EarlyFooEntryPoint.class).earlyFoo();
+  }
+
+  public Foo lazyFoo() {
+    return EntryPoints.get(this, FooEntryPoint.class).lazyFoo();
+  }
+}
diff --git a/javatests/artifacts/hilt-android/simple/app/src/test/java/dagger/hilt/android/simple/CustomTestApplicationTest.java b/javatests/artifacts/hilt-android/simple/app/src/test/java/dagger/hilt/android/simple/CustomTestApplicationTest.java
new file mode 100644
index 0000000..8c7f73f
--- /dev/null
+++ b/javatests/artifacts/hilt-android/simple/app/src/test/java/dagger/hilt/android/simple/CustomTestApplicationTest.java
@@ -0,0 +1,72 @@
+/*
+ * Copyright (C) 2021 The Dagger Authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package dagger.hilt.android.simple;
+
+import static androidx.test.core.app.ApplicationProvider.getApplicationContext;
+import static com.google.common.truth.Truth.assertThat;
+
+import android.content.Context;
+import android.os.Build;
+import androidx.test.ext.junit.runners.AndroidJUnit4;
+import dagger.hilt.android.simple.BaseTestApplication.Foo;
+import dagger.hilt.android.testing.CustomTestApplication;
+import dagger.hilt.android.testing.HiltAndroidRule;
+import dagger.hilt.android.testing.HiltAndroidTest;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.robolectric.annotation.Config;
+
+/** Tests {@link dagger.hilt.android.testing.CustomTestApplication}. */
+@CustomTestApplication(BaseTestApplication.class)
+@HiltAndroidTest
+@RunWith(AndroidJUnit4.class)
+// Robolectric requires Java9 to run API 29 and above, so use API 28 instead
+@Config(sdk = Build.VERSION_CODES.P, application = CustomTestApplicationTest_Application.class)
+public final class CustomTestApplicationTest {
+  @Rule public HiltAndroidRule rule = new HiltAndroidRule(this);
+
+  @Test
+  public void testApplicationBaseClass() throws Exception {
+    assertThat((Context) getApplicationContext()).isInstanceOf(BaseTestApplication.class);
+  }
+
+  @Test
+  public void testEarlyEntryPoint() throws Exception {
+    BaseTestApplication app = (BaseTestApplication) getApplicationContext();
+
+    // Assert that all scoped Foo instances from EarlyEntryPoint are equal
+    Foo earlyFoo = app.earlyFoo();
+    Foo lazyEarlyFoo1 = app.lazyEarlyFoo();
+    Foo lazyEarlyFoo2 = app.lazyEarlyFoo();
+    assertThat(earlyFoo).isNotNull();
+    assertThat(lazyEarlyFoo1).isNotNull();
+    assertThat(lazyEarlyFoo2).isNotNull();
+    assertThat(earlyFoo).isEqualTo(lazyEarlyFoo1);
+    assertThat(earlyFoo).isEqualTo(lazyEarlyFoo2);
+
+    // Assert that all scoped Foo instances from EntryPoint are equal
+    Foo lazyFoo1 = app.lazyFoo();
+    Foo lazyFoo2 = app.lazyFoo();
+    assertThat(lazyFoo1).isNotNull();
+    assertThat(lazyFoo2).isNotNull();
+    assertThat(lazyFoo1).isEqualTo(lazyFoo2);
+
+    // Assert that scoped Foo instances from EarlyEntryPoint and EntryPoint are not equal
+    assertThat(earlyFoo).isNotEqualTo(lazyFoo1);
+  }
+}
diff --git a/javatests/artifacts/hilt-android/simple/build.gradle b/javatests/artifacts/hilt-android/simple/build.gradle
index 770fabd..59336bc 100644
--- a/javatests/artifacts/hilt-android/simple/build.gradle
+++ b/javatests/artifacts/hilt-android/simple/build.gradle
@@ -39,4 +39,20 @@
       mavenCentral()
       mavenLocal()
     }
+    // TODO(bcorso): Consider organizing all projects under a single project
+    // that share a build.gradle configuration to reduce the copy-paste.
+    // Local tests: Adds logs for individual local tests
+    tasks.withType(Test) {
+        testLogging {
+            exceptionFormat "full"
+            showCauses true
+            showExceptions true
+            showStackTraces true
+            showStandardStreams true
+            events = ["passed", "skipped", "failed", "standardOut", "standardError"]
+        }
+    }
 }
+
+// Instrumentation tests: Combines test reports for all modules
+apply plugin: 'android-reporting'
diff --git a/javatests/artifacts/hilt-android/simpleKotlin/build.gradle b/javatests/artifacts/hilt-android/simpleKotlin/build.gradle
index d594476..e7eb9b2 100644
--- a/javatests/artifacts/hilt-android/simpleKotlin/build.gradle
+++ b/javatests/artifacts/hilt-android/simpleKotlin/build.gradle
@@ -38,4 +38,17 @@
       mavenCentral()
       mavenLocal()
     }
+    // TODO(bcorso): Consider organizing all projects under a single project
+    // that share a build.gradle configuration to reduce the copy-paste.
+    // Local tests: Adds logs for individual local tests
+    tasks.withType(Test) {
+        testLogging {
+            exceptionFormat "full"
+            showCauses true
+            showExceptions true
+            showStackTraces true
+            showStandardStreams true
+            events = ["passed", "skipped", "failed", "standardOut", "standardError"]
+        }
+    }
 }
diff --git a/javatests/dagger/android/AndroidInjectionTest.java b/javatests/dagger/android/AndroidInjectionTest.java
index 7e60ebd..499e981 100644
--- a/javatests/dagger/android/AndroidInjectionTest.java
+++ b/javatests/dagger/android/AndroidInjectionTest.java
@@ -23,16 +23,19 @@
 import android.app.Activity;
 import android.app.Application;
 import android.app.Fragment;
+import android.os.Build;
+import androidx.test.ext.junit.runners.AndroidJUnit4;
 import org.junit.Test;
 import org.junit.runner.RunWith;
 import org.robolectric.Robolectric;
-import org.robolectric.RobolectricTestRunner;
 import org.robolectric.annotation.Config;
 import org.robolectric.annotation.LooperMode;
 import org.robolectric.util.FragmentTestUtil;
 
 @LooperMode(LEGACY)
-@RunWith(RobolectricTestRunner.class)
+@RunWith(AndroidJUnit4.class)
+// Robolectric requires Java9 to run API 29 and above, so use API 28 instead
+@Config(sdk = Build.VERSION_CODES.P)
 public final class AndroidInjectionTest {
 
   // Most positive tests are performed in javatests/dagger/android/support/functional, but
diff --git a/javatests/dagger/android/AndroidProguardTest.java b/javatests/dagger/android/AndroidProguardTest.java
index 0f51e49..899d1f5 100644
--- a/javatests/dagger/android/AndroidProguardTest.java
+++ b/javatests/dagger/android/AndroidProguardTest.java
@@ -18,13 +18,17 @@
 
 import static com.google.common.truth.Truth.assertThat;
 
+import android.os.Build;
+import androidx.test.ext.junit.runners.AndroidJUnit4;
 import dagger.android.internal.AndroidInjectionKeys;
 import java.net.URL;
 import org.junit.Test;
 import org.junit.runner.RunWith;
-import org.robolectric.RobolectricTestRunner;
+import org.robolectric.annotation.Config;
 
-@RunWith(RobolectricTestRunner.class)
+@RunWith(AndroidJUnit4.class)
+// Robolectric requires Java9 to run API 29 and above, so use API 28 instead
+@Config(sdk = Build.VERSION_CODES.P)
 public class AndroidProguardTest {
 
   @Test
diff --git a/javatests/dagger/android/DispatchingAndroidInjectorTest.java b/javatests/dagger/android/DispatchingAndroidInjectorTest.java
index 37d3d61..4ae1b07 100644
--- a/javatests/dagger/android/DispatchingAndroidInjectorTest.java
+++ b/javatests/dagger/android/DispatchingAndroidInjectorTest.java
@@ -20,6 +20,8 @@
 import static org.junit.Assert.fail;
 
 import android.app.Activity;
+import android.os.Build;
+import androidx.test.ext.junit.runners.AndroidJUnit4;
 import com.google.common.collect.ImmutableMap;
 import dagger.android.AndroidInjector.Factory;
 import dagger.android.DispatchingAndroidInjector.InvalidInjectorBindingException;
@@ -28,9 +30,11 @@
 import org.junit.Test;
 import org.junit.runner.RunWith;
 import org.robolectric.Robolectric;
-import org.robolectric.RobolectricTestRunner;
+import org.robolectric.annotation.Config;
 
-@RunWith(RobolectricTestRunner.class)
+@RunWith(AndroidJUnit4.class)
+// Robolectric requires Java9 to run API 29 and above, so use API 28 instead
+@Config(sdk = Build.VERSION_CODES.P)
 public final class DispatchingAndroidInjectorTest {
   @Test
   public void withClassKeys() {
@@ -108,10 +112,7 @@
       Map<String, Provider<AndroidInjector.Factory<?>>>
           injectorFactoriesWithStringKeys) {
     return new DispatchingAndroidInjector<>(
-        injectorFactoriesWithClassKeys,
-        injectorFactoriesWithStringKeys ,
-        ImmutableMap.of(),
-        ImmutableMap.of());
+        injectorFactoriesWithClassKeys, injectorFactoriesWithStringKeys);
   }
 
   static class FooActivity extends Activity {}
diff --git a/javatests/dagger/android/processor/BUILD b/javatests/dagger/android/processor/BUILD
index d7b210c..c639a41 100644
--- a/javatests/dagger/android/processor/BUILD
+++ b/javatests/dagger/android/processor/BUILD
@@ -36,6 +36,10 @@
         "@google_bazel_common//third_party/java/compile_testing",
         "@google_bazel_common//third_party/java/junit",
         "@google_bazel_common//third_party/java/truth",
+        "@maven//:androidx_activity_activity",
         "@maven//:androidx_fragment_fragment",
+        "@maven//:androidx_lifecycle_lifecycle_common",
+        "@maven//:androidx_lifecycle_lifecycle_viewmodel",
+        "@maven//:androidx_lifecycle_lifecycle_viewmodel_savedstate",
     ],
 )
diff --git a/javatests/dagger/android/support/AndroidSupportInjectionTest.java b/javatests/dagger/android/support/AndroidSupportInjectionTest.java
index 25c5e94..f7d3009 100644
--- a/javatests/dagger/android/support/AndroidSupportInjectionTest.java
+++ b/javatests/dagger/android/support/AndroidSupportInjectionTest.java
@@ -20,21 +20,25 @@
 import static org.junit.Assert.fail;
 
 import android.app.Application;
+import android.os.Build;
 import androidx.fragment.app.Fragment;
+import androidx.fragment.app.FragmentActivity;
+import androidx.test.ext.junit.runners.AndroidJUnit4;
 import dagger.android.AndroidInjector;
 import dagger.android.HasAndroidInjector;
 import org.junit.Test;
 import org.junit.runner.RunWith;
-import org.robolectric.RobolectricTestRunner;
+import org.robolectric.Robolectric;
 import org.robolectric.annotation.Config;
-import org.robolectric.shadows.support.v4.SupportFragmentTestUtil;
 
-@RunWith(RobolectricTestRunner.class)
+@RunWith(AndroidJUnit4.class)
+// Robolectric requires Java9 to run API 29 and above, so use API 28 instead
+@Config(sdk = Build.VERSION_CODES.P)
 public final class AndroidSupportInjectionTest {
   @Test
   public void injectFragment_simpleApplication() {
     Fragment fragment = new Fragment();
-    SupportFragmentTestUtil.startFragment(fragment);
+    startFragment(fragment);
 
     try {
       AndroidSupportInjection.inject(fragment);
@@ -56,7 +60,7 @@
   @Config(application = ApplicationReturnsNull.class)
   public void fragmentInjector_returnsNull() {
     Fragment fragment = new Fragment();
-    SupportFragmentTestUtil.startFragment(fragment);
+    startFragment(fragment);
 
     try {
       AndroidSupportInjection.inject(fragment);
@@ -75,4 +79,12 @@
       assertThat(e).hasMessageThat().contains("fragment");
     }
   }
+
+  void startFragment(Fragment fragment) {
+    Robolectric.setupActivity(FragmentActivity.class)
+        .getSupportFragmentManager()
+        .beginTransaction()
+        .add(fragment, "")
+        .commitNow();
+  }
 }
diff --git a/javatests/dagger/android/support/BUILD b/javatests/dagger/android/support/BUILD
index 6bbfaa1..2ef1ece 100644
--- a/javatests/dagger/android/support/BUILD
+++ b/javatests/dagger/android/support/BUILD
@@ -25,6 +25,9 @@
     srcs = glob(["*.java"]),
     functional = False,
     javacopts = DOCLINT_HTML_AND_SYNTAX,
+    manifest_values = {
+        "minSdkVersion": "14",
+    },
     deps = [
         "//:dagger_with_compiler",
         "//java/dagger/android",
@@ -34,7 +37,11 @@
         "//java/dagger/internal/guava:concurrent",
         "@google_bazel_common//third_party/java/junit",
         "@google_bazel_common//third_party/java/truth",
+        "@maven//:androidx_activity_activity",
         "@maven//:androidx_appcompat_appcompat",
         "@maven//:androidx_fragment_fragment",
+        "@maven//:androidx_lifecycle_lifecycle_common",
+        "@maven//:androidx_lifecycle_lifecycle_viewmodel",
+        "@maven//:androidx_lifecycle_lifecycle_viewmodel_savedstate",
     ],
 )
diff --git a/javatests/dagger/android/support/functional/AndroidManifest.xml b/javatests/dagger/android/support/functional/AndroidManifest.xml
index 0b5f4cd..2b40e10 100644
--- a/javatests/dagger/android/support/functional/AndroidManifest.xml
+++ b/javatests/dagger/android/support/functional/AndroidManifest.xml
@@ -17,7 +17,7 @@
   package="dagger.android.support.functional">
 
   <!-- Bump targetSdk to 29 when we update to Java 11 -->
-  <uses-sdk android:minSdkVersion="1" android:targetSdkVersion="28" />
+  <uses-sdk android:minSdkVersion="14" android:targetSdkVersion="28" />
 
   <application android:theme="@style/Theme.AppCompat"
       android:name=".UsesGeneratedModulesApplication">
diff --git a/javatests/dagger/android/support/functional/BUILD b/javatests/dagger/android/support/functional/BUILD
index 1ae1108..459d55f 100644
--- a/javatests/dagger/android/support/functional/BUILD
+++ b/javatests/dagger/android/support/functional/BUILD
@@ -29,10 +29,13 @@
     manifest = "AndroidManifest.xml",
     resource_files = glob(["res/**"]),
     deps = [
+        "@maven//:androidx_activity_activity",
         "@maven//:androidx_fragment_fragment",
+        "@maven//:androidx_lifecycle_lifecycle_common",
+        "@maven//:androidx_lifecycle_lifecycle_viewmodel",
+        "@maven//:androidx_lifecycle_lifecycle_viewmodel_savedstate",
         "@maven//:androidx_appcompat_appcompat",
         "@maven//:androidx_annotation_annotation",
-        "//java/dagger/internal/guava:collect-android",
         "//:dagger_with_compiler",
         "//:android",
         "//:android-support",
@@ -44,13 +47,22 @@
 GenRobolectricTests(
     name = "functional_tests",
     srcs = glob(["*Test.java"]),
+    manifest_values = {
+        "minSdkVersion": "14",
+    },
     deps = [
         ":functional",
         "//:android",
         "//:android-support",
         "//:dagger_with_compiler",
         "@google_bazel_common//third_party/java/truth",
+        "@maven//:androidx_activity_activity",
         "@maven//:androidx_fragment_fragment",
+        "@maven//:androidx_lifecycle_lifecycle_common",
+        "@maven//:androidx_lifecycle_lifecycle_viewmodel",
+        "@maven//:androidx_lifecycle_lifecycle_viewmodel_savedstate",
+        "@maven//:androidx_test_core",
+        "@maven//:androidx_test_ext_junit",
         "@maven//:junit_junit",
         "@maven//:org_robolectric_robolectric",
     ],
diff --git a/javatests/dagger/android/support/functional/InjectorsTest.java b/javatests/dagger/android/support/functional/InjectorsTest.java
index 73bb98a..eef8429 100644
--- a/javatests/dagger/android/support/functional/InjectorsTest.java
+++ b/javatests/dagger/android/support/functional/InjectorsTest.java
@@ -20,16 +20,19 @@
 
 import android.content.Intent;
 import android.content.res.Configuration;
+import android.os.Build;
 import androidx.test.core.app.ApplicationProvider;
+import androidx.test.ext.junit.runners.AndroidJUnit4;
 import org.junit.Before;
 import org.junit.Test;
 import org.junit.runner.RunWith;
 import org.robolectric.Robolectric;
-import org.robolectric.RobolectricTestRunner;
 import org.robolectric.android.controller.ActivityController;
 import org.robolectric.annotation.Config;
 
-@RunWith(RobolectricTestRunner.class)
+@RunWith(AndroidJUnit4.class)
+// Robolectric requires Java9 to run API 29 and above, so use API 28 instead
+@Config(sdk = Build.VERSION_CODES.P)
 public class InjectorsTest {
   private ActivityController<TestActivity> activityController;
   private TestActivity activity;
diff --git a/javatests/dagger/functional/assisted/AssistedFactoryAsQualifiedBindingTest.java b/javatests/dagger/functional/assisted/AssistedFactoryAsQualifiedBindingTest.java
new file mode 100644
index 0000000..494372a
--- /dev/null
+++ b/javatests/dagger/functional/assisted/AssistedFactoryAsQualifiedBindingTest.java
@@ -0,0 +1,259 @@
+/*
+ * Copyright (C) 2021 The Dagger Authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package dagger.functional.assisted;
+
+import static com.google.common.truth.Truth.assertThat;
+import static com.google.common.truth.Truth8.assertThat;
+import static java.lang.annotation.RetentionPolicy.RUNTIME;
+
+import dagger.Binds;
+import dagger.BindsInstance;
+import dagger.BindsOptionalOf;
+import dagger.Component;
+import dagger.Module;
+import dagger.Provides;
+import dagger.assisted.AssistedFactory;
+import dagger.assisted.AssistedInject;
+import dagger.multibindings.IntoSet;
+import dagger.multibindings.Multibinds;
+import java.lang.annotation.Retention;
+import java.util.Optional;
+import java.util.Set;
+import javax.inject.Inject;
+import javax.inject.Qualifier;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.JUnit4;
+
+/** Tests that qualified assisted types can be provided and injected as normal types. */
+@RunWith(JUnit4.class)
+public final class AssistedFactoryAsQualifiedBindingTest {
+
+  @Qualifier
+  @Retention(RUNTIME)
+  @interface AsComponentDependency {}
+
+  @Qualifier
+  @Retention(RUNTIME)
+  @interface AsProvides {}
+
+  @Qualifier
+  @Retention(RUNTIME)
+  @interface AsBinds {}
+
+  @Qualifier
+  @Retention(RUNTIME)
+  @interface AsOptional {}
+
+  @Qualifier
+  @Retention(RUNTIME)
+  @interface AsMultibinding {}
+
+  @Component(modules = BarFactoryModule.class)
+  interface TestComponent {
+    Foo foo();
+
+    @Component.Factory
+    interface Factory {
+      TestComponent create(
+          @BindsInstance @AsComponentDependency Bar bar,
+          @BindsInstance @AsComponentDependency BarFactory barFactory);
+    }
+  }
+
+  @Module
+  interface BarFactoryModule {
+
+    @Provides
+    @AsProvides
+    static Bar providesBar(@AsComponentDependency Bar bar) {
+      return bar;
+    }
+
+    @Provides
+    @AsProvides
+    static BarFactory providesBarFactory(@AsComponentDependency BarFactory barFactory) {
+      return barFactory;
+    }
+
+    @Binds
+    @AsBinds
+    Bar bindsBar(@AsComponentDependency Bar bar);
+
+    @Binds
+    @AsBinds
+    BarFactory bindsBarFactory(@AsComponentDependency BarFactory barFactory);
+
+    @BindsOptionalOf
+    @AsOptional
+    Bar optionalBar();
+
+    @BindsOptionalOf
+    @AsOptional
+    BarFactory optionalBarFactory();
+
+    @Provides
+    @AsOptional
+    static Bar providesOptionalBar(@AsComponentDependency Bar bar) {
+      return bar;
+    }
+
+    @Provides
+    @AsOptional
+    static BarFactory providesOptionalBarFactory(@AsComponentDependency BarFactory barFactory) {
+      return barFactory;
+    }
+
+    @Multibinds
+    @AsMultibinding
+    Set<Bar> barSet();
+
+    @Multibinds
+    @AsMultibinding
+    Set<BarFactory> barFactorySet();
+
+    @Provides
+    @IntoSet
+    @AsMultibinding
+    static Bar providesMultibindingBar(@AsComponentDependency Bar bar) {
+      return bar;
+    }
+
+    @Provides
+    @IntoSet
+    @AsMultibinding
+    static BarFactory providesMultibindingBarFactory(@AsComponentDependency BarFactory barFactory) {
+      return barFactory;
+    }
+
+    @Multibinds
+    Set<Bar> unqualifiedBarSet();
+
+    @Multibinds
+    Set<BarFactory> unqualifiedBarFactorySet();
+
+    @Provides
+    @IntoSet
+    static Bar providesUnqualifiedMultibindingBar(@AsComponentDependency Bar bar) {
+      return bar;
+    }
+
+    @Provides
+    @IntoSet
+    static BarFactory providesUnqualifiedMultibindingBarFactory(
+        @AsComponentDependency BarFactory barFactory) {
+      return barFactory;
+    }
+  }
+
+  static class Foo {
+    private final BarFactory barFactory;
+    private final Bar barAsComponentDependency;
+    private final BarFactory barFactoryAsComponentDependency;
+    private final Bar barAsProvides;
+    private final BarFactory barFactoryAsProvides;
+    private final Bar barAsBinds;
+    private final BarFactory barFactoryAsBinds;
+    private final Optional<Bar> optionalBar;
+    private final Optional<BarFactory> optionalBarFactory;
+    private final Set<Bar> barSet;
+    private final Set<BarFactory> barFactorySet;
+    private final Set<Bar> unqualifiedBarSet;
+    private final Set<BarFactory> unqualifiedBarFactorySet;
+
+    @Inject
+    Foo(
+        BarFactory barFactory,
+        @AsComponentDependency Bar barAsComponentDependency,
+        @AsComponentDependency BarFactory barFactoryAsComponentDependency,
+        @AsProvides Bar barAsProvides,
+        @AsProvides BarFactory barFactoryAsProvides,
+        @AsBinds Bar barAsBinds,
+        @AsBinds BarFactory barFactoryAsBinds,
+        @AsOptional Optional<Bar> optionalBar,
+        @AsOptional Optional<BarFactory> optionalBarFactory,
+        @AsMultibinding Set<Bar> barSet,
+        @AsMultibinding Set<BarFactory> barFactorySet,
+        Set<Bar> unqualifiedBarSet,
+        Set<BarFactory> unqualifiedBarFactorySet) {
+      this.barFactory = barFactory;
+      this.barAsComponentDependency = barAsComponentDependency;
+      this.barFactoryAsComponentDependency = barFactoryAsComponentDependency;
+      this.barAsProvides = barAsProvides;
+      this.barFactoryAsProvides = barFactoryAsProvides;
+      this.barAsBinds = barAsBinds;
+      this.barFactoryAsBinds = barFactoryAsBinds;
+      this.optionalBar = optionalBar;
+      this.optionalBarFactory = optionalBarFactory;
+      this.barSet = barSet;
+      this.barFactorySet = barFactorySet;
+      this.unqualifiedBarSet = unqualifiedBarSet;
+      this.unqualifiedBarFactorySet = unqualifiedBarFactorySet;
+    }
+  }
+
+  static class Bar {
+    @AssistedInject
+    Bar() {}
+  }
+
+  @AssistedFactory
+  interface BarFactory {
+    Bar create();
+  }
+
+  @Test
+  public void testFoo() {
+    Bar bar = new Bar();
+    BarFactory barFactory = () -> bar;
+    Foo foo =
+        DaggerAssistedFactoryAsQualifiedBindingTest_TestComponent.factory()
+            .create(bar, barFactory)
+            .foo();
+
+    // Test we can inject the "real" BarFactory implemented by Dagger
+    assertThat(foo.barFactory).isNotNull();
+    assertThat(foo.barFactory).isNotEqualTo(barFactory);
+    assertThat(foo.barFactory.create()).isNotEqualTo(bar);
+
+    // Test injection of a qualified Bar/BarFactory with custom @BindsInstance implementation
+    assertThat(foo.barAsComponentDependency).isEqualTo(bar);
+    assertThat(foo.barFactoryAsComponentDependency).isEqualTo(barFactory);
+
+    // Test injection of a qualified Bar/BarFactory with custom @Provides implementation
+    assertThat(foo.barAsProvides).isEqualTo(bar);
+    assertThat(foo.barFactoryAsProvides).isEqualTo(barFactory);
+
+    // Test injection of a qualified Bar/BarFactory with custom @Binds implementation
+    assertThat(foo.barAsBinds).isEqualTo(bar);
+    assertThat(foo.barFactoryAsBinds).isEqualTo(barFactory);
+
+    // Test injection of a qualified Bar/BarFactory with custom @BindsOptionalOf implementation
+    assertThat(foo.optionalBar).isPresent();
+    assertThat(foo.optionalBar).hasValue(bar);
+    assertThat(foo.optionalBarFactory).isPresent();
+    assertThat(foo.optionalBarFactory).hasValue(barFactory);
+
+    // Test injection of a qualified Bar/BarFactory as multibinding
+    assertThat(foo.barSet).containsExactly(bar);
+    assertThat(foo.barFactorySet).containsExactly(barFactory);
+
+    // Test injection of a unqualified Bar/BarFactory as multibinding
+    assertThat(foo.unqualifiedBarSet).containsExactly(bar);
+    assertThat(foo.unqualifiedBarFactorySet).containsExactly(barFactory);
+  }
+}
diff --git a/javatests/dagger/functional/subcomponent/SubcomponentBuilderMultibindingsTest.java b/javatests/dagger/functional/subcomponent/SubcomponentBuilderMultibindingsTest.java
new file mode 100644
index 0000000..7065acc
--- /dev/null
+++ b/javatests/dagger/functional/subcomponent/SubcomponentBuilderMultibindingsTest.java
@@ -0,0 +1,265 @@
+/*
+ * Copyright (C) 2015 The Dagger Authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package dagger.functional.subcomponent;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import dagger.Component;
+import dagger.Module;
+import dagger.Provides;
+import dagger.Subcomponent;
+import dagger.multibindings.IntoSet;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.util.Set;
+import javax.inject.Inject;
+import javax.inject.Qualifier;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.JUnit4;
+
+/**
+ * Regression tests for an issue where subcomponent builder bindings were incorrectly reused from
+ * a parent even if the subcomponent were redeclared on the child component. This manifested via
+ * multibindings, especially since subcomponent builder bindings are special in that we cannot
+ * traverse them to see if they depend on local multibinding contributions.
+ */
+@RunWith(JUnit4.class)
+public final class SubcomponentBuilderMultibindingsTest {
+
+  @Retention(RetentionPolicy.RUNTIME)
+  @Qualifier
+  public @interface ParentFoo{}
+
+  @Retention(RetentionPolicy.RUNTIME)
+  @Qualifier
+  public @interface ChildFoo{}
+
+  public static final class Foo {
+    final Set<String> multi;
+    @Inject Foo(Set<String> multi) {
+      this.multi = multi;
+    }
+  }
+
+  // This tests the case where a subcomponent is installed in both the parent and child component.
+  // In this case, we expect two subcomponents to be generated with the child one including the
+  // child multibinding contribution.
+  public static final class ChildInstallsFloating {
+    @Component(modules = ParentModule.class)
+    public interface Parent {
+      @ParentFoo Foo getParentFoo();
+
+      Child getChild();
+    }
+
+    @Subcomponent(modules = ChildModule.class)
+    public interface Child {
+      @ChildFoo Foo getChildFoo();
+    }
+
+    @Subcomponent
+    public interface FloatingSub {
+      Foo getFoo();
+
+      @Subcomponent.Builder
+      public interface Builder {
+        FloatingSub build();
+      }
+    }
+
+    @Module(subcomponents = FloatingSub.class)
+    public interface ParentModule {
+      @Provides
+      @IntoSet
+      static String provideStringMulti() {
+         return "parent";
+      }
+
+      @Provides
+      @ParentFoo
+      static Foo provideParentFoo(FloatingSub.Builder builder) {
+        return builder.build().getFoo();
+      }
+    }
+
+    // The subcomponent installation of FloatingSub here is the key difference
+    @Module(subcomponents = FloatingSub.class)
+    public interface ChildModule {
+      @Provides
+      @IntoSet
+      static String provideStringMulti() {
+         return "child";
+      }
+
+      @Provides
+      @ChildFoo
+      static Foo provideChildFoo(FloatingSub.Builder builder) {
+        return builder.build().getFoo();
+      }
+    }
+
+    private ChildInstallsFloating() {}
+  }
+
+  // This is the same as the above, except this time the child does not install the subcomponent
+  // builder. Here, we expect the child to reuse the parent subcomponent binding (we want to avoid
+  // any mistakes that might implicitly create a new subcomponent relationship) and so therefore
+  // we expect only one subcomponent to be generated in the parent resulting in the child not seeing
+  // the child multibinding contribution.
+  public static final class ChildDoesNotInstallFloating {
+    @Component(modules = ParentModule.class)
+    public interface Parent {
+      @ParentFoo Foo getParentFoo();
+
+      Child getChild();
+    }
+
+    @Subcomponent(modules = ChildModule.class)
+    public interface Child {
+      @ChildFoo Foo getChildFoo();
+    }
+
+    @Subcomponent
+    public interface FloatingSub {
+      Foo getFoo();
+
+      @Subcomponent.Builder
+      public interface Builder {
+        FloatingSub build();
+      }
+    }
+
+    @Module(subcomponents = FloatingSub.class)
+    public interface ParentModule {
+      @Provides
+      @IntoSet
+      static String provideStringMulti() {
+         return "parent";
+      }
+
+      @Provides
+      @ParentFoo
+      static Foo provideParentFoo(FloatingSub.Builder builder) {
+        return builder.build().getFoo();
+      }
+    }
+
+    // The lack of a subcomponent installation of FloatingSub here is the key difference
+    @Module
+    public interface ChildModule {
+      @Provides
+      @IntoSet
+      static String provideStringMulti() {
+         return "child";
+      }
+
+      @Provides
+      @ChildFoo
+      static Foo provideChildFoo(FloatingSub.Builder builder) {
+        return builder.build().getFoo();
+      }
+    }
+
+    private ChildDoesNotInstallFloating() {}
+  }
+
+  // This is similar to the first, except this time the components installs the subcomponent via
+  // factory methods. Here, we expect the child to get a new subcomponent and so should see its
+  // multibinding contribution.
+  public static final class ChildInstallsFloatingFactoryMethod {
+    @Component(modules = ParentModule.class)
+    public interface Parent {
+      @ParentFoo Foo getParentFoo();
+
+      Child getChild();
+
+      FloatingSub getFloatingSub();
+    }
+
+    @Subcomponent(modules = ChildModule.class)
+    public interface Child {
+      @ChildFoo Foo getChildFoo();
+
+      FloatingSub getFloatingSub();
+    }
+
+    @Subcomponent
+    public interface FloatingSub {
+      Foo getFoo();
+    }
+
+    @Module
+    public interface ParentModule {
+      @Provides
+      @IntoSet
+      static String provideStringMulti() {
+         return "parent";
+      }
+
+      @Provides
+      @ParentFoo
+      static Foo provideParentFoo(Parent componentSelf) {
+        return componentSelf.getFloatingSub().getFoo();
+      }
+    }
+
+    @Module
+    public interface ChildModule {
+      @Provides
+      @IntoSet
+      static String provideStringMulti() {
+         return "child";
+      }
+
+      @Provides
+      @ChildFoo
+      static Foo provideChildFoo(Child componentSelf) {
+        return componentSelf.getFloatingSub().getFoo();
+      }
+    }
+
+    private ChildInstallsFloatingFactoryMethod() {}
+  }
+
+  @Test
+  public void testChildInstallsFloating() {
+    ChildInstallsFloating.Parent parentComponent =
+        DaggerSubcomponentBuilderMultibindingsTest_ChildInstallsFloating_Parent.create();
+    assertThat(parentComponent.getParentFoo().multi).containsExactly("parent");
+    assertThat(parentComponent.getChild().getChildFoo().multi).containsExactly("parent", "child");
+  }
+
+  @Test
+  public void testChildDoesNotInstallFloating() {
+    ChildDoesNotInstallFloating.Parent parentComponent =
+        DaggerSubcomponentBuilderMultibindingsTest_ChildDoesNotInstallFloating_Parent.create();
+    assertThat(parentComponent.getParentFoo().multi).containsExactly("parent");
+    // Don't expect the child contribution because the child didn't redeclare the subcomponent
+    // dependency, meaning it intends to just use the subcomponent relationship from the parent
+    // component.
+    assertThat(parentComponent.getChild().getChildFoo().multi).containsExactly("parent");
+  }
+
+  @Test
+  public void testChildInstallsFloatingFactoryMethod() {
+    ChildInstallsFloatingFactoryMethod.Parent parentComponent =
+        DaggerSubcomponentBuilderMultibindingsTest_ChildInstallsFloatingFactoryMethod_Parent.create();
+    assertThat(parentComponent.getParentFoo().multi).containsExactly("parent");
+    assertThat(parentComponent.getChild().getChildFoo().multi).containsExactly("parent", "child");
+  }
+}
diff --git a/javatests/dagger/hilt/android/ActivityInjectedSavedStateViewModelTest.java b/javatests/dagger/hilt/android/ActivityInjectedSavedStateViewModelTest.java
new file mode 100644
index 0000000..03aa324
--- /dev/null
+++ b/javatests/dagger/hilt/android/ActivityInjectedSavedStateViewModelTest.java
@@ -0,0 +1,118 @@
+/*
+ * Copyright (C) 2021 The Dagger Authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package dagger.hilt.android;
+
+import static androidx.test.core.app.ApplicationProvider.getApplicationContext;
+import static com.google.common.truth.Truth.assertThat;
+
+import androidx.lifecycle.SavedStateHandle;
+import androidx.lifecycle.ViewModel;
+import androidx.lifecycle.ViewModelProvider;
+import android.content.Intent;
+import android.os.Build;
+import android.os.Bundle;
+import androidx.fragment.app.FragmentActivity;
+import androidx.test.core.app.ActivityScenario;
+import androidx.test.ext.junit.runners.AndroidJUnit4;
+import dagger.Module;
+import dagger.Provides;
+import dagger.hilt.InstallIn;
+import dagger.hilt.android.components.ActivityComponent;
+import dagger.hilt.android.testing.HiltAndroidRule;
+import dagger.hilt.android.testing.HiltAndroidTest;
+import dagger.hilt.android.testing.HiltTestApplication;
+import javax.inject.Inject;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.robolectric.annotation.Config;
+
+@HiltAndroidTest
+@RunWith(AndroidJUnit4.class)
+// Robolectric requires Java9 to run API 29 and above, so use API 28 instead
+@Config(sdk = Build.VERSION_CODES.P, application = HiltTestApplication.class)
+public class ActivityInjectedSavedStateViewModelTest {
+
+  private static final String DATA_KEY = "TEST_KEY";
+
+  @Rule public final HiltAndroidRule rule = new HiltAndroidRule(this);
+
+  @Test
+  public void memberInjectedViewModelWithSavedState() {
+    Intent intent = new Intent(getApplicationContext(), TestActivity.class);
+    intent.putExtra(DATA_KEY, "test data");
+    try (ActivityScenario<TestActivity> scenario = ActivityScenario.launch(intent)) {
+      scenario.onActivity(
+          activity -> {
+            String data = activity.myViewModel.handle.get(DATA_KEY);
+            assertThat(data).isEqualTo("test data");
+          });
+    }
+  }
+
+  // Note that assertion of object not being yet injected is in the SuperActivity, while the
+  // assertion in the scenario is confirming injection eventually does occur.
+  @Test
+  public void notYetMemberInjectedSuperActivity() {
+    try (ActivityScenario<TestActivityWithSuperActivity> scenario =
+        ActivityScenario.launch(TestActivityWithSuperActivity.class)) {
+      scenario.onActivity(activity -> assertThat(activity.someObject).isNotNull());
+    }
+  }
+
+  @AndroidEntryPoint(FragmentActivity.class)
+  public static final class TestActivity
+      extends Hilt_ActivityInjectedSavedStateViewModelTest_TestActivity {
+    @Inject MyViewModel myViewModel;
+  }
+
+  @AndroidEntryPoint(SuperActivity.class)
+  public static final class TestActivityWithSuperActivity
+      extends Hilt_ActivityInjectedSavedStateViewModelTest_TestActivityWithSuperActivity {}
+
+  public static class SuperActivity extends FragmentActivity {
+    @Inject Object someObject;
+
+    @Override
+    protected void onCreate(Bundle savedInstanceState) {
+      assertThat(someObject).isNull(); // not yet injected
+      super.onCreate(savedInstanceState);
+    }
+  }
+
+  @Module
+  @InstallIn(ActivityComponent.class)
+  static final class MyViewModelModel {
+    @Provides
+    static MyViewModel provideModel(FragmentActivity activity) {
+      return new ViewModelProvider(activity).get(MyViewModel.class);
+    }
+
+    @Provides
+    static Object provideObject() {
+      return new Object();
+    }
+  }
+
+  public static final class MyViewModel extends ViewModel {
+    final SavedStateHandle handle;
+
+    public MyViewModel(SavedStateHandle handle) {
+      this.handle = handle;
+    }
+  }
+}
diff --git a/javatests/dagger/hilt/android/ActivityInjectedViewModelTest.java b/javatests/dagger/hilt/android/ActivityInjectedViewModelTest.java
new file mode 100644
index 0000000..0e75741
--- /dev/null
+++ b/javatests/dagger/hilt/android/ActivityInjectedViewModelTest.java
@@ -0,0 +1,83 @@
+/*
+ * Copyright (C) 2020 The Dagger Authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package dagger.hilt.android;
+
+import static androidx.test.core.app.ApplicationProvider.getApplicationContext;
+import static com.google.common.truth.Truth.assertThat;
+
+import androidx.lifecycle.ViewModel;
+import androidx.lifecycle.ViewModelProvider;
+import android.content.Intent;
+import android.os.Build;
+import androidx.fragment.app.FragmentActivity;
+import androidx.test.core.app.ActivityScenario;
+import androidx.test.ext.junit.runners.AndroidJUnit4;
+import dagger.Module;
+import dagger.Provides;
+import dagger.hilt.InstallIn;
+import dagger.hilt.android.components.ActivityComponent;
+import dagger.hilt.android.testing.HiltAndroidRule;
+import dagger.hilt.android.testing.HiltAndroidTest;
+import dagger.hilt.android.testing.HiltTestApplication;
+import javax.inject.Inject;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.robolectric.annotation.Config;
+
+/**
+ * Tests that the Hilt view model factory doesn't cause errors with non-Hilt view models. This was
+ * a problem at one point because if the non-Hilt view model is accessed before saved state
+ * restoration happens, even if the non-Hilt view model doesn't use saved state, it can cause an
+ * error because the default Hilt view model factory does use saved state. This test ensures we
+ * still allow non-Hilt view models without saved state to be used before saved state is restored.
+ */
+@HiltAndroidTest
+@RunWith(AndroidJUnit4.class)
+// Robolectric requires Java9 to run API 29 and above, so use API 28 instead
+@Config(sdk = Build.VERSION_CODES.P, application = HiltTestApplication.class)
+public class ActivityInjectedViewModelTest {
+
+  private static final String DATA_KEY = "TEST_KEY";
+
+  @Rule public final HiltAndroidRule rule = new HiltAndroidRule(this);
+
+  @Test
+  public void memberInjectedViewModelWithSavedState() {
+    final Intent intent = new Intent(getApplicationContext(), TestActivity.class);
+    intent.putExtra(DATA_KEY, "test data");
+    try (ActivityScenario<TestActivity> scenario = ActivityScenario.launch(intent)) {
+      scenario.onActivity(activity -> assertThat(activity.myViewModel).isNotNull());
+    }
+  }
+
+  @AndroidEntryPoint(FragmentActivity.class)
+  public static final class TestActivity extends Hilt_ActivityInjectedViewModelTest_TestActivity {
+    @Inject MyViewModel myViewModel;
+  }
+
+  @Module
+  @InstallIn(ActivityComponent.class)
+  static final class MyViewModelModel {
+    @Provides
+    static MyViewModel provideModel(FragmentActivity activity) {
+      return new ViewModelProvider(activity).get(MyViewModel.class);
+    }
+  }
+
+  public static final class MyViewModel extends ViewModel {}
+}
diff --git a/javatests/dagger/hilt/android/ActivityRetainedClearedListenerTest.java b/javatests/dagger/hilt/android/ActivityRetainedClearedListenerTest.java
new file mode 100644
index 0000000..286998c
--- /dev/null
+++ b/javatests/dagger/hilt/android/ActivityRetainedClearedListenerTest.java
@@ -0,0 +1,124 @@
+/*
+ * Copyright (C) 2020 The Dagger Authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package dagger.hilt.android;
+
+import static com.google.common.truth.Truth.assertThat;
+import static org.junit.Assert.fail;
+
+import androidx.lifecycle.Lifecycle.State;
+import android.os.Build;
+import androidx.fragment.app.FragmentActivity;
+import androidx.test.core.app.ActivityScenario;
+import androidx.test.ext.junit.runners.AndroidJUnit4;
+import dagger.hilt.android.ActivityRetainedLifecycle.OnClearedListener;
+import dagger.hilt.android.testing.HiltAndroidRule;
+import dagger.hilt.android.testing.HiltAndroidTest;
+import dagger.hilt.android.testing.HiltTestApplication;
+import java.util.concurrent.atomic.AtomicReference;
+import javax.inject.Inject;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.robolectric.annotation.Config;
+
+@HiltAndroidTest
+@RunWith(AndroidJUnit4.class)
+// Robolectric requires Java9 to run API 29 and above, so use API 28 instead
+@Config(sdk = Build.VERSION_CODES.P, application = HiltTestApplication.class)
+public class ActivityRetainedClearedListenerTest {
+
+  @Rule public final HiltAndroidRule rule = new HiltAndroidRule(this);
+
+  @Test
+  public void onClearedInvoked() {
+    final TestClearedListener callback = new TestClearedListener();
+    try (ActivityScenario<TestActivity> scenario = ActivityScenario.launch(TestActivity.class)) {
+      scenario.onActivity(
+          activity -> activity.activityRetainedLifecycle.addOnClearedListener(callback));
+      assertThat(callback.onClearedInvoked).isEqualTo(0);
+
+      // Recreate should not cause ViewModel to be cleared
+      scenario.recreate();
+      assertThat(callback.onClearedInvoked).isEqualTo(0);
+
+      // Destroying activity (not due to recreate) should cause ViewModel to be cleared
+      scenario.moveToState(State.DESTROYED);
+      assertThat(callback.onClearedInvoked).isEqualTo(1);
+    }
+  }
+
+  @Test
+  public void addOnClearedListener_tooLate() {
+    try (ActivityScenario<TestActivity> scenario = ActivityScenario.launch(TestActivity.class)) {
+      // Grab the activity (leak it a bit) from the scenario and destroy it.
+      AtomicReference<TestActivity> testActivity = new AtomicReference<>();
+      scenario.onActivity(testActivity::set);
+      scenario.moveToState(State.DESTROYED);
+
+      try {
+        TestClearedListener callback = new TestClearedListener();
+        testActivity.get().activityRetainedLifecycle.addOnClearedListener(callback);
+        fail("An exception should have been thrown.");
+      } catch (IllegalStateException e) {
+        assertThat(e)
+            .hasMessageThat()
+            .contains(
+                "There was a race between the call to add/remove an OnClearedListener and "
+                    + "onCleared(). This can happen when posting to the Main thread from a "
+                    + "background thread, which is not supported.");
+      }
+    }
+  }
+
+  @Test
+  public void removeOnClearedListener_tooLate() {
+    try (ActivityScenario<TestActivity> scenario = ActivityScenario.launch(TestActivity.class)) {
+      // Grab the activity (leak it a bit) from the scenario and destroy it.
+      AtomicReference<TestActivity> testActivity = new AtomicReference<>();
+      scenario.onActivity(testActivity::set);
+      scenario.moveToState(State.DESTROYED);
+
+      try {
+        TestClearedListener callback = new TestClearedListener();
+        testActivity.get().activityRetainedLifecycle.removeOnClearedListener(callback);
+        fail("An exception should have been thrown.");
+      } catch (IllegalStateException e) {
+        assertThat(e)
+            .hasMessageThat()
+            .contains(
+                "There was a race between the call to add/remove an OnClearedListener and "
+                    + "onCleared(). This can happen when posting to the Main thread from a "
+                    + "background thread, which is not supported.");
+      }
+    }
+  }
+
+  static class TestClearedListener implements OnClearedListener {
+    int onClearedInvoked = 0;
+
+    @Override
+    public void onCleared() {
+      onClearedInvoked++;
+    }
+  }
+
+  @AndroidEntryPoint(FragmentActivity.class)
+  public static final class TestActivity
+      extends Hilt_ActivityRetainedClearedListenerTest_TestActivity {
+    @Inject ActivityRetainedLifecycle activityRetainedLifecycle;
+  }
+}
diff --git a/javatests/dagger/hilt/android/ActivityScenarioRuleTest.java b/javatests/dagger/hilt/android/ActivityScenarioRuleTest.java
new file mode 100644
index 0000000..c716225
--- /dev/null
+++ b/javatests/dagger/hilt/android/ActivityScenarioRuleTest.java
@@ -0,0 +1,71 @@
+/*
+ * Copyright (C) 2020 The Dagger Authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package dagger.hilt.android;
+
+import static androidx.lifecycle.Lifecycle.State.RESUMED;
+import static com.google.common.truth.Truth.assertThat;
+
+import android.os.Build;
+import androidx.activity.ComponentActivity;
+import androidx.test.ext.junit.rules.ActivityScenarioRule;
+import androidx.test.ext.junit.runners.AndroidJUnit4;
+import dagger.hilt.android.testing.BindValue;
+import dagger.hilt.android.testing.HiltAndroidRule;
+import dagger.hilt.android.testing.HiltAndroidTest;
+import dagger.hilt.android.testing.HiltTestApplication;
+import javax.inject.Inject;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.rules.RuleChain;
+import org.junit.runner.RunWith;
+import org.robolectric.annotation.Config;
+
+/** Tests that {@link ActivityScenarioRule} works with Hilt tests. */
+@HiltAndroidTest
+@RunWith(AndroidJUnit4.class)
+// Robolectric requires Java9 to run API 29 and above, so use API 28 instead
+@Config(sdk = Build.VERSION_CODES.P, application = HiltTestApplication.class)
+public final class ActivityScenarioRuleTest {
+  private static final String STR_VALUE = "STR_VALUE";
+
+  /** An activity to test injection. */
+  @AndroidEntryPoint(ComponentActivity.class)
+  public static final class TestActivity extends Hilt_ActivityScenarioRuleTest_TestActivity {
+    @Inject String str;
+  }
+
+  public HiltAndroidRule hiltRule = new HiltAndroidRule(this);
+
+  public ActivityScenarioRule<TestActivity> scenarioRule =
+      new ActivityScenarioRule<>(TestActivity.class);
+
+  @Rule public RuleChain chain = RuleChain.outerRule(hiltRule).around(scenarioRule);
+
+  @BindValue String str = STR_VALUE;
+
+  @Test
+  public void testState() {
+    assertThat(scenarioRule.getScenario().getState()).isEqualTo(RESUMED);
+  }
+
+  @Test
+  public void testInjection() {
+    scenarioRule
+        .getScenario()
+        .onActivity(activity -> assertThat(activity.str).isEqualTo(STR_VALUE));
+  }
+}
diff --git a/javatests/dagger/hilt/android/AndroidEntryPointBaseClassOtherPkg.java b/javatests/dagger/hilt/android/AndroidEntryPointBaseClassOtherPkg.java
new file mode 100644
index 0000000..048e0f7
--- /dev/null
+++ b/javatests/dagger/hilt/android/AndroidEntryPointBaseClassOtherPkg.java
@@ -0,0 +1,32 @@
+/*
+ * Copyright (C) 2020 The Dagger Authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package dagger.hilt.android.other.pkg;
+
+import androidx.activity.ComponentActivity;
+import dagger.hilt.android.AndroidEntryPoint;
+
+/** Used in {@link AndroidEntryPointBaseClassTest}. */
+public final class AndroidEntryPointBaseClassOtherPkg {
+
+  @AndroidEntryPoint(ComponentActivity.class)
+  public static class LBaseActivity extends Hilt_AndroidEntryPointBaseClassOtherPkg_LBaseActivity {}
+
+  @AndroidEntryPoint
+  public static class SBaseActivity extends ComponentActivity {}
+
+  private AndroidEntryPointBaseClassOtherPkg() {}
+}
diff --git a/javatests/dagger/hilt/android/AndroidEntryPointBaseClassTest.java b/javatests/dagger/hilt/android/AndroidEntryPointBaseClassTest.java
new file mode 100644
index 0000000..1078e7d
--- /dev/null
+++ b/javatests/dagger/hilt/android/AndroidEntryPointBaseClassTest.java
@@ -0,0 +1,163 @@
+/*
+ * Copyright (C) 2020 The Dagger Authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package dagger.hilt.android;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import android.os.Build;
+import androidx.activity.ComponentActivity;
+import androidx.test.ext.junit.runners.AndroidJUnit4;
+import dagger.hilt.android.other.pkg.AndroidEntryPointBaseClassOtherPkg;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.robolectric.annotation.Config;
+
+/**
+ * Regression test for https://github.com/google/dagger/issues/1910
+ *
+ * <p>There are 8 different tests to cover 3 levels of inheritance where each level uses either the
+ * long-form (L) or short-form (S) of @AndroidEntryPoint:
+ *
+ * <ol>
+ *   <li> L -> L -> L
+ *   <li> L -> L -> S
+ *   <li> L -> S -> L
+ *   <li> L -> S -> S
+ *   <li> S -> L -> L
+ *   <li> S -> L -> S
+ *   <li> S -> S -> L
+ *   <li> S -> S -> S
+ * </ol>
+ *
+ * Note: We don't actually test injection in this class because Bazel doesn't do bytecode injection.
+ * We're only testing that the classes build, and verifying their inheritance matches what we
+ * expect.
+ */
+@RunWith(AndroidJUnit4.class)
+// Robolectric requires Java9 to run API 29 and above, so use API 28 instead
+@Config(sdk = Build.VERSION_CODES.P)
+public final class AndroidEntryPointBaseClassTest {
+
+  @AndroidEntryPoint
+  public static final class SSActivity extends AndroidEntryPointBaseClassOtherPkg.SBaseActivity {}
+
+  @AndroidEntryPoint
+  public static final class SLActivity extends AndroidEntryPointBaseClassOtherPkg.LBaseActivity {}
+
+  @AndroidEntryPoint(AndroidEntryPointBaseClassOtherPkg.SBaseActivity.class)
+  public static final class LSActivity extends Hilt_AndroidEntryPointBaseClassTest_LSActivity {}
+
+  @AndroidEntryPoint(AndroidEntryPointBaseClassOtherPkg.LBaseActivity.class)
+  public static final class LLActivity extends Hilt_AndroidEntryPointBaseClassTest_LLActivity {}
+
+  @AndroidEntryPoint(LL.class)
+  public static final class LLL extends Hilt_AndroidEntryPointBaseClassTest_LLL {}
+
+  @AndroidEntryPoint(LS.class)
+  public static final class LLS extends Hilt_AndroidEntryPointBaseClassTest_LLS {}
+
+  @AndroidEntryPoint(SL.class)
+  public static final class LSL extends Hilt_AndroidEntryPointBaseClassTest_LSL {}
+
+  @AndroidEntryPoint(SS.class)
+  public static final class LSS extends Hilt_AndroidEntryPointBaseClassTest_LSS {}
+
+  @AndroidEntryPoint
+  public static final class SLL extends LL {}
+
+  @AndroidEntryPoint
+  public static final class SLS extends LS {}
+
+  @AndroidEntryPoint
+  public static final class SSL extends SL {}
+
+  @AndroidEntryPoint
+  public static final class SSS extends SS {}
+
+  @AndroidEntryPoint(L.class)
+  public static class LL extends Hilt_AndroidEntryPointBaseClassTest_LL {}
+
+  @AndroidEntryPoint(S.class)
+  public static class LS extends Hilt_AndroidEntryPointBaseClassTest_LS {}
+
+  @AndroidEntryPoint
+  public static class SL extends L {}
+
+  @AndroidEntryPoint
+  public static class SS extends S {}
+
+  @AndroidEntryPoint(ComponentActivity.class)
+  public static class L extends Hilt_AndroidEntryPointBaseClassTest_L {}
+
+  @AndroidEntryPoint
+  public static class S extends ComponentActivity {}
+
+  @Test
+  public void checkGeneratedClassHierarchy_shortForm() throws Exception {
+    // When using the short form notation, the generated top level class is not actually assignable
+    // to the generated base classes at compile time
+    assertIsNotAssignableTo(
+        Hilt_AndroidEntryPointBaseClassTest_SSS.class,
+        Hilt_AndroidEntryPointBaseClassTest_S.class);
+    assertIsNotAssignableTo(
+        Hilt_AndroidEntryPointBaseClassTest_SS.class,
+        Hilt_AndroidEntryPointBaseClassTest_S.class);
+  }
+
+  @Test
+  public void checkGeneratedClassHierarchy_longForm() throws Exception {
+    // When using the long form notation, they are assignable at compile time
+    assertIsAssignableTo(
+        Hilt_AndroidEntryPointBaseClassTest_LLL.class,
+        Hilt_AndroidEntryPointBaseClassTest_LL.class);
+    assertIsAssignableTo(
+        Hilt_AndroidEntryPointBaseClassTest_LL.class,
+        Hilt_AndroidEntryPointBaseClassTest_L.class);
+  }
+
+  @Test
+  public void checkGeneratedClassHierarchy_shortFormRoot() throws Exception {
+    // If the root is short-form, then the child class cannot be assigned to it.
+    assertIsNotAssignableTo(
+        Hilt_AndroidEntryPointBaseClassTest_LLS.class,
+        Hilt_AndroidEntryPointBaseClassTest_S.class);
+    assertIsNotAssignableTo(
+        Hilt_AndroidEntryPointBaseClassTest_LS.class,
+        Hilt_AndroidEntryPointBaseClassTest_S.class);
+  }
+
+  @Test
+  public void checkGeneratedClassHierarchy_longFormRoot() throws Exception {
+    // If the root is long-form, then the child class can be assigned to it.
+    assertIsAssignableTo(
+        Hilt_AndroidEntryPointBaseClassTest_SSL.class,
+        Hilt_AndroidEntryPointBaseClassTest_L.class);
+    assertIsAssignableTo(
+        Hilt_AndroidEntryPointBaseClassTest_SL.class,
+        Hilt_AndroidEntryPointBaseClassTest_L.class);
+  }
+
+  /** Asserts that the {@code class1} is not assignable to the {@code class2}. */
+  private static void assertIsNotAssignableTo(Class<?> class1, Class<?> class2) {
+    assertThat(class2.isAssignableFrom(class1)).isFalse();
+  }
+
+  /** Asserts that the {@code class1} is assignable to the {@code class2}. */
+  private static void assertIsAssignableTo(Class<?> class1, Class<?> class2) {
+    assertThat(class1).isAssignableTo(class2);
+  }
+}
diff --git a/javatests/dagger/hilt/android/AndroidManifest.xml b/javatests/dagger/hilt/android/AndroidManifest.xml
new file mode 100644
index 0000000..7f6ede6
--- /dev/null
+++ b/javatests/dagger/hilt/android/AndroidManifest.xml
@@ -0,0 +1,47 @@
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+    package="dagger.hilt.android">
+
+  <uses-sdk android:minSdkVersion="14" />
+
+  <application>
+    <activity
+        android:name=".ActivityInjectedSavedStateViewModelTest$TestActivity"
+        android:exported="false"/>
+    <activity
+        android:name=".ActivityInjectedSavedStateViewModelTest$TestActivityWithSuperActivity"
+        android:exported="false"/>
+    <activity
+        android:name=".ActivityInjectedViewModelTest$TestActivity"
+        android:exported="false"/>
+    <activity
+        android:name=".ActivityRetainedClearedListenerTest$TestActivity"
+        android:exported="false"/>
+    <activity
+        android:name=".ActivityScenarioRuleTest$TestActivity"
+        android:exported="false"/>
+    <activity
+        android:name=".DefaultViewModelFactoryTest$TestActivity"
+        android:exported="false"/>
+    <activity
+        android:name=".QualifierInKotlinFieldsTest$TestActivity"
+        android:exported="false"/>
+    <activity
+        android:name=".UsesSharedComponent1Test$TestActivity"
+        android:exported="false"/>
+    <activity
+        android:name=".UsesSharedComponent2Test$TestActivity"
+        android:exported="false"/>
+    <activity
+        android:name=".UsesSharedComponentEnclosedTest$EnclosedTest$TestActivity"
+        android:exported="false"/>
+    <activity
+        android:name=".ViewModelScopedTest$TestActivity"
+        android:exported="false"/>
+    <activity
+        android:name=".ViewModelWithBaseTest$TestActivity"
+        android:exported="false"/>
+    <activity
+        android:name="dagger.hilt.android.testsubpackage.UsesSharedComponent1Test$TestActivity"
+        android:exported="false"/>
+  </application>
+</manifest>
diff --git a/javatests/dagger/hilt/android/BUILD b/javatests/dagger/hilt/android/BUILD
new file mode 100644
index 0000000..1f9bd08
--- /dev/null
+++ b/javatests/dagger/hilt/android/BUILD
@@ -0,0 +1,649 @@
+# Copyright (C) 2020 The Dagger Authors.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+# Description:
+#   Tests for internal code for implementing Hilt processors.
+
+load("@io_bazel_rules_kotlin//kotlin:kotlin.bzl", "kt_android_library")
+
+package(default_visibility = ["//:src"])
+
+# Checks that multiple test roots can be compiled together. This library
+# only compiles the sources. they are tested in the android_local_tests.
+android_library(
+    name = "multi_test_root_tests",
+    srcs = [
+        "ActivityScenarioRuleTest.java",
+        "CustomTestApplicationTest.java",
+        "MultiTestRoot1Test.java",
+        "MultiTestRoot2Test.java",
+        "MultiTestRootExternalModules.java",
+    ],
+    exports_manifest = 1,
+    manifest = "AndroidManifest.xml",
+    deps = [
+        "//:android_local_test_exports",
+        "//:dagger_with_compiler",
+        "//java/dagger/hilt:entry_point",
+        "//java/dagger/hilt:install_in",
+        "//java/dagger/hilt/android:android_entry_point",
+        "//java/dagger/hilt/android:package_info",
+        "//java/dagger/hilt/android/testing:bind_value",
+        "//java/dagger/hilt/android/testing:custom_test_application",
+        "//java/dagger/hilt/android/testing:hilt_android_test",
+        "//java/dagger/hilt/android/testing:uninstall_modules",
+        "@google_bazel_common//third_party/java/truth",
+        "@maven//:androidx_test_core",
+        "@maven//:androidx_test_ext_junit",
+        "@maven//:junit_junit",
+        "@maven//:org_robolectric_robolectric",
+    ],
+)
+
+android_local_test(
+    name = "ActivityScenarioRuleTest",
+    manifest_values = {
+        "minSdkVersion": "14",
+    },
+    deps = [
+        ":multi_test_root_tests",
+    ],
+)
+
+android_local_test(
+    name = "CustomTestApplicationTest",
+    manifest_values = {
+        "minSdkVersion": "14",
+    },
+    deps = [
+        ":multi_test_root_tests",
+    ],
+)
+
+android_local_test(
+    name = "MultiTestRoot1Test",
+    manifest_values = {
+        "minSdkVersion": "14",
+    },
+    deps = [
+        ":multi_test_root_tests",
+    ],
+)
+
+android_local_test(
+    name = "MultiTestRoot2Test",
+    manifest_values = {
+        "minSdkVersion": "14",
+    },
+    deps = [
+        ":multi_test_root_tests",
+    ],
+)
+
+android_local_test(
+    name = "EarlyEntryPointHiltAndroidAppRuntimeTest",
+    size = "small",
+    srcs = ["EarlyEntryPointHiltAndroidAppRuntimeTest.java"],
+    manifest_values = {
+        "minSdkVersion": "14",
+    },
+    deps = [
+        ":EarlyEntryPointHiltAndroidAppRuntimeClasses",
+        "//:android_local_test_exports",
+        "//java/dagger/hilt:entry_point",
+        "//java/dagger/hilt/android:early_entry_point",
+        "//java/dagger/hilt/android:package_info",
+        "@google_bazel_common//third_party/java/truth",
+        "@maven//:junit_junit",
+    ],
+)
+
+android_library(
+    name = "EarlyEntryPointHiltAndroidAppRuntimeClasses",
+    srcs = ["EarlyEntryPointHiltAndroidAppRuntimeClasses.java"],
+    deps = [
+        "//java/dagger/hilt:entry_point",
+        "//java/dagger/hilt/android:early_entry_point",
+        "//java/dagger/hilt/android:hilt_android_app",
+        "//java/dagger/hilt/android:package_info",
+    ],
+)
+
+android_local_test(
+    name = "EarlyEntryPointHiltAndroidTestRuntimeTest",
+    size = "small",
+    srcs = [
+        "EarlyEntryPointHiltAndroidTestRuntimeClasses.java",
+        "EarlyEntryPointHiltAndroidTestRuntimeTest.java",
+    ],
+    manifest_values = {
+        "minSdkVersion": "14",
+    },
+    deps = [
+        "//:android_local_test_exports",
+        "//:dagger_with_compiler",
+        "//java/dagger/hilt:define_component",
+        "//java/dagger/hilt:entry_point",
+        "//java/dagger/hilt:install_in",
+        "//java/dagger/hilt/android:early_entry_point",
+        "//java/dagger/hilt/android:package_info",
+        "//java/dagger/hilt/android/testing:hilt_android_test",
+        "@google_bazel_common//third_party/java/truth",
+    ],
+)
+
+android_local_test(
+    name = "EarlyEntryPointCustomApplicationTest",
+    size = "small",
+    srcs = [
+        "EarlyEntryPointCustomApplicationClasses.java",
+        "EarlyEntryPointCustomApplicationTest.java",
+    ],
+    manifest_values = {
+        "minSdkVersion": "14",
+    },
+    deps = [
+        "//:android_local_test_exports",
+        "//:dagger_with_compiler",
+        "//java/dagger/hilt:entry_point",
+        "//java/dagger/hilt:install_in",
+        "//java/dagger/hilt/android:early_entry_point",
+        "//java/dagger/hilt/android:package_info",
+        "//java/dagger/hilt/android/testing:custom_test_application",
+        "//java/dagger/hilt/android/testing:hilt_android_test",
+        "@google_bazel_common//third_party/java/truth",
+    ],
+)
+
+android_local_test(
+    name = "EarlyEntryPointNoEntryPointsDefinedTest",
+    size = "small",
+    srcs = ["EarlyEntryPointNoEntryPointsDefinedTest.java"],
+    manifest_values = {
+        "minSdkVersion": "14",
+    },
+    deps = [
+        "//:android_local_test_exports",
+        "//:dagger_with_compiler",
+        "//java/dagger/hilt:entry_point",
+        "//java/dagger/hilt:install_in",
+        "//java/dagger/hilt/android:early_entry_point",
+        "//java/dagger/hilt/android:package_info",
+        "//java/dagger/hilt/android/testing:hilt_android_test",
+        "@google_bazel_common//third_party/java/truth",
+    ],
+)
+
+android_local_test(
+    name = "AndroidEntryPointBaseClassTest",
+    size = "small",
+    srcs = [
+        "AndroidEntryPointBaseClassOtherPkg.java",
+        "AndroidEntryPointBaseClassTest.java",
+    ],
+    javacopts = [
+        # Note: Hilt's bytecode injection doesn't work in Blaze but we disable
+        # superclass validation in this test just to verify everything builds.
+        "-Adagger.hilt.android.internal.disableAndroidSuperclassValidation=true",
+        # Note: Used to test base classes across java packages.
+        "-Xep:PackageLocation:OFF",
+    ],
+    manifest_values = {
+        "minSdkVersion": "14",
+    },
+    deps = [
+        "//:android_local_test_exports",
+        "//:dagger_with_compiler",
+        "//java/dagger/hilt:install_in",
+        "//java/dagger/hilt/android:android_entry_point",
+        "//java/dagger/hilt/android:package_info",
+        "@google_bazel_common//third_party/java/truth",
+    ],
+)
+
+android_local_test(
+    name = "ModuleTest",
+    srcs = ["ModuleTest.java"],
+    manifest_values = {
+        "minSdkVersion": "14",
+    },
+    deps = [
+        "//:android_local_test_exports",
+        "//:dagger_with_compiler",
+        "//java/dagger/hilt:install_in",
+        "//java/dagger/hilt/android:package_info",
+        "//java/dagger/hilt/android/testing:hilt_android_test",
+        "@google_bazel_common//third_party/java/jsr330_inject",
+        "@google_bazel_common//third_party/java/truth",
+    ],
+)
+
+android_local_test(
+    name = "InjectionTest",
+    size = "small",
+    srcs = ["InjectionTest.java"],
+    manifest_values = {
+        "minSdkVersion": "14",
+    },
+    deps = [
+        "//:android_local_test_exports",
+        "//:dagger_with_compiler",
+        "//java/dagger/hilt:install_in",
+        "//java/dagger/hilt/android:android_entry_point",
+        "//java/dagger/hilt/android:package_info",
+        "//java/dagger/hilt/android/testing:hilt_android_test",
+        "@google_bazel_common//third_party/java/jsr330_inject",
+        "@google_bazel_common//third_party/java/truth",
+    ],
+)
+
+android_local_test(
+    name = "InstallInObjectModuleTest",
+    srcs = ["InstallInObjectModuleTest.java"],
+    manifest_values = {
+        "minSdkVersion": "14",
+    },
+    deps = [
+        ":InstallInObjectModuleClasses",
+        "//:android_local_test_exports",
+        "//:dagger_with_compiler",
+        "//java/dagger/hilt:entry_point",
+        "//java/dagger/hilt/android:android_entry_point",
+        "//java/dagger/hilt/android:package_info",
+        "//java/dagger/hilt/android/testing:hilt_android_test",
+        "@google_bazel_common//third_party/java/junit",
+        "@google_bazel_common//third_party/java/truth",
+    ],
+)
+
+kt_android_library(
+    name = "InstallInObjectModuleClasses",
+    testonly = True,
+    srcs = ["InstallInObjectModule.kt"],
+    deps = [
+        "//:dagger_with_compiler",
+        "//java/dagger/hilt:entry_point",
+        "//java/dagger/hilt:install_in",
+        "//java/dagger/hilt/components",
+    ],
+)
+
+android_local_test(
+    name = "InternalKtModuleTest",
+    size = "small",
+    srcs = ["InternalKtModuleTest.java"],
+    manifest_values = {
+        "minSdkVersion": "14",
+    },
+    deps = [
+        "//:android_local_test_exports",
+        "//:dagger_with_compiler",
+        "//java/dagger/hilt:entry_point",
+        "//java/dagger/hilt:install_in",
+        "//java/dagger/hilt/android:package_info",
+        "//java/dagger/hilt/android/testing:hilt_android_test",
+        "//javatests/dagger/hilt/testmodules",
+        "@google_bazel_common//third_party/java/truth",
+    ],
+)
+
+android_local_test(
+    name = "DefaultViewModelFactoryTest",
+    srcs = ["DefaultViewModelFactoryTest.java"],
+    manifest = "AndroidManifest.xml",
+    manifest_values = {
+        "minSdkVersion": "14",
+    },
+    deps = [
+        "//:android_local_test_exports",
+        "//:dagger_with_compiler",
+        "//java/dagger/hilt:entry_point",
+        "//java/dagger/hilt:install_in",
+        "//java/dagger/hilt/android:android_entry_point",
+        "//java/dagger/hilt/android:package_info",
+        "//java/dagger/hilt/android/lifecycle",
+        "//java/dagger/hilt/android/testing:bind_value",
+        "//java/dagger/hilt/android/testing:hilt_android_test",
+        "@google_bazel_common//third_party/java/jsr330_inject",
+        "@google_bazel_common//third_party/java/truth",
+        "@maven//:androidx_activity_activity",
+        "@maven//:androidx_fragment_fragment",
+        "@maven//:androidx_lifecycle_lifecycle_common",
+        "@maven//:androidx_lifecycle_lifecycle_viewmodel",
+        "@maven//:androidx_lifecycle_lifecycle_viewmodel_savedstate",
+        "@maven//:junit_junit",
+    ],
+)
+
+android_local_test(
+    name = "QualifierInKotlinFieldsTest",
+    srcs = ["QualifierInKotlinFieldsTest.java"],
+    manifest = "AndroidManifest.xml",
+    manifest_values = {
+        "minSdkVersion": "14",
+    },
+    deps = [
+        ":QualifierInFieldsClass",
+        "//:android_local_test_exports",
+        "//:dagger_with_compiler",
+        "//java/dagger/hilt/android:android_entry_point",
+        "//java/dagger/hilt/android:package_info",
+        "//java/dagger/hilt/android/testing:hilt_android_test",
+        "@google_bazel_common//third_party/java/jsr330_inject",
+        "@google_bazel_common//third_party/java/truth",
+        "@maven//:androidx_activity_activity",
+        "@maven//:androidx_fragment_fragment",
+        "@maven//:androidx_lifecycle_lifecycle_common",
+        "@maven//:androidx_lifecycle_lifecycle_viewmodel",
+        "@maven//:androidx_lifecycle_lifecycle_viewmodel_savedstate",
+        "@maven//:junit_junit",
+    ],
+)
+
+kt_android_library(
+    name = "QualifierInFieldsClass",
+    testonly = True,
+    srcs = ["QualifierInFieldsClass.kt"],
+    deps = [
+        "//:dagger_with_compiler",
+        "//java/dagger/hilt/android/qualifiers",
+    ],
+)
+
+android_local_test(
+    name = "ActivityRetainedClearedListenerTest",
+    srcs = ["ActivityRetainedClearedListenerTest.java"],
+    manifest = "AndroidManifest.xml",
+    manifest_values = {
+        "minSdkVersion": "14",
+    },
+    deps = [
+        "//:android_local_test_exports",
+        "//:dagger_with_compiler",
+        "//java/dagger/hilt:entry_point",
+        "//java/dagger/hilt:install_in",
+        "//java/dagger/hilt/android:activity_retained_lifecycle",
+        "//java/dagger/hilt/android:android_entry_point",
+        "//java/dagger/hilt/android:package_info",
+        "//java/dagger/hilt/android/testing:hilt_android_test",
+        "@google_bazel_common//third_party/java/jsr330_inject",
+        "@google_bazel_common//third_party/java/truth",
+        "@maven//:androidx_activity_activity",
+        "@maven//:androidx_fragment_fragment",
+        "@maven//:androidx_lifecycle_lifecycle_common",
+        "@maven//:androidx_lifecycle_lifecycle_viewmodel",
+        "@maven//:androidx_lifecycle_lifecycle_viewmodel_savedstate",
+        "@maven//:junit_junit",
+    ],
+)
+
+android_local_test(
+    name = "ActivityInjectedViewModelTest",
+    srcs = ["ActivityInjectedViewModelTest.java"],
+    manifest = "AndroidManifest.xml",
+    manifest_values = {
+        "minSdkVersion": "14",
+    },
+    deps = [
+        "//:android_local_test_exports",
+        "//:dagger_with_compiler",
+        "//java/dagger/hilt:entry_point",
+        "//java/dagger/hilt:install_in",
+        "//java/dagger/hilt/android:android_entry_point",
+        "//java/dagger/hilt/android:package_info",
+        "//java/dagger/hilt/android/testing:hilt_android_test",
+        "@google_bazel_common//third_party/java/jsr330_inject",
+        "@google_bazel_common//third_party/java/truth",
+        "@maven//:androidx_activity_activity",
+        "@maven//:androidx_fragment_fragment",
+        "@maven//:androidx_lifecycle_lifecycle_common",
+        "@maven//:androidx_lifecycle_lifecycle_viewmodel",
+        "@maven//:androidx_lifecycle_lifecycle_viewmodel_savedstate",
+        "@maven//:junit_junit",
+    ],
+)
+
+android_local_test(
+    name = "ViewModelScopedTest",
+    srcs = ["ViewModelScopedTest.java"],
+    manifest = "AndroidManifest.xml",
+    manifest_values = {
+        "minSdkVersion": "14",
+    },
+    deps = [
+        "//:android_local_test_exports",
+        "//:dagger_with_compiler",
+        "//java/dagger/hilt:entry_point",
+        "//java/dagger/hilt:install_in",
+        "//java/dagger/hilt/android:android_entry_point",
+        "//java/dagger/hilt/android:package_info",
+        "//java/dagger/hilt/android/lifecycle",
+        "//java/dagger/hilt/android/scopes",
+        "//java/dagger/hilt/android/testing:hilt_android_test",
+        "@google_bazel_common//third_party/java/jsr330_inject",
+        "@google_bazel_common//third_party/java/truth",
+        "@maven//:androidx_activity_activity",
+        "@maven//:androidx_fragment_fragment",
+        "@maven//:androidx_lifecycle_lifecycle_common",
+        "@maven//:androidx_lifecycle_lifecycle_viewmodel",
+        "@maven//:androidx_lifecycle_lifecycle_viewmodel_savedstate",
+        "@maven//:junit_junit",
+    ],
+)
+
+android_local_test(
+    name = "ViewModelWithBaseTest",
+    srcs = ["ViewModelWithBaseTest.java"],
+    manifest = "AndroidManifest.xml",
+    manifest_values = {
+        "minSdkVersion": "14",
+    },
+    deps = [
+        "//:android_local_test_exports",
+        "//:dagger_with_compiler",
+        "//java/dagger/hilt:entry_point",
+        "//java/dagger/hilt:install_in",
+        "//java/dagger/hilt/android:android_entry_point",
+        "//java/dagger/hilt/android:package_info",
+        "//java/dagger/hilt/android/lifecycle",
+        "//java/dagger/hilt/android/testing:hilt_android_test",
+        "@google_bazel_common//third_party/java/jsr330_inject",
+        "@google_bazel_common//third_party/java/truth",
+        "@maven//:androidx_activity_activity",
+        "@maven//:androidx_fragment_fragment",
+        "@maven//:androidx_lifecycle_lifecycle_common",
+        "@maven//:androidx_lifecycle_lifecycle_viewmodel",
+        "@maven//:androidx_lifecycle_lifecycle_viewmodel_savedstate",
+        "@maven//:junit_junit",
+    ],
+)
+
+android_local_test(
+    name = "ActivityInjectedSavedStateViewModelTest",
+    srcs = ["ActivityInjectedSavedStateViewModelTest.java"],
+    manifest = "AndroidManifest.xml",
+    manifest_values = {
+        "minSdkVersion": "14",
+    },
+    deps = [
+        "//:android_local_test_exports",
+        "//:dagger_with_compiler",
+        "//java/dagger/hilt:entry_point",
+        "//java/dagger/hilt:install_in",
+        "//java/dagger/hilt/android:android_entry_point",
+        "//java/dagger/hilt/android:package_info",
+        "//java/dagger/hilt/android/testing:hilt_android_test",
+        "//java/dagger/internal/guava:base-android",
+        "@google_bazel_common//third_party/java/jsr330_inject",
+        "@google_bazel_common//third_party/java/truth",
+        "@maven//:junit_junit",
+    ],
+)
+
+android_library(
+    name = "uses_component_common",
+    srcs = [
+        "UsesComponentHelper.java",
+        "UsesComponentTestClasses.java",
+    ],
+    deps = [
+        "//:dagger_with_compiler",
+        "//java/dagger/hilt:define_component",
+        "//java/dagger/hilt:entry_point",
+        "//java/dagger/hilt:install_in",
+        "//java/dagger/hilt/android/components",
+        "@google_bazel_common//third_party/java/jsr330_inject",
+    ],
+)
+
+# This has to be split into a separate target in order to avoid
+# being compiled as "test bindings" in the compilation unit of
+# the test class itself.
+android_library(
+    name = "uses_component_test_module",
+    srcs = ["UsesComponentTestModule.java"],
+    deps = [
+        ":uses_component_common",
+        "//:dagger_with_compiler",
+        "//java/dagger/hilt:install_in",
+        "//java/dagger/hilt/android/components",
+    ],
+)
+
+android_library(
+    name = "shared_component_test_classes",
+    srcs = [
+        "UsesLocalComponentTestBindingsTest.java",
+        "UsesLocalComponentUninstallModuleTest.java",
+        "UsesSharedComponent1Test.java",
+        "UsesSharedComponent2Test.java",
+        "UsesSharedComponentEnclosedTest.java",
+        "//javatests/dagger/hilt/android/testsubpackage:UsesLocalComponentTestBindingsTest.java",
+        "//javatests/dagger/hilt/android/testsubpackage:UsesSharedComponent1Test.java",
+    ],
+    exports_manifest = 1,
+    javacopts = ["-Adagger.hilt.shareTestComponents=true"],
+    manifest = "AndroidManifest.xml",
+    deps = [
+        ":uses_component_common",
+        ":uses_component_test_module",
+        "//:android_local_test_exports",
+        "//:dagger_with_compiler",
+        "//java/dagger/hilt:entry_point",
+        "//java/dagger/hilt:install_in",
+        "//java/dagger/hilt/android:android_entry_point",
+        "//java/dagger/hilt/android:hilt_android_app",
+        "//java/dagger/hilt/android:package_info",
+        "//java/dagger/hilt/android/testing:bind_value",
+        "//java/dagger/hilt/android/testing:custom_test_application",
+        "//java/dagger/hilt/android/testing:hilt_android_test",
+        "//java/dagger/hilt/android/testing:uninstall_modules",
+        "@google_bazel_common//third_party/java/jsr330_inject",
+        "@google_bazel_common//third_party/java/truth",
+        "@maven//:androidx_test_core",
+        "@maven//:androidx_test_ext_junit",
+        "@maven//:junit_junit",
+        "@maven//:org_robolectric_annotations",
+        "@maven//:org_robolectric_robolectric",
+    ],
+)
+
+# Separate target that uses @TestInstallIn to replace the global binding
+android_library(
+    name = "test_install_in_test_classes",
+    srcs = [
+        "TestInstallInModules.java",
+        "UsesSharedComponentTestInstallInTest.java",
+    ],
+    javacopts = ["-Adagger.hilt.shareTestComponents=true"],
+    deps = [
+        ":uses_component_common",
+        ":uses_component_test_module",
+        "//:android_local_test_exports",
+        "//:dagger_with_compiler",
+        "//java/dagger/hilt:install_in",
+        "//java/dagger/hilt/android:hilt_android_app",
+        "//java/dagger/hilt/android:package_info",
+        "//java/dagger/hilt/android/components",
+        "//java/dagger/hilt/android/testing:bind_value",
+        "//java/dagger/hilt/android/testing:hilt_android_test",
+        "//java/dagger/hilt/testing:test_install_in",
+        "@google_bazel_common//third_party/java/jsr330_inject",
+        "@google_bazel_common//third_party/java/truth",
+        "@maven//:androidx_test_core",
+        "@maven//:androidx_test_ext_junit",
+        "@maven//:junit_junit",
+        "@maven//:org_robolectric_annotations",
+        "@maven//:org_robolectric_robolectric",
+    ],
+)
+
+android_local_test(
+    name = "UsesLocalComponentTestBindingsTest",
+    manifest_values = {
+        "minSdkVersion": "14",
+    },
+    deps = [
+        ":shared_component_test_classes",
+    ],
+)
+
+android_local_test(
+    name = "UsesLocalComponentUninstallModuleTest",
+    manifest_values = {
+        "minSdkVersion": "14",
+    },
+    deps = [
+        ":shared_component_test_classes",
+    ],
+)
+
+android_local_test(
+    name = "UsesSharedComponent1Test",
+    manifest_values = {
+        "minSdkVersion": "14",
+    },
+    deps = [
+        ":shared_component_test_classes",
+    ],
+)
+
+android_local_test(
+    name = "UsesSharedComponent2Test",
+    manifest_values = {
+        "minSdkVersion": "14",
+    },
+    deps = [
+        ":shared_component_test_classes",
+    ],
+)
+
+android_local_test(
+    name = "UsesSharedComponentEnclosedTest",
+    manifest_values = {
+        "minSdkVersion": "14",
+    },
+    deps = [
+        ":shared_component_test_classes",
+    ],
+)
+
+android_local_test(
+    name = "UsesSharedComponentTestInstallInTest",
+    manifest_values = {
+        "minSdkVersion": "14",
+    },
+    deps = [
+        ":test_install_in_test_classes",
+    ],
+)
diff --git a/javatests/dagger/hilt/android/CustomTestApplicationTest.java b/javatests/dagger/hilt/android/CustomTestApplicationTest.java
new file mode 100644
index 0000000..26de8d4
--- /dev/null
+++ b/javatests/dagger/hilt/android/CustomTestApplicationTest.java
@@ -0,0 +1,60 @@
+/*
+ * Copyright (C) 2020 The Dagger Authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package dagger.hilt.android;
+
+import static androidx.test.core.app.ApplicationProvider.getApplicationContext;
+import static com.google.common.truth.Truth.assertThat;
+
+import android.app.Application;
+import android.content.Context;
+import android.os.Build;
+import androidx.test.ext.junit.runners.AndroidJUnit4;
+import dagger.hilt.android.testing.CustomTestApplication;
+import dagger.hilt.android.testing.HiltAndroidRule;
+import dagger.hilt.android.testing.HiltAndroidTest;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.robolectric.annotation.Config;
+
+/** Tests {@link dagger.hilt.android.testing.CustomTestApplication}. */
+@CustomTestApplication(CustomTestApplicationTest.BaseApplication.class)
+@HiltAndroidTest
+@RunWith(AndroidJUnit4.class)
+// Robolectric requires Java9 to run API 29 and above, so use API 28 instead
+@Config(sdk = Build.VERSION_CODES.P, application = CustomTestApplicationTest_Application.class)
+public final class CustomTestApplicationTest {
+  static class BaseApplication extends Application {}
+
+  static class OtherBaseApplication extends Application {}
+
+  @Rule public HiltAndroidRule rule = new HiltAndroidRule(this);
+
+  @Test
+  public void testApplicationBaseClass() throws Exception {
+    assertThat((Context) getApplicationContext()).isInstanceOf(BaseApplication.class);
+  }
+
+  @CustomTestApplication(OtherBaseApplication.class)
+  public static class Other {}
+
+  @Test
+  @Config(application = CustomTestApplicationTest_Other_Application.class)
+  public void testOtherApplicationBaseClass() throws Exception {
+    assertThat((Context) getApplicationContext()).isInstanceOf(OtherBaseApplication.class);
+  }
+}
diff --git a/javatests/dagger/hilt/android/DefaultViewModelFactoryTest.java b/javatests/dagger/hilt/android/DefaultViewModelFactoryTest.java
new file mode 100644
index 0000000..78cd568
--- /dev/null
+++ b/javatests/dagger/hilt/android/DefaultViewModelFactoryTest.java
@@ -0,0 +1,125 @@
+/*
+ * Copyright (C) 2020 The Dagger Authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package dagger.hilt.android;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import androidx.lifecycle.ViewModel;
+import androidx.lifecycle.ViewModelProvider;
+import android.os.Build;
+import androidx.fragment.app.Fragment;
+import androidx.fragment.app.FragmentActivity;
+import androidx.test.core.app.ActivityScenario;
+import androidx.test.ext.junit.runners.AndroidJUnit4;
+import dagger.hilt.android.lifecycle.HiltViewModel;
+import dagger.hilt.android.testing.BindValue;
+import dagger.hilt.android.testing.HiltAndroidRule;
+import dagger.hilt.android.testing.HiltAndroidTest;
+import dagger.hilt.android.testing.HiltTestApplication;
+import javax.inject.Inject;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.robolectric.annotation.Config;
+
+@HiltAndroidTest
+@RunWith(AndroidJUnit4.class)
+// Robolectric requires Java9 to run API 29 and above, so use API 28 instead
+@Config(sdk = Build.VERSION_CODES.P, application = HiltTestApplication.class)
+public class DefaultViewModelFactoryTest {
+
+  @Rule public final HiltAndroidRule rule = new HiltAndroidRule(this);
+
+  @BindValue String hiltStringValue = "hilt";
+
+  @Test
+  public void activityFactoryFallsBackToBase() {
+    try (ActivityScenario<TestActivity> scenario = ActivityScenario.launch(TestActivity.class)) {
+      scenario.onActivity(
+          activity -> {
+            assertThat(new ViewModelProvider(activity).get(TestHiltViewModel.class).value)
+                .isEqualTo("hilt");
+            assertThat(new ViewModelProvider(activity).get(TestViewModel.class).value)
+                .isEqualTo("non-hilt");
+          });
+    }
+  }
+
+  @Test
+  public void fragmentFactoryFallbsBackToBase() {
+    // TODO(danysantiago): Use FragmentScenario when it becomes available.
+    try (ActivityScenario<TestActivity> scenario = ActivityScenario.launch(TestActivity.class)) {
+      scenario.onActivity(
+          activity -> {
+            TestFragment fragment = new TestFragment();
+            activity.getSupportFragmentManager().beginTransaction().add(fragment, "").commitNow();
+            assertThat(new ViewModelProvider(fragment).get(TestHiltViewModel.class).value)
+                .isEqualTo("hilt");
+            assertThat(new ViewModelProvider(fragment).get(TestViewModel.class).value)
+                .isEqualTo("non-hilt");
+          });
+    }
+  }
+
+  @HiltViewModel
+  public static final class TestHiltViewModel extends ViewModel {
+    final String value;
+
+    @Inject
+    TestHiltViewModel(String value) {
+      this.value = value;
+    }
+  }
+
+  public static final class TestViewModel extends ViewModel {
+    final String value;
+    // Take in a string so it cannot be constructed by the default view model factory
+    public TestViewModel(String value) {
+      this.value = value;
+    }
+  }
+
+  @AndroidEntryPoint(BaseActivity.class)
+  public static final class TestActivity extends Hilt_DefaultViewModelFactoryTest_TestActivity {}
+
+  public static class BaseActivity extends FragmentActivity {
+    @SuppressWarnings("unchecked")
+    @Override public ViewModelProvider.Factory getDefaultViewModelProviderFactory() {
+      return new ViewModelProvider.Factory() {
+        @Override public <T extends ViewModel> T create(Class<T> clazz) {
+          assertThat(clazz).isEqualTo(TestViewModel.class);
+          return (T) new TestViewModel("non-hilt");
+        }
+      };
+    }
+  }
+
+  @AndroidEntryPoint(BaseFragment.class)
+  public static final class TestFragment extends Hilt_DefaultViewModelFactoryTest_TestFragment {}
+
+  public static class BaseFragment extends Fragment {
+    @SuppressWarnings("unchecked")
+    @Override public ViewModelProvider.Factory getDefaultViewModelProviderFactory() {
+      return new ViewModelProvider.Factory() {
+        @Override public <T extends ViewModel> T create(Class<T> clazz) {
+          assertThat(clazz).isEqualTo(TestViewModel.class);
+          return (T) new TestViewModel("non-hilt");
+        }
+      };
+    }
+  }
+}
diff --git a/javatests/dagger/hilt/android/EarlyEntryPointCustomApplicationClasses.java b/javatests/dagger/hilt/android/EarlyEntryPointCustomApplicationClasses.java
new file mode 100644
index 0000000..3e48ee2
--- /dev/null
+++ b/javatests/dagger/hilt/android/EarlyEntryPointCustomApplicationClasses.java
@@ -0,0 +1,40 @@
+/*
+ * Copyright (C) 2021 The Dagger Authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package dagger.hilt.android;
+
+import dagger.hilt.InstallIn;
+import dagger.hilt.components.SingletonComponent;
+import javax.inject.Inject;
+import javax.inject.Singleton;
+
+/** Classes for {@link EarlyEntryPointCustomApplicationTest}. */
+public final class EarlyEntryPointCustomApplicationClasses {
+  private EarlyEntryPointCustomApplicationClasses() {}
+
+  // @EarlyEntryPoint cannot be nested in tests, so we've separated it out into this class.
+  @EarlyEntryPoint
+  @InstallIn(SingletonComponent.class)
+  interface EarlyFooEntryPoint {
+    Foo foo();
+  }
+
+  @Singleton
+  public static class Foo {
+    @Inject
+    Foo() {}
+  }
+}
diff --git a/javatests/dagger/hilt/android/EarlyEntryPointCustomApplicationTest.java b/javatests/dagger/hilt/android/EarlyEntryPointCustomApplicationTest.java
new file mode 100644
index 0000000..3d88190
--- /dev/null
+++ b/javatests/dagger/hilt/android/EarlyEntryPointCustomApplicationTest.java
@@ -0,0 +1,89 @@
+/*
+ * Copyright (C) 2021 The Dagger Authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package dagger.hilt.android;
+
+import static androidx.test.core.app.ApplicationProvider.getApplicationContext;
+import static com.google.common.truth.Truth.assertThat;
+
+import android.app.Application;
+import android.os.Build;
+import androidx.test.ext.junit.runners.AndroidJUnit4;
+import dagger.hilt.EntryPoint;
+import dagger.hilt.EntryPoints;
+import dagger.hilt.InstallIn;
+import dagger.hilt.android.EarlyEntryPointCustomApplicationClasses.EarlyFooEntryPoint;
+import dagger.hilt.android.EarlyEntryPointCustomApplicationClasses.Foo;
+import dagger.hilt.android.testing.CustomTestApplication;
+import dagger.hilt.android.testing.HiltAndroidRule;
+import dagger.hilt.android.testing.HiltAndroidTest;
+import dagger.hilt.components.SingletonComponent;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.robolectric.annotation.Config;
+
+@CustomTestApplication(EarlyEntryPointCustomApplicationTest.BaseApplication.class)
+@HiltAndroidTest
+@RunWith(AndroidJUnit4.class)
+// Robolectric requires Java9 to run API 29 and above, so use API 28 instead
+@Config(
+    sdk = Build.VERSION_CODES.P,
+    application = EarlyEntryPointCustomApplicationTest_Application.class)
+public final class EarlyEntryPointCustomApplicationTest {
+  @EntryPoint
+  @InstallIn(SingletonComponent.class)
+  interface FooEntryPoint {
+    Foo foo();
+  }
+
+  public static class BaseApplication extends Application {
+    Foo earlyFoo = null;
+    IllegalStateException entryPointsException = null;
+
+    @Override
+    public void onCreate() {
+      super.onCreate();
+
+      // Test that calling EarlyEntryPoints works before the test instance is created.
+      earlyFoo = EarlyEntryPoints.get(this, EarlyFooEntryPoint.class).foo();
+
+      // Test that calling EntryPoints fails if called before the test instance is created.
+      try {
+        EntryPoints.get(this, FooEntryPoint.class);
+      } catch (IllegalStateException e) {
+        entryPointsException = e;
+      }
+    }
+  }
+
+  @Rule public HiltAndroidRule rule = new HiltAndroidRule(this);
+
+  @Test
+  public void testEarlyEntryPointsPasses() throws Exception {
+    BaseApplication baseApplication = (BaseApplication) getApplicationContext();
+    assertThat(baseApplication.earlyFoo).isNotNull();
+  }
+
+  @Test
+  public void testEntryPointsFails() throws Exception {
+    BaseApplication baseApplication = (BaseApplication) getApplicationContext();
+    assertThat(baseApplication.entryPointsException).isNotNull();
+    assertThat(baseApplication.entryPointsException)
+        .hasMessageThat()
+        .contains("The component was not created.");
+  }
+}
diff --git a/javatests/dagger/hilt/android/EarlyEntryPointHiltAndroidAppRuntimeClasses.java b/javatests/dagger/hilt/android/EarlyEntryPointHiltAndroidAppRuntimeClasses.java
new file mode 100644
index 0000000..80defd1
--- /dev/null
+++ b/javatests/dagger/hilt/android/EarlyEntryPointHiltAndroidAppRuntimeClasses.java
@@ -0,0 +1,53 @@
+/*
+ * Copyright (C) 2021 The Dagger Authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package dagger.hilt.android;
+
+import android.app.Application;
+import dagger.hilt.EntryPoint;
+import dagger.hilt.InstallIn;
+import dagger.hilt.components.SingletonComponent;
+import javax.inject.Inject;
+import javax.inject.Singleton;
+
+/** Classes for {@link EarlyEntryPointHiltAndroidAppRuntimeTest}. */
+public final class EarlyEntryPointHiltAndroidAppRuntimeClasses {
+  private EarlyEntryPointHiltAndroidAppRuntimeClasses() {}
+
+  // @HiltAndroidApp cannot be nested in tests because @Config.application won't accept it.
+  @HiltAndroidApp(Application.class)
+  public static class TestApplication
+      extends Hilt_EarlyEntryPointHiltAndroidAppRuntimeClasses_TestApplication {}
+
+  @EntryPoint
+  @InstallIn(SingletonComponent.class)
+  interface FooEntryPoint {
+    Foo foo();
+  }
+
+  // @EarlyEntryPoint cannot be nested in tests, so we've separated it out into this class.
+  @EarlyEntryPoint
+  @InstallIn(SingletonComponent.class)
+  interface EarlyFooEntryPoint {
+    Foo foo();
+  }
+
+  @Singleton
+  static class Foo {
+    @Inject
+    Foo() {}
+  }
+}
diff --git a/javatests/dagger/hilt/android/EarlyEntryPointHiltAndroidAppRuntimeTest.java b/javatests/dagger/hilt/android/EarlyEntryPointHiltAndroidAppRuntimeTest.java
new file mode 100644
index 0000000..fe75ae3
--- /dev/null
+++ b/javatests/dagger/hilt/android/EarlyEntryPointHiltAndroidAppRuntimeTest.java
@@ -0,0 +1,57 @@
+/*
+ * Copyright (C) 2021 The Dagger Authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package dagger.hilt.android;
+
+import static androidx.test.core.app.ApplicationProvider.getApplicationContext;
+import static com.google.common.truth.Truth.assertThat;
+
+import android.os.Build;
+import androidx.test.ext.junit.runners.AndroidJUnit4;
+import dagger.hilt.EntryPoints;
+import dagger.hilt.android.EarlyEntryPointHiltAndroidAppRuntimeClasses.EarlyFooEntryPoint;
+import dagger.hilt.android.EarlyEntryPointHiltAndroidAppRuntimeClasses.Foo;
+import dagger.hilt.android.EarlyEntryPointHiltAndroidAppRuntimeClasses.FooEntryPoint;
+import dagger.hilt.android.EarlyEntryPointHiltAndroidAppRuntimeClasses.TestApplication;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.robolectric.annotation.Config;
+
+@RunWith(AndroidJUnit4.class)
+// Robolectric requires Java9 to run API 29 and above, so use API 28 instead
+@Config(sdk = Build.VERSION_CODES.P, application = TestApplication.class)
+public final class EarlyEntryPointHiltAndroidAppRuntimeTest {
+  // Tests that when using @HiltAndroidApp
+  //   1) calling with the wrong parameters doesn't throw
+  //   2) EarlyEntryPoints returns the same thing as EntryPoints.
+  @Test
+  public void testEntryPoints() throws Exception {
+    Object generatedComponent = ((TestApplication) getApplicationContext()).generatedComponent();
+
+    Foo foo1 = EntryPoints.get(getApplicationContext(), FooEntryPoint.class).foo();
+    Foo foo2 = EntryPoints.get(getApplicationContext(), EarlyFooEntryPoint.class).foo();
+    Foo foo3 = EntryPoints.get(generatedComponent, FooEntryPoint.class).foo();
+    Foo foo4 = EntryPoints.get(generatedComponent, EarlyFooEntryPoint.class).foo();
+    Foo foo5 = EarlyEntryPoints.get(getApplicationContext(), FooEntryPoint.class).foo();
+    Foo foo6 = EarlyEntryPoints.get(getApplicationContext(), EarlyFooEntryPoint.class).foo();
+
+    assertThat(foo1).isEqualTo(foo2);
+    assertThat(foo1).isEqualTo(foo3);
+    assertThat(foo1).isEqualTo(foo4);
+    assertThat(foo1).isEqualTo(foo5);
+    assertThat(foo1).isEqualTo(foo6);
+  }
+}
diff --git a/javatests/dagger/hilt/android/EarlyEntryPointHiltAndroidTestRuntimeClasses.java b/javatests/dagger/hilt/android/EarlyEntryPointHiltAndroidTestRuntimeClasses.java
new file mode 100644
index 0000000..85d7c7c
--- /dev/null
+++ b/javatests/dagger/hilt/android/EarlyEntryPointHiltAndroidTestRuntimeClasses.java
@@ -0,0 +1,79 @@
+/*
+ * Copyright (C) 2021 The Dagger Authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package dagger.hilt.android;
+
+import static java.lang.annotation.RetentionPolicy.RUNTIME;
+
+import dagger.BindsInstance;
+import dagger.hilt.DefineComponent;
+import dagger.hilt.EntryPoint;
+import dagger.hilt.InstallIn;
+import dagger.hilt.android.EarlyEntryPointHiltAndroidTestRuntimeClasses.MySubcomponentScoped;
+import dagger.hilt.android.EarlyEntryPointHiltAndroidTestRuntimeTest.Bar;
+import dagger.hilt.android.EarlyEntryPointHiltAndroidTestRuntimeTest.Foo;
+import dagger.hilt.components.SingletonComponent;
+import java.lang.annotation.Retention;
+import javax.inject.Scope;
+
+/** Classes for {@link EarlyEntryPointHiltAndroidTestRuntimeTest}. */
+public final class EarlyEntryPointHiltAndroidTestRuntimeClasses {
+  private EarlyEntryPointHiltAndroidTestRuntimeClasses() {}
+
+  // @EarlyEntryPoint cannot be nested in tests, so we've separated it out into this class.
+  @EarlyEntryPoint
+  @InstallIn(SingletonComponent.class)
+  interface EarlyFooEntryPoint {
+    Foo foo();
+  }
+
+  // @EarlyEntryPoint cannot be nested in tests, so we've separated it out into this class.
+  @EarlyEntryPoint
+  @InstallIn(SingletonComponent.class)
+  interface EarlyMySubcomponentBuilderEntryPoint {
+    MySubcomponent.Builder mySubcomponentBuilder();
+  }
+
+  @Scope
+  @Retention(RUNTIME)
+  public @interface MySubcomponentScoped {}
+
+  @MySubcomponentScoped
+  @DefineComponent(parent = SingletonComponent.class)
+  public interface MySubcomponent {
+    @DefineComponent.Builder
+    interface Builder {
+      @BindsInstance
+      Builder id(int id);
+
+      MySubcomponent build();
+    }
+  }
+
+  // This needs to be defined outside the test so that it gets installed in the early component.
+  @EntryPoint
+  @InstallIn(SingletonComponent.class)
+  interface MySubcomponentBuilderEntryPoint {
+    MySubcomponent.Builder mySubcomponentBuilder();
+  }
+
+  // This needs to be defined outside the test so that it gets installed in the early component.
+  @EntryPoint
+  @InstallIn(MySubcomponent.class)
+  interface BarEntryPoint {
+    Bar bar();
+  }
+}
diff --git a/javatests/dagger/hilt/android/EarlyEntryPointHiltAndroidTestRuntimeTest.java b/javatests/dagger/hilt/android/EarlyEntryPointHiltAndroidTestRuntimeTest.java
new file mode 100644
index 0000000..2050d67
--- /dev/null
+++ b/javatests/dagger/hilt/android/EarlyEntryPointHiltAndroidTestRuntimeTest.java
@@ -0,0 +1,181 @@
+/*
+ * Copyright (C) 2021 The Dagger Authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package dagger.hilt.android;
+
+import static androidx.test.core.app.ApplicationProvider.getApplicationContext;
+import static com.google.common.truth.Truth.assertThat;
+import static org.junit.Assert.assertThrows;
+
+import android.os.Build;
+import androidx.test.ext.junit.runners.AndroidJUnit4;
+import dagger.hilt.EntryPoint;
+import dagger.hilt.EntryPoints;
+import dagger.hilt.InstallIn;
+import dagger.hilt.android.EarlyEntryPointHiltAndroidTestRuntimeClasses.BarEntryPoint;
+import dagger.hilt.android.EarlyEntryPointHiltAndroidTestRuntimeClasses.EarlyFooEntryPoint;
+import dagger.hilt.android.EarlyEntryPointHiltAndroidTestRuntimeClasses.EarlyMySubcomponentBuilderEntryPoint;
+import dagger.hilt.android.EarlyEntryPointHiltAndroidTestRuntimeClasses.MySubcomponent;
+import dagger.hilt.android.EarlyEntryPointHiltAndroidTestRuntimeClasses.MySubcomponentBuilderEntryPoint;
+import dagger.hilt.android.EarlyEntryPointHiltAndroidTestRuntimeClasses.MySubcomponentScoped;
+import dagger.hilt.android.internal.testing.TestApplicationComponentManager;
+import dagger.hilt.android.testing.HiltAndroidRule;
+import dagger.hilt.android.testing.HiltAndroidTest;
+import dagger.hilt.android.testing.HiltTestApplication;
+import dagger.hilt.components.SingletonComponent;
+import javax.inject.Inject;
+import javax.inject.Singleton;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.robolectric.annotation.Config;
+
+@HiltAndroidTest
+@RunWith(AndroidJUnit4.class)
+// Robolectric requires Java9 to run API 29 and above, so use API 28 instead
+@Config(sdk = Build.VERSION_CODES.P, application = HiltTestApplication.class)
+public final class EarlyEntryPointHiltAndroidTestRuntimeTest {
+  @EntryPoint
+  @InstallIn(SingletonComponent.class)
+  interface FooEntryPoint {
+    Foo foo();
+  }
+
+  @Singleton
+  public static class Foo {
+    @Inject
+    Foo() {}
+  }
+
+  @MySubcomponentScoped
+  public static class Bar {
+    final Foo foo;
+    final int id;
+
+    @Inject
+    Bar(Foo foo, int id) {
+      this.foo = foo;
+      this.id = id;
+    }
+  }
+
+  @Rule public HiltAndroidRule rule = new HiltAndroidRule(this);
+
+  @Test
+  public void testEarlyEntryPointsWrongEntryPointFails() throws Exception {
+    IllegalStateException exception =
+        assertThrows(
+            IllegalStateException.class,
+            () -> EarlyEntryPoints.get(getApplicationContext(), FooEntryPoint.class));
+
+    assertThat(exception)
+        .hasMessageThat()
+        .contains(
+            "FooEntryPoint should be called with EntryPoints.get() rather than "
+                + "EarlyEntryPoints.get()");
+  }
+
+  @Test
+  public void testEntryPointsWrongEntryPointFails() throws Exception {
+    IllegalStateException exception =
+        assertThrows(
+            IllegalStateException.class,
+            () -> EntryPoints.get(getApplicationContext(), EarlyFooEntryPoint.class));
+
+    assertThat(exception)
+        .hasMessageThat()
+        .contains(
+            "Interface, dagger.hilt.android.EarlyEntryPointHiltAndroidTestRuntimeClasses."
+                + "EarlyFooEntryPoint, annotated with @EarlyEntryPoint should be called with "
+                + "EarlyEntryPoints.get() rather than EntryPoints.get()");
+  }
+
+  @Test
+  public void testComponentInstances() {
+    Object componentManager = ((HiltTestApplication) getApplicationContext()).componentManager();
+    Object component1 = ((TestApplicationComponentManager) componentManager).generatedComponent();
+    Object component2 = ((TestApplicationComponentManager) componentManager).generatedComponent();
+    assertThat(component1).isNotNull();
+    assertThat(component2).isNotNull();
+    assertThat(component1).isEqualTo(component2);
+
+    Object earlyComponent1 =
+        ((TestApplicationComponentManager) componentManager).earlySingletonComponent();
+    Object earlyComponent2 =
+        ((TestApplicationComponentManager) componentManager).earlySingletonComponent();
+    assertThat(earlyComponent1).isNotNull();
+    assertThat(earlyComponent2).isNotNull();
+    assertThat(earlyComponent1).isEqualTo(earlyComponent2);
+    assertThat(earlyComponent1).isNotEqualTo(component1);
+  }
+
+  // Test that the early entry point returns a different @Singleton binding instance.
+  @Test
+  public void testScopedEntryPointValues() {
+    Foo foo1 = EntryPoints.get(getApplicationContext(), FooEntryPoint.class).foo();
+    Foo foo2 = EntryPoints.get(getApplicationContext(), FooEntryPoint.class).foo();
+    Foo earlyFoo1 =
+        EarlyEntryPoints.get(getApplicationContext(), EarlyFooEntryPoint.class).foo();
+    Foo earlyFoo2 =
+        EarlyEntryPoints.get(getApplicationContext(), EarlyFooEntryPoint.class).foo();
+
+    assertThat(foo1).isNotNull();
+    assertThat(foo2).isNotNull();
+    assertThat(earlyFoo1).isNotNull();
+    assertThat(earlyFoo2).isNotNull();
+
+    assertThat(foo1).isEqualTo(foo2);
+    assertThat(earlyFoo1).isEqualTo(earlyFoo2);
+    assertThat(earlyFoo1).isNotEqualTo(foo1);
+  }
+
+  // Test that the a subcomponent of the early component does not need to use EarlyEntryPoints.
+  @Test
+  public void testSubcomponentEntryPoints() {
+    MySubcomponent subcomponent =
+        EntryPoints.get(getApplicationContext(), MySubcomponentBuilderEntryPoint.class)
+            .mySubcomponentBuilder()
+            .id(5)
+            .build();
+
+    MySubcomponent earlySubcomponent =
+        EarlyEntryPoints.get(
+                getApplicationContext(), EarlyMySubcomponentBuilderEntryPoint.class)
+            .mySubcomponentBuilder()
+            .id(11)
+            .build();
+
+    assertThat(subcomponent).isNotNull();
+    assertThat(earlySubcomponent).isNotNull();
+    assertThat(subcomponent).isNotEqualTo(earlySubcomponent);
+
+    // Test that subcomponents can use EntryPoints
+    Bar bar1 = EntryPoints.get(subcomponent, BarEntryPoint.class).bar();
+    Bar bar2 = EntryPoints.get(subcomponent, BarEntryPoint.class).bar();
+
+    // Test that early subcomponents can use EntryPoints or EarlyEntryPoints
+    Bar earlyBar1 = EntryPoints.get(earlySubcomponent, BarEntryPoint.class).bar();
+    Bar earlyBar2 = EntryPoints.get(earlySubcomponent, BarEntryPoint.class).bar();
+
+    assertThat(bar1).isNotNull();
+    assertThat(bar2).isNotNull();
+    assertThat(earlyBar1).isNotNull();
+    assertThat(earlyBar2).isNotNull();
+    assertThat(bar1).isEqualTo(bar2);
+    assertThat(earlyBar1).isEqualTo(earlyBar2);
+    assertThat(bar1).isNotEqualTo(earlyBar1);
+  }
+}
diff --git a/javatests/dagger/hilt/android/EarlyEntryPointNoEntryPointsDefinedTest.java b/javatests/dagger/hilt/android/EarlyEntryPointNoEntryPointsDefinedTest.java
new file mode 100644
index 0000000..a912615
--- /dev/null
+++ b/javatests/dagger/hilt/android/EarlyEntryPointNoEntryPointsDefinedTest.java
@@ -0,0 +1,90 @@
+/*
+ * Copyright (C) 2021 The Dagger Authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package dagger.hilt.android;
+
+import static androidx.test.core.app.ApplicationProvider.getApplicationContext;
+import static com.google.common.truth.Truth.assertThat;
+import static org.junit.Assert.assertThrows;
+
+import android.os.Build;
+import androidx.test.ext.junit.runners.AndroidJUnit4;
+import dagger.hilt.EntryPoint;
+import dagger.hilt.InstallIn;
+import dagger.hilt.android.internal.testing.TestApplicationComponentManager;
+import dagger.hilt.android.testing.HiltAndroidRule;
+import dagger.hilt.android.testing.HiltAndroidTest;
+import dagger.hilt.android.testing.HiltTestApplication;
+import dagger.hilt.components.SingletonComponent;
+import javax.inject.Inject;
+import javax.inject.Singleton;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.robolectric.annotation.Config;
+
+// The main purpose of this test is to check the error messages if EarlyEntryPoints is called
+// without the EarlyComponent being generated (i.e. if no @EarlyEntryPoints are defined).
+@HiltAndroidTest
+@RunWith(AndroidJUnit4.class)
+// Robolectric requires Java9 to run API 29 and above, so use API 28 instead
+@Config(sdk = Build.VERSION_CODES.P, application = HiltTestApplication.class)
+public final class EarlyEntryPointNoEntryPointsDefinedTest {
+  @EntryPoint
+  @InstallIn(SingletonComponent.class)
+  interface FooEntryPoint {
+    Foo foo();
+  }
+
+  @Singleton
+  public static class Foo {
+    @Inject
+    Foo() {}
+  }
+
+  @Rule public HiltAndroidRule rule = new HiltAndroidRule(this);
+
+  @Test
+  public void testEarlyComponentDoesNotExist() throws Exception {
+    HiltTestApplication app = (HiltTestApplication) getApplicationContext();
+    TestApplicationComponentManager componentManager =
+        (TestApplicationComponentManager) app.componentManager();
+
+    RuntimeException exception =
+        assertThrows(RuntimeException.class, () -> componentManager.earlySingletonComponent());
+
+    assertThat(exception)
+        .hasMessageThat()
+        .contains(
+            "The EarlyComponent was requested but does not exist. Check that you have "
+                + "annotated your test class with @HiltAndroidTest and that the processor is "
+                + "running over your test");
+  }
+
+  @Test
+  public void testEarlyEntryPointsWrongEntryPointFails() throws Exception {
+    IllegalStateException exception =
+        assertThrows(
+            IllegalStateException.class,
+            () -> EarlyEntryPoints.get(getApplicationContext(), FooEntryPoint.class));
+
+    assertThat(exception)
+        .hasMessageThat()
+        .contains(
+            "FooEntryPoint should be called with EntryPoints.get() rather than "
+                + "EarlyEntryPoints.get()");
+  }
+}
diff --git a/javatests/dagger/hilt/android/InjectionTest.java b/javatests/dagger/hilt/android/InjectionTest.java
new file mode 100644
index 0000000..7c34279
--- /dev/null
+++ b/javatests/dagger/hilt/android/InjectionTest.java
@@ -0,0 +1,706 @@
+/*
+ * Copyright (C) 2020 The Dagger Authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package dagger.hilt.android;
+
+import static androidx.test.core.app.ApplicationProvider.getApplicationContext;
+import static com.google.common.truth.Truth.assertThat;
+import static java.lang.annotation.RetentionPolicy.RUNTIME;
+import static org.junit.Assert.assertThrows;
+import static org.junit.Assert.fail;
+
+import android.annotation.TargetApi;
+import android.app.Activity;
+import android.app.IntentService;
+import android.app.Service;
+import android.content.BroadcastReceiver;
+import android.content.Context;
+import android.content.Intent;
+import android.os.Build;
+import android.os.Bundle;
+import android.os.IBinder;
+import androidx.fragment.app.Fragment;
+import androidx.fragment.app.FragmentActivity;
+import android.util.AttributeSet;
+import android.widget.LinearLayout;
+import androidx.test.ext.junit.runners.AndroidJUnit4;
+import dagger.Module;
+import dagger.Provides;
+import dagger.hilt.InstallIn;
+import dagger.hilt.android.components.ActivityComponent;
+import dagger.hilt.android.components.FragmentComponent;
+import dagger.hilt.android.components.ServiceComponent;
+import dagger.hilt.android.testing.HiltAndroidRule;
+import dagger.hilt.android.testing.HiltAndroidTest;
+import dagger.hilt.android.testing.HiltTestApplication;
+import dagger.hilt.components.SingletonComponent;
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Retention;
+import java.lang.annotation.Target;
+import java.util.concurrent.atomic.AtomicLong;
+import javax.inject.Inject;
+import javax.inject.Qualifier;
+import javax.inject.Singleton;
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.robolectric.Robolectric;
+import org.robolectric.android.controller.ActivityController;
+import org.robolectric.annotation.Config;
+
+@HiltAndroidTest
+@RunWith(AndroidJUnit4.class)
+// Robolectric requires Java9 to run API 29 and above, so use API 28 instead
+@Config(sdk = Build.VERSION_CODES.P, application = HiltTestApplication.class)
+public final class InjectionTest {
+  private static final String APP_BINDING = "APP_BINDING";
+  private static final String ACTIVITY_BINDING = "ACTIVIY_BINDING";
+  private static final String FRAGMENT_BINDING = "FRAGMENT_BINDING";
+  private static final String SERVICE_BINDING = "SERVICE_BINDING";
+
+  @Retention(RUNTIME)
+  @Qualifier
+  @Target({ElementType.METHOD, ElementType.PARAMETER, ElementType.FIELD})
+  @interface ApplicationLevel {}
+
+  @Retention(RUNTIME)
+  @Qualifier
+  @Target({ElementType.METHOD, ElementType.PARAMETER, ElementType.FIELD})
+  @interface ActivityLevel {}
+
+  @Retention(RUNTIME)
+  @Qualifier
+  @Target({ElementType.METHOD, ElementType.PARAMETER, ElementType.FIELD})
+  @interface FragmentLevel {}
+
+  @Retention(RUNTIME)
+  @Qualifier
+  @Target({ElementType.METHOD, ElementType.PARAMETER, ElementType.FIELD})
+  @interface ServiceLevel {}
+
+  /** Application level bindings */
+  @Module
+  @InstallIn(SingletonComponent.class)
+  static final class AppModule {
+    @Provides
+    @ApplicationLevel
+    static String providesAppBinding() {
+      return APP_BINDING;
+    }
+
+    @Provides
+    @Singleton
+    static AtomicLong provideCounter() {
+      return new AtomicLong();
+    }
+
+    @Provides
+    static Long provideCount(AtomicLong counter) {
+      return counter.incrementAndGet();
+    }
+  }
+
+  /** Activity level bindings */
+  @Module
+  @InstallIn(ActivityComponent.class)
+  static final class ActivityModule {
+    @Provides
+    @ActivityLevel
+    static String providesActivityBinding() {
+      return ACTIVITY_BINDING;
+    }
+  }
+
+  /** Fragment level bindings */
+  @Module
+  @InstallIn(FragmentComponent.class)
+  static final class FragmentModule {
+    @Provides
+    @FragmentLevel
+    static String providesFragmentBinding() {
+      return FRAGMENT_BINDING;
+    }
+  }
+
+  /** Service level bindings */
+  @Module
+  @InstallIn(ServiceComponent.class)
+  static final class ServiceModule {
+    @Provides
+    @ServiceLevel
+    static String providesServiceBinding() {
+      return SERVICE_BINDING;
+    }
+  }
+
+  /** Hilt Activity */
+  @AndroidEntryPoint(FragmentActivity.class)
+  public static final class TestActivity extends Hilt_InjectionTest_TestActivity {
+    @Inject @ApplicationLevel String appBinding;
+    @Inject @ActivityLevel String activityBinding;
+    boolean onCreateCalled;
+
+    @Override
+    public void onCreate(Bundle onSavedInstanceState) {
+      assertThat(appBinding).isNull();
+      assertThat(activityBinding).isNull();
+
+      super.onCreate(onSavedInstanceState);
+
+      assertThat(appBinding).isEqualTo(APP_BINDING);
+      assertThat(activityBinding).isEqualTo(ACTIVITY_BINDING);
+
+      onCreateCalled = true;
+    }
+  }
+
+  /** Non-Hilt Activity */
+  public static final class NonHiltActivity extends FragmentActivity {}
+
+  /** Hilt Fragment */
+  @AndroidEntryPoint(Fragment.class)
+  public static final class TestFragment extends Hilt_InjectionTest_TestFragment {
+    @Inject @ApplicationLevel String appBinding;
+    @Inject @ActivityLevel String activityBinding;
+    @Inject @FragmentLevel String fragmentBinding;
+    boolean onAttachContextCalled;
+    boolean onAttachActivityCalled;
+
+    @Override
+    public void onAttach(Context context) {
+      preInjectionAssert();
+      super.onAttach(context);
+      postInjectionAssert();
+      onAttachContextCalled = true;
+    }
+
+    @Override
+    public void onAttach(Activity activity) {
+      preInjectionAssert();
+      super.onAttach(activity);
+      postInjectionAssert();
+      onAttachActivityCalled = true;
+    }
+
+    private void preInjectionAssert() {
+      assertThat(appBinding).isNull();
+      assertThat(activityBinding).isNull();
+      assertThat(fragmentBinding).isNull();
+    }
+
+    private void postInjectionAssert() {
+      assertThat(appBinding).isEqualTo(APP_BINDING);
+      assertThat(activityBinding).isEqualTo(ACTIVITY_BINDING);
+      assertThat(fragmentBinding).isEqualTo(FRAGMENT_BINDING);
+    }
+  }
+
+  /** Non-Hilt Fragment */
+  public static final class NonHiltFragment extends Fragment {}
+
+  /** Hilt extends parameterized fragment. */
+  @AndroidEntryPoint(ParameterizedFragment.class)
+  public static final class TestParameterizedFragment
+      extends Hilt_InjectionTest_TestParameterizedFragment<Integer> {
+    @Inject @ApplicationLevel String appBinding;
+    @Inject @ActivityLevel String activityBinding;
+    @Inject @FragmentLevel String fragmentBinding;
+    boolean onAttachContextCalled;
+    boolean onAttachActivityCalled;
+
+    @Override
+    public void onAttach(Context context) {
+      preInjectionAssert();
+      super.onAttach(context);
+      postInjectionAssert();
+      onAttachContextCalled = true;
+    }
+
+    @Override
+    public void onAttach(Activity activity) {
+      preInjectionAssert();
+      super.onAttach(activity);
+      postInjectionAssert();
+      onAttachActivityCalled = true;
+    }
+
+    private void preInjectionAssert() {
+      assertThat(appBinding).isNull();
+      assertThat(activityBinding).isNull();
+      assertThat(fragmentBinding).isNull();
+    }
+
+    private void postInjectionAssert() {
+      assertThat(appBinding).isEqualTo(APP_BINDING);
+      assertThat(activityBinding).isEqualTo(ACTIVITY_BINDING);
+      assertThat(fragmentBinding).isEqualTo(FRAGMENT_BINDING);
+    }
+  }
+
+  /** Non-Hilt parameterized fragment */
+  public static class ParameterizedFragment<T> extends Fragment {}
+
+  /** Hilt View */
+  @AndroidEntryPoint(LinearLayout.class)
+  public static final class TestView extends Hilt_InjectionTest_TestView {
+    @Inject @ApplicationLevel String appBinding;
+    @Inject @ActivityLevel String activityBinding;
+
+    TestView(Context context) {
+      super(context);
+    }
+
+    TestView(Context context, AttributeSet attrs) {
+      super(context, attrs);
+    }
+
+    TestView(Context context, AttributeSet attrs, int defStyleAttr) {
+      super(context, attrs, defStyleAttr);
+    }
+
+    @TargetApi(21)
+    TestView(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
+      super(context, attrs, defStyleAttr, defStyleRes);
+    }
+  }
+
+  /** Hilt View (With Fragment bindings) */
+  @WithFragmentBindings
+  @AndroidEntryPoint(LinearLayout.class)
+  public static final class TestViewWithFragmentBindings
+      extends Hilt_InjectionTest_TestViewWithFragmentBindings {
+    @Inject @ApplicationLevel String appBinding;
+    @Inject @ActivityLevel String activityBinding;
+    @Inject @FragmentLevel String fragmentBinding;
+
+    TestViewWithFragmentBindings(Context context) {
+      super(context);
+    }
+  }
+
+  @AndroidEntryPoint(Service.class)
+  public static final class TestService extends Hilt_InjectionTest_TestService {
+    @Inject @ApplicationLevel String appBinding;
+    @Inject @ServiceLevel String serviceBinding;
+
+    @Override
+    public IBinder onBind(Intent intent) {
+      return null;
+    }
+  }
+
+  @AndroidEntryPoint(IntentService.class)
+  public static final class TestIntentService extends Hilt_InjectionTest_TestIntentService {
+    private static final String NAME = "TestIntentServiceName";
+    @Inject @ApplicationLevel String appBinding;
+    @Inject @ServiceLevel String serviceBinding;
+
+    TestIntentService() {
+      super(NAME);
+    }
+
+    @Override
+    public void onHandleIntent(Intent intent) {}
+  }
+
+  @AndroidEntryPoint(BroadcastReceiver.class)
+  public static final class TestBroadcastReceiver extends Hilt_InjectionTest_TestBroadcastReceiver {
+    @Inject @ApplicationLevel String appBinding;
+    Intent lastIntent = null;
+
+    @Override
+    public void onReceive(Context context, Intent intent) {
+      super.onReceive(context, intent);
+      lastIntent = intent;
+    }
+  }
+
+  @AndroidEntryPoint(BaseBroadcastReceiver.class)
+  public static final class TestBroadcastReceiverWithBaseImplementingOnReceive
+      extends Hilt_InjectionTest_TestBroadcastReceiverWithBaseImplementingOnReceive {
+    @Inject @ApplicationLevel String appBinding;
+    Intent baseLastIntent = null;
+
+    @Override
+    public void onReceive(Context context, Intent intent) {
+      super.onReceive(context, intent);
+      baseLastIntent = intent;
+    }
+  }
+
+  abstract static class BaseBroadcastReceiver extends BroadcastReceiver {
+    Intent lastIntent = null;
+
+    @Override
+    public void onReceive(Context context, Intent intent) {
+      lastIntent = intent;
+    }
+  }
+
+  @Rule public final HiltAndroidRule rule = new HiltAndroidRule(this);
+
+  @Inject @ApplicationLevel String appBinding;
+
+  @Before
+  public void setup() {
+    rule.inject();
+  }
+
+  @Test
+  public void testAppInjection() throws Exception {
+    assertThat(appBinding).isEqualTo(APP_BINDING);
+  }
+
+  @Test
+  public void testActivityInjection() throws Exception {
+    ActivityController<TestActivity> controller = Robolectric.buildActivity(TestActivity.class);
+
+    assertThat(controller.get().onCreateCalled).isFalse();
+    controller.create();
+    assertThat(controller.get().onCreateCalled).isTrue();
+  }
+
+  @Test
+  public void testFragmentInjection() throws Exception {
+    TestFragment fragment = new TestFragment();
+    assertThat(fragment.onAttachContextCalled).isFalse();
+    assertThat(fragment.onAttachActivityCalled).isFalse();
+    setupFragment(TestActivity.class, fragment);
+    assertThat(fragment.onAttachContextCalled).isTrue();
+    assertThat(fragment.onAttachActivityCalled).isTrue();
+  }
+
+  @Test
+  public void testParameterizedFragmentInjection() throws Exception {
+    TestParameterizedFragment fragment = new TestParameterizedFragment();
+    assertThat(fragment.onAttachContextCalled).isFalse();
+    assertThat(fragment.onAttachActivityCalled).isFalse();
+    setupFragment(TestActivity.class, fragment);
+    assertThat(fragment.onAttachContextCalled).isTrue();
+    assertThat(fragment.onAttachActivityCalled).isTrue();
+  }
+
+  @Test
+  public void testViewNoFragmentBindingsWithActivity() throws Exception {
+    TestActivity activity = Robolectric.setupActivity(TestActivity.class);
+    TestView view = new TestView(activity);
+    assertThat(view.appBinding).isEqualTo(APP_BINDING);
+    assertThat(view.activityBinding).isEqualTo(ACTIVITY_BINDING);
+  }
+
+  @Test
+  public void testViewNoFragmentBindingsWithFragment() throws Exception {
+    TestFragment fragment = setupFragment(TestActivity.class, new TestFragment());
+    TestView view = new TestView(fragment.getContext());
+    assertThat(view.appBinding).isEqualTo(APP_BINDING);
+    assertThat(view.activityBinding).isEqualTo(ACTIVITY_BINDING);
+  }
+
+  @Test
+  public void testViewNoFragmentBindingsWithFragment_secondConstructor() throws Exception {
+    TestFragment fragment = setupFragment(TestActivity.class, new TestFragment());
+    TestView view = new TestView(fragment.getContext(), /* attrs= */ null);
+    assertThat(view.appBinding).isEqualTo(APP_BINDING);
+    assertThat(view.activityBinding).isEqualTo(ACTIVITY_BINDING);
+  }
+
+  @Test
+  public void testViewNoFragmentBindingsWithFragment_thirdConstructor() throws Exception {
+    TestFragment fragment = setupFragment(TestActivity.class, new TestFragment());
+    TestView view = new TestView(fragment.getContext(), /* attrs= */ null, /* defStyleAttr= */ 0);
+    assertThat(view.appBinding).isEqualTo(APP_BINDING);
+    assertThat(view.activityBinding).isEqualTo(ACTIVITY_BINDING);
+  }
+
+  @Test
+  @Config(sdk = 21)
+  public void testViewNoFragmentBindingsWithFragment_fourthConstructor_presentOnTwentyOne()
+      throws Exception {
+    TestFragment fragment = setupFragment(TestActivity.class, new TestFragment());
+    TestView view =
+        new TestView(
+            fragment.getContext(), /* attrs= */ null, /* defStyleAttr= */ 0, /* defStyleRes= */ 0);
+    assertThat(view.appBinding).isEqualTo(APP_BINDING);
+    assertThat(view.activityBinding).isEqualTo(ACTIVITY_BINDING);
+  }
+
+  @Test
+  @Config(sdk = 19)
+  public void testViewNoFragmentBindingsWithFragment_fourthConstructor_notPresentOnTwenty() {
+    TestFragment fragment = setupFragment(TestActivity.class, new TestFragment());
+
+    assertThrows(
+        NoSuchMethodError.class,
+        () ->
+            new TestView(
+                fragment.getContext(),
+                /* attrs= */ null,
+                /* defStyleAttr= */ 0,
+                /* defStyleRes= */ 0));
+  }
+
+  @Test
+  public void testServiceInjection() throws Exception {
+    TestService testService = Robolectric.setupService(TestService.class);
+    assertThat(testService.appBinding).isEqualTo(APP_BINDING);
+    assertThat(testService.serviceBinding).isEqualTo(SERVICE_BINDING);
+  }
+
+  @Test
+  public void testIntentServiceInjection() throws Exception {
+    TestIntentService testIntentService = Robolectric.setupService(TestIntentService.class);
+    assertThat(testIntentService.appBinding).isEqualTo(APP_BINDING);
+    assertThat(testIntentService.serviceBinding).isEqualTo(SERVICE_BINDING);
+  }
+
+  @Test
+  public void testBroadcastReceiverInjection() throws Exception {
+    TestBroadcastReceiver testBroadcastReceiver = new TestBroadcastReceiver();
+    Intent intent = new Intent();
+    testBroadcastReceiver.onReceive(getApplicationContext(), intent);
+    assertThat(testBroadcastReceiver.appBinding).isEqualTo(APP_BINDING);
+    assertThat(testBroadcastReceiver.lastIntent).isSameInstanceAs(intent);
+  }
+
+  @Test
+  public void testBroadcastReceiverWithBaseImplementingOnReceiveInjection() throws Exception {
+    TestBroadcastReceiverWithBaseImplementingOnReceive testBroadcastReceiver =
+        new TestBroadcastReceiverWithBaseImplementingOnReceive();
+    Intent intent = new Intent();
+    testBroadcastReceiver.onReceive(getApplicationContext(), intent);
+    assertThat(testBroadcastReceiver.appBinding).isEqualTo(APP_BINDING);
+    assertThat(testBroadcastReceiver.lastIntent).isSameInstanceAs(intent);
+    assertThat(testBroadcastReceiver.baseLastIntent).isSameInstanceAs(intent);
+  }
+
+  @Test
+  public void testViewWithFragmentBindingsWithFragment() throws Exception {
+    TestFragment fragment = setupFragment(TestActivity.class, new TestFragment());
+
+    Context fragmentContext = fragment.getContext();
+    TestViewWithFragmentBindings view = new TestViewWithFragmentBindings(fragmentContext);
+    assertThat(view.appBinding).isEqualTo(APP_BINDING);
+    assertThat(view.activityBinding).isEqualTo(ACTIVITY_BINDING);
+    assertThat(view.fragmentBinding).isEqualTo(FRAGMENT_BINDING);
+  }
+
+  @Test
+  public void testViewWithFragmentBindingsFailsWithActivity() throws Exception {
+    TestActivity activity = Robolectric.setupActivity(TestActivity.class);
+    try {
+      new TestViewWithFragmentBindings(activity);
+      fail("Expected test to fail but it passes!");
+    } catch (IllegalStateException e) {
+      assertThat(e)
+          .hasMessageThat()
+      .contains(
+      "@WithFragmentBindings Hilt view must be attached to an @AndroidEntryPoint Fragment");
+    }
+  }
+
+  @Test
+  public void testFragmentAttachedToNonHiltActivityFails() throws Exception {
+    NonHiltActivity activity = Robolectric.setupActivity(NonHiltActivity.class);
+    try {
+      activity
+          .getSupportFragmentManager()
+          .beginTransaction()
+          .add(new TestFragment(), null)
+          .commitNow();
+      fail("Expected test to fail but it passes!");
+    } catch (IllegalStateException e) {
+      assertThat(e)
+          .hasMessageThat()
+      .contains("Hilt Fragments must be attached to an @AndroidEntryPoint Activity");
+    }
+  }
+
+  @Test
+  public void testViewAttachedToNonHiltActivityFails() throws Exception {
+    NonHiltActivity activity = Robolectric.setupActivity(NonHiltActivity.class);
+    try {
+      new TestView(activity);
+      fail("Expected test to fail but it passes!");
+    } catch (IllegalStateException e) {
+      assertThat(e)
+          .hasMessageThat()
+      .contains("Hilt view must be attached to an @AndroidEntryPoint Fragment or Activity");
+    }
+  }
+
+  @Test
+  public void testViewAttachedToNonHiltFragmentFails() throws Exception {
+    NonHiltActivity activity = Robolectric.setupActivity(NonHiltActivity.class);
+    NonHiltFragment fragment = new NonHiltFragment();
+    activity.getSupportFragmentManager().beginTransaction().add(fragment, null).commitNow();
+    Context nonHiltContext = fragment.getContext();
+    try {
+      new TestView(nonHiltContext);
+      fail("Expected test to fail but it passes!");
+    } catch (IllegalStateException e) {
+      assertThat(e)
+          .hasMessageThat()
+      .contains("Hilt view must be attached to an @AndroidEntryPoint Fragment or Activity");
+    }
+  }
+
+  @Test
+  public void testViewAttachedToApplicationContextFails() throws Exception {
+    try {
+      new TestView(getApplicationContext());
+      fail("Expected test to fail but it passes!");
+    } catch (IllegalStateException e) {
+      assertThat(e)
+          .hasMessageThat()
+      .contains(
+          "Hilt view cannot be created using the application context. "
+              + "Use a Hilt Fragment or Activity context");
+    }
+  }
+
+  /** Hilt Activity that manually calls inject(). */
+  @AndroidEntryPoint(FragmentActivity.class)
+  public static final class DoubleInjectActivity extends Hilt_InjectionTest_DoubleInjectActivity {
+    @Inject Long counter;
+
+    @Override
+    public void onCreate(Bundle onSavedInstanceState) {
+      inject();
+      super.onCreate(onSavedInstanceState);
+    }
+  }
+
+  @Test
+  public void testActivityDoesNotInjectTwice() throws Exception {
+    ActivityController<DoubleInjectActivity> controller =
+        Robolectric.buildActivity(DoubleInjectActivity.class);
+    controller.create();
+    assertThat(controller.get().counter).isEqualTo(1L);
+  }
+
+  /** Hilt Fragment that manually calls inject(). */
+  @AndroidEntryPoint(Fragment.class)
+  public static final class DoubleInjectFragment extends Hilt_InjectionTest_DoubleInjectFragment {
+    @Inject Long counter;
+
+    @Override
+    public void onAttach(Context context) {
+      inject();
+      super.onAttach(context);
+    }
+
+    @Override
+    public void onAttach(Activity activity) {
+      inject();
+      super.onAttach(activity);
+    }
+  }
+
+  @Test
+  public void testFragmentDoesNotInjectTwice() throws Exception {
+    DoubleInjectFragment fragment = setupFragment(TestActivity.class, new DoubleInjectFragment());
+    assertThat(fragment.counter).isEqualTo(1L);
+  }
+
+  /** Hilt View that manually calls inject(). */
+  @AndroidEntryPoint(LinearLayout.class)
+  public static final class DoubleInjectView extends Hilt_InjectionTest_DoubleInjectView {
+    @Inject Long counter;
+
+    DoubleInjectView(Context context) {
+      super(context);
+      inject();
+    }
+
+    DoubleInjectView(Context context, AttributeSet attrs) {
+      super(context, attrs);
+      inject();
+    }
+
+    DoubleInjectView(Context context, AttributeSet attrs, int defStyleAttr) {
+      super(context, attrs, defStyleAttr);
+      inject();
+    }
+
+    @TargetApi(21)
+    DoubleInjectView(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
+      super(context, attrs, defStyleAttr, defStyleRes);
+      inject();
+    }
+  }
+
+  @Test
+  public void testViewDoesNotInjectTwice() throws Exception {
+    TestActivity activity = Robolectric.setupActivity(TestActivity.class);
+    DoubleInjectView view = new DoubleInjectView(activity);
+    assertThat(view.counter).isEqualTo(1L);
+  }
+
+  /** Hilt Service that manually calls inject(). */
+  @AndroidEntryPoint(Service.class)
+  public static final class DoubleInjectService extends Hilt_InjectionTest_DoubleInjectService {
+    @Inject Long counter;
+
+    @Override public void onCreate() {
+      inject();
+      super.onCreate();
+    }
+
+    @Override
+    public IBinder onBind(Intent intent) {
+      return null;
+    }
+  }
+
+  @Test
+  public void testServiceDoesNotInjectTwice() throws Exception {
+    DoubleInjectService testService = Robolectric.setupService(DoubleInjectService.class);
+    assertThat(testService.counter).isEqualTo(1L);
+  }
+
+  /** Hilt BroadcastReceiver that manually calls inject(). */
+  @AndroidEntryPoint(BroadcastReceiver.class)
+  public static final class DoubleInjectBroadcastReceiver
+      extends Hilt_InjectionTest_DoubleInjectBroadcastReceiver {
+    @Inject Long counter;
+
+    @Override
+    public void onReceive(Context context, Intent intent) {
+      inject(context);
+      super.onReceive(context, intent);
+    }
+  }
+
+  @Test
+  public void testBroadcastReceiverDoesNotInjectTwice() throws Exception {
+    DoubleInjectBroadcastReceiver testBroadcastReceiver = new DoubleInjectBroadcastReceiver();
+    Intent intent = new Intent();
+    testBroadcastReceiver.onReceive(getApplicationContext(), intent);
+    assertThat(testBroadcastReceiver.counter).isEqualTo(1L);
+  }
+
+  private static <T extends Fragment> T setupFragment(
+      Class<? extends FragmentActivity> activityClass, T fragment) {
+    FragmentActivity activity = Robolectric.setupActivity(activityClass);
+    attachFragment(activity, fragment);
+    return fragment;
+  }
+
+  private static void attachFragment(FragmentActivity activity, Fragment fragment) {
+    activity.getSupportFragmentManager().beginTransaction().add(fragment, "").commitNow();
+  }
+}
diff --git a/javatests/dagger/hilt/android/InstallInObjectModule.kt b/javatests/dagger/hilt/android/InstallInObjectModule.kt
new file mode 100644
index 0000000..dba8571
--- /dev/null
+++ b/javatests/dagger/hilt/android/InstallInObjectModule.kt
@@ -0,0 +1,50 @@
+/*
+ * Copyright (C) 2020 The Dagger Authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package dagger.hilt.android
+
+import dagger.Module
+import dagger.Provides
+import dagger.hilt.EntryPoint
+import dagger.hilt.InstallIn
+import dagger.hilt.components.SingletonComponent
+
+@EntryPoint
+@InstallIn(SingletonComponent::class)
+interface TestEntryPoint {
+  fun getInteger(): Int
+  fun getDouble(): Double
+  fun getBoolean(): Boolean
+}
+
+@Module
+@InstallIn(SingletonComponent::class)
+object InstallInObjectModule {
+  @Provides
+  fun provideBoolean() = true
+}
+
+@Module
+@InstallIn(SingletonComponent::class)
+class InstallInCompanionObjectModule {
+  @Provides
+  fun provideDouble() = 2.0
+
+  companion object {
+    @Provides
+    fun provideInt() = 1
+  }
+}
diff --git a/javatests/dagger/hilt/android/InstallInObjectModuleTest.java b/javatests/dagger/hilt/android/InstallInObjectModuleTest.java
new file mode 100644
index 0000000..760d181
--- /dev/null
+++ b/javatests/dagger/hilt/android/InstallInObjectModuleTest.java
@@ -0,0 +1,48 @@
+/*
+ * Copyright (C) 2020 The Dagger Authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package dagger.hilt.android;
+
+import static androidx.test.core.app.ApplicationProvider.getApplicationContext;
+import static com.google.common.truth.Truth.assertThat;
+
+import android.os.Build;
+import androidx.test.ext.junit.runners.AndroidJUnit4;
+import dagger.hilt.EntryPoints;
+import dagger.hilt.android.testing.HiltAndroidRule;
+import dagger.hilt.android.testing.HiltAndroidTest;
+import dagger.hilt.android.testing.HiltTestApplication;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.robolectric.annotation.Config;
+
+@HiltAndroidTest
+@RunWith(AndroidJUnit4.class)
+// Robolectric requires Java9 to run API 29 and above, so use API 28 instead
+@Config(sdk = Build.VERSION_CODES.P, application = HiltTestApplication.class)
+public class InstallInObjectModuleTest {
+
+  @Rule public final HiltAndroidRule rule = new HiltAndroidRule(this);
+
+  @Test
+  public void verifyObjectModule() {
+    TestEntryPoint entryPoint = EntryPoints.get(getApplicationContext(), TestEntryPoint.class);
+    assertThat(entryPoint.getInteger()).isEqualTo(1);
+    assertThat(entryPoint.getDouble()).isEqualTo(2.0);
+    assertThat(entryPoint.getBoolean()).isTrue();
+  }
+}
diff --git a/javatests/dagger/hilt/android/InternalKtModuleTest.java b/javatests/dagger/hilt/android/InternalKtModuleTest.java
new file mode 100644
index 0000000..0b48989
--- /dev/null
+++ b/javatests/dagger/hilt/android/InternalKtModuleTest.java
@@ -0,0 +1,52 @@
+/*
+ * Copyright (C) 2021 The Dagger Authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package dagger.hilt.android;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import android.os.Build;
+import androidx.test.ext.junit.runners.AndroidJUnit4;
+import dagger.hilt.android.testing.HiltAndroidRule;
+import dagger.hilt.android.testing.HiltAndroidTest;
+import dagger.hilt.android.testing.HiltTestApplication;
+import javax.inject.Inject;
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.robolectric.annotation.Config;
+
+@HiltAndroidTest
+@RunWith(AndroidJUnit4.class)
+@Config(sdk = Build.VERSION_CODES.P, application = HiltTestApplication.class)
+public final class InternalKtModuleTest {
+  @Rule public final HiltAndroidRule rule = new HiltAndroidRule(this);
+
+  @Inject String string;
+  @Inject Integer intValue;
+
+  @Before
+  public void setUp() {
+    rule.inject();
+  }
+
+  @Test
+  public void testBindingFromInternalKtModule() {
+    assertThat(string).isEqualTo("expected_string_value");
+    assertThat(intValue).isEqualTo(9);
+  }
+}
diff --git a/javatests/dagger/hilt/android/ModuleTest.java b/javatests/dagger/hilt/android/ModuleTest.java
new file mode 100644
index 0000000..0dadfe0
--- /dev/null
+++ b/javatests/dagger/hilt/android/ModuleTest.java
@@ -0,0 +1,182 @@
+/*
+ * Copyright (C) 2020 The Dagger Authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package dagger.hilt.android;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import android.os.Build;
+import androidx.test.ext.junit.runners.AndroidJUnit4;
+import dagger.Binds;
+import dagger.Module;
+import dagger.Provides;
+import dagger.hilt.InstallIn;
+import dagger.hilt.android.testing.HiltAndroidRule;
+import dagger.hilt.android.testing.HiltAndroidTest;
+import dagger.hilt.android.testing.HiltTestApplication;
+import dagger.hilt.components.SingletonComponent;
+import javax.inject.Inject;
+import javax.inject.Named;
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.robolectric.annotation.Config;
+
+@HiltAndroidTest
+@RunWith(AndroidJUnit4.class)
+// Robolectric requires Java9 to run API 29 and above, so use API 28 instead
+@Config(sdk = Build.VERSION_CODES.P, application = HiltTestApplication.class)
+public final class ModuleTest {
+  public static class Dep1 {}
+  public static class Dep2 {}
+  public static class Dep3 {}
+  public static class Dep4 { @Inject Dep4() {}}
+  public static class Dep5 { @Inject Dep5() {}}
+  public static class Dep6 {}
+  public static class Dep7 {}
+
+  @Rule public HiltAndroidRule rule = new HiltAndroidRule(this);
+
+  // Test that modules with only static methods can have private constructors.
+  @Module
+  @InstallIn(SingletonComponent.class)
+  static final class TestModule1 {
+    private TestModule1() {} // This is fine because Dagger doesn't need an instance.
+
+    @Provides
+    static Dep1 provide() {
+      return new Dep1();
+    }
+  }
+
+  // Test that modules with only static methods can have constructors with parameters.
+  @Module
+  @InstallIn(SingletonComponent.class)
+  static final class TestModule2 {
+    TestModule2(String str) {} // This is fine because Dagger doesn't need an instance.
+
+    @Provides
+    static Dep2 provide() {
+      return new Dep2();
+    }
+  }
+
+  // Test that modules with non-static methods can have constructors with parameters if no-arg
+  // constructor exists.
+  @Module
+  @InstallIn(SingletonComponent.class)
+  static final class TestModule3 {
+    TestModule3() {
+      this("");
+    }
+
+    TestModule3(String str) {} // This is fine because Dagger can use the other constructor
+
+    @Provides
+    Dep3 provide() {
+      return new Dep3();
+    }
+  }
+
+  // Test that modules with only abstract methods can have private constructors.
+  @Module
+  @InstallIn(SingletonComponent.class)
+  @SuppressWarnings("ClassCanBeStatic") // purposely testing non-static class here
+  abstract class TestModule4 {
+    private TestModule4() {}  // This is fine because Dagger doesn't need an instance.
+
+    @Binds @Named("Dep4") abstract Object bind(Dep4 impl);
+  }
+
+  // Test that modules with only abstract methods can have constructors with parameters.
+  @Module
+  @InstallIn(SingletonComponent.class)
+  @SuppressWarnings("ClassCanBeStatic") // purposely testing non-static class here
+  abstract class TestModule5 {
+    TestModule5(String str) {} // This is fine because Dagger doesn't need an instance.
+
+    @Binds @Named("Dep5") abstract Object bind(Dep5 impl);
+  }
+
+  // Test that static modules with no methods can have private constructors.
+  @Module
+  @InstallIn(SingletonComponent.class)
+  static final class TestModule6 {
+    private TestModule6() {} // This is fine because Dagger doesn't need an instance.
+  }
+
+  // Test that static modules with no methods can have constructors with parameters.
+  @Module
+  @InstallIn(SingletonComponent.class)
+  static final class TestModule7 {
+    TestModule7(String str) {} // This is fine because Dagger doesn't need an instance.
+  }
+
+  // Test that abstract modules with no methods can have private constructors.
+  @Module
+  @InstallIn(SingletonComponent.class)
+  @SuppressWarnings("ClassCanBeStatic") // purposely testing non-static class here
+  abstract class TestModule8 {
+    private TestModule8() {} // This is fine because Dagger doesn't need an instance.
+  }
+
+  // Test that abstract modules with no methods can have constructors with parameters.
+  @Module
+  @InstallIn(SingletonComponent.class)
+  @SuppressWarnings("ClassCanBeStatic") // purposely testing non-static class here
+  abstract class TestModule9 {
+    TestModule9(String str) {} // This is fine because Dagger doesn't need an instance.
+  }
+
+  @Inject Dep1 dep1;
+  @Inject Dep2 dep2;
+  @Inject Dep5 dep3;
+  @Inject @Named("Dep4") Object dep4;
+  @Inject @Named("Dep5") Object dep5;
+
+  @Before
+  public void setup() {
+    rule.inject();
+  }
+
+  @Test
+  public void testDep1() throws Exception {
+    assertThat(dep1).isNotNull();
+  }
+
+  @Test
+  public void testDep2() throws Exception {
+    assertThat(dep2).isNotNull();
+  }
+
+  @Test
+  public void testDep3() throws Exception {
+    assertThat(dep3).isNotNull();
+  }
+
+  @Test
+  public void testDep4() throws Exception {
+    assertThat(dep4).isNotNull();
+    assertThat(dep4).isInstanceOf(Dep4.class);
+  }
+
+  @Test
+  public void testDep5() throws Exception {
+    assertThat(dep5).isNotNull();
+    assertThat(dep5).isInstanceOf(Dep5.class);
+  }
+}
diff --git a/javatests/dagger/hilt/android/MultiTestRoot1Test.java b/javatests/dagger/hilt/android/MultiTestRoot1Test.java
new file mode 100644
index 0000000..49c03bc
--- /dev/null
+++ b/javatests/dagger/hilt/android/MultiTestRoot1Test.java
@@ -0,0 +1,307 @@
+/*
+ * Copyright (C) 2020 The Dagger Authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package dagger.hilt.android;
+
+import static androidx.test.core.app.ApplicationProvider.getApplicationContext;
+import static com.google.common.truth.Truth.assertThat;
+import static org.junit.Assert.assertThrows;
+
+import android.os.Build;
+import androidx.activity.ComponentActivity;
+import androidx.test.ext.junit.runners.AndroidJUnit4;
+import dagger.Module;
+import dagger.Provides;
+import dagger.hilt.EntryPoint;
+import dagger.hilt.EntryPoints;
+import dagger.hilt.InstallIn;
+import dagger.hilt.android.components.ActivityComponent;
+import dagger.hilt.android.testing.BindValue;
+import dagger.hilt.android.testing.HiltAndroidRule;
+import dagger.hilt.android.testing.HiltAndroidTest;
+import dagger.hilt.android.testing.HiltTestApplication;
+import dagger.hilt.android.testing.UninstallModules;
+import dagger.hilt.components.SingletonComponent;
+import javax.inject.Inject;
+import javax.inject.Named;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.robolectric.Robolectric;
+import org.robolectric.android.controller.ActivityController;
+import org.robolectric.annotation.Config;
+
+// TODO(bcorso): Support transitively ignoring the @Module.includes of ignored modules?
+// TODO(bcorso): Support including non-test @UninstallModules using @UninstallModules.includes?
+@UninstallModules({
+  MultiTestRootExternalModules.PkgPrivateAppModule.class,
+  MultiTestRootExternalModules.PkgPrivateActivityModule.class
+})
+@HiltAndroidTest
+@RunWith(AndroidJUnit4.class)
+// Robolectric requires Java9 to run API 29 and above, so use API 28 instead
+@Config(sdk = Build.VERSION_CODES.P, application = HiltTestApplication.class)
+public final class MultiTestRoot1Test {
+  private static final int INT_VALUE = 9;
+  private static final String STR_VALUE = "MultiTestRoot1TestValue";
+  private static final long LONG_VALUE = 11L;
+  private static final String REPLACE_EXTERNAL_STR_VALUE = "REPLACED_EXTERNAL_STR_VALUE";
+  private static final long REPLACE_EXTERNAL_LONG_VALUE = 17L;
+  private static final String BIND_VALUE_STRING = "BIND_VALUE_STRING";
+  private static final String TEST_QUALIFIER = "TEST_QUALIFIER";
+
+  @AndroidEntryPoint(ComponentActivity.class)
+  public static class TestActivity extends Hilt_MultiTestRoot1Test_TestActivity {
+    @Inject Baz baz;
+    @Inject @MultiTestRootExternalModules.External Long externalLongValue;
+  }
+
+  @EntryPoint
+  @InstallIn(SingletonComponent.class)
+  public interface BindValueEntryPoint {
+    @Named(TEST_QUALIFIER)
+    String bindValueString();
+  }
+
+  @Module
+  @InstallIn(SingletonComponent.class)
+  public interface ReplaceExternalAppModule {
+    @Provides
+    @MultiTestRootExternalModules.External
+    static String provideString() {
+      return REPLACE_EXTERNAL_STR_VALUE;
+    }
+  }
+
+  @Module
+  @InstallIn(ActivityComponent.class)
+  public interface ReplaceExternalActivityModule {
+    @Provides
+    @MultiTestRootExternalModules.External
+    static Long provideString() {
+      return REPLACE_EXTERNAL_LONG_VALUE;
+    }
+  }
+
+  @Module
+  @InstallIn(ActivityComponent.class)
+  public interface TestActivityModule {
+    @Provides
+    static Baz provideBaz() {
+      return new Baz(LONG_VALUE);
+    }
+  }
+
+  @Module
+  @InstallIn(SingletonComponent.class)
+  interface PkgPrivateTestModule {
+    @Provides
+    static Qux provideQux() {
+      return new Qux();
+    }
+  }
+
+  @Module
+  @InstallIn(SingletonComponent.class)
+  public interface TestModule {
+    @Provides
+    static int provideInt() {
+      return INT_VALUE;
+    }
+
+    @Provides
+    static String provideString() {
+      return STR_VALUE;
+    }
+  }
+
+  public static final class Outer {
+    @Module
+    @InstallIn(SingletonComponent.class)
+    public interface NestedTestModule {
+      @Provides
+      static long provideLong() {
+        return LONG_VALUE;
+      }
+    }
+
+    private Outer() {}
+  }
+
+  static class Foo {
+    final int value;
+
+    @Inject
+    Foo(int value) {
+      this.value = value;
+    }
+  }
+
+  static class Bar {
+    final String value;
+
+    Bar(String value) {
+      this.value = value;
+    }
+  }
+
+  static class Baz {
+    final long value;
+
+    Baz(long value) {
+      this.value = value;
+    }
+  }
+
+  static class Qux {}
+
+  @Module
+  @InstallIn(SingletonComponent.class)
+  public interface BarModule {
+    @Provides
+    static Bar provideBar(String value) {
+      return new Bar(value);
+    }
+  }
+
+  @EntryPoint
+  @InstallIn(SingletonComponent.class)
+  public interface BarEntryPoint {
+    Bar getBar();
+  }
+
+  @EntryPoint
+  @InstallIn(SingletonComponent.class)
+  interface PkgPrivateQuxEntryPoint {
+    Qux getQux();
+  }
+
+  @Rule public HiltAndroidRule rule = new HiltAndroidRule(this);
+
+  @Inject Foo foo;
+  @Inject Qux qux;
+  @Inject Long longValue;
+  @Inject @MultiTestRootExternalModules.External String externalStrValue;
+
+  @BindValue
+  @Named(TEST_QUALIFIER)
+  String bindValueString = BIND_VALUE_STRING;
+
+  @Test
+  public void testInjectFromTestModule() throws Exception {
+    assertThat(foo).isNull();
+    setupComponent();
+    assertThat(foo).isNotNull();
+    assertThat(foo.value).isEqualTo(INT_VALUE);
+  }
+
+  @Test
+  public void testInjectFromNestedTestModule() throws Exception {
+    assertThat(longValue).isNull();
+    setupComponent();
+    assertThat(longValue).isNotNull();
+    assertThat(longValue).isEqualTo(LONG_VALUE);
+  }
+
+  @Test
+  public void testInjectFromExternalAppModule() throws Exception {
+    assertThat(externalStrValue).isNull();
+    setupComponent();
+    assertThat(externalStrValue).isNotNull();
+    assertThat(externalStrValue).isEqualTo(REPLACE_EXTERNAL_STR_VALUE);
+    assertThat(externalStrValue).isNotEqualTo(MultiTestRootExternalModules.EXTERNAL_STR_VALUE);
+  }
+
+  @Test
+  public void testInjectFromExternalActivityModule() throws Exception {
+    setupComponent();
+    ActivityController<TestActivity> ac = Robolectric.buildActivity(TestActivity.class);
+    assertThat(ac.get().externalLongValue).isNull();
+    ac.create();
+    assertThat(ac.get().externalLongValue).isNotNull();
+    assertThat(ac.get().externalLongValue).isEqualTo(REPLACE_EXTERNAL_LONG_VALUE);
+    assertThat(ac.get().externalLongValue)
+        .isNotEqualTo(MultiTestRootExternalModules.EXTERNAL_LONG_VALUE);
+  }
+
+  @Test
+  public void testInjectFromPkgPrivateTestModule() throws Exception {
+    assertThat(qux).isNull();
+    setupComponent();
+    assertThat(qux).isNotNull();
+  }
+
+  @Test
+  public void testLocalEntryPoint() throws Exception {
+    setupComponent();
+    Bar bar = EntryPoints.get(getApplicationContext(), BarEntryPoint.class).getBar();
+    assertThat(bar).isNotNull();
+    assertThat(bar.value).isEqualTo(STR_VALUE);
+  }
+
+  @Test
+  public void testLocalPkgPrivateEntryPoint() throws Exception {
+    setupComponent();
+    Qux qux = EntryPoints.get(getApplicationContext(), PkgPrivateQuxEntryPoint.class).getQux();
+    assertThat(qux).isNotNull();
+  }
+
+  @Test
+  public void testAndroidEntryPoint() throws Exception {
+    setupComponent();
+    ActivityController<TestActivity> ac = Robolectric.buildActivity(TestActivity.class);
+    assertThat(ac.get().baz).isNull();
+    ac.create();
+    assertThat(ac.get().baz).isNotNull();
+    assertThat(ac.get().baz.value).isEqualTo(LONG_VALUE);
+  }
+
+  @Test
+  public void testMissingMultiTestRoot2EntryPoint() throws Exception {
+    setupComponent();
+    ClassCastException exception =
+        assertThrows(
+            ClassCastException.class,
+            () -> EntryPoints.get(getApplicationContext(), MultiTestRoot2Test.BarEntryPoint.class));
+    assertThat(exception)
+          .hasMessageThat()
+          .isEqualTo(
+              "Cannot cast dagger.hilt.android.DaggerMultiTestRoot1Test_HiltComponents_SingletonC"
+              + " to dagger.hilt.android.MultiTestRoot2Test$BarEntryPoint");
+  }
+
+  @Test
+  public void testBindValueFieldIsProvided() throws Exception {
+    setupComponent();
+    assertThat(bindValueString).isEqualTo(BIND_VALUE_STRING);
+    assertThat(getBinding()).isEqualTo(BIND_VALUE_STRING);
+  }
+
+  @Test
+  public void testBindValueIsMutable() throws Exception {
+    setupComponent();
+    bindValueString = "newValue";
+    assertThat(getBinding()).isEqualTo("newValue");
+  }
+
+  void setupComponent() {
+    rule.inject();
+  }
+
+  private static String getBinding() {
+    return EntryPoints.get(getApplicationContext(), BindValueEntryPoint.class).bindValueString();
+  }
+}
diff --git a/javatests/dagger/hilt/android/MultiTestRoot2Test.java b/javatests/dagger/hilt/android/MultiTestRoot2Test.java
new file mode 100644
index 0000000..39aecfa
--- /dev/null
+++ b/javatests/dagger/hilt/android/MultiTestRoot2Test.java
@@ -0,0 +1,281 @@
+/*
+ * Copyright (C) 2020 The Dagger Authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package dagger.hilt.android;
+
+import static androidx.test.core.app.ApplicationProvider.getApplicationContext;
+import static com.google.common.truth.Truth.assertThat;
+import static org.junit.Assert.assertThrows;
+
+import android.os.Build;
+import androidx.activity.ComponentActivity;
+import androidx.test.ext.junit.runners.AndroidJUnit4;
+import dagger.Module;
+import dagger.Provides;
+import dagger.hilt.EntryPoint;
+import dagger.hilt.EntryPoints;
+import dagger.hilt.InstallIn;
+import dagger.hilt.android.components.ActivityComponent;
+import dagger.hilt.android.testing.BindValue;
+import dagger.hilt.android.testing.HiltAndroidRule;
+import dagger.hilt.android.testing.HiltAndroidTest;
+import dagger.hilt.android.testing.HiltTestApplication;
+import dagger.hilt.components.SingletonComponent;
+import javax.inject.Inject;
+import javax.inject.Named;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.robolectric.Robolectric;
+import org.robolectric.android.controller.ActivityController;
+import org.robolectric.annotation.Config;
+
+@HiltAndroidTest
+@RunWith(AndroidJUnit4.class)
+// Robolectric requires Java9 to run API 29 and above, so use API 28 instead
+@Config(sdk = Build.VERSION_CODES.P, application = HiltTestApplication.class)
+public final class MultiTestRoot2Test {
+  private static final int INT_VALUE = 13;
+  private static final String STR_VALUE = "MultiTestRoot2TestValue";
+  private static final long LONG_VALUE = 17L;
+  private static final String BIND_VALUE_STRING = "BIND_VALUE_STRING";
+  private static final String TEST_QUALIFIER = "TEST_QUALIFIER";
+
+  @EntryPoint
+  @InstallIn(SingletonComponent.class)
+  public interface BindValueEntryPoint {
+    @Named(TEST_QUALIFIER)
+    String bindValueString();
+  }
+
+  @AndroidEntryPoint(ComponentActivity.class)
+  public static class TestActivity extends Hilt_MultiTestRoot2Test_TestActivity {
+    @Inject Baz baz;
+    @Inject @MultiTestRootExternalModules.External Long externalLongValue;
+  }
+
+  @Module
+  @InstallIn(ActivityComponent.class)
+  public interface TestActivityModule {
+    @Provides
+    static Baz provideBaz() {
+      return new Baz(LONG_VALUE);
+    }
+  }
+
+  @Module
+  @InstallIn(SingletonComponent.class)
+  interface PkgPrivateTestModule {
+    @Provides
+    static Qux provideQux() {
+      return new Qux();
+    }
+  }
+
+  @Module
+  @InstallIn(SingletonComponent.class)
+  public interface TestModule {
+    @Provides
+    static int provideInt() {
+      return INT_VALUE;
+    }
+
+    @Provides
+    static String provideString() {
+      return STR_VALUE;
+    }
+  }
+
+  public static final class Outer {
+    @Module
+    @InstallIn(SingletonComponent.class)
+    public interface NestedTestModule {
+      @Provides
+      static long provideLong() {
+        return LONG_VALUE;
+      }
+    }
+
+    private Outer() {}
+  }
+
+  static class Foo {
+    final int value;
+
+    @Inject
+    Foo(int value) {
+      this.value = value;
+    }
+  }
+
+  static class Bar {
+    final String value;
+
+    Bar(String value) {
+      this.value = value;
+    }
+  }
+
+  static class Baz {
+    final long value;
+
+    Baz(long value) {
+      this.value = value;
+    }
+  }
+
+  static class Qux {}
+
+  @Module
+  @InstallIn(SingletonComponent.class)
+  public interface BarModule {
+    @Provides
+    static Bar provideBar(String value) {
+      return new Bar(value);
+    }
+  }
+
+  @EntryPoint
+  @InstallIn(SingletonComponent.class)
+  public interface BarEntryPoint {
+    Bar getBar();
+  }
+
+  @EntryPoint
+  @InstallIn(SingletonComponent.class)
+  interface PkgPrivateQuxEntryPoint {
+    Qux getQux();
+  }
+
+  @Rule public HiltAndroidRule rule = new HiltAndroidRule(this);
+
+  @Inject Foo foo;
+  @Inject Qux qux;
+  @Inject String str;
+  @Inject Long longValue;
+  @Inject @MultiTestRootExternalModules.External String externalStrValue;
+
+  @BindValue
+  @Named(TEST_QUALIFIER)
+  String bindValueString = BIND_VALUE_STRING;
+
+  @Test
+  public void testInjectFromTestModule() throws Exception {
+    assertThat(foo).isNull();
+    rule.inject();
+    assertThat(foo).isNotNull();
+    assertThat(foo.value).isEqualTo(INT_VALUE);
+  }
+
+  @Test
+  public void testInjectFromTestModuleWithArgs() throws Exception {
+    assertThat(str).isNull();
+    rule.inject();
+    assertThat(str).isNotNull();
+    assertThat(str).isEqualTo(STR_VALUE);
+  }
+
+  @Test
+  public void testInjectFromNestedTestModule() throws Exception {
+    assertThat(longValue).isNull();
+    rule.inject();
+    assertThat(longValue).isNotNull();
+    assertThat(longValue).isEqualTo(LONG_VALUE);
+  }
+
+  @Test
+  public void testInjectFromPkgPrivateTestModule() throws Exception {
+    assertThat(qux).isNull();
+    rule.inject();
+    assertThat(qux).isNotNull();
+  }
+
+  @Test
+  public void testInjectFromExternalAppModule() throws Exception {
+    assertThat(externalStrValue).isNull();
+    rule.inject();
+    assertThat(externalStrValue).isNotNull();
+    assertThat(externalStrValue).isEqualTo(MultiTestRootExternalModules.EXTERNAL_STR_VALUE);
+  }
+
+  @Test
+  public void testInjectFromExternalActivityModule() throws Exception {
+    rule.inject();
+    ActivityController<TestActivity> ac = Robolectric.buildActivity(TestActivity.class);
+    assertThat(ac.get().externalLongValue).isNull();
+    ac.create();
+    assertThat(ac.get().externalLongValue).isNotNull();
+    assertThat(ac.get().externalLongValue)
+        .isEqualTo(MultiTestRootExternalModules.EXTERNAL_LONG_VALUE);
+  }
+
+  @Test
+  public void testLocalEntryPoint() throws Exception {
+    rule.inject();
+    Bar bar = EntryPoints.get(getApplicationContext(), BarEntryPoint.class).getBar();
+    assertThat(bar).isNotNull();
+    assertThat(bar.value).isEqualTo(STR_VALUE);
+  }
+
+  @Test
+  public void testLocalPkgPrivateEntryPoint() throws Exception {
+    rule.inject();
+    Qux qux = EntryPoints.get(getApplicationContext(), PkgPrivateQuxEntryPoint.class).getQux();
+    assertThat(qux).isNotNull();
+  }
+
+  @Test
+  public void testAndroidEntryPoint() throws Exception {
+    rule.inject();
+    ActivityController<TestActivity> ac = Robolectric.buildActivity(TestActivity.class);
+    assertThat(ac.get().baz).isNull();
+    ac.create();
+    assertThat(ac.get().baz).isNotNull();
+    assertThat(ac.get().baz.value).isEqualTo(LONG_VALUE);
+  }
+
+  @Test
+  public void testMissingMultiTestRoot1EntryPoint() throws Exception {
+    rule.inject();
+    ClassCastException exception =
+        assertThrows(
+            ClassCastException.class,
+            () -> EntryPoints.get(getApplicationContext(), MultiTestRoot1Test.BarEntryPoint.class));
+    assertThat(exception)
+          .hasMessageThat()
+          .isEqualTo(
+              "Cannot cast dagger.hilt.android.DaggerMultiTestRoot2Test_HiltComponents_SingletonC"
+              + " to dagger.hilt.android.MultiTestRoot1Test$BarEntryPoint");
+  }
+
+  @Test
+  public void testBindValueFieldIsProvided() throws Exception {
+    rule.inject();
+    assertThat(bindValueString).isEqualTo(BIND_VALUE_STRING);
+    assertThat(getBinding()).isEqualTo(BIND_VALUE_STRING);
+  }
+
+  @Test
+  public void testBindValueIsMutable() throws Exception {
+    rule.inject();
+    bindValueString = "newValue";
+    assertThat(getBinding()).isEqualTo("newValue");
+  }
+
+  private static String getBinding() {
+    return EntryPoints.get(getApplicationContext(), BindValueEntryPoint.class).bindValueString();
+  }
+}
diff --git a/javatests/dagger/hilt/android/MultiTestRootExternalModules.java b/javatests/dagger/hilt/android/MultiTestRootExternalModules.java
new file mode 100644
index 0000000..5d2c7f8
--- /dev/null
+++ b/javatests/dagger/hilt/android/MultiTestRootExternalModules.java
@@ -0,0 +1,54 @@
+/*
+ * Copyright (C) 2020 The Dagger Authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package dagger.hilt.android;
+
+import dagger.Module;
+import dagger.Provides;
+import dagger.hilt.InstallIn;
+import dagger.hilt.android.components.ActivityComponent;
+import dagger.hilt.components.SingletonComponent;
+import javax.inject.Qualifier;
+
+public final class MultiTestRootExternalModules {
+  static final long EXTERNAL_LONG_VALUE = 43L;
+  static final String EXTERNAL_STR_VALUE = "EXTERNAL_STRING_VALUE";
+
+  @Qualifier
+  @interface External {}
+
+  @Module
+  @InstallIn(SingletonComponent.class)
+  interface PkgPrivateAppModule {
+    @Provides
+    @External
+    static String provideStringValue() {
+      return EXTERNAL_STR_VALUE;
+    }
+  }
+
+  @Module
+  @InstallIn(ActivityComponent.class)
+  interface PkgPrivateActivityModule {
+    @Provides
+    @External
+    static Long provideLongValue() {
+      return EXTERNAL_LONG_VALUE;
+    }
+  }
+
+  private MultiTestRootExternalModules() {}
+}
diff --git a/java/dagger/hilt/android/internal/testing/TestApplicationInjector.java b/javatests/dagger/hilt/android/QualifierInFieldsClass.kt
similarity index 60%
copy from java/dagger/hilt/android/internal/testing/TestApplicationInjector.java
copy to javatests/dagger/hilt/android/QualifierInFieldsClass.kt
index c7ff5c9..ec3f6ff 100644
--- a/java/dagger/hilt/android/internal/testing/TestApplicationInjector.java
+++ b/javatests/dagger/hilt/android/QualifierInFieldsClass.kt
@@ -14,11 +14,19 @@
  * limitations under the License.
  */
 
-package dagger.hilt.android.internal.testing;
+package dagger.hilt.android
 
-/**
- * Interface to expose a method for members injection for use in tests.
- */
-public interface TestApplicationInjector<T> {
-  void injectApp(T t);
+import android.content.Context
+import dagger.hilt.android.qualifiers.ActivityContext
+import dagger.hilt.android.qualifiers.ApplicationContext
+import javax.inject.Inject
+
+class QualifierInFieldsClass @Inject constructor() {
+  @Inject
+  @ApplicationContext
+  lateinit var appContext: Context
+
+  @Inject
+  @ActivityContext
+  lateinit var activityContext: Context
 }
diff --git a/javatests/dagger/hilt/android/QualifierInKotlinFieldsTest.java b/javatests/dagger/hilt/android/QualifierInKotlinFieldsTest.java
new file mode 100644
index 0000000..8e62b99
--- /dev/null
+++ b/javatests/dagger/hilt/android/QualifierInKotlinFieldsTest.java
@@ -0,0 +1,61 @@
+/*
+ * Copyright (C) 2020 The Dagger Authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package dagger.hilt.android;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import android.os.Build;
+import androidx.fragment.app.FragmentActivity;
+import androidx.test.core.app.ActivityScenario;
+import androidx.test.ext.junit.runners.AndroidJUnit4;
+import dagger.hilt.android.testing.HiltAndroidRule;
+import dagger.hilt.android.testing.HiltAndroidTest;
+import dagger.hilt.android.testing.HiltTestApplication;
+import javax.inject.Inject;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.robolectric.annotation.Config;
+
+@HiltAndroidTest
+@RunWith(AndroidJUnit4.class)
+// Robolectric requires Java9 to run API 29 and above, so use API 28 instead
+@Config(sdk = Build.VERSION_CODES.P, application = HiltTestApplication.class)
+public class QualifierInKotlinFieldsTest {
+
+  @Rule public final HiltAndroidRule rule = new HiltAndroidRule(this);
+
+  @Test
+  public void activityFactory() {
+    try (ActivityScenario<TestActivity> scenario = ActivityScenario.launch(TestActivity.class)) {
+      scenario.onActivity(
+          activity -> {
+            assertThat(activity.object.appContext).isNotNull();
+            assertThat(activity.object.activityContext).isNotNull();
+          });
+    }
+  }
+
+  // This test activity injects a class that is defined in Kotlin because we want to test the
+  // qualifiers in Kotlin fields / properties (generated getters and setter with backing field).
+  // Ideally we would write this test in Kotlin, but there is no open fule for writting android
+  // local tests in Kotlin.
+  @AndroidEntryPoint(FragmentActivity.class)
+  public static final class TestActivity extends Hilt_QualifierInKotlinFieldsTest_TestActivity {
+    @Inject QualifierInFieldsClass object;
+  }
+}
diff --git a/javatests/dagger/hilt/android/TestInstallInModules.java b/javatests/dagger/hilt/android/TestInstallInModules.java
new file mode 100644
index 0000000..0affa73
--- /dev/null
+++ b/javatests/dagger/hilt/android/TestInstallInModules.java
@@ -0,0 +1,38 @@
+/*
+ * Copyright (C) 2021 The Dagger Authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package dagger.hilt.android;
+
+import dagger.Module;
+import dagger.Provides;
+import dagger.hilt.android.UsesComponentTestClasses.UsesComponentQualifier;
+import dagger.hilt.components.SingletonComponent;
+import dagger.hilt.testing.TestInstallIn;
+
+/** Replaces a production binding in tests. */
+final class TestInstallInModules {
+  private TestInstallInModules() {}
+
+  @Module
+  @TestInstallIn(components = SingletonComponent.class, replaces = UsesComponentTestModule.class)
+  interface TestInstallInModule {
+    @Provides
+    @UsesComponentQualifier
+    static String provideLocalString() {
+      return "test_install_in_string";
+    }
+  }
+}
diff --git a/javatests/dagger/hilt/android/UsesComponentHelper.java b/javatests/dagger/hilt/android/UsesComponentHelper.java
new file mode 100644
index 0000000..179bf71
--- /dev/null
+++ b/javatests/dagger/hilt/android/UsesComponentHelper.java
@@ -0,0 +1,51 @@
+/*
+ * Copyright (C) 2021 The Dagger Authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package dagger.hilt.android;
+
+/**
+ * Utility methods for tests that verify which generated component is used to inject the test class.
+ */
+public abstract class UsesComponentHelper {
+
+  public static String defaultComponentName() {
+    return "dagger.hilt.android.internal.testing.root.DaggerDefault_HiltComponents_SingletonC";
+  }
+
+  /**
+   * Returns the name of a component that cannot use the default component. Does not handle deduping
+   * if test class names clash.
+   */
+  public static String perTestComponentName(Object testInstance) {
+    return "dagger.hilt.android.internal.testing.root.Dagger"
+        + testInstance.getClass().getSimpleName()
+        + "_HiltComponents_SingletonC";
+  }
+
+  /**
+   * Returns the name of a component that cannot use the default component, including the expected
+   * prefix applied by Hilt to dedupe clashing class names.
+   */
+  public static String perTestComponentNameWithDedupePrefix(
+      String expectedPrefix, Object testInstance) {
+    return "dagger.hilt.android.internal.testing.root.Dagger"
+        + expectedPrefix
+        + testInstance.getClass().getSimpleName()
+        + "_HiltComponents_SingletonC";
+  }
+
+  private UsesComponentHelper() {}
+}
diff --git a/javatests/dagger/hilt/android/UsesComponentTestClasses.java b/javatests/dagger/hilt/android/UsesComponentTestClasses.java
new file mode 100644
index 0000000..9445f2a
--- /dev/null
+++ b/javatests/dagger/hilt/android/UsesComponentTestClasses.java
@@ -0,0 +1,78 @@
+/*
+ * Copyright (C) 2021 The Dagger Authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package dagger.hilt.android;
+
+import static java.lang.annotation.RetentionPolicy.RUNTIME;
+
+import dagger.BindsInstance;
+import dagger.hilt.DefineComponent;
+import dagger.hilt.EntryPoint;
+import dagger.hilt.InstallIn;
+import dagger.hilt.components.SingletonComponent;
+import java.lang.annotation.Retention;
+import javax.inject.Inject;
+import javax.inject.Qualifier;
+import javax.inject.Scope;
+
+/**
+ * Subcomponent used to verify that subcomponents are correctly installed in shared test components.
+ */
+public abstract class UsesComponentTestClasses {
+  /** Qualifier for test bindings. */
+  @Qualifier
+  public @interface UsesComponentQualifier {}
+
+  @UsesComponentTestSubcomponentScoped
+  public static class Foo {
+    final int id;
+
+    @Inject
+    Foo(int id) {
+      this.id = id;
+    }
+  }
+
+  @Scope
+  @Retention(RUNTIME)
+  public @interface UsesComponentTestSubcomponentScoped {}
+
+  @UsesComponentTestSubcomponentScoped
+  @DefineComponent(parent = SingletonComponent.class)
+  public interface UsesComponentTestSubcomponent {
+    @DefineComponent.Builder
+    interface Builder {
+      @BindsInstance
+      Builder id(int id);
+
+      UsesComponentTestSubcomponent build();
+    }
+  }
+
+  @EntryPoint
+  @InstallIn(SingletonComponent.class)
+  public interface UsesComponentTestSubcomponentBuilderEntryPoint {
+    UsesComponentTestSubcomponent.Builder mySubcomponentBuilder();
+  }
+
+  @EntryPoint
+  @InstallIn(UsesComponentTestSubcomponent.class)
+  public interface FooEntryPoint {
+    Foo foo();
+  }
+
+  private UsesComponentTestClasses() {}
+}
diff --git a/javatests/dagger/hilt/android/UsesComponentTestModule.java b/javatests/dagger/hilt/android/UsesComponentTestModule.java
new file mode 100644
index 0000000..2b16a1c
--- /dev/null
+++ b/javatests/dagger/hilt/android/UsesComponentTestModule.java
@@ -0,0 +1,37 @@
+/*
+ * Copyright (C) 2021 The Dagger Authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package dagger.hilt.android;
+
+import dagger.Module;
+import dagger.Provides;
+import dagger.hilt.InstallIn;
+import dagger.hilt.android.UsesComponentTestClasses.UsesComponentQualifier;
+import dagger.hilt.components.SingletonComponent;
+
+/** Module for shared test component tests. */
+@Module
+@InstallIn(SingletonComponent.class)
+final class UsesComponentTestModule {
+
+  @Provides
+  @UsesComponentQualifier
+  static String provideString() {
+    return "shared_string";
+  }
+
+  private UsesComponentTestModule() {}
+}
diff --git a/javatests/dagger/hilt/android/UsesLocalComponentTestBindingsTest.java b/javatests/dagger/hilt/android/UsesLocalComponentTestBindingsTest.java
new file mode 100644
index 0000000..6c878e7
--- /dev/null
+++ b/javatests/dagger/hilt/android/UsesLocalComponentTestBindingsTest.java
@@ -0,0 +1,110 @@
+/*
+ * Copyright (C) 2021 The Dagger Authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package dagger.hilt.android;
+
+import static androidx.test.core.app.ApplicationProvider.getApplicationContext;
+import static com.google.common.truth.Truth.assertThat;
+
+import android.os.Build;
+import androidx.test.ext.junit.runners.AndroidJUnit4;
+import dagger.Module;
+import dagger.Provides;
+import dagger.hilt.EntryPoints;
+import dagger.hilt.InstallIn;
+import dagger.hilt.android.UsesComponentTestClasses.Foo;
+import dagger.hilt.android.UsesComponentTestClasses.FooEntryPoint;
+import dagger.hilt.android.UsesComponentTestClasses.UsesComponentQualifier;
+import dagger.hilt.android.UsesComponentTestClasses.UsesComponentTestSubcomponent;
+import dagger.hilt.android.UsesComponentTestClasses.UsesComponentTestSubcomponentBuilderEntryPoint;
+import dagger.hilt.android.internal.testing.TestApplicationComponentManager;
+import dagger.hilt.android.testing.HiltAndroidRule;
+import dagger.hilt.android.testing.HiltAndroidTest;
+import dagger.hilt.android.testing.HiltTestApplication;
+import dagger.hilt.components.SingletonComponent;
+import javax.inject.Inject;
+import javax.inject.Qualifier;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.robolectric.annotation.Config;
+
+/**
+ * A test that provides its own test bindings, and therefore cannot use the shared components.
+ *
+ * <p>Note that this test class exactly matches the simple name of {@link
+ * dagger.hilt.android.testsubpackage.UsesLocalComponentTestBindingsTest}. This is intentional and
+ * used to verify generated code class names do not clash.
+ */
+@HiltAndroidTest
+@RunWith(AndroidJUnit4.class)
+// Robolectric requires Java9 to run API 29 and above, so use API 28 instead
+@Config(sdk = Build.VERSION_CODES.P, application = HiltTestApplication.class)
+public final class UsesLocalComponentTestBindingsTest {
+
+  @Rule public HiltAndroidRule rule = new HiltAndroidRule(this);
+
+  @Qualifier
+  @interface TestQualifier {}
+
+  @Inject @UsesComponentQualifier String injectedString;
+  @Inject @TestQualifier String localString;
+
+  @Module
+  @InstallIn(SingletonComponent.class)
+  public static final class TestModule {
+    @Provides
+    @TestQualifier
+    static String provideString() {
+      return "local_string";
+    }
+
+    private TestModule() {}
+  }
+
+  @Test
+  public void testInject() {
+    rule.inject();
+    assertThat(injectedString).isEqualTo("shared_string");
+    assertThat(localString).isEqualTo("local_string");
+  }
+
+  @Test
+  public void testSubcomponent() {
+    UsesComponentTestSubcomponent subcomponent =
+        EntryPoints.get(
+                getApplicationContext(), UsesComponentTestSubcomponentBuilderEntryPoint.class)
+            .mySubcomponentBuilder()
+            .id(123)
+            .build();
+
+    Foo foo1 = EntryPoints.get(subcomponent, FooEntryPoint.class).foo();
+    Foo foo2 = EntryPoints.get(subcomponent, FooEntryPoint.class).foo();
+
+    assertThat(foo1).isNotNull();
+    assertThat(foo2).isNotNull();
+    assertThat(foo1).isSameInstanceAs(foo2);
+  }
+
+  @Test
+  public void testUsesLocalComponent() {
+    HiltTestApplication app = (HiltTestApplication) getApplicationContext();
+    Object generatedComponent =
+        ((TestApplicationComponentManager) app.componentManager()).generatedComponent();
+    assertThat(generatedComponent.getClass().getName())
+        .isEqualTo(UsesComponentHelper.perTestComponentNameWithDedupePrefix("dha_", this));
+  }
+}
diff --git a/javatests/dagger/hilt/android/UsesLocalComponentUninstallModuleTest.java b/javatests/dagger/hilt/android/UsesLocalComponentUninstallModuleTest.java
new file mode 100644
index 0000000..624ba83
--- /dev/null
+++ b/javatests/dagger/hilt/android/UsesLocalComponentUninstallModuleTest.java
@@ -0,0 +1,98 @@
+/*
+ * Copyright (C) 2021 The Dagger Authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package dagger.hilt.android;
+
+import static androidx.test.core.app.ApplicationProvider.getApplicationContext;
+import static com.google.common.truth.Truth.assertThat;
+
+import android.os.Build;
+import androidx.test.ext.junit.runners.AndroidJUnit4;
+import dagger.Module;
+import dagger.Provides;
+import dagger.hilt.EntryPoints;
+import dagger.hilt.InstallIn;
+import dagger.hilt.android.UsesComponentTestClasses.Foo;
+import dagger.hilt.android.UsesComponentTestClasses.FooEntryPoint;
+import dagger.hilt.android.UsesComponentTestClasses.UsesComponentQualifier;
+import dagger.hilt.android.UsesComponentTestClasses.UsesComponentTestSubcomponent;
+import dagger.hilt.android.UsesComponentTestClasses.UsesComponentTestSubcomponentBuilderEntryPoint;
+import dagger.hilt.android.internal.testing.TestApplicationComponentManager;
+import dagger.hilt.android.testing.HiltAndroidRule;
+import dagger.hilt.android.testing.HiltAndroidTest;
+import dagger.hilt.android.testing.HiltTestApplication;
+import dagger.hilt.android.testing.UninstallModules;
+import dagger.hilt.components.SingletonComponent;
+import javax.inject.Inject;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.robolectric.annotation.Config;
+
+@HiltAndroidTest
+@RunWith(AndroidJUnit4.class)
+// Robolectric requires Java9 to run API 29 and above, so use API 28 instead
+@Config(sdk = Build.VERSION_CODES.P, application = HiltTestApplication.class)
+@UninstallModules(UsesComponentTestModule.class)
+public final class UsesLocalComponentUninstallModuleTest {
+
+  @Rule public HiltAndroidRule rule = new HiltAndroidRule(this);
+
+  @Inject @UsesComponentQualifier String injectedString;
+
+  @Module
+  @InstallIn(SingletonComponent.class)
+  public interface LocalTestModule {
+
+    @Provides
+    @UsesComponentQualifier
+    static String provideString() {
+      return "local_string";
+    }
+  }
+
+  @Test
+  public void testInject() {
+    rule.inject();
+    assertThat(injectedString).isEqualTo("local_string");
+  }
+
+  @Test
+  public void testSubcomponent() {
+    UsesComponentTestSubcomponent subcomponent =
+        EntryPoints.get(
+                getApplicationContext(), UsesComponentTestSubcomponentBuilderEntryPoint.class)
+            .mySubcomponentBuilder()
+            .id(123)
+            .build();
+
+    Foo foo1 = EntryPoints.get(subcomponent, FooEntryPoint.class).foo();
+    Foo foo2 = EntryPoints.get(subcomponent, FooEntryPoint.class).foo();
+
+    assertThat(foo1).isNotNull();
+    assertThat(foo2).isNotNull();
+    assertThat(foo1).isSameInstanceAs(foo2);
+  }
+
+  @Test
+  public void testUsesLocalComponent() {
+    HiltTestApplication app = (HiltTestApplication) getApplicationContext();
+    Object generatedComponent =
+        ((TestApplicationComponentManager) app.componentManager()).generatedComponent();
+    assertThat(generatedComponent.getClass().getName())
+        .isEqualTo(UsesComponentHelper.perTestComponentName(this));
+  }
+}
diff --git a/javatests/dagger/hilt/android/UsesSharedComponent1Test.java b/javatests/dagger/hilt/android/UsesSharedComponent1Test.java
new file mode 100644
index 0000000..9b8f287
--- /dev/null
+++ b/javatests/dagger/hilt/android/UsesSharedComponent1Test.java
@@ -0,0 +1,113 @@
+/*
+ * Copyright (C) 2021 The Dagger Authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package dagger.hilt.android;
+
+import static androidx.test.core.app.ApplicationProvider.getApplicationContext;
+import static com.google.common.truth.Truth.assertThat;
+import static org.junit.Assert.assertThrows;
+
+import android.os.Build;
+import androidx.fragment.app.FragmentActivity;
+import androidx.test.core.app.ActivityScenario;
+import androidx.test.ext.junit.runners.AndroidJUnit4;
+import dagger.hilt.EntryPoints;
+import dagger.hilt.android.UsesComponentTestClasses.Foo;
+import dagger.hilt.android.UsesComponentTestClasses.FooEntryPoint;
+import dagger.hilt.android.UsesComponentTestClasses.UsesComponentQualifier;
+import dagger.hilt.android.UsesComponentTestClasses.UsesComponentTestSubcomponent;
+import dagger.hilt.android.UsesComponentTestClasses.UsesComponentTestSubcomponentBuilderEntryPoint;
+import dagger.hilt.android.internal.testing.TestApplicationComponentManager;
+import dagger.hilt.android.testing.HiltAndroidRule;
+import dagger.hilt.android.testing.HiltAndroidTest;
+import dagger.hilt.android.testing.HiltTestApplication;
+import javax.inject.Inject;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.robolectric.annotation.Config;
+
+/**
+ * A test that provides none of its own test bindings, and therefore uses the shared components.
+ *
+ * <p>Note that this test class exactly matches the simple name of {@link
+ * dagger.hilt.android.testsubpackage.UsesSharedComponent1Test}. This is intentional and used to
+ * verify generated code class names do not clash.
+ */
+@HiltAndroidTest
+@RunWith(AndroidJUnit4.class)
+// Robolectric requires Java9 to run API 29 and above, so use API 28 instead
+@Config(sdk = Build.VERSION_CODES.P, application = HiltTestApplication.class)
+public final class UsesSharedComponent1Test {
+
+  @Rule public HiltAndroidRule rule = new HiltAndroidRule(this);
+
+  @Inject @UsesComponentQualifier String injectedString;
+
+  @Test
+  public void testInject() {
+    rule.inject();
+    assertThat(injectedString).isEqualTo("shared_string");
+  }
+
+  @Test
+  public void testSubcomponent() {
+    UsesComponentTestSubcomponent subcomponent =
+        EntryPoints.get(
+                getApplicationContext(), UsesComponentTestSubcomponentBuilderEntryPoint.class)
+            .mySubcomponentBuilder()
+            .id(123)
+            .build();
+
+    Foo foo1 = EntryPoints.get(subcomponent, FooEntryPoint.class).foo();
+    Foo foo2 = EntryPoints.get(subcomponent, FooEntryPoint.class).foo();
+
+    assertThat(foo1).isNotNull();
+    assertThat(foo2).isNotNull();
+    assertThat(foo1).isSameInstanceAs(foo2);
+  }
+
+  @Test
+  public void testActivity() {
+    try (ActivityScenario<TestActivity> scenario = ActivityScenario.launch(TestActivity.class)) {
+      scenario.onActivity(
+          activity -> {
+            assertThat(activity.activityString).isEqualTo("shared_string");
+          });
+    }
+  }
+
+  @Test
+  public void testUsesSharedComponent() {
+    HiltTestApplication app = (HiltTestApplication) getApplicationContext();
+    Object generatedComponent =
+        ((TestApplicationComponentManager) app.componentManager()).generatedComponent();
+    assertThat(generatedComponent.getClass().getName())
+        .isEqualTo(UsesComponentHelper.defaultComponentName());
+  }
+
+  @Test
+  public void testLocalComponentNotGenerated() {
+    assertThrows(
+        ClassNotFoundException.class,
+        () -> Class.forName(UsesComponentHelper.perTestComponentName(this)));
+  }
+
+  @AndroidEntryPoint(FragmentActivity.class)
+  public static final class TestActivity extends Hilt_UsesSharedComponent1Test_TestActivity {
+    @Inject @UsesComponentQualifier String activityString;
+  }
+}
diff --git a/javatests/dagger/hilt/android/UsesSharedComponent2Test.java b/javatests/dagger/hilt/android/UsesSharedComponent2Test.java
new file mode 100644
index 0000000..d3b247d
--- /dev/null
+++ b/javatests/dagger/hilt/android/UsesSharedComponent2Test.java
@@ -0,0 +1,111 @@
+/*
+ * Copyright (C) 2021 The Dagger Authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package dagger.hilt.android;
+
+import static androidx.test.core.app.ApplicationProvider.getApplicationContext;
+import static com.google.common.truth.Truth.assertThat;
+import static org.junit.Assert.assertThrows;
+
+import android.os.Build;
+import androidx.fragment.app.FragmentActivity;
+import androidx.test.core.app.ActivityScenario;
+import androidx.test.ext.junit.runners.AndroidJUnit4;
+import dagger.hilt.EntryPoints;
+import dagger.hilt.android.UsesComponentTestClasses.Foo;
+import dagger.hilt.android.UsesComponentTestClasses.FooEntryPoint;
+import dagger.hilt.android.UsesComponentTestClasses.UsesComponentQualifier;
+import dagger.hilt.android.UsesComponentTestClasses.UsesComponentTestSubcomponent;
+import dagger.hilt.android.UsesComponentTestClasses.UsesComponentTestSubcomponentBuilderEntryPoint;
+import dagger.hilt.android.internal.testing.TestApplicationComponentManager;
+import dagger.hilt.android.testing.HiltAndroidRule;
+import dagger.hilt.android.testing.HiltAndroidTest;
+import dagger.hilt.android.testing.HiltTestApplication;
+import javax.inject.Inject;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.robolectric.annotation.Config;
+
+/**
+ * A test that provides none of its own test bindings, and therefore uses the shared components. A
+ * duplicate of {@link SharedComponent1Test} to test that multiple test roots with shared components
+ * can be compiled in the same target.
+ */
+@HiltAndroidTest
+@RunWith(AndroidJUnit4.class)
+// Robolectric requires Java9 to run API 29 and above, so use API 28 instead
+@Config(sdk = Build.VERSION_CODES.P, application = HiltTestApplication.class)
+public final class UsesSharedComponent2Test {
+
+  @Rule public HiltAndroidRule rule = new HiltAndroidRule(this);
+
+  @Inject @UsesComponentQualifier String injectedString;
+
+  @Test
+  public void testInject() {
+    rule.inject();
+    assertThat(injectedString).isEqualTo("shared_string");
+  }
+
+  @Test
+  public void testSubcomponent() {
+    UsesComponentTestSubcomponent subcomponent =
+        EntryPoints.get(
+                getApplicationContext(), UsesComponentTestSubcomponentBuilderEntryPoint.class)
+            .mySubcomponentBuilder()
+            .id(123)
+            .build();
+
+    Foo foo1 = EntryPoints.get(subcomponent, FooEntryPoint.class).foo();
+    Foo foo2 = EntryPoints.get(subcomponent, FooEntryPoint.class).foo();
+
+    assertThat(foo1).isNotNull();
+    assertThat(foo2).isNotNull();
+    assertThat(foo1).isSameInstanceAs(foo2);
+  }
+
+  @Test
+  public void testActivity() {
+    try (ActivityScenario<TestActivity> scenario = ActivityScenario.launch(TestActivity.class)) {
+      scenario.onActivity(
+          activity -> {
+            assertThat(activity.activityString).isEqualTo("shared_string");
+          });
+    }
+  }
+
+  @Test
+  public void testUsesSharedComponent() {
+    HiltTestApplication app = (HiltTestApplication) getApplicationContext();
+    Object generatedComponent =
+        ((TestApplicationComponentManager) app.componentManager()).generatedComponent();
+    assertThat(generatedComponent.getClass().getName())
+        .isEqualTo(UsesComponentHelper.defaultComponentName());
+  }
+
+  @Test
+  public void testLocalComponentNotGenerated() {
+    assertThrows(
+        ClassNotFoundException.class,
+        () -> Class.forName(UsesComponentHelper.perTestComponentName(this)));
+  }
+
+  @AndroidEntryPoint(FragmentActivity.class)
+  public static final class TestActivity extends Hilt_UsesSharedComponent2Test_TestActivity {
+    @Inject @UsesComponentQualifier String activityString;
+  }
+}
diff --git a/javatests/dagger/hilt/android/UsesSharedComponentEnclosedTest.java b/javatests/dagger/hilt/android/UsesSharedComponentEnclosedTest.java
new file mode 100644
index 0000000..60ef2fc
--- /dev/null
+++ b/javatests/dagger/hilt/android/UsesSharedComponentEnclosedTest.java
@@ -0,0 +1,118 @@
+/*
+ * Copyright (C) 2021 The Dagger Authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package dagger.hilt.android;
+
+import static androidx.test.core.app.ApplicationProvider.getApplicationContext;
+import static com.google.common.truth.Truth.assertThat;
+import static org.junit.Assert.assertThrows;
+
+import android.os.Build;
+import androidx.fragment.app.FragmentActivity;
+import androidx.test.core.app.ActivityScenario;
+import androidx.test.ext.junit.runners.AndroidJUnit4;
+import dagger.hilt.EntryPoints;
+import dagger.hilt.android.UsesComponentTestClasses.Foo;
+import dagger.hilt.android.UsesComponentTestClasses.FooEntryPoint;
+import dagger.hilt.android.UsesComponentTestClasses.UsesComponentQualifier;
+import dagger.hilt.android.UsesComponentTestClasses.UsesComponentTestSubcomponent;
+import dagger.hilt.android.UsesComponentTestClasses.UsesComponentTestSubcomponentBuilderEntryPoint;
+import dagger.hilt.android.internal.testing.TestApplicationComponentManager;
+import dagger.hilt.android.testing.HiltAndroidRule;
+import dagger.hilt.android.testing.HiltAndroidTest;
+import dagger.hilt.android.testing.HiltTestApplication;
+import javax.inject.Inject;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.experimental.runners.Enclosed;
+import org.junit.runner.RunWith;
+import org.robolectric.annotation.Config;
+
+/**
+ * A test that uses the {@link Enclosed} test runner and provides none of its own test bindings, and
+ * therefore uses the shared components.
+ */
+@RunWith(Enclosed.class)
+// Robolectric requires Java9 to run API 29 and above, so use API 28 instead
+@Config(sdk = Build.VERSION_CODES.P, application = HiltTestApplication.class)
+public final class UsesSharedComponentEnclosedTest {
+
+  @HiltAndroidTest
+  @RunWith(AndroidJUnit4.class)
+  // Robolectric requires Java9 to run API 29 and above, so use API 28 instead
+  @Config(sdk = Build.VERSION_CODES.P, application = HiltTestApplication.class)
+  public static final class EnclosedTest {
+
+    @Rule public HiltAndroidRule rule = new HiltAndroidRule(this);
+
+    @Inject @UsesComponentQualifier String injectedString;
+
+    @Test
+    public void testInject() {
+      rule.inject();
+      assertThat(injectedString).isEqualTo("shared_string");
+    }
+
+    @Test
+    public void testSubcomponent() {
+      UsesComponentTestSubcomponent subcomponent =
+          EntryPoints.get(
+                  getApplicationContext(), UsesComponentTestSubcomponentBuilderEntryPoint.class)
+              .mySubcomponentBuilder()
+              .id(123)
+              .build();
+
+      Foo foo1 = EntryPoints.get(subcomponent, FooEntryPoint.class).foo();
+      Foo foo2 = EntryPoints.get(subcomponent, FooEntryPoint.class).foo();
+
+      assertThat(foo1).isNotNull();
+      assertThat(foo2).isNotNull();
+      assertThat(foo1).isSameInstanceAs(foo2);
+    }
+
+    @Test
+    public void testActivity() {
+      try (ActivityScenario<TestActivity> scenario = ActivityScenario.launch(TestActivity.class)) {
+        scenario.onActivity(
+            activity -> {
+              assertThat(activity.activityString).isEqualTo("shared_string");
+            });
+      }
+    }
+
+    @Test
+    public void testUsesSharedComponent() {
+      HiltTestApplication app = (HiltTestApplication) getApplicationContext();
+      Object generatedComponent =
+          ((TestApplicationComponentManager) app.componentManager()).generatedComponent();
+      assertThat(generatedComponent.getClass().getName())
+          .isEqualTo(UsesComponentHelper.defaultComponentName());
+    }
+
+    @Test
+    public void testLocalComponentNotGenerated() {
+      assertThrows(
+          ClassNotFoundException.class,
+          () -> Class.forName(UsesComponentHelper.perTestComponentName(this)));
+    }
+
+    @AndroidEntryPoint(FragmentActivity.class)
+    public static final class TestActivity
+        extends Hilt_UsesSharedComponentEnclosedTest_EnclosedTest_TestActivity {
+      @Inject @UsesComponentQualifier String activityString;
+    }
+  }
+}
diff --git a/javatests/dagger/hilt/android/UsesSharedComponentTestInstallInTest.java b/javatests/dagger/hilt/android/UsesSharedComponentTestInstallInTest.java
new file mode 100644
index 0000000..8de562b
--- /dev/null
+++ b/javatests/dagger/hilt/android/UsesSharedComponentTestInstallInTest.java
@@ -0,0 +1,67 @@
+/*
+ * Copyright (C) 2021 The Dagger Authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package dagger.hilt.android;
+
+import static androidx.test.core.app.ApplicationProvider.getApplicationContext;
+import static com.google.common.truth.Truth.assertThat;
+import static org.junit.Assert.assertThrows;
+
+import android.os.Build;
+import androidx.test.ext.junit.runners.AndroidJUnit4;
+import dagger.hilt.android.UsesComponentTestClasses.UsesComponentQualifier;
+import dagger.hilt.android.internal.testing.TestApplicationComponentManager;
+import dagger.hilt.android.testing.HiltAndroidRule;
+import dagger.hilt.android.testing.HiltAndroidTest;
+import dagger.hilt.android.testing.HiltTestApplication;
+import javax.inject.Inject;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.robolectric.annotation.Config;
+
+@HiltAndroidTest
+@RunWith(AndroidJUnit4.class)
+// Robolectric requires Java9 to run API 29 and above, so use API 28 instead
+@Config(sdk = Build.VERSION_CODES.P, application = HiltTestApplication.class)
+public final class UsesSharedComponentTestInstallInTest {
+
+  @Rule public HiltAndroidRule rule = new HiltAndroidRule(this);
+
+  @Inject @UsesComponentQualifier String injectedString;
+
+  @Test
+  public void testInject() {
+    rule.inject();
+    assertThat(injectedString).isEqualTo("test_install_in_string");
+  }
+
+  @Test
+  public void testUsesLocalComponent() {
+    HiltTestApplication app = (HiltTestApplication) getApplicationContext();
+    Object generatedComponent =
+        ((TestApplicationComponentManager) app.componentManager()).generatedComponent();
+    assertThat(generatedComponent.getClass().getName())
+        .isEqualTo(UsesComponentHelper.defaultComponentName());
+  }
+
+  @Test
+  public void testLocalComponentNotGenerated() {
+    assertThrows(
+        ClassNotFoundException.class,
+        () -> Class.forName(UsesComponentHelper.perTestComponentName(this)));
+  }
+}
diff --git a/javatests/dagger/hilt/android/ViewModelScopedTest.java b/javatests/dagger/hilt/android/ViewModelScopedTest.java
new file mode 100644
index 0000000..e37e42a
--- /dev/null
+++ b/javatests/dagger/hilt/android/ViewModelScopedTest.java
@@ -0,0 +1,124 @@
+/*
+ * Copyright (C) 2020 The Dagger Authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package dagger.hilt.android;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import androidx.lifecycle.ViewModel;
+import androidx.lifecycle.ViewModelProvider;
+import android.os.Build;
+import android.os.Bundle;
+import androidx.annotation.Nullable;
+import androidx.fragment.app.Fragment;
+import androidx.fragment.app.FragmentActivity;
+import androidx.test.core.app.ActivityScenario;
+import androidx.test.ext.junit.runners.AndroidJUnit4;
+import dagger.hilt.android.lifecycle.HiltViewModel;
+import dagger.hilt.android.scopes.ViewModelScoped;
+import dagger.hilt.android.testing.HiltAndroidRule;
+import dagger.hilt.android.testing.HiltAndroidTest;
+import dagger.hilt.android.testing.HiltTestApplication;
+import javax.inject.Inject;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.robolectric.annotation.Config;
+
+@HiltAndroidTest
+@RunWith(AndroidJUnit4.class)
+// Robolectric requires Java9 to run API 29 and above, so use API 28 instead
+@Config(sdk = Build.VERSION_CODES.P, application = HiltTestApplication.class)
+public class ViewModelScopedTest {
+
+  @Rule public final HiltAndroidRule rule = new HiltAndroidRule(this);
+
+  @Test
+  public void testViewModelScopeInFragment() {
+    try (ActivityScenario<TestActivity> scenario = ActivityScenario.launch(TestActivity.class)) {
+      scenario.onActivity(
+          activity -> {
+            TestFragment fragment =
+                (TestFragment) activity.getSupportFragmentManager().findFragmentByTag("tag");
+            assertThat(fragment.vm.one.bar).isEqualTo(fragment.vm.two.bar);
+          });
+    }
+  }
+
+  @AndroidEntryPoint(FragmentActivity.class)
+  public static class TestActivity extends Hilt_ViewModelScopedTest_TestActivity {
+
+    @Override
+    protected void onCreate(@Nullable Bundle savedInstanceState) {
+      super.onCreate(savedInstanceState);
+      if (savedInstanceState == null) {
+        Fragment f =
+            getSupportFragmentManager()
+                .getFragmentFactory()
+                .instantiate(TestFragment.class.getClassLoader(), TestFragment.class.getName());
+        getSupportFragmentManager().beginTransaction().add(0, f, "tag").commitNow();
+      }
+    }
+  }
+
+  @AndroidEntryPoint(Fragment.class)
+  public static class TestFragment extends Hilt_ViewModelScopedTest_TestFragment {
+    MyViewModel vm;
+
+    @Override
+    public void onCreate(@Nullable Bundle bundle) {
+      super.onCreate(bundle);
+      vm = new ViewModelProvider(this).get(MyViewModel.class);
+    }
+  }
+
+  @HiltViewModel
+  static class MyViewModel extends ViewModel {
+
+    final DependsOnBarOne one;
+    final DependsOnBarTwo two;
+
+    @Inject
+    MyViewModel(DependsOnBarOne one, DependsOnBarTwo two) {
+      this.one = one;
+      this.two = two;
+    }
+  }
+
+  @ViewModelScoped
+  static class Bar {
+    @Inject
+    Bar() {}
+  }
+
+  static class DependsOnBarOne {
+    final Bar bar;
+
+    @Inject
+    DependsOnBarOne(Bar bar) {
+      this.bar = bar;
+    }
+  }
+
+  static class DependsOnBarTwo {
+    final Bar bar;
+
+    @Inject
+    DependsOnBarTwo(Bar bar) {
+      this.bar = bar;
+    }
+  }
+}
diff --git a/javatests/dagger/hilt/android/ViewModelWithBaseTest.java b/javatests/dagger/hilt/android/ViewModelWithBaseTest.java
new file mode 100644
index 0000000..4e4a47c
--- /dev/null
+++ b/javatests/dagger/hilt/android/ViewModelWithBaseTest.java
@@ -0,0 +1,95 @@
+/*
+ * Copyright (C) 2020 The Dagger Authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package dagger.hilt.android;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import androidx.lifecycle.SavedStateHandle;
+import androidx.lifecycle.ViewModel;
+import androidx.lifecycle.ViewModelProvider;
+import android.os.Build;
+import android.os.Bundle;
+import androidx.fragment.app.FragmentActivity;
+import androidx.annotation.Nullable;
+import androidx.test.core.app.ActivityScenario;
+import androidx.test.ext.junit.runners.AndroidJUnit4;
+import dagger.hilt.android.lifecycle.HiltViewModel;
+import dagger.hilt.android.testing.HiltAndroidRule;
+import dagger.hilt.android.testing.HiltAndroidTest;
+import dagger.hilt.android.testing.HiltTestApplication;
+import javax.inject.Inject;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.robolectric.annotation.Config;
+
+@HiltAndroidTest
+@RunWith(AndroidJUnit4.class)
+// Robolectric requires Java9 to run API 29 and above, so use API 28 instead
+@Config(sdk = Build.VERSION_CODES.P, application = HiltTestApplication.class)
+public class ViewModelWithBaseTest {
+
+  @Rule public final HiltAndroidRule rule = new HiltAndroidRule(this);
+
+  @Test
+  public void verifyBaseInjection() {
+    try (ActivityScenario<TestActivity> scenario = ActivityScenario.launch(TestActivity.class)) {
+      scenario.onActivity(
+          activity -> {
+            assertThat(activity.myViewModel.foo).isNotNull();
+            assertThat(activity.myViewModel.bar).isNotNull();
+          });
+    }
+  }
+
+  @AndroidEntryPoint(FragmentActivity.class)
+  public static class TestActivity extends Hilt_ViewModelWithBaseTest_TestActivity {
+
+    MyViewModel myViewModel;
+
+    @Override
+    public void onCreate(@Nullable Bundle savedInstanceState) {
+      super.onCreate(savedInstanceState);
+      myViewModel = new ViewModelProvider(this).get(MyViewModel.class);
+    }
+  }
+
+  @HiltViewModel
+  static class MyViewModel extends BaseViewModel {
+
+    final Foo foo;
+
+    @Inject
+    MyViewModel(SavedStateHandle handle, Foo foo) {
+      this.foo = foo;
+    }
+  }
+
+  abstract static class BaseViewModel extends ViewModel {
+    @Inject Bar bar;
+  }
+
+  static class Foo {
+    @Inject
+    Foo() {}
+  }
+
+  static class Bar {
+    @Inject
+    Bar() {}
+  }
+}
diff --git a/javatests/dagger/hilt/android/internal/managers/AndroidManifest.xml b/javatests/dagger/hilt/android/internal/managers/AndroidManifest.xml
new file mode 100644
index 0000000..cc5128e
--- /dev/null
+++ b/javatests/dagger/hilt/android/internal/managers/AndroidManifest.xml
@@ -0,0 +1,11 @@
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+    package="dagger.hilt.android.internal.managers">
+
+  <uses-sdk android:minSdkVersion="14" />
+
+  <application>
+    <activity
+        android:name=".FragmentContextWrapperLeakTest$TestActivity"
+        android:exported="false"/>
+  </application>
+</manifest>
diff --git a/javatests/dagger/hilt/android/internal/managers/BUILD b/javatests/dagger/hilt/android/internal/managers/BUILD
new file mode 100644
index 0000000..ff4f432
--- /dev/null
+++ b/javatests/dagger/hilt/android/internal/managers/BUILD
@@ -0,0 +1,39 @@
+# Copyright (C) 2021 The Dagger Authors.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+# Description:
+#   Tests for internal code for implementing Hilt processors.
+
+package(default_visibility = ["//:src"])
+
+android_local_test(
+    name = "FragmentContextWrapperLeakTest",
+    size = "small",
+    srcs = ["FragmentContextWrapperLeakTest.java"],
+    manifest = "AndroidManifest.xml",
+    manifest_values = {
+        "minSdkVersion": "14",
+    },
+    deps = [
+        "//:android_local_test_exports",
+        "//:dagger_with_compiler",
+        "@google_bazel_common//third_party/java/jsr330_inject",
+        "@maven//:junit_junit",
+        "@google_bazel_common//third_party/java/truth",
+        "//java/dagger/hilt:entry_point",
+        "//java/dagger/hilt:install_in",
+        "//java/dagger/hilt/android:android_entry_point",
+        "//java/dagger/hilt/android/lifecycle",
+        "//java/dagger/hilt/android/testing:hilt_android_test",
+    ],
+)
diff --git a/javatests/dagger/hilt/android/internal/managers/FragmentContextWrapperLeakTest.java b/javatests/dagger/hilt/android/internal/managers/FragmentContextWrapperLeakTest.java
new file mode 100644
index 0000000..38086c6
--- /dev/null
+++ b/javatests/dagger/hilt/android/internal/managers/FragmentContextWrapperLeakTest.java
@@ -0,0 +1,74 @@
+/*
+ * Copyright (C) 2021 The Dagger Authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package dagger.hilt.android.internal.managers;
+
+import static com.google.common.truth.Truth.assertThat;
+import static org.junit.Assert.assertThrows;
+
+import androidx.lifecycle.Lifecycle;
+import android.os.Build;
+import androidx.fragment.app.Fragment;
+import androidx.fragment.app.FragmentActivity;
+import androidx.test.core.app.ActivityScenario;
+import androidx.test.ext.junit.runners.AndroidJUnit4;
+import dagger.hilt.android.AndroidEntryPoint;
+import dagger.hilt.android.internal.managers.ViewComponentManager.FragmentContextWrapper;
+import dagger.hilt.android.testing.HiltAndroidRule;
+import dagger.hilt.android.testing.HiltAndroidTest;
+import dagger.hilt.android.testing.HiltTestApplication;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.robolectric.annotation.Config;
+
+@HiltAndroidTest
+@RunWith(AndroidJUnit4.class)
+// Robolectric requires Java9 to run API 29 and above, so use API 28 instead
+@Config(sdk = Build.VERSION_CODES.P, application = HiltTestApplication.class)
+public final class FragmentContextWrapperLeakTest {
+  /** An activity to test injection. */
+  @AndroidEntryPoint(FragmentActivity.class)
+  public static final class TestActivity extends Hilt_FragmentContextWrapperLeakTest_TestActivity {}
+
+  /** A fragment to test injection. */
+  @AndroidEntryPoint(Fragment.class)
+  public static final class TestFragment extends Hilt_FragmentContextWrapperLeakTest_TestFragment {}
+
+  @Rule public HiltAndroidRule hiltRule = new HiltAndroidRule(this);
+
+  @Test
+  public void testFragmentContextWrapperDoesNotLeakFragment() {
+    try (ActivityScenario<TestActivity> scenario = ActivityScenario.launch(TestActivity.class)) {
+      TestFragment fragment = new TestFragment();
+      scenario.onActivity(
+          activity ->
+              activity
+                  .getSupportFragmentManager()
+                  .beginTransaction()
+                  .add(fragment, "TestFragment")
+                  .commitNow());
+
+      FragmentContextWrapper fragmentContextWrapper =
+          (FragmentContextWrapper) fragment.getContext();
+      assertThat(fragmentContextWrapper.getFragment()).isEqualTo(fragment);
+      scenario.moveToState(Lifecycle.State.DESTROYED);
+      NullPointerException exception =
+          assertThrows(NullPointerException.class, fragmentContextWrapper::getFragment);
+      assertThat(exception).hasMessageThat().contains("The fragment has already been destroyed");
+    }
+  }
+}
diff --git a/javatests/dagger/hilt/android/processor/AndroidCompilers.java b/javatests/dagger/hilt/android/processor/AndroidCompilers.java
index 5ce0a2a..1fb19ae 100644
--- a/javatests/dagger/hilt/android/processor/AndroidCompilers.java
+++ b/javatests/dagger/hilt/android/processor/AndroidCompilers.java
@@ -22,12 +22,13 @@
 import com.google.testing.compile.Compiler;
 import dagger.hilt.android.processor.internal.androidentrypoint.AndroidEntryPointProcessor;
 import dagger.hilt.android.processor.internal.customtestapplication.CustomTestApplicationProcessor;
-import dagger.hilt.android.processor.internal.uninstallmodules.UninstallModulesProcessor;
 import dagger.hilt.processor.internal.aggregateddeps.AggregatedDepsProcessor;
 import dagger.hilt.processor.internal.definecomponent.DefineComponentProcessor;
+import dagger.hilt.processor.internal.earlyentrypoint.EarlyEntryPointProcessor;
 import dagger.hilt.processor.internal.generatesrootinput.GeneratesRootInputProcessor;
 import dagger.hilt.processor.internal.originatingelement.OriginatingElementProcessor;
 import dagger.hilt.processor.internal.root.RootProcessor;
+import dagger.hilt.processor.internal.uninstallmodules.UninstallModulesProcessor;
 import dagger.internal.codegen.ComponentProcessor;
 import dagger.testing.compile.CompilerTests;
 import java.util.Arrays;
@@ -68,6 +69,7 @@
         new AndroidEntryPointProcessor(),
         new ComponentProcessor(),
         new DefineComponentProcessor(),
+        new EarlyEntryPointProcessor(),
         new GeneratesRootInputProcessor(),
         new OriginatingElementProcessor(),
         new CustomTestApplicationProcessor(),
diff --git a/javatests/dagger/hilt/android/processor/BUILD b/javatests/dagger/hilt/android/processor/BUILD
index 7fb2320..4ec4d56 100644
--- a/javatests/dagger/hilt/android/processor/BUILD
+++ b/javatests/dagger/hilt/android/processor/BUILD
@@ -22,13 +22,13 @@
     deps = [
         "//java/dagger/hilt/android/processor/internal/androidentrypoint:processor_lib",
         "//java/dagger/hilt/android/processor/internal/customtestapplication:processor_lib",
-        "//java/dagger/hilt/android/processor/internal/uninstallmodules:processor_lib",
-        "//java/dagger/hilt/android/processor/internal/viewmodel:processor_lib",
         "//java/dagger/hilt/processor/internal/aggregateddeps:processor_lib",
         "//java/dagger/hilt/processor/internal/definecomponent:processor_lib",
+        "//java/dagger/hilt/processor/internal/earlyentrypoint:processor_lib",
         "//java/dagger/hilt/processor/internal/generatesrootinput:processor_lib",
         "//java/dagger/hilt/processor/internal/originatingelement:processor_lib",
         "//java/dagger/hilt/processor/internal/root:processor_lib",
+        "//java/dagger/hilt/processor/internal/uninstallmodules:processor_lib",
         "//java/dagger/internal/codegen:processor",
         "//java/dagger/internal/guava:collect",
         "//java/dagger/testing/compile",
diff --git a/javatests/dagger/hilt/android/processor/internal/BUILD b/javatests/dagger/hilt/android/processor/internal/BUILD
new file mode 100644
index 0000000..9a0a8f2
--- /dev/null
+++ b/javatests/dagger/hilt/android/processor/internal/BUILD
@@ -0,0 +1,36 @@
+# Copyright (C) 2020 The Dagger Authors.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+# Description:
+#   Tests for internal code for implementing Hilt processors.
+
+load("//java/dagger/testing/compile:macros.bzl", "compiler_test")
+
+package(default_visibility = ["//:src"])
+
+compiler_test(
+    name = "GeneratorsTest",
+    srcs = ["GeneratorsTest.java"],
+    compiler_deps = [
+        "//java/dagger/hilt/android:hilt_android_app",
+        "//java/dagger/hilt/android:android_entry_point",
+        "@androidsdk//:platforms/android-30/android.jar",
+        "@maven//:androidx_annotation_annotation",
+    ],
+    deps = [
+        "@google_bazel_common//third_party/java/compile_testing",
+        "@google_bazel_common//third_party/java/junit",
+        "@google_bazel_common//third_party/java/truth",
+        "//javatests/dagger/hilt/android/processor:android_compilers",
+    ],
+)
diff --git a/javatests/dagger/hilt/android/processor/internal/GeneratorsTest.java b/javatests/dagger/hilt/android/processor/internal/GeneratorsTest.java
new file mode 100644
index 0000000..ecfd1f6
--- /dev/null
+++ b/javatests/dagger/hilt/android/processor/internal/GeneratorsTest.java
@@ -0,0 +1,318 @@
+/*
+ * Copyright (C) 2020 The Dagger Authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package dagger.hilt.android.processor.internal;
+
+import static com.google.testing.compile.CompilationSubject.assertThat;
+import static dagger.hilt.android.processor.AndroidCompilers.compiler;
+
+import com.google.testing.compile.Compilation;
+import com.google.testing.compile.JavaFileObjects;
+import javax.tools.JavaFileObject;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.JUnit4;
+
+@RunWith(JUnit4.class)
+public final class GeneratorsTest {
+
+  @Test
+  public void copyConstructorParametersCopiesExternalNullables() {
+    JavaFileObject baseActivity =
+        JavaFileObjects.forSourceLines(
+            "test.BaseActivity",
+            "package test;",
+            "",
+            "import androidx.fragment.app.FragmentActivity;",
+            "",
+            "public abstract class BaseActivity extends FragmentActivity {",
+            "  protected BaseActivity(",
+            "      @androidx.annotation.Nullable String supportNullable,",
+            "      @androidx.annotation.Nullable String androidxNullable,",
+            "      @javax.annotation.Nullable String javaxNullable) { }",
+            "}");
+    JavaFileObject myActivity =
+        JavaFileObjects.forSourceLines(
+            "test.MyActivity",
+            "package test;",
+            "",
+            "import dagger.hilt.android.AndroidEntryPoint;",
+            "",
+            "@AndroidEntryPoint(BaseActivity.class)",
+            "public class MyActivity extends Hilt_MyActivity {",
+            "  public MyActivity(",
+            "      String supportNullable,",
+            "      String androidxNullable,",
+            "      String javaxNullable) {",
+            "    super(supportNullable, androidxNullable, javaxNullable);",
+            "  }",
+            "}");
+    Compilation compilation = compiler().compile(baseActivity, myActivity);
+    assertThat(compilation).succeeded();
+    assertThat(compilation)
+        .generatedSourceFile("test/Hilt_MyActivity")
+        .containsElementsIn(
+            JavaFileObjects.forSourceLines(
+                "test.Hilt_MyActivity",
+                "package test;",
+                "",
+                "import androidx.annotation.Nullable;",
+                "",
+                "@Generated(\"dagger.hilt.android.processor.internal.androidentrypoint.ActivityGenerator\")",
+                "abstract class Hilt_MyActivity extends BaseActivity implements",
+                "    GeneratedComponentManagerHolder {",
+                "  Hilt_MyActivity(",
+                "      @Nullable String supportNullable,",
+                "      @Nullable String androidxNullable,",
+                "      @javax.annotation.Nullable String javaxNullable) {",
+                "    super(supportNullable, androidxNullable, javaxNullable);",
+                "    _initHiltInternal();",
+                "  }",
+                "}"));
+  }
+
+  @Test
+  public void copyConstructorParametersConvertsAndroidInternalNullableToExternal() {
+    // Relies on View(Context, AttributeSet), which has android-internal
+    // @android.annotation.Nullable on AttributeSet.
+    JavaFileObject myView =
+        JavaFileObjects.forSourceLines(
+            "test.MyView",
+            "package test;",
+            "",
+            "import android.content.Context;",
+            "import android.util.AttributeSet;",
+            "import android.view.View;",
+            "import dagger.hilt.android.AndroidEntryPoint;",
+            "",
+            "@AndroidEntryPoint(View.class)",
+            "public class MyView extends Hilt_MyView {",
+            "  public MyView(Context context, AttributeSet attrs) {",
+            "    super(context, attrs);",
+            "  }",
+            "}");
+    Compilation compilation = compiler().compile(myView);
+    assertThat(compilation).succeeded();
+    assertThat(compilation)
+        .generatedSourceFile("test/Hilt_MyView")
+        .containsElementsIn(
+            JavaFileObjects.forSourceLines(
+                "test.Hilt_MyView",
+                "package test;",
+                "",
+                "@Generated(\"dagger.hilt.android.processor.internal.androidentrypoint.ViewGenerator\")",
+                "abstract class Hilt_MyView extends View implements",
+                "GeneratedComponentManagerHolder {",
+                // The generated parameter names are copied from the base class. Since we only have
+                // the jar and not the source for these base classes the parameter names are missing
+                "  Hilt_MyView(Context arg0, @Nullable AttributeSet arg1) {",
+                "    super(arg0, arg1);",
+                "    inject();",
+                "  }",
+                "}"));
+  }
+
+  @Test
+  public void copyTargetApiAnnotationActivity() {
+    JavaFileObject myActivity =
+        JavaFileObjects.forSourceLines(
+            "test.MyActivity",
+            "package test;",
+            "",
+            "import android.annotation.TargetApi;",
+            "import androidx.fragment.app.FragmentActivity;",
+            "import dagger.hilt.android.AndroidEntryPoint;",
+            "",
+            "@TargetApi(24)",
+            "@AndroidEntryPoint(FragmentActivity.class)",
+            "public class MyActivity extends Hilt_MyActivity {}");
+    Compilation compilation = compiler().compile(myActivity);
+    assertThat(compilation).succeeded();
+    assertThat(compilation)
+        .generatedSourceFile("test/Hilt_MyActivity")
+        .containsElementsIn(
+            JavaFileObjects.forSourceLines(
+                "test.Hilt_MyActivity",
+                " package test;",
+                "",
+                "@Generated(\"dagger.hilt.android.processor.internal.androidentrypoint.ActivityGenerator\")",
+                "@TargetApi(24)",
+                "abstract class Hilt_MyActivity extends FragmentActivity ",
+                "implements GeneratedComponentManagerHolder {",
+                "}"));
+  }
+
+  @Test
+  public void copyTargetApiAnnotationOverView() {
+    JavaFileObject myView =
+        JavaFileObjects.forSourceLines(
+            "test.MyView",
+            "package test;",
+            "",
+            "import android.annotation.TargetApi;",
+            "import android.widget.LinearLayout;",
+            "import android.content.Context;",
+            "import android.util.AttributeSet;",
+            "import dagger.hilt.android.AndroidEntryPoint;",
+            "",
+            "@TargetApi(24)",
+            "@AndroidEntryPoint(LinearLayout.class)",
+            "public class MyView extends Hilt_MyView {",
+            " public MyView(Context context, AttributeSet attributeSet){",
+            "   super(context, attributeSet);",
+            " }",
+            "",
+            "}");
+    Compilation compilation = compiler().compile(myView);
+    assertThat(compilation).succeeded();
+    assertThat(compilation)
+        .generatedSourceFile("test/Hilt_MyView")
+        .containsElementsIn(
+            JavaFileObjects.forSourceLines(
+                "test.Hilt_MyView",
+                "",
+                "package test;",
+                "",
+                "@Generated(\"dagger.hilt.android.processor.internal.androidentrypoint.ViewGenerator\")",
+                "@TargetApi(24)",
+                "abstract class Hilt_MyView extends LinearLayout implements"
+                    + " GeneratedComponentManagerHolder {",
+                "}"));
+  }
+
+  @Test
+  public void copyTargetApiAnnotationApplication() {
+    JavaFileObject myApplication =
+        JavaFileObjects.forSourceLines(
+            "test.MyApplication",
+            "package test;",
+            "",
+            "import android.annotation.TargetApi;",
+            "import android.app.Application;",
+            "import dagger.hilt.android.HiltAndroidApp;",
+            "",
+            "@TargetApi(24)",
+            "@HiltAndroidApp(Application.class)",
+            "public class MyApplication extends Hilt_MyApplication {}");
+    Compilation compilation = compiler().compile(myApplication);
+    assertThat(compilation).succeeded();
+    assertThat(compilation)
+        .generatedSourceFile("test/Hilt_MyApplication")
+        .containsElementsIn(
+            JavaFileObjects.forSourceLines(
+                "test.Hilt_MyApplication",
+                " package test;",
+                "",
+                "@Generated(\"dagger.hilt.android.processor.internal.androidentrypoint.ApplicationGenerator\")",
+                "@TargetApi(24)",
+                "abstract class Hilt_MyApplication extends Application implements"
+                    + " GeneratedComponentManagerHolder {}"));
+  }
+
+  @Test
+  public void copyTargetApiAnnotationFragment() {
+    JavaFileObject myApplication =
+        JavaFileObjects.forSourceLines(
+            "test.MyFragment",
+            "package test;",
+            "",
+            "import android.annotation.TargetApi;",
+            "import androidx.fragment.app.Fragment;",
+            "import dagger.hilt.android.AndroidEntryPoint;",
+            "",
+            "@TargetApi(24)",
+            "@AndroidEntryPoint(Fragment.class)",
+            "public class MyFragment extends Hilt_MyFragment {}");
+    Compilation compilation = compiler().compile(myApplication);
+    assertThat(compilation).succeeded();
+    assertThat(compilation)
+        .generatedSourceFile("test/Hilt_MyFragment")
+        .containsElementsIn(
+            JavaFileObjects.forSourceLines(
+                "test.Hilt_MyFragment",
+                "package test;",
+                "",
+                "@Generated(\"dagger.hilt.android.processor.internal.androidentrypoint.FragmentGenerator\")",
+                "@TargetApi(24)",
+                "@SuppressWarnings(\"deprecation\")",
+                "abstract class Hilt_MyFragment extends Fragment implements"
+                    + " GeneratedComponentManagerHolder {}"));
+  }
+
+  @Test
+  public void copyTargetApiBroadcastRecieverGenerator() {
+    JavaFileObject myBroadcastReceiver =
+        JavaFileObjects.forSourceLines(
+            "test.MyBroadcastReceiver",
+            "package test;",
+            "",
+            "import android.content.BroadcastReceiver;",
+            "import android.annotation.TargetApi;",
+            "import dagger.hilt.android.AndroidEntryPoint;",
+            "",
+            "@TargetApi(24)",
+            "@AndroidEntryPoint(BroadcastReceiver.class)",
+            "public class MyBroadcastReceiver extends Hilt_MyBroadcastReceiver {}");
+    Compilation compilation = compiler().compile(myBroadcastReceiver);
+    assertThat(compilation).succeeded();
+    assertThat(compilation)
+        .generatedSourceFile("test/Hilt_MyBroadcastReceiver")
+        .containsElementsIn(
+            JavaFileObjects.forSourceLines(
+                "test.Hilt_MyBroadcastReceiver",
+                "package test;",
+                "",
+                "@Generated(\"dagger.hilt.android.processor.internal.androidentrypoint.BroadcastReceiverGenerator\")",
+                "@TargetApi(24)",
+                "abstract class Hilt_MyBroadcastReceiver extends BroadcastReceiver {}"));
+  }
+
+  @Test
+  public void copyTargetApiServiceGenerator() {
+    JavaFileObject myService =
+        JavaFileObjects.forSourceLines(
+            "test.MyService",
+            "package test;",
+            "",
+            "import android.annotation.TargetApi;",
+            "import android.content.Intent;",
+            "import android.app.Service;",
+            "import android.os.IBinder;",
+            "import dagger.hilt.android.AndroidEntryPoint;",
+            "",
+            "@TargetApi(24)",
+            "@AndroidEntryPoint(Service.class)",
+            "public class MyService extends Hilt_MyService {",
+            "   @Override",
+            "   public IBinder onBind(Intent intent){",
+            "     return null;",
+            "   }",
+            "}");
+    Compilation compilation = compiler().compile(myService);
+    assertThat(compilation).succeeded();
+    assertThat(compilation)
+        .generatedSourceFile("test/Hilt_MyService")
+        .containsElementsIn(
+            JavaFileObjects.forSourceLines(
+                "test.Hilt_MyService",
+                "package test;",
+                "",
+                "@Generated(\"dagger.hilt.android.processor.internal.androidentrypoint.ServiceGenerator\")",
+                "@TargetApi(24)",
+                "abstract class Hilt_MyService extends Service implements"
+                    + " GeneratedComponentManagerHolder{}"));
+  }
+}
diff --git a/javatests/dagger/hilt/android/processor/internal/aggregateddeps/BUILD b/javatests/dagger/hilt/android/processor/internal/aggregateddeps/BUILD
index bf2ed4c..654b573 100644
--- a/javatests/dagger/hilt/android/processor/internal/aggregateddeps/BUILD
+++ b/javatests/dagger/hilt/android/processor/internal/aggregateddeps/BUILD
@@ -40,6 +40,25 @@
     ],
 )
 
+compiler_test(
+    name = "EarlyEntryPointProcessorTest",
+    srcs = ["EarlyEntryPointProcessorTest.java"],
+    compiler_deps = [
+        "//java/dagger/hilt:entry_point",
+        "//java/dagger/hilt:install_in",
+        "//java/dagger/hilt/android:early_entry_point",
+        "//java/dagger/hilt/android/components",
+        "//java/dagger/hilt/android/testing:hilt_android_test",
+        "@androidsdk//:platforms/android-30/android.jar",
+    ],
+    deps = [
+        "@google_bazel_common//third_party/java/compile_testing",
+        "@google_bazel_common//third_party/java/junit",
+        "@google_bazel_common//third_party/java/truth",
+        "//javatests/dagger/hilt/android/processor:android_compilers",
+    ],
+)
+
 java_library(
     name = "InstallInModule",
     srcs = ["InstallInModule.java"],
diff --git a/javatests/dagger/hilt/android/processor/internal/aggregateddeps/EarlyEntryPointProcessorTest.java b/javatests/dagger/hilt/android/processor/internal/aggregateddeps/EarlyEntryPointProcessorTest.java
new file mode 100644
index 0000000..3bf3a31
--- /dev/null
+++ b/javatests/dagger/hilt/android/processor/internal/aggregateddeps/EarlyEntryPointProcessorTest.java
@@ -0,0 +1,116 @@
+/*
+ * Copyright (C) 2021 The Dagger Authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package dagger.hilt.android.processor.internal.aggregateddeps;
+
+import static com.google.testing.compile.CompilationSubject.assertThat;
+import static dagger.hilt.android.processor.AndroidCompilers.compiler;
+
+import com.google.testing.compile.Compilation;
+import com.google.testing.compile.JavaFileObjects;
+import javax.tools.JavaFileObject;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.JUnit4;
+
+@RunWith(JUnit4.class)
+public class EarlyEntryPointProcessorTest {
+
+  @Test
+  public void testUsedWithEntryPoint_fails() {
+    JavaFileObject entryPoint =
+        JavaFileObjects.forSourceLines(
+            "test.UsedWithEntryPoint",
+            "package test;",
+            "",
+            "import dagger.hilt.android.EarlyEntryPoint;",
+            "import dagger.hilt.EntryPoint;",
+            "import dagger.hilt.InstallIn;",
+            "import dagger.hilt.components.SingletonComponent;",
+            "",
+            "@EarlyEntryPoint",
+            "@EntryPoint",
+            "@InstallIn(SingletonComponent.class)",
+            "public interface UsedWithEntryPoint {}");
+    Compilation compilation = compiler().compile(entryPoint);
+
+    assertThat(compilation).failed();
+    assertThat(compilation)
+        .hadErrorContaining(
+            "Only one of the following annotations can be used on test.UsedWithEntryPoint: "
+                + "[dagger.hilt.EntryPoint, dagger.hilt.android.EarlyEntryPoint]")
+        .inFile(entryPoint)
+        .onLine(11);
+  }
+
+  @Test
+  public void testNotSingletonComponent_fails() {
+    JavaFileObject entryPoint =
+        JavaFileObjects.forSourceLines(
+            "test.NotSingletonComponent",
+            "package test;",
+            "",
+            "import dagger.hilt.android.EarlyEntryPoint;",
+            "import dagger.hilt.android.components.ActivityComponent;",
+            "import dagger.hilt.EntryPoint;",
+            "import dagger.hilt.InstallIn;",
+            "",
+            "@EarlyEntryPoint",
+            "@InstallIn(ActivityComponent.class)",
+            "public interface NotSingletonComponent {}");
+
+    Compilation compilation = compiler().compile(entryPoint);
+
+    assertThat(compilation).failed();
+    assertThat(compilation)
+        .hadErrorContaining(
+            "@EarlyEntryPoint can only be installed into the SingletonComponent. "
+                + "Found: [dagger.hilt.android.components.ActivityComponent]")
+        .inFile(entryPoint)
+        .onLine(10);
+  }
+
+  @Test
+  public void testThatTestInstallInCannotOriginateFromTest() {
+    JavaFileObject test =
+        JavaFileObjects.forSourceLines(
+            "test.MyTest",
+            "package test;",
+            "",
+            "import dagger.hilt.EntryPoint;",
+            "import dagger.hilt.InstallIn;",
+            "import dagger.hilt.android.EarlyEntryPoint;",
+            "import dagger.hilt.android.testing.HiltAndroidTest;",
+            "import dagger.hilt.components.SingletonComponent;",
+            "",
+            "@HiltAndroidTest",
+            "public class MyTest {",
+            "  @EarlyEntryPoint",
+            "  @InstallIn(SingletonComponent.class)",
+            "  interface NestedEarlyEntryPoint {}",
+            "}");
+    Compilation compilation = compiler().compile(test);
+    assertThat(compilation).failed();
+    assertThat(compilation).hadErrorCount(1);
+    assertThat(compilation)
+        .hadErrorContaining(
+            "@EarlyEntryPoint-annotated entry point, test.MyTest.NestedEarlyEntryPoint, cannot "
+                + "be nested in (or originate from) a @HiltAndroidTest-annotated class, "
+                + "test.MyTest.")
+        .inFile(test)
+        .onLine(13);
+  }
+}
diff --git a/javatests/dagger/hilt/android/processor/internal/customtestapplication/BUILD b/javatests/dagger/hilt/android/processor/internal/customtestapplication/BUILD
new file mode 100644
index 0000000..6fc39e4
--- /dev/null
+++ b/javatests/dagger/hilt/android/processor/internal/customtestapplication/BUILD
@@ -0,0 +1,36 @@
+# Copyright (C) 2020 The Dagger Authors.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+# Description:
+#   Tests for internal code for implementing Hilt processors.
+
+load("//java/dagger/testing/compile:macros.bzl", "compiler_test")
+
+package(default_visibility = ["//:src"])
+
+compiler_test(
+    name = "CustomTestApplicationProcessorTest",
+    srcs = ["CustomTestApplicationProcessorTest.java"],
+    compiler_deps = [
+        "//java/dagger/hilt/android/testing:custom_test_application",
+        "//java/dagger/hilt/android/testing:hilt_android_test",
+        "//java/dagger/hilt/android:hilt_android_app",
+        "@androidsdk//:platforms/android-30/android.jar",
+    ],
+    deps = [
+        "@google_bazel_common//third_party/java/compile_testing",
+        "@google_bazel_common//third_party/java/junit",
+        "@google_bazel_common//third_party/java/truth",
+        "//javatests/dagger/hilt/android/processor:android_compilers",
+    ],
+)
diff --git a/javatests/dagger/hilt/android/processor/internal/customtestapplication/CustomTestApplicationProcessorTest.java b/javatests/dagger/hilt/android/processor/internal/customtestapplication/CustomTestApplicationProcessorTest.java
new file mode 100644
index 0000000..f0372ab
--- /dev/null
+++ b/javatests/dagger/hilt/android/processor/internal/customtestapplication/CustomTestApplicationProcessorTest.java
@@ -0,0 +1,334 @@
+/*
+ * Copyright (C) 2020 The Dagger Authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package dagger.hilt.android.processor.internal.customtestapplication;
+
+import static com.google.testing.compile.CompilationSubject.assertThat;
+import static dagger.hilt.android.processor.AndroidCompilers.compiler;
+
+import com.google.testing.compile.Compilation;
+import com.google.testing.compile.JavaFileObjects;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.JUnit4;
+
+@RunWith(JUnit4.class)
+public class CustomTestApplicationProcessorTest {
+
+  @Test
+  public void validBaseClass_succeeds() {
+    Compilation compilation =
+        compiler().compile(
+            JavaFileObjects.forSourceLines(
+                "test.HiltTest",
+                "package test;",
+                "",
+                "import android.app.Application;",
+                "import dagger.hilt.android.testing.CustomTestApplication;",
+                "import dagger.hilt.android.testing.HiltAndroidTest;",
+                "",
+                "@CustomTestApplication(Application.class)",
+                "@HiltAndroidTest",
+                "public class HiltTest {}"));
+
+    assertThat(compilation).succeeded();
+  }
+
+  @Test
+  public void incorrectBaseType_fails() {
+    Compilation compilation =
+        compiler().compile(
+            JavaFileObjects.forSourceLines(
+                "test.Foo",
+                "package test;",
+                "",
+                "public class Foo {}"),
+            JavaFileObjects.forSourceLines(
+                "test.HiltTest",
+                "package test;",
+                "",
+                "import dagger.hilt.android.testing.CustomTestApplication;",
+                "",
+                "@CustomTestApplication(Foo.class)",
+                "public class HiltTest {}"));
+
+    assertThat(compilation).failed();
+    assertThat(compilation)
+        .hadErrorContaining(
+            "@CustomTestApplication value should be an instance of android.app.Application. "
+                + "Found: test.Foo");
+  }
+
+  @Test
+  public void baseWithHiltAndroidApp_fails() {
+    Compilation compilation =
+        compiler().compile(
+            JavaFileObjects.forSourceLines(
+                "test.BaseApplication",
+                "package test;",
+                "",
+                "import android.app.Application;",
+                "import dagger.hilt.android.HiltAndroidApp;",
+                "",
+                "@HiltAndroidApp(Application.class)",
+                "public class BaseApplication extends Hilt_BaseApplication {}"),
+            JavaFileObjects.forSourceLines(
+                "test.HiltTest",
+                "package test;",
+                "",
+                "import dagger.hilt.android.testing.CustomTestApplication;",
+                "",
+                "@CustomTestApplication(BaseApplication.class)",
+                "public class HiltTest {}"));
+
+    assertThat(compilation).failed();
+    assertThat(compilation)
+        .hadErrorContaining(
+            "@CustomTestApplication value cannot be annotated with @HiltAndroidApp. "
+                + "Found: test.BaseApplication");
+  }
+
+  @Test
+  public void superclassWithHiltAndroidApp_fails() {
+    Compilation compilation =
+        compiler().compile(
+            JavaFileObjects.forSourceLines(
+                "test.BaseApplication",
+                "package test;",
+                "",
+                "import android.app.Application;",
+                "import dagger.hilt.android.HiltAndroidApp;",
+                "",
+                "@HiltAndroidApp(Application.class)",
+                "public class BaseApplication extends Hilt_BaseApplication {}"),
+            JavaFileObjects.forSourceLines(
+                "test.ParentApplication",
+                "package test;",
+                "",
+                "public class ParentApplication extends BaseApplication {}"),
+            JavaFileObjects.forSourceLines(
+                "test.HiltTest",
+                "package test;",
+                "",
+                "import dagger.hilt.android.testing.CustomTestApplication;",
+                "",
+                "@CustomTestApplication(ParentApplication.class)",
+                "public class HiltTest {}"));
+
+    assertThat(compilation).failed();
+    assertThat(compilation)
+        .hadErrorContaining(
+            "@CustomTestApplication value cannot be annotated with @HiltAndroidApp. "
+                + "Found: test.BaseApplication");
+  }
+
+  @Test
+  public void withInjectField_fails() {
+    Compilation compilation =
+        compiler().compile(
+            JavaFileObjects.forSourceLines(
+                "test.BaseApplication",
+                "package test;",
+                "",
+                "import android.app.Application;",
+                "import javax.inject.Inject;",
+                "",
+                "public class BaseApplication extends Application {",
+                "  @Inject String str;",
+                "}"),
+            JavaFileObjects.forSourceLines(
+                "test.HiltTest",
+                "package test;",
+                "",
+                "import dagger.hilt.android.testing.CustomTestApplication;",
+                "",
+                "@CustomTestApplication(BaseApplication.class)",
+                "public class HiltTest {}"));
+
+    assertThat(compilation).failed();
+    assertThat(compilation)
+        .hadErrorContaining(
+            "@CustomTestApplication does not support application classes (or super classes) with "
+                + "@Inject fields. Found test.BaseApplication with @Inject fields [str]");
+  }
+
+  @Test
+  public void withSuperclassInjectField_fails() {
+    Compilation compilation =
+        compiler().compile(
+            JavaFileObjects.forSourceLines(
+                "test.BaseApplication",
+                "package test;",
+                "",
+                "import android.app.Application;",
+                "import javax.inject.Inject;",
+                "",
+                "public class BaseApplication extends Application {",
+                "  @Inject String str;",
+                "}"),
+            JavaFileObjects.forSourceLines(
+                "test.ParentApplication",
+                "package test;",
+                "",
+                "public class ParentApplication extends BaseApplication {}"),
+            JavaFileObjects.forSourceLines(
+                "test.HiltTest",
+                "package test;",
+                "",
+                "import dagger.hilt.android.testing.CustomTestApplication;",
+                "",
+                "@CustomTestApplication(ParentApplication.class)",
+                "public class HiltTest {}"));
+
+    assertThat(compilation).failed();
+    assertThat(compilation)
+        .hadErrorContaining(
+            "@CustomTestApplication does not support application classes (or super classes) with "
+                + "@Inject fields. Found test.BaseApplication with @Inject fields [str]");
+  }
+
+  @Test
+  public void withInjectMethod_fails() {
+    Compilation compilation =
+        compiler().compile(
+            JavaFileObjects.forSourceLines(
+                "test.BaseApplication",
+                "package test;",
+                "",
+                "import android.app.Application;",
+                "import javax.inject.Inject;",
+                "",
+                "public class BaseApplication extends Application {",
+                "  @Inject String str() { return null; }",
+                "}"),
+            JavaFileObjects.forSourceLines(
+                "test.HiltTest",
+                "package test;",
+                "",
+                "import dagger.hilt.android.testing.CustomTestApplication;",
+                "",
+                "@CustomTestApplication(BaseApplication.class)",
+                "public class HiltTest {}"));
+
+    assertThat(compilation).failed();
+    assertThat(compilation)
+        .hadErrorContaining(
+            "@CustomTestApplication does not support application classes (or super classes) with "
+                + "@Inject methods. Found test.BaseApplication with @Inject methods [str()]");
+  }
+
+  @Test
+  public void withSuperclassInjectMethod_fails() {
+    Compilation compilation =
+        compiler().compile(
+            JavaFileObjects.forSourceLines(
+                "test.BaseApplication",
+                "package test;",
+                "",
+                "import android.app.Application;",
+                "import javax.inject.Inject;",
+                "",
+                "public class BaseApplication extends Application {",
+                "  @Inject String str() { return null; }",
+                "}"),
+            JavaFileObjects.forSourceLines(
+                "test.ParentApplication",
+                "package test;",
+                "",
+                "public class ParentApplication extends BaseApplication {}"),
+            JavaFileObjects.forSourceLines(
+                "test.HiltTest",
+                "package test;",
+                "",
+                "import dagger.hilt.android.testing.CustomTestApplication;",
+                "",
+                "@CustomTestApplication(ParentApplication.class)",
+                "public class HiltTest {}"));
+
+    assertThat(compilation).failed();
+    assertThat(compilation)
+        .hadErrorContaining(
+            "@CustomTestApplication does not support application classes (or super classes) with "
+                + "@Inject methods. Found test.BaseApplication with @Inject methods [str()]");
+  }
+
+  @Test
+  public void withInjectConstructor_fails() {
+    Compilation compilation =
+        compiler().compile(
+            JavaFileObjects.forSourceLines(
+                "test.BaseApplication",
+                "package test;",
+                "",
+                "import android.app.Application;",
+                "import javax.inject.Inject;",
+                "",
+                "public class BaseApplication extends Application {",
+                "  @Inject BaseApplication() {}",
+                "}"),
+            JavaFileObjects.forSourceLines(
+                "test.HiltTest",
+                "package test;",
+                "",
+                "import dagger.hilt.android.testing.CustomTestApplication;",
+                "",
+                "@CustomTestApplication(BaseApplication.class)",
+                "public class HiltTest {}"));
+
+    assertThat(compilation).failed();
+    assertThat(compilation)
+        .hadErrorContaining(
+            "@CustomTestApplication does not support application classes (or super classes) with "
+                + "@Inject constructors. Found test.BaseApplication with @Inject constructors "
+                + "[BaseApplication()]");
+  }
+
+  @Test
+  public void withSuperclassInjectConstructor_fails() {
+    Compilation compilation =
+        compiler().compile(
+            JavaFileObjects.forSourceLines(
+                "test.BaseApplication",
+                "package test;",
+                "",
+                "import android.app.Application;",
+                "import javax.inject.Inject;",
+                "",
+                "public class BaseApplication extends Application {",
+                "  @Inject BaseApplication() {}",
+                "}"),
+            JavaFileObjects.forSourceLines(
+                "test.ParentApplication",
+                "package test;",
+                "",
+                "public class ParentApplication extends BaseApplication {}"),
+            JavaFileObjects.forSourceLines(
+                "test.HiltTest",
+                "package test;",
+                "",
+                "import dagger.hilt.android.testing.CustomTestApplication;",
+                "",
+                "@CustomTestApplication(ParentApplication.class)",
+                "public class HiltTest {}"));
+
+    assertThat(compilation).failed();
+    assertThat(compilation)
+        .hadErrorContaining(
+            "@CustomTestApplication does not support application classes (or super classes) with "
+                + "@Inject constructors. Found test.BaseApplication with @Inject constructors "
+                + "[BaseApplication()]");
+  }
+}
diff --git a/javatests/dagger/hilt/android/processor/internal/viewmodel/ViewModelGeneratorTest.kt b/javatests/dagger/hilt/android/processor/internal/viewmodel/ViewModelGeneratorTest.kt
index 7c3e45f..df020ff 100644
--- a/javatests/dagger/hilt/android/processor/internal/viewmodel/ViewModelGeneratorTest.kt
+++ b/javatests/dagger/hilt/android/processor/internal/viewmodel/ViewModelGeneratorTest.kt
@@ -45,7 +45,7 @@
 
     val expected = """
         package dagger.hilt.android.test;
-        
+
         import androidx.lifecycle.ViewModel;
         import dagger.Binds;
         import dagger.Module;
@@ -60,7 +60,7 @@
         import dagger.multibindings.StringKey;
         import java.lang.String;
         import $GENERATED_TYPE
-        
+
         $GENERATED_ANNOTATION
         @OriginatingElement(
             topLevelClass = MyViewModel.class
@@ -68,23 +68,25 @@
         public final class MyViewModel_HiltModules {
           private MyViewModel_HiltModules() {
           }
-        
+
           @Module
           @InstallIn(ViewModelComponent.class)
           public static abstract class BindsModule {
+            private BindsModule() {}
+
             @Binds
             @IntoMap
             @StringKey("dagger.hilt.android.test.MyViewModel")
             @HiltViewModelMap
             public abstract ViewModel binds(MyViewModel vm);
           }
-        
+
           @Module
           @InstallIn(ActivityRetainedComponent.class)
           public static final class KeyModule {
             private KeyModule() {
             }
-        
+
             @Provides
             @IntoSet
             @HiltViewModelMap.KeySet
@@ -123,7 +125,7 @@
 
     val expected = """
         package dagger.hilt.android.test;
-        
+
         import androidx.lifecycle.ViewModel;
         import dagger.Binds;
         import dagger.Module;
@@ -138,7 +140,7 @@
         import dagger.multibindings.StringKey;
         import java.lang.String;
         import $GENERATED_TYPE
-        
+
         $GENERATED_ANNOTATION
         @OriginatingElement(
             topLevelClass = MyViewModel.class
@@ -146,23 +148,25 @@
         public final class MyViewModel_HiltModules {
           private MyViewModel_HiltModules() {
           }
-        
+
           @Module
           @InstallIn(ViewModelComponent.class)
           public static abstract class BindsModule {
+            private BindsModule() {}
+
             @Binds
             @IntoMap
             @StringKey("dagger.hilt.android.test.MyViewModel")
             @HiltViewModelMap
             public abstract ViewModel binds(MyViewModel vm);
           }
-        
+
           @Module
           @InstallIn(ActivityRetainedComponent.class)
           public static final class KeyModule {
             private KeyModule() {
             }
-        
+
             @Provides
             @IntoSet
             @HiltViewModelMap.KeySet
@@ -208,7 +212,7 @@
 
     val expected = """
         package dagger.hilt.android.test;
-        
+
         import androidx.lifecycle.ViewModel;
         import dagger.Binds;
         import dagger.Module;
@@ -223,7 +227,7 @@
         import dagger.multibindings.StringKey;
         import java.lang.String;
         import $GENERATED_TYPE
-        
+
         $GENERATED_ANNOTATION
         @OriginatingElement(
             topLevelClass = MyViewModel.class
@@ -231,23 +235,25 @@
         public final class MyViewModel_HiltModules {
           private MyViewModel_HiltModules() {
           }
-        
+
           @Module
           @InstallIn(ViewModelComponent.class)
           public static abstract class BindsModule {
+            private BindsModule() {}
+
             @Binds
             @IntoMap
             @StringKey("dagger.hilt.android.test.MyViewModel")
             @HiltViewModelMap
             public abstract ViewModel binds(MyViewModel vm);
           }
-        
+
           @Module
           @InstallIn(ActivityRetainedComponent.class)
           public static final class KeyModule {
             private KeyModule() {
             }
-        
+
             @Provides
             @IntoSet
             @HiltViewModelMap.KeySet
@@ -294,7 +300,7 @@
 
     val expected = """
         package dagger.hilt.android.test;
-    
+
         import androidx.lifecycle.ViewModel;
         import dagger.Binds;
         import dagger.Module;
@@ -309,7 +315,7 @@
         import dagger.multibindings.StringKey;
         import java.lang.String;
         import $GENERATED_TYPE;
-        
+
         $GENERATED_ANNOTATION
         @OriginatingElement(
             topLevelClass = MyViewModel.class
@@ -317,23 +323,25 @@
         public final class MyViewModel_HiltModules {
           private MyViewModel_HiltModules() {
           }
-        
+
           @Module
           @InstallIn(ViewModelComponent.class)
           public static abstract class BindsModule {
+            private BindsModule() {}
+
             @Binds
             @IntoMap
             @StringKey("dagger.hilt.android.test.MyViewModel")
             @HiltViewModelMap
             public abstract ViewModel binds(MyViewModel vm);
           }
-        
+
           @Module
           @InstallIn(ActivityRetainedComponent.class)
           public static final class KeyModule {
             private KeyModule() {
             }
-        
+
             @Provides
             @IntoSet
             @HiltViewModelMap.KeySet
@@ -387,7 +395,7 @@
 
     val expected = """
         package dagger.hilt.android.test;
-        
+
         import androidx.lifecycle.ViewModel;
         import dagger.Binds;
         import dagger.Module;
@@ -402,7 +410,7 @@
         import dagger.multibindings.StringKey;
         import java.lang.String;
         import $GENERATED_TYPE;
-        
+
         $GENERATED_ANNOTATION
         @OriginatingElement(
             topLevelClass = MyViewModel.class
@@ -410,23 +418,25 @@
         public final class MyViewModel_HiltModules {
           private MyViewModel_HiltModules() {
           }
-        
+
           @Module
           @InstallIn(ViewModelComponent.class)
           public static abstract class BindsModule {
+            private BindsModule() {}
+
             @Binds
             @IntoMap
             @StringKey("dagger.hilt.android.test.MyViewModel")
             @HiltViewModelMap
             public abstract ViewModel binds(MyViewModel vm);
           }
-        
+
           @Module
           @InstallIn(ActivityRetainedComponent.class)
           public static final class KeyModule {
             private KeyModule() {
             }
-        
+
             @Provides
             @IntoSet
             @HiltViewModelMap.KeySet
@@ -466,7 +476,7 @@
 
     val expectedModule = """
         package dagger.hilt.android.test;
-        
+
         import androidx.lifecycle.ViewModel;
         import dagger.Binds;
         import dagger.Module;
@@ -481,7 +491,7 @@
         import dagger.multibindings.StringKey;
         import java.lang.String;
         import $GENERATED_TYPE
-        
+
         $GENERATED_ANNOTATION
         @OriginatingElement(
             topLevelClass = Outer.class
@@ -489,23 +499,25 @@
         public final class Outer_InnerViewModel_HiltModules {
           private Outer_InnerViewModel_HiltModules() {
           }
-        
+
           @Module
           @InstallIn(ViewModelComponent.class)
           public static abstract class BindsModule {
+            private BindsModule() {}
+
             @Binds
             @IntoMap
             @StringKey("dagger.hilt.android.test.Outer${'$'}InnerViewModel")
             @HiltViewModelMap
             public abstract ViewModel binds(Outer.InnerViewModel vm);
           }
-        
+
           @Module
           @InstallIn(ActivityRetainedComponent.class)
           public static final class KeyModule {
             private KeyModule() {
             }
-        
+
             @Provides
             @IntoSet
             @HiltViewModelMap.KeySet
diff --git a/javatests/dagger/hilt/android/testing/BUILD b/javatests/dagger/hilt/android/testing/BUILD
new file mode 100644
index 0000000..e336777
--- /dev/null
+++ b/javatests/dagger/hilt/android/testing/BUILD
@@ -0,0 +1,162 @@
+# Copyright (C) 2020 The Dagger Authors.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+# Description:
+#   Tests for internal code for implementing Hilt processors.
+
+package(default_visibility = ["//:src"])
+
+android_local_test(
+    name = "BindValueTest",
+    srcs = ["BindValueTest.java"],
+    manifest_values = {
+        "minSdkVersion": "15",
+        "targetSdkVersion": "27",
+    },
+    deps = [
+        "//:android_local_test_exports",
+        "@google_bazel_common//third_party/java/jsr330_inject",
+        "@google_bazel_common//third_party/java/truth",
+        "//java/dagger/hilt:entry_point",
+        "//java/dagger/hilt/android:android_entry_point",
+        "//java/dagger/hilt/android/testing:bind_value",
+        "//java/dagger/hilt/android/testing:hilt_android_test",
+    ],
+)
+
+android_local_test(
+    name = "BindValueIntoMapTest",
+    size = "small",
+    srcs = ["BindValueIntoMapTest.java"],
+    manifest_values = {
+        "minSdkVersion": "15",
+        "targetSdkVersion": "27",
+    },
+    deps = [
+        "//:android_local_test_exports",
+        "@google_bazel_common//third_party/java/auto:value",
+        "//:dagger_with_compiler",
+        "@google_bazel_common//third_party/java/jsr330_inject",
+        "@google_bazel_common//third_party/java/truth",
+        "//java/dagger/hilt:entry_point",
+        "//java/dagger/hilt/android:android_entry_point",
+        "//java/dagger/hilt/android/testing:bind_value",
+        "//java/dagger/hilt/android/testing:hilt_android_test",
+    ],
+)
+
+android_local_test(
+    name = "BindValueIntoSetTest",
+    size = "small",
+    srcs = ["BindValueIntoSetTest.java"],
+    manifest_values = {
+        "minSdkVersion": "15",
+        "targetSdkVersion": "27",
+    },
+    deps = [
+        "//:android_local_test_exports",
+        "//:dagger_with_compiler",
+        "@google_bazel_common//third_party/java/jsr330_inject",
+        "@google_bazel_common//third_party/java/truth",
+        "//java/dagger/hilt:entry_point",
+        "//java/dagger/hilt/android:android_entry_point",
+        "//java/dagger/hilt/android/testing:bind_value",
+        "//java/dagger/hilt/android/testing:hilt_android_test",
+    ],
+)
+
+android_local_test(
+    name = "BindElementsIntoSetTest",
+    size = "small",
+    srcs = ["BindElementsIntoSetTest.java"],
+    manifest_values = {
+        "minSdkVersion": "15",
+        "targetSdkVersion": "27",
+    },
+    deps = [
+        "//:android_local_test_exports",
+        "//java/dagger/internal/guava:collect",
+        "//:dagger_with_compiler",
+        "@google_bazel_common//third_party/java/jsr330_inject",
+        "@google_bazel_common//third_party/java/truth",
+        "//java/dagger/hilt:entry_point",
+        "//java/dagger/hilt/android:android_entry_point",
+        "//java/dagger/hilt/android/testing:bind_value",
+        "//java/dagger/hilt/android/testing:hilt_android_test",
+    ],
+)
+
+android_local_test(
+    name = "TestRootModulesTest",
+    size = "small",
+    srcs = ["TestRootModulesTest.java"],
+    manifest_values = {
+        "minSdkVersion": "15",
+        "targetSdkVersion": "27",
+    },
+    deps = [
+        "//:android_local_test_exports",
+        "//:dagger_with_compiler",
+        "@google_bazel_common//third_party/java/jsr330_inject",
+        "//java/dagger/hilt:install_in",
+        "//java/dagger/hilt/android:android_entry_point",
+        "//java/dagger/hilt/android/testing:hilt_android_test",
+    ],
+)
+
+android_local_test(
+    name = "HiltAndroidRuleTest",
+    size = "small",
+    srcs = ["HiltAndroidRuleTest.java"],
+    manifest_values = {
+        "minSdkVersion": "15",
+        "targetSdkVersion": "27",
+    },
+    deps = [
+        ":HiltAndroidRuleTestApp",
+        "//:android_local_test_exports",
+        "//java/dagger/internal/guava:collect",
+        "//:dagger_with_compiler",
+        "@google_bazel_common//third_party/java/jsr330_inject",
+        "@google_bazel_common//third_party/java/truth",
+        "//java/dagger/hilt/android/qualifiers",
+        "//java/dagger/hilt/android/testing:hilt_android_rule",
+        "//java/dagger/hilt/android/testing:hilt_android_test",
+    ],
+)
+
+android_library(
+    name = "HiltAndroidRuleTestApp",
+    srcs = ["HiltAndroidRuleTestApp.java"],
+    deps = [
+        "//java/dagger/hilt/android:hilt_android_app",
+    ],
+)
+
+android_local_test(
+    name = "DelayComponentReadyTest",
+    srcs = ["DelayComponentReadyTest.java"],
+    manifest_values = {
+        "minSdkVersion": "15",
+        "targetSdkVersion": "27",
+    },
+    deps = [
+        "//:android_local_test_exports",
+        "@google_bazel_common//third_party/java/jsr330_inject",
+        "@google_bazel_common//third_party/java/truth",
+        "//java/dagger/hilt:entry_point",
+        "//java/dagger/hilt/android:android_entry_point",
+        "//java/dagger/hilt/android/testing:bind_value",
+        "//java/dagger/hilt/android/testing:hilt_android_test",
+    ],
+)
diff --git a/javatests/dagger/hilt/android/testing/BindElementsIntoSetTest.java b/javatests/dagger/hilt/android/testing/BindElementsIntoSetTest.java
new file mode 100644
index 0000000..e92565f
--- /dev/null
+++ b/javatests/dagger/hilt/android/testing/BindElementsIntoSetTest.java
@@ -0,0 +1,72 @@
+/*
+ * Copyright (C) 2020 The Dagger Authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package dagger.hilt.android.testing;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import androidx.test.ext.junit.runners.AndroidJUnit4;
+import com.google.common.collect.ImmutableSet;
+import dagger.hilt.EntryPoint;
+import dagger.hilt.InstallIn;
+import dagger.hilt.components.SingletonComponent;
+import java.util.Set;
+import javax.inject.Inject;
+import javax.inject.Provider;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.robolectric.annotation.Config;
+
+@HiltAndroidTest
+@RunWith(AndroidJUnit4.class)
+@Config(application = HiltTestApplication.class)
+public final class BindElementsIntoSetTest {
+  private static final String SET_STRING_1 = "SetString1";
+  private static final String SET_STRING_2 = "SetString2";
+  private static final String SET_STRING_3 = "SetString3";
+
+  @BindElementsIntoSet Set<String> bindElementsSet1 = ImmutableSet.of(SET_STRING_1);
+
+  @BindElementsIntoSet Set<String> bindElementsSet2 = ImmutableSet.of(SET_STRING_2);
+
+  @EntryPoint
+  @InstallIn(SingletonComponent.class)
+  public interface BindElementsIntoSetEntryPoint {
+    Set<String> getStringSet();
+  }
+
+  @Rule public final HiltAndroidRule rule = new HiltAndroidRule(this);
+
+  @Inject Set<String> stringSet;
+
+  @Inject Provider<Set<String>> providedStringSet;
+
+  @Test
+  public void testMutated() throws Exception {
+    rule.inject();
+    // basic check that initial/default values are properly injected
+    assertThat(providedStringSet.get()).containsExactly(SET_STRING_1, SET_STRING_2);
+    // Test empty sets (something that cannot be done with @BindValueIntoSet)
+    bindElementsSet1 = ImmutableSet.of();
+    bindElementsSet2 = ImmutableSet.of();
+    assertThat(providedStringSet.get()).isEmpty();
+    // Test multiple elements in set.
+    bindElementsSet1 = ImmutableSet.of(SET_STRING_1, SET_STRING_2, SET_STRING_3);
+    assertThat(providedStringSet.get()).containsExactly(SET_STRING_1, SET_STRING_2, SET_STRING_3);
+  }
+
+}
diff --git a/javatests/dagger/hilt/android/testing/BindValueInKotlinValTest.kt b/javatests/dagger/hilt/android/testing/BindValueInKotlinValTest.kt
new file mode 100644
index 0000000..e0f6079
--- /dev/null
+++ b/javatests/dagger/hilt/android/testing/BindValueInKotlinValTest.kt
@@ -0,0 +1,59 @@
+package dagger.hilt.android.testing
+
+import androidx.test.core.app.ApplicationProvider.getApplicationContext
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import com.google.common.truth.Truth.assertThat
+import dagger.hilt.EntryPoint
+import dagger.hilt.EntryPoints
+import dagger.hilt.InstallIn
+import dagger.hilt.components.SingletonComponent
+import javax.inject.Named
+import org.junit.Rule
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.robolectric.annotation.Config
+
+@HiltAndroidTest
+@RunWith(AndroidJUnit4::class)
+@Config(application = HiltTestApplication::class)
+class BindValueInKotlinValTest {
+
+  @EntryPoint
+  @InstallIn(SingletonComponent::class)
+  interface BindValueEntryPoint {
+    fun bindValueString1(): String
+
+    @Named(TEST_QUALIFIER)
+    fun bindValueString2(): String
+  }
+
+  @get:Rule
+  val rule = HiltAndroidRule(this)
+
+  @BindValue
+  val bindValueString1 = BIND_VALUE_STRING1
+
+  @BindValue
+  @Named(TEST_QUALIFIER)
+  val bindValueString2 = BIND_VALUE_STRING2
+
+  @Test
+  fun testBindValueFieldIsProvided() {
+    assertThat(bindValueString1).isEqualTo(BIND_VALUE_STRING1)
+    assertThat(getBinding1()).isEqualTo(BIND_VALUE_STRING1)
+    assertThat(bindValueString2).isEqualTo(BIND_VALUE_STRING2)
+    assertThat(getBinding2()).isEqualTo(BIND_VALUE_STRING2)
+  }
+
+  companion object {
+    private const val BIND_VALUE_STRING1 = "BIND_VALUE_STRING1"
+    private const val BIND_VALUE_STRING2 = "BIND_VALUE_STRING2"
+    private const val TEST_QUALIFIER = "TEST_QUALIFIER"
+
+    private fun getBinding1() =
+      EntryPoints.get(getApplicationContext(), BindValueEntryPoint::class.java).bindValueString1()
+
+    private fun getBinding2() =
+      EntryPoints.get(getApplicationContext(), BindValueEntryPoint::class.java).bindValueString2()
+  }
+}
diff --git a/javatests/dagger/hilt/android/testing/BindValueIntoMapTest.java b/javatests/dagger/hilt/android/testing/BindValueIntoMapTest.java
new file mode 100644
index 0000000..588edcb
--- /dev/null
+++ b/javatests/dagger/hilt/android/testing/BindValueIntoMapTest.java
@@ -0,0 +1,77 @@
+/*
+ * Copyright (C) 2020 The Dagger Authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package dagger.hilt.android.testing;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import androidx.test.ext.junit.runners.AndroidJUnit4;
+import dagger.MapKey;
+import dagger.hilt.EntryPoint;
+import dagger.hilt.InstallIn;
+import dagger.hilt.components.SingletonComponent;
+import java.util.Map;
+import javax.inject.Inject;
+import javax.inject.Provider;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.robolectric.annotation.Config;
+
+@HiltAndroidTest
+@RunWith(AndroidJUnit4.class)
+@Config(application = HiltTestApplication.class)
+public final class BindValueIntoMapTest {
+  private static final String KEY1 = "SOME_KEY";
+  private static final String KEY2 = "SOME_OTHER_KEY";
+  private static final String VALUE1 = "SOME_VALUE";
+  private static final String VALUE2 = "SOME_OTHER_VALUE";
+  private static final String VALUE3 = "A_THIRD_VALUE";
+
+  @BindValueIntoMap
+  @MyMapKey(KEY1)
+  String boundValue1 = VALUE1;
+
+  @BindValueIntoMap
+  @MyMapKey(KEY2)
+  String boundValue2 = VALUE2;
+
+  @EntryPoint
+  @InstallIn(SingletonComponent.class)
+  public interface BindValuesIntoMapEntryPoint {
+    Map<String, String> getStringStringMap();
+  }
+
+  @Rule public final HiltAndroidRule rule = new HiltAndroidRule(this);
+
+  @Inject Provider<Map<String, String>> mapProvider;
+
+  @Test
+  public void testInjectedAndModified() throws Exception {
+    rule.inject();
+    Map<String, String> oldMap = mapProvider.get();
+    assertThat(oldMap).containsExactly(KEY1, VALUE1, KEY2, VALUE2);
+    boundValue1 = VALUE3;
+    Map<String, String> newMap = mapProvider.get();
+    assertThat(oldMap).containsExactly(KEY1, VALUE1, KEY2, VALUE2);
+    assertThat(newMap).containsExactly(KEY1, VALUE3, KEY2, VALUE2);
+  }
+
+  @MapKey
+  @interface MyMapKey {
+    String value();
+  }
+}
diff --git a/javatests/dagger/hilt/android/testing/BindValueIntoSetTest.java b/javatests/dagger/hilt/android/testing/BindValueIntoSetTest.java
new file mode 100644
index 0000000..276a3d8
--- /dev/null
+++ b/javatests/dagger/hilt/android/testing/BindValueIntoSetTest.java
@@ -0,0 +1,66 @@
+/*
+ * Copyright (C) 2020 The Dagger Authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package dagger.hilt.android.testing;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import androidx.test.ext.junit.runners.AndroidJUnit4;
+import dagger.hilt.EntryPoint;
+import dagger.hilt.InstallIn;
+import dagger.hilt.components.SingletonComponent;
+import java.util.Set;
+import javax.inject.Inject;
+import javax.inject.Provider;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.robolectric.annotation.Config;
+
+@HiltAndroidTest
+@RunWith(AndroidJUnit4.class)
+@Config(application = HiltTestApplication.class)
+public final class BindValueIntoSetTest {
+  private static final String SET_STRING_1 = "SetString1";
+  private static final String SET_STRING_2 = "SetString2";
+  private static final String SET_STRING_3 = "SetString3";
+
+  @EntryPoint
+  @InstallIn(SingletonComponent.class)
+  public interface BindValueIntoSetEntryPoint {
+    Set<String> getStringSet();
+  }
+
+  @Rule public final HiltAndroidRule rule = new HiltAndroidRule(this);
+
+  @BindValueIntoSet String bindValueSetString1 = SET_STRING_1;
+  @BindValueIntoSet String bindValueSetString2 = SET_STRING_2;
+
+  @Inject Set<String> stringSet;
+  @Inject Provider<Set<String>> providedStringSet;
+
+  @Test
+  public void testMutated() throws Exception {
+    rule.inject();
+    // basic check that initial/default values are properly injected
+    assertThat(providedStringSet.get()).containsExactly(SET_STRING_1, SET_STRING_2);
+    bindValueSetString1 = SET_STRING_3;
+    // change the value for bindValueSetString1 from 1 to 3
+    assertThat(providedStringSet.get()).containsExactly(SET_STRING_2, SET_STRING_3);
+  }
+
+
+}
diff --git a/javatests/dagger/hilt/android/testing/BindValueTest.java b/javatests/dagger/hilt/android/testing/BindValueTest.java
new file mode 100644
index 0000000..2e85f6b
--- /dev/null
+++ b/javatests/dagger/hilt/android/testing/BindValueTest.java
@@ -0,0 +1,94 @@
+/*
+ * Copyright (C) 2020 The Dagger Authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package dagger.hilt.android.testing;
+
+import static androidx.test.core.app.ApplicationProvider.getApplicationContext;
+import static com.google.common.truth.Truth.assertThat;
+import static org.junit.Assert.assertThrows;
+
+import androidx.test.ext.junit.runners.AndroidJUnit4;
+import dagger.hilt.EntryPoint;
+import dagger.hilt.EntryPoints;
+import dagger.hilt.InstallIn;
+import dagger.hilt.components.SingletonComponent;
+import javax.inject.Named;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.robolectric.annotation.Config;
+
+@HiltAndroidTest
+@RunWith(AndroidJUnit4.class)
+@Config(application = HiltTestApplication.class)
+public final class BindValueTest {
+  private static final String BIND_VALUE_STRING1 = "BIND_VALUE_STRING1";
+  private static final String BIND_VALUE_STRING2 = "BIND_VALUE_STRING2";
+  private static final String TEST_QUALIFIER1 = "TEST_QUALIFIER1";
+  private static final String TEST_QUALIFIER2 = "TEST_QUALIFIER2";
+
+  @EntryPoint
+  @InstallIn(SingletonComponent.class)
+  public interface BindValueEntryPoint {
+    @Named(TEST_QUALIFIER1)
+    String bindValueString1();
+
+    @Named(TEST_QUALIFIER2)
+    String bindValueString2();
+  }
+
+  @Rule public final HiltAndroidRule rule = new HiltAndroidRule(this);
+
+  @BindValue
+  @Named(TEST_QUALIFIER1)
+  String bindValueString1 = BIND_VALUE_STRING1;
+
+  @BindValue
+  @Named(TEST_QUALIFIER2)
+  String bindValueString2 = BIND_VALUE_STRING2;
+
+  @Test
+  public void testBindValueFieldIsProvided() throws Exception {
+    assertThat(bindValueString1).isEqualTo(BIND_VALUE_STRING1);
+    assertThat(getBinding1()).isEqualTo(BIND_VALUE_STRING1);
+
+    assertThat(bindValueString2).isEqualTo(BIND_VALUE_STRING2);
+    assertThat(getBinding2()).isEqualTo(BIND_VALUE_STRING2);
+  }
+
+  @Test
+  public void testBindValueIsMutable() throws Exception {
+    bindValueString1 = "newValue";
+    assertThat(getBinding1()).isEqualTo("newValue");
+  }
+
+  @Test
+  public void testCallingComponentReadyWithoutDelayComponentReady_fails() throws Exception {
+    IllegalStateException expected =
+        assertThrows(IllegalStateException.class, rule::componentReady);
+    assertThat(expected)
+        .hasMessageThat()
+        .isEqualTo("Called componentReady(), even though delayComponentReady() was not used.");
+  }
+
+  private static String getBinding1() {
+    return EntryPoints.get(getApplicationContext(), BindValueEntryPoint.class).bindValueString1();
+  }
+
+  private static String getBinding2() {
+    return EntryPoints.get(getApplicationContext(), BindValueEntryPoint.class).bindValueString2();
+  }
+}
diff --git a/javatests/dagger/hilt/android/testing/DelayComponentReadyTest.java b/javatests/dagger/hilt/android/testing/DelayComponentReadyTest.java
new file mode 100644
index 0000000..760e018
--- /dev/null
+++ b/javatests/dagger/hilt/android/testing/DelayComponentReadyTest.java
@@ -0,0 +1,154 @@
+/*
+ * Copyright (C) 2020 The Dagger Authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package dagger.hilt.android.testing;
+
+import static androidx.test.core.app.ApplicationProvider.getApplicationContext;
+import static com.google.common.truth.Truth.assertThat;
+import static org.junit.Assert.assertThrows;
+
+import androidx.test.core.app.ApplicationProvider;
+import androidx.test.ext.junit.runners.AndroidJUnit4;
+import dagger.hilt.EntryPoint;
+import dagger.hilt.EntryPoints;
+import dagger.hilt.InstallIn;
+import dagger.hilt.components.SingletonComponent;
+import java.util.concurrent.atomic.AtomicReference;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.rules.RuleChain;
+import org.junit.rules.TestRule;
+import org.junit.runner.RunWith;
+import org.junit.runners.model.Statement;
+import org.robolectric.annotation.Config;
+
+@HiltAndroidTest
+@RunWith(AndroidJUnit4.class)
+@Config(application = HiltTestApplication.class)
+public final class DelayComponentReadyTest {
+  private static final String EXPECTED_VALUE = "expected";
+
+  @EntryPoint
+  @InstallIn(SingletonComponent.class)
+  public interface FooEntryPoint {
+    String foo();
+  }
+
+  // If true, verifies that HiltAndroidRule threw an IllegalStateException
+  private boolean verifyTestRuleThrew = false;
+
+  // A test rule that wraps HiltAndroidRule to verify it throws
+  private final TestRule exceptionVerifyingRule =
+      (base, description) -> {
+        return new Statement() {
+          @Override
+          public void evaluate() throws Throwable {
+            AtomicReference<IllegalStateException> caught = new AtomicReference<>();
+            try {
+              base.evaluate();
+            } catch (IllegalStateException e) {
+              caught.set(e);
+              if (!verifyTestRuleThrew) {
+                throw e;
+              }
+            }
+            if (verifyTestRuleThrew) {
+              IllegalStateException expected = caught.get();
+              if (expected == null) {
+                throw new AssertionError("Did not throw expected expection");
+              }
+              assertThat(expected)
+                  .hasMessageThat()
+                  .isEqualTo("Used delayComponentReady(), but never called componentReady()");
+            }
+          }
+        };
+      };
+
+  private final HiltAndroidRule rule = new HiltAndroidRule(this).delayComponentReady();
+
+  @Rule public final RuleChain ruleChain = RuleChain.outerRule(exceptionVerifyingRule).around(rule);
+
+  @BindValue String foo;
+
+  @Test
+  public void testLateBindValue() throws Exception {
+    AtomicReference<String> fooRef = new AtomicReference<>();
+    OnComponentReadyRunner.addListener(
+        ApplicationProvider.getApplicationContext(),
+        FooEntryPoint.class,
+        entryPoint -> fooRef.set(entryPoint.foo()));
+
+    // Test that setting the listener before the component is ready doesn't run the listener.
+    assertThat(fooRef.get()).isNull();
+
+    foo = EXPECTED_VALUE;
+    rule.componentReady().inject();
+    assertThat(EntryPoints.get(getApplicationContext(), FooEntryPoint.class).foo())
+        .isEqualTo(EXPECTED_VALUE);
+  }
+
+  @Test
+  public void testUnitializedBindValue_fails() throws Exception {
+    OnComponentReadyRunner.addListener(
+        ApplicationProvider.getApplicationContext(), FooEntryPoint.class, FooEntryPoint::foo);
+
+    // foo not set
+    NullPointerException expected = assertThrows(NullPointerException.class, rule::componentReady);
+    // This is not the best error message, but it is equivalent to the message from a regular
+    // (non-delayed) @BindValue returning null;
+    assertThat(expected)
+        .hasMessageThat()
+        .isEqualTo("Cannot return null from a non-@Nullable @Provides method");
+  }
+
+  @Test
+  public void testDoubleComponentReady_fails() throws Exception {
+    foo = EXPECTED_VALUE;
+    rule.componentReady();
+    IllegalStateException expected =
+        assertThrows(IllegalStateException.class, rule::componentReady);
+    assertThat(expected).hasMessageThat().isEqualTo("Called componentReady() multiple times");
+  }
+
+  @Test
+  public void testMissingComponentReady_fails() throws Exception {
+    // componentReady not called
+    foo = EXPECTED_VALUE;
+    IllegalStateException expected = assertThrows(IllegalStateException.class, rule::inject);
+    assertThat(expected)
+        .hasMessageThat()
+        .isEqualTo("Called inject() before calling componentReady()");
+  }
+
+  @Test
+  public void testDelayComponentReadyAfterStart_fails() throws Exception {
+    IllegalStateException expected =
+        assertThrows(IllegalStateException.class, rule::delayComponentReady);
+    assertThat(expected)
+        .hasMessageThat()
+        .isEqualTo("Called delayComponentReady after test execution started");
+    // Prevents failure due to never calling componentReady()
+    foo = EXPECTED_VALUE;
+    rule.componentReady();
+  }
+
+  @Test
+  public void neverCallsComponentReady_fails() throws Exception {
+    // Does not call componentReady()
+    verifyTestRuleThrew = true;
+  }
+}
diff --git a/javatests/dagger/hilt/android/testing/HiltAndroidRuleTest.java b/javatests/dagger/hilt/android/testing/HiltAndroidRuleTest.java
new file mode 100644
index 0000000..2f5fcd2
--- /dev/null
+++ b/javatests/dagger/hilt/android/testing/HiltAndroidRuleTest.java
@@ -0,0 +1,82 @@
+/*
+ * Copyright (C) 2020 The Dagger Authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package dagger.hilt.android.testing;
+
+import static com.google.common.truth.Truth.assertThat;
+import static org.junit.Assert.assertThrows;
+
+import android.app.Application;
+import androidx.test.ext.junit.runners.AndroidJUnit4;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.robolectric.annotation.Config;
+
+@HiltAndroidTest
+@RunWith(AndroidJUnit4.class)
+public final class HiltAndroidRuleTest {
+  public static final class NonHiltTest {}
+
+  @Test
+  @Config(application = HiltTestApplication.class)
+  public void testMissingHiltAndroidTest_fails() throws Exception {
+    IllegalStateException exception =
+        assertThrows(
+            IllegalStateException.class,
+            () -> new HiltAndroidRule(new NonHiltTest()));
+    assertThat(exception)
+          .hasMessageThat()
+          .isEqualTo(
+              "Expected dagger.hilt.android.testing.HiltAndroidRuleTest$NonHiltTest to be "
+                  + "annotated with @HiltAndroidTest.");
+
+  }
+
+  @Test
+  @Config(application = Application.class)
+  public void testNonHiltTestApplication_fails() throws Exception {
+    IllegalStateException exception =
+        assertThrows(
+            IllegalStateException.class,
+            () -> new HiltAndroidRule(HiltAndroidRuleTest.this));
+    assertThat(exception)
+          .hasMessageThat()
+          .isEqualTo(
+              "Hilt test, dagger.hilt.android.testing.HiltAndroidRuleTest, must use a Hilt test "
+                  + "application but found android.app.Application. To fix, configure the test to "
+                  + "use HiltTestApplication or a custom Hilt test application generated with "
+                  + "@CustomTestApplication.");
+
+  }
+
+  @Test
+  @Config(application = HiltAndroidRuleTestApp.class)
+  public void testHiltAndroidApplication_fails() throws Exception {
+    IllegalStateException exception =
+        assertThrows(
+            IllegalStateException.class,
+            () -> new HiltAndroidRule(HiltAndroidRuleTest.this));
+    assertThat(exception)
+          .hasMessageThat()
+          .isEqualTo(
+              "Hilt test, dagger.hilt.android.testing.HiltAndroidRuleTest, cannot use a "
+                  + "@HiltAndroidApp application but found "
+                  + "dagger.hilt.android.testing.HiltAndroidRuleTestApp. To fix, configure the "
+                  + "test to use HiltTestApplication or a custom Hilt test application generated "
+                  + "with @CustomTestApplication.");
+
+  }
+}
diff --git a/java/dagger/hilt/android/internal/testing/TestApplicationInjector.java b/javatests/dagger/hilt/android/testing/HiltAndroidRuleTestApp.java
similarity index 66%
copy from java/dagger/hilt/android/internal/testing/TestApplicationInjector.java
copy to javatests/dagger/hilt/android/testing/HiltAndroidRuleTestApp.java
index c7ff5c9..9ec7d08 100644
--- a/java/dagger/hilt/android/internal/testing/TestApplicationInjector.java
+++ b/javatests/dagger/hilt/android/testing/HiltAndroidRuleTestApp.java
@@ -14,11 +14,11 @@
  * limitations under the License.
  */
 
-package dagger.hilt.android.internal.testing;
+package dagger.hilt.android.testing;
 
-/**
- * Interface to expose a method for members injection for use in tests.
- */
-public interface TestApplicationInjector<T> {
-  void injectApp(T t);
-}
+import android.app.Application;
+import dagger.hilt.android.HiltAndroidApp;
+
+/** A Hilt application used to test errors in {@link HiltAndroidRuleTest}. */
+@HiltAndroidApp(Application.class)
+final class HiltAndroidRuleTestApp extends Hilt_HiltAndroidRuleTestApp {}
diff --git a/javatests/dagger/hilt/android/testing/TestRootModulesTest.java b/javatests/dagger/hilt/android/testing/TestRootModulesTest.java
new file mode 100644
index 0000000..97753cc
--- /dev/null
+++ b/javatests/dagger/hilt/android/testing/TestRootModulesTest.java
@@ -0,0 +1,156 @@
+/*
+ * Copyright (C) 2020 The Dagger Authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package dagger.hilt.android.testing;
+
+import static java.lang.annotation.RetentionPolicy.RUNTIME;
+import static org.junit.Assert.assertEquals;
+
+import androidx.test.ext.junit.runners.AndroidJUnit4;
+import dagger.Binds;
+import dagger.Module;
+import dagger.Provides;
+import dagger.hilt.InstallIn;
+import dagger.hilt.components.SingletonComponent;
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Retention;
+import java.lang.annotation.Target;
+import javax.inject.Inject;
+import javax.inject.Qualifier;
+import javax.inject.Singleton;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.robolectric.annotation.Config;
+
+@HiltAndroidTest
+@RunWith(AndroidJUnit4.class)
+@Config(application = HiltTestApplication.class)
+public final class TestRootModulesTest {
+  @Rule public final HiltAndroidRule rules = new HiltAndroidRule(this);
+
+  @Retention(RUNTIME)
+  @Qualifier
+  @Target({ElementType.METHOD, ElementType.PARAMETER, ElementType.FIELD})
+  public @interface TestQualifier {
+    int value();
+  }
+
+  @Inject
+  @TestQualifier(0)
+  String testString0;
+
+  @Inject
+  @TestQualifier(1)
+  String testString1;
+
+  @Inject
+  @TestQualifier(2)
+  String testString2;
+
+  @Inject
+  @TestQualifier(3)
+  String testString3;
+
+  @Inject
+  @TestQualifier(4)
+  String testString4;
+
+  @Inject FooImpl fooImpl;
+  @Inject Foo foo;
+
+  @Module
+  @InstallIn(SingletonComponent.class)
+  public final class NonStaticModuleNonStaticProvidesDefaultConstructor {
+    @Provides
+    @TestQualifier(0)
+    String provideString() {
+      return "0";
+    }
+
+    NonStaticModuleNonStaticProvidesDefaultConstructor() {}
+  }
+
+  @Module
+  @InstallIn(SingletonComponent.class)
+  public final class NonStaticModuleNonStaticProvides {
+    @Provides
+    @TestQualifier(1)
+    String provideString() {
+      return "1";
+    }
+  }
+
+  @Module
+  @InstallIn(SingletonComponent.class)
+  public static final class StaticModuleStaticProvides {
+    @Provides
+    @TestQualifier(2)
+    static String provideString() {
+      return "2";
+    }
+
+    private StaticModuleStaticProvides() {}
+  }
+
+  @Module
+  @InstallIn(SingletonComponent.class)
+  public static final class StaticModuleNonStaticProvidesDefaultConstructor {
+    @Provides
+    @TestQualifier(3)
+    String provideString() {
+      return "3";
+    }
+  }
+
+  @Module
+  @InstallIn(SingletonComponent.class)
+  public abstract static class AbstractModuleStaticProvides {
+    @Provides
+    @TestQualifier(4)
+    static String provideString() {
+      return "4";
+    }
+
+    private AbstractModuleStaticProvides() {}
+  }
+
+  @Module
+  @InstallIn(SingletonComponent.class)
+  public abstract static class AbstractModuleBindsMethod {
+    @Binds
+    abstract Foo foo(FooImpl fooImpl);
+  }
+
+  interface Foo {}
+
+  @Singleton
+  static final class FooImpl implements Foo {
+    @Inject
+    FooImpl() {}
+  }
+
+  @Test
+  public void testInjection() throws Exception {
+    rules.inject();
+    assertEquals("0", testString0);
+    assertEquals("1", testString1);
+    assertEquals("2", testString2);
+    assertEquals("3", testString3);
+    assertEquals("4", testString4);
+    assertEquals(fooImpl, foo);
+  }
+}
diff --git a/javatests/dagger/hilt/android/testing/testinstallin/BUILD b/javatests/dagger/hilt/android/testing/testinstallin/BUILD
new file mode 100644
index 0000000..63d0bca
--- /dev/null
+++ b/javatests/dagger/hilt/android/testing/testinstallin/BUILD
@@ -0,0 +1,107 @@
+# Copyright (C) 2020 The Dagger Authors.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+# Description:
+#   Tests for internal code for implementing Hilt processors.
+
+package(default_visibility = ["//:src"])
+
+android_local_test(
+    name = "TestInstallInFooTest",
+    srcs = ["TestInstallInFooTest.java"],
+    manifest_values = {
+        "minSdkVersion": "15",
+        "targetSdkVersion": "27",
+    },
+    deps = [
+        ":TestInstallInModules",
+        "//:android_local_test_exports",
+        "//:dagger_with_compiler",
+        "@maven//:junit_junit",
+        
+                        "@maven//:org_robolectric_robolectric",
+                        "@maven//:androidx_test_ext_junit",
+                        "@maven//:androidx_test_core",
+                        
+        "@google_bazel_common//third_party/java/truth",
+        "//java/dagger/hilt/android/testing:hilt_android_test",
+    ],
+)
+
+android_local_test(
+    name = "TestInstallInBarTest",
+    srcs = ["TestInstallInBarTest.java"],
+    manifest_values = {
+        "minSdkVersion": "15",
+        "targetSdkVersion": "27",
+    },
+    deps = [
+        ":TestInstallInModules",
+        "//:android_local_test_exports",
+        "//:dagger_with_compiler",
+        "@maven//:junit_junit",
+        
+                        "@maven//:org_robolectric_robolectric",
+                        "@maven//:androidx_test_ext_junit",
+                        "@maven//:androidx_test_core",
+                        
+        "@google_bazel_common//third_party/java/truth",
+        "//java/dagger/hilt/android/testing:hilt_android_test",
+        "//java/dagger/hilt/android/testing:uninstall_modules",
+        "//java/dagger/hilt/testing:test_install_in",
+    ],
+)
+
+android_local_test(
+    name = "TestInstallInAppTest",
+    srcs = ["TestInstallInAppTest.java"],
+    manifest_values = {
+        "minSdkVersion": "15",
+        "targetSdkVersion": "27",
+    },
+    deps = [
+        ":TestInstallInApp",
+        ":TestInstallInModules",
+        "//:android_local_test_exports",
+        "@maven//:junit_junit",
+        
+                        "@maven//:org_robolectric_robolectric",
+                        "@maven//:androidx_test_ext_junit",
+                        "@maven//:androidx_test_core",
+                        
+        "@google_bazel_common//third_party/java/truth",
+    ],
+)
+
+android_library(
+    name = "TestInstallInApp",
+    testonly = True,
+    srcs = ["TestInstallInApp.java"],
+    deps = [
+        ":TestInstallInModules",
+        "//:dagger_with_compiler",
+        "//java/dagger/hilt/android:hilt_android_app",
+    ],
+)
+
+android_library(
+    name = "TestInstallInModules",
+    testonly = True,
+    srcs = ["TestInstallInModules.java"],
+    deps = [
+        "//:dagger_with_compiler",
+        "//java/dagger/hilt:install_in",
+        "//java/dagger/hilt/android/components",
+        "//java/dagger/hilt/testing:test_install_in",
+    ],
+)
diff --git a/javatests/dagger/hilt/android/testing/testinstallin/TestInstallInApp.java b/javatests/dagger/hilt/android/testing/testinstallin/TestInstallInApp.java
new file mode 100644
index 0000000..0ad35df
--- /dev/null
+++ b/javatests/dagger/hilt/android/testing/testinstallin/TestInstallInApp.java
@@ -0,0 +1,35 @@
+/*
+ * Copyright (C) 2020 The Dagger Authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package dagger.hilt.android.testing.testinstallin;
+
+import android.app.Application;
+import dagger.hilt.android.HiltAndroidApp;
+import dagger.hilt.android.testing.testinstallin.TestInstallInModules.Bar;
+import dagger.hilt.android.testing.testinstallin.TestInstallInModules.Foo;
+import javax.inject.Inject;
+
+/**
+ * An application to test {@link dagger.hilt.testing.TestInstallIn} are ignored when using {@link
+ * HiltAndroidApp}.
+ *
+ * <p>This class is used by {@link TestInstallInAppTest}.
+ */
+@HiltAndroidApp(Application.class)
+public class TestInstallInApp extends Hilt_TestInstallInApp {
+  @Inject Foo foo;
+  @Inject Bar bar;
+}
diff --git a/javatests/dagger/hilt/android/testing/testinstallin/TestInstallInAppTest.java b/javatests/dagger/hilt/android/testing/testinstallin/TestInstallInAppTest.java
new file mode 100644
index 0000000..beb198c
--- /dev/null
+++ b/javatests/dagger/hilt/android/testing/testinstallin/TestInstallInAppTest.java
@@ -0,0 +1,47 @@
+/*
+ * Copyright (C) 2020 The Dagger Authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package dagger.hilt.android.testing.testinstallin;
+
+import static androidx.test.core.app.ApplicationProvider.getApplicationContext;
+import static com.google.common.truth.Truth.assertThat;
+
+import androidx.test.ext.junit.runners.AndroidJUnit4;
+import dagger.hilt.android.testing.testinstallin.TestInstallInModules.GlobalBarModule;
+import dagger.hilt.android.testing.testinstallin.TestInstallInModules.GlobalFooModule;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.robolectric.annotation.Config;
+
+// Test that Foo and Bar use the global @InstallIn module
+@RunWith(AndroidJUnit4.class)
+@Config(application = TestInstallInApp.class)
+public final class TestInstallInAppTest {
+
+  @Test
+  public void testFoo() {
+    assertThat(getMyApplication().foo.moduleClass).isEqualTo(GlobalFooModule.class);
+  }
+
+  @Test
+  public void testBar() {
+    assertThat(getMyApplication().bar.moduleClass).isEqualTo(GlobalBarModule.class);
+  }
+
+  private static TestInstallInApp getMyApplication() {
+    return (TestInstallInApp) getApplicationContext();
+  }
+}
diff --git a/javatests/dagger/hilt/android/testing/testinstallin/TestInstallInBarTest.java b/javatests/dagger/hilt/android/testing/testinstallin/TestInstallInBarTest.java
new file mode 100644
index 0000000..8cf9de7
--- /dev/null
+++ b/javatests/dagger/hilt/android/testing/testinstallin/TestInstallInBarTest.java
@@ -0,0 +1,75 @@
+/*
+ * Copyright (C) 2020 The Dagger Authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package dagger.hilt.android.testing.testinstallin;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import androidx.test.ext.junit.runners.AndroidJUnit4;
+import dagger.Module;
+import dagger.Provides;
+import dagger.hilt.InstallIn;
+import dagger.hilt.android.testing.HiltAndroidRule;
+import dagger.hilt.android.testing.HiltAndroidTest;
+import dagger.hilt.android.testing.HiltTestApplication;
+import dagger.hilt.android.testing.UninstallModules;
+import dagger.hilt.android.testing.testinstallin.TestInstallInModules.Bar;
+import dagger.hilt.android.testing.testinstallin.TestInstallInModules.Foo;
+import dagger.hilt.android.testing.testinstallin.TestInstallInModules.GlobalBarModule;
+import dagger.hilt.android.testing.testinstallin.TestInstallInModules.GlobalFooTestModule;
+import dagger.hilt.components.SingletonComponent;
+import javax.inject.Inject;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.robolectric.annotation.Config;
+
+/**
+ * Tests that Foo uses the global {@linkplain TestInstallIn} module and that Bar uses the local
+ * {@linkplain InstallIn} module due to {@linkplain UninstallModules}.
+ */
+@UninstallModules(GlobalBarModule.class)
+@HiltAndroidTest
+@RunWith(AndroidJUnit4.class)
+@Config(application = HiltTestApplication.class)
+public final class TestInstallInBarTest {
+
+  @Rule public HiltAndroidRule hiltRule = new HiltAndroidRule(this);
+
+  @Module
+  @InstallIn(SingletonComponent.class)
+  public interface LocalBarTestModule {
+    @Provides
+    static Bar provideBar() {
+      return new Bar(LocalBarTestModule.class);
+    }
+  }
+
+  @Inject Foo foo;
+  @Inject Bar bar;
+
+  @Test
+  public void testFoo() {
+    hiltRule.inject();
+    assertThat(foo.moduleClass).isEqualTo(GlobalFooTestModule.class);
+  }
+
+  @Test
+  public void testBar() {
+    hiltRule.inject();
+    assertThat(bar.moduleClass).isEqualTo(LocalBarTestModule.class);
+  }
+}
diff --git a/javatests/dagger/hilt/android/testing/testinstallin/TestInstallInFooTest.java b/javatests/dagger/hilt/android/testing/testinstallin/TestInstallInFooTest.java
new file mode 100644
index 0000000..8a78157
--- /dev/null
+++ b/javatests/dagger/hilt/android/testing/testinstallin/TestInstallInFooTest.java
@@ -0,0 +1,57 @@
+/*
+ * Copyright (C) 2020 The Dagger Authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package dagger.hilt.android.testing.testinstallin;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import androidx.test.ext.junit.runners.AndroidJUnit4;
+import dagger.hilt.android.testing.HiltAndroidRule;
+import dagger.hilt.android.testing.HiltAndroidTest;
+import dagger.hilt.android.testing.HiltTestApplication;
+import dagger.hilt.android.testing.testinstallin.TestInstallInModules.Bar;
+import dagger.hilt.android.testing.testinstallin.TestInstallInModules.Foo;
+import dagger.hilt.android.testing.testinstallin.TestInstallInModules.GlobalBarModule;
+import dagger.hilt.android.testing.testinstallin.TestInstallInModules.GlobalFooTestModule;
+import javax.inject.Inject;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.robolectric.annotation.Config;
+
+// Test that Foo uses the global @TestInstallIn module and Bar uses the global @InstallIn module.
+@HiltAndroidTest
+@RunWith(AndroidJUnit4.class)
+@Config(application = HiltTestApplication.class)
+public final class TestInstallInFooTest {
+
+  @Rule public HiltAndroidRule hiltRule = new HiltAndroidRule(this);
+
+  @Inject Foo foo;
+  @Inject Bar bar;
+
+  @Test
+  public void testFoo() {
+    hiltRule.inject();
+    assertThat(foo.moduleClass).isEqualTo(GlobalFooTestModule.class);
+  }
+
+  @Test
+  public void testBar() {
+    hiltRule.inject();
+    assertThat(bar.moduleClass).isEqualTo(GlobalBarModule.class);
+  }
+}
diff --git a/javatests/dagger/hilt/android/testing/testinstallin/TestInstallInModules.java b/javatests/dagger/hilt/android/testing/testinstallin/TestInstallInModules.java
new file mode 100644
index 0000000..ab15d98
--- /dev/null
+++ b/javatests/dagger/hilt/android/testing/testinstallin/TestInstallInModules.java
@@ -0,0 +1,71 @@
+/*
+ * Copyright (C) 2020 The Dagger Authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package dagger.hilt.android.testing.testinstallin;
+
+import dagger.Module;
+import dagger.Provides;
+import dagger.hilt.InstallIn;
+import dagger.hilt.components.SingletonComponent;
+import dagger.hilt.testing.TestInstallIn;
+
+/** Modules and classes used in TestInstallInFooTest and TestInstallInBarTest. */
+final class TestInstallInModules {
+  private TestInstallInModules() {}
+
+  static class Foo {
+    Class<?> moduleClass;
+
+    Foo(Class<?> moduleClass) {
+      this.moduleClass = moduleClass;
+    }
+  }
+
+  static class Bar {
+    Class<?> moduleClass;
+
+    Bar(Class<?> moduleClass) {
+      this.moduleClass = moduleClass;
+    }
+  }
+
+  @Module
+  @InstallIn(SingletonComponent.class)
+  interface GlobalFooModule {
+    @Provides
+    static Foo provideFoo() {
+      return new Foo(GlobalFooModule.class);
+    }
+  }
+
+  @Module
+  @InstallIn(SingletonComponent.class)
+  interface GlobalBarModule {
+    @Provides
+    static Bar provideFoo() {
+      return new Bar(GlobalBarModule.class);
+    }
+  }
+
+  @Module
+  @TestInstallIn(components = SingletonComponent.class, replaces = GlobalFooModule.class)
+  interface GlobalFooTestModule {
+    @Provides
+    static Foo provideFoo() {
+      return new Foo(GlobalFooTestModule.class);
+    }
+  }
+}
diff --git a/javatests/dagger/hilt/android/testsubpackage/BUILD b/javatests/dagger/hilt/android/testsubpackage/BUILD
new file mode 100644
index 0000000..114c861
--- /dev/null
+++ b/javatests/dagger/hilt/android/testsubpackage/BUILD
@@ -0,0 +1,40 @@
+# Copyright (C) 2021 The Dagger Authors.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+package(default_visibility = ["//:src"])
+
+android_local_test(
+    name = "UsesLocalComponentTestBindingsTest",
+    manifest_values = {
+        "minSdkVersion": "14",
+    },
+    deps = [
+        "//javatests/dagger/hilt/android:shared_component_test_classes",
+    ],
+)
+
+android_local_test(
+    name = "UsesSharedComponent1Test",
+    manifest_values = {
+        "minSdkVersion": "14",
+    },
+    deps = [
+        "//javatests/dagger/hilt/android:shared_component_test_classes",
+    ],
+)
+
+exports_files(srcs = [
+    "UsesLocalComponentTestBindingsTest.java",
+    "UsesSharedComponent1Test.java",
+])
diff --git a/javatests/dagger/hilt/android/testsubpackage/UsesLocalComponentTestBindingsTest.java b/javatests/dagger/hilt/android/testsubpackage/UsesLocalComponentTestBindingsTest.java
new file mode 100644
index 0000000..5f1ff8f
--- /dev/null
+++ b/javatests/dagger/hilt/android/testsubpackage/UsesLocalComponentTestBindingsTest.java
@@ -0,0 +1,111 @@
+/*
+ * Copyright (C) 2021 The Dagger Authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package dagger.hilt.android.testsubpackage;
+
+import static androidx.test.core.app.ApplicationProvider.getApplicationContext;
+import static com.google.common.truth.Truth.assertThat;
+
+import android.os.Build;
+import androidx.test.ext.junit.runners.AndroidJUnit4;
+import dagger.Module;
+import dagger.Provides;
+import dagger.hilt.EntryPoints;
+import dagger.hilt.InstallIn;
+import dagger.hilt.android.UsesComponentHelper;
+import dagger.hilt.android.UsesComponentTestClasses.Foo;
+import dagger.hilt.android.UsesComponentTestClasses.FooEntryPoint;
+import dagger.hilt.android.UsesComponentTestClasses.UsesComponentQualifier;
+import dagger.hilt.android.UsesComponentTestClasses.UsesComponentTestSubcomponent;
+import dagger.hilt.android.UsesComponentTestClasses.UsesComponentTestSubcomponentBuilderEntryPoint;
+import dagger.hilt.android.internal.testing.TestApplicationComponentManager;
+import dagger.hilt.android.testing.HiltAndroidRule;
+import dagger.hilt.android.testing.HiltAndroidTest;
+import dagger.hilt.android.testing.HiltTestApplication;
+import dagger.hilt.components.SingletonComponent;
+import javax.inject.Inject;
+import javax.inject.Qualifier;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.robolectric.annotation.Config;
+
+/**
+ * A test that provides its own test bindings, and therefore cannot use the shared components.
+ *
+ * <p>Note that this test class exactly matches the simple name of {@link
+ * dagger.hilt.android.UsesLocalComponentTestBindingsTest}. This is intentional and used to verify
+ * generated code class names do not clash.
+ */
+@HiltAndroidTest
+@RunWith(AndroidJUnit4.class)
+// Robolectric requires Java9 to run API 29 and above, so use API 28 instead
+@Config(sdk = Build.VERSION_CODES.P, application = HiltTestApplication.class)
+public final class UsesLocalComponentTestBindingsTest {
+
+  @Rule public HiltAndroidRule rule = new HiltAndroidRule(this);
+
+  @Qualifier
+  @interface TestQualifier {}
+
+  @Inject @UsesComponentQualifier String injectedString;
+  @Inject @TestQualifier String localString;
+
+  @Module
+  @InstallIn(SingletonComponent.class)
+  public static final class TestModule {
+    @Provides
+    @TestQualifier
+    static String provideString() {
+      return "local_string";
+    }
+
+    private TestModule() {}
+  }
+
+  @Test
+  public void testInject() {
+    rule.inject();
+    assertThat(injectedString).isEqualTo("shared_string");
+    assertThat(localString).isEqualTo("local_string");
+  }
+
+  @Test
+  public void testSubcomponent() {
+    UsesComponentTestSubcomponent subcomponent =
+        EntryPoints.get(
+                getApplicationContext(), UsesComponentTestSubcomponentBuilderEntryPoint.class)
+            .mySubcomponentBuilder()
+            .id(123)
+            .build();
+
+    Foo foo1 = EntryPoints.get(subcomponent, FooEntryPoint.class).foo();
+    Foo foo2 = EntryPoints.get(subcomponent, FooEntryPoint.class).foo();
+
+    assertThat(foo1).isNotNull();
+    assertThat(foo2).isNotNull();
+    assertThat(foo1).isSameInstanceAs(foo2);
+  }
+
+  @Test
+  public void testUsesLocalComponent() {
+    HiltTestApplication app = (HiltTestApplication) getApplicationContext();
+    Object generatedComponent =
+        ((TestApplicationComponentManager) app.componentManager()).generatedComponent();
+    assertThat(generatedComponent.getClass().getName())
+        .isEqualTo(UsesComponentHelper.perTestComponentNameWithDedupePrefix("dhat_", this));
+  }
+}
diff --git a/javatests/dagger/hilt/android/testsubpackage/UsesSharedComponent1Test.java b/javatests/dagger/hilt/android/testsubpackage/UsesSharedComponent1Test.java
new file mode 100644
index 0000000..e99bbe2
--- /dev/null
+++ b/javatests/dagger/hilt/android/testsubpackage/UsesSharedComponent1Test.java
@@ -0,0 +1,115 @@
+/*
+ * Copyright (C) 2021 The Dagger Authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package dagger.hilt.android.testsubpackage;
+
+import static androidx.test.core.app.ApplicationProvider.getApplicationContext;
+import static com.google.common.truth.Truth.assertThat;
+import static org.junit.Assert.assertThrows;
+
+import android.os.Build;
+import androidx.fragment.app.FragmentActivity;
+import androidx.test.core.app.ActivityScenario;
+import androidx.test.ext.junit.runners.AndroidJUnit4;
+import dagger.hilt.EntryPoints;
+import dagger.hilt.android.AndroidEntryPoint;
+import dagger.hilt.android.UsesComponentHelper;
+import dagger.hilt.android.UsesComponentTestClasses.Foo;
+import dagger.hilt.android.UsesComponentTestClasses.FooEntryPoint;
+import dagger.hilt.android.UsesComponentTestClasses.UsesComponentQualifier;
+import dagger.hilt.android.UsesComponentTestClasses.UsesComponentTestSubcomponent;
+import dagger.hilt.android.UsesComponentTestClasses.UsesComponentTestSubcomponentBuilderEntryPoint;
+import dagger.hilt.android.internal.testing.TestApplicationComponentManager;
+import dagger.hilt.android.testing.HiltAndroidRule;
+import dagger.hilt.android.testing.HiltAndroidTest;
+import dagger.hilt.android.testing.HiltTestApplication;
+import javax.inject.Inject;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.robolectric.annotation.Config;
+
+/**
+ * A test that provides none of its own test bindings, and therefore uses the shared components.
+ *
+ * <p>Note that this test class exactly matches the simple name of {@link
+ * dagger.hilt.android.UsesSharedComponent1Test}. This is intentional and used to verify generated
+ * code class names do not clash.
+ */
+@HiltAndroidTest
+@RunWith(AndroidJUnit4.class)
+// Robolectric requires Java9 to run API 29 and above, so use API 28 instead
+@Config(sdk = Build.VERSION_CODES.P, application = HiltTestApplication.class)
+public final class UsesSharedComponent1Test {
+
+  @Rule public HiltAndroidRule rule = new HiltAndroidRule(this);
+
+  @Inject @UsesComponentQualifier String injectedString;
+
+  @Test
+  public void testInject() {
+    rule.inject();
+    assertThat(injectedString).isEqualTo("shared_string");
+  }
+
+  @Test
+  public void testSubcomponent() {
+    UsesComponentTestSubcomponent subcomponent =
+        EntryPoints.get(
+                getApplicationContext(), UsesComponentTestSubcomponentBuilderEntryPoint.class)
+            .mySubcomponentBuilder()
+            .id(123)
+            .build();
+
+    Foo foo1 = EntryPoints.get(subcomponent, FooEntryPoint.class).foo();
+    Foo foo2 = EntryPoints.get(subcomponent, FooEntryPoint.class).foo();
+
+    assertThat(foo1).isNotNull();
+    assertThat(foo2).isNotNull();
+    assertThat(foo1).isSameInstanceAs(foo2);
+  }
+
+  @Test
+  public void testActivity() {
+    try (ActivityScenario<TestActivity> scenario = ActivityScenario.launch(TestActivity.class)) {
+      scenario.onActivity(
+          activity -> {
+            assertThat(activity.activityString).isEqualTo("shared_string");
+          });
+    }
+  }
+
+  @Test
+  public void testUsesSharedComponent() {
+    HiltTestApplication app = (HiltTestApplication) getApplicationContext();
+    Object generatedComponent =
+        ((TestApplicationComponentManager) app.componentManager()).generatedComponent();
+    assertThat(generatedComponent.getClass().getName())
+        .isEqualTo(UsesComponentHelper.defaultComponentName());
+  }
+
+  @Test
+  public void testLocalComponentNotGenerated() {
+    assertThrows(
+        ClassNotFoundException.class,
+        () -> Class.forName(UsesComponentHelper.perTestComponentName(this)));
+  }
+
+  @AndroidEntryPoint(FragmentActivity.class)
+  public static final class TestActivity extends Hilt_UsesSharedComponent1Test_TestActivity {
+    @Inject @UsesComponentQualifier String activityString;
+  }
+}
diff --git a/javatests/dagger/hilt/processor/internal/definecomponent/DefineComponentProcessorTest.java b/javatests/dagger/hilt/processor/internal/definecomponent/DefineComponentProcessorTest.java
index 901c7a3..a8c5fbe 100644
--- a/javatests/dagger/hilt/processor/internal/definecomponent/DefineComponentProcessorTest.java
+++ b/javatests/dagger/hilt/processor/internal/definecomponent/DefineComponentProcessorTest.java
@@ -64,7 +64,7 @@
 
     JavaFileObject componentOutput =
         JavaFileObjects.forSourceLines(
-            "dagger.hilt.processor.internal.definecomponent.codegen.test_FooComponent",
+            "dagger.hilt.processor.internal.definecomponent.codegen._test_FooComponent",
             "package dagger.hilt.processor.internal.definecomponent.codegen;",
             "",
             "import dagger.hilt.internal.definecomponent.DefineComponentClasses;",
@@ -72,11 +72,11 @@
             "",
             "@DefineComponentClasses(component = \"test.FooComponent\")",
             "@Generated(\"" + DefineComponentProcessor.class.getName() + "\")",
-            "interface test_FooComponent {}");
+            "public class _test_FooComponent {}");
 
     JavaFileObject builderOutput =
         JavaFileObjects.forSourceLines(
-            "dagger.hilt.processor.internal.definecomponent.codegen.test_FooComponentBuilder",
+            "dagger.hilt.processor.internal.definecomponent.codegen._test_FooComponentBuilder",
             "package dagger.hilt.processor.internal.definecomponent.codegen;",
             "",
             "import dagger.hilt.internal.definecomponent.DefineComponentClasses;",
@@ -84,7 +84,7 @@
             "",
             "@DefineComponentClasses(builder = \"test.FooComponentBuilder\")",
             "@Generated(\"" + DefineComponentProcessor.class.getName() + "\")",
-            "interface test_FooComponentBuilder {}");
+            "public class _test_FooComponentBuilder {}");
 
     Compilation compilation = compiler().compile(component, builder);
     assertThat(compilation).succeeded();
diff --git a/javatests/dagger/hilt/processor/internal/originatingelement/BUILD b/javatests/dagger/hilt/processor/internal/originatingelement/BUILD
new file mode 100644
index 0000000..3135159
--- /dev/null
+++ b/javatests/dagger/hilt/processor/internal/originatingelement/BUILD
@@ -0,0 +1,41 @@
+# Copyright (C) 2020 The Dagger Authors.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+# Description:
+#   Tests for internal code for implementing Hilt processors.
+
+load("//java/dagger/testing/compile:macros.bzl", "compiler_test")
+
+package(default_visibility = ["//:src"])
+
+compiler_test(
+    name = "OriginatingElementProcessorTest",
+    srcs = ["OriginatingElementProcessorTest.java"],
+    compiler_deps = [
+        "//java/dagger/hilt/codegen:originating_element",
+        "@androidsdk//:platforms/android-30/android.jar",
+        "@maven//:androidx_annotation_annotation",
+    ],
+    deps = [
+        "//javatests/dagger/hilt/android/processor:android_compilers",
+        "@google_bazel_common//third_party/java/compile_testing",
+        "@google_bazel_common//third_party/java/junit",
+        "@google_bazel_common//third_party/java/truth",
+    ],
+)
+
+filegroup(
+    name = "srcs_filegroup",
+    srcs = glob(["**/*"]),
+)
diff --git a/javatests/dagger/hilt/processor/internal/originatingelement/OriginatingElementProcessorTest.java b/javatests/dagger/hilt/processor/internal/originatingelement/OriginatingElementProcessorTest.java
new file mode 100644
index 0000000..444fb1d
--- /dev/null
+++ b/javatests/dagger/hilt/processor/internal/originatingelement/OriginatingElementProcessorTest.java
@@ -0,0 +1,85 @@
+/*
+ * Copyright (C) 2020 The Dagger Authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package dagger.hilt.processor.internal.originatingelement;
+
+import static com.google.testing.compile.CompilationSubject.assertThat;
+import static dagger.hilt.android.processor.AndroidCompilers.compiler;
+
+import com.google.testing.compile.Compilation;
+import com.google.testing.compile.JavaFileObjects;
+import javax.tools.JavaFileObject;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.JUnit4;
+
+@RunWith(JUnit4.class)
+public class OriginatingElementProcessorTest {
+
+  @Test
+  public void originatingElementOnInnerClass_fails() {
+    JavaFileObject outer1 =
+        JavaFileObjects.forSourceLines(
+            "test.Outer1",
+            "package test;",
+            "",
+            "class Outer1 {}");
+    JavaFileObject outer2 =
+        JavaFileObjects.forSourceLines(
+            "test.Outer2",
+            "package test;",
+            "",
+            "import dagger.hilt.codegen.OriginatingElement;",
+            "",
+            "class Outer2 {",
+            "  @OriginatingElement(topLevelClass = Outer1.class)",
+            "  static class Inner {}",
+            "}");
+    Compilation compilation = compiler().compile(outer1, outer2);
+    assertThat(compilation).failed();
+    assertThat(compilation)
+        .hadErrorContaining(
+            "@OriginatingElement should only be used to annotate top-level types, but found: "
+            + "test.Outer2.Inner");
+  }
+
+  @Test
+  public void originatingElementValueWithInnerClass_fails() {
+    JavaFileObject outer1 =
+        JavaFileObjects.forSourceLines(
+            "test.Outer1",
+            "package test;",
+            "",
+            "class Outer1 {",
+            "  static class Inner {}",
+            "}");
+    JavaFileObject outer2 =
+        JavaFileObjects.forSourceLines(
+            "test.Outer2",
+            "package test;",
+            "",
+            "import dagger.hilt.codegen.OriginatingElement;",
+            "",
+            "@OriginatingElement(topLevelClass = Outer1.Inner.class)",
+            "class Outer2 {}");
+    Compilation compilation = compiler().compile(outer1, outer2);
+    assertThat(compilation).failed();
+    assertThat(compilation)
+        .hadErrorContaining(
+            "OriginatingElement.topLevelClass value should be a top-level class, but found: "
+            + "test.Outer1.Inner");
+  }
+}
diff --git a/javatests/dagger/hilt/processor/internal/root/BUILD b/javatests/dagger/hilt/processor/internal/root/BUILD
new file mode 100644
index 0000000..0dba875
--- /dev/null
+++ b/javatests/dagger/hilt/processor/internal/root/BUILD
@@ -0,0 +1,127 @@
+# Copyright (C) 2020 The Dagger Authors.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+# Description:
+#   Tests for internal code for implementing Hilt processors.
+
+load("//java/dagger/testing/compile:macros.bzl", "compiler_test")
+
+package(default_visibility = ["//:src"])
+
+android_library(
+    name = "MyAppPreviousCompilation",
+    srcs = ["MyAppPreviousCompilation.java"],
+    deps = [
+        "//java/dagger/hilt/android:hilt_android_app",
+    ],
+)
+
+compiler_test(
+    name = "MyAppPreviousCompilationTest",
+    srcs = ["MyAppPreviousCompilationTest.java"],
+    compiler_deps = [
+        ":MyAppPreviousCompilation",
+        "//java/dagger/hilt/android:hilt_android_app",
+        "//java/dagger/hilt/android/testing:hilt_android_test",
+        "@androidsdk//:platforms/android-30/android.jar",
+        "@maven//:androidx_annotation_annotation",
+        "@maven//:org_robolectric_robolectric",
+        "@maven//:androidx_test_ext_junit",
+        "@maven//:androidx_test_core",
+    ],
+    deps = [
+        "//java/dagger/internal/guava:collect",
+        "//javatests/dagger/hilt/android/processor:android_compilers",
+        "@google_bazel_common//third_party/java/compile_testing",
+        "@google_bazel_common//third_party/java/junit",
+        "@google_bazel_common//third_party/java/truth",
+    ],
+)
+
+android_library(
+    name = "MyTestPreviousCompilation",
+    srcs = ["MyTestPreviousCompilation.java"],
+    deps = [
+        "//:android_local_test_exports",
+        "//java/dagger/hilt/android/testing:hilt_android_test",
+    ],
+)
+
+compiler_test(
+    name = "MyTestPreviousCompilationTest",
+    srcs = ["MyTestPreviousCompilationTest.java"],
+    compiler_deps = [
+        ":MyTestPreviousCompilation",
+        "//java/dagger/hilt/android:hilt_android_app",
+        "//java/dagger/hilt/android/testing:hilt_android_test",
+        "@androidsdk//:platforms/android-30/android.jar",
+        "@maven//:androidx_annotation_annotation",
+        "@maven//:org_robolectric_robolectric",
+        "@maven//:androidx_test_ext_junit",
+        "@maven//:androidx_test_core",
+    ],
+    deps = [
+        "//java/dagger/internal/guava:collect",
+        "//javatests/dagger/hilt/android/processor:android_compilers",
+        "@google_bazel_common//third_party/java/compile_testing",
+        "@google_bazel_common//third_party/java/junit",
+        "@google_bazel_common//third_party/java/truth",
+    ],
+)
+
+compiler_test(
+    name = "RootProcessorErrorsTest",
+    srcs = ["RootProcessorErrorsTest.java"],
+    compiler_deps = [
+        "//java/dagger/hilt/android:hilt_android_app",
+        "//java/dagger/hilt/android/testing:hilt_android_test",
+        "@androidsdk//:platforms/android-30/android.jar",
+        "@maven//:androidx_annotation_annotation",
+        "@maven//:org_robolectric_robolectric",
+        "@maven//:androidx_test_ext_junit",
+        "@maven//:androidx_test_core",
+    ],
+    deps = [
+        "//java/dagger/internal/guava:collect",
+        "//javatests/dagger/hilt/android/processor:android_compilers",
+        "@google_bazel_common//third_party/java/compile_testing",
+        "@google_bazel_common//third_party/java/junit",
+        "@google_bazel_common//third_party/java/truth",
+    ],
+)
+
+compiler_test(
+    name = "RootFileFormatterTest",
+    srcs = ["RootFileFormatterTest.java"],
+    compiler_deps = [
+        "//java/dagger/hilt/android:hilt_android_app",
+        "//java/dagger/hilt/android/testing:hilt_android_test",
+        "@androidsdk//:platforms/android-30/android.jar",
+        "@maven//:androidx_annotation_annotation",
+        "@maven//:org_robolectric_robolectric",
+        "@maven//:androidx_test_ext_junit",
+        "@maven//:androidx_test_core",
+    ],
+    deps = [
+        "//java/dagger/internal/guava:base",
+        "//javatests/dagger/hilt/android/processor:android_compilers",
+        "@google_bazel_common//third_party/java/compile_testing",
+        "@google_bazel_common//third_party/java/junit",
+        "@google_bazel_common//third_party/java/truth",
+    ],
+)
+
+filegroup(
+    name = "srcs_filegroup",
+    srcs = glob(["*"]),
+)
diff --git a/javatests/dagger/hilt/processor/internal/root/MyAppPreviousCompilation.java b/javatests/dagger/hilt/processor/internal/root/MyAppPreviousCompilation.java
new file mode 100644
index 0000000..5830803
--- /dev/null
+++ b/javatests/dagger/hilt/processor/internal/root/MyAppPreviousCompilation.java
@@ -0,0 +1,29 @@
+/*
+ * Copyright (C) 2021 The Dagger Authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package dagger.hilt.processor.internal.root;
+
+import android.app.Application;
+import dagger.hilt.android.HiltAndroidApp;
+
+/** Defines a {@link HiltAndroidApp} for {@link MyAppPreviousCompilationTest}. */
+public final class MyAppPreviousCompilation {
+
+  @HiltAndroidApp(Application.class)
+  public static final class MyApp extends Hilt_MyAppPreviousCompilation_MyApp {}
+
+  private MyAppPreviousCompilation() {}
+}
diff --git a/javatests/dagger/hilt/processor/internal/root/MyAppPreviousCompilationTest.java b/javatests/dagger/hilt/processor/internal/root/MyAppPreviousCompilationTest.java
new file mode 100644
index 0000000..adf6d9b
--- /dev/null
+++ b/javatests/dagger/hilt/processor/internal/root/MyAppPreviousCompilationTest.java
@@ -0,0 +1,100 @@
+/*
+ * Copyright (C) 2021 The Dagger Authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package dagger.hilt.processor.internal.root;
+
+import static com.google.testing.compile.CompilationSubject.assertThat;
+
+import com.google.common.collect.ImmutableCollection;
+import com.google.common.collect.ImmutableList;
+import com.google.testing.compile.Compilation;
+import com.google.testing.compile.Compiler;
+import com.google.testing.compile.JavaFileObjects;
+import dagger.hilt.android.processor.AndroidCompilers;
+import javax.tools.JavaFileObject;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+import org.junit.runners.Parameterized.Parameters;
+
+@RunWith(Parameterized.class)
+public final class MyAppPreviousCompilationTest {
+
+  @Parameters(name = "{0}")
+  public static ImmutableCollection<Object[]> parameters() {
+    return ImmutableList.copyOf(new Object[][] {{true}, {false}});
+  }
+
+  private final boolean disableCrossCompilationRootValidation;
+
+  public MyAppPreviousCompilationTest(boolean disableCrossCompilationRootValidation) {
+    this.disableCrossCompilationRootValidation = disableCrossCompilationRootValidation;
+  }
+
+  private Compiler compiler() {
+    return AndroidCompilers.compiler()
+        .withOptions(
+            String.format(
+                "-Adagger.hilt.disableCrossCompilationRootValidation=%s",
+                disableCrossCompilationRootValidation));
+  }
+
+  @Test
+  public void testRootTest() {
+    JavaFileObject testRoot =
+        JavaFileObjects.forSourceLines(
+            "test.TestRoot",
+            "package test;",
+            "",
+            "import dagger.hilt.android.testing.HiltAndroidTest;",
+            "",
+            "@HiltAndroidTest",
+            "public class TestRoot {}");
+
+    // This test case should succeed independent of disableCrossCompilationRootValidation.
+    Compilation compilation = compiler().compile(testRoot);
+    assertThat(compilation).succeeded();
+  }
+
+  @Test
+  public void appRootTest() {
+    JavaFileObject appRoot =
+        JavaFileObjects.forSourceLines(
+            "test.AppRoot",
+            "package test;",
+            "",
+            "import android.app.Application;",
+            "import dagger.hilt.android.HiltAndroidApp;",
+            "",
+            "@HiltAndroidApp(Application.class)",
+            "public class AppRoot extends Hilt_AppRoot {}");
+
+    Compilation compilation = compiler().compile(appRoot);
+    if (disableCrossCompilationRootValidation) {
+      assertThat(compilation).succeeded();
+    } else {
+      assertThat(compilation).failed();
+      assertThat(compilation).hadErrorCount(1);
+      assertThat(compilation)
+          .hadErrorContaining(
+              "Cannot process app roots in this compilation unit since there are app roots in a "
+                  + "previous compilation unit:"
+                  + "\n  \tApp roots in previous compilation unit: ["
+                  + "dagger.hilt.processor.internal.root.MyAppPreviousCompilation.MyApp]"
+                  + "\n  \tApp roots in this compilation unit: [test.AppRoot]");
+    }
+  }
+}
diff --git a/javatests/dagger/hilt/processor/internal/root/MyTestPreviousCompilation.java b/javatests/dagger/hilt/processor/internal/root/MyTestPreviousCompilation.java
new file mode 100644
index 0000000..f704df5
--- /dev/null
+++ b/javatests/dagger/hilt/processor/internal/root/MyTestPreviousCompilation.java
@@ -0,0 +1,28 @@
+/*
+ * Copyright (C) 2021 The Dagger Authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package dagger.hilt.processor.internal.root;
+
+import dagger.hilt.android.testing.HiltAndroidTest;
+
+/** Defines a {@link HiltAndroidTest} for {@link MyTestPreviousCompilationTest}. */
+public final class MyTestPreviousCompilation {
+
+  @HiltAndroidTest
+  public static final class MyTest {}
+
+  private MyTestPreviousCompilation() {}
+}
diff --git a/javatests/dagger/hilt/processor/internal/root/MyTestPreviousCompilationTest.java b/javatests/dagger/hilt/processor/internal/root/MyTestPreviousCompilationTest.java
new file mode 100644
index 0000000..9e9fae5
--- /dev/null
+++ b/javatests/dagger/hilt/processor/internal/root/MyTestPreviousCompilationTest.java
@@ -0,0 +1,109 @@
+/*
+ * Copyright (C) 2021 The Dagger Authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package dagger.hilt.processor.internal.root;
+
+import static com.google.testing.compile.CompilationSubject.assertThat;
+
+import com.google.common.collect.ImmutableCollection;
+import com.google.common.collect.ImmutableList;
+import com.google.testing.compile.Compilation;
+import com.google.testing.compile.Compiler;
+import com.google.testing.compile.JavaFileObjects;
+import dagger.hilt.android.processor.AndroidCompilers;
+import javax.tools.JavaFileObject;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+import org.junit.runners.Parameterized.Parameters;
+
+@RunWith(Parameterized.class)
+public final class MyTestPreviousCompilationTest {
+
+  @Parameters(name = "{0}")
+  public static ImmutableCollection<Object[]> parameters() {
+    return ImmutableList.copyOf(new Object[][] {{true}, {false}});
+  }
+
+  private final boolean disableCrossCompilationRootValidation;
+
+  public MyTestPreviousCompilationTest(boolean disableCrossCompilationRootValidation) {
+    this.disableCrossCompilationRootValidation = disableCrossCompilationRootValidation;
+  }
+
+  private Compiler compiler() {
+    return AndroidCompilers.compiler()
+        .withOptions(
+            String.format(
+                "-Adagger.hilt.disableCrossCompilationRootValidation=%s",
+                disableCrossCompilationRootValidation));
+  }
+
+  @Test
+  public void testRootTest() {
+    JavaFileObject testRoot =
+        JavaFileObjects.forSourceLines(
+            "test.TestRoot",
+            "package test;",
+            "",
+            "import dagger.hilt.android.testing.HiltAndroidTest;",
+            "",
+            "@HiltAndroidTest",
+            "public class TestRoot {}");
+
+    Compilation compilation = compiler().compile(testRoot);
+    if (disableCrossCompilationRootValidation) {
+      assertThat(compilation).succeeded();
+    } else {
+      assertThat(compilation).failed();
+      assertThat(compilation).hadErrorCount(1);
+      assertThat(compilation)
+          .hadErrorContaining(
+              "Cannot process new roots when there are test roots from a previous compilation unit:"
+                  + "\n  \tTest roots from previous compilation unit: "
+                  + "[dagger.hilt.processor.internal.root.MyTestPreviousCompilation.MyTest]"
+                  + "\n  \tAll roots from this compilation unit: [test.TestRoot]");
+    }
+  }
+
+  @Test
+  public void appRootTest() {
+    JavaFileObject appRoot =
+        JavaFileObjects.forSourceLines(
+            "test.AppRoot",
+            "package test;",
+            "",
+            "import android.app.Application;",
+            "import dagger.hilt.android.HiltAndroidApp;",
+            "",
+            "@HiltAndroidApp(Application.class)",
+            "public class AppRoot extends Hilt_AppRoot {}");
+
+    Compilation compilation = compiler().compile(appRoot);
+    if (disableCrossCompilationRootValidation) {
+      assertThat(compilation).succeeded();
+    } else {
+      assertThat(compilation).failed();
+      assertThat(compilation).hadErrorCount(1);
+      assertThat(compilation)
+          .hadErrorContaining(
+              "Cannot process new roots when there are test roots from a previous compilation unit:"
+                  + "\n  \tTest roots from previous compilation unit: "
+                  + "[dagger.hilt.processor.internal.root.MyTestPreviousCompilation.MyTest]"
+                  + "\n  \tAll roots from this compilation unit: [test.AppRoot]");
+    }
+  }
+}
diff --git a/javatests/dagger/hilt/processor/internal/root/RootFileFormatterTest.java b/javatests/dagger/hilt/processor/internal/root/RootFileFormatterTest.java
new file mode 100644
index 0000000..6d598c8
--- /dev/null
+++ b/javatests/dagger/hilt/processor/internal/root/RootFileFormatterTest.java
@@ -0,0 +1,173 @@
+/*
+ * Copyright (C) 2020 The Dagger Authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package dagger.hilt.processor.internal.root;
+
+import static com.google.testing.compile.CompilationSubject.assertThat;
+import static dagger.hilt.android.processor.AndroidCompilers.compiler;
+
+import com.google.common.base.Joiner;
+import com.google.testing.compile.Compilation;
+import com.google.testing.compile.JavaFileObjects;
+import javax.tools.JavaFileObject;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.JUnit4;
+
+// This test makes sure we don't regress the formatting in the components file.
+@RunWith(JUnit4.class)
+public final class RootFileFormatterTest {
+  private static final Joiner JOINER = Joiner.on("\n");
+
+  @Test
+  public void testProdComponents() {
+    Compilation compilation =
+        compiler()
+            .compile(
+                JavaFileObjects.forSourceLines(
+                    "test.TestApplication",
+                    "package test;",
+                    "",
+                    "import android.app.Application;",
+                    "import dagger.hilt.android.HiltAndroidApp;",
+                    "",
+                    "@HiltAndroidApp(Application.class)",
+                    "public class TestApplication extends Hilt_TestApplication {}"),
+                entryPoint("SingletonComponent", "EntryPoint1"),
+                entryPoint("SingletonComponent", "EntryPoint2"),
+                entryPoint("ActivityComponent", "EntryPoint3"),
+                entryPoint("ActivityComponent", "EntryPoint4"));
+    assertThat(compilation).succeeded();
+    assertThat(compilation)
+        .generatedSourceFile("test/TestApplication_HiltComponents")
+        .contentsAsUtf8String()
+        .contains(
+            JOINER.join(
+                "  public abstract static class SingletonC implements"
+                + " HiltWrapper_ActivityRetainedComponentManager"
+                + "_ActivityRetainedComponentBuilderEntryPoint,",
+                "      ServiceComponentManager.ServiceComponentBuilderEntryPoint,",
+                "      SingletonComponent,",
+                "      GeneratedComponent,",
+                "      EntryPoint1,",
+                "      EntryPoint2,",
+                "      TestApplication_GeneratedInjector {"));
+
+    assertThat(compilation)
+        .generatedSourceFile("test/TestApplication_HiltComponents")
+        .contentsAsUtf8String()
+        .contains(
+            JOINER.join(
+                "  public abstract static class ActivityC implements ActivityComponent,",
+                "      FragmentComponentManager.FragmentComponentBuilderEntryPoint,",
+                "      ViewComponentManager.ViewComponentBuilderEntryPoint,",
+                "      GeneratedComponent,",
+                "      EntryPoint3,",
+                "      EntryPoint4 {"));
+  }
+
+  @Test
+  public void testTestComponents() {
+    Compilation compilation =
+        compiler()
+            .compile(
+                JavaFileObjects.forSourceLines(
+                    "test.MyTest",
+                    "package test;",
+                    "",
+                    "import dagger.hilt.android.testing.HiltAndroidTest;",
+                    "",
+                    "@HiltAndroidTest",
+                    "public class MyTest {}"),
+                entryPoint("SingletonComponent", "EntryPoint1"),
+                entryPoint("SingletonComponent", "EntryPoint2"),
+                entryPoint("ActivityComponent", "EntryPoint3"),
+                entryPoint("ActivityComponent", "EntryPoint4"));
+    assertThat(compilation).succeeded();
+    assertThat(compilation)
+        .generatedSourceFile("test/MyTest_HiltComponents")
+        .contentsAsUtf8String()
+        .contains(
+            JOINER.join(
+                "  public abstract static class SingletonC implements"
+                + " HiltWrapper_ActivityRetainedComponentManager"
+                + "_ActivityRetainedComponentBuilderEntryPoint,",
+                "      ServiceComponentManager.ServiceComponentBuilderEntryPoint,",
+                "      SingletonComponent,",
+                "      TestSingletonComponent,",
+                "      EntryPoint1,",
+                "      EntryPoint2,",
+                "      MyTest_GeneratedInjector {"));
+
+    assertThat(compilation)
+        .generatedSourceFile("test/MyTest_HiltComponents")
+        .contentsAsUtf8String()
+        .contains(
+            JOINER.join(
+                "  public abstract static class ActivityC implements ActivityComponent,",
+                "      FragmentComponentManager.FragmentComponentBuilderEntryPoint,",
+                "      ViewComponentManager.ViewComponentBuilderEntryPoint,",
+                "      GeneratedComponent,",
+                "      EntryPoint3,",
+                "      EntryPoint4 {"));
+  }
+
+  @Test
+  public void testSharedTestComponents() {
+    Compilation compilation =
+        compiler()
+            .withOptions("-Adagger.hilt.shareTestComponents=true")
+            .compile(
+                JavaFileObjects.forSourceLines(
+                    "test.MyTest",
+                    "package test;",
+                    "",
+                    "import dagger.hilt.android.testing.HiltAndroidTest;",
+                    "",
+                    "@HiltAndroidTest",
+                    "public class MyTest {}"),
+                entryPoint("SingletonComponent", "EntryPoint1"));
+    assertThat(compilation).succeeded();
+    assertThat(compilation)
+        .generatedSourceFile("dagger/hilt/android/internal/testing/root/Default_HiltComponents")
+        .contentsAsUtf8String()
+        .contains(
+            JOINER.join(
+                "  public abstract static class SingletonC implements"
+                + " HiltWrapper_ActivityRetainedComponentManager"
+                + "_ActivityRetainedComponentBuilderEntryPoint,",
+                "      ServiceComponentManager.ServiceComponentBuilderEntryPoint,",
+                "      SingletonComponent,",
+                "      TestSingletonComponent,",
+                "      EntryPoint1,",
+                "      MyTest_GeneratedInjector {"));
+  }
+
+  private static JavaFileObject entryPoint(String component, String name) {
+    return JavaFileObjects.forSourceLines(
+        "test." + name,
+        "package test;",
+        "",
+        "import dagger.hilt.EntryPoint;",
+        "import dagger.hilt.InstallIn;",
+        component.equals("SingletonComponent") ? "import dagger.hilt.components.SingletonComponent;"
+            : "import dagger.hilt.android.components." + component + ";",
+        "",
+        "@EntryPoint",
+        "@InstallIn(" + component + ".class)",
+        "public interface " + name + " {}");
+  }
+}
diff --git a/javatests/dagger/hilt/processor/internal/root/RootProcessorErrorsTest.java b/javatests/dagger/hilt/processor/internal/root/RootProcessorErrorsTest.java
new file mode 100644
index 0000000..e9ed8c4
--- /dev/null
+++ b/javatests/dagger/hilt/processor/internal/root/RootProcessorErrorsTest.java
@@ -0,0 +1,122 @@
+/*
+ * Copyright (C) 2021 The Dagger Authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package dagger.hilt.processor.internal.root;
+
+import static com.google.testing.compile.CompilationSubject.assertThat;
+
+import com.google.common.collect.ImmutableCollection;
+import com.google.common.collect.ImmutableList;
+import com.google.testing.compile.Compilation;
+import com.google.testing.compile.Compiler;
+import com.google.testing.compile.JavaFileObjects;
+import dagger.hilt.android.processor.AndroidCompilers;
+import javax.tools.JavaFileObject;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+import org.junit.runners.Parameterized.Parameters;
+
+@RunWith(Parameterized.class)
+public final class RootProcessorErrorsTest {
+
+  @Parameters(name = "{0}")
+  public static ImmutableCollection<Object[]> parameters() {
+    return ImmutableList.copyOf(new Object[][] {{true}, {false}});
+  }
+
+  private final boolean disableCrossCompilationRootValidation;
+
+  public RootProcessorErrorsTest(boolean disableCrossCompilationRootValidation) {
+    this.disableCrossCompilationRootValidation = disableCrossCompilationRootValidation;
+  }
+
+  private Compiler compiler() {
+    return AndroidCompilers.compiler()
+        .withOptions(
+            String.format(
+                "-Adagger.hilt.disableCrossCompilationRootValidation=%s",
+                disableCrossCompilationRootValidation));
+  }
+
+  @Test
+  public void multipleAppRootsTest() {
+    JavaFileObject appRoot1 =
+        JavaFileObjects.forSourceLines(
+            "test.AppRoot1",
+            "package test;",
+            "",
+            "import android.app.Application;",
+            "import dagger.hilt.android.HiltAndroidApp;",
+            "",
+            "@HiltAndroidApp(Application.class)",
+            "public class AppRoot1 extends Hilt_AppRoot1 {}");
+
+    JavaFileObject appRoot2 =
+        JavaFileObjects.forSourceLines(
+            "test.AppRoot2",
+            "package test;",
+            "",
+            "import android.app.Application;",
+            "import dagger.hilt.android.HiltAndroidApp;",
+            "",
+            "@HiltAndroidApp(Application.class)",
+            "public class AppRoot2 extends Hilt_AppRoot2 {}");
+
+    // This test case should fail independent of disableCrossCompilationRootValidation.
+    Compilation compilation = compiler().compile(appRoot1, appRoot2);
+    assertThat(compilation).failed();
+    assertThat(compilation).hadErrorCount(1);
+    assertThat(compilation)
+        .hadErrorContaining(
+            "Cannot process multiple app roots in the same compilation unit: "
+                + "[test.AppRoot1, test.AppRoot2]");
+  }
+
+  @Test
+  public void appRootWithTestRootTest() {
+    JavaFileObject appRoot =
+        JavaFileObjects.forSourceLines(
+            "test.AppRoot",
+            "package test;",
+            "",
+            "import android.app.Application;",
+            "import dagger.hilt.android.HiltAndroidApp;",
+            "",
+            "@HiltAndroidApp(Application.class)",
+            "public class AppRoot extends Hilt_AppRoot {}");
+
+    JavaFileObject testRoot =
+        JavaFileObjects.forSourceLines(
+            "test.TestRoot",
+            "package test;",
+            "",
+            "import dagger.hilt.android.testing.HiltAndroidTest;",
+            "",
+            "@HiltAndroidTest",
+            "public class TestRoot {}");
+
+    // This test case should fail independent of disableCrossCompilationRootValidation.
+    Compilation compilation = compiler().compile(appRoot, testRoot);
+    assertThat(compilation).failed();
+    assertThat(compilation).hadErrorCount(1);
+    assertThat(compilation)
+        .hadErrorContaining(
+            "Cannot process test roots and app roots in the same compilation unit:"
+                + "\n  \tApp root in this compilation unit: [test.AppRoot]"
+                + "\n  \tTest roots in this compilation unit: [test.TestRoot]");
+  }
+}
diff --git a/javatests/dagger/hilt/processor/internal/uninstallmodules/BUILD b/javatests/dagger/hilt/processor/internal/uninstallmodules/BUILD
new file mode 100644
index 0000000..caceb7c
--- /dev/null
+++ b/javatests/dagger/hilt/processor/internal/uninstallmodules/BUILD
@@ -0,0 +1,44 @@
+# Copyright (C) 2020 The Dagger Authors.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+# Description:
+#   Tests for internal code for implementing Hilt processors.
+
+load("//java/dagger/testing/compile:macros.bzl", "compiler_test")
+
+package(default_visibility = ["//:src"])
+
+compiler_test(
+    name = "UninstallModulesProcessorTest",
+    srcs = ["UninstallModulesProcessorTest.java"],
+    compiler_deps = [
+        "//java/dagger/hilt/android:android_entry_point",
+        "//java/dagger/hilt/android/testing:hilt_android_test",
+        "//java/dagger/hilt/android/testing:uninstall_modules",
+        "//java/dagger/hilt/migration:disable_install_in_check",
+        "@androidsdk//:platforms/android-30/android.jar",
+        "@maven//:androidx_annotation_annotation",
+    ],
+    deps = [
+        "//javatests/dagger/hilt/android/processor:android_compilers",
+        "@google_bazel_common//third_party/java/compile_testing",
+        "@google_bazel_common//third_party/java/junit",
+        "@google_bazel_common//third_party/java/truth",
+    ],
+)
+
+filegroup(
+    name = "srcs_filegroup",
+    srcs = glob(["*"]),
+)
diff --git a/javatests/dagger/hilt/processor/internal/uninstallmodules/UninstallModulesProcessorTest.java b/javatests/dagger/hilt/processor/internal/uninstallmodules/UninstallModulesProcessorTest.java
new file mode 100644
index 0000000..2be9ab6
--- /dev/null
+++ b/javatests/dagger/hilt/processor/internal/uninstallmodules/UninstallModulesProcessorTest.java
@@ -0,0 +1,162 @@
+/*
+ * Copyright (C) 2020 The Dagger Authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package dagger.hilt.processor.internal.uninstallmodules;
+
+import static com.google.testing.compile.CompilationSubject.assertThat;
+import static dagger.hilt.android.processor.AndroidCompilers.compiler;
+
+import com.google.testing.compile.Compilation;
+import com.google.testing.compile.JavaFileObjects;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.JUnit4;
+
+@RunWith(JUnit4.class)
+public class UninstallModulesProcessorTest {
+
+  @Test
+  public void testInvalidModuleNoInstallIn_fails() {
+    Compilation compilation =
+        compiler()
+            .compile(
+                JavaFileObjects.forSourceLines(
+                    "test.MyTest",
+                    "package test;",
+                    "",
+                    "import dagger.hilt.android.testing.HiltAndroidTest;",
+                    "import dagger.hilt.android.testing.UninstallModules;",
+                    "",
+                    "@UninstallModules(InvalidModule.class)",
+                    "@HiltAndroidTest",
+                    "public class MyTest {}"),
+                JavaFileObjects.forSourceLines(
+                    "test.InvalidModule",
+                    "package test;",
+                    "",
+                    "import dagger.Module;",
+                    "import dagger.hilt.migration.DisableInstallInCheck;",
+                    "",
+                    "@DisableInstallInCheck",
+                    "@Module",
+                    "public class InvalidModule {}"));
+    assertThat(compilation).failed();
+    assertThat(compilation).hadErrorCount(1);
+    assertThat(compilation)
+        .hadErrorContaining(
+            "@UninstallModules should only include modules annotated with both @Module and "
+                + "@InstallIn, but found: [test.InvalidModule].");
+  }
+
+  @Test
+  public void testInvalidModuleNoModule_fails() {
+    Compilation compilation =
+        compiler()
+            .compile(
+                JavaFileObjects.forSourceLines(
+                    "test.MyTest",
+                    "package test;",
+                    "",
+                    "import dagger.hilt.android.testing.HiltAndroidTest;",
+                    "import dagger.hilt.android.testing.UninstallModules;",
+                    "",
+                    "@UninstallModules(InvalidModule.class)",
+                    "@HiltAndroidTest",
+                    "public class MyTest {}"),
+                JavaFileObjects.forSourceLines(
+                    "test.InvalidModule",
+                    "package test;",
+                    "",
+                    "public class InvalidModule {",
+                    "}"));
+    assertThat(compilation).failed();
+    assertThat(compilation).hadErrorCount(1);
+    assertThat(compilation)
+        .hadErrorContaining(
+            "@UninstallModules should only include modules annotated with both @Module and "
+                + "@InstallIn, but found: [test.InvalidModule].");
+  }
+
+  @Test
+  public void testInvalidTest_fails() {
+    Compilation compilation =
+        compiler()
+            .compile(
+                JavaFileObjects.forSourceLines(
+                    "test.InvalidTest",
+                    "package test;",
+                    "",
+                    "import dagger.hilt.android.testing.UninstallModules;",
+                    "",
+                    "@UninstallModules(ValidModule.class)",
+                    "public class InvalidTest {}"),
+                JavaFileObjects.forSourceLines(
+                    "test.ValidModule",
+                    "package test;",
+                    "",
+                    "import dagger.Module;",
+                    "import dagger.hilt.InstallIn;",
+                    "import dagger.hilt.components.SingletonComponent;",
+                    "",
+                    "@Module",
+                    "@InstallIn(SingletonComponent.class)",
+                    "public class ValidModule {}"));
+    assertThat(compilation).failed();
+    assertThat(compilation).hadErrorCount(1);
+    assertThat(compilation)
+        .hadErrorContaining(
+            "@UninstallModules should only be used on test classes annotated with @HiltAndroidTest,"
+                + " but found: test.InvalidTest");
+  }
+
+  @Test
+  public void testInvalidTestModule_fails() {
+    Compilation compilation =
+        compiler()
+            .compile(
+                JavaFileObjects.forSourceLines(
+                    "test.MyTest",
+                    "package test;",
+                    "",
+                    "import dagger.Module;",
+                    "import dagger.hilt.InstallIn;",
+                    "import dagger.hilt.components.SingletonComponent;",
+                    "import dagger.hilt.android.testing.HiltAndroidTest;",
+                    "import dagger.hilt.android.testing.UninstallModules;",
+                    "",
+                    "@UninstallModules({",
+                    "    MyTest.PkgPrivateInvalidModule.class,",
+                    "    MyTest.PublicInvalidModule.class,",
+                    "})",
+                    "@HiltAndroidTest",
+                    "public class MyTest {",
+                    "  @Module",
+                    "  @InstallIn(SingletonComponent.class)",
+                    "  interface PkgPrivateInvalidModule {}",
+                    "",
+                    "  @Module",
+                    "  @InstallIn(SingletonComponent.class)",
+                    "  public interface PublicInvalidModule {}",
+                    "}"));
+    assertThat(compilation).failed();
+    assertThat(compilation).hadErrorCount(1);
+    // TODO(bcorso): Consider unwrapping pkg-private modules before reporting the error.
+    assertThat(compilation)
+        .hadErrorContaining(
+            "@UninstallModules should not contain test modules, but found: "
+                + "[test.MyTest.PkgPrivateInvalidModule, test.MyTest.PublicInvalidModule]");
+  }
+}
diff --git a/javatests/dagger/hilt/testmodules/BUILD b/javatests/dagger/hilt/testmodules/BUILD
new file mode 100644
index 0000000..7acd3d1
--- /dev/null
+++ b/javatests/dagger/hilt/testmodules/BUILD
@@ -0,0 +1,37 @@
+# Copyright (C) 2021 The Dagger Authors.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+# Description:
+#  Builds and run tests related to AggregatedDepsProcessor.
+
+load("@io_bazel_rules_kotlin//kotlin:kotlin.bzl", "kt_android_library")
+
+package(default_visibility = ["//:src"])
+
+kt_android_library(
+    name = "testmodules",
+    testonly = True,
+    srcs = glob(["*.kt"]),
+    deps = [
+        "//:dagger_with_compiler",
+        "//java/dagger/hilt:install_in",
+        "//java/dagger/hilt/components",
+        "@google_bazel_common//third_party/java/jsr330_inject",
+    ],
+)
+
+filegroup(
+    name = "srcs_filegroup",
+    srcs = glob(["*"]),
+)
diff --git a/javatests/dagger/hilt/testmodules/InternalKtTestModule.kt b/javatests/dagger/hilt/testmodules/InternalKtTestModule.kt
new file mode 100644
index 0000000..0a71e44
--- /dev/null
+++ b/javatests/dagger/hilt/testmodules/InternalKtTestModule.kt
@@ -0,0 +1,41 @@
+/*
+ * Copyright (C) 2021 The Dagger Authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package dagger.hilt.testmodules
+
+import dagger.Module
+import dagger.Provides
+import dagger.hilt.InstallIn
+import dagger.hilt.components.SingletonComponent
+import javax.inject.Singleton
+
+/** Module for [dagger.hilt.android.InternalKtModuleTest]. */
+@Module
+@InstallIn(SingletonComponent::class)
+internal abstract class InternalKtTestModule private constructor() {
+  companion object {
+    @Provides
+    @Singleton
+    fun provideString(): String = "expected_string_value"
+  }
+}
+
+@Module
+@InstallIn(SingletonComponent::class)
+internal object InternalKtObjectModule {
+  @Provides
+  fun provideString(): Int = 9
+}
diff --git a/javatests/dagger/internal/codegen/AssistedFactoryErrorsTest.java b/javatests/dagger/internal/codegen/AssistedFactoryErrorsTest.java
index 2bcfedc..42b8f8a 100644
--- a/javatests/dagger/internal/codegen/AssistedFactoryErrorsTest.java
+++ b/javatests/dagger/internal/codegen/AssistedFactoryErrorsTest.java
@@ -570,6 +570,56 @@
   }
 
   @Test
+  public void testProvidesAssistedBindingsAsOptional() {
+    JavaFileObject foo =
+        JavaFileObjects.forSourceLines(
+            "test.Foo",
+            "package test;",
+            "",
+            "import dagger.assisted.Assisted;",
+            "import dagger.assisted.AssistedInject;",
+            "import dagger.assisted.AssistedFactory;",
+            "",
+            "class Foo {",
+            "  @AssistedInject Foo() {}",
+            "",
+            "  @AssistedFactory",
+            "  interface Factory {",
+            "    Foo create();",
+            "  }",
+            "}");
+
+    JavaFileObject module =
+        JavaFileObjects.forSourceLines(
+            "test.FooModule",
+            "package test;",
+            "",
+            "import dagger.BindsOptionalOf;",
+            "import dagger.Module;",
+            "import dagger.Provides;",
+            "",
+            "@Module",
+            "interface FooModule {",
+            "  @BindsOptionalOf Foo optionalFoo();",
+            "",
+            "  @BindsOptionalOf Foo.Factory optionalFooFactory();",
+            "}");
+
+    Compilation compilation = compilerWithOptions(compilerMode.javacopts()).compile(foo, module);
+    assertThat(compilation).failed();
+    assertThat(compilation).hadErrorCount(2);
+    assertThat(compilation)
+        .hadErrorContaining("[test.Foo] Dagger does not support providing @AssistedInject types.")
+        .inFile(module)
+        .onLine(9);
+    assertThat(compilation)
+        .hadErrorContaining(
+            "[test.Foo.Factory] Dagger does not support providing @AssistedFactory types.")
+        .inFile(module)
+        .onLine(11);
+  }
+
+  @Test
   public void testInjectsProviderOfAssistedFactory() {
     JavaFileObject foo =
         JavaFileObjects.forSourceLines(
diff --git a/javatests/dagger/internal/codegen/AssistedFactoryTest.java b/javatests/dagger/internal/codegen/AssistedFactoryTest.java
index d752321..cffd42e 100644
--- a/javatests/dagger/internal/codegen/AssistedFactoryTest.java
+++ b/javatests/dagger/internal/codegen/AssistedFactoryTest.java
@@ -20,7 +20,6 @@
 import static dagger.internal.codegen.CompilerMode.DEFAULT_MODE;
 import static dagger.internal.codegen.CompilerMode.FAST_INIT_MODE;
 import static dagger.internal.codegen.Compilers.compilerWithOptions;
-import static dagger.internal.codegen.GeneratedLines.GENERATED_CODE_ANNOTATIONS;
 
 import com.google.common.collect.ImmutableCollection;
 import com.google.testing.compile.Compilation;
@@ -96,7 +95,7 @@
     JavaFileObject generatedComponent =
         compilerMode
             .javaFileBuilder("test.DaggerTestComponent")
-            .addLines("package test;", "", GENERATED_CODE_ANNOTATIONS)
+            .addLines("package test;", "", GeneratedLines.generatedAnnotations())
             .addLinesIn(
                 FAST_INIT_MODE,
                 "final class DaggerTestComponent implements TestComponent {",
@@ -192,7 +191,7 @@
     JavaFileObject generatedComponent =
         compilerMode
             .javaFileBuilder("test.DaggerTestComponent")
-            .addLines("package test;", "", GENERATED_CODE_ANNOTATIONS)
+            .addLines("package test;", "", GeneratedLines.generatedAnnotations())
             .addLinesIn(
                 FAST_INIT_MODE,
                 "final class DaggerTestComponent implements TestComponent {",
diff --git a/javatests/dagger/internal/codegen/ComponentBuilderTest.java b/javatests/dagger/internal/codegen/ComponentBuilderTest.java
index acf5437..c4e0e07 100644
--- a/javatests/dagger/internal/codegen/ComponentBuilderTest.java
+++ b/javatests/dagger/internal/codegen/ComponentBuilderTest.java
@@ -18,7 +18,6 @@
 
 import static com.google.testing.compile.CompilationSubject.assertThat;
 import static dagger.internal.codegen.Compilers.compilerWithOptions;
-import static dagger.internal.codegen.GeneratedLines.GENERATED_CODE_ANNOTATIONS;
 import static dagger.internal.codegen.binding.ComponentCreatorAnnotation.COMPONENT_BUILDER;
 import static dagger.internal.codegen.binding.ErrorMessages.creatorMessagesFor;
 
@@ -88,7 +87,7 @@
             "",
             "import dagger.internal.Preconditions;",
             "",
-            GENERATED_CODE_ANNOTATIONS,
+            GeneratedLines.generatedAnnotations(),
             "final class DaggerTestComponent implements TestComponent {",
             "  private static final class Builder implements TestComponent.Builder {",
             "    private TestModule testModule;",
diff --git a/javatests/dagger/internal/codegen/ComponentCreatorTest.java b/javatests/dagger/internal/codegen/ComponentCreatorTest.java
index 0a7e40e..3cf05ac 100644
--- a/javatests/dagger/internal/codegen/ComponentCreatorTest.java
+++ b/javatests/dagger/internal/codegen/ComponentCreatorTest.java
@@ -23,8 +23,6 @@
 import static dagger.internal.codegen.Compilers.compilerWithOptions;
 import static dagger.internal.codegen.Compilers.daggerCompiler;
 import static dagger.internal.codegen.ComponentCreatorTest.CompilerType.JAVAC;
-import static dagger.internal.codegen.GeneratedLines.GENERATED_CODE_ANNOTATIONS;
-import static dagger.internal.codegen.GeneratedLines.IMPORT_GENERATED_ANNOTATION;
 import static dagger.internal.codegen.binding.ComponentCreatorAnnotation.COMPONENT_BUILDER;
 import static dagger.internal.codegen.binding.ComponentCreatorAnnotation.COMPONENT_FACTORY;
 import static dagger.internal.codegen.binding.ComponentCreatorKind.BUILDER;
@@ -106,7 +104,7 @@
             "test.DaggerSimpleComponent",
             "package test;",
             "",
-            GENERATED_CODE_ANNOTATIONS,
+            GeneratedLines.generatedAnnotations(),
             "final class DaggerSimpleComponent implements SimpleComponent {",
             "  private static final class Builder implements SimpleComponent.Builder {",
             "    @Override",
@@ -159,9 +157,9 @@
             "test.DaggerTestComponent",
             "package test;",
             "",
-            IMPORT_GENERATED_ANNOTATION,
+            GeneratedLines.generatedImports(),
             "",
-            GENERATED_CODE_ANNOTATIONS,
+            GeneratedLines.generatedAnnotations(),
             "final class DaggerTestComponent implements TestComponent {",
             "  private final TestModule testModule;",
             "",
@@ -357,10 +355,9 @@
             .addLines(
                 "package test;",
                 "",
-                "import dagger.internal.Preconditions;",
-                IMPORT_GENERATED_ANNOTATION,
+                GeneratedLines.generatedImports("import dagger.internal.Preconditions;"),
                 "",
-                GENERATED_CODE_ANNOTATIONS,
+                GeneratedLines.generatedAnnotations(),
                 "final class DaggerSimpleComponent implements SimpleComponent {",
                 "  private final Object object;",
                 "",
@@ -459,10 +456,9 @@
             .addLines(
                 "package test;",
                 "",
-                "import dagger.internal.Preconditions;",
-                IMPORT_GENERATED_ANNOTATION,
+                GeneratedLines.generatedImports("import dagger.internal.Preconditions;"),
                 "",
-                GENERATED_CODE_ANNOTATIONS,
+                GeneratedLines.generatedAnnotations(),
                 "final class DaggerSimpleComponent implements SimpleComponent {",
                 "  private final Integer i;",
                 "",
diff --git a/javatests/dagger/internal/codegen/ComponentFactoryTest.java b/javatests/dagger/internal/codegen/ComponentFactoryTest.java
index 0f155b9..35cbb6a 100644
--- a/javatests/dagger/internal/codegen/ComponentFactoryTest.java
+++ b/javatests/dagger/internal/codegen/ComponentFactoryTest.java
@@ -18,7 +18,6 @@
 
 import static com.google.testing.compile.CompilationSubject.assertThat;
 import static dagger.internal.codegen.Compilers.compilerWithOptions;
-import static dagger.internal.codegen.GeneratedLines.GENERATED_CODE_ANNOTATIONS;
 import static dagger.internal.codegen.binding.ComponentCreatorAnnotation.COMPONENT_FACTORY;
 import static dagger.internal.codegen.binding.ErrorMessages.creatorMessagesFor;
 
@@ -87,7 +86,7 @@
             "",
             "import dagger.internal.Preconditions;",
             "",
-            GENERATED_CODE_ANNOTATIONS,
+            GeneratedLines.generatedAnnotations(),
             "final class DaggerTestComponent implements TestComponent {",
             "  private static final class Factory implements TestComponent.Factory {",
             "    @Override",
diff --git a/javatests/dagger/internal/codegen/ComponentProcessorTest.java b/javatests/dagger/internal/codegen/ComponentProcessorTest.java
index eee6a0c..3e514c8 100644
--- a/javatests/dagger/internal/codegen/ComponentProcessorTest.java
+++ b/javatests/dagger/internal/codegen/ComponentProcessorTest.java
@@ -22,8 +22,6 @@
 import static dagger.internal.codegen.CompilerMode.FAST_INIT_MODE;
 import static dagger.internal.codegen.Compilers.compilerWithOptions;
 import static dagger.internal.codegen.Compilers.daggerCompiler;
-import static dagger.internal.codegen.GeneratedLines.GENERATED_CODE_ANNOTATIONS;
-import static dagger.internal.codegen.GeneratedLines.IMPORT_GENERATED_ANNOTATION;
 
 import com.google.auto.common.MoreElements;
 import com.google.common.base.Predicate;
@@ -177,12 +175,12 @@
             .addLines(
                 "package test;",
                 "",
-                "import dagger.Lazy;",
-                "import dagger.internal.DoubleCheck;",
-                IMPORT_GENERATED_ANNOTATION,
-                "import javax.inject.Provider;",
+                GeneratedLines.generatedImports(
+                    "import dagger.Lazy;",
+                    "import dagger.internal.DoubleCheck;",
+                    "import javax.inject.Provider;"),
                 "",
-                GENERATED_CODE_ANNOTATIONS,
+                GeneratedLines.generatedAnnotations(),
                 "final class DaggerSimpleComponent implements SimpleComponent {")
             .addLinesIn(
                 FAST_INIT_MODE,
@@ -298,7 +296,7 @@
             .addLines(
                 "package test;",
                 "",
-                GENERATED_CODE_ANNOTATIONS,
+                GeneratedLines.generatedAnnotations(),
                 "final class DaggerSimpleComponent implements SimpleComponent {")
             .addLinesIn(
                 FAST_INIT_MODE,
@@ -417,7 +415,7 @@
             .addLines(
                 "package test;",
                 "",
-                GENERATED_CODE_ANNOTATIONS,
+                GeneratedLines.generatedAnnotations(),
                 "final class DaggerOuterType_SimpleComponent",
                 "    implements OuterType.SimpleComponent {",
                 "  private DaggerOuterType_SimpleComponent() {}",
@@ -498,10 +496,9 @@
             .addLines(
                 "package test;",
                 "",
-                "import dagger.internal.Preconditions;",
-                IMPORT_GENERATED_ANNOTATION,
+                GeneratedLines.generatedImports("import dagger.internal.Preconditions;"),
                 "",
-                GENERATED_CODE_ANNOTATIONS,
+                GeneratedLines.generatedAnnotations(),
                 "final class DaggerTestComponent implements TestComponent {",
                 "  private final TestModule testModule;",
                 "",
@@ -604,7 +601,7 @@
             .addLines(
                 "package test;",
                 "",
-                GENERATED_CODE_ANNOTATIONS,
+                GeneratedLines.generatedAnnotations(),
                 "final class DaggerTestComponent implements TestComponent {",
                 "  private B b() {",
                 "    return TestModule_BFactory.b(new C());",
@@ -698,10 +695,9 @@
         "test.DaggerTestComponent",
         "package test;",
         "",
-        "import dagger.internal.Preconditions;",
-        IMPORT_GENERATED_ANNOTATION,
+        GeneratedLines.generatedImports("import dagger.internal.Preconditions;"),
         "",
-        GENERATED_CODE_ANNOTATIONS,
+        GeneratedLines.generatedAnnotations(),
         "final class DaggerTestComponent implements TestComponent {",
         "  static final class Builder {",
         "",
@@ -882,10 +878,9 @@
             "test.DaggerParent",
             "package test;",
             "",
-            "import dagger.internal.Preconditions;",
-            IMPORT_GENERATED_ANNOTATION,
+            GeneratedLines.generatedImports("import dagger.internal.Preconditions;"),
             "",
-            GENERATED_CODE_ANNOTATIONS,
+            GeneratedLines.generatedAnnotations(),
             "final class DaggerParent implements Parent {",
             "",
             "  private DaggerParent() {}",
@@ -997,7 +992,7 @@
                 "",
                 "import com.google.errorprone.annotations.CanIgnoreReturnValue;",
                 "",
-                GENERATED_CODE_ANNOTATIONS,
+                GeneratedLines.generatedAnnotations(),
                 "final class DaggerSimpleComponent implements SimpleComponent {",
                 "  @Override",
                 "  public void inject(SomeInjectedType instance) {",
@@ -1053,7 +1048,7 @@
             "test.DaggerSimpleComponent",
             "package test;",
             "",
-            GENERATED_CODE_ANNOTATIONS,
+            GeneratedLines.generatedAnnotations(),
             "final class DaggerSimpleComponent implements SimpleComponent {",
             "  private Provider<SimpleComponent> simpleComponentProvider;",
             "",
@@ -1117,7 +1112,7 @@
                 "",
                 "import com.google.errorprone.annotations.CanIgnoreReturnValue;",
                 "",
-                GENERATED_CODE_ANNOTATIONS,
+                GeneratedLines.generatedAnnotations(),
                 "final class DaggerSimpleComponent implements SimpleComponent {",
                 "  @Override",
                 "  public SomeInjectedType createAndInject() {",
@@ -1185,7 +1180,7 @@
             .addLines(
                 "package test;",
                 "",
-                GENERATED_CODE_ANNOTATIONS,
+                GeneratedLines.generatedAnnotations(),
                 "final class DaggerBComponent implements BComponent {")
             .addLinesIn(DEFAULT_MODE, "  private Provider<A> aProvider;")
             .addLinesIn(
@@ -1232,7 +1227,7 @@
                 "  }")
             .addLinesIn(
                 DEFAULT_MODE,
-                "  private static class test_AComponent_a implements Provider<A> {",
+                "  private static final class test_AComponent_a implements Provider<A> {",
                 "    private final AComponent aComponent;",
                 "    ",
                 "    test_AComponent_a(AComponent aComponent) {",
@@ -1318,7 +1313,7 @@
             "test.DaggerTestComponent",
             "package test;",
             "",
-            GENERATED_CODE_ANNOTATIONS,
+            GeneratedLines.generatedAnnotations(),
             "final class DaggerTestComponent implements TestComponent {",
             "  private final TestModule testModule;",
             "  private final other.test.TestModule testModule2;",
@@ -1440,10 +1435,9 @@
             "test.DaggerBComponent",
             "package test;",
             "",
-            "import dagger.internal.Preconditions;",
-            IMPORT_GENERATED_ANNOTATION,
+            GeneratedLines.generatedImports("import dagger.internal.Preconditions;"),
             "",
-            GENERATED_CODE_ANNOTATIONS,
+            GeneratedLines.generatedAnnotations(),
             "final class DaggerBComponent implements BComponent {",
             "  private final AComponent aComponent;",
             "",
@@ -1525,9 +1519,7 @@
             .addLines(
                 "package test;",
                 "",
-                IMPORT_GENERATED_ANNOTATION,
-                "",
-                GENERATED_CODE_ANNOTATIONS,
+                GeneratedLines.generatedAnnotations(),
                 "final class DaggerTestComponent implements TestComponent {",
                 "  private B b() {",
                 "    return new B(new C());",
@@ -1605,9 +1597,9 @@
             "test.DaggerSimpleComponent",
             "package test;",
             "",
-            IMPORT_GENERATED_ANNOTATION,
+            GeneratedLines.generatedImports(),
             "",
-            GENERATED_CODE_ANNOTATIONS,
+            GeneratedLines.generatedAnnotations(),
             "final class DaggerSimpleComponent implements SimpleComponent {",
             "  private DaggerSimpleComponent() {}",
             "",
@@ -1676,7 +1668,7 @@
             "test.DaggerSimpleComponent",
             "package test;",
             "",
-            GENERATED_CODE_ANNOTATIONS,
+            GeneratedLines.generatedAnnotations(),
             "final class DaggerSimpleComponent implements SimpleComponent {",
             "  @Override",
             "  public SomeInjectableType someInjectableType() {",
@@ -2046,10 +2038,9 @@
             "test.DaggerParent",
             "package test;",
             "",
-            "import dagger.internal.Preconditions;",
-            IMPORT_GENERATED_ANNOTATION,
+            GeneratedLines.generatedImports("import dagger.internal.Preconditions;"),
             "",
-            GENERATED_CODE_ANNOTATIONS,
+            GeneratedLines.generatedAnnotations(),
             "final class DaggerParent implements Parent {",
             "  private DaggerParent() {",
             "  }",
@@ -2190,7 +2181,7 @@
                 "test.TestModule_NonNullableStringFactory",
                 "package test;",
                 "",
-                GENERATED_CODE_ANNOTATIONS,
+                GeneratedLines.generatedAnnotations(),
                 "public final class TestModule_NonNullableStringFactory",
                 "    implements Factory<String> {",
                 "  @Override",
@@ -2210,7 +2201,7 @@
             .addLines(
                 "package test;",
                 "",
-                GENERATED_CODE_ANNOTATIONS,
+                GeneratedLines.generatedAnnotations(),
                 "final class DaggerTestComponent implements TestComponent {",
                 "  @Override",
                 "  public String nonNullableString() {",
@@ -2280,7 +2271,7 @@
                 "test.TestModule_PrimitiveIntegerFactory",
                 "package test;",
                 "",
-                GENERATED_CODE_ANNOTATIONS,
+                GeneratedLines.generatedAnnotations(),
                 "public final class TestModule_PrimitiveIntegerFactory",
                 "    implements Factory<Integer> {",
                 "",
@@ -2300,7 +2291,7 @@
             .addLines(
                 "package test;",
                 "",
-                GENERATED_CODE_ANNOTATIONS,
+                GeneratedLines.generatedAnnotations(),
                 "final class DaggerTestComponent implements TestComponent {",
                 "  @Override",
                 "  public Integer nonNullableInteger() {",
@@ -2376,7 +2367,7 @@
         JavaFileObjects.forSourceLines(
             "test.DaggerParent",
             "package test;",
-            GENERATED_CODE_ANNOTATIONS,
+            GeneratedLines.generatedAnnotations(),
             "final class DaggerParent implements Parent {",
             "  private String string() {",
             "    return TestModule_StringFactory.string(numberProvider.get());",
@@ -2446,7 +2437,7 @@
         JavaFileObjects.forSourceLines(
             "test.DaggerParent",
             "package test;",
-            GENERATED_CODE_ANNOTATIONS,
+            GeneratedLines.generatedAnnotations(),
             "final class DaggerParent implements Parent {",
             "  private final class ChildImpl implements Child {",
             "    @Override",
@@ -2513,7 +2504,7 @@
             "test.DaggerTestComponent",
             "package test;",
             "",
-            GENERATED_CODE_ANNOTATIONS,
+            GeneratedLines.generatedAnnotations(),
             "final class DaggerTestComponent implements TestComponent {",
             "  @Override",
             "  public Injected injected() {",
@@ -2586,7 +2577,7 @@
             "test.DaggerTestComponent",
             "package test;",
             "",
-            GENERATED_CODE_ANNOTATIONS,
+            GeneratedLines.generatedAnnotations(),
             "final class DaggerTestComponent implements TestComponent {",
             "  @Override",
             "  public String unqualified() {",
@@ -2638,9 +2629,9 @@
                 "test.DaggerPublicComponent",
                 "package test;",
                 "",
-                IMPORT_GENERATED_ANNOTATION,
+                GeneratedLines.generatedImports(),
                 "",
-                GENERATED_CODE_ANNOTATIONS,
+                GeneratedLines.generatedAnnotations(),
                 "public final class DaggerPublicComponent implements PublicComponent {",
                 "  private DaggerPublicComponent() {}",
                 "",
diff --git a/javatests/dagger/internal/codegen/ComponentRequirementFieldTest.java b/javatests/dagger/internal/codegen/ComponentRequirementFieldTest.java
index 049ff85..05164a6 100644
--- a/javatests/dagger/internal/codegen/ComponentRequirementFieldTest.java
+++ b/javatests/dagger/internal/codegen/ComponentRequirementFieldTest.java
@@ -18,7 +18,6 @@
 
 import static com.google.testing.compile.CompilationSubject.assertThat;
 import static dagger.internal.codegen.Compilers.compilerWithOptions;
-import static dagger.internal.codegen.GeneratedLines.GENERATED_CODE_ANNOTATIONS;
 
 import com.google.testing.compile.Compilation;
 import com.google.testing.compile.JavaFileObjects;
@@ -75,7 +74,7 @@
                 "test.DaggerTestComponent",
                 "package test;",
                 "",
-                GENERATED_CODE_ANNOTATIONS,
+                GeneratedLines.generatedAnnotations(),
                 "final class DaggerTestComponent implements TestComponent {",
                 "  private final Integer i;",
                 "  private final List<String> list;",
@@ -172,7 +171,7 @@
             "import other.OtherPackageModule;",
             "import other.OtherPackageModule_LFactory;",
             "",
-            GENERATED_CODE_ANNOTATIONS,
+            GeneratedLines.generatedAnnotations(),
             "final class DaggerTestComponent implements TestComponent {",
             "  private final ParentModule parentModule;",
             "  private final OtherPackageModule otherPackageModule;",
@@ -244,7 +243,7 @@
                 "test.DaggerTestComponent",
                 "package test;",
                 "",
-                GENERATED_CODE_ANNOTATIONS,
+                GeneratedLines.generatedAnnotations(),
                 "final class DaggerTestComponent implements TestComponent {",
                 "  private final Dep dep;",
                 "",
@@ -357,7 +356,7 @@
                 "test.DaggerTestComponent",
                 "package test;",
                 "",
-                GENERATED_CODE_ANNOTATIONS,
+                GeneratedLines.generatedAnnotations(),
                 "final class DaggerTestComponent implements TestComponent {",
                 "  private final ParentModule parentModule;",
                 "",
@@ -385,7 +384,7 @@
                 "test.DaggerTestComponent",
                 "package test;",
                 "",
-                GENERATED_CODE_ANNOTATIONS,
+                GeneratedLines.generatedAnnotations(),
                 "final class DaggerTestComponent implements TestComponent {",
                 "  private final ParentModule parentModule;",
                 "",
diff --git a/javatests/dagger/internal/codegen/ComponentShardTest.java b/javatests/dagger/internal/codegen/ComponentShardTest.java
index fc59c92..ec7f499 100644
--- a/javatests/dagger/internal/codegen/ComponentShardTest.java
+++ b/javatests/dagger/internal/codegen/ComponentShardTest.java
@@ -18,7 +18,6 @@
 
 import static com.google.testing.compile.CompilationSubject.assertThat;
 import static com.google.testing.compile.Compiler.javac;
-import static dagger.internal.codegen.GeneratedLines.GENERATED_CODE_ANNOTATIONS;
 import static java.util.stream.Collectors.joining;
 
 import com.google.common.collect.ImmutableList;
@@ -89,7 +88,7 @@
         JavaFileObjects.forSourceLines(
             "dagger.internal.codegen.DaggerTestComponent",
                 "package dagger.internal.codegen;",
-            GENERATED_CODE_ANNOTATIONS,
+            GeneratedLines.generatedAnnotations(),
                 "final class DaggerTestComponent implements TestComponent {",
                 "  private final Shard1 shard1 = new Shard1();",
                 "",
diff --git a/javatests/dagger/internal/codegen/DelegateBindingExpressionTest.java b/javatests/dagger/internal/codegen/DelegateBindingExpressionTest.java
index bc729fc..dc8b758 100644
--- a/javatests/dagger/internal/codegen/DelegateBindingExpressionTest.java
+++ b/javatests/dagger/internal/codegen/DelegateBindingExpressionTest.java
@@ -20,7 +20,6 @@
 import static dagger.internal.codegen.CompilerMode.DEFAULT_MODE;
 import static dagger.internal.codegen.CompilerMode.FAST_INIT_MODE;
 import static dagger.internal.codegen.Compilers.compilerWithOptions;
-import static dagger.internal.codegen.GeneratedLines.GENERATED_CODE_ANNOTATIONS;
 
 import com.google.testing.compile.Compilation;
 import com.google.testing.compile.CompilationSubject;
@@ -144,7 +143,7 @@
                 .addLines(
                     "package test;",
                     "",
-                    GENERATED_CODE_ANNOTATIONS,
+                    GeneratedLines.generatedAnnotations(),
                     "final class DaggerTestComponent implements TestComponent {")
                 .addLinesIn(
                     FAST_INIT_MODE,
@@ -223,7 +222,7 @@
                 .addLines(
                     "package test;",
                     "",
-                    GENERATED_CODE_ANNOTATIONS,
+                    GeneratedLines.generatedAnnotations(),
                     "final class DaggerTestComponent implements TestComponent {")
                 .addLinesIn(
                     FAST_INIT_MODE,
@@ -299,7 +298,7 @@
                 .addLines(
                     "package test;",
                     "",
-                    GENERATED_CODE_ANNOTATIONS,
+                    GeneratedLines.generatedAnnotations(),
                     "final class DaggerTestComponent implements TestComponent {")
                 .addLinesIn(
                     FAST_INIT_MODE,
@@ -401,7 +400,7 @@
                 .addLines(
                     "package test;",
                     "",
-                    GENERATED_CODE_ANNOTATIONS,
+                    GeneratedLines.generatedAnnotations(),
                     "final class DaggerTestComponent implements TestComponent {")
                 .addLinesIn(
                     DEFAULT_MODE,
@@ -509,7 +508,7 @@
                 .addLines(
                     "package test;",
                     "",
-                    GENERATED_CODE_ANNOTATIONS,
+                    GeneratedLines.generatedAnnotations(),
                     "final class DaggerTestComponent implements TestComponent {")
                 .addLinesIn(
                     DEFAULT_MODE,
@@ -599,7 +598,7 @@
                 .addLines(
                     "package test;",
                     "",
-                    GENERATED_CODE_ANNOTATIONS,
+                    GeneratedLines.generatedAnnotations(),
                     "final class DaggerTestComponent implements TestComponent {")
                 .addLinesIn(
                     DEFAULT_MODE,
@@ -700,7 +699,7 @@
                 .addLines(
                     "package test;",
                     "",
-                    GENERATED_CODE_ANNOTATIONS,
+                    GeneratedLines.generatedAnnotations(),
                     "final class DaggerTestComponent implements TestComponent {")
                 .addLinesIn(
                     DEFAULT_MODE,
@@ -805,7 +804,7 @@
                 .addLines(
                     "package test;",
                     "",
-                    GENERATED_CODE_ANNOTATIONS,
+                    GeneratedLines.generatedAnnotations(),
                     "final class DaggerRequestsSubtypeAsProvider",
                     "    implements RequestsSubtypeAsProvider {")
                 .addLinesIn(
@@ -897,7 +896,7 @@
                 .addLines(
                     "package test;",
                     "",
-                    GENERATED_CODE_ANNOTATIONS,
+                    GeneratedLines.generatedAnnotations(),
                     "final class DaggerTestComponent implements TestComponent {")
                 .addLinesIn(
                     DEFAULT_MODE,
diff --git a/javatests/dagger/internal/codegen/ElidedFactoriesTest.java b/javatests/dagger/internal/codegen/ElidedFactoriesTest.java
index 6857039..0c557c0 100644
--- a/javatests/dagger/internal/codegen/ElidedFactoriesTest.java
+++ b/javatests/dagger/internal/codegen/ElidedFactoriesTest.java
@@ -18,8 +18,6 @@
 
 import static com.google.testing.compile.CompilationSubject.assertThat;
 import static dagger.internal.codegen.Compilers.compilerWithOptions;
-import static dagger.internal.codegen.GeneratedLines.GENERATED_CODE_ANNOTATIONS;
-import static dagger.internal.codegen.GeneratedLines.IMPORT_GENERATED_ANNOTATION;
 
 import com.google.testing.compile.Compilation;
 import com.google.testing.compile.JavaFileObjects;
@@ -82,9 +80,9 @@
             "test.DaggerSimpleComponent",
             "package test;",
             "",
-            IMPORT_GENERATED_ANNOTATION,
+            GeneratedLines.generatedImports(),
             "",
-            GENERATED_CODE_ANNOTATIONS,
+            GeneratedLines.generatedAnnotations(),
             "final class DaggerSimpleComponent implements SimpleComponent {",
             "  private DaggerSimpleComponent() {}",
             "",
@@ -179,12 +177,12 @@
                 "test.DaggerSimpleComponent",
                 "package test;",
                 "",
-                "import dagger.internal.DoubleCheck;",
-                "import dagger.internal.MemoizedSentinel;",
-                IMPORT_GENERATED_ANNOTATION,
-                "import javax.inject.Provider;",
+                GeneratedLines.generatedImports(
+                    "import dagger.internal.DoubleCheck;",
+                    "import dagger.internal.MemoizedSentinel;",
+                    "import javax.inject.Provider;"),
                 "",
-                GENERATED_CODE_ANNOTATIONS,
+                GeneratedLines.generatedAnnotations(),
                 "final class DaggerSimpleComponent implements SimpleComponent {",
                 "  private volatile Object scopedType = new MemoizedSentinel();",
                 "  private volatile Provider<DependsOnScoped> dependsOnScopedProvider;",
@@ -262,11 +260,11 @@
                 "test.DaggerSimpleComponent",
                 "package test;",
                 "",
-                "import dagger.internal.DoubleCheck;",
-                IMPORT_GENERATED_ANNOTATION,
-                "import javax.inject.Provider;",
+                GeneratedLines.generatedImports(
+                    "import dagger.internal.DoubleCheck;",
+                    "import javax.inject.Provider;"),
                 "",
-                GENERATED_CODE_ANNOTATIONS,
+                GeneratedLines.generatedAnnotations(),
                 "final class DaggerSimpleComponent implements SimpleComponent {",
                 "  private Provider<ScopedType> scopedTypeProvider;",
                 "  private Provider<DependsOnScoped> dependsOnScopedProvider;",
@@ -373,11 +371,11 @@
                 "test.DaggerSimpleComponent",
                 "package test;",
                 "",
-                "import dagger.internal.DoubleCheck;",
-                "import dagger.internal.MemoizedSentinel;",
-                IMPORT_GENERATED_ANNOTATION,
+                GeneratedLines.generatedImports(
+                    "import dagger.internal.DoubleCheck;",
+                    "import dagger.internal.MemoizedSentinel;"),
                 "",
-                GENERATED_CODE_ANNOTATIONS,
+                GeneratedLines.generatedAnnotations(),
                 "final class DaggerSimpleComponent implements SimpleComponent {",
                 "  private volatile Object scopedType = new MemoizedSentinel();",
                 "",
@@ -434,11 +432,11 @@
                 "test.DaggerSimpleComponent",
                 "package test;",
                 "",
-                "import dagger.internal.DoubleCheck;",
-                IMPORT_GENERATED_ANNOTATION,
-                "import javax.inject.Provider;",
+                GeneratedLines.generatedImports(
+                    "import dagger.internal.DoubleCheck;",
+                    "import javax.inject.Provider;"),
                 "",
-                GENERATED_CODE_ANNOTATIONS,
+                GeneratedLines.generatedAnnotations(),
                 "final class DaggerSimpleComponent implements SimpleComponent {",
                 "  private Provider<ScopedType> scopedTypeProvider;",
                 "",
diff --git a/javatests/dagger/internal/codegen/GeneratedLines.java b/javatests/dagger/internal/codegen/GeneratedLines.java
index f9a1b70..09b6188 100644
--- a/javatests/dagger/internal/codegen/GeneratedLines.java
+++ b/javatests/dagger/internal/codegen/GeneratedLines.java
@@ -17,12 +17,16 @@
 package dagger.internal.codegen;
 
 import com.google.common.base.Joiner;
+import com.google.common.collect.ImmutableSet;
+import java.util.stream.Collectors;
 
 /**
  * Common lines outputted during code generation.
  */
 public final class GeneratedLines {
-  public static final String GENERATED_ANNOTATION =
+  private static final String DAGGER_GENERATED_ANNOTATION = "@DaggerGenerated";
+
+  private static final String GENERATED_ANNOTATION =
      "@Generated("
         + "value = \"dagger.internal.codegen.ComponentProcessor\", "
         + "comments = \"https://dagger.dev\")";
@@ -30,14 +34,36 @@
   private static final String SUPPRESS_WARNINGS_ANNOTATION =
       "@SuppressWarnings({\"unchecked\", \"rawtypes\"})";
 
-  public static final String GENERATED_CODE_ANNOTATIONS =
-      Joiner.on('\n').join(GENERATED_ANNOTATION, SUPPRESS_WARNINGS_ANNOTATION);
+  private static final String IMPORT_DAGGER_GENERATED = "import dagger.internal.DaggerGenerated;";
 
-  public static final String IMPORT_GENERATED_ANNOTATION =
+  private static final String IMPORT_GENERATED_ANNOTATION =
       isBeforeJava9()
           ? "import javax.annotation.Generated;"
           : "import javax.annotation.processing.Generated;";
 
+  /** Returns a {@code String} of sorted imports. Includes generated imports automatically. */
+  public static String generatedImports(String... extraImports) {
+    return ImmutableSet.<String>builder()
+        .add(IMPORT_DAGGER_GENERATED)
+        .add(IMPORT_GENERATED_ANNOTATION)
+        .add(extraImports)
+        .build()
+        .stream()
+        .sorted()
+        .collect(Collectors.joining("\n"));
+  }
+
+  /** Returns the annotations for a generated class. */
+  public static String generatedAnnotations() {
+    return Joiner.on('\n')
+        .join(DAGGER_GENERATED_ANNOTATION, GENERATED_ANNOTATION, SUPPRESS_WARNINGS_ANNOTATION);
+  }
+
+  /** Returns the annotations for a generated class without {@code SuppressWarnings}. */
+  public static String generatedAnnotationsWithoutSuppressWarnings() {
+    return Joiner.on('\n').join(DAGGER_GENERATED_ANNOTATION, GENERATED_ANNOTATION);
+  }
+
   static final String GENERATION_OPTIONS_ANNOTATION = "@GenerationOptions(fastInit = false)";
 
   private static boolean isBeforeJava9() {
diff --git a/javatests/dagger/internal/codegen/InjectConstructorFactoryGeneratorTest.java b/javatests/dagger/internal/codegen/InjectConstructorFactoryGeneratorTest.java
index 579e87a..3e78d6b 100644
--- a/javatests/dagger/internal/codegen/InjectConstructorFactoryGeneratorTest.java
+++ b/javatests/dagger/internal/codegen/InjectConstructorFactoryGeneratorTest.java
@@ -22,8 +22,6 @@
 import static com.google.testing.compile.JavaSourcesSubjectFactory.javaSources;
 import static dagger.internal.codegen.Compilers.compilerWithOptions;
 import static dagger.internal.codegen.Compilers.daggerCompiler;
-import static dagger.internal.codegen.GeneratedLines.GENERATED_CODE_ANNOTATIONS;
-import static dagger.internal.codegen.GeneratedLines.IMPORT_GENERATED_ANNOTATION;
 
 import com.google.common.collect.ImmutableList;
 import com.google.testing.compile.Compilation;
@@ -134,11 +132,11 @@
             "test.GenericClass_Factory",
             "package test;",
             "",
-            "import dagger.internal.Factory;",
-            IMPORT_GENERATED_ANNOTATION,
-            "import javax.inject.Provider;",
+            GeneratedLines.generatedImports(
+                "import dagger.internal.Factory;",
+                "import javax.inject.Provider;"),
             "",
-            GENERATED_CODE_ANNOTATIONS,
+            GeneratedLines.generatedAnnotations(),
             "public final class GenericClass_Factory<T> implements Factory<GenericClass<T>> {",
             "  private final Provider<T> tProvider;",
             "",
@@ -183,11 +181,11 @@
             "test.GenericClass_Factory",
             "package test;",
             "",
-            "import dagger.internal.Factory;",
-            IMPORT_GENERATED_ANNOTATION,
-            "import javax.inject.Provider;",
+            GeneratedLines.generatedImports(
+                "import dagger.internal.Factory;",
+                "import javax.inject.Provider;"),
             "",
-            GENERATED_CODE_ANNOTATIONS,
+            GeneratedLines.generatedAnnotations(),
             "public final class GenericClass_Factory<A, B> implements",
             "    Factory<GenericClass<A, B>> {",
             "  private final Provider<A> aProvider;",
@@ -236,10 +234,9 @@
             "test.GenericClass_Factory",
             "package test;",
             "",
-            "import dagger.internal.Factory;",
-            IMPORT_GENERATED_ANNOTATION,
+            GeneratedLines.generatedImports("import dagger.internal.Factory;"),
             "",
-            GENERATED_CODE_ANNOTATIONS,
+            GeneratedLines.generatedAnnotations(),
             "public final class GenericClass_Factory<T> implements Factory<GenericClass<T>> {",
             "  @Override",
             "  public GenericClass<T> get() {",
@@ -280,11 +277,11 @@
             "test.GenericClass_Factory",
             "package test;",
             "",
-            "import dagger.internal.Factory;",
-            IMPORT_GENERATED_ANNOTATION,
-            "import javax.inject.Provider;",
+            GeneratedLines.generatedImports(
+                "import dagger.internal.Factory;",
+                "import javax.inject.Provider;"),
             "",
-            GENERATED_CODE_ANNOTATIONS,
+            GeneratedLines.generatedAnnotations(),
             "public final class GenericClass_Factory<A, B>",
             "    implements Factory<GenericClass<A, B>> {",
             "  private final Provider<A> aProvider;",
@@ -332,12 +329,12 @@
             "test.GenericClass_Factory",
             "package test;",
             "",
-            "import dagger.internal.Factory;",
-            "import java.util.List;",
-            IMPORT_GENERATED_ANNOTATION,
-            "import javax.inject.Provider;",
+            GeneratedLines.generatedImports(
+                "import dagger.internal.Factory;",
+                "import java.util.List;",
+                "import javax.inject.Provider;"),
             "",
-            GENERATED_CODE_ANNOTATIONS,
+            GeneratedLines.generatedAnnotations(),
             "public final class GenericClass_Factory<A extends Number & Comparable<A>,",
             "        B extends List<? extends String>,",
             "        C extends List<? super String>>",
@@ -399,13 +396,13 @@
             "test.GenericClass_Factory",
             "package test;",
             "",
-            "import dagger.Lazy;",
-            "import dagger.internal.DoubleCheck;",
-            "import dagger.internal.Factory;",
-            IMPORT_GENERATED_ANNOTATION,
-            "import javax.inject.Provider;",
+            GeneratedLines.generatedImports(
+                "import dagger.Lazy;",
+                "import dagger.internal.DoubleCheck;",
+                "import dagger.internal.Factory;",
+                "import javax.inject.Provider;"),
             "",
-            GENERATED_CODE_ANNOTATIONS,
+            GeneratedLines.generatedAnnotations(),
             "public final class GenericClass_Factory<A, B>",
             "    implements Factory<GenericClass<A, B>> {",
             "  private final Provider<A> aProvider;",
@@ -1068,11 +1065,11 @@
             "test.InjectConstructor_Factory",
             "package test;",
             "",
-            "import dagger.internal.Factory;",
-            IMPORT_GENERATED_ANNOTATION,
-            "import javax.inject.Provider;",
+            GeneratedLines.generatedImports(
+                "import dagger.internal.Factory;",
+                "import javax.inject.Provider;"),
             "",
-            GENERATED_CODE_ANNOTATIONS,
+            GeneratedLines.generatedAnnotations(),
             "public final class InjectConstructor_Factory ",
             "    implements Factory<InjectConstructor> {",
             "",
@@ -1115,11 +1112,11 @@
             "test.AllInjections_Factory",
             "package test;",
             "",
-            "import dagger.internal.Factory;",
-            IMPORT_GENERATED_ANNOTATION,
-            "import javax.inject.Provider;",
+            GeneratedLines.generatedImports(
+                "import dagger.internal.Factory;",
+                "import javax.inject.Provider;"),
             "",
-            GENERATED_CODE_ANNOTATIONS,
+            GeneratedLines.generatedAnnotations(),
             "public final class AllInjections_Factory implements Factory<AllInjections> {",
             "  private final Provider<String> sProvider;",
             "  private final Provider<String> sProvider2;",
@@ -1175,12 +1172,12 @@
             "test.InjectConstructor_Factory",
             "package test;",
             "",
-            "import dagger.internal.Factory;",
-            "import java.util.List;",
-            IMPORT_GENERATED_ANNOTATION,
-            "import javax.inject.Provider;",
+            GeneratedLines.generatedImports(
+                "import dagger.internal.Factory;",
+                "import java.util.List;",
+                "import javax.inject.Provider;"),
             "",
-            GENERATED_CODE_ANNOTATIONS,
+            GeneratedLines.generatedAnnotations(),
             "public final class InjectConstructor_Factory ",
             "    implements Factory<InjectConstructor> {",
             "",
@@ -1228,11 +1225,11 @@
             "test.InjectConstructor_Factory",
             "package test;",
             "",
-            "import dagger.internal.Factory;",
-            IMPORT_GENERATED_ANNOTATION,
-            "import javax.inject.Provider;",
+            GeneratedLines.generatedImports(
+                "import dagger.internal.Factory;",
+                "import javax.inject.Provider;"),
             "",
-            GENERATED_CODE_ANNOTATIONS,
+            GeneratedLines.generatedAnnotations(),
             "public final class InjectConstructor_Factory ",
             "    implements Factory<InjectConstructor> {",
             "",
@@ -1284,12 +1281,12 @@
             "test.InjectConstructor_Factory",
             "package test;",
             "",
-            "import dagger.internal.Factory;",
-            IMPORT_GENERATED_ANNOTATION,
-            "import javax.inject.Provider;",
-            "import other.pkg.Outer;",
+            GeneratedLines.generatedImports(
+                "import dagger.internal.Factory;",
+                "import javax.inject.Provider;",
+                "import other.pkg.Outer;"),
             "",
-            GENERATED_CODE_ANNOTATIONS,
+            GeneratedLines.generatedAnnotations(),
             "public final class InjectConstructor_Factory ",
             "    implements Factory<InjectConstructor> {",
             "",
@@ -1343,11 +1340,11 @@
             "test.InjectConstructor_Factory",
             "package test;",
             "",
-            "import dagger.internal.Factory;",
-            IMPORT_GENERATED_ANNOTATION,
-            "import javax.inject.Provider;",
+            GeneratedLines.generatedImports(
+                "import dagger.internal.Factory;",
+                "import javax.inject.Provider;"),
             "",
-            GENERATED_CODE_ANNOTATIONS,
+            GeneratedLines.generatedAnnotations(),
             "public final class InjectConstructor_Factory ",
             "    implements Factory<InjectConstructor> {",
             "",
@@ -1398,10 +1395,9 @@
             "test.SimpleType_Factory",
             "package test;",
             "",
-            "import dagger.internal.Factory;",
-            IMPORT_GENERATED_ANNOTATION,
+            GeneratedLines.generatedImports("import dagger.internal.Factory;"),
             "",
-            GENERATED_CODE_ANNOTATIONS,
+            GeneratedLines.generatedAnnotations(),
             "public final class SimpleType_Factory implements Factory<SimpleType> {",
             "  @Override public SimpleType get() {",
             "    return newInstance();",
@@ -1446,10 +1442,9 @@
             "test.OuterType_A_Factory",
             "package test;",
             "",
-            "import dagger.internal.Factory;",
-            IMPORT_GENERATED_ANNOTATION,
+            GeneratedLines.generatedImports("import dagger.internal.Factory;"),
             "",
-            GENERATED_CODE_ANNOTATIONS,
+            GeneratedLines.generatedAnnotations(),
             "public final class OuterType_A_Factory implements Factory<OuterType.A> {",
             "  @Override public OuterType.A get() {",
             "    return newInstance();",
diff --git a/javatests/dagger/internal/codegen/MapBindingComponentProcessorTest.java b/javatests/dagger/internal/codegen/MapBindingComponentProcessorTest.java
index ed9dd65..aa2a0ff 100644
--- a/javatests/dagger/internal/codegen/MapBindingComponentProcessorTest.java
+++ b/javatests/dagger/internal/codegen/MapBindingComponentProcessorTest.java
@@ -19,7 +19,6 @@
 import static com.google.testing.compile.CompilationSubject.assertThat;
 import static dagger.internal.codegen.Compilers.compilerWithOptions;
 import static dagger.internal.codegen.Compilers.daggerCompiler;
-import static dagger.internal.codegen.GeneratedLines.GENERATED_CODE_ANNOTATIONS;
 
 import com.google.testing.compile.Compilation;
 import com.google.testing.compile.JavaFileObjects;
@@ -129,7 +128,7 @@
                 "test.DaggerTestComponent",
                 "package test;",
                 "",
-                GENERATED_CODE_ANNOTATIONS,
+                GeneratedLines.generatedAnnotations(),
                 "final class DaggerTestComponent implements TestComponent {",
                 "  private final MapModuleOne mapModuleOne;",
                 "  private final MapModuleTwo mapModuleTwo;",
@@ -206,7 +205,7 @@
                 "test.DaggerTestComponent",
                 "package test;",
                 "",
-                GENERATED_CODE_ANNOTATIONS,
+                GeneratedLines.generatedAnnotations(),
                 "final class DaggerTestComponent implements TestComponent {",
                 "  private Provider<Handler> provideAdminHandlerProvider;",
                 "  private Provider<Handler> provideLoginHandlerProvider;",
@@ -357,7 +356,7 @@
                 "test.DaggerTestComponent",
                 "package test;",
                 "",
-                GENERATED_CODE_ANNOTATIONS,
+                GeneratedLines.generatedAnnotations(),
                 "final class DaggerTestComponent implements TestComponent {",
                 "  private Provider<Map<Class<?>, Integer>> mapOfClassOfAndIntegerProvider;",
                 "",
@@ -443,7 +442,7 @@
                 "mapkeys.MapModule_ComplexKeyWithInaccessibleAnnotationValueMapKey",
                 "package mapkeys;",
                 "",
-                GENERATED_CODE_ANNOTATIONS,
+                GeneratedLines.generatedAnnotations(),
                 "public final class MapModule_ComplexKeyWithInaccessibleAnnotationValueMapKey {",
                 "  public static MapKeys.ComplexKey create() {",
                 "    return MapKeys_ComplexKeyCreator.createComplexKey(",
@@ -459,7 +458,7 @@
                 "mapkeys.MapModule_ClassKeyMapKey",
                 "package mapkeys;",
                 "",
-                GENERATED_CODE_ANNOTATIONS,
+                GeneratedLines.generatedAnnotations(),
                 "public final class MapModule_ClassKeyMapKey {",
                 "  public static Class<?> create() {",
                 "    return MapKeys.Inaccessible.class;",
@@ -536,7 +535,7 @@
                 "test.DaggerTestComponent",
                 "package test;",
                 "",
-                GENERATED_CODE_ANNOTATIONS,
+                GeneratedLines.generatedAnnotations(),
                 "final class DaggerTestComponent implements TestComponent {",
                 "  private final MapModuleOne mapModuleOne;",
                 "  private final MapModuleTwo mapModuleTwo;",
@@ -613,7 +612,7 @@
                 "test.DaggerTestComponent",
                 "package test;",
                 "",
-                GENERATED_CODE_ANNOTATIONS,
+                GeneratedLines.generatedAnnotations(),
                 "final class DaggerTestComponent implements TestComponent {",
                 "  private Provider<Handler> provideAdminHandlerProvider;",
                 "  private Provider<Handler> provideLoginHandlerProvider;",
@@ -736,7 +735,7 @@
                 "test.DaggerTestComponent",
                 "package test;",
                 "",
-                GENERATED_CODE_ANNOTATIONS,
+                GeneratedLines.generatedAnnotations(),
                 "final class DaggerTestComponent implements TestComponent {",
                 "  private final MapModuleOne mapModuleOne;",
                 "  private final MapModuleTwo mapModuleTwo;",
@@ -822,7 +821,7 @@
                 "test.DaggerTestComponent",
                 "package test;",
                 "",
-                GENERATED_CODE_ANNOTATIONS,
+                GeneratedLines.generatedAnnotations(),
                 "final class DaggerTestComponent implements TestComponent {",
                 "  private Provider<Handler> provideAdminHandlerProvider;",
                 "  private Provider<Handler> provideLoginHandlerProvider;",
@@ -949,7 +948,7 @@
                 "test.DaggerTestComponent",
                 "package test;",
                 "",
-                GENERATED_CODE_ANNOTATIONS,
+                GeneratedLines.generatedAnnotations(),
                 "final class DaggerTestComponent implements TestComponent {",
                 "  private final MapModuleOne mapModuleOne;",
                 "  private final MapModuleTwo mapModuleTwo;",
@@ -1000,7 +999,7 @@
                 "test.DaggerTestComponent",
                 "package test;",
                 "",
-                GENERATED_CODE_ANNOTATIONS,
+                GeneratedLines.generatedAnnotations(),
                 "final class DaggerTestComponent implements TestComponent {",
                 "  private Provider<Handler> provideAdminHandlerProvider;",
                 "  private Provider<Handler> provideLoginHandlerProvider;",
@@ -1077,7 +1076,7 @@
             "test.DaggerTestComponent",
             "package test;",
             "",
-            GENERATED_CODE_ANNOTATIONS,
+            GeneratedLines.generatedAnnotations(),
             "final class DaggerTestComponent implements TestComponent {",
             "  private final MapModule mapModule;",
             "",
diff --git a/javatests/dagger/internal/codegen/MapBindingExpressionTest.java b/javatests/dagger/internal/codegen/MapBindingExpressionTest.java
index 3f33bd5..9acc20d 100644
--- a/javatests/dagger/internal/codegen/MapBindingExpressionTest.java
+++ b/javatests/dagger/internal/codegen/MapBindingExpressionTest.java
@@ -21,7 +21,6 @@
 import static dagger.internal.codegen.CompilerMode.FAST_INIT_MODE;
 import static dagger.internal.codegen.Compilers.CLASS_PATH_WITHOUT_GUAVA_OPTION;
 import static dagger.internal.codegen.Compilers.compilerWithOptions;
-import static dagger.internal.codegen.GeneratedLines.GENERATED_CODE_ANNOTATIONS;
 
 import com.google.testing.compile.Compilation;
 import com.google.testing.compile.Compiler;
@@ -92,7 +91,7 @@
                 "",
                 "import dagger.internal.MapBuilder;",
                 "",
-                GENERATED_CODE_ANNOTATIONS,
+                GeneratedLines.generatedAnnotations(),
                 "final class DaggerTestComponent implements TestComponent {")
             .addLinesIn(
                 FAST_INIT_MODE,
@@ -274,7 +273,7 @@
             "import other.UsesInaccessible;",
             "import other.UsesInaccessible_Factory;",
             "",
-            GENERATED_CODE_ANNOTATIONS,
+            GeneratedLines.generatedAnnotations(),
             "final class DaggerTestComponent implements TestComponent {",
             "  @Override",
             "  public UsesInaccessible usesInaccessible() {",
@@ -337,7 +336,7 @@
             "test.DaggerParent",
             "package test;",
             "",
-            GENERATED_CODE_ANNOTATIONS,
+            GeneratedLines.generatedAnnotations(),
             "final class DaggerParent implements Parent {",
             "  private final ParentModule parentModule;",
             "",
diff --git a/javatests/dagger/internal/codegen/MapBindingExpressionWithGuavaTest.java b/javatests/dagger/internal/codegen/MapBindingExpressionWithGuavaTest.java
index 69ab5a7..30b0519 100644
--- a/javatests/dagger/internal/codegen/MapBindingExpressionWithGuavaTest.java
+++ b/javatests/dagger/internal/codegen/MapBindingExpressionWithGuavaTest.java
@@ -20,7 +20,6 @@
 import static dagger.internal.codegen.CompilerMode.DEFAULT_MODE;
 import static dagger.internal.codegen.CompilerMode.FAST_INIT_MODE;
 import static dagger.internal.codegen.Compilers.compilerWithOptions;
-import static dagger.internal.codegen.GeneratedLines.GENERATED_CODE_ANNOTATIONS;
 
 import com.google.testing.compile.Compilation;
 import com.google.testing.compile.JavaFileObjects;
@@ -127,7 +126,7 @@
             .addLines(
                 "package test;",
                 "",
-                GENERATED_CODE_ANNOTATIONS,
+                GeneratedLines.generatedAnnotations(),
                 "final class DaggerTestComponent implements TestComponent {")
             .addLinesIn(
                 FAST_INIT_MODE,
@@ -401,7 +400,7 @@
             "import other.UsesInaccessible;",
             "import other.UsesInaccessible_Factory;",
             "",
-            GENERATED_CODE_ANNOTATIONS,
+            GeneratedLines.generatedAnnotations(),
             "final class DaggerTestComponent implements TestComponent {",
             "  @Override",
             "  public UsesInaccessible usesInaccessible() {",
@@ -463,7 +462,7 @@
             "test.DaggerParent",
             "package test;",
             "",
-            GENERATED_CODE_ANNOTATIONS,
+            GeneratedLines.generatedAnnotations(),
             "final class DaggerParent implements Parent {",
             "  private final ParentModule parentModule;",
             "",
@@ -519,7 +518,7 @@
             "",
             "import dagger.producers.internal.CancellationListener;",
             "",
-            GENERATED_CODE_ANNOTATIONS,
+            GeneratedLines.generatedAnnotations(),
             "final class DaggerTestComponent implements TestComponent, "
                 + "CancellationListener {",
             "  @Override",
diff --git a/javatests/dagger/internal/codegen/MapKeyProcessorTest.java b/javatests/dagger/internal/codegen/MapKeyProcessorTest.java
index ffdb2bc..85f5a5c 100644
--- a/javatests/dagger/internal/codegen/MapKeyProcessorTest.java
+++ b/javatests/dagger/internal/codegen/MapKeyProcessorTest.java
@@ -18,8 +18,6 @@
 
 import static com.google.common.truth.Truth.assertAbout;
 import static com.google.testing.compile.JavaSourcesSubjectFactory.javaSources;
-import static dagger.internal.codegen.GeneratedLines.GENERATED_CODE_ANNOTATIONS;
-import static dagger.internal.codegen.GeneratedLines.IMPORT_GENERATED_ANNOTATION;
 
 import com.google.auto.value.processor.AutoAnnotationProcessor;
 import com.google.common.collect.ImmutableList;
@@ -70,10 +68,9 @@
             "test.PathKeyCreator",
             "package test;",
             "",
-            "import com.google.auto.value.AutoAnnotation;",
-            IMPORT_GENERATED_ANNOTATION,
+            GeneratedLines.generatedImports("import com.google.auto.value.AutoAnnotation;"),
             "",
-            GENERATED_CODE_ANNOTATIONS,
+            GeneratedLines.generatedAnnotations(),
             "public final class PathKeyCreator {",
             "  private PathKeyCreator() {}",
             "",
@@ -119,10 +116,9 @@
             "test.Container_PathKeyCreator",
             "package test;",
             "",
-            "import com.google.auto.value.AutoAnnotation;",
-            IMPORT_GENERATED_ANNOTATION,
+            GeneratedLines.generatedImports("import com.google.auto.value.AutoAnnotation;"),
             "",
-            GENERATED_CODE_ANNOTATIONS,
+            GeneratedLines.generatedAnnotations(),
             "public final class Container_PathKeyCreator {",
             "  private Container_PathKeyCreator() {}",
             "",
diff --git a/javatests/dagger/internal/codegen/MembersInjectionTest.java b/javatests/dagger/internal/codegen/MembersInjectionTest.java
index ef69c71..c442779 100644
--- a/javatests/dagger/internal/codegen/MembersInjectionTest.java
+++ b/javatests/dagger/internal/codegen/MembersInjectionTest.java
@@ -23,8 +23,6 @@
 import static dagger.internal.codegen.CompilerMode.DEFAULT_MODE;
 import static dagger.internal.codegen.CompilerMode.FAST_INIT_MODE;
 import static dagger.internal.codegen.Compilers.compilerWithOptions;
-import static dagger.internal.codegen.GeneratedLines.GENERATED_CODE_ANNOTATIONS;
-import static dagger.internal.codegen.GeneratedLines.IMPORT_GENERATED_ANNOTATION;
 import static javax.tools.StandardLocation.CLASS_OUTPUT;
 
 import com.google.common.base.Joiner;
@@ -87,7 +85,7 @@
             "test.DaggerTestComponent",
             "package test;",
             "",
-            GENERATED_CODE_ANNOTATIONS,
+            GeneratedLines.generatedAnnotations(),
             "final class DaggerTestComponent implements TestComponent {",
             "  @Override",
             "  public Child child() {",
@@ -144,9 +142,10 @@
             "test.DaggerTestComponent",
             "package test;",
             "",
-            "import com.google.errorprone.annotations.CanIgnoreReturnValue;",
+            GeneratedLines.generatedImports(
+                "import com.google.errorprone.annotations.CanIgnoreReturnValue;"),
             "",
-            GENERATED_CODE_ANNOTATIONS,
+            GeneratedLines.generatedAnnotations(),
             "final class DaggerTestComponent implements TestComponent {",
             "  @Override",
             "  public Child child() {",
@@ -187,12 +186,12 @@
             "test.GenericClass_MembersInjector",
             "package test;",
             "",
-            "import dagger.MembersInjector;",
-            "import dagger.internal.InjectedFieldSignature;",
-            IMPORT_GENERATED_ANNOTATION,
-            "import javax.inject.Provider;",
+            GeneratedLines.generatedImports(
+                "import dagger.MembersInjector;",
+                "import dagger.internal.InjectedFieldSignature;",
+                "import javax.inject.Provider;"),
             "",
-            GENERATED_CODE_ANNOTATIONS,
+            GeneratedLines.generatedAnnotations(),
             "public final class GenericClass_MembersInjector<A, B>",
             "    implements MembersInjector<GenericClass<A, B>> {",
             "  private final Provider<A> aProvider;",
@@ -277,12 +276,12 @@
             "test.Child_MembersInjector",
             "package test;",
             "",
-            "import dagger.MembersInjector;",
-            "import dagger.internal.InjectedFieldSignature;",
-            IMPORT_GENERATED_ANNOTATION,
-            "import javax.inject.Provider;",
+            GeneratedLines.generatedImports(
+                "import dagger.MembersInjector;",
+                "import dagger.internal.InjectedFieldSignature;",
+                "import javax.inject.Provider;"),
             "",
-            GENERATED_CODE_ANNOTATIONS,
+            GeneratedLines.generatedAnnotations(),
             "public final class Child_MembersInjector<T>",
             "    implements MembersInjector<Child<T>> {",
             "  private final Provider<T> xProvider;",
@@ -360,14 +359,14 @@
             "test.FieldInjection_MembersInjector",
             "package test;",
             "",
-            "import dagger.Lazy;",
-            "import dagger.MembersInjector;",
-            "import dagger.internal.DoubleCheck;",
-            "import dagger.internal.InjectedFieldSignature;",
-            IMPORT_GENERATED_ANNOTATION,
-            "import javax.inject.Provider;",
+            GeneratedLines.generatedImports(
+                "import dagger.Lazy;",
+                "import dagger.MembersInjector;",
+                "import dagger.internal.DoubleCheck;",
+                "import dagger.internal.InjectedFieldSignature;",
+                "import javax.inject.Provider;"),
             "",
-            GENERATED_CODE_ANNOTATIONS,
+            GeneratedLines.generatedAnnotations(),
             "public final class FieldInjection_MembersInjector",
             "    implements MembersInjector<FieldInjection> {",
             "  private final Provider<String> stringProvider;",
@@ -442,13 +441,13 @@
             "test.FieldInjectionWithQualifier_MembersInjector",
             "package test;",
             "",
-            "import dagger.MembersInjector;",
-            "import dagger.internal.InjectedFieldSignature;",
-            IMPORT_GENERATED_ANNOTATION,
-            "import javax.inject.Named;",
-            "import javax.inject.Provider;",
+            GeneratedLines.generatedImports(
+                "import dagger.MembersInjector;",
+                "import dagger.internal.InjectedFieldSignature;",
+                "import javax.inject.Named;",
+                "import javax.inject.Provider;"),
             "",
-            GENERATED_CODE_ANNOTATIONS,
+            GeneratedLines.generatedAnnotations(),
             "public final class FieldInjectionWithQualifier_MembersInjector",
             "    implements MembersInjector<FieldInjectionWithQualifier> {",
             "  private final Provider<String> aProvider;",
@@ -511,13 +510,13 @@
             "test.MethodInjection_MembersInjector",
             "package test;",
             "",
-            "import dagger.Lazy;",
-            "import dagger.MembersInjector;",
-            "import dagger.internal.DoubleCheck;",
-            IMPORT_GENERATED_ANNOTATION,
-            "import javax.inject.Provider;",
+            GeneratedLines.generatedImports(
+                "import dagger.Lazy;",
+                "import dagger.MembersInjector;",
+                "import dagger.internal.DoubleCheck;",
+                "import javax.inject.Provider;"),
             "",
-            GENERATED_CODE_ANNOTATIONS,
+            GeneratedLines.generatedAnnotations(),
             "public final class MethodInjection_MembersInjector",
             "     implements MembersInjector<MethodInjection> {",
             "  private final Provider<String> stringProvider;",
@@ -601,12 +600,12 @@
             "test.MixedMemberInjection_MembersInjector",
             "package test;",
             "",
-            "import dagger.MembersInjector;",
-            "import dagger.internal.InjectedFieldSignature;",
-            IMPORT_GENERATED_ANNOTATION,
-            "import javax.inject.Provider;",
+            GeneratedLines.generatedImports(
+                "import dagger.MembersInjector;",
+                "import dagger.internal.InjectedFieldSignature;",
+                "import javax.inject.Provider;"),
             "",
-            GENERATED_CODE_ANNOTATIONS,
+            GeneratedLines.generatedAnnotations(),
             "public final class MixedMemberInjection_MembersInjector",
             "    implements MembersInjector<MixedMemberInjection> {",
             "  private final Provider<String> stringProvider;",
@@ -684,12 +683,12 @@
             "test.AllInjections_MembersInjector",
             "package test;",
             "",
-            "import dagger.MembersInjector;",
-            "import dagger.internal.InjectedFieldSignature;",
-            IMPORT_GENERATED_ANNOTATION,
-            "import javax.inject.Provider;",
+            GeneratedLines.generatedImports(
+                "import dagger.MembersInjector;",
+                "import dagger.internal.InjectedFieldSignature;",
+                "import javax.inject.Provider;"),
             "",
-            GENERATED_CODE_ANNOTATIONS,
+            GeneratedLines.generatedAnnotations(),
             "public final class AllInjections_MembersInjector ",
             "    implements MembersInjector<AllInjections> {",
             "  private final Provider<String> sProvider;",
@@ -751,12 +750,12 @@
             "test.AllInjections_MembersInjector",
             "package test;",
             "",
-            "import dagger.MembersInjector;",
-            "import dagger.internal.InjectedFieldSignature;",
-            IMPORT_GENERATED_ANNOTATION,
-            "import javax.inject.Provider;",
+            GeneratedLines.generatedImports(
+                "import dagger.MembersInjector;",
+                "import dagger.internal.InjectedFieldSignature;",
+                "import javax.inject.Provider;"),
             "",
-            GENERATED_CODE_ANNOTATIONS,
+            GeneratedLines.generatedAnnotations(),
             "public final class B_MembersInjector implements MembersInjector<B> {",
             "  private final Provider<String> sProvider;",
             "",
@@ -813,12 +812,12 @@
             "test.OuterType_B_MembersInjector",
             "package test;",
             "",
-            "import dagger.MembersInjector;",
-            "import dagger.internal.InjectedFieldSignature;",
-            IMPORT_GENERATED_ANNOTATION,
-            "import javax.inject.Provider;",
+            GeneratedLines.generatedImports(
+                "import dagger.MembersInjector;",
+                "import dagger.internal.InjectedFieldSignature;",
+                "import javax.inject.Provider;"),
             "",
-            GENERATED_CODE_ANNOTATIONS,
+            GeneratedLines.generatedAnnotations(),
             "public final class OuterType_B_MembersInjector",
             "    implements MembersInjector<OuterType.B> {",
             "  private final Provider<OuterType.A> aProvider;",
@@ -879,12 +878,12 @@
             "test.OuterType_B_MembersInjector",
             "package test;",
             "",
-            "import dagger.MembersInjector;",
-            "import dagger.internal.InjectedFieldSignature;",
-            IMPORT_GENERATED_ANNOTATION,
-            "import javax.inject.Provider;",
+            GeneratedLines.generatedImports(
+                "import dagger.MembersInjector;",
+                "import dagger.internal.InjectedFieldSignature;",
+                "import javax.inject.Provider;"),
             "",
-            GENERATED_CODE_ANNOTATIONS,
+            GeneratedLines.generatedAnnotations(),
             "public final class OuterType_B_MembersInjector",
             "    implements MembersInjector<OuterType.B> {",
             "  private final Provider<OuterType.A> aProvider;",
@@ -1055,12 +1054,12 @@
             "test.Child_MembersInjector",
             "package test;",
             "",
-            "import dagger.MembersInjector;",
-            "import dagger.internal.InjectedFieldSignature;",
-            IMPORT_GENERATED_ANNOTATION,
-            "import javax.inject.Provider;",
+            GeneratedLines.generatedImports(
+                "import dagger.MembersInjector;",
+                "import dagger.internal.InjectedFieldSignature;",
+                "import javax.inject.Provider;"),
             "",
-            GENERATED_CODE_ANNOTATIONS,
+            GeneratedLines.generatedAnnotations(),
             "public final class Child_MembersInjector implements MembersInjector<Child> {",
             "  private final Provider<Foo> objectProvider;",
             "  private final Provider<Bar> objectProvider2;",
@@ -1253,12 +1252,12 @@
             "test.InjectedType_MembersInjector",
             "package test;",
             "",
-            "import dagger.MembersInjector;",
-            "import dagger.internal.InjectedFieldSignature;",
-            IMPORT_GENERATED_ANNOTATION,
-            "import javax.inject.Provider;",
+            GeneratedLines.generatedImports(
+                "import dagger.MembersInjector;",
+                "import dagger.internal.InjectedFieldSignature;",
+                "import javax.inject.Provider;"),
             "",
-            GENERATED_CODE_ANNOTATIONS,
+            GeneratedLines.generatedAnnotations(),
             "public final class InjectedType_MembersInjector ",
             "    implements MembersInjector<InjectedType> {",
             "  private final Provider<Integer> primitiveIntProvider;",
@@ -1295,11 +1294,11 @@
             "test.InjectedType_Factory",
             "package test;",
             "",
-            "import dagger.internal.Factory;",
-            IMPORT_GENERATED_ANNOTATION,
-            "import javax.inject.Provider;",
+            GeneratedLines.generatedImports(
+                "import dagger.internal.Factory;",
+                "import javax.inject.Provider;"),
             "",
-            GENERATED_CODE_ANNOTATIONS,
+            GeneratedLines.generatedAnnotations(),
             "public final class InjectedType_Factory implements Factory<InjectedType> {",
             "  private final Provider<Integer> primitiveIntProvider;",
             "",
@@ -1398,12 +1397,12 @@
                 "other.Inaccessible_MembersInjector",
                 "package other;",
                 "",
-                "import dagger.MembersInjector;",
-                "import dagger.internal.InjectedFieldSignature;",
-                IMPORT_GENERATED_ANNOTATION,
-                "import javax.inject.Provider;",
+                GeneratedLines.generatedImports(
+                    "import dagger.MembersInjector;",
+                    "import dagger.internal.InjectedFieldSignature;",
+                    "import javax.inject.Provider;"),
                 "",
-                GENERATED_CODE_ANNOTATIONS,
+                GeneratedLines.generatedAnnotations(),
                 "public final class Inaccessible_MembersInjector",
                 "    implements MembersInjector<Inaccessible> {",
                 "  private final Provider<Foo> fooProvider;",
@@ -1439,15 +1438,15 @@
             "test.DaggerTestComponent",
             "package test;",
             "",
-            "import com.google.errorprone.annotations.CanIgnoreReturnValue;",
-            IMPORT_GENERATED_ANNOTATION,
-            "import other.Foo_Factory;",
-            "import other.Inaccessible_Factory;",
-            "import other.Inaccessible_MembersInjector;",
-            "import other.UsesInaccessible;",
-            "import other.UsesInaccessible_Factory;",
+            GeneratedLines.generatedImports(
+                "import com.google.errorprone.annotations.CanIgnoreReturnValue;",
+                "import other.Foo_Factory;",
+                "import other.Inaccessible_Factory;",
+                "import other.Inaccessible_MembersInjector;",
+                "import other.UsesInaccessible;",
+                "import other.UsesInaccessible_Factory;"),
             "",
-            GENERATED_CODE_ANNOTATIONS,
+            GeneratedLines.generatedAnnotations(),
             "final class DaggerTestComponent implements TestComponent {",
             "  private Object inaccessible() {",
             "    return injectInaccessible(Inaccessible_Factory.newInstance());",
@@ -1538,14 +1537,15 @@
             .addLines(
                 "package test;",
                 "",
-                "import com.google.errorprone.annotations.CanIgnoreReturnValue;",
-                "import other.InaccessiblesModule;",
-                "import other.InaccessiblesModule_InaccessiblesFactory;",
-                "import other.UsesInaccessibles;",
-                "import other.UsesInaccessibles_Factory;",
-                "import other.UsesInaccessibles_MembersInjector;",
+                GeneratedLines.generatedImports(
+                    "import com.google.errorprone.annotations.CanIgnoreReturnValue;",
+                    "import other.InaccessiblesModule;",
+                    "import other.InaccessiblesModule_InaccessiblesFactory;",
+                    "import other.UsesInaccessibles;",
+                    "import other.UsesInaccessibles_Factory;",
+                    "import other.UsesInaccessibles_MembersInjector;"),
                 "",
-                GENERATED_CODE_ANNOTATIONS,
+                GeneratedLines.generatedAnnotations(),
                 "final class DaggerTestComponent implements TestComponent {")
             .addLinesIn(
                 FAST_INIT_MODE,
@@ -1666,15 +1666,16 @@
             "test.DaggerTestComponent",
             "package test;",
             "",
-            "import com.google.errorprone.annotations.CanIgnoreReturnValue;",
-            "import other.Foo_Factory;",
-            "import other.InjectsSubtype;",
-            "import other.InjectsSubtype_Factory;",
-            "import other.Subtype_Factory;",
-            "import other.Supertype;",
-            "import other.Supertype_MembersInjector;",
+            GeneratedLines.generatedImports(
+                "import com.google.errorprone.annotations.CanIgnoreReturnValue;",
+                "import other.Foo_Factory;",
+                "import other.InjectsSubtype;",
+                "import other.InjectsSubtype_Factory;",
+                "import other.Subtype_Factory;",
+                "import other.Supertype;",
+                "import other.Supertype_MembersInjector;"),
             "",
-            GENERATED_CODE_ANNOTATIONS,
+            GeneratedLines.generatedAnnotations(),
             "final class DaggerTestComponent implements TestComponent {",
             "  private Object subtype() {",
             "    return injectSubtype(Subtype_Factory.newInstance());",
@@ -1734,12 +1735,12 @@
             "test.A_MembersInjector",
             "package test;",
             "",
-            "import dagger.MembersInjector;",
-            "import dagger.internal.InjectedFieldSignature;",
-            IMPORT_GENERATED_ANNOTATION,
-            "import javax.inject.Provider;",
+            GeneratedLines.generatedImports(
+                "import dagger.MembersInjector;",
+                "import dagger.internal.InjectedFieldSignature;",
+                "import javax.inject.Provider;"),
             "",
-            GENERATED_CODE_ANNOTATIONS,
+            GeneratedLines.generatedAnnotations(),
             "public final class A_MembersInjector implements MembersInjector<A> {",
             "  private final Provider<String> valueCProvider;",
             "  private final Provider<String> valueAProvider;",
@@ -1772,12 +1773,12 @@
             "test.C_MembersInjector",
             "package test;",
             "",
-            "import dagger.MembersInjector;",
-            "import dagger.internal.InjectedFieldSignature;",
-            IMPORT_GENERATED_ANNOTATION,
-            "import javax.inject.Provider;",
+            GeneratedLines.generatedImports(
+                "import dagger.MembersInjector;",
+                "import dagger.internal.InjectedFieldSignature;",
+                "import javax.inject.Provider;"),
             "",
-            GENERATED_CODE_ANNOTATIONS,
+            GeneratedLines.generatedAnnotations(),
             "public final class C_MembersInjector implements MembersInjector<C> {",
             "  private final Provider<String> valueCProvider;",
             "",
@@ -1852,11 +1853,11 @@
             "test.A_MembersInjector",
             "package test;",
             "",
-            "import dagger.MembersInjector;",
-            IMPORT_GENERATED_ANNOTATION,
-            "import javax.inject.Provider;",
+            GeneratedLines.generatedImports(
+                "import dagger.MembersInjector;",
+                "import javax.inject.Provider;"),
             "",
-            GENERATED_CODE_ANNOTATIONS,
+            GeneratedLines.generatedAnnotations(),
             "public final class A_MembersInjector implements MembersInjector<A> {",
             "  private final Provider<String> valueBProvider;",
             "",
@@ -1879,12 +1880,12 @@
             "test.B_MembersInjector",
             "package test;",
             "",
-            "import dagger.MembersInjector;",
-            "import dagger.internal.InjectedFieldSignature;",
-            IMPORT_GENERATED_ANNOTATION,
-            "import javax.inject.Provider;",
+            GeneratedLines.generatedImports(
+                "import dagger.MembersInjector;",
+                "import dagger.internal.InjectedFieldSignature;",
+                "import javax.inject.Provider;"),
             "",
-            GENERATED_CODE_ANNOTATIONS,
+            GeneratedLines.generatedAnnotations(),
             "public final class B_MembersInjector implements MembersInjector<B> {",
             "  private final Provider<String> valueBProvider;",
             "",
diff --git a/javatests/dagger/internal/codegen/ModuleFactoryGeneratorTest.java b/javatests/dagger/internal/codegen/ModuleFactoryGeneratorTest.java
index cbda1b8..84c7be4 100644
--- a/javatests/dagger/internal/codegen/ModuleFactoryGeneratorTest.java
+++ b/javatests/dagger/internal/codegen/ModuleFactoryGeneratorTest.java
@@ -24,8 +24,6 @@
 import static dagger.internal.codegen.Compilers.daggerCompiler;
 import static dagger.internal.codegen.DaggerModuleMethodSubject.Factory.assertThatMethodInUnannotatedClass;
 import static dagger.internal.codegen.DaggerModuleMethodSubject.Factory.assertThatModuleMethod;
-import static dagger.internal.codegen.GeneratedLines.GENERATED_CODE_ANNOTATIONS;
-import static dagger.internal.codegen.GeneratedLines.IMPORT_GENERATED_ANNOTATION;
 
 import com.google.common.collect.ImmutableList;
 import com.google.testing.compile.Compilation;
@@ -224,11 +222,11 @@
             "TestModule_ProvideStringFactory",
             "package test;",
             "",
-            "import dagger.internal.Factory;",
-            "import dagger.internal.Preconditions;",
-            IMPORT_GENERATED_ANNOTATION,
+            GeneratedLines.generatedImports(
+                "import dagger.internal.Factory;",
+                "import dagger.internal.Preconditions;"),
             "",
-            GENERATED_CODE_ANNOTATIONS,
+            GeneratedLines.generatedAnnotations(),
             "public final class TestModule_ProvideStringFactory implements Factory<String> {",
             "  private final TestModule module;",
             "",
@@ -272,10 +270,9 @@
             "TestModule_ProvideStringFactory",
             "package test;",
             "",
-            "import dagger.internal.Factory;",
-            IMPORT_GENERATED_ANNOTATION,
+            GeneratedLines.generatedImports("import dagger.internal.Factory;"),
             "",
-            GENERATED_CODE_ANNOTATIONS,
+            GeneratedLines.generatedAnnotations(),
             "public final class TestModule_ProvideStringFactory implements Factory<String> {",
             "  private final TestModule module;",
             "",
@@ -318,10 +315,9 @@
             "TestModule_ProvideStringFactory",
             "package test;",
             "",
-            "import dagger.internal.Factory;",
-            IMPORT_GENERATED_ANNOTATION,
+            GeneratedLines.generatedImports("import dagger.internal.Factory;"),
             "",
-            GENERATED_CODE_ANNOTATIONS,
+            GeneratedLines.generatedAnnotations(),
             "public final class TestModule_ProvideStringFactory implements Factory<String> {",
             "  private final TestModule module;",
             "",
@@ -388,14 +384,14 @@
             "TestModule_ProvideObjectsFactory",
             "package test;",
             "",
-            "import dagger.MembersInjector;",
-            "import dagger.internal.Factory;",
-            "import dagger.internal.Preconditions;",
-            "import java.util.List;",
-            IMPORT_GENERATED_ANNOTATION,
-            "import javax.inject.Provider;",
+            GeneratedLines.generatedImports(
+                "import dagger.MembersInjector;",
+                "import dagger.internal.Factory;",
+                "import dagger.internal.Preconditions;",
+                "import java.util.List;",
+                "import javax.inject.Provider;"),
             "",
-            GENERATED_CODE_ANNOTATIONS,
+            GeneratedLines.generatedAnnotations(),
             "public final class TestModule_ProvideObjectsFactory",
             "    implements Factory<List<Object>> {",
             "  private final TestModule module;",
@@ -461,11 +457,11 @@
             "TestModule_ProvideStringFactory",
             "package test;",
             "",
-            "import dagger.internal.Factory;",
-            "import dagger.internal.Preconditions;",
-            IMPORT_GENERATED_ANNOTATION,
+            GeneratedLines.generatedImports(
+                "import dagger.internal.Factory;",
+                "import dagger.internal.Preconditions;"),
             "",
-            GENERATED_CODE_ANNOTATIONS,
+            GeneratedLines.generatedAnnotations(),
             "public final class TestModule_ProvideStringFactory implements Factory<String> {",
             "  private final TestModule module;",
             "",
@@ -513,12 +509,12 @@
             "TestModule_ProvideWildcardListFactory",
             "package test;",
             "",
-            "import dagger.internal.Factory;",
-            "import dagger.internal.Preconditions;",
-            "import java.util.List;",
-            IMPORT_GENERATED_ANNOTATION,
+            GeneratedLines.generatedImports(
+                "import dagger.internal.Factory;",
+                "import dagger.internal.Preconditions;",
+                "import java.util.List;"),
             "",
-            GENERATED_CODE_ANNOTATIONS,
+            GeneratedLines.generatedAnnotations(),
             "public final class TestModule_ProvideWildcardListFactory implements "
                 + "Factory<List<List<?>>> {",
             "  private final TestModule module;",
@@ -566,12 +562,12 @@
             "TestModule_ProvideStringsFactory",
             "package test;",
             "",
-            "import dagger.internal.Factory;",
-            "import dagger.internal.Preconditions;",
-            "import java.util.Set;",
-            IMPORT_GENERATED_ANNOTATION,
+            GeneratedLines.generatedImports(
+                "import dagger.internal.Factory;",
+                "import dagger.internal.Preconditions;",
+                "import java.util.Set;"),
             "",
-            GENERATED_CODE_ANNOTATIONS,
+            GeneratedLines.generatedAnnotations(),
             "public final class TestModule_ProvideStringsFactory implements Factory<Set<String>> {",
             "  private final TestModule module;",
             "",
@@ -877,13 +873,13 @@
             "test.ParentModule_ProvideListBFactory",
             "package test;",
             "",
-            "import dagger.internal.Factory;",
-            "import dagger.internal.Preconditions;",
-            "import java.util.List;",
-            IMPORT_GENERATED_ANNOTATION,
-            "import javax.inject.Provider;",
+            GeneratedLines.generatedImports(
+                "import dagger.internal.Factory;",
+                "import dagger.internal.Preconditions;",
+                "import java.util.List;",
+                "import javax.inject.Provider;"),
             "",
-            GENERATED_CODE_ANNOTATIONS,
+            GeneratedLines.generatedAnnotations(),
             "public final class ParentModule_ProvideListBFactory<A extends CharSequence,",
             "    B, C extends Number & Comparable<C>> implements Factory<List<B>> {",
             "  private final ParentModule<A, B, C> module;",
@@ -916,12 +912,12 @@
             "test.ParentModule_ProvideBElementFactory",
             "package test;",
             "",
-            "import dagger.internal.Factory;",
-            "import dagger.internal.Preconditions;",
-            IMPORT_GENERATED_ANNOTATION,
-            "import javax.inject.Provider;",
+            GeneratedLines.generatedImports(
+                "import dagger.internal.Factory;",
+                "import dagger.internal.Preconditions;",
+                "import javax.inject.Provider;"),
             "",
-            GENERATED_CODE_ANNOTATIONS,
+            GeneratedLines.generatedAnnotations(),
             "public final class ParentModule_ProvideBElementFactory<A extends CharSequence,",
             "    B, C extends Number & Comparable<C>> implements Factory<B> {",
             "  private final ParentModule<A, B, C> module;",
@@ -955,12 +951,12 @@
             "test.ParentModule_ProvideBEntryFactory",
             "package test;",
             "",
-            "import dagger.internal.Factory;",
-            "import dagger.internal.Preconditions;",
-            IMPORT_GENERATED_ANNOTATION,
-            "import javax.inject.Provider;",
+            GeneratedLines.generatedImports(
+                "import dagger.internal.Factory;",
+                "import dagger.internal.Preconditions;",
+                "import javax.inject.Provider;"),
             "",
-            GENERATED_CODE_ANNOTATIONS,
+            GeneratedLines.generatedAnnotations(),
             "public final class ParentModule_ProvideBEntryFactory<A extends CharSequence,",
             "    B, C extends Number & Comparable<C>> implements Factory<B>> {",
             "  private final ParentModule<A, B, C> module;",
@@ -994,11 +990,11 @@
             "test.ChildNumberModule_ProvideNumberFactory",
             "package test;",
             "",
-            "import dagger.internal.Factory;",
-            "import dagger.internal.Preconditions;",
-            IMPORT_GENERATED_ANNOTATION,
+            GeneratedLines.generatedImports(
+                "import dagger.internal.Factory;",
+                "import dagger.internal.Preconditions;"),
             "",
-            GENERATED_CODE_ANNOTATIONS,
+            GeneratedLines.generatedAnnotations(),
             "public final class ChildNumberModule_ProvideNumberFactory",
             "    implements Factory<Number> {",
             "  private final ChildNumberModule module;",
@@ -1026,11 +1022,11 @@
             "test.ChildIntegerModule_ProvideIntegerFactory",
             "package test;",
             "",
-            "import dagger.internal.Factory;",
-            "import dagger.internal.Preconditions;",
-            IMPORT_GENERATED_ANNOTATION,
+            GeneratedLines.generatedImports(
+                "import dagger.internal.Factory;",
+                "import dagger.internal.Preconditions;"),
             "",
-            GENERATED_CODE_ANNOTATIONS,
+            GeneratedLines.generatedAnnotations(),
             "public final class ChildIntegerModule_ProvideIntegerFactory",
             "    implements Factory<Integer> {",
             "  private final ChildIntegerModule module;",
@@ -1099,12 +1095,12 @@
             "test.ParameterizedModule_ProvideMapStringNumberFactory;",
             "package test;",
             "",
-            "import dagger.internal.Factory;",
-            "import dagger.internal.Preconditions;",
-            "import java.util.Map;",
-            IMPORT_GENERATED_ANNOTATION,
+            GeneratedLines.generatedImports(
+                "import dagger.internal.Factory;",
+                "import dagger.internal.Preconditions;",
+                "import java.util.Map;"),
             "",
-            GENERATED_CODE_ANNOTATIONS,
+            GeneratedLines.generatedAnnotations(),
             "public final class ParameterizedModule_ProvideMapStringNumberFactory",
             "    implements Factory<Map<String, Number>> {",
             "  @Override",
@@ -1132,11 +1128,11 @@
             "test.ParameterizedModule_ProvideNonGenericTypeFactory;",
             "package test;",
             "",
-            "import dagger.internal.Factory;",
-            "import dagger.internal.Preconditions;",
-            IMPORT_GENERATED_ANNOTATION,
+            GeneratedLines.generatedImports(
+                "import dagger.internal.Factory;",
+                "import dagger.internal.Preconditions;"),
             "",
-            GENERATED_CODE_ANNOTATIONS,
+            GeneratedLines.generatedAnnotations(),
             "public final class ParameterizedModule_ProvideNonGenericTypeFactory",
             "    implements Factory<Object> {",
             "  @Override",
@@ -1164,12 +1160,12 @@
             "test.ParameterizedModule_ProvideNonGenericTypeWithDepsFactory;",
             "package test;",
             "",
-            "import dagger.internal.Factory;",
-            "import dagger.internal.Preconditions;",
-            IMPORT_GENERATED_ANNOTATION,
-            "import javax.inject.Provider;",
+            GeneratedLines.generatedImports(
+                "import dagger.internal.Factory;",
+                "import dagger.internal.Preconditions;",
+                "import javax.inject.Provider;"),
             "",
-            GENERATED_CODE_ANNOTATIONS,
+            GeneratedLines.generatedAnnotations(),
             "public final class ParameterizedModule_ProvideNonGenericTypeWithDepsFactory",
             "    implements Factory<String> {",
             "  private final Provider<Object> oProvider;",
@@ -1406,7 +1402,7 @@
                 "test.TestModule_GetFactory",
                 "package test;",
                 "",
-                GENERATED_CODE_ANNOTATIONS,
+                GeneratedLines.generatedAnnotations(),
                 "public final class TestModule_GetFactory implements Factory<Integer> {",
                 "  @Override",
                 "  public Integer get() {",
@@ -1429,7 +1425,7 @@
                 "test.TestModule_CreateFactory",
                 "package test;",
                 "",
-                GENERATED_CODE_ANNOTATIONS,
+                GeneratedLines.generatedAnnotations(),
                 "public final class TestModule_CreateFactory implements Factory<Boolean> {",
                 "  @Override",
                 "  public Boolean get() {",
diff --git a/javatests/dagger/internal/codegen/OptionalBindingRequestFulfillmentTest.java b/javatests/dagger/internal/codegen/OptionalBindingRequestFulfillmentTest.java
index 60b6898..9b43530 100644
--- a/javatests/dagger/internal/codegen/OptionalBindingRequestFulfillmentTest.java
+++ b/javatests/dagger/internal/codegen/OptionalBindingRequestFulfillmentTest.java
@@ -20,7 +20,6 @@
 import static dagger.internal.codegen.CompilerMode.DEFAULT_MODE;
 import static dagger.internal.codegen.CompilerMode.FAST_INIT_MODE;
 import static dagger.internal.codegen.Compilers.compilerWithOptions;
-import static dagger.internal.codegen.GeneratedLines.GENERATED_CODE_ANNOTATIONS;
 
 import com.google.testing.compile.Compilation;
 import com.google.testing.compile.JavaFileObjects;
@@ -110,7 +109,7 @@
                 "",
                 "import com.google.common.base.Optional;",
                 "",
-                GENERATED_CODE_ANNOTATIONS,
+                GeneratedLines.generatedAnnotations(),
                 "final class DaggerTestComponent implements TestComponent {")
             .addLinesIn(
                 FAST_INIT_MODE,
@@ -250,7 +249,7 @@
             "import com.google.common.base.Optional;",
             "import dagger.producers.internal.CancellationListener;",
             "",
-            GENERATED_CODE_ANNOTATIONS,
+            GeneratedLines.generatedAnnotations(),
             "final class DaggerTestComponent implements TestComponent, CancellationListener {",
             "  @Override",
             "  public ListenableFuture<Optional<Maybe>> maybe() {",
diff --git a/javatests/dagger/internal/codegen/ProducerModuleFactoryGeneratorTest.java b/javatests/dagger/internal/codegen/ProducerModuleFactoryGeneratorTest.java
index d41bc2d..8e9bb8a 100644
--- a/javatests/dagger/internal/codegen/ProducerModuleFactoryGeneratorTest.java
+++ b/javatests/dagger/internal/codegen/ProducerModuleFactoryGeneratorTest.java
@@ -23,8 +23,6 @@
 import static dagger.internal.codegen.Compilers.daggerCompiler;
 import static dagger.internal.codegen.DaggerModuleMethodSubject.Factory.assertThatMethodInUnannotatedClass;
 import static dagger.internal.codegen.DaggerModuleMethodSubject.Factory.assertThatProductionModuleMethod;
-import static dagger.internal.codegen.GeneratedLines.GENERATED_ANNOTATION;
-import static dagger.internal.codegen.GeneratedLines.IMPORT_GENERATED_ANNOTATION;
 import static java.lang.annotation.RetentionPolicy.RUNTIME;
 
 import com.google.common.util.concurrent.ListenableFuture;
@@ -361,16 +359,16 @@
             "TestModule_ProduceStringFactory",
             "package test;",
             "",
-            "import com.google.common.util.concurrent.Futures;",
-            "import com.google.common.util.concurrent.ListenableFuture;",
-            "import dagger.producers.internal.AbstractProducesMethodProducer;",
-            "import dagger.producers.monitoring.ProducerToken;",
-            "import dagger.producers.monitoring.ProductionComponentMonitor;",
-            "import java.util.concurrent.Executor;",
-            IMPORT_GENERATED_ANNOTATION,
-            "import javax.inject.Provider;",
+            GeneratedLines.generatedImports(
+                "import com.google.common.util.concurrent.Futures;",
+                "import com.google.common.util.concurrent.ListenableFuture;",
+                "import dagger.producers.internal.AbstractProducesMethodProducer;",
+                "import dagger.producers.monitoring.ProducerToken;",
+                "import dagger.producers.monitoring.ProductionComponentMonitor;",
+                "import java.util.concurrent.Executor;",
+                "import javax.inject.Provider;"),
             "",
-            GENERATED_ANNOTATION,
+            GeneratedLines.generatedAnnotationsWithoutSuppressWarnings(),
             "@SuppressWarnings({\"FutureReturnValueIgnored\", \"unchecked\", \"rawtypes\"})",
             "public final class TestModule_ProduceStringFactory",
             "    extends AbstractProducesMethodProducer<Void, String> {",
@@ -434,16 +432,16 @@
             "TestModule_ProduceStringFactory",
             "package test;",
             "",
-            "import com.google.common.util.concurrent.Futures;",
-            "import com.google.common.util.concurrent.ListenableFuture;",
-            "import dagger.producers.internal.AbstractProducesMethodProducer;",
-            "import dagger.producers.monitoring.ProducerToken;",
-            "import dagger.producers.monitoring.ProductionComponentMonitor;",
-            "import java.util.concurrent.Executor;",
-            IMPORT_GENERATED_ANNOTATION,
-            "import javax.inject.Provider;",
+            GeneratedLines.generatedImports(
+                "import com.google.common.util.concurrent.Futures;",
+                "import com.google.common.util.concurrent.ListenableFuture;",
+                "import dagger.producers.internal.AbstractProducesMethodProducer;",
+                "import dagger.producers.monitoring.ProducerToken;",
+                "import dagger.producers.monitoring.ProductionComponentMonitor;",
+                "import java.util.concurrent.Executor;",
+                "import javax.inject.Provider;"),
             "",
-            GENERATED_ANNOTATION,
+            GeneratedLines.generatedAnnotationsWithoutSuppressWarnings(),
             "@SuppressWarnings({\"FutureReturnValueIgnored\", \"unchecked\", \"rawtypes\"})",
             "public final class TestModule_ProduceStringFactory",
             "    extends AbstractProducesMethodProducer<Void, String> {",
diff --git a/javatests/dagger/internal/codegen/ProductionComponentProcessorTest.java b/javatests/dagger/internal/codegen/ProductionComponentProcessorTest.java
index 797fe2e..4043e76 100644
--- a/javatests/dagger/internal/codegen/ProductionComponentProcessorTest.java
+++ b/javatests/dagger/internal/codegen/ProductionComponentProcessorTest.java
@@ -21,8 +21,6 @@
 import static com.google.testing.compile.JavaSourceSubjectFactory.javaSource;
 import static dagger.internal.codegen.Compilers.compilerWithOptions;
 import static dagger.internal.codegen.Compilers.daggerCompiler;
-import static dagger.internal.codegen.GeneratedLines.GENERATED_CODE_ANNOTATIONS;
-import static dagger.internal.codegen.GeneratedLines.IMPORT_GENERATED_ANNOTATION;
 
 import com.google.testing.compile.Compilation;
 import com.google.testing.compile.JavaFileObjects;
@@ -234,21 +232,21 @@
                 "test.DaggerTestClass_SimpleComponent",
                 "package test;",
                 "",
-                "import com.google.common.util.concurrent.ListenableFuture;",
-                "import dagger.internal.DoubleCheck;",
-                "import dagger.internal.InstanceFactory;",
-                "import dagger.internal.MemoizedSentinel;",
-                "import dagger.internal.Preconditions;",
-                "import dagger.internal.SetFactory;",
-                "import dagger.producers.Producer;",
-                "import dagger.producers.internal.CancellationListener;",
-                "import dagger.producers.internal.Producers;",
-                "import dagger.producers.monitoring.ProductionComponentMonitor;",
-                "import java.util.concurrent.Executor;",
-                IMPORT_GENERATED_ANNOTATION,
-                "import javax.inject.Provider;",
+                GeneratedLines.generatedImports(
+                    "import com.google.common.util.concurrent.ListenableFuture;",
+                    "import dagger.internal.DoubleCheck;",
+                    "import dagger.internal.InstanceFactory;",
+                    "import dagger.internal.MemoizedSentinel;",
+                    "import dagger.internal.Preconditions;",
+                    "import dagger.internal.SetFactory;",
+                    "import dagger.producers.Producer;",
+                    "import dagger.producers.internal.CancellationListener;",
+                    "import dagger.producers.internal.Producers;",
+                    "import dagger.producers.monitoring.ProductionComponentMonitor;",
+                    "import java.util.concurrent.Executor;",
+                    "import javax.inject.Provider;"),
                 "",
-                GENERATED_CODE_ANNOTATIONS,
+                GeneratedLines.generatedAnnotations(),
                 "final class DaggerTestClass_SimpleComponent",
                 "    implements TestClass.SimpleComponent, CancellationListener {",
                 "  private final TestClass.BModule bModule;",
@@ -430,20 +428,20 @@
                 "test.DaggerTestClass_SimpleComponent",
                 "package test;",
                 "",
-                "import com.google.common.util.concurrent.ListenableFuture;",
-                "import dagger.internal.DoubleCheck;",
-                "import dagger.internal.InstanceFactory;",
-                "import dagger.internal.Preconditions;",
-                "import dagger.internal.SetFactory;",
-                "import dagger.producers.Producer;",
-                "import dagger.producers.internal.CancellationListener;",
-                "import dagger.producers.internal.Producers;",
-                "import dagger.producers.monitoring.ProductionComponentMonitor;",
-                "import java.util.concurrent.Executor;",
-                IMPORT_GENERATED_ANNOTATION,
-                "import javax.inject.Provider;",
+                GeneratedLines.generatedImports(
+                    "import com.google.common.util.concurrent.ListenableFuture;",
+                    "import dagger.internal.DoubleCheck;",
+                    "import dagger.internal.InstanceFactory;",
+                    "import dagger.internal.Preconditions;",
+                    "import dagger.internal.SetFactory;",
+                    "import dagger.producers.Producer;",
+                    "import dagger.producers.internal.CancellationListener;",
+                    "import dagger.producers.internal.Producers;",
+                    "import dagger.producers.monitoring.ProductionComponentMonitor;",
+                    "import java.util.concurrent.Executor;",
+                    "import javax.inject.Provider;"),
                 "",
-                GENERATED_CODE_ANNOTATIONS,
+                GeneratedLines.generatedAnnotations(),
                 "final class DaggerTestClass_SimpleComponent",
                 "    implements TestClass.SimpleComponent, CancellationListener {",
                 "  private Producer<TestClass.A> aEntryPoint;",
@@ -650,7 +648,7 @@
             new JavaFileBuilder(compilerMode, "test.DaggerRoot")
                 .addLines(
                     "package test;",
-                    GENERATED_CODE_ANNOTATIONS,
+                    GeneratedLines.generatedAnnotations(),
                     "final class DaggerParent implements Parent, CancellationListener {",
                     "  private final class ChildImpl implements Child, CancellationListener {",
                     "    @Override",
diff --git a/javatests/dagger/internal/codegen/SetBindingRequestFulfillmentTest.java b/javatests/dagger/internal/codegen/SetBindingRequestFulfillmentTest.java
index e143370..a343a60 100644
--- a/javatests/dagger/internal/codegen/SetBindingRequestFulfillmentTest.java
+++ b/javatests/dagger/internal/codegen/SetBindingRequestFulfillmentTest.java
@@ -19,8 +19,6 @@
 import static com.google.testing.compile.CompilationSubject.assertThat;
 import static dagger.internal.codegen.Compilers.CLASS_PATH_WITHOUT_GUAVA_OPTION;
 import static dagger.internal.codegen.Compilers.compilerWithOptions;
-import static dagger.internal.codegen.GeneratedLines.GENERATED_CODE_ANNOTATIONS;
-import static dagger.internal.codegen.GeneratedLines.IMPORT_GENERATED_ANNOTATION;
 
 import com.google.testing.compile.Compilation;
 import com.google.testing.compile.Compiler;
@@ -96,7 +94,7 @@
             "",
             "import dagger.internal.SetBuilder;",
             "",
-            GENERATED_CODE_ANNOTATIONS,
+            GeneratedLines.generatedAnnotations(),
             "final class DaggerTestComponent implements TestComponent {",
             "  @Override",
             "  public Set<String> strings() {",
@@ -191,7 +189,7 @@
             "import other.UsesInaccessible;",
             "import other.UsesInaccessible_Factory;",
             "",
-            GENERATED_CODE_ANNOTATIONS,
+            GeneratedLines.generatedAnnotations(),
             "final class DaggerTestComponent implements TestComponent {",
             "  private Set setOfInaccessible2() {",
             "    return SetBuilder.newSetBuilder(1)",
@@ -261,12 +259,12 @@
             "test.DaggerParent",
             "package test;",
             "",
-            "import dagger.internal.Preconditions;",
-            "import java.util.Collections;",
-            "import java.util.Set;",
-            IMPORT_GENERATED_ANNOTATION,
+            GeneratedLines.generatedImports(
+                "import dagger.internal.Preconditions;",
+                "import java.util.Collections;",
+                "import java.util.Set;"),
             "",
-            GENERATED_CODE_ANNOTATIONS,
+            GeneratedLines.generatedAnnotations(),
             "final class DaggerParent implements Parent {",
             "  private DaggerParent() {}",
             "",
diff --git a/javatests/dagger/internal/codegen/SetBindingRequestFulfillmentWithGuavaTest.java b/javatests/dagger/internal/codegen/SetBindingRequestFulfillmentWithGuavaTest.java
index 2e3686f..a081130 100644
--- a/javatests/dagger/internal/codegen/SetBindingRequestFulfillmentWithGuavaTest.java
+++ b/javatests/dagger/internal/codegen/SetBindingRequestFulfillmentWithGuavaTest.java
@@ -18,8 +18,6 @@
 
 import static com.google.testing.compile.CompilationSubject.assertThat;
 import static dagger.internal.codegen.Compilers.compilerWithOptions;
-import static dagger.internal.codegen.GeneratedLines.GENERATED_CODE_ANNOTATIONS;
-import static dagger.internal.codegen.GeneratedLines.IMPORT_GENERATED_ANNOTATION;
 
 import com.google.testing.compile.Compilation;
 import com.google.testing.compile.JavaFileObjects;
@@ -99,7 +97,7 @@
             "",
             "import com.google.common.collect.ImmutableSet;",
             "",
-            GENERATED_CODE_ANNOTATIONS,
+            GeneratedLines.generatedAnnotations(),
             "final class DaggerTestComponent implements TestComponent {",
             "  @Override",
             "  public Set<String> strings() {",
@@ -202,7 +200,7 @@
             "import other.UsesInaccessible;",
             "import other.UsesInaccessible_Factory;",
             "",
-            GENERATED_CODE_ANNOTATIONS,
+            GeneratedLines.generatedAnnotations(),
             "final class DaggerTestComponent implements TestComponent {",
             "  private Set setOfInaccessible2() {",
             "    return ImmutableSet.copyOf(TestModule_EmptySetFactory.emptySet());",
@@ -272,7 +270,7 @@
             "",
             "import com.google.common.collect.ImmutableSet;",
             "",
-            GENERATED_CODE_ANNOTATIONS,
+            GeneratedLines.generatedAnnotations(),
             "final class DaggerParent implements Parent {",
             "  private final class ChildImpl implements Child {",
             "    @Override",
@@ -324,14 +322,14 @@
             "test.DaggerTestComponent",
             "package test;",
             "",
-            "import com.google.common.collect.ImmutableSet;",
-            "import com.google.common.util.concurrent.Futures;",
-            "import com.google.common.util.concurrent.ListenableFuture;",
-            "import dagger.producers.internal.CancellationListener;",
-            "import java.util.Set;",
-            IMPORT_GENERATED_ANNOTATION,
+            GeneratedLines.generatedImports(
+                "import com.google.common.collect.ImmutableSet;",
+                "import com.google.common.util.concurrent.Futures;",
+                "import com.google.common.util.concurrent.ListenableFuture;",
+                "import dagger.producers.internal.CancellationListener;",
+                "import java.util.Set;"),
             "",
-            GENERATED_CODE_ANNOTATIONS,
+            GeneratedLines.generatedAnnotations(),
             "final class DaggerTestComponent implements TestComponent, "
                 + "CancellationListener {",
             "  private DaggerTestComponent() {}",
diff --git a/javatests/dagger/internal/codegen/SubcomponentCreatorRequestFulfillmentTest.java b/javatests/dagger/internal/codegen/SubcomponentCreatorRequestFulfillmentTest.java
index ea0b4cc..32ae245 100644
--- a/javatests/dagger/internal/codegen/SubcomponentCreatorRequestFulfillmentTest.java
+++ b/javatests/dagger/internal/codegen/SubcomponentCreatorRequestFulfillmentTest.java
@@ -21,8 +21,6 @@
 import static com.google.testing.compile.CompilationSubject.assertThat;
 import static dagger.internal.codegen.CompilerMode.DEFAULT_MODE;
 import static dagger.internal.codegen.CompilerMode.FAST_INIT_MODE;
-import static dagger.internal.codegen.GeneratedLines.GENERATED_CODE_ANNOTATIONS;
-import static dagger.internal.codegen.GeneratedLines.IMPORT_GENERATED_ANNOTATION;
 import static dagger.internal.codegen.binding.ComponentCreatorAnnotation.SUBCOMPONENT_BUILDER;
 import static dagger.internal.codegen.binding.ComponentCreatorAnnotation.SUBCOMPONENT_FACTORY;
 
@@ -99,9 +97,9 @@
             "test.DaggerC",
             "package test;",
             "",
-            IMPORT_GENERATED_ANNOTATION,
+            GeneratedLines.generatedImports(),
             "",
-            GENERATED_CODE_ANNOTATIONS,
+            GeneratedLines.generatedAnnotations(),
             "final class DaggerC implements C {",
             "  @Override",
             "  public Sub.Builder sBuilder() {",
diff --git a/javatests/dagger/internal/codegen/SubcomponentValidationTest.java b/javatests/dagger/internal/codegen/SubcomponentValidationTest.java
index 19a27ff..c63522e 100644
--- a/javatests/dagger/internal/codegen/SubcomponentValidationTest.java
+++ b/javatests/dagger/internal/codegen/SubcomponentValidationTest.java
@@ -21,8 +21,6 @@
 import static dagger.internal.codegen.CompilerMode.FAST_INIT_MODE;
 import static dagger.internal.codegen.Compilers.compilerWithOptions;
 import static dagger.internal.codegen.Compilers.daggerCompiler;
-import static dagger.internal.codegen.GeneratedLines.GENERATED_CODE_ANNOTATIONS;
-import static dagger.internal.codegen.GeneratedLines.IMPORT_GENERATED_ANNOTATION;
 
 import com.google.testing.compile.Compilation;
 import com.google.testing.compile.JavaFileObjects;
@@ -457,7 +455,7 @@
             .addLines(
                 "package test;",
                 "",
-                GENERATED_CODE_ANNOTATIONS,
+                GeneratedLines.generatedAnnotations(),
                 "final class DaggerParentComponent implements ParentComponent {")
             .addLinesIn(
                 DEFAULT_MODE,
@@ -656,7 +654,7 @@
             "",
             "import test.subpackage.Sub;",
             "",
-            GENERATED_CODE_ANNOTATIONS,
+            GeneratedLines.generatedAnnotations(),
             "final class DaggerParentComponent implements ParentComponent {",
             "  @Override",
             "  public Foo.Sub newInstanceSubcomponent() {",
@@ -683,10 +681,10 @@
             "    private final class B_SubImpl implements Bar.Sub {",
             "      @Override",
             "      public Sub newSubcomponentInSubpackage() {",
-            "        return new ts_SubImpl();",
+            "        return new ts_SubI();",
             "      }",
             "",
-            "      private final class ts_SubImpl implements Sub {}",
+            "      private final class ts_SubI implements Sub {}",
             "    }",
             "  }",
             "",
@@ -738,7 +736,7 @@
             "test.DaggerParentComponent",
             "package test;",
             "",
-            GENERATED_CODE_ANNOTATIONS,
+            GeneratedLines.generatedAnnotations(),
             "final class DaggerParentComponent implements ParentComponent {",
             "  @Override",
             "  public Sub newSubcomponent() {",
@@ -803,7 +801,7 @@
         JavaFileObjects.forSourceLines(
             "DaggerParentComponent",
             "",
-            GENERATED_CODE_ANNOTATIONS,
+            GeneratedLines.generatedAnnotations(),
             "final class DaggerParentComponent implements ParentComponent {",
             "  @Override",
             "  public Sub newSubcomponent() {",
@@ -880,7 +878,7 @@
             "",
             "import top1.a.b.c.d.E;",
             "",
-            GENERATED_CODE_ANNOTATIONS,
+            GeneratedLines.generatedAnnotations(),
             "final class DaggerParentComponent implements ParentComponent {",
             "  @Override",
             "  public E.F.Sub top1() {",
@@ -937,7 +935,7 @@
             "test.DaggerC",
             "package test;",
             "",
-            GENERATED_CODE_ANNOTATIONS,
+            GeneratedLines.generatedAnnotations(),
             "final class DaggerC implements C {",
             "  @Override",
             "  public Foo.C newInstanceC() {",
@@ -996,9 +994,9 @@
             "test.DaggerC",
             "package test;",
             "",
-            IMPORT_GENERATED_ANNOTATION,
+            GeneratedLines.generatedImports(),
             "",
-            GENERATED_CODE_ANNOTATIONS,
+            GeneratedLines.generatedAnnotations(),
             "final class DaggerC implements C {",
             "  @Override",
             "  public C.Foo.Sub.Builder fooBuilder() {",
diff --git a/javatests/dagger/internal/codegen/SwitchingProviderTest.java b/javatests/dagger/internal/codegen/SwitchingProviderTest.java
index 593ad49..615b09d 100644
--- a/javatests/dagger/internal/codegen/SwitchingProviderTest.java
+++ b/javatests/dagger/internal/codegen/SwitchingProviderTest.java
@@ -18,7 +18,6 @@
 
 import static com.google.testing.compile.CompilationSubject.assertThat;
 import static com.google.testing.compile.Compiler.javac;
-import static dagger.internal.codegen.GeneratedLines.GENERATED_CODE_ANNOTATIONS;
 
 import com.google.common.collect.ImmutableList;
 import com.google.testing.compile.Compilation;
@@ -68,7 +67,7 @@
         JavaFileObjects.forSourceLines(
             "test.DaggerTestComponent",
                 "package test;",
-            GENERATED_CODE_ANNOTATIONS,
+            GeneratedLines.generatedAnnotations(),
                 "final class DaggerTestComponent implements TestComponent {",
                 "  private final class SwitchingProvider<T> implements Provider<T> {",
                 "    @SuppressWarnings(\"unchecked\")",
@@ -248,7 +247,7 @@
                 "test.DaggerTestComponent",
                 "package test;",
                 "",
-                GENERATED_CODE_ANNOTATIONS,
+                GeneratedLines.generatedAnnotations(),
                 "final class DaggerTestComponent implements TestComponent {",
                 "  private volatile Provider<String> sProvider;",
                 "",
@@ -333,7 +332,7 @@
                 "test.DaggerTestComponent",
                 "package test;",
                 "",
-                GENERATED_CODE_ANNOTATIONS,
+                GeneratedLines.generatedAnnotations(),
                 "final class DaggerTestComponent implements TestComponent {",
                 "  private volatile Object charSequence = new MemoizedSentinel();",
                 "  private volatile Provider<CharSequence> cProvider;",
@@ -424,7 +423,7 @@
                 "test.DaggerTestComponent",
                 "package test;",
                 "",
-                GENERATED_CODE_ANNOTATIONS,
+                GeneratedLines.generatedAnnotations(),
                 "final class DaggerTestComponent implements TestComponent {",
                 "  @Override",
                 "  public Provider<Set<String>> setProvider() {",
@@ -469,7 +468,7 @@
                 "test.DaggerTestComponent",
                 "package test;",
                 "",
-                GENERATED_CODE_ANNOTATIONS,
+                GeneratedLines.generatedAnnotations(),
                 "final class DaggerTestComponent implements TestComponent {",
                 "  private Provider<MembersInjector<Foo>> fooMembersInjectorProvider;",
                 "",
@@ -540,7 +539,7 @@
                 "test.DaggerTestComponent",
                 "package test;",
                 "",
-                GENERATED_CODE_ANNOTATIONS,
+                GeneratedLines.generatedAnnotations(),
                 "final class DaggerTestComponent implements TestComponent {",
                 "  @SuppressWarnings(\"rawtypes\")",
                 "  private static final Provider ABSENT_JDK_OPTIONAL_PROVIDER =",
diff --git a/javatests/dagger/internal/codegen/bindinggraphvalidation/BUILD b/javatests/dagger/internal/codegen/bindinggraphvalidation/BUILD
new file mode 100644
index 0000000..551e091
--- /dev/null
+++ b/javatests/dagger/internal/codegen/bindinggraphvalidation/BUILD
@@ -0,0 +1,37 @@
+# Copyright (C) 2020 The Dagger Authors.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+# Description:
+#   Tests for dagger.internal.codegen.bindinggraphvalidation
+
+load("//:build_defs.bzl", "DOCLINT_HTML_AND_SYNTAX")
+load("//:test_defs.bzl", "GenJavaTests")
+
+package(default_visibility = ["//:src"])
+
+GenJavaTests(
+    name = "bindinggraphvalidation_tests",
+    srcs = glob(["*.java"]),
+    functional = False,
+    javacopts = DOCLINT_HTML_AND_SYNTAX,
+    deps = [
+        "//java/dagger/internal/codegen/bindinggraphvalidation",
+        "//javatests/dagger/internal/codegen:compilers",
+        "@google_bazel_common//third_party/java/compile_testing",
+        "@google_bazel_common//third_party/java/javapoet",
+        "@google_bazel_common//third_party/java/junit",
+        "@google_bazel_common//third_party/java/truth",
+        "@maven//:com_google_auto_auto_common",
+    ],
+)
diff --git a/javatests/dagger/internal/codegen/bindinggraphvalidation/NullableBindingValidationTest.java b/javatests/dagger/internal/codegen/bindinggraphvalidation/NullableBindingValidationTest.java
new file mode 100644
index 0000000..50a65ac
--- /dev/null
+++ b/javatests/dagger/internal/codegen/bindinggraphvalidation/NullableBindingValidationTest.java
@@ -0,0 +1,410 @@
+/*
+ * Copyright (C) 2018 The Dagger Authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package dagger.internal.codegen.bindinggraphvalidation;
+
+import static com.google.testing.compile.CompilationSubject.assertThat;
+import static dagger.internal.codegen.Compilers.compilerWithOptions;
+import static dagger.internal.codegen.Compilers.daggerCompiler;
+import static dagger.internal.codegen.bindinggraphvalidation.NullableBindingValidator.nullableToNonNullable;
+
+import com.google.testing.compile.Compilation;
+import com.google.testing.compile.JavaFileObjects;
+import javax.tools.JavaFileObject;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.JUnit4;
+
+@RunWith(JUnit4.class)
+public class NullableBindingValidationTest {
+  private static final JavaFileObject NULLABLE =
+      JavaFileObjects.forSourceLines(
+          "test.Nullable", // force one-string-per-line format
+          "package test;",
+          "",
+          "public @interface Nullable {}");
+
+  @Test public void nullCheckForConstructorParameters() {
+    JavaFileObject a = JavaFileObjects.forSourceLines("test.A",
+        "package test;",
+        "",
+        "import javax.inject.Inject;",
+        "",
+        "final class A {",
+        "  @Inject A(String string) {}",
+        "}");
+    JavaFileObject module = JavaFileObjects.forSourceLines("test.TestModule",
+        "package test;",
+        "",
+        "import dagger.Provides;",
+        "import javax.inject.Inject;",
+        "",
+        "@dagger.Module",
+        "final class TestModule {",
+        "  @Nullable @Provides String provideString() { return null; }",
+        "}");
+    JavaFileObject component = JavaFileObjects.forSourceLines("test.TestComponent",
+        "package test;",
+        "",
+        "import dagger.Component;",
+        "",
+        "@Component(modules = TestModule.class)",
+        "interface TestComponent {",
+        "  A a();",
+        "}");
+    Compilation compilation = daggerCompiler().compile(NULLABLE, a, module, component);
+    assertThat(compilation).failed();
+    assertThat(compilation)
+        .hadErrorContaining(
+            nullableToNonNullable(
+                "String",
+                "@Nullable @Provides String TestModule.provideString()"));
+
+    // but if we disable the validation, then it compiles fine.
+    Compilation compilation2 =
+        compilerWithOptions("-Adagger.nullableValidation=WARNING")
+            .compile(NULLABLE, a, module, component);
+    assertThat(compilation2).succeeded();
+  }
+
+  @Test public void nullCheckForMembersInjectParam() {
+    JavaFileObject a = JavaFileObjects.forSourceLines("test.A",
+        "package test;",
+        "",
+        "import javax.inject.Inject;",
+        "",
+        "final class A {",
+        "  @Inject A() {}",
+        "  @Inject void register(String string) {}",
+        "}");
+    JavaFileObject module = JavaFileObjects.forSourceLines("test.TestModule",
+        "package test;",
+        "",
+        "import dagger.Provides;",
+        "import javax.inject.Inject;",
+        "",
+        "@dagger.Module",
+        "final class TestModule {",
+        "  @Nullable @Provides String provideString() { return null; }",
+        "}");
+    JavaFileObject component = JavaFileObjects.forSourceLines("test.TestComponent",
+        "package test;",
+        "",
+        "import dagger.Component;",
+        "",
+        "@Component(modules = TestModule.class)",
+        "interface TestComponent {",
+        "  A a();",
+        "}");
+    Compilation compilation = daggerCompiler().compile(NULLABLE, a, module, component);
+    assertThat(compilation).failed();
+    assertThat(compilation)
+        .hadErrorContaining(
+            nullableToNonNullable(
+                "String",
+                "@Nullable @Provides String TestModule.provideString()"));
+
+    // but if we disable the validation, then it compiles fine.
+    Compilation compilation2 =
+        compilerWithOptions("-Adagger.nullableValidation=WARNING")
+            .compile(NULLABLE, a, module, component);
+    assertThat(compilation2).succeeded();
+  }
+
+  @Test public void nullCheckForVariable() {
+    JavaFileObject a = JavaFileObjects.forSourceLines("test.A",
+        "package test;",
+        "",
+        "import javax.inject.Inject;",
+        "",
+        "final class A {",
+        "  @Inject String string;",
+        "  @Inject A() {}",
+        "}");
+    JavaFileObject module = JavaFileObjects.forSourceLines("test.TestModule",
+        "package test;",
+        "",
+        "import dagger.Provides;",
+        "import javax.inject.Inject;",
+        "",
+        "@dagger.Module",
+        "final class TestModule {",
+        "  @Nullable @Provides String provideString() { return null; }",
+        "}");
+    JavaFileObject component = JavaFileObjects.forSourceLines("test.TestComponent",
+        "package test;",
+        "",
+        "import dagger.Component;",
+        "",
+        "@Component(modules = TestModule.class)",
+        "interface TestComponent {",
+        "  A a();",
+        "}");
+    Compilation compilation = daggerCompiler().compile(NULLABLE, a, module, component);
+    assertThat(compilation).failed();
+    assertThat(compilation)
+        .hadErrorContaining(
+            nullableToNonNullable(
+                "String",
+                "@Nullable @Provides String TestModule.provideString()"));
+
+    // but if we disable the validation, then it compiles fine.
+    Compilation compilation2 =
+        compilerWithOptions("-Adagger.nullableValidation=WARNING")
+            .compile(NULLABLE, a, module, component);
+    assertThat(compilation2).succeeded();
+  }
+
+  @Test public void nullCheckForComponentReturn() {
+    JavaFileObject module = JavaFileObjects.forSourceLines("test.TestModule",
+        "package test;",
+        "",
+        "import dagger.Provides;",
+        "import javax.inject.Inject;",
+        "",
+        "@dagger.Module",
+        "final class TestModule {",
+        "  @Nullable @Provides String provideString() { return null; }",
+        "}");
+    JavaFileObject component = JavaFileObjects.forSourceLines("test.TestComponent",
+        "package test;",
+        "",
+        "import dagger.Component;",
+        "",
+        "@Component(modules = TestModule.class)",
+        "interface TestComponent {",
+        "  String string();",
+        "}");
+    Compilation compilation = daggerCompiler().compile(NULLABLE, module, component);
+    assertThat(compilation).failed();
+    assertThat(compilation)
+        .hadErrorContaining(
+            nullableToNonNullable(
+                "String",
+                "@Nullable @Provides String TestModule.provideString()"));
+
+    // but if we disable the validation, then it compiles fine.
+    Compilation compilation2 =
+        compilerWithOptions("-Adagger.nullableValidation=WARNING")
+            .compile(NULLABLE, module, component);
+    assertThat(compilation2).succeeded();
+  }
+
+  @Test
+  public void nullCheckForOptionalInstance() {
+    JavaFileObject a =
+        JavaFileObjects.forSourceLines(
+            "test.A",
+            "package test;",
+            "",
+            "import com.google.common.base.Optional;",
+            "import javax.inject.Inject;",
+            "",
+            "final class A {",
+            "  @Inject A(Optional<String> optional) {}",
+            "}");
+    JavaFileObject module =
+        JavaFileObjects.forSourceLines(
+            "test.TestModule",
+            "package test;",
+            "",
+            "import dagger.BindsOptionalOf;",
+            "import dagger.Provides;",
+            "import javax.inject.Inject;",
+            "",
+            "@dagger.Module",
+            "abstract class TestModule {",
+            "  @Nullable @Provides static String provideString() { return null; }",
+            "  @BindsOptionalOf abstract String optionalString();",
+            "}");
+    JavaFileObject component =
+        JavaFileObjects.forSourceLines(
+            "test.TestComponent",
+            "package test;",
+            "",
+            "import dagger.Component;",
+            "",
+            "@Component(modules = TestModule.class)",
+            "interface TestComponent {",
+            "  A a();",
+            "}");
+    Compilation compilation = daggerCompiler().compile(NULLABLE, a, module, component);
+    assertThat(compilation).failed();
+    assertThat(compilation)
+        .hadErrorContaining(
+            nullableToNonNullable(
+                "String",
+                "@Nullable @Provides String TestModule.provideString()"));
+  }
+
+  @Test
+  public void nullCheckForOptionalProvider() {
+    JavaFileObject a =
+        JavaFileObjects.forSourceLines(
+            "test.A",
+            "package test;",
+            "",
+            "import com.google.common.base.Optional;",
+            "import javax.inject.Inject;",
+            "import javax.inject.Provider;",
+            "",
+            "final class A {",
+            "  @Inject A(Optional<Provider<String>> optional) {}",
+            "}");
+    JavaFileObject module =
+        JavaFileObjects.forSourceLines(
+            "test.TestModule",
+            "package test;",
+            "",
+            "import dagger.BindsOptionalOf;",
+            "import dagger.Provides;",
+            "import javax.inject.Inject;",
+            "",
+            "@dagger.Module",
+            "abstract class TestModule {",
+            "  @Nullable @Provides static String provideString() { return null; }",
+            "  @BindsOptionalOf abstract String optionalString();",
+            "}");
+    JavaFileObject component =
+        JavaFileObjects.forSourceLines(
+            "test.TestComponent",
+            "package test;",
+            "",
+            "import dagger.Component;",
+            "",
+            "@Component(modules = TestModule.class)",
+            "interface TestComponent {",
+            "  A a();",
+            "}");
+    Compilation compilation = daggerCompiler().compile(NULLABLE, a, module, component);
+    assertThat(compilation).succeeded();
+  }
+
+  @Test
+  public void nullCheckForOptionalLazy() {
+    JavaFileObject a =
+        JavaFileObjects.forSourceLines(
+            "test.A",
+            "package test;",
+            "",
+            "import com.google.common.base.Optional;",
+            "import dagger.Lazy;",
+            "import javax.inject.Inject;",
+            "",
+            "final class A {",
+            "  @Inject A(Optional<Lazy<String>> optional) {}",
+            "}");
+    JavaFileObject module =
+        JavaFileObjects.forSourceLines(
+            "test.TestModule",
+            "package test;",
+            "",
+            "import dagger.BindsOptionalOf;",
+            "import dagger.Provides;",
+            "import javax.inject.Inject;",
+            "",
+            "@dagger.Module",
+            "abstract class TestModule {",
+            "  @Nullable @Provides static String provideString() { return null; }",
+            "  @BindsOptionalOf abstract String optionalString();",
+            "}");
+    JavaFileObject component =
+        JavaFileObjects.forSourceLines(
+            "test.TestComponent",
+            "package test;",
+            "",
+            "import dagger.Component;",
+            "",
+            "@Component(modules = TestModule.class)",
+            "interface TestComponent {",
+            "  A a();",
+            "}");
+    Compilation compilation = daggerCompiler().compile(NULLABLE, a, module, component);
+    assertThat(compilation).succeeded();
+  }
+
+  @Test
+  public void nullCheckForOptionalProviderOfLazy() {
+    JavaFileObject a =
+        JavaFileObjects.forSourceLines(
+            "test.A",
+            "package test;",
+            "",
+            "import com.google.common.base.Optional;",
+            "import dagger.Lazy;",
+            "import javax.inject.Inject;",
+            "import javax.inject.Provider;",
+            "",
+            "final class A {",
+            "  @Inject A(Optional<Provider<Lazy<String>>> optional) {}",
+            "}");
+    JavaFileObject module =
+        JavaFileObjects.forSourceLines(
+            "test.TestModule",
+            "package test;",
+            "",
+            "import dagger.BindsOptionalOf;",
+            "import dagger.Provides;",
+            "import javax.inject.Inject;",
+            "",
+            "@dagger.Module",
+            "abstract class TestModule {",
+            "  @Nullable @Provides static String provideString() { return null; }",
+            "  @BindsOptionalOf abstract String optionalString();",
+            "}");
+    JavaFileObject component =
+        JavaFileObjects.forSourceLines(
+            "test.TestComponent",
+            "package test;",
+            "",
+            "import dagger.Component;",
+            "",
+            "@Component(modules = TestModule.class)",
+            "interface TestComponent {",
+            "  A a();",
+            "}");
+    Compilation compilation = daggerCompiler().compile(NULLABLE, a, module, component);
+    assertThat(compilation).succeeded();
+  }
+
+  @Test
+  public void moduleValidation() {
+    JavaFileObject module =
+        JavaFileObjects.forSourceLines(
+            "test.TestModule",
+            "package test;",
+            "",
+            "import dagger.Binds;",
+            "import dagger.Module;",
+            "import dagger.Provides;",
+            "",
+            "@Module",
+            "abstract class TestModule {",
+            "  @Provides @Nullable static String nullableString() { return null; }",
+            "  @Binds abstract Object object(String string);",
+            "}");
+
+    Compilation compilation =
+        compilerWithOptions("-Adagger.fullBindingGraphValidation=ERROR")
+            .compile(module, NULLABLE);
+    assertThat(compilation).failed();
+    assertThat(compilation)
+        .hadErrorContaining(
+            nullableToNonNullable(
+                "String",
+                "@Provides @Nullable String TestModule.nullableString()"));
+  }
+}
diff --git a/javatests/dagger/internal/codegen/langmodel/AccessibilityTest.java b/javatests/dagger/internal/codegen/langmodel/AccessibilityTest.java
new file mode 100644
index 0000000..e9b81f3
--- /dev/null
+++ b/javatests/dagger/internal/codegen/langmodel/AccessibilityTest.java
@@ -0,0 +1,135 @@
+/*
+ * Copyright (C) 2015 The Dagger Authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package dagger.internal.codegen.langmodel;
+
+import static com.google.common.truth.Truth.assertThat;
+import static dagger.internal.codegen.langmodel.Accessibility.isElementAccessibleFrom;
+
+import com.google.testing.compile.CompilationRule;
+import javax.lang.model.element.Element;
+import javax.lang.model.element.TypeElement;
+import javax.lang.model.util.Elements;
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.JUnit4;
+
+@RunWith(JUnit4.class)
+@SuppressWarnings("unused") // contains a variety things used by the compilation rule for testing
+public class AccessibilityTest {
+  /* test data */
+  public AccessibilityTest() {}
+  protected AccessibilityTest(Object o) {}
+  AccessibilityTest(Object o1, Object o2) {}
+  private AccessibilityTest(Object o1, Object o2, Object o3) {}
+
+  public String publicField;
+  protected String protectedField;
+  String packagePrivateField;
+  private String privateField;
+
+  public void publicMethod() {}
+  protected void protectedMethod() {}
+  void packagePrivateMethod() {}
+  private void privateMethod() {}
+
+  public static final class PublicNestedClass {}
+  protected static final class ProtectedNestedClass {}
+  static final class PackagePrivateNestedClass {}
+  private static final class PrivateNestedClass {}
+
+  @Rule
+  public final CompilationRule compilationRule = new CompilationRule();
+
+  private TypeElement testElement;
+
+  @Before
+  public void setUp() {
+    Elements elements = compilationRule.getElements();
+    testElement = elements.getTypeElement(AccessibilityTest.class.getCanonicalName());
+  }
+
+  @Test
+  public void isElementAccessibleFrom_publicType() {
+    assertThat(isElementAccessibleFrom(testElement, "literally.anything")).isTrue();
+  }
+
+  @Test
+  public void isElementAccessibleFrom_publicMethod() {
+    Element member = getMemberNamed("publicMethod");
+    assertThat(isElementAccessibleFrom(member, "literally.anything")).isTrue();
+  }
+
+  @Test
+  public void isElementAccessibleFrom_protectedMethod() {
+    Element member = getMemberNamed("protectedMethod");
+    assertThat(isElementAccessibleFrom(member, "dagger.internal.codegen")).isTrue();
+    assertThat(isElementAccessibleFrom(member, "not.dagger.internal.codegen")).isFalse();
+  }
+
+  @Test
+  public void isElementAccessibleFrom_packagePrivateMethod() {
+    Element member = getMemberNamed("packagePrivateMethod");
+    assertThat(isElementAccessibleFrom(member, "dagger.internal.codegen")).isTrue();
+    assertThat(isElementAccessibleFrom(member, "not.dagger.internal.codegen")).isFalse();
+  }
+
+  @Test
+  public void isElementAccessibleFrom_privateMethod() {
+    Element member = getMemberNamed( "privateMethod");
+    assertThat(isElementAccessibleFrom(member, "dagger.internal.codegen")).isFalse();
+    assertThat(isElementAccessibleFrom(member, "not.dagger.internal.codegen")).isFalse();
+  }
+
+  @Test
+  public void isElementAccessibleFrom_publicField() {
+    Element member = getMemberNamed("publicField");
+    assertThat(isElementAccessibleFrom(member, "literally.anything")).isTrue();
+  }
+
+  @Test
+  public void isElementAccessibleFrom_protectedField() {
+    Element member = getMemberNamed("protectedField");
+    assertThat(isElementAccessibleFrom(member, "dagger.internal.codegen")).isTrue();
+    assertThat(isElementAccessibleFrom(member, "not.dagger.internal.codegen")).isFalse();
+  }
+
+  @Test
+  public void isElementAccessibleFrom_packagePrivateField() {
+    Element member = getMemberNamed("packagePrivateField");
+    assertThat(isElementAccessibleFrom(member, "dagger.internal.codegen")).isTrue();
+    assertThat(isElementAccessibleFrom(member, "not.dagger.internal.codegen")).isFalse();
+  }
+
+  @Test
+  public void isElementAccessibleFrom_privateField() {
+    Element member = getMemberNamed("privateField");
+    assertThat(isElementAccessibleFrom(member, "dagger.internal.codegen")).isFalse();
+    assertThat(isElementAccessibleFrom(member, "not.dagger.internal.codegen")).isFalse();
+  }
+
+  private Element getMemberNamed(String memberName) {
+    for (Element enclosedElement : testElement.getEnclosedElements()) {
+      if (enclosedElement.getSimpleName().contentEquals(memberName)) {
+        return enclosedElement;
+      }
+    }
+    throw new IllegalArgumentException();
+  }
+}
+
diff --git a/javatests/dagger/internal/codegen/validation/BUILD b/javatests/dagger/internal/codegen/validation/BUILD
new file mode 100644
index 0000000..03fd684
--- /dev/null
+++ b/javatests/dagger/internal/codegen/validation/BUILD
@@ -0,0 +1,33 @@
+# Copyright (C) 2020 The Dagger Authors.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+# Description:
+#   Tests for dagger.internal.codegen.validation
+
+load("//:build_defs.bzl", "DOCLINT_HTML_AND_SYNTAX")
+load("//:test_defs.bzl", "GenJavaTests")
+
+package(default_visibility = ["//:src"])
+
+GenJavaTests(
+    name = "validation_tests",
+    srcs = glob(["*.java"]),
+    functional = False,
+    javacopts = DOCLINT_HTML_AND_SYNTAX,
+    deps = [
+        "//java/dagger/internal/codegen/validation",
+        "@google_bazel_common//third_party/java/junit",
+        "@google_bazel_common//third_party/java/truth",
+    ],
+)
diff --git a/javatests/dagger/internal/codegen/validation/PackageNameCompressorTest.java b/javatests/dagger/internal/codegen/validation/PackageNameCompressorTest.java
new file mode 100644
index 0000000..f6a1b79
--- /dev/null
+++ b/javatests/dagger/internal/codegen/validation/PackageNameCompressorTest.java
@@ -0,0 +1,139 @@
+/*
+ * Copyright (C) 2020 The Dagger Authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package dagger.internal.codegen.validation;
+
+import static com.google.common.truth.Truth.assertThat;
+import static dagger.internal.codegen.validation.PackageNameCompressor.LEGEND_FOOTER;
+import static dagger.internal.codegen.validation.PackageNameCompressor.LEGEND_HEADER;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.JUnit4;
+
+/** Tests for {@link PackageNameCompressor}. */
+@RunWith(JUnit4.class)
+public class PackageNameCompressorTest {
+  @Test
+  public void testSimple() {
+    String input = "Something is wrong with foo.bar.baz.Foo class!";
+    String expectedOutput = "Something is wrong with Foo class!"
+        + LEGEND_HEADER
+        + "Foo: foo.bar.baz.Foo\n"
+        + LEGEND_FOOTER;
+    assertThat(PackageNameCompressor.compressPackagesInMessage(input)).isEqualTo(expectedOutput);
+  }
+
+  @Test
+  public void testSameSimpleNames() {
+    String input = "Something is wrong with foo.bar.baz.Foo and foo.bar.qux.Foo class!";
+    String expectedOutput = "Something is wrong with baz.Foo and qux.Foo class!"
+        + LEGEND_HEADER
+        + "baz.Foo: foo.bar.baz.Foo\n"
+        + "qux.Foo: foo.bar.qux.Foo\n"
+        + LEGEND_FOOTER;
+    assertThat(PackageNameCompressor.compressPackagesInMessage(input)).isEqualTo(expectedOutput);
+  }
+
+  @Test
+  public void testMethodNames() {
+    String input = "Something is wrong with foo.bar.baz.Foo.provideFoo()!";
+    String expectedOutput = "Something is wrong with Foo.provideFoo()!"
+        + LEGEND_HEADER
+        + "Foo: foo.bar.baz.Foo\n"
+        + LEGEND_FOOTER;
+    assertThat(PackageNameCompressor.compressPackagesInMessage(input)).isEqualTo(expectedOutput);
+  }
+
+  @Test
+  public void testMultipleLevelsOfConflicts() {
+    String input = "Something is wrong with z.a.b.c.Foo, z.b.b.c.Foo, z.a.b.d.Foo class!";
+    String expectedOutput = "Something is wrong with a.b.c.Foo, b.b.c.Foo, d.Foo class!"
+        + LEGEND_HEADER
+        + "a.b.c.Foo: z.a.b.c.Foo\n"
+        + "b.b.c.Foo: z.b.b.c.Foo\n"
+        + "d.Foo:     z.a.b.d.Foo\n"
+        + LEGEND_FOOTER;
+    assertThat(PackageNameCompressor.compressPackagesInMessage(input)).isEqualTo(expectedOutput);
+  }
+
+  // In some sense, we're really just compressing the outer class since the legend is going to
+  // only refer to the outer class.
+  @Test
+  public void testInnerClassesKeepOuterClassNameToo() {
+    String input = "Something is wrong with foo.bar.baz.Foo.Bar.Baz class!";
+    String expectedOutput = "Something is wrong with Foo.Bar.Baz class!"
+        + LEGEND_HEADER
+        + "Foo: foo.bar.baz.Foo\n"
+        + LEGEND_FOOTER;
+    assertThat(PackageNameCompressor.compressPackagesInMessage(input)).isEqualTo(expectedOutput);
+  }
+
+  // If relying on conflicts by inserting into the map, an extra conflict on c.Foo may result in
+  // uneven renaming because when the first two conflict on c.Foo they may make room for the next
+  // conflict to just take over what had previously been a conflict. Make sure that this unevenness
+  // doesn't happen.
+  @Test
+  public void testThreeMultiLevelConflicts() {
+    String input = "Something is wrong with z.a.c.Foo, z.b.c.Foo, and z.c.c.Foo class!";
+    String expectedOutput = "Something is wrong with a.c.Foo, b.c.Foo, and c.c.Foo class!"
+        + LEGEND_HEADER
+        + "a.c.Foo: z.a.c.Foo\n"
+        + "b.c.Foo: z.b.c.Foo\n"
+        + "c.c.Foo: z.c.c.Foo\n"
+        + LEGEND_FOOTER;
+    assertThat(PackageNameCompressor.compressPackagesInMessage(input)).isEqualTo(expectedOutput);
+  }
+
+  @Test
+  public void testDoesNotCompressSubstringsOfClasses() {
+    // This shouldn't try to compress the "ar.Foo" in "Bar.Foo"
+    String input = "Something is wrong with Bar.Foo class!";
+    assertThat(PackageNameCompressor.compressPackagesInMessage(input)).isEqualTo(input);
+  }
+
+  @Test
+  public void testNoClassNamesDoNotPutInLegend() {
+    String input = "Something is wrong with something!";
+    assertThat(PackageNameCompressor.compressPackagesInMessage(input)).isEqualTo(input);
+  }
+
+  @Test
+  public void testFullConflictsDoNotPutInLegend() {
+    String input = "Something is wrong with foo.Foo and bar.Foo class!";
+    // No shortening can be done without loss of clarity so do not modify this and add no legend.
+    assertThat(PackageNameCompressor.compressPackagesInMessage(input)).isEqualTo(input);
+  }
+
+  @Test
+  public void testLegendDoesNotIncludeJavaLang() {
+    String input = "Something is wrong with java.lang.Set, java.lang.a.Foo,"
+        + " and java.lang.b.Foo class!";
+    String expectedOutput = "Something is wrong with Set, a.Foo, and b.Foo class!"
+        + LEGEND_HEADER
+        + "a.Foo: java.lang.a.Foo\n"
+        + "b.Foo: java.lang.b.Foo\n"
+        + LEGEND_FOOTER;
+    assertThat(PackageNameCompressor.compressPackagesInMessage(input)).isEqualTo(expectedOutput);
+  }
+
+  @Test
+  public void testOnlyExcludedPrefixesDoesNotPutInLegend() {
+    String input = "Something is wrong with java.lang.Set class!";
+    String expectedOutput = "Something is wrong with Set class!";
+    assertThat(PackageNameCompressor.compressPackagesInMessage(input)).isEqualTo(expectedOutput);
+  }
+}
diff --git a/lint-baseline.xml b/lint-baseline.xml
index cf790c2..147f137 100644
--- a/lint-baseline.xml
+++ b/lint-baseline.xml
@@ -7,8 +7,19 @@
         errorLine1="    } catch (ClassNotFoundException"
         errorLine2="             ^">
         <location
-            file="external/dagger2/java/dagger/hilt/android/internal/testing/TestApplicationComponentManager.java"
-            line="85"
+            file="external/dagger2/java/dagger/hilt/android/internal/testing/EarlySingletonComponentCreator.java"
+            line="33"
+            column="14"/>
+    </issue>
+
+    <issue
+        id="NewApi"
+        message="Multi-catch with these reflection exceptions requires API level 19 (current min is 14) because they get compiled to the common but new super type `ReflectiveOperationException`. As a workaround either create individual catch statements, or catch `Exception`."
+        errorLine1="    } catch (ClassNotFoundException"
+        errorLine2="             ^">
+        <location
+            file="external/dagger2/java/dagger/hilt/android/internal/testing/TestComponentDataSupplier.java"
+            line="35"
             column="14"/>
     </issue>
 
diff --git a/test_defs.bzl b/test_defs.bzl
index d16ad36..6f23088 100644
--- a/test_defs.bzl
+++ b/test_defs.bzl
@@ -59,11 +59,21 @@
         test_javacopts = None,
         functional = True,
         manifest_values = None):
-    # TODO(ronshapiro): enable these with these instructions:
-    # https://docs.bazel.build/versions/master/be/android.html#android_local_test_examples
-    # We probably want to import all of Robolectric's dependencies into bazel-common because there
-    # are some differences (i.e. we both provide Guava).
-    pass
+    deps = (deps or []) + ["//:android_local_test_exports"]
+    _GenTests(
+        native.android_library,
+        native.android_local_test,
+        name,
+        srcs,
+        deps,
+        test_only_deps,
+        plugins,
+        javacopts,
+        lib_javacopts,
+        test_javacopts,
+        functional,
+        test_kwargs = {"manifest_values": manifest_values},
+    )
 
 def _GenTests(
         library_rule_type,
diff --git a/util/deploy-to-maven-central.sh b/util/deploy-to-maven-central.sh
index de2b49c..1a71f5f 100755
--- a/util/deploy-to-maven-central.sh
+++ b/util/deploy-to-maven-central.sh
@@ -22,21 +22,6 @@
 
 bash $(dirname $0)/run-local-tests.sh
 
-# Note: we detach from head before making any sed changes to avoid commiting
-# a particular version to master. This sed change used to be done at the very
-# end of the script, but with the introduction of "-alpha" to the Hilt
-# artifacts, we need to do the sed replacement before deploying the artifacts to
-# maven. Note, that this sed replacement is only done for versioned releases.
-# HEAD-SNAPSHOT and LOCAL_SNAPSHOT versions of Hilt artifacts do not contain
-# "-alpha".
-git checkout --detach
-
-# Set the version string that is used as a tag in all of our libraries. If
-# another repo depends on a versioned tag of Dagger, their java_library.tags
-# should match the versioned release.
-sed -i s/'#ALPHA_POSTFIX'/'+ "-alpha"'/g build_defs.bzl
-sed -i s/'${project.version}'/"${VERSION_NAME}"/g build_defs.bzl
-
 bash $(dirname $0)/deploy-dagger.sh \
   "gpg:sign-and-deploy-file" \
   "$VERSION_NAME" \
@@ -46,11 +31,20 @@
 
 bash $(dirname $0)/deploy-hilt.sh \
   "gpg:sign-and-deploy-file" \
-  "${VERSION_NAME}-alpha" \
+  "$VERSION_NAME" \
   "-DrepositoryId=sonatype-nexus-staging" \
   "-Durl=https://oss.sonatype.org/service/local/staging/deploy/maven2/" \
   "-Dgpg.keyname=${KEY}"
 
+# Note: we detach from head before making any sed changes to avoid commiting
+# a particular version to master.
+git checkout --detach
+
+# Set the version string that is used as a tag in all of our libraries. If
+# another repo depends on a versioned tag of Dagger, their java_library.tags
+# should match the versioned release.
+sed -i s/'${project.version}'/"${VERSION_NAME}"/g build_defs.bzl
+
 # Note: We avoid commiting until after deploying in case deploying fails and
 # we need to run the script again.
 git commit -m "${VERSION_NAME} release" build_defs.bzl
diff --git a/util/run-local-emulator-tests.sh b/util/run-local-emulator-tests.sh
index 7fc7762..347a632 100755
--- a/util/run-local-emulator-tests.sh
+++ b/util/run-local-emulator-tests.sh
@@ -2,17 +2,23 @@
 
 set -ex
 
+# Instrumentation tests log results to logcat, so enable it during test runs.
+adb logcat *:S TestRunner:V & LOGCAT_PID=$!
+
 readonly GRADLE_PROJECTS=(
     "javatests/artifacts/hilt-android/simple"
     "javatests/artifacts/hilt-android/simpleKotlin"
 )
 for project in "${GRADLE_PROJECTS[@]}"; do
     echo "Running gradle Android emulator tests for $project"
-    ./$project/gradlew -p $project connectedAndroidTest --no-daemon --stacktrace
+    ./$project/gradlew -p $project connectedAndroidTest --continue --no-daemon --stacktrace
 done
 
 # Run emulator tests in a project with configuration cache enabled
 # TODO(danysantiago): Once AGP 4.2.0 is stable, remove these project and enable
 # config cache in the other test projects.
 readonly CONFIG_CACHE_PROJECT="javatests/artifacts/hilt-android/gradleConfigCache"
-./$CONFIG_CACHE_PROJECT/gradlew -p $CONFIG_CACHE_PROJECT connectedAndroidTest --no-daemon --stacktrace --configuration-cache
+./$CONFIG_CACHE_PROJECT/gradlew -p $CONFIG_CACHE_PROJECT connectedAndroidTest --continue --no-daemon --stacktrace --configuration-cache
+
+# Close logcat
+if [ -n "$LOGCAT_PID" ] ; then kill $LOGCAT_PID; fi
diff --git a/util/run-local-gradle-android-tests.sh b/util/run-local-gradle-android-tests.sh
index ba51078..a9418a5 100755
--- a/util/run-local-gradle-android-tests.sh
+++ b/util/run-local-gradle-android-tests.sh
@@ -12,7 +12,7 @@
 for project in "${ANDROID_GRADLE_PROJECTS[@]}"; do
     echo "Running gradle tests for $project with AGP $AGP_VERSION_INPUT"
     AGP_VERSION=$AGP_VERSION_INPUT ./$project/gradlew -p $project buildDebug --no-daemon --stacktrace
-    AGP_VERSION=$AGP_VERSION_INPUT ./$project/gradlew -p $project testDebug --no-daemon --stacktrace
+    AGP_VERSION=$AGP_VERSION_INPUT ./$project/gradlew -p $project testDebug  --continue --no-daemon --stacktrace
 done
 
 # Run gradle tests in a project with configuration cache enabled
diff --git a/util/run-local-gradle-tests.sh b/util/run-local-gradle-tests.sh
index b88ccab..479158d 100755
--- a/util/run-local-gradle-tests.sh
+++ b/util/run-local-gradle-tests.sh
@@ -10,5 +10,5 @@
 for project in "${GRADLE_PROJECTS[@]}"; do
     echo "Running gradle tests for $project"
     ./$project/gradlew -p $project build --no-daemon --stacktrace
-    ./$project/gradlew -p $project test --no-daemon --stacktrace
+    ./$project/gradlew -p $project test  --continue --no-daemon --stacktrace
 done
diff --git a/workspace_defs.bzl b/workspace_defs.bzl
index c017b76..2a63e43 100644
--- a/workspace_defs.bzl
+++ b/workspace_defs.bzl
@@ -14,10 +14,10 @@
 
 """A macro to configure Dagger deps within a workspace"""
 
-load("//:build_defs.bzl", "POM_VERSION", "POM_VERSION_ALPHA")
+load("//:build_defs.bzl", "POM_VERSION")
 
 _DAGGER_VERSION = POM_VERSION
-_HILT_VERSION = POM_VERSION_ALPHA
+_HILT_VERSION = POM_VERSION
 
 DAGGER_ARTIFACTS = [
     "com.google.dagger:dagger:" + _DAGGER_VERSION,