Snap for 6948038 from 81788fb46daf729ae89ac357528a803ae965dcee to rvc-platform-release

Change-Id: Id72e5e614fc2b43e3995b3cb7ffce7affbfca008
diff --git a/android/androidmk.go b/android/androidmk.go
index f86061a..54a0c64 100644
--- a/android/androidmk.go
+++ b/android/androidmk.go
@@ -126,6 +126,26 @@
 	}
 }
 
+func (a *AndroidMkEntries) SetPaths(name string, paths Paths) {
+	if _, ok := a.EntryMap[name]; !ok {
+		a.entryOrder = append(a.entryOrder, name)
+	}
+	a.EntryMap[name] = paths.Strings()
+}
+
+func (a *AndroidMkEntries) SetOptionalPaths(name string, paths Paths) {
+	if len(paths) > 0 {
+		a.SetPaths(name, paths)
+	}
+}
+
+func (a *AndroidMkEntries) AddPaths(name string, paths Paths) {
+	if _, ok := a.EntryMap[name]; !ok {
+		a.entryOrder = append(a.entryOrder, name)
+	}
+	a.EntryMap[name] = append(a.EntryMap[name], paths.Strings()...)
+}
+
 func (a *AndroidMkEntries) SetBoolIfTrue(name string, flag bool) {
 	if flag {
 		if _, ok := a.EntryMap[name]; !ok {
diff --git a/android/arch.go b/android/arch.go
index d14221f..18ff13e 100644
--- a/android/arch.go
+++ b/android/arch.go
@@ -1440,20 +1440,15 @@
 			//         key: value,
 			//     },
 			// },
-			// TODO(ccross): is this still necessary with native bridge?
 			if os.Class == Device {
-				if (arch.ArchType == X86 && (hasArmAbi(arch) ||
-					hasArmAndroidArch(ctx.Config().Targets[Android]))) ||
-					(arch.ArchType == Arm &&
-						hasX86AndroidArch(ctx.Config().Targets[Android])) {
+				if arch.ArchType == X86 && (hasArmAbi(arch) ||
+					hasArmAndroidArch(ctx.Config().Targets[Android])) {
 					field := "Arm_on_x86"
 					prefix := "target.arm_on_x86"
 					m.appendProperties(ctx, genProps, targetProp, field, prefix)
 				}
-				if (arch.ArchType == X86_64 && (hasArmAbi(arch) ||
-					hasArmAndroidArch(ctx.Config().Targets[Android]))) ||
-					(arch.ArchType == Arm &&
-						hasX8664AndroidArch(ctx.Config().Targets[Android])) {
+				if arch.ArchType == X86_64 && (hasArmAbi(arch) ||
+					hasArmAndroidArch(ctx.Config().Targets[Android])) {
 					field := "Arm_on_x86_64"
 					prefix := "target.arm_on_x86_64"
 					m.appendProperties(ctx, genProps, targetProp, field, prefix)
@@ -1600,27 +1595,7 @@
 // hasArmArch returns true if targets has at least non-native_bridge arm Android arch
 func hasArmAndroidArch(targets []Target) bool {
 	for _, target := range targets {
-		if target.Os == Android && target.Arch.ArchType == Arm && target.NativeBridge == NativeBridgeDisabled {
-			return true
-		}
-	}
-	return false
-}
-
-// hasX86Arch returns true if targets has at least x86 Android arch
-func hasX86AndroidArch(targets []Target) bool {
-	for _, target := range targets {
-		if target.Os == Android && target.Arch.ArchType == X86 {
-			return true
-		}
-	}
-	return false
-}
-
-// hasX8664Arch returns true if targets has at least x86_64 Android arch
-func hasX8664AndroidArch(targets []Target) bool {
-	for _, target := range targets {
-		if target.Os == Android && target.Arch.ArchType == X86_64 {
+		if target.Os == Android && target.Arch.ArchType == Arm {
 			return true
 		}
 	}
diff --git a/apex/androidmk.go b/apex/androidmk.go
index 5879e56..e4cdef0 100644
--- a/apex/androidmk.go
+++ b/apex/androidmk.go
@@ -333,6 +333,10 @@
 				if apexType == imageApex {
 					fmt.Fprintln(w, "ALL_MODULES.$(LOCAL_MODULE).BUNDLE :=", a.bundleModuleFile.String())
 				}
+				if len(a.lintReports) > 0 {
+					fmt.Fprintln(w, "ALL_MODULES.$(my_register_name).LINT_REPORTS :=",
+						strings.Join(a.lintReports.Strings(), " "))
+				}
 
 				if a.installedFilesFile != nil {
 					goal := "checkbuild"
diff --git a/apex/apex.go b/apex/apex.go
index b4fae34..7da8e1c 100644
--- a/apex/apex.go
+++ b/apex/apex.go
@@ -1265,6 +1265,7 @@
 	hostRequiredModuleNames   []string
 
 	jacocoReportClassesFile android.Path     // only for javalibs and apps
+	lintDepSets             java.LintDepSets // only for javalibs and apps
 	certificate             java.Certificate // only for apps
 	overriddenPackageName   string           // only for apps
 }
@@ -1387,6 +1388,9 @@
 
 	// Struct holding the merged notice file paths in different formats
 	mergedNotices android.NoticeOutputs
+
+	// Optional list of lint report zip files for apexes that contain java or app modules
+	lintReports android.Paths
 }
 
 func addDependenciesForNativeModules(ctx android.BottomUpMutatorContext,
@@ -1749,9 +1753,16 @@
 type javaDependency interface {
 	DexJar() android.Path
 	JacocoReportClassesFile() android.Path
+	LintDepSets() java.LintDepSets
+
 	Stem() string
 }
 
+var _ javaDependency = (*java.Library)(nil)
+var _ javaDependency = (*java.SdkLibrary)(nil)
+var _ javaDependency = (*java.DexImport)(nil)
+var _ javaDependency = (*java.SdkLibraryImport)(nil)
+
 func apexFileForJavaLibrary(ctx android.BaseModuleContext, lib javaDependency, module android.Module) apexFile {
 	dirInApex := "javalib"
 	fileToCopy := lib.DexJar()
@@ -1759,6 +1770,7 @@
 	name := strings.TrimPrefix(module.Name(), "prebuilt_")
 	af := newApexFile(ctx, fileToCopy, name, dirInApex, javaSharedLib, module)
 	af.jacocoReportClassesFile = lib.JacocoReportClassesFile()
+	af.lintDepSets = lib.LintDepSets()
 	af.stem = lib.Stem() + ".jar"
 	return af
 }
@@ -2274,6 +2286,8 @@
 	a.compatSymlinks = makeCompatSymlinks(a.BaseModuleName(), ctx)
 
 	a.buildApexDependencyInfo(ctx)
+
+	a.buildLintReports(ctx)
 }
 
 // Enforce that Java deps of the apex are using stable SDKs to compile
diff --git a/apex/builder.go b/apex/builder.go
index 9da6ef8..8573dc0 100644
--- a/apex/builder.go
+++ b/apex/builder.go
@@ -752,3 +752,12 @@
 		},
 	})
 }
+
+func (a *apexBundle) buildLintReports(ctx android.ModuleContext) {
+	depSetsBuilder := java.NewLintDepSetBuilder()
+	for _, fi := range a.filesInfo {
+		depSetsBuilder.Transitive(fi.lintDepSets)
+	}
+
+	a.lintReports = java.BuildModuleLintReportZips(ctx, depSetsBuilder.Build())
+}
diff --git a/cc/builder.go b/cc/builder.go
index 37fbf4e..5deb129 100644
--- a/cc/builder.go
+++ b/cc/builder.go
@@ -76,12 +76,12 @@
 		&remoteexec.REParams{
 			Labels:          map[string]string{"type": "link", "tool": "clang"},
 			ExecStrategy:    "${config.RECXXLinksExecStrategy}",
-			Inputs:          []string{"${out}.rsp"},
+			Inputs:          []string{"${out}.rsp", "$implicitInputs"},
 			RSPFile:         "${out}.rsp",
 			OutputFiles:     []string{"${out}", "$implicitOutputs"},
 			ToolchainInputs: []string{"$ldCmd"},
 			Platform:        map[string]string{remoteexec.PoolKey: "${config.RECXXLinksPool}"},
-		}, []string{"ldCmd", "crtBegin", "libFlags", "crtEnd", "ldFlags", "extraLibFlags"}, []string{"implicitOutputs"})
+		}, []string{"ldCmd", "crtBegin", "libFlags", "crtEnd", "ldFlags", "extraLibFlags"}, []string{"implicitInputs", "implicitOutputs"})
 
 	partialLd, partialLdRE = remoteexec.StaticRules(pctx, "partialLd",
 		blueprint.RuleParams{
@@ -92,11 +92,11 @@
 		}, &remoteexec.REParams{
 			Labels:          map[string]string{"type": "link", "tool": "clang"},
 			ExecStrategy:    "${config.RECXXLinksExecStrategy}",
-			Inputs:          []string{"$inCommaList"},
+			Inputs:          []string{"$inCommaList", "$implicitInputs"},
 			OutputFiles:     []string{"${out}", "$implicitOutputs"},
 			ToolchainInputs: []string{"$ldCmd"},
 			Platform:        map[string]string{remoteexec.PoolKey: "${config.RECXXLinksPool}"},
-		}, []string{"ldCmd", "ldFlags"}, []string{"inCommaList", "implicitOutputs"})
+		}, []string{"ldCmd", "ldFlags"}, []string{"implicitInputs", "inCommaList", "implicitOutputs"})
 
 	ar = pctx.AndroidStaticRule("ar",
 		blueprint.RuleParams{
@@ -215,15 +215,23 @@
 		}, []string{"cFlags", "exportDirs"}, nil)
 
 	_ = pctx.SourcePathVariable("sAbiLinker", "prebuilts/clang-tools/${config.HostPrebuiltTag}/bin/header-abi-linker")
+	_ = pctx.SourcePathVariable("sAbiLinkerLibs", "prebuilts/clang-tools/${config.HostPrebuiltTag}/lib64")
 
-	sAbiLink = pctx.AndroidStaticRule("sAbiLink",
+	sAbiLink, sAbiLinkRE = remoteexec.StaticRules(pctx, "sAbiLink",
 		blueprint.RuleParams{
-			Command:        "$sAbiLinker -o ${out} $symbolFilter -arch $arch  $exportedHeaderFlags @${out}.rsp ",
+			Command:        "$reTemplate$sAbiLinker -o ${out} $symbolFilter -arch $arch  $exportedHeaderFlags @${out}.rsp ",
 			CommandDeps:    []string{"$sAbiLinker"},
 			Rspfile:        "${out}.rsp",
 			RspfileContent: "${in}",
-		},
-		"symbolFilter", "arch", "exportedHeaderFlags")
+		}, &remoteexec.REParams{
+			Labels:          map[string]string{"type": "tool", "name": "abi-linker"},
+			ExecStrategy:    "${config.REAbiLinkerExecStrategy}",
+			Inputs:          []string{"$sAbiLinkerLibs", "${out}.rsp", "$implicitInputs"},
+			RSPFile:         "${out}.rsp",
+			OutputFiles:     []string{"$out"},
+			ToolchainInputs: []string{"$sAbiLinker"},
+			Platform:        map[string]string{remoteexec.PoolKey: "${config.RECXXPool}"},
+		}, []string{"symbolFilter", "arch", "exportedHeaderFlags"}, []string{"implicitInputs"})
 
 	_ = pctx.SourcePathVariable("sAbiDiffer", "prebuilts/clang-tools/${config.HostPrebuiltTag}/bin/header-abi-diff")
 
@@ -695,6 +703,7 @@
 	if ctx.Config().IsEnvTrue("RBE_CXX_LINKS") {
 		rule = ldRE
 		args["implicitOutputs"] = strings.Join(implicitOutputs.Strings(), ",")
+		args["implicitInputs"] = strings.Join(deps.Strings(), ",")
 	}
 
 	ctx.Build(pctx, android.BuildParams{
@@ -729,17 +738,30 @@
 	for _, tag := range excludedSymbolTags {
 		symbolFilterStr += " --exclude-symbol-tag " + tag
 	}
+	rule := sAbiLink
+	args := map[string]string{
+		"symbolFilter":        symbolFilterStr,
+		"arch":                ctx.Arch().ArchType.Name,
+		"exportedHeaderFlags": exportedHeaderFlags,
+	}
+	if ctx.Config().IsEnvTrue("RBE_ABI_LINKER") {
+		rule = sAbiLinkRE
+		rbeImplicits := implicits.Strings()
+		for _, p := range strings.Split(exportedHeaderFlags, " ") {
+			if len(p) > 2 {
+				// Exclude the -I prefix.
+				rbeImplicits = append(rbeImplicits, p[2:])
+			}
+		}
+		args["implicitInputs"] = strings.Join(rbeImplicits, ",")
+	}
 	ctx.Build(pctx, android.BuildParams{
-		Rule:        sAbiLink,
+		Rule:        rule,
 		Description: "header-abi-linker " + outputFile.Base(),
 		Output:      outputFile,
 		Inputs:      sAbiDumps,
 		Implicits:   implicits,
-		Args: map[string]string{
-			"symbolFilter":        symbolFilterStr,
-			"arch":                ctx.Arch().ArchType.Name,
-			"exportedHeaderFlags": exportedHeaderFlags,
-		},
+		Args:        args,
 	})
 	return android.OptionalPathForPath(outputFile)
 }
@@ -839,6 +861,7 @@
 	if ctx.Config().IsEnvTrue("RBE_CXX_LINKS") {
 		rule = partialLdRE
 		args["inCommaList"] = strings.Join(objFiles.Strings(), ",")
+		args["implicitInputs"] = strings.Join(deps.Strings(), ",")
 	}
 	ctx.Build(pctx, android.BuildParams{
 		Rule:        rule,
diff --git a/cc/compiler.go b/cc/compiler.go
index 08ce133..f6c3587 100644
--- a/cc/compiler.go
+++ b/cc/compiler.go
@@ -333,6 +333,10 @@
 		}
 	}
 
+	if ctx.Target().NativeBridge == android.NativeBridgeEnabled {
+		flags.Global.CommonFlags = append(flags.Global.CommonFlags, "-D__ANDROID_NATIVE_BRIDGE__")
+	}
+
 	instructionSet := String(compiler.Properties.Instruction_set)
 	if flags.RequiredInstructionSet != "" {
 		instructionSet = flags.RequiredInstructionSet
diff --git a/cc/config/global.go b/cc/config/global.go
index fce0306..57d8db9 100644
--- a/cc/config/global.go
+++ b/cc/config/global.go
@@ -261,6 +261,7 @@
 	pctx.VariableFunc("RECXXLinksPool", remoteexec.EnvOverrideFunc("RBE_CXX_LINKS_POOL", remoteexec.DefaultPool))
 	pctx.VariableFunc("RECXXLinksExecStrategy", remoteexec.EnvOverrideFunc("RBE_CXX_LINKS_EXEC_STRATEGY", remoteexec.LocalExecStrategy))
 	pctx.VariableFunc("REAbiDumperExecStrategy", remoteexec.EnvOverrideFunc("RBE_ABI_DUMPER_EXEC_STRATEGY", remoteexec.LocalExecStrategy))
+	pctx.VariableFunc("REAbiLinkerExecStrategy", remoteexec.EnvOverrideFunc("RBE_ABI_LINKER_EXEC_STRATEGY", remoteexec.LocalExecStrategy))
 }
 
 var HostPrebuiltTag = pctx.VariableConfigMethod("HostPrebuiltTag", android.Config.PrebuiltOS)
diff --git a/cmd/soong_ui/main.go b/cmd/soong_ui/main.go
index db61fba..78e636c 100644
--- a/cmd/soong_ui/main.go
+++ b/cmd/soong_ui/main.go
@@ -117,6 +117,8 @@
 // Command is the type of soong_ui execution. Only one type of
 // execution is specified. The args are specific to the command.
 func main() {
+	buildStarted := time.Now()
+
 	c, args := getCommand(os.Args)
 	if c == nil {
 		fmt.Fprintf(os.Stderr, "The `soong` native UI is not yet available.\n")
@@ -136,6 +138,7 @@
 	defer trace.Close()
 
 	met := metrics.New()
+	met.SetBuildDateTime(buildStarted)
 
 	stat := &status.Status{}
 	defer stat.Finish()
@@ -166,19 +169,25 @@
 		logsDir = filepath.Join(config.DistDir(), "logs")
 	}
 
+	buildErrorFile := filepath.Join(logsDir, c.logsPrefix+"build_error")
+	rbeMetricsFile := filepath.Join(logsDir, c.logsPrefix+"rbe_metrics.pb")
+	soongMetricsFile := filepath.Join(logsDir, c.logsPrefix+"soong_metrics")
+	defer build.UploadMetrics(buildCtx, config, c.forceDumbOutput, buildStarted, buildErrorFile, rbeMetricsFile, soongMetricsFile)
+
 	os.MkdirAll(logsDir, 0777)
 	log.SetOutput(filepath.Join(logsDir, c.logsPrefix+"soong.log"))
 	trace.SetOutput(filepath.Join(logsDir, c.logsPrefix+"build.trace"))
 	stat.AddOutput(status.NewVerboseLog(log, filepath.Join(logsDir, c.logsPrefix+"verbose.log")))
 	stat.AddOutput(status.NewErrorLog(log, filepath.Join(logsDir, c.logsPrefix+"error.log")))
-	stat.AddOutput(status.NewProtoErrorLog(log, filepath.Join(logsDir, c.logsPrefix+"build_error")))
+	stat.AddOutput(status.NewProtoErrorLog(log, buildErrorFile))
 	stat.AddOutput(status.NewCriticalPath(log))
 
 	buildCtx.Verbosef("Detected %.3v GB total RAM", float32(config.TotalRAM())/(1024*1024*1024))
 	buildCtx.Verbosef("Parallelism (local/remote/highmem): %v/%v/%v",
 		config.Parallel(), config.RemoteParallel(), config.HighmemParallel())
 
-	defer met.Dump(filepath.Join(logsDir, c.logsPrefix+"soong_metrics"))
+	defer met.Dump(soongMetricsFile)
+	defer build.DumpRBEMetrics(buildCtx, config, rbeMetricsFile)
 
 	if start, ok := os.LookupEnv("TRACE_BEGIN_SOONG"); ok {
 		if !strings.HasSuffix(start, "N") {
diff --git a/java/androidmk.go b/java/androidmk.go
index 62f9706..ae257d7 100644
--- a/java/androidmk.go
+++ b/java/androidmk.go
@@ -132,9 +132,7 @@
 					}
 					entries.SetString("LOCAL_MODULE_STEM", library.Stem())
 
-					entries.AddOptionalPath("LOCAL_SOONG_LINT_REPORTS", library.linter.outputs.transitiveHTMLZip)
-					entries.AddOptionalPath("LOCAL_SOONG_LINT_REPORTS", library.linter.outputs.transitiveTextZip)
-					entries.AddOptionalPath("LOCAL_SOONG_LINT_REPORTS", library.linter.outputs.transitiveXMLZip)
+					entries.SetOptionalPaths("LOCAL_SOONG_LINT_REPORTS", library.linter.reports)
 				},
 			},
 		}
@@ -394,9 +392,7 @@
 					entries.AddStrings("LOCAL_SOONG_BUILT_INSTALLED", extra.String()+":"+install)
 				}
 
-				entries.AddOptionalPath("LOCAL_SOONG_LINT_REPORTS", app.linter.outputs.transitiveHTMLZip)
-				entries.AddOptionalPath("LOCAL_SOONG_LINT_REPORTS", app.linter.outputs.transitiveTextZip)
-				entries.AddOptionalPath("LOCAL_SOONG_LINT_REPORTS", app.linter.outputs.transitiveXMLZip)
+				entries.SetOptionalPaths("LOCAL_SOONG_LINT_REPORTS", app.linter.reports)
 			},
 		},
 		ExtraFooters: []android.AndroidMkExtraFootersFunc{
diff --git a/java/config/config.go b/java/config/config.go
index 95add01..1d0dd61 100644
--- a/java/config/config.go
+++ b/java/config/config.go
@@ -148,9 +148,9 @@
 	pctx.HostBinToolVariable("DexpreoptGen", "dexpreopt_gen")
 
 	pctx.VariableFunc("REJavaPool", remoteexec.EnvOverrideFunc("RBE_JAVA_POOL", "java16"))
-	pctx.VariableFunc("REJavacExecStrategy", remoteexec.EnvOverrideFunc("RBE_JAVAC_EXEC_STRATEGY", remoteexec.LocalExecStrategy))
-	pctx.VariableFunc("RED8ExecStrategy", remoteexec.EnvOverrideFunc("RBE_D8_EXEC_STRATEGY", remoteexec.LocalExecStrategy))
-	pctx.VariableFunc("RER8ExecStrategy", remoteexec.EnvOverrideFunc("RBE_R8_EXEC_STRATEGY", remoteexec.LocalExecStrategy))
+	pctx.VariableFunc("REJavacExecStrategy", remoteexec.EnvOverrideFunc("RBE_JAVAC_EXEC_STRATEGY", remoteexec.RemoteLocalFallbackExecStrategy))
+	pctx.VariableFunc("RED8ExecStrategy", remoteexec.EnvOverrideFunc("RBE_D8_EXEC_STRATEGY", remoteexec.RemoteLocalFallbackExecStrategy))
+	pctx.VariableFunc("RER8ExecStrategy", remoteexec.EnvOverrideFunc("RBE_R8_EXEC_STRATEGY", remoteexec.RemoteLocalFallbackExecStrategy))
 	pctx.VariableFunc("RETurbineExecStrategy", remoteexec.EnvOverrideFunc("RBE_TURBINE_EXEC_STRATEGY", remoteexec.LocalExecStrategy))
 	pctx.VariableFunc("RESignApkExecStrategy", remoteexec.EnvOverrideFunc("RBE_SIGNAPK_EXEC_STRATEGY", remoteexec.LocalExecStrategy))
 	pctx.VariableFunc("REJarExecStrategy", remoteexec.EnvOverrideFunc("RBE_JAR_EXEC_STRATEGY", remoteexec.LocalExecStrategy))
diff --git a/java/droiddoc.go b/java/droiddoc.go
index 230b1f0..b564fea 100644
--- a/java/droiddoc.go
+++ b/java/droiddoc.go
@@ -287,6 +287,9 @@
 	// the dirs which Metalava extracts API levels annotations from.
 	Api_levels_annotations_dirs []string
 
+	// the filename which Metalava extracts API levels annotations from. Defaults to android.jar.
+	Api_levels_jar_filename *string
+
 	// if set to true, collect the values used by the Dev tools and
 	// write them in files packaged with the SDK. Defaults to false.
 	Write_sdk_values *bool
@@ -1387,34 +1390,37 @@
 }
 
 func (d *Droidstubs) apiLevelsAnnotationsFlags(ctx android.ModuleContext, cmd *android.RuleBuilderCommand) {
-	if Bool(d.properties.Api_levels_annotations_enabled) {
-		d.apiVersionsXml = android.PathForModuleOut(ctx, "api-versions.xml")
-
-		if len(d.properties.Api_levels_annotations_dirs) == 0 {
-			ctx.PropertyErrorf("api_levels_annotations_dirs",
-				"has to be non-empty if api levels annotations was enabled!")
-		}
-
-		cmd.FlagWithOutput("--generate-api-levels ", d.apiVersionsXml)
-		cmd.FlagWithInput("--apply-api-levels ", d.apiVersionsXml)
-		cmd.FlagWithArg("--current-version ", ctx.Config().PlatformSdkVersion())
-		cmd.FlagWithArg("--current-codename ", ctx.Config().PlatformSdkCodename())
-
-		ctx.VisitDirectDepsWithTag(metalavaAPILevelsAnnotationsDirTag, func(m android.Module) {
-			if t, ok := m.(*ExportedDroiddocDir); ok {
-				for _, dep := range t.deps {
-					if strings.HasSuffix(dep.String(), "android.jar") {
-						cmd.Implicit(dep)
-					}
-				}
-				cmd.FlagWithArg("--android-jar-pattern ", t.dir.String()+"/%/public/android.jar")
-			} else {
-				ctx.PropertyErrorf("api_levels_annotations_dirs",
-					"module %q is not a metalava api-levels-annotations dir", ctx.OtherModuleName(m))
-			}
-		})
-
+	if !Bool(d.properties.Api_levels_annotations_enabled) {
+		return
 	}
+
+	d.apiVersionsXml = android.PathForModuleOut(ctx, "api-versions.xml")
+
+	if len(d.properties.Api_levels_annotations_dirs) == 0 {
+		ctx.PropertyErrorf("api_levels_annotations_dirs",
+			"has to be non-empty if api levels annotations was enabled!")
+	}
+
+	cmd.FlagWithOutput("--generate-api-levels ", d.apiVersionsXml)
+	cmd.FlagWithInput("--apply-api-levels ", d.apiVersionsXml)
+	cmd.FlagWithArg("--current-version ", ctx.Config().PlatformSdkVersion())
+	cmd.FlagWithArg("--current-codename ", ctx.Config().PlatformSdkCodename())
+
+	filename := proptools.StringDefault(d.properties.Api_levels_jar_filename, "android.jar")
+
+	ctx.VisitDirectDepsWithTag(metalavaAPILevelsAnnotationsDirTag, func(m android.Module) {
+		if t, ok := m.(*ExportedDroiddocDir); ok {
+			for _, dep := range t.deps {
+				if strings.HasSuffix(dep.String(), filename) {
+					cmd.Implicit(dep)
+				}
+			}
+			cmd.FlagWithArg("--android-jar-pattern ", t.dir.String()+"/%/public/"+filename)
+		} else {
+			ctx.PropertyErrorf("api_levels_annotations_dirs",
+				"module %q is not a metalava api-levels-annotations dir", ctx.OtherModuleName(m))
+		}
+	})
 }
 
 func (d *Droidstubs) apiToXmlFlags(ctx android.ModuleContext, cmd *android.RuleBuilderCommand) {
diff --git a/java/java.go b/java/java.go
index 0a764e6..4612b76 100644
--- a/java/java.go
+++ b/java/java.go
@@ -1683,6 +1683,9 @@
 		j.linter.compileSdkVersion = lintSDKVersionString(j.sdkVersion())
 		j.linter.javaLanguageLevel = flags.javaVersion.String()
 		j.linter.kotlinLanguageLevel = "1.3"
+		if j.ApexName() != "" && ctx.Config().UnbundledBuild() {
+			j.linter.buildModuleReportZip = true
+		}
 		j.linter.lint(ctx)
 	}
 
@@ -2738,6 +2741,10 @@
 	return nil
 }
 
+func (a *DexImport) LintDepSets() LintDepSets {
+	return LintDepSets{}
+}
+
 func (j *DexImport) IsInstallable() bool {
 	return true
 }
diff --git a/java/java_test.go b/java/java_test.go
index f0de52f..8797119 100644
--- a/java/java_test.go
+++ b/java/java_test.go
@@ -1006,6 +1006,62 @@
 	}
 }
 
+func TestDroidstubs(t *testing.T) {
+	ctx, _ := testJavaWithFS(t, `
+		droiddoc_exported_dir {
+		    name: "droiddoc-templates-sdk",
+		    path: ".",
+		}
+
+		droidstubs {
+		    name: "bar-stubs",
+		    srcs: [
+		        "bar-doc/a.java",
+				],
+				api_levels_annotations_dirs: [
+					"droiddoc-templates-sdk",
+				],
+				api_levels_annotations_enabled: true,
+		}
+
+		droidstubs {
+		    name: "bar-stubs-other",
+		    srcs: [
+		        "bar-doc/a.java",
+				],
+				api_levels_annotations_dirs: [
+					"droiddoc-templates-sdk",
+				],
+				api_levels_annotations_enabled: true,
+				api_levels_jar_filename: "android.other.jar",
+		}
+		`,
+		map[string][]byte{
+			"bar-doc/a.java": nil,
+		})
+	testcases := []struct {
+		moduleName          string
+		expectedJarFilename string
+	}{
+		{
+			moduleName:          "bar-stubs",
+			expectedJarFilename: "android.jar",
+		},
+		{
+			moduleName:          "bar-stubs-other",
+			expectedJarFilename: "android.other.jar",
+		},
+	}
+	for _, c := range testcases {
+		m := ctx.ModuleForTests(c.moduleName, "android_common")
+		metalava := m.Rule("metalava")
+		expected := "--android-jar-pattern ./%/public/" + c.expectedJarFilename
+		if actual := metalava.RuleParams.Command; !strings.Contains(actual, expected) {
+			t.Errorf("For %q, expected metalava argument %q, but was not found %q", c.moduleName, expected, actual)
+		}
+	}
+}
+
 func TestDroidstubsWithSystemModules(t *testing.T) {
 	ctx, _ := testJava(t, `
 		droidstubs {
diff --git a/java/lint.go b/java/lint.go
index 20a7dc4..6391067 100644
--- a/java/lint.go
+++ b/java/lint.go
@@ -17,6 +17,7 @@
 import (
 	"fmt"
 	"sort"
+	"strings"
 
 	"android/soong/android"
 )
@@ -68,28 +69,78 @@
 	outputs             lintOutputs
 	properties          LintProperties
 
+	reports android.Paths
+
 	buildModuleReportZip bool
 }
 
 type lintOutputs struct {
-	html android.ModuleOutPath
-	text android.ModuleOutPath
-	xml  android.ModuleOutPath
+	html android.Path
+	text android.Path
+	xml  android.Path
 
-	transitiveHTML *android.DepSet
-	transitiveText *android.DepSet
-	transitiveXML  *android.DepSet
-
-	transitiveHTMLZip android.OptionalPath
-	transitiveTextZip android.OptionalPath
-	transitiveXMLZip  android.OptionalPath
+	depSets LintDepSets
 }
 
-type lintOutputIntf interface {
+type lintOutputsIntf interface {
 	lintOutputs() *lintOutputs
 }
 
-var _ lintOutputIntf = (*linter)(nil)
+type lintDepSetsIntf interface {
+	LintDepSets() LintDepSets
+}
+
+type LintDepSets struct {
+	HTML, Text, XML *android.DepSet
+}
+
+type LintDepSetsBuilder struct {
+	HTML, Text, XML *android.DepSetBuilder
+}
+
+func NewLintDepSetBuilder() LintDepSetsBuilder {
+	return LintDepSetsBuilder{
+		HTML: android.NewDepSetBuilder(android.POSTORDER),
+		Text: android.NewDepSetBuilder(android.POSTORDER),
+		XML:  android.NewDepSetBuilder(android.POSTORDER),
+	}
+}
+
+func (l LintDepSetsBuilder) Direct(html, text, xml android.Path) LintDepSetsBuilder {
+	l.HTML.Direct(html)
+	l.Text.Direct(text)
+	l.XML.Direct(xml)
+	return l
+}
+
+func (l LintDepSetsBuilder) Transitive(depSets LintDepSets) LintDepSetsBuilder {
+	if depSets.HTML != nil {
+		l.HTML.Transitive(depSets.HTML)
+	}
+	if depSets.Text != nil {
+		l.Text.Transitive(depSets.Text)
+	}
+	if depSets.XML != nil {
+		l.XML.Transitive(depSets.XML)
+	}
+	return l
+}
+
+func (l LintDepSetsBuilder) Build() LintDepSets {
+	return LintDepSets{
+		HTML: l.HTML.Build(),
+		Text: l.Text.Build(),
+		XML:  l.XML.Build(),
+	}
+}
+
+func (l *linter) LintDepSets() LintDepSets {
+	return l.outputs.depSets
+}
+
+var _ lintDepSetsIntf = (*linter)(nil)
+
+var _ lintOutputsIntf = (*linter)(nil)
 
 func (l *linter) lintOutputs() *lintOutputs {
 	return &l.outputs
@@ -104,7 +155,16 @@
 		return
 	}
 
-	ctx.AddFarVariationDependencies(ctx.Config().BuildOSCommonTarget.Variations(), extraLintCheckTag, l.properties.Lint.Extra_check_modules...)
+	extraCheckModules := l.properties.Lint.Extra_check_modules
+
+	if checkOnly := ctx.Config().Getenv("ANDROID_LINT_CHECK"); checkOnly != "" {
+		if checkOnlyModules := ctx.Config().Getenv("ANDROID_LINT_CHECK_EXTRA_MODULES"); checkOnlyModules != "" {
+			extraCheckModules = strings.Split(checkOnlyModules, ",")
+		}
+	}
+
+	ctx.AddFarVariationDependencies(ctx.Config().BuildOSCommonTarget.Variations(),
+		extraLintCheckTag, extraCheckModules...)
 }
 
 func (l *linter) writeLintProjectXML(ctx android.ModuleContext,
@@ -237,16 +297,11 @@
 	text := android.PathForModuleOut(ctx, "lint-report.txt")
 	xml := android.PathForModuleOut(ctx, "lint-report.xml")
 
-	htmlDeps := android.NewDepSetBuilder(android.POSTORDER).Direct(html)
-	textDeps := android.NewDepSetBuilder(android.POSTORDER).Direct(text)
-	xmlDeps := android.NewDepSetBuilder(android.POSTORDER).Direct(xml)
+	depSetsBuilder := NewLintDepSetBuilder().Direct(html, text, xml)
 
 	ctx.VisitDirectDepsWithTag(staticLibTag, func(dep android.Module) {
-		if depLint, ok := dep.(lintOutputIntf); ok {
-			depLintOutputs := depLint.lintOutputs()
-			htmlDeps.Transitive(depLintOutputs.transitiveHTML)
-			textDeps.Transitive(depLintOutputs.transitiveText)
-			xmlDeps.Transitive(depLintOutputs.transitiveXML)
+		if depLint, ok := dep.(lintDepSetsIntf); ok {
+			depSetsBuilder.Transitive(depLint.LintDepSets())
 		}
 	})
 
@@ -262,7 +317,7 @@
 		apiVersionsXMLPath = copiedAPIVersionsXmlPath(ctx)
 	}
 
-	rule.Command().
+	cmd := rule.Command().
 		Text("(").
 		Flag("JAVA_OPTS=-Xmx2048m").
 		FlagWithArg("ANDROID_SDK_HOME=", homeDir.String()).
@@ -282,9 +337,13 @@
 		FlagWithArg("--url ", fmt.Sprintf(".=.,%s=out", android.PathForOutput(ctx).String())).
 		Flag("--exitcode").
 		Flags(l.properties.Lint.Flags).
-		Implicits(deps).
-		Text("|| (").Text("cat").Input(text).Text("; exit 7)").
-		Text(")")
+		Implicits(deps)
+
+	if checkOnly := ctx.Config().Getenv("ANDROID_LINT_CHECK"); checkOnly != "" {
+		cmd.FlagWithArg("--check ", checkOnly)
+	}
+
+	cmd.Text("|| (").Text("cat").Input(text).Text("; exit 7)").Text(")")
 
 	rule.Command().Text("rm -rf").Flag(cacheDir.String()).Flag(homeDir.String())
 
@@ -295,26 +354,35 @@
 		text: text,
 		xml:  xml,
 
-		transitiveHTML: htmlDeps.Build(),
-		transitiveText: textDeps.Build(),
-		transitiveXML:  xmlDeps.Build(),
+		depSets: depSetsBuilder.Build(),
 	}
 
 	if l.buildModuleReportZip {
-		htmlZip := android.PathForModuleOut(ctx, "lint-report-html.zip")
-		l.outputs.transitiveHTMLZip = android.OptionalPathForPath(htmlZip)
-		lintZip(ctx, l.outputs.transitiveHTML.ToSortedList(), htmlZip)
-
-		textZip := android.PathForModuleOut(ctx, "lint-report-text.zip")
-		l.outputs.transitiveTextZip = android.OptionalPathForPath(textZip)
-		lintZip(ctx, l.outputs.transitiveText.ToSortedList(), textZip)
-
-		xmlZip := android.PathForModuleOut(ctx, "lint-report-xml.zip")
-		l.outputs.transitiveXMLZip = android.OptionalPathForPath(xmlZip)
-		lintZip(ctx, l.outputs.transitiveXML.ToSortedList(), xmlZip)
+		l.reports = BuildModuleLintReportZips(ctx, l.LintDepSets())
 	}
 }
 
+func BuildModuleLintReportZips(ctx android.ModuleContext, depSets LintDepSets) android.Paths {
+	htmlList := depSets.HTML.ToSortedList()
+	textList := depSets.Text.ToSortedList()
+	xmlList := depSets.XML.ToSortedList()
+
+	if len(htmlList) == 0 && len(textList) == 0 && len(xmlList) == 0 {
+		return nil
+	}
+
+	htmlZip := android.PathForModuleOut(ctx, "lint-report-html.zip")
+	lintZip(ctx, htmlList, htmlZip)
+
+	textZip := android.PathForModuleOut(ctx, "lint-report-text.zip")
+	lintZip(ctx, textList, textZip)
+
+	xmlZip := android.PathForModuleOut(ctx, "lint-report-xml.zip")
+	lintZip(ctx, xmlList, xmlZip)
+
+	return android.Paths{htmlZip, textZip, xmlZip}
+}
+
 type lintSingleton struct {
 	htmlZip android.WritablePath
 	textZip android.WritablePath
@@ -389,7 +457,7 @@
 			return
 		}
 
-		if l, ok := m.(lintOutputIntf); ok {
+		if l, ok := m.(lintOutputsIntf); ok {
 			outputs = append(outputs, l.lintOutputs())
 		}
 	})
@@ -400,7 +468,9 @@
 		var paths android.Paths
 
 		for _, output := range outputs {
-			paths = append(paths, get(output))
+			if p := get(output); p != nil {
+				paths = append(paths, p)
+			}
 		}
 
 		lintZip(ctx, paths, outputPath)
diff --git a/java/sdk_library.go b/java/sdk_library.go
index 679c075..f2a509a 100644
--- a/java/sdk_library.go
+++ b/java/sdk_library.go
@@ -2011,6 +2011,15 @@
 }
 
 // to satisfy apex.javaDependency interface
+func (module *SdkLibraryImport) LintDepSets() LintDepSets {
+	if module.implLibraryModule == nil {
+		return LintDepSets{}
+	} else {
+		return module.implLibraryModule.LintDepSets()
+	}
+}
+
+// to satisfy apex.javaDependency interface
 func (module *SdkLibraryImport) Stem() string {
 	return module.BaseModuleName()
 }
diff --git a/ui/build/Android.bp b/ui/build/Android.bp
index 2a5a51a..4ef2721 100644
--- a/ui/build/Android.bp
+++ b/ui/build/Android.bp
@@ -56,12 +56,15 @@
         "signal.go",
         "soong.go",
         "test_build.go",
+        "upload.go",
         "util.go",
     ],
     testSrcs: [
         "cleanbuild_test.go",
         "config_test.go",
         "environment_test.go",
+        "rbe_test.go",
+        "upload_test.go",
         "util_test.go",
         "proc_sync_test.go",
     ],
diff --git a/ui/build/config.go b/ui/build/config.go
index 3a1188b..4490e1e 100644
--- a/ui/build/config.go
+++ b/ui/build/config.go
@@ -15,6 +15,7 @@
 package build
 
 import (
+	"fmt"
 	"os"
 	"path/filepath"
 	"runtime"
@@ -23,6 +24,10 @@
 	"time"
 
 	"android/soong/shared"
+
+	"github.com/golang/protobuf/proto"
+
+	smpb "android/soong/ui/metrics/metrics_proto"
 )
 
 type Config struct{ *configImpl }
@@ -181,10 +186,22 @@
 		"EMPTY_NINJA_FILE",
 	)
 
+	if ret.UseGoma() {
+		ctx.Println("Goma for Android is being deprecated and replaced with RBE. See go/rbe_for_android for instructions on how to use RBE.")
+		ctx.Println()
+		ctx.Println("See go/goma_android_exceptions for exceptions.")
+		ctx.Fatalln("USE_GOMA flag is no longer supported.")
+	}
+
+	if ret.ForceUseGoma() {
+		ret.environ.Set("USE_GOMA", "true")
+	}
+
 	// Tell python not to spam the source tree with .pyc files.
 	ret.environ.Set("PYTHONDONTWRITEBYTECODE", "1")
 
-	ret.environ.Set("TMPDIR", absPath(ctx, ret.TempDir()))
+	tmpDir := absPath(ctx, ret.TempDir())
+	ret.environ.Set("TMPDIR", tmpDir)
 
 	// Always set ASAN_SYMBOLIZER_PATH so that ASAN-based tools can symbolize any crashes
 	symbolizerPath := filepath.Join("prebuilts/clang/host", ret.HostPrebuiltTag(),
@@ -256,12 +273,17 @@
 		ret.buildDateTime = strconv.FormatInt(time.Now().Unix(), 10)
 	}
 
-	if ctx.Metrics != nil {
-		ctx.Metrics.SetBuildDateTime(ret.buildDateTime)
-	}
 	ret.environ.Set("BUILD_DATETIME_FILE", buildDateTimeFile)
 
-	return Config{ret}
+	if ret.UseRBE() {
+		for k, v := range getRBEVars(ctx, Config{ret}) {
+			ret.environ.Set(k, v)
+		}
+	}
+
+	c := Config{ret}
+	storeConfigMetrics(ctx, c)
+	return c
 }
 
 // NewBuildActionConfig returns a build configuration based on the build action. The arguments are
@@ -270,6 +292,21 @@
 	return NewConfig(ctx, getConfigArgs(action, dir, ctx, args)...)
 }
 
+// storeConfigMetrics selects a set of configuration information and store in
+// the metrics system for further analysis.
+func storeConfigMetrics(ctx Context, config Config) {
+	if ctx.Metrics == nil {
+		return
+	}
+
+	b := &smpb.BuildConfig{
+		ForceUseGoma: proto.Bool(config.ForceUseGoma()),
+		UseGoma:      proto.Bool(config.UseGoma()),
+		UseRbe:       proto.Bool(config.UseRBE()),
+	}
+	ctx.Metrics.BuildConfig(b)
+}
+
 // getConfigArgs processes the command arguments based on the build action and creates a set of new
 // arguments to be accepted by Config.
 func getConfigArgs(action BuildAction, dir string, ctx Context, args []string) []string {
@@ -756,6 +793,18 @@
 	return c.totalRAM
 }
 
+// ForceUseGoma determines whether we should override Goma deprecation
+// and use Goma for the current build or not.
+func (c *configImpl) ForceUseGoma() bool {
+	if v, ok := c.environ.Get("FORCE_USE_GOMA"); ok {
+		v = strings.TrimSpace(v)
+		if v != "" && v != "false" {
+			return true
+		}
+	}
+	return false
+}
+
 func (c *configImpl) UseGoma() bool {
 	if v, ok := c.environ.Get("USE_GOMA"); ok {
 		v = strings.TrimSpace(v)
@@ -804,6 +853,75 @@
 	return true
 }
 
+func (c *configImpl) logDir() string {
+	if c.Dist() {
+		return filepath.Join(c.DistDir(), "logs")
+	}
+	return c.OutDir()
+}
+
+func (c *configImpl) rbeStatsOutputDir() string {
+	for _, f := range []string{"RBE_output_dir", "FLAG_output_dir"} {
+		if v, ok := c.environ.Get(f); ok {
+			return v
+		}
+	}
+	return c.logDir()
+}
+
+func (c *configImpl) rbeLogPath() string {
+	for _, f := range []string{"RBE_log_path", "FLAG_log_path"} {
+		if v, ok := c.environ.Get(f); ok {
+			return v
+		}
+	}
+	return fmt.Sprintf("text://%v/reproxy_log.txt", c.logDir())
+}
+
+func (c *configImpl) rbeExecRoot() string {
+	for _, f := range []string{"RBE_exec_root", "FLAG_exec_root"} {
+		if v, ok := c.environ.Get(f); ok {
+			return v
+		}
+	}
+	wd, err := os.Getwd()
+	if err != nil {
+		return ""
+	}
+	return wd
+}
+
+func (c *configImpl) rbeDir() string {
+	if v, ok := c.environ.Get("RBE_DIR"); ok {
+		return v
+	}
+	return "prebuilts/remoteexecution-client/live/"
+}
+
+func (c *configImpl) rbeReproxy() string {
+	for _, f := range []string{"RBE_re_proxy", "FLAG_re_proxy"} {
+		if v, ok := c.environ.Get(f); ok {
+			return v
+		}
+	}
+	return filepath.Join(c.rbeDir(), "reproxy")
+}
+
+func (c *configImpl) rbeAuth() (string, string) {
+	credFlags := []string{"use_application_default_credentials", "use_gce_credentials", "credential_file"}
+	for _, cf := range credFlags {
+		for _, f := range []string{"RBE_" + cf, "FLAG_" + cf} {
+			if v, ok := c.environ.Get(f); ok {
+				v = strings.TrimSpace(v)
+				if v != "" && v != "false" && v != "0" {
+					return "RBE_" + cf, v
+				}
+			}
+		}
+	}
+	return "RBE_use_application_default_credentials", "true"
+}
+
 func (c *configImpl) UseRemoteBuild() bool {
 	return c.UseGoma() || c.UseRBE()
 }
@@ -962,3 +1080,14 @@
 func (c *configImpl) IsPdkBuild() bool {
 	return c.pdkBuild
 }
+
+func (c *configImpl) BuildDateTime() string {
+	return c.buildDateTime
+}
+
+func (c *configImpl) MetricsUploaderApp() string {
+	if p, ok := c.environ.Get("ANDROID_ENABLE_METRICS_UPLOAD"); ok {
+		return p
+	}
+	return ""
+}
diff --git a/ui/build/config_test.go b/ui/build/config_test.go
index df618c4..7b14c47 100644
--- a/ui/build/config_test.go
+++ b/ui/build/config_test.go
@@ -26,6 +26,7 @@
 	"testing"
 
 	"android/soong/ui/logger"
+	"android/soong/ui/status"
 )
 
 func testContext() Context {
@@ -33,6 +34,7 @@
 		Context: context.Background(),
 		Logger:  logger.New(&bytes.Buffer{}),
 		Writer:  &bytes.Buffer{},
+		Status:  &status.Status{},
 	}}
 }
 
diff --git a/ui/build/dumpvars.go b/ui/build/dumpvars.go
index a3234e3..bd073e5 100644
--- a/ui/build/dumpvars.go
+++ b/ui/build/dumpvars.go
@@ -54,18 +54,16 @@
 
 	var ret map[string]string
 	if len(makeVars) > 0 {
+		// It's not safe to use the same TMPDIR as the build, as that can be removed.
 		tmpDir, err := ioutil.TempDir("", "dumpvars")
 		if err != nil {
 			return nil, err
 		}
 		defer os.RemoveAll(tmpDir)
 
-		// It's not safe to use the same TMPDIR as the build, as that can be removed.
-		config.Environment().Set("TMPDIR", tmpDir)
+		SetupLitePath(ctx, config, tmpDir)
 
-		SetupLitePath(ctx, config)
-
-		ret, err = dumpMakeVars(ctx, config, goals, makeVars, false)
+		ret, err = dumpMakeVars(ctx, config, goals, makeVars, false, tmpDir)
 		if err != nil {
 			return ret, err
 		}
@@ -82,7 +80,7 @@
 	return ret, nil
 }
 
-func dumpMakeVars(ctx Context, config Config, goals, vars []string, write_soong_vars bool) (map[string]string, error) {
+func dumpMakeVars(ctx Context, config Config, goals, vars []string, write_soong_vars bool, tmpDir string) (map[string]string, error) {
 	ctx.BeginTrace(metrics.RunKati, "dumpvars")
 	defer ctx.EndTrace()
 
@@ -98,6 +96,9 @@
 		cmd.Environment.Set("WRITE_SOONG_VARIABLES", "true")
 	}
 	cmd.Environment.Set("DUMP_MANY_VARS", strings.Join(vars, " "))
+	if tmpDir != "" {
+		cmd.Environment.Set("TMPDIR", tmpDir)
+	}
 	cmd.Sandbox = dumpvarsSandbox
 	output := bytes.Buffer{}
 	cmd.Stdout = &output
@@ -262,7 +263,7 @@
 		"BUILD_BROKEN_USES_BUILD_TARGET_TEST_CONFIG",
 	}, exportEnvVars...), BannerVars...)
 
-	make_vars, err := dumpMakeVars(ctx, config, config.Arguments(), allVars, true)
+	make_vars, err := dumpMakeVars(ctx, config, config.Arguments(), allVars, true, "")
 	if err != nil {
 		ctx.Fatalln("Error dumping make vars:", err)
 	}
diff --git a/ui/build/ninja.go b/ui/build/ninja.go
index 4fc1f01..dfc3be1 100644
--- a/ui/build/ninja.go
+++ b/ui/build/ninja.go
@@ -128,6 +128,16 @@
 			"GOMA_USE_LOCAL",
 
 			// RBE client
+			"RBE_compare",
+			"RBE_exec_root",
+			"RBE_exec_strategy",
+			"RBE_invocation_id",
+			"RBE_log_dir",
+			"RBE_platform",
+			"RBE_remote_accept_cache",
+			"RBE_remote_update_cache",
+			"RBE_server_address",
+			// TODO: remove old FLAG_ variables.
 			"FLAG_compare",
 			"FLAG_exec_root",
 			"FLAG_exec_strategy",
diff --git a/ui/build/path.go b/ui/build/path.go
index c34ba1b..f515775 100644
--- a/ui/build/path.go
+++ b/ui/build/path.go
@@ -55,8 +55,9 @@
 }
 
 // A "lite" version of SetupPath used for dumpvars, or other places that need
-// minimal overhead (but at the expense of logging).
-func SetupLitePath(ctx Context, config Config) {
+// minimal overhead (but at the expense of logging). If tmpDir is empty, the
+// default TMPDIR is used from config.
+func SetupLitePath(ctx Context, config Config, tmpDir string) {
 	if config.pathReplaced {
 		return
 	}
@@ -65,8 +66,11 @@
 	defer ctx.EndTrace()
 
 	origPath, _ := config.Environment().Get("PATH")
-	myPath, _ := config.Environment().Get("TMPDIR")
-	myPath = filepath.Join(myPath, "path")
+
+	if tmpDir == "" {
+		tmpDir, _ = config.Environment().Get("TMPDIR")
+	}
+	myPath := filepath.Join(tmpDir, "path")
 	ensureEmptyDirectoriesExist(ctx, myPath)
 
 	os.Setenv("PATH", origPath)
diff --git a/ui/build/rbe.go b/ui/build/rbe.go
index ceea4bf..6a26063 100644
--- a/ui/build/rbe.go
+++ b/ui/build/rbe.go
@@ -15,14 +15,57 @@
 package build
 
 import (
+	"fmt"
+	"math/rand"
+	"os"
 	"path/filepath"
+	"time"
 
 	"android/soong/ui/metrics"
 )
 
-const bootstrapCmd = "bootstrap"
-const rbeLeastNProcs = 2500
-const rbeLeastNFiles = 16000
+const (
+	rbeLeastNProcs = 2500
+	rbeLeastNFiles = 16000
+
+	// prebuilt RBE binaries
+	bootstrapCmd = "bootstrap"
+
+	// RBE metrics proto buffer file
+	rbeMetricsPBFilename = "rbe_metrics.pb"
+)
+
+func rbeCommand(ctx Context, config Config, rbeCmd string) string {
+	var cmdPath string
+	if rbeDir := config.rbeDir(); rbeDir != "" {
+		cmdPath = filepath.Join(rbeDir, rbeCmd)
+	} else {
+		ctx.Fatalf("rbe command path not found")
+	}
+
+	if _, err := os.Stat(cmdPath); err != nil && os.IsNotExist(err) {
+		ctx.Fatalf("rbe command %q not found", rbeCmd)
+	}
+
+	return cmdPath
+}
+
+func getRBEVars(ctx Context, config Config) map[string]string {
+	rand.Seed(time.Now().UnixNano())
+	vars := map[string]string{
+		"RBE_log_path":   config.rbeLogPath(),
+		"RBE_log_dir":    config.logDir(),
+		"RBE_re_proxy":   config.rbeReproxy(),
+		"RBE_exec_root":  config.rbeExecRoot(),
+		"RBE_output_dir": config.rbeStatsOutputDir(),
+	}
+	if config.StartRBE() {
+		vars["RBE_server_address"] = fmt.Sprintf("unix://%v/reproxy_%v.sock", absPath(ctx, config.TempDir()), rand.Intn(1000))
+	}
+	k, v := config.rbeAuth()
+	vars[k] = v
+	return vars
+}
 
 func startRBE(ctx Context, config Config) {
 	ctx.BeginTrace(metrics.RunSetupTool, "rbe_bootstrap")
@@ -35,18 +78,53 @@
 		ctx.Fatalf("max open files is insufficient: %d; want >= %d.\n", n, rbeLeastNFiles)
 	}
 
-	var rbeBootstrap string
-	if rbeDir, ok := config.Environment().Get("RBE_DIR"); ok {
-		rbeBootstrap = filepath.Join(rbeDir, bootstrapCmd)
-	} else if home, ok := config.Environment().Get("HOME"); ok {
-		rbeBootstrap = filepath.Join(home, "rbe", bootstrapCmd)
-	} else {
-		ctx.Fatalln("rbe bootstrap not found")
-	}
-
-	cmd := Command(ctx, config, "boostrap", rbeBootstrap)
+	cmd := Command(ctx, config, "startRBE bootstrap", rbeCommand(ctx, config, bootstrapCmd))
 
 	if output, err := cmd.CombinedOutput(); err != nil {
 		ctx.Fatalf("rbe bootstrap failed with: %v\n%s\n", err, output)
 	}
 }
+
+func stopRBE(ctx Context, config Config) {
+	cmd := Command(ctx, config, "stopRBE bootstrap", rbeCommand(ctx, config, bootstrapCmd), "-shutdown")
+	if output, err := cmd.CombinedOutput(); err != nil {
+		ctx.Fatalf("rbe bootstrap with shutdown failed with: %v\n%s\n", err, output)
+	}
+}
+
+// DumpRBEMetrics creates a metrics protobuf file containing RBE related metrics.
+// The protobuf file is created if RBE is enabled and the proxy service has
+// started. The proxy service is shutdown in order to dump the RBE metrics to the
+// protobuf file.
+func DumpRBEMetrics(ctx Context, config Config, filename string) {
+	ctx.BeginTrace(metrics.RunShutdownTool, "dump_rbe_metrics")
+	defer ctx.EndTrace()
+
+	// Remove the previous metrics file in case there is a failure or RBE has been
+	// disable for this run.
+	os.Remove(filename)
+
+	// If RBE is not enabled then there are no metrics to generate.
+	// If RBE does not require to start, the RBE proxy maybe started
+	// manually for debugging purpose and can generate the metrics
+	// afterwards.
+	if !config.StartRBE() {
+		return
+	}
+
+	outputDir := config.rbeStatsOutputDir()
+	if outputDir == "" {
+		ctx.Fatal("RBE output dir variable not defined. Aborting metrics dumping.")
+	}
+	metricsFile := filepath.Join(outputDir, rbeMetricsPBFilename)
+
+	// Stop the proxy first in order to generate the RBE metrics protobuf file.
+	stopRBE(ctx, config)
+
+	if metricsFile == filename {
+		return
+	}
+	if _, err := copyFile(metricsFile, filename); err != nil {
+		ctx.Fatalf("failed to copy %q to %q: %v\n", metricsFile, filename, err)
+	}
+}
diff --git a/ui/build/rbe_test.go b/ui/build/rbe_test.go
new file mode 100644
index 0000000..c56a33c
--- /dev/null
+++ b/ui/build/rbe_test.go
@@ -0,0 +1,142 @@
+// Copyright 2020 Google Inc. All rights reserved.
+//
+// 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 build
+
+import (
+	"fmt"
+	"io/ioutil"
+	"os"
+	"path/filepath"
+	"strings"
+	"testing"
+
+	"android/soong/ui/logger"
+)
+
+func TestDumpRBEMetrics(t *testing.T) {
+	ctx := testContext()
+	tests := []struct {
+		description string
+		env         []string
+		generated   bool
+	}{{
+		description: "RBE disabled",
+		env: []string{
+			"NOSTART_RBE=true",
+		},
+	}, {
+		description: "rbe metrics generated",
+		env: []string{
+			"USE_RBE=true",
+		},
+		generated: true,
+	}}
+
+	for _, tt := range tests {
+		t.Run(tt.description, func(t *testing.T) {
+			tmpDir, err := ioutil.TempDir("", "")
+			if err != nil {
+				t.Fatalf("failed to create a temp directory: %v", err)
+			}
+			defer os.RemoveAll(tmpDir)
+
+			rbeBootstrapCmd := filepath.Join(tmpDir, bootstrapCmd)
+			if err := ioutil.WriteFile(rbeBootstrapCmd, []byte(rbeBootstrapProgram), 0755); err != nil {
+				t.Fatalf("failed to create a fake bootstrap command file %s: %v", rbeBootstrapCmd, err)
+			}
+
+			env := Environment(tt.env)
+			env.Set("OUT_DIR", tmpDir)
+			env.Set("RBE_DIR", tmpDir)
+
+			tmpRBEDir, err := ioutil.TempDir("", "")
+			if err != nil {
+				t.Fatalf("failed to create a temp directory for RBE: %v", err)
+			}
+			defer os.RemoveAll(tmpRBEDir)
+			env.Set("RBE_output_dir", tmpRBEDir)
+
+			config := Config{&configImpl{
+				environ: &env,
+			}}
+
+			rbeMetricsFilename := filepath.Join(tmpDir, rbeMetricsPBFilename)
+			DumpRBEMetrics(ctx, config, rbeMetricsFilename)
+
+			// Validate that the rbe metrics file exists if RBE is enabled.
+			if _, err := os.Stat(rbeMetricsFilename); err == nil {
+				if !tt.generated {
+					t.Errorf("got true, want false for rbe metrics file %s to exist.", rbeMetricsFilename)
+				}
+			} else if os.IsNotExist(err) {
+				if tt.generated {
+					t.Errorf("got false, want true for rbe metrics file %s to exist.", rbeMetricsFilename)
+				}
+			} else {
+				t.Errorf("unknown error found on checking %s exists: %v", rbeMetricsFilename, err)
+			}
+		})
+	}
+}
+
+func TestDumpRBEMetricsErrors(t *testing.T) {
+	ctx := testContext()
+	tests := []struct {
+		description      string
+		bootstrapProgram string
+		expectedErr      string
+	}{{
+		description:      "stopRBE failed",
+		bootstrapProgram: "#!/bin/bash\nexit 1\n",
+		expectedErr:      "shutdown failed",
+	}}
+
+	for _, tt := range tests {
+		t.Run(tt.description, func(t *testing.T) {
+			defer logger.Recover(func(err error) {
+				got := err.Error()
+				if !strings.Contains(got, tt.expectedErr) {
+					t.Errorf("got %q, want %q to be contained in error", got, tt.expectedErr)
+				}
+			})
+
+			tmpDir, err := ioutil.TempDir("", "")
+			if err != nil {
+				t.Fatalf("failed to create a temp directory: %v", err)
+			}
+			defer os.RemoveAll(tmpDir)
+
+			rbeBootstrapCmd := filepath.Join(tmpDir, bootstrapCmd)
+			if err := ioutil.WriteFile(rbeBootstrapCmd, []byte(tt.bootstrapProgram), 0755); err != nil {
+				t.Fatalf("failed to create a fake bootstrap command file %s: %v", rbeBootstrapCmd, err)
+			}
+
+			env := &Environment{}
+			env.Set("USE_RBE", "true")
+			env.Set("OUT_DIR", tmpDir)
+			env.Set("RBE_DIR", tmpDir)
+
+			config := Config{&configImpl{
+				environ: env,
+			}}
+
+			rbeMetricsFilename := filepath.Join(tmpDir, rbeMetricsPBFilename)
+			DumpRBEMetrics(ctx, config, rbeMetricsFilename)
+			t.Errorf("got nil, expecting %q as a failure", tt.expectedErr)
+		})
+	}
+}
+
+var rbeBootstrapProgram = fmt.Sprintf("#!/bin/bash\necho 1 > $RBE_output_dir/%s\n", rbeMetricsPBFilename)
diff --git a/ui/build/upload.go b/ui/build/upload.go
new file mode 100644
index 0000000..1cc2e94
--- /dev/null
+++ b/ui/build/upload.go
@@ -0,0 +1,113 @@
+// Copyright 2020 Google Inc. All rights reserved.
+//
+// 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 build
+
+// This file contains the functionality to upload data from one location to
+// another.
+
+import (
+	"io/ioutil"
+	"os"
+	"path/filepath"
+	"time"
+
+	"android/soong/ui/metrics"
+	"github.com/golang/protobuf/proto"
+
+	upload_proto "android/soong/ui/metrics/upload_proto"
+)
+
+const (
+	uploadPbFilename = ".uploader.pb"
+)
+
+var (
+	// For testing purpose
+	getTmpDir = ioutil.TempDir
+)
+
+// UploadMetrics uploads a set of metrics files to a server for analysis. An
+// uploader full path is required to be specified in order to upload the set
+// of metrics files. This is accomplished by defining the ANDROID_ENABLE_METRICS_UPLOAD
+// environment variable. The metrics files are copied to a temporary directory
+// and the uploader is then executed in the background to allow the user to continue
+// working.
+func UploadMetrics(ctx Context, config Config, forceDumbOutput bool, buildStarted time.Time, files ...string) {
+	ctx.BeginTrace(metrics.RunSetupTool, "upload_metrics")
+	defer ctx.EndTrace()
+
+	uploader := config.MetricsUploaderApp()
+	// No metrics to upload if the path to the uploader was not specified.
+	if uploader == "" {
+		return
+	}
+
+	// Some files may not exist. For example, build errors protobuf file
+	// may not exist since the build was successful.
+	var metricsFiles []string
+	for _, f := range files {
+		if _, err := os.Stat(f); err == nil {
+			metricsFiles = append(metricsFiles, f)
+		}
+	}
+
+	if len(metricsFiles) == 0 {
+		return
+	}
+
+	// The temporary directory cannot be deleted as the metrics uploader is started
+	// in the background and requires to exist until the operation is done. The
+	// uploader can delete the directory as it is specified in the upload proto.
+	tmpDir, err := getTmpDir("", "upload_metrics")
+	if err != nil {
+		ctx.Fatalf("failed to create a temporary directory to store the list of metrics files: %v\n", err)
+	}
+
+	for i, src := range metricsFiles {
+		dst := filepath.Join(tmpDir, filepath.Base(src))
+		if _, err := copyFile(src, dst); err != nil {
+			ctx.Fatalf("failed to copy %q to %q: %v\n", src, dst, err)
+		}
+		metricsFiles[i] = dst
+	}
+
+	// For platform builds, the branch and target name is hardcoded to specific
+	// values for later extraction of the metrics in the data metrics pipeline.
+	data, err := proto.Marshal(&upload_proto.Upload{
+		CreationTimestampMs:   proto.Uint64(uint64(buildStarted.UnixNano() / int64(time.Millisecond))),
+		CompletionTimestampMs: proto.Uint64(uint64(time.Now().UnixNano() / int64(time.Millisecond))),
+		BranchName:            proto.String("developer-metrics"),
+		TargetName:            proto.String("platform-build-systems-metrics"),
+		MetricsFiles:          metricsFiles,
+		DirectoriesToDelete:   []string{tmpDir},
+	})
+	if err != nil {
+		ctx.Fatalf("failed to marshal metrics upload proto buffer message: %v\n", err)
+	}
+
+	pbFile := filepath.Join(tmpDir, uploadPbFilename)
+	if err := ioutil.WriteFile(pbFile, data, 0644); err != nil {
+		ctx.Fatalf("failed to write the marshaled metrics upload protobuf to %q: %v\n", pbFile, err)
+	}
+
+	// Start the uploader in the background as it takes several milliseconds to start the uploader
+	// and prepare the metrics for upload. This affects small commands like "lunch".
+	cmd := Command(ctx, config, "upload metrics", uploader, "--upload-metrics", pbFile)
+	if forceDumbOutput {
+		cmd.RunOrFatal()
+	} else {
+		cmd.RunAndStreamOrFatal()
+	}
+}
diff --git a/ui/build/upload_test.go b/ui/build/upload_test.go
new file mode 100644
index 0000000..dccf156
--- /dev/null
+++ b/ui/build/upload_test.go
@@ -0,0 +1,158 @@
+// Copyright 2020 Google Inc. All rights reserved.
+//
+// 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 build
+
+import (
+	"errors"
+	"io/ioutil"
+	"os"
+	"path/filepath"
+	"strconv"
+	"strings"
+	"testing"
+	"time"
+
+	"android/soong/ui/logger"
+)
+
+func TestUploadMetrics(t *testing.T) {
+	ctx := testContext()
+	tests := []struct {
+		description string
+		uploader    string
+		createFiles bool
+		files       []string
+	}{{
+		description: "ANDROID_ENABLE_METRICS_UPLOAD not set",
+	}, {
+		description: "no metrics files to upload",
+		uploader:    "fake",
+	}, {
+		description: "non-existent metrics files no upload",
+		uploader:    "fake",
+		files:       []string{"metrics_file_1", "metrics_file_2", "metrics_file_3"},
+	}, {
+		description: "trigger upload",
+		uploader:    "echo",
+		createFiles: true,
+		files:       []string{"metrics_file_1", "metrics_file_2"},
+	}}
+
+	for _, tt := range tests {
+		t.Run(tt.description, func(t *testing.T) {
+			defer logger.Recover(func(err error) {
+				t.Fatalf("got unexpected error: %v", err)
+			})
+
+			outDir, err := ioutil.TempDir("", "")
+			if err != nil {
+				t.Fatalf("failed to create out directory: %v", outDir)
+			}
+			defer os.RemoveAll(outDir)
+
+			// Supply our own getTmpDir to delete the temp dir once the test is done.
+			orgGetTmpDir := getTmpDir
+			getTmpDir = func(string, string) (string, error) {
+				retDir := filepath.Join(outDir, "tmp_upload_dir")
+				if err := os.Mkdir(retDir, 0755); err != nil {
+					t.Fatalf("failed to create temporary directory %q: %v", retDir, err)
+				}
+				return retDir, nil
+			}
+			defer func() { getTmpDir = orgGetTmpDir }()
+
+			metricsUploadDir := filepath.Join(outDir, ".metrics_uploader")
+			if err := os.Mkdir(metricsUploadDir, 0755); err != nil {
+				t.Fatalf("failed to create %q directory for oauth valid check: %v", metricsUploadDir, err)
+			}
+
+			var metricsFiles []string
+			if tt.createFiles {
+				for _, f := range tt.files {
+					filename := filepath.Join(outDir, f)
+					metricsFiles = append(metricsFiles, filename)
+					if err := ioutil.WriteFile(filename, []byte("test file"), 0644); err != nil {
+						t.Fatalf("failed to create a fake metrics file %q for uploading: %v", filename, err)
+					}
+				}
+			}
+
+			config := Config{&configImpl{
+				environ: &Environment{
+					"OUT_DIR=" + outDir,
+					"ANDROID_ENABLE_METRICS_UPLOAD=" + tt.uploader,
+				},
+				buildDateTime: strconv.FormatInt(time.Now().UnixNano()/int64(time.Millisecond), 10),
+			}}
+
+			UploadMetrics(ctx, config, false, time.Now(), metricsFiles...)
+		})
+	}
+}
+
+func TestUploadMetricsErrors(t *testing.T) {
+	ctx := testContext()
+	tests := []struct {
+		description string
+		tmpDir      string
+		tmpDirErr   error
+		expectedErr string
+	}{{
+		description: "getTmpDir returned error",
+		tmpDirErr:   errors.New("getTmpDir failed"),
+		expectedErr: "getTmpDir failed",
+	}, {
+		description: "copyFile operation error",
+		tmpDir:      "/fake_dir",
+		expectedErr: "failed to copy",
+	}}
+
+	for _, tt := range tests {
+		t.Run(tt.description, func(t *testing.T) {
+			defer logger.Recover(func(err error) {
+				got := err.Error()
+				if !strings.Contains(got, tt.expectedErr) {
+					t.Errorf("got %q, want %q to be contained in error", got, tt.expectedErr)
+				}
+			})
+
+			outDir, err := ioutil.TempDir("", "")
+			if err != nil {
+				t.Fatalf("failed to create out directory: %v", outDir)
+			}
+			defer os.RemoveAll(outDir)
+
+			orgGetTmpDir := getTmpDir
+			getTmpDir = func(string, string) (string, error) {
+				return tt.tmpDir, tt.tmpDirErr
+			}
+			defer func() { getTmpDir = orgGetTmpDir }()
+
+			metricsFile := filepath.Join(outDir, "metrics_file_1")
+			if err := ioutil.WriteFile(metricsFile, []byte("test file"), 0644); err != nil {
+				t.Fatalf("failed to create a fake metrics file %q for uploading: %v", metricsFile, err)
+			}
+
+			config := Config{&configImpl{
+				environ: &Environment{
+					"ANDROID_ENABLE_METRICS_UPLOAD=fake",
+					"OUT_DIR=/bad",
+				}}}
+
+			UploadMetrics(ctx, config, true, time.Now(), metricsFile)
+			t.Errorf("got nil, expecting %q as a failure", tt.expectedErr)
+		})
+	}
+}
diff --git a/ui/build/util.go b/ui/build/util.go
index 75e6753..d44cd6d 100644
--- a/ui/build/util.go
+++ b/ui/build/util.go
@@ -15,6 +15,7 @@
 package build
 
 import (
+	"io"
 	"os"
 	"path/filepath"
 	"strings"
@@ -124,3 +125,20 @@
 	}
 	return str[:idx], str[idx+1:], true
 }
+
+// copyFile copies a file from src to dst. filepath.Dir(dst) must exist.
+func copyFile(src, dst string) (int64, error) {
+	source, err := os.Open(src)
+	if err != nil {
+		return 0, err
+	}
+	defer source.Close()
+
+	destination, err := os.Create(dst)
+	if err != nil {
+		return 0, err
+	}
+	defer destination.Close()
+
+	return io.Copy(destination, source)
+}
diff --git a/ui/build/util_test.go b/ui/build/util_test.go
index 89bfc77..b22e997 100644
--- a/ui/build/util_test.go
+++ b/ui/build/util_test.go
@@ -15,6 +15,7 @@
 package build
 
 import (
+	"bytes"
 	"io/ioutil"
 	"os"
 	"path/filepath"
@@ -49,3 +50,72 @@
 
 	ensureEmptyDirectoriesExist(ctx, filepath.Join(tmpDir, "a"))
 }
+
+func TestCopyFile(t *testing.T) {
+	tmpDir, err := ioutil.TempDir("", "test_copy_file")
+	if err != nil {
+		t.Fatalf("failed to create temporary directory to hold test text files: %v", err)
+	}
+	defer os.Remove(tmpDir)
+
+	data := []byte("fake data")
+	src := filepath.Join(tmpDir, "src.txt")
+	if err := ioutil.WriteFile(src, data, 0755); err != nil {
+		t.Fatalf("failed to create a src file %q for copying: %v", src, err)
+	}
+
+	dst := filepath.Join(tmpDir, "dst.txt")
+
+	l, err := copyFile(src, dst)
+	if err != nil {
+		t.Fatalf("got %v, expecting nil error on copyFile operation", err)
+	}
+
+	if l != int64(len(data)) {
+		t.Errorf("got %d, expecting %d for copied bytes", l, len(data))
+	}
+
+	dstData, err := ioutil.ReadFile(dst)
+	if err != nil {
+		t.Fatalf("got %v, expecting nil error reading dst %q file", err, dst)
+	}
+
+	if bytes.Compare(data, dstData) != 0 {
+		t.Errorf("got %q, expecting data %q from dst %q text file", string(data), string(dstData), dst)
+	}
+}
+
+func TestCopyFileErrors(t *testing.T) {
+	tmpDir, err := ioutil.TempDir("", "test_copy_file_errors")
+	if err != nil {
+		t.Fatalf("failed to create temporary directory to hold test text files: %v", err)
+	}
+	defer os.Remove(tmpDir)
+
+	srcExists := filepath.Join(tmpDir, "src_exist.txt")
+	if err := ioutil.WriteFile(srcExists, []byte("fake data"), 0755); err != nil {
+		t.Fatalf("failed to create a src file %q for copying: %v", srcExists, err)
+	}
+
+	tests := []struct {
+		description string
+		src         string
+		dst         string
+	}{{
+		description: "src file does not exist",
+		src:         "/src/not/exist",
+		dst:         "/dst/not/exist",
+	}, {
+		description: "dst directory does not exist",
+		src:         srcExists,
+		dst:         "/dst/not/exist",
+	}}
+
+	for _, tt := range tests {
+		t.Run(tt.description, func(t *testing.T) {
+			if _, err := copyFile(tt.src, tt.dst); err == nil {
+				t.Errorf("got nil, expecting error")
+			}
+		})
+	}
+}
diff --git a/ui/metrics/Android.bp b/ui/metrics/Android.bp
index 529639d..8188a69 100644
--- a/ui/metrics/Android.bp
+++ b/ui/metrics/Android.bp
@@ -17,6 +17,7 @@
     pkgPath: "android/soong/ui/metrics",
     deps: [
         "golang-protobuf-proto",
+        "soong-ui-metrics_upload_proto",
         "soong-ui-metrics_proto",
         "soong-ui-tracer",
     ],
@@ -24,6 +25,9 @@
         "metrics.go",
         "time.go",
     ],
+    testSrcs: [
+        "time_test.go",
+    ],
 }
 
 bootstrap_go_package {
@@ -35,3 +39,11 @@
     ],
 }
 
+bootstrap_go_package {
+    name: "soong-ui-metrics_upload_proto",
+    pkgPath: "android/soong/ui/metrics/upload_proto",
+    deps: ["golang-protobuf-proto"],
+    srcs: [
+        "upload_proto/upload.pb.go",
+    ],
+}
diff --git a/ui/metrics/metrics.go b/ui/metrics/metrics.go
index 8254e4a..65cae4a 100644
--- a/ui/metrics/metrics.go
+++ b/ui/metrics/metrics.go
@@ -17,7 +17,8 @@
 import (
 	"io/ioutil"
 	"os"
-	"strconv"
+	"runtime"
+	"time"
 
 	"github.com/golang/protobuf/proto"
 
@@ -25,12 +26,13 @@
 )
 
 const (
-	RunSetupTool = "setup"
-	RunKati      = "kati"
-	RunSoong     = "soong"
-	PrimaryNinja = "ninja"
-	TestRun      = "test"
-	Total        = "total"
+	PrimaryNinja    = "ninja"
+	RunKati         = "kati"
+	RunSetupTool    = "setup"
+	RunShutdownTool = "shutdown"
+	RunSoong        = "soong"
+	TestRun         = "test"
+	Total           = "total"
 )
 
 type Metrics struct {
@@ -64,6 +66,10 @@
 	}
 }
 
+func (m *Metrics) BuildConfig(b *soong_metrics_proto.BuildConfig) {
+	m.metrics.BuildConfig = b
+}
+
 func (m *Metrics) SetMetadataMetrics(metadata map[string]string) {
 	for k, v := range metadata {
 		switch k {
@@ -97,8 +103,6 @@
 			m.metrics.HostArch = m.getArch(v)
 		case "HOST_2ND_ARCH":
 			m.metrics.Host_2NdArch = m.getArch(v)
-		case "HOST_OS":
-			m.metrics.HostOs = proto.String(v)
 		case "HOST_OS_EXTRA":
 			m.metrics.HostOsExtra = proto.String(v)
 		case "HOST_CROSS_OS":
@@ -130,18 +134,13 @@
 	}
 }
 
-func (m *Metrics) SetBuildDateTime(date_time string) {
-	if date_time != "" {
-		date_time_timestamp, err := strconv.ParseInt(date_time, 10, 64)
-		if err != nil {
-			panic(err)
-		}
-		m.metrics.BuildDateTimestamp = &date_time_timestamp
-	}
+func (m *Metrics) SetBuildDateTime(buildTimestamp time.Time) {
+	m.metrics.BuildDateTimestamp = proto.Int64(buildTimestamp.UnixNano() / int64(time.Second))
 }
 
 // exports the output to the file at outputPath
 func (m *Metrics) Dump(outputPath string) (err error) {
+	m.metrics.HostOs = proto.String(runtime.GOOS)
 	return writeMessageToFile(&m.metrics, outputPath)
 }
 
diff --git a/ui/metrics/metrics_proto/metrics.pb.go b/ui/metrics/metrics_proto/metrics.pb.go
index 3986d0e..84706b2 100644
--- a/ui/metrics/metrics_proto/metrics.pb.go
+++ b/ui/metrics/metrics_proto/metrics.pb.go
@@ -152,7 +152,7 @@
 }
 
 func (ModuleTypeInfo_BuildSystem) EnumDescriptor() ([]byte, []int) {
-	return fileDescriptor_6039342a2ba47b72, []int{2, 0}
+	return fileDescriptor_6039342a2ba47b72, []int{3, 0}
 }
 
 type MetricsBase struct {
@@ -197,10 +197,11 @@
 	// The metrics for calling Ninja.
 	NinjaRuns []*PerfInfo `protobuf:"bytes,20,rep,name=ninja_runs,json=ninjaRuns" json:"ninja_runs,omitempty"`
 	// The metrics for the whole build
-	Total                *PerfInfo `protobuf:"bytes,21,opt,name=total" json:"total,omitempty"`
-	XXX_NoUnkeyedLiteral struct{}  `json:"-"`
-	XXX_unrecognized     []byte    `json:"-"`
-	XXX_sizecache        int32     `json:"-"`
+	Total                *PerfInfo    `protobuf:"bytes,21,opt,name=total" json:"total,omitempty"`
+	BuildConfig          *BuildConfig `protobuf:"bytes,23,opt,name=build_config,json=buildConfig" json:"build_config,omitempty"`
+	XXX_NoUnkeyedLiteral struct{}     `json:"-"`
+	XXX_unrecognized     []byte       `json:"-"`
+	XXX_sizecache        int32        `json:"-"`
 }
 
 func (m *MetricsBase) Reset()         { *m = MetricsBase{} }
@@ -380,6 +381,68 @@
 	return nil
 }
 
+func (m *MetricsBase) GetBuildConfig() *BuildConfig {
+	if m != nil {
+		return m.BuildConfig
+	}
+	return nil
+}
+
+type BuildConfig struct {
+	UseGoma              *bool    `protobuf:"varint,1,opt,name=use_goma,json=useGoma" json:"use_goma,omitempty"`
+	UseRbe               *bool    `protobuf:"varint,2,opt,name=use_rbe,json=useRbe" json:"use_rbe,omitempty"`
+	ForceUseGoma         *bool    `protobuf:"varint,3,opt,name=force_use_goma,json=forceUseGoma" json:"force_use_goma,omitempty"`
+	XXX_NoUnkeyedLiteral struct{} `json:"-"`
+	XXX_unrecognized     []byte   `json:"-"`
+	XXX_sizecache        int32    `json:"-"`
+}
+
+func (m *BuildConfig) Reset()         { *m = BuildConfig{} }
+func (m *BuildConfig) String() string { return proto.CompactTextString(m) }
+func (*BuildConfig) ProtoMessage()    {}
+func (*BuildConfig) Descriptor() ([]byte, []int) {
+	return fileDescriptor_6039342a2ba47b72, []int{1}
+}
+
+func (m *BuildConfig) XXX_Unmarshal(b []byte) error {
+	return xxx_messageInfo_BuildConfig.Unmarshal(m, b)
+}
+func (m *BuildConfig) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) {
+	return xxx_messageInfo_BuildConfig.Marshal(b, m, deterministic)
+}
+func (m *BuildConfig) XXX_Merge(src proto.Message) {
+	xxx_messageInfo_BuildConfig.Merge(m, src)
+}
+func (m *BuildConfig) XXX_Size() int {
+	return xxx_messageInfo_BuildConfig.Size(m)
+}
+func (m *BuildConfig) XXX_DiscardUnknown() {
+	xxx_messageInfo_BuildConfig.DiscardUnknown(m)
+}
+
+var xxx_messageInfo_BuildConfig proto.InternalMessageInfo
+
+func (m *BuildConfig) GetUseGoma() bool {
+	if m != nil && m.UseGoma != nil {
+		return *m.UseGoma
+	}
+	return false
+}
+
+func (m *BuildConfig) GetUseRbe() bool {
+	if m != nil && m.UseRbe != nil {
+		return *m.UseRbe
+	}
+	return false
+}
+
+func (m *BuildConfig) GetForceUseGoma() bool {
+	if m != nil && m.ForceUseGoma != nil {
+		return *m.ForceUseGoma
+	}
+	return false
+}
+
 type PerfInfo struct {
 	// The description for the phase/action/part while the tool running.
 	Desc *string `protobuf:"bytes,1,opt,name=desc" json:"desc,omitempty"`
@@ -402,7 +465,7 @@
 func (m *PerfInfo) String() string { return proto.CompactTextString(m) }
 func (*PerfInfo) ProtoMessage()    {}
 func (*PerfInfo) Descriptor() ([]byte, []int) {
-	return fileDescriptor_6039342a2ba47b72, []int{1}
+	return fileDescriptor_6039342a2ba47b72, []int{2}
 }
 
 func (m *PerfInfo) XXX_Unmarshal(b []byte) error {
@@ -474,7 +537,7 @@
 func (m *ModuleTypeInfo) String() string { return proto.CompactTextString(m) }
 func (*ModuleTypeInfo) ProtoMessage()    {}
 func (*ModuleTypeInfo) Descriptor() ([]byte, []int) {
-	return fileDescriptor_6039342a2ba47b72, []int{2}
+	return fileDescriptor_6039342a2ba47b72, []int{3}
 }
 
 func (m *ModuleTypeInfo) XXX_Unmarshal(b []byte) error {
@@ -532,7 +595,7 @@
 func (m *CriticalUserJourneyMetrics) String() string { return proto.CompactTextString(m) }
 func (*CriticalUserJourneyMetrics) ProtoMessage()    {}
 func (*CriticalUserJourneyMetrics) Descriptor() ([]byte, []int) {
-	return fileDescriptor_6039342a2ba47b72, []int{3}
+	return fileDescriptor_6039342a2ba47b72, []int{4}
 }
 
 func (m *CriticalUserJourneyMetrics) XXX_Unmarshal(b []byte) error {
@@ -579,7 +642,7 @@
 func (m *CriticalUserJourneysMetrics) String() string { return proto.CompactTextString(m) }
 func (*CriticalUserJourneysMetrics) ProtoMessage()    {}
 func (*CriticalUserJourneysMetrics) Descriptor() ([]byte, []int) {
-	return fileDescriptor_6039342a2ba47b72, []int{4}
+	return fileDescriptor_6039342a2ba47b72, []int{5}
 }
 
 func (m *CriticalUserJourneysMetrics) XXX_Unmarshal(b []byte) error {
@@ -612,6 +675,7 @@
 	proto.RegisterEnum("soong_build_metrics.MetricsBase_Arch", MetricsBase_Arch_name, MetricsBase_Arch_value)
 	proto.RegisterEnum("soong_build_metrics.ModuleTypeInfo_BuildSystem", ModuleTypeInfo_BuildSystem_name, ModuleTypeInfo_BuildSystem_value)
 	proto.RegisterType((*MetricsBase)(nil), "soong_build_metrics.MetricsBase")
+	proto.RegisterType((*BuildConfig)(nil), "soong_build_metrics.BuildConfig")
 	proto.RegisterType((*PerfInfo)(nil), "soong_build_metrics.PerfInfo")
 	proto.RegisterType((*ModuleTypeInfo)(nil), "soong_build_metrics.ModuleTypeInfo")
 	proto.RegisterType((*CriticalUserJourneyMetrics)(nil), "soong_build_metrics.CriticalUserJourneyMetrics")
@@ -621,58 +685,63 @@
 func init() { proto.RegisterFile("metrics.proto", fileDescriptor_6039342a2ba47b72) }
 
 var fileDescriptor_6039342a2ba47b72 = []byte{
-	// 847 bytes of a gzipped FileDescriptorProto
-	0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0x9c, 0x55, 0xdd, 0x6e, 0xdb, 0x36,
-	0x14, 0xae, 0x12, 0x25, 0xb6, 0x8e, 0x62, 0x57, 0x65, 0x52, 0x54, 0x5d, 0x11, 0xcc, 0x10, 0xd6,
-	0x21, 0x17, 0xab, 0x5b, 0x78, 0x45, 0x50, 0x18, 0xc5, 0x80, 0xc4, 0x31, 0x8a, 0x2e, 0xb0, 0x5d,
-	0x28, 0x71, 0x57, 0x6c, 0x17, 0x02, 0x23, 0xd1, 0x8d, 0x3a, 0x4b, 0x14, 0x48, 0xaa, 0x98, 0x1f,
-	0x62, 0x0f, 0xb9, 0x8b, 0xbd, 0xc7, 0xc0, 0x43, 0xc9, 0x51, 0x00, 0x0f, 0x09, 0x7a, 0x47, 0x9d,
-	0xef, 0x87, 0xdf, 0xa1, 0xc4, 0x23, 0xe8, 0x64, 0x4c, 0x89, 0x34, 0x96, 0xfd, 0x42, 0x70, 0xc5,
-	0xc9, 0xbe, 0xe4, 0x3c, 0xff, 0x1c, 0x5d, 0x95, 0xe9, 0x32, 0x89, 0x2a, 0x28, 0xf8, 0xc7, 0x01,
-	0x77, 0x62, 0xd6, 0xa7, 0x54, 0x32, 0xf2, 0x0a, 0x0e, 0x0c, 0x21, 0xa1, 0x8a, 0x45, 0x2a, 0xcd,
-	0x98, 0x54, 0x34, 0x2b, 0x7c, 0xab, 0x67, 0x1d, 0x6d, 0x87, 0x04, 0xb1, 0x33, 0xaa, 0xd8, 0x65,
-	0x8d, 0x90, 0xa7, 0xd0, 0x36, 0x8a, 0x34, 0xf1, 0xb7, 0x7a, 0xd6, 0x91, 0x13, 0xb6, 0xf0, 0xf9,
-	0x7d, 0x42, 0x86, 0xf0, 0xb4, 0x58, 0x52, 0xb5, 0xe0, 0x22, 0x8b, 0xbe, 0x32, 0x21, 0x53, 0x9e,
-	0x47, 0x31, 0x4f, 0x58, 0x4e, 0x33, 0xe6, 0x6f, 0x23, 0xf7, 0x49, 0x4d, 0xf8, 0x68, 0xf0, 0x51,
-	0x05, 0x93, 0xe7, 0xd0, 0x55, 0x54, 0x7c, 0x66, 0x2a, 0x2a, 0x04, 0x4f, 0xca, 0x58, 0xf9, 0x36,
-	0x0a, 0x3a, 0xa6, 0xfa, 0xc1, 0x14, 0x49, 0x02, 0x07, 0x15, 0xcd, 0x84, 0xf8, 0x4a, 0x45, 0x4a,
-	0x73, 0xe5, 0xef, 0xf4, 0xac, 0xa3, 0xee, 0xe0, 0x45, 0x7f, 0x43, 0xcf, 0xfd, 0x46, 0xbf, 0xfd,
-	0x53, 0x8d, 0x7c, 0x34, 0xa2, 0xe1, 0xf6, 0x78, 0xfa, 0x2e, 0x24, 0xc6, 0xaf, 0x09, 0x90, 0x19,
-	0xb8, 0xd5, 0x2e, 0x54, 0xc4, 0xd7, 0xfe, 0x2e, 0x9a, 0x3f, 0xbf, 0xd3, 0xfc, 0x44, 0xc4, 0xd7,
-	0xc3, 0xd6, 0x7c, 0x7a, 0x3e, 0x9d, 0xfd, 0x36, 0x0d, 0xc1, 0x58, 0xe8, 0x22, 0xe9, 0xc3, 0x7e,
-	0xc3, 0x70, 0x9d, 0xba, 0x85, 0x2d, 0x3e, 0xba, 0x21, 0xd6, 0x01, 0x7e, 0x82, 0x2a, 0x56, 0x14,
-	0x17, 0xe5, 0x9a, 0xde, 0x46, 0xba, 0x67, 0x90, 0x51, 0x51, 0xd6, 0xec, 0x73, 0x70, 0xae, 0xb9,
-	0xac, 0xc2, 0x3a, 0xdf, 0x14, 0xb6, 0xad, 0x0d, 0x30, 0x6a, 0x08, 0x1d, 0x34, 0x1b, 0xe4, 0x89,
-	0x31, 0x84, 0x6f, 0x32, 0x74, 0xb5, 0xc9, 0x20, 0x4f, 0xd0, 0xf3, 0x09, 0xb4, 0xd0, 0x93, 0x4b,
-	0xdf, 0xc5, 0x1e, 0x76, 0xf5, 0xe3, 0x4c, 0x92, 0xa0, 0xda, 0x8c, 0xcb, 0x88, 0xfd, 0xa5, 0x04,
-	0xf5, 0xf7, 0x10, 0x76, 0x0d, 0x3c, 0xd6, 0xa5, 0x35, 0x27, 0x16, 0x5c, 0x4a, 0x6d, 0xd1, 0xb9,
-	0xe1, 0x8c, 0x74, 0x6d, 0x26, 0xc9, 0x8f, 0xf0, 0xb0, 0xc1, 0xc1, 0xd8, 0x5d, 0xf3, 0xf9, 0xac,
-	0x59, 0x18, 0xe4, 0x05, 0xec, 0x37, 0x78, 0xeb, 0x16, 0x1f, 0x9a, 0x83, 0x5d, 0x73, 0x1b, 0xb9,
-	0x79, 0xa9, 0xa2, 0x24, 0x15, 0xbe, 0x67, 0x72, 0xf3, 0x52, 0x9d, 0xa5, 0x82, 0xfc, 0x02, 0xae,
-	0x64, 0xaa, 0x2c, 0x22, 0xc5, 0xf9, 0x52, 0xfa, 0x8f, 0x7a, 0xdb, 0x47, 0xee, 0xe0, 0x70, 0xe3,
-	0x11, 0x7d, 0x60, 0x62, 0xf1, 0x3e, 0x5f, 0xf0, 0x10, 0x50, 0x71, 0xa9, 0x05, 0x64, 0x08, 0xce,
-	0x9f, 0x54, 0xa5, 0x91, 0x28, 0x73, 0xe9, 0x93, 0xfb, 0xa8, 0xdb, 0x9a, 0x1f, 0x96, 0xb9, 0x24,
-	0x6f, 0x01, 0x0c, 0x13, 0xc5, 0xfb, 0xf7, 0x11, 0x3b, 0x88, 0xd6, 0xea, 0x3c, 0xcd, 0xbf, 0x50,
-	0xa3, 0x3e, 0xb8, 0x97, 0x1a, 0x05, 0xa8, 0xfe, 0x19, 0x76, 0x14, 0x57, 0x74, 0xe9, 0x3f, 0xee,
-	0x59, 0x77, 0x0b, 0x0d, 0x37, 0x78, 0x05, 0x7b, 0xb7, 0x6e, 0x57, 0x1b, 0xec, 0xf9, 0xc5, 0x38,
-	0xf4, 0x1e, 0x90, 0x0e, 0x38, 0x7a, 0x75, 0x36, 0x3e, 0x9d, 0xbf, 0xf3, 0x2c, 0xd2, 0x02, 0x7d,
-	0x23, 0xbd, 0xad, 0xe0, 0x2d, 0xd8, 0x78, 0xfe, 0x2e, 0xd4, 0xdf, 0x93, 0xf7, 0x40, 0xa3, 0x27,
-	0xe1, 0xc4, 0xb3, 0x88, 0x03, 0x3b, 0x27, 0xe1, 0xe4, 0xf8, 0xb5, 0xb7, 0xa5, 0x6b, 0x9f, 0xde,
-	0x1c, 0x7b, 0xdb, 0x04, 0x60, 0xf7, 0xd3, 0x9b, 0xe3, 0xe8, 0xf8, 0xb5, 0x67, 0x07, 0x7f, 0x5b,
-	0xd0, 0xae, 0x33, 0x10, 0x02, 0x76, 0xc2, 0x64, 0x8c, 0x03, 0xcd, 0x09, 0x71, 0xad, 0x6b, 0x38,
-	0x92, 0xcc, 0xf8, 0xc2, 0x35, 0x39, 0x04, 0x90, 0x8a, 0x0a, 0x85, 0x33, 0x10, 0x87, 0x95, 0x1d,
-	0x3a, 0x58, 0xd1, 0xa3, 0x8f, 0x3c, 0x03, 0x47, 0x30, 0xba, 0x34, 0xa8, 0x8d, 0x68, 0x5b, 0x17,
-	0x10, 0x3c, 0x04, 0xc8, 0x58, 0xc6, 0xc5, 0x2a, 0x2a, 0x25, 0xc3, 0x51, 0x64, 0x87, 0x8e, 0xa9,
-	0xcc, 0x25, 0x0b, 0xfe, 0xb5, 0xa0, 0x3b, 0xe1, 0x49, 0xb9, 0x64, 0x97, 0xab, 0x82, 0x61, 0xaa,
-	0x3f, 0x60, 0xcf, 0x9c, 0x99, 0x5c, 0x49, 0xc5, 0x32, 0x4c, 0xd7, 0x1d, 0xbc, 0xdc, 0x7c, 0xc7,
-	0x6e, 0x49, 0xcd, 0x04, 0xbb, 0x40, 0x59, 0xe3, 0xb6, 0x5d, 0xdd, 0x54, 0xc9, 0xf7, 0xe0, 0x66,
-	0xa8, 0x89, 0xd4, 0xaa, 0xa8, 0xbb, 0x84, 0x6c, 0x6d, 0x43, 0x7e, 0x80, 0x6e, 0x5e, 0x66, 0x11,
-	0x5f, 0x44, 0xa6, 0x28, 0xb1, 0xdf, 0x4e, 0xb8, 0x97, 0x97, 0xd9, 0x6c, 0x61, 0xf6, 0x93, 0xc1,
-	0x4b, 0x70, 0x1b, 0x7b, 0xdd, 0x7e, 0x17, 0x0e, 0xec, 0x5c, 0xcc, 0x66, 0x53, 0xfd, 0xd2, 0xda,
-	0x60, 0x4f, 0x4e, 0xce, 0xc7, 0xde, 0x56, 0xb0, 0x84, 0xef, 0x46, 0x22, 0x55, 0x69, 0x4c, 0x97,
-	0x73, 0xc9, 0xc4, 0xaf, 0xbc, 0x14, 0x39, 0x5b, 0x55, 0x23, 0x62, 0x7d, 0xe8, 0x56, 0xe3, 0xd0,
-	0x87, 0xd0, 0xaa, 0xba, 0xc4, 0x94, 0xee, 0xa0, 0x77, 0xd7, 0x94, 0x09, 0x6b, 0x41, 0x70, 0x05,
-	0xcf, 0x36, 0xec, 0x26, 0xeb, 0xed, 0x46, 0x60, 0xc7, 0xe5, 0x17, 0xe9, 0x5b, 0xf8, 0x85, 0x6f,
-	0x3e, 0xd9, 0xff, 0x4f, 0x1b, 0xa2, 0xf8, 0xf4, 0xf1, 0xef, 0xd5, 0x4f, 0xb4, 0x52, 0x44, 0xf8,
-	0x67, 0xfd, 0x2f, 0x00, 0x00, 0xff, 0xff, 0xc4, 0xbd, 0xe2, 0xb1, 0x69, 0x07, 0x00, 0x00,
+	// 924 bytes of a gzipped FileDescriptorProto
+	0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0x9c, 0x56, 0xdf, 0x6e, 0xdb, 0x36,
+	0x17, 0xaf, 0x62, 0x25, 0x96, 0x8e, 0x62, 0x57, 0x65, 0x52, 0x44, 0xfd, 0x8a, 0xe0, 0x33, 0x84,
+	0x75, 0xc8, 0xc5, 0x9a, 0x16, 0x59, 0x11, 0x14, 0x41, 0x31, 0x20, 0x71, 0x82, 0xa0, 0x0b, 0x6c,
+	0x17, 0x4c, 0xdc, 0x15, 0xdb, 0x85, 0x20, 0x4b, 0xb4, 0xa3, 0xce, 0x12, 0x0d, 0x92, 0x2a, 0xe6,
+	0x87, 0xd8, 0x6b, 0xec, 0xcd, 0xf6, 0x1e, 0x03, 0x0f, 0x25, 0x47, 0x01, 0x5c, 0x34, 0xe8, 0x9d,
+	0x74, 0x7e, 0x7f, 0xf8, 0x3b, 0x24, 0x75, 0x6c, 0xe8, 0xe4, 0x4c, 0x89, 0x2c, 0x91, 0x87, 0x0b,
+	0xc1, 0x15, 0x27, 0x3b, 0x92, 0xf3, 0x62, 0x16, 0x4d, 0xca, 0x6c, 0x9e, 0x46, 0x15, 0x14, 0xfe,
+	0x03, 0xe0, 0x0d, 0xcc, 0xf3, 0x59, 0x2c, 0x19, 0x79, 0x0d, 0xbb, 0x86, 0x90, 0xc6, 0x8a, 0x45,
+	0x2a, 0xcb, 0x99, 0x54, 0x71, 0xbe, 0x08, 0xac, 0x9e, 0x75, 0xd0, 0xa2, 0x04, 0xb1, 0xf3, 0x58,
+	0xb1, 0x9b, 0x1a, 0x21, 0xcf, 0xc0, 0x31, 0x8a, 0x2c, 0x0d, 0x36, 0x7a, 0xd6, 0x81, 0x4b, 0xdb,
+	0xf8, 0xfe, 0x3e, 0x25, 0x27, 0xf0, 0x6c, 0x31, 0x8f, 0xd5, 0x94, 0x8b, 0x3c, 0xfa, 0xc2, 0x84,
+	0xcc, 0x78, 0x11, 0x25, 0x3c, 0x65, 0x45, 0x9c, 0xb3, 0xa0, 0x85, 0xdc, 0xbd, 0x9a, 0xf0, 0xd1,
+	0xe0, 0xfd, 0x0a, 0x26, 0x2f, 0xa0, 0xab, 0x62, 0x31, 0x63, 0x2a, 0x5a, 0x08, 0x9e, 0x96, 0x89,
+	0x0a, 0x6c, 0x14, 0x74, 0x4c, 0xf5, 0x83, 0x29, 0x92, 0x14, 0x76, 0x2b, 0x9a, 0x09, 0xf1, 0x25,
+	0x16, 0x59, 0x5c, 0xa8, 0x60, 0xb3, 0x67, 0x1d, 0x74, 0x8f, 0x5e, 0x1e, 0xae, 0xe9, 0xf9, 0xb0,
+	0xd1, 0xef, 0xe1, 0x99, 0x46, 0x3e, 0x1a, 0xd1, 0x49, 0xeb, 0x62, 0x78, 0x49, 0x89, 0xf1, 0x6b,
+	0x02, 0x64, 0x04, 0x5e, 0xb5, 0x4a, 0x2c, 0x92, 0xdb, 0x60, 0x0b, 0xcd, 0x5f, 0x7c, 0xd3, 0xfc,
+	0x54, 0x24, 0xb7, 0x27, 0xed, 0xf1, 0xf0, 0x6a, 0x38, 0xfa, 0x6d, 0x48, 0xc1, 0x58, 0xe8, 0x22,
+	0x39, 0x84, 0x9d, 0x86, 0xe1, 0x2a, 0x75, 0x1b, 0x5b, 0x7c, 0x72, 0x47, 0xac, 0x03, 0xfc, 0x04,
+	0x55, 0xac, 0x28, 0x59, 0x94, 0x2b, 0xba, 0x83, 0x74, 0xdf, 0x20, 0xfd, 0x45, 0x59, 0xb3, 0xaf,
+	0xc0, 0xbd, 0xe5, 0xb2, 0x0a, 0xeb, 0x7e, 0x57, 0x58, 0x47, 0x1b, 0x60, 0x54, 0x0a, 0x1d, 0x34,
+	0x3b, 0x2a, 0x52, 0x63, 0x08, 0xdf, 0x65, 0xe8, 0x69, 0x93, 0xa3, 0x22, 0x45, 0xcf, 0x3d, 0x68,
+	0xa3, 0x27, 0x97, 0x81, 0x87, 0x3d, 0x6c, 0xe9, 0xd7, 0x91, 0x24, 0x61, 0xb5, 0x18, 0x97, 0x11,
+	0xfb, 0x4b, 0x89, 0x38, 0xd8, 0x46, 0xd8, 0x33, 0xf0, 0x85, 0x2e, 0xad, 0x38, 0x89, 0xe0, 0x52,
+	0x6a, 0x8b, 0xce, 0x1d, 0xa7, 0xaf, 0x6b, 0x23, 0x49, 0x7e, 0x84, 0xc7, 0x0d, 0x0e, 0xc6, 0xee,
+	0x9a, 0xeb, 0xb3, 0x62, 0x61, 0x90, 0x97, 0xb0, 0xd3, 0xe0, 0xad, 0x5a, 0x7c, 0x6c, 0x36, 0x76,
+	0xc5, 0x6d, 0xe4, 0xe6, 0xa5, 0x8a, 0xd2, 0x4c, 0x04, 0xbe, 0xc9, 0xcd, 0x4b, 0x75, 0x9e, 0x09,
+	0xf2, 0x0b, 0x78, 0x92, 0xa9, 0x72, 0x11, 0x29, 0xce, 0xe7, 0x32, 0x78, 0xd2, 0x6b, 0x1d, 0x78,
+	0x47, 0xfb, 0x6b, 0xb7, 0xe8, 0x03, 0x13, 0xd3, 0xf7, 0xc5, 0x94, 0x53, 0x40, 0xc5, 0x8d, 0x16,
+	0x90, 0x13, 0x70, 0xff, 0x8c, 0x55, 0x16, 0x89, 0xb2, 0x90, 0x01, 0x79, 0x88, 0xda, 0xd1, 0x7c,
+	0x5a, 0x16, 0x92, 0xbc, 0x03, 0x30, 0x4c, 0x14, 0xef, 0x3c, 0x44, 0xec, 0x22, 0x5a, 0xab, 0x8b,
+	0xac, 0xf8, 0x1c, 0x1b, 0xf5, 0xee, 0x83, 0xd4, 0x28, 0x40, 0xf5, 0xcf, 0xb0, 0xa9, 0xb8, 0x8a,
+	0xe7, 0xc1, 0xd3, 0x9e, 0xf5, 0x6d, 0xa1, 0xe1, 0x92, 0x3e, 0x6c, 0x1b, 0x42, 0xc2, 0x8b, 0x69,
+	0x36, 0x0b, 0xf6, 0x50, 0xdb, 0x5b, 0xab, 0xc5, 0xcf, 0xb0, 0x8f, 0x3c, 0xea, 0x4d, 0xee, 0x5e,
+	0xc2, 0xd7, 0xb0, 0x7d, 0xef, 0x13, 0x75, 0xc0, 0x1e, 0x5f, 0x5f, 0x50, 0xff, 0x11, 0xe9, 0x80,
+	0xab, 0x9f, 0xce, 0x2f, 0xce, 0xc6, 0x97, 0xbe, 0x45, 0xda, 0xa0, 0x3f, 0x6b, 0x7f, 0x23, 0x7c,
+	0x07, 0x36, 0x1e, 0xa2, 0x07, 0xf5, 0xa5, 0xf4, 0x1f, 0x69, 0xf4, 0x94, 0x0e, 0x7c, 0x8b, 0xb8,
+	0xb0, 0x79, 0x4a, 0x07, 0xc7, 0x6f, 0xfc, 0x0d, 0x5d, 0xfb, 0xf4, 0xf6, 0xd8, 0x6f, 0x11, 0x80,
+	0xad, 0x4f, 0x6f, 0x8f, 0xa3, 0xe3, 0x37, 0xbe, 0x1d, 0xce, 0xc0, 0x6b, 0x64, 0xd1, 0x53, 0xaf,
+	0x94, 0x2c, 0x9a, 0xf1, 0x3c, 0xc6, 0xd9, 0xe8, 0xd0, 0x76, 0x29, 0xd9, 0x25, 0xcf, 0x63, 0x7d,
+	0x49, 0x34, 0x24, 0x26, 0x0c, 0xe7, 0xa1, 0x43, 0xb7, 0x4a, 0xc9, 0xe8, 0x84, 0x91, 0x1f, 0xa0,
+	0x3b, 0xe5, 0x22, 0x61, 0xd1, 0x4a, 0xd9, 0x42, 0x7c, 0x1b, 0xab, 0x63, 0x23, 0x0f, 0xff, 0xb6,
+	0xc0, 0xa9, 0x77, 0x8c, 0x10, 0xb0, 0x53, 0x26, 0x13, 0x5c, 0xc2, 0xa5, 0xf8, 0xac, 0x6b, 0x38,
+	0x40, 0xcd, 0xb0, 0xc5, 0x67, 0xb2, 0x0f, 0x20, 0x55, 0x2c, 0x14, 0x4e, 0x6c, 0xb4, 0xb5, 0xa9,
+	0x8b, 0x15, 0x3d, 0xa8, 0xc9, 0x73, 0x70, 0x05, 0x8b, 0xe7, 0x06, 0xb5, 0x11, 0x75, 0x74, 0x01,
+	0xc1, 0x7d, 0x80, 0x9c, 0xe5, 0x5c, 0x2c, 0x75, 0x2e, 0x1c, 0x9c, 0x36, 0x75, 0x4d, 0x65, 0x2c,
+	0x59, 0xf8, 0xaf, 0x05, 0xdd, 0x01, 0x4f, 0xcb, 0x39, 0xbb, 0x59, 0x2e, 0x18, 0xa6, 0xfa, 0xa3,
+	0x3e, 0x40, 0xb9, 0x94, 0x8a, 0xe5, 0x98, 0xae, 0x7b, 0xf4, 0x6a, 0xfd, 0x44, 0xb8, 0x27, 0x35,
+	0xe7, 0x79, 0x8d, 0xb2, 0xc6, 0x6c, 0x98, 0xdc, 0x55, 0xc9, 0xff, 0xc1, 0xcb, 0x51, 0x13, 0xa9,
+	0xe5, 0xa2, 0xee, 0x12, 0xf2, 0x95, 0x8d, 0xde, 0xc6, 0xa2, 0xcc, 0x23, 0x3e, 0x8d, 0x4c, 0x51,
+	0x62, 0xbf, 0x1d, 0xba, 0x5d, 0x94, 0xf9, 0x68, 0x6a, 0xd6, 0x93, 0xe1, 0xab, 0xea, 0xbc, 0x2a,
+	0xd7, 0x7b, 0x87, 0xee, 0xc2, 0xe6, 0xf5, 0x68, 0x34, 0xd4, 0xb7, 0xc3, 0x01, 0x7b, 0x70, 0x7a,
+	0x75, 0xe1, 0x6f, 0x84, 0x73, 0xf8, 0x5f, 0x5f, 0x64, 0x2a, 0x4b, 0xe2, 0xf9, 0x58, 0x32, 0xf1,
+	0x2b, 0x2f, 0x45, 0xc1, 0x96, 0xd5, 0x40, 0x5b, 0x6d, 0xba, 0xd5, 0xd8, 0xf4, 0x13, 0x68, 0x57,
+	0x5d, 0x62, 0xca, 0xaf, 0x5d, 0xe1, 0xc6, 0x4c, 0xa4, 0xb5, 0x20, 0x9c, 0xc0, 0xf3, 0x35, 0xab,
+	0xc9, 0x7a, 0xb9, 0x3e, 0xd8, 0x49, 0xf9, 0x59, 0x06, 0x16, 0x7e, 0x8f, 0xeb, 0x77, 0xf6, 0xeb,
+	0x69, 0x29, 0x8a, 0xcf, 0x9e, 0xfe, 0x5e, 0xfd, 0xe4, 0x57, 0x8a, 0x08, 0xff, 0x07, 0xfc, 0x17,
+	0x00, 0x00, 0xff, 0xff, 0xdd, 0xe8, 0xec, 0xe5, 0x17, 0x08, 0x00, 0x00,
 }
diff --git a/ui/metrics/metrics_proto/metrics.proto b/ui/metrics/metrics_proto/metrics.proto
index 194aa6b..08f7d44 100644
--- a/ui/metrics/metrics_proto/metrics.proto
+++ b/ui/metrics/metrics_proto/metrics.proto
@@ -92,6 +92,16 @@
 
   // The metrics for the whole build
   optional PerfInfo total = 21;
+
+  optional BuildConfig build_config = 23;
+}
+
+message BuildConfig {
+  optional bool use_goma = 1;
+
+  optional bool use_rbe = 2;
+
+  optional bool force_use_goma = 3;
 }
 
 message PerfInfo {
diff --git a/ui/metrics/time.go b/ui/metrics/time.go
index b8baf16..4016563 100644
--- a/ui/metrics/time.go
+++ b/ui/metrics/time.go
@@ -19,13 +19,18 @@
 
 	"android/soong/ui/metrics/metrics_proto"
 	"android/soong/ui/tracer"
+	"github.com/golang/protobuf/proto"
 )
 
+// for testing purpose only
+var _now = now
+
 type timeEvent struct {
 	desc string
 	name string
 
-	atNanos uint64 // timestamp measured in nanoseconds since the reference date
+	// the time that the event started to occur.
+	start time.Time
 }
 
 type TimeTracer interface {
@@ -39,33 +44,26 @@
 
 var _ TimeTracer = &timeTracerImpl{}
 
-func (t *timeTracerImpl) now() uint64 {
-	return uint64(time.Now().UnixNano())
+func now() time.Time {
+	return time.Now()
 }
 
-func (t *timeTracerImpl) Begin(name, desc string, thread tracer.Thread) {
-	t.beginAt(name, desc, t.now())
+func (t *timeTracerImpl) Begin(name, desc string, _ tracer.Thread) {
+	t.activeEvents = append(t.activeEvents, timeEvent{name: name, desc: desc, start: _now()})
 }
 
-func (t *timeTracerImpl) beginAt(name, desc string, atNanos uint64) {
-	t.activeEvents = append(t.activeEvents, timeEvent{name: name, desc: desc, atNanos: atNanos})
-}
-
-func (t *timeTracerImpl) End(thread tracer.Thread) soong_metrics_proto.PerfInfo {
-	return t.endAt(t.now())
-}
-
-func (t *timeTracerImpl) endAt(atNanos uint64) soong_metrics_proto.PerfInfo {
+func (t *timeTracerImpl) End(tracer.Thread) soong_metrics_proto.PerfInfo {
 	if len(t.activeEvents) < 1 {
 		panic("Internal error: No pending events for endAt to end!")
 	}
 	lastEvent := t.activeEvents[len(t.activeEvents)-1]
 	t.activeEvents = t.activeEvents[:len(t.activeEvents)-1]
-	realTime := atNanos - lastEvent.atNanos
+	realTime := uint64(_now().Sub(lastEvent.start).Nanoseconds())
 
 	return soong_metrics_proto.PerfInfo{
-		Desc:      &lastEvent.desc,
-		Name:      &lastEvent.name,
-		StartTime: &lastEvent.atNanos,
-		RealTime:  &realTime}
+		Desc:      proto.String(lastEvent.desc),
+		Name:      proto.String(lastEvent.name),
+		StartTime: proto.Uint64(uint64(lastEvent.start.UnixNano())),
+		RealTime:  proto.Uint64(realTime),
+	}
 }
diff --git a/ui/metrics/time_test.go b/ui/metrics/time_test.go
new file mode 100644
index 0000000..d73080a
--- /dev/null
+++ b/ui/metrics/time_test.go
@@ -0,0 +1,42 @@
+// Copyright 2020 Google Inc. All Rights Reserved.
+//
+// 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 metrics
+
+import (
+	"testing"
+	"time"
+
+	"android/soong/ui/tracer"
+)
+
+func TestEnd(t *testing.T) {
+	startTime := time.Date(2020, time.July, 13, 13, 0, 0, 0, time.UTC)
+	dur := time.Nanosecond * 10
+	initialNow := _now
+	_now = func() time.Time { return startTime.Add(dur) }
+	defer func() { _now = initialNow }()
+
+	timeTracer := &timeTracerImpl{}
+	timeTracer.activeEvents = append(timeTracer.activeEvents, timeEvent{
+		desc:  "test",
+		name:  "test",
+		start: startTime,
+	})
+
+	perf := timeTracer.End(tracer.Thread(0))
+	if perf.GetRealTime() != uint64(dur.Nanoseconds()) {
+		t.Errorf("got %d, want %d nanoseconds for event duration", perf.GetRealTime(), dur.Nanoseconds())
+	}
+}
diff --git a/ui/metrics/upload_proto/regen.sh b/ui/metrics/upload_proto/regen.sh
new file mode 100755
index 0000000..4521df7
--- /dev/null
+++ b/ui/metrics/upload_proto/regen.sh
@@ -0,0 +1,17 @@
+#!/bin/bash
+
+# Generates the golang source file of upload.proto file.
+
+set -e
+
+function die() { echo "ERROR: $1" >&2; exit 1; }
+
+readonly error_msg="Maybe you need to run 'lunch aosp_arm-eng && m aprotoc blueprint_tools'?"
+
+if ! hash aprotoc &>/dev/null; then
+  die "could not find aprotoc. ${error_msg}"
+fi
+
+if ! aprotoc --go_out=paths=source_relative:. upload.proto; then
+  die "build failed. ${error_msg}"
+fi
diff --git a/ui/metrics/upload_proto/upload.pb.go b/ui/metrics/upload_proto/upload.pb.go
new file mode 100644
index 0000000..614d4c7
--- /dev/null
+++ b/ui/metrics/upload_proto/upload.pb.go
@@ -0,0 +1,134 @@
+// Code generated by protoc-gen-go. DO NOT EDIT.
+// source: upload.proto
+
+package soong_metrics_upload_proto
+
+import (
+	fmt "fmt"
+	proto "github.com/golang/protobuf/proto"
+	math "math"
+)
+
+// Reference imports to suppress errors if they are not otherwise used.
+var _ = proto.Marshal
+var _ = fmt.Errorf
+var _ = math.Inf
+
+// This is a compile-time assertion to ensure that this generated file
+// is compatible with the proto package it is being compiled against.
+// A compilation error at this line likely means your copy of the
+// proto package needs to be updated.
+const _ = proto.ProtoPackageIsVersion3 // please upgrade the proto package
+
+type Upload struct {
+	// The timestamp in milliseconds that the build was created.
+	CreationTimestampMs *uint64 `protobuf:"varint,1,opt,name=creation_timestamp_ms,json=creationTimestampMs" json:"creation_timestamp_ms,omitempty"`
+	// The timestamp in milliseconds when the build was completed.
+	CompletionTimestampMs *uint64 `protobuf:"varint,2,opt,name=completion_timestamp_ms,json=completionTimestampMs" json:"completion_timestamp_ms,omitempty"`
+	// The branch name.
+	BranchName *string `protobuf:"bytes,3,opt,name=branch_name,json=branchName" json:"branch_name,omitempty"`
+	// The target name.
+	TargetName *string `protobuf:"bytes,4,opt,name=target_name,json=targetName" json:"target_name,omitempty"`
+	// A list of metrics filepaths to upload.
+	MetricsFiles []string `protobuf:"bytes,5,rep,name=metrics_files,json=metricsFiles" json:"metrics_files,omitempty"`
+	// A list of directories to delete after the copy of metrics files
+	// is completed for uploading.
+	DirectoriesToDelete  []string `protobuf:"bytes,6,rep,name=directories_to_delete,json=directoriesToDelete" json:"directories_to_delete,omitempty"`
+	XXX_NoUnkeyedLiteral struct{} `json:"-"`
+	XXX_unrecognized     []byte   `json:"-"`
+	XXX_sizecache        int32    `json:"-"`
+}
+
+func (m *Upload) Reset()         { *m = Upload{} }
+func (m *Upload) String() string { return proto.CompactTextString(m) }
+func (*Upload) ProtoMessage()    {}
+func (*Upload) Descriptor() ([]byte, []int) {
+	return fileDescriptor_91b94b655bd2a7e5, []int{0}
+}
+
+func (m *Upload) XXX_Unmarshal(b []byte) error {
+	return xxx_messageInfo_Upload.Unmarshal(m, b)
+}
+func (m *Upload) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) {
+	return xxx_messageInfo_Upload.Marshal(b, m, deterministic)
+}
+func (m *Upload) XXX_Merge(src proto.Message) {
+	xxx_messageInfo_Upload.Merge(m, src)
+}
+func (m *Upload) XXX_Size() int {
+	return xxx_messageInfo_Upload.Size(m)
+}
+func (m *Upload) XXX_DiscardUnknown() {
+	xxx_messageInfo_Upload.DiscardUnknown(m)
+}
+
+var xxx_messageInfo_Upload proto.InternalMessageInfo
+
+func (m *Upload) GetCreationTimestampMs() uint64 {
+	if m != nil && m.CreationTimestampMs != nil {
+		return *m.CreationTimestampMs
+	}
+	return 0
+}
+
+func (m *Upload) GetCompletionTimestampMs() uint64 {
+	if m != nil && m.CompletionTimestampMs != nil {
+		return *m.CompletionTimestampMs
+	}
+	return 0
+}
+
+func (m *Upload) GetBranchName() string {
+	if m != nil && m.BranchName != nil {
+		return *m.BranchName
+	}
+	return ""
+}
+
+func (m *Upload) GetTargetName() string {
+	if m != nil && m.TargetName != nil {
+		return *m.TargetName
+	}
+	return ""
+}
+
+func (m *Upload) GetMetricsFiles() []string {
+	if m != nil {
+		return m.MetricsFiles
+	}
+	return nil
+}
+
+func (m *Upload) GetDirectoriesToDelete() []string {
+	if m != nil {
+		return m.DirectoriesToDelete
+	}
+	return nil
+}
+
+func init() {
+	proto.RegisterType((*Upload)(nil), "soong_metrics_upload.Upload")
+}
+
+func init() {
+	proto.RegisterFile("upload.proto", fileDescriptor_91b94b655bd2a7e5)
+}
+
+var fileDescriptor_91b94b655bd2a7e5 = []byte{
+	// 230 bytes of a gzipped FileDescriptorProto
+	0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0x6c, 0x90, 0xb1, 0x4a, 0x04, 0x31,
+	0x10, 0x86, 0xd9, 0xbb, 0xf3, 0xe0, 0xe2, 0xd9, 0xec, 0x79, 0x18, 0x44, 0x70, 0xd1, 0x66, 0x2b,
+	0x0b, 0x0b, 0x1f, 0x40, 0xc4, 0x4e, 0x8b, 0xe5, 0x6c, 0x6c, 0x86, 0x98, 0x1d, 0xd7, 0x40, 0x92,
+	0x09, 0xc9, 0xf8, 0x1c, 0xbe, 0xb2, 0x6c, 0xe2, 0xe2, 0x82, 0x76, 0xc3, 0xff, 0x7d, 0x7f, 0x31,
+	0xbf, 0xd8, 0x7e, 0x06, 0x4b, 0xaa, 0xbf, 0x09, 0x91, 0x98, 0xea, 0xd3, 0x44, 0xe4, 0x07, 0x70,
+	0xc8, 0xd1, 0xe8, 0x04, 0x85, 0x5d, 0x7d, 0x2d, 0xc4, 0xfa, 0x25, 0x9f, 0xf5, 0xad, 0xd8, 0xeb,
+	0x88, 0x8a, 0x0d, 0x79, 0x60, 0xe3, 0x30, 0xb1, 0x72, 0x01, 0x5c, 0x92, 0x55, 0x53, 0xb5, 0xab,
+	0x6e, 0x37, 0xc1, 0xc3, 0xc4, 0x9e, 0x52, 0x7d, 0x27, 0xce, 0x34, 0xb9, 0x60, 0xf1, 0x6f, 0x6b,
+	0x91, 0x5b, 0xfb, 0x5f, 0x3c, 0xef, 0x5d, 0x8a, 0xe3, 0xb7, 0xa8, 0xbc, 0xfe, 0x00, 0xaf, 0x1c,
+	0xca, 0x65, 0x53, 0xb5, 0x9b, 0x4e, 0x94, 0xe8, 0x59, 0x39, 0x1c, 0x05, 0x56, 0x71, 0x40, 0x2e,
+	0xc2, 0xaa, 0x08, 0x25, 0xca, 0xc2, 0xb5, 0x38, 0x99, 0x5e, 0x79, 0x37, 0x16, 0x93, 0x3c, 0x6a,
+	0x96, 0xed, 0xa6, 0xdb, 0xfe, 0x84, 0x8f, 0x63, 0x36, 0xbe, 0xd4, 0x9b, 0x88, 0x9a, 0x29, 0x1a,
+	0x4c, 0xc0, 0x04, 0x3d, 0x5a, 0x64, 0x94, 0xeb, 0x2c, 0xef, 0x66, 0xf0, 0x40, 0x0f, 0x19, 0xdd,
+	0x5f, 0xbc, 0x9e, 0xff, 0xb7, 0x14, 0xe4, 0x15, 0xbf, 0x03, 0x00, 0x00, 0xff, 0xff, 0x64, 0x04,
+	0xa8, 0xf4, 0x54, 0x01, 0x00, 0x00,
+}
diff --git a/ui/metrics/upload_proto/upload.proto b/ui/metrics/upload_proto/upload.proto
new file mode 100644
index 0000000..bcd0ab2
--- /dev/null
+++ b/ui/metrics/upload_proto/upload.proto
@@ -0,0 +1,39 @@
+// Copyright 2020 Google Inc. All Rights Reserved.
+//
+// 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.
+
+syntax = "proto2";
+
+package soong_metrics_upload;
+option go_package = "soong_metrics_upload_proto";
+
+message Upload {
+  // The timestamp in milliseconds that the build was created.
+  optional uint64 creation_timestamp_ms = 1;
+
+  // The timestamp in milliseconds when the build was completed.
+  optional uint64 completion_timestamp_ms = 2;
+
+  // The branch name.
+  optional string branch_name = 3;
+
+  // The target name.
+  optional string target_name = 4;
+
+  // A list of metrics filepaths to upload.
+  repeated string metrics_files = 5;
+
+  // A list of directories to delete after the copy of metrics files
+  // is completed for uploading.
+  repeated string directories_to_delete = 6;
+}