Merge "Sandbox vm-tests-tf-lib" into main
diff --git a/android/androidmk.go b/android/androidmk.go
index 62f82f2..f6e8799 100644
--- a/android/androidmk.go
+++ b/android/androidmk.go
@@ -486,17 +486,6 @@
 	return generateDistContributionsForMake(distContributions)
 }
 
-// Write the license variables to Make for AndroidMkData.Custom(..) methods that do not call WriteAndroidMkData(..)
-// It's required to propagate the license metadata even for module types that have non-standard interfaces to Make.
-func (a *AndroidMkEntries) WriteLicenseVariables(w io.Writer) {
-	AndroidMkEmitAssignList(w, "LOCAL_LICENSE_KINDS", a.EntryMap["LOCAL_LICENSE_KINDS"])
-	AndroidMkEmitAssignList(w, "LOCAL_LICENSE_CONDITIONS", a.EntryMap["LOCAL_LICENSE_CONDITIONS"])
-	AndroidMkEmitAssignList(w, "LOCAL_NOTICE_FILE", a.EntryMap["LOCAL_NOTICE_FILE"])
-	if pn, ok := a.EntryMap["LOCAL_LICENSE_PACKAGE_NAME"]; ok {
-		AndroidMkEmitAssignList(w, "LOCAL_LICENSE_PACKAGE_NAME", pn)
-	}
-}
-
 // fillInEntries goes through the common variable processing and calls the extra data funcs to
 // generate and fill in AndroidMkEntries's in-struct data, ready to be flushed to a file.
 type fillInEntriesContext interface {
@@ -534,15 +523,6 @@
 	// Collect make variable assignment entries.
 	a.SetString("LOCAL_PATH", ctx.ModuleDir(mod))
 	a.SetString("LOCAL_MODULE", name+a.SubName)
-	a.AddStrings("LOCAL_LICENSE_KINDS", base.commonProperties.Effective_license_kinds...)
-	a.AddStrings("LOCAL_LICENSE_CONDITIONS", base.commonProperties.Effective_license_conditions...)
-	a.AddStrings("LOCAL_NOTICE_FILE", base.commonProperties.Effective_license_text.Strings()...)
-	// TODO(b/151177513): Does this code need to set LOCAL_MODULE_IS_CONTAINER ?
-	if base.commonProperties.Effective_package_name != nil {
-		a.SetString("LOCAL_LICENSE_PACKAGE_NAME", *base.commonProperties.Effective_package_name)
-	} else if len(base.commonProperties.Effective_licenses) > 0 {
-		a.SetString("LOCAL_LICENSE_PACKAGE_NAME", strings.Join(base.commonProperties.Effective_licenses, " "))
-	}
 	a.SetString("LOCAL_MODULE_CLASS", a.Class)
 	a.SetString("LOCAL_PREBUILT_MODULE_FILE", a.OutputFile.String())
 	a.AddStrings("LOCAL_REQUIRED_MODULES", a.Required...)
diff --git a/apex/androidmk.go b/apex/androidmk.go
index 6136cbd..4e968ea 100644
--- a/apex/androidmk.go
+++ b/apex/androidmk.go
@@ -245,7 +245,6 @@
 			fmt.Fprintln(w, "\ninclude $(CLEAR_VARS)  # apex.apexBundle")
 			fmt.Fprintln(w, "LOCAL_PATH :=", moduleDir)
 			fmt.Fprintln(w, "LOCAL_MODULE :=", name)
-			data.Entries.WriteLicenseVariables(w)
 			fmt.Fprintln(w, "LOCAL_MODULE_CLASS := ETC") // do we need a new class?
 			fmt.Fprintln(w, "LOCAL_PREBUILT_MODULE_FILE :=", a.outputFile.String())
 			fmt.Fprintln(w, "LOCAL_MODULE_PATH :=", a.installDir.String())
diff --git a/bpf/bpf.go b/bpf/bpf.go
index ba825cf..58213aa 100644
--- a/bpf/bpf.go
+++ b/bpf/bpf.go
@@ -229,7 +229,6 @@
 				names = append(names, objName)
 				fmt.Fprintln(w, "include $(CLEAR_VARS)", " # bpf.bpf.obj")
 				fmt.Fprintln(w, "LOCAL_MODULE := ", objName)
-				data.Entries.WriteLicenseVariables(w)
 				fmt.Fprintln(w, "LOCAL_PREBUILT_MODULE_FILE :=", obj.String())
 				fmt.Fprintln(w, "LOCAL_MODULE_STEM :=", obj.Base())
 				fmt.Fprintln(w, "LOCAL_MODULE_CLASS := ETC")
@@ -239,7 +238,6 @@
 			}
 			fmt.Fprintln(w, "include $(CLEAR_VARS)", " # bpf.bpf")
 			fmt.Fprintln(w, "LOCAL_MODULE := ", name)
-			data.Entries.WriteLicenseVariables(w)
 			android.AndroidMkEmitAssignList(w, "LOCAL_REQUIRED_MODULES", names)
 			fmt.Fprintln(w, "include $(BUILD_PHONY_PACKAGE)")
 		},
diff --git a/cc/Android.bp b/cc/Android.bp
index 77e96db..9496bac 100644
--- a/cc/Android.bp
+++ b/cc/Android.bp
@@ -40,7 +40,6 @@
         "lto.go",
         "makevars.go",
         "orderfile.go",
-        "pgo.go",
         "prebuilt.go",
         "proto.go",
         "rs.go",
diff --git a/cc/cc.go b/cc/cc.go
index e215438..74a8d35 100644
--- a/cc/cc.go
+++ b/cc/cc.go
@@ -141,6 +141,8 @@
 
 	// List of libs that need to be excluded for APEX variant
 	ExcludeLibsForApex []string
+	// List of libs that need to be excluded for non-APEX variant
+	ExcludeLibsForNonApex []string
 }
 
 // PathDeps is a struct containing file paths to dependencies of a module.
@@ -529,7 +531,6 @@
 	baseModuleName() string
 	getVndkExtendsModuleName() string
 	isAfdoCompile() bool
-	isPgoCompile() bool
 	isOrderfileCompile() bool
 	isCfi() bool
 	isFuzzer() bool
@@ -728,6 +729,8 @@
 
 	// Whether or not this dependency has to be followed for the apex variants
 	excludeInApex bool
+	// Whether or not this dependency has to be followed for the non-apex variants
+	excludeInNonApex bool
 
 	// If true, don't automatically export symbols from the static library into a shared library.
 	unexportedSymbols bool
@@ -891,7 +894,6 @@
 	vndkdep   *vndkdep
 	lto       *lto
 	afdo      *afdo
-	pgo       *pgo
 	orderfile *orderfile
 
 	library libraryInterface
@@ -1274,9 +1276,6 @@
 	if c.afdo != nil {
 		c.AddProperties(c.afdo.props()...)
 	}
-	if c.pgo != nil {
-		c.AddProperties(c.pgo.props()...)
-	}
 	if c.orderfile != nil {
 		c.AddProperties(c.orderfile.props()...)
 	}
@@ -1406,13 +1405,6 @@
 	return false
 }
 
-func (c *Module) isPgoCompile() bool {
-	if pgo := c.pgo; pgo != nil {
-		return pgo.Properties.PgoCompile
-	}
-	return false
-}
-
 func (c *Module) isOrderfileCompile() bool {
 	if orderfile := c.orderfile; orderfile != nil {
 		return orderfile.Properties.OrderfileLoad
@@ -1721,10 +1713,6 @@
 	return ctx.mod.isAfdoCompile()
 }
 
-func (ctx *moduleContextImpl) isPgoCompile() bool {
-	return ctx.mod.isPgoCompile()
-}
-
 func (ctx *moduleContextImpl) isOrderfileCompile() bool {
 	return ctx.mod.isOrderfileCompile()
 }
@@ -1837,7 +1825,6 @@
 	module.vndkdep = &vndkdep{}
 	module.lto = &lto{}
 	module.afdo = &afdo{}
-	module.pgo = &pgo{}
 	module.orderfile = &orderfile{}
 	return module
 }
@@ -2263,9 +2250,6 @@
 	if c.afdo != nil {
 		flags = c.afdo.flags(ctx, flags)
 	}
-	if c.pgo != nil {
-		flags = c.pgo.flags(ctx, flags)
-	}
 	if c.orderfile != nil {
 		flags = c.orderfile.flags(ctx, flags)
 	}
@@ -2417,9 +2401,6 @@
 	if c.orderfile != nil {
 		c.orderfile.begin(ctx)
 	}
-	if c.pgo != nil {
-		c.pgo.begin(ctx)
-	}
 	if ctx.useSdk() && c.IsSdkVariant() {
 		version, err := nativeApiLevelFromUser(ctx, ctx.sdkVersion())
 		if err != nil {
@@ -2819,6 +2800,9 @@
 		if inList(lib, deps.ExcludeLibsForApex) {
 			depTag.excludeInApex = true
 		}
+		if inList(lib, deps.ExcludeLibsForNonApex) {
+			depTag.excludeInNonApex = true
+		}
 
 		name, version := StubsLibNameAndVersion(lib)
 		if apiLibraryName, ok := apiImportInfo.SharedLibs[name]; ok && !ctx.OtherModuleExists(name) {
@@ -3335,6 +3319,9 @@
 			if !apexInfo.IsForPlatform() && libDepTag.excludeInApex {
 				return
 			}
+			if apexInfo.IsForPlatform() && libDepTag.excludeInNonApex {
+				return
+			}
 
 			depExporterInfo := ctx.OtherModuleProvider(dep, FlagExporterInfoProvider).(FlagExporterInfo)
 
@@ -4323,7 +4310,6 @@
 		&VndkProperties{},
 		&LTOProperties{},
 		&AfdoProperties{},
-		&PgoProperties{},
 		&OrderfileProperties{},
 		&android.ProtoProperties{},
 		// RustBindgenProperties is included here so that cc_defaults can be used for rust_bindgen modules.
diff --git a/cc/linker.go b/cc/linker.go
index 257fe86..357d1ce 100644
--- a/cc/linker.go
+++ b/cc/linker.go
@@ -214,6 +214,11 @@
 			// variant of the C/C++ module.
 			Exclude_static_libs []string
 		}
+		Non_apex struct {
+			// list of shared libs that should not be used to build the non-apex
+			// variant of the C/C++ module.
+			Exclude_shared_libs []string
+		}
 	} `android:"arch_variant"`
 
 	// make android::build:GetBuildNumber() available containing the build ID.
@@ -300,6 +305,10 @@
 	// variants.
 	deps.ExcludeLibsForApex = append(deps.ExcludeLibsForApex, linker.Properties.Target.Apex.Exclude_shared_libs...)
 	deps.ExcludeLibsForApex = append(deps.ExcludeLibsForApex, linker.Properties.Target.Apex.Exclude_static_libs...)
+	// Record the libraries that need to be excluded when building for non-APEX variants
+	// for the same reason above. This is used for marking deps and marked deps are
+	// ignored for non-apex variants.
+	deps.ExcludeLibsForNonApex = append(deps.ExcludeLibsForNonApex, linker.Properties.Target.Non_apex.Exclude_shared_libs...)
 
 	if Bool(linker.Properties.Use_version_lib) {
 		deps.WholeStaticLibs = append(deps.WholeStaticLibs, "libbuildversion")
diff --git a/cc/lto.go b/cc/lto.go
index 30d7b757..d2a43d2 100644
--- a/cc/lto.go
+++ b/cc/lto.go
@@ -140,7 +140,7 @@
 		// Reduce the inlining threshold for a better balance of binary size and
 		// performance.
 		if !ctx.Darwin() {
-			if ctx.isPgoCompile() || ctx.isAfdoCompile() {
+			if ctx.isAfdoCompile() {
 				ltoLdFlags = append(ltoLdFlags, "-Wl,-plugin-opt,-import-instr-limit=40")
 			} else {
 				ltoLdFlags = append(ltoLdFlags, "-Wl,-plugin-opt,-import-instr-limit=5")
diff --git a/cc/pgo.go b/cc/pgo.go
deleted file mode 100644
index 463e2e6..0000000
--- a/cc/pgo.go
+++ /dev/null
@@ -1,294 +0,0 @@
-// Copyright 2017 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 cc
-
-import (
-	"fmt"
-	"path/filepath"
-	"strings"
-
-	"github.com/google/blueprint/proptools"
-
-	"android/soong/android"
-)
-
-var (
-	// Add flags to ignore warnings that profiles are old or missing for
-	// some functions.
-	profileUseOtherFlags = []string{
-		"-Wno-backend-plugin",
-	}
-
-	globalPgoProfileProjects = []string{
-		"toolchain/pgo-profiles/pgo",
-		"vendor/google_data/pgo_profile/pgo",
-	}
-)
-
-var pgoProfileProjectsConfigKey = android.NewOnceKey("PgoProfileProjects")
-
-const profileInstrumentFlag = "-fprofile-generate=/data/local/tmp"
-const profileUseInstrumentFormat = "-fprofile-use=%s"
-
-func getPgoProfileProjects(config android.DeviceConfig) []string {
-	return config.OnceStringSlice(pgoProfileProjectsConfigKey, func() []string {
-		return append(globalPgoProfileProjects, config.PgoAdditionalProfileDirs()...)
-	})
-}
-
-func recordMissingProfileFile(ctx BaseModuleContext, missing string) {
-	getNamedMapForConfig(ctx.Config(), modulesMissingProfileFileKey).Store(missing, true)
-}
-
-type PgoProperties struct {
-	Pgo struct {
-		Instrumentation    *bool
-		Profile_file       *string `android:"arch_variant"`
-		Benchmarks         []string
-		Enable_profile_use *bool `android:"arch_variant"`
-		// Additional compiler flags to use when building this module
-		// for profiling.
-		Cflags []string `android:"arch_variant"`
-	} `android:"arch_variant"`
-
-	PgoPresent          bool `blueprint:"mutated"`
-	ShouldProfileModule bool `blueprint:"mutated"`
-	PgoCompile          bool `blueprint:"mutated"`
-	PgoInstrLink        bool `blueprint:"mutated"`
-}
-
-type pgo struct {
-	Properties PgoProperties
-}
-
-func (props *PgoProperties) isInstrumentation() bool {
-	return props.Pgo.Instrumentation != nil && *props.Pgo.Instrumentation == true
-}
-
-func (pgo *pgo) props() []interface{} {
-	return []interface{}{&pgo.Properties}
-}
-
-func (props *PgoProperties) addInstrumentationProfileGatherFlags(ctx ModuleContext, flags Flags) Flags {
-	// Add to C flags iff PGO is explicitly enabled for this module.
-	if props.ShouldProfileModule {
-		flags.Local.CFlags = append(flags.Local.CFlags, props.Pgo.Cflags...)
-		flags.Local.CFlags = append(flags.Local.CFlags, profileInstrumentFlag)
-	}
-	flags.Local.LdFlags = append(flags.Local.LdFlags, profileInstrumentFlag)
-	return flags
-}
-
-func (props *PgoProperties) getPgoProfileFile(ctx BaseModuleContext) android.OptionalPath {
-	profileFile := *props.Pgo.Profile_file
-
-	// Test if the profile_file is present in any of the PGO profile projects
-	for _, profileProject := range getPgoProfileProjects(ctx.DeviceConfig()) {
-		// Bug: http://b/74395273 If the profile_file is unavailable,
-		// use a versioned file named
-		// <profile_file>.<arbitrary-version> when available.  This
-		// works around an issue where ccache serves stale cache
-		// entries when the profile file has changed.
-		globPattern := filepath.Join(profileProject, profileFile+".*")
-		versionedProfiles, err := ctx.GlobWithDeps(globPattern, nil)
-		if err != nil {
-			ctx.ModuleErrorf("glob: %s", err.Error())
-		}
-
-		path := android.ExistentPathForSource(ctx, profileProject, profileFile)
-		if path.Valid() {
-			if len(versionedProfiles) != 0 {
-				ctx.PropertyErrorf("pgo.profile_file", "Profile_file has multiple versions: "+filepath.Join(profileProject, profileFile)+", "+strings.Join(versionedProfiles, ", "))
-			}
-			return path
-		}
-
-		if len(versionedProfiles) > 1 {
-			ctx.PropertyErrorf("pgo.profile_file", "Profile_file has multiple versions: "+strings.Join(versionedProfiles, ", "))
-		} else if len(versionedProfiles) == 1 {
-			return android.OptionalPathForPath(android.PathForSource(ctx, versionedProfiles[0]))
-		}
-	}
-
-	// Record that this module's profile file is absent
-	missing := *props.Pgo.Profile_file + ":" + ctx.ModuleDir() + "/Android.bp:" + ctx.ModuleName()
-	recordMissingProfileFile(ctx, missing)
-
-	return android.OptionalPathForPath(nil)
-}
-
-func (props *PgoProperties) profileUseFlags(ctx ModuleContext, file string) []string {
-	flags := []string{fmt.Sprintf(profileUseInstrumentFormat, file)}
-	flags = append(flags, profileUseOtherFlags...)
-	return flags
-}
-
-func (props *PgoProperties) addProfileUseFlags(ctx ModuleContext, flags Flags) Flags {
-	// Return if 'pgo' property is not present in this module.
-	if !props.PgoPresent {
-		return flags
-	}
-
-	if props.PgoCompile {
-		profileFile := props.getPgoProfileFile(ctx)
-		profileFilePath := profileFile.Path()
-		profileUseFlags := props.profileUseFlags(ctx, profileFilePath.String())
-
-		flags.Local.CFlags = append(flags.Local.CFlags, profileUseFlags...)
-		flags.Local.LdFlags = append(flags.Local.LdFlags, profileUseFlags...)
-
-		// Update CFlagsDeps and LdFlagsDeps so the module is rebuilt
-		// if profileFile gets updated
-		flags.CFlagsDeps = append(flags.CFlagsDeps, profileFilePath)
-		flags.LdFlagsDeps = append(flags.LdFlagsDeps, profileFilePath)
-	}
-	return flags
-}
-
-func (props *PgoProperties) isPGO(ctx BaseModuleContext) bool {
-	isInstrumentation := props.isInstrumentation()
-
-	profileKindPresent := isInstrumentation
-	filePresent := props.Pgo.Profile_file != nil
-	benchmarksPresent := len(props.Pgo.Benchmarks) > 0
-
-	// If all three properties are absent, PGO is OFF for this module
-	if !profileKindPresent && !filePresent && !benchmarksPresent {
-		return false
-	}
-
-	// profileKindPresent and filePresent are mandatory properties.
-	if !profileKindPresent || !filePresent {
-		var missing []string
-		if !profileKindPresent {
-			missing = append(missing, "profile kind")
-		}
-		if !filePresent {
-			missing = append(missing, "profile_file property")
-		}
-		missingProps := strings.Join(missing, ", ")
-		ctx.ModuleErrorf("PGO specification is missing properties: " + missingProps)
-	}
-
-	// Benchmark property is mandatory for instrumentation PGO.
-	if isInstrumentation && !benchmarksPresent {
-		ctx.ModuleErrorf("Instrumentation PGO specification is missing benchmark property")
-	}
-
-	return true
-}
-
-func (pgo *pgo) begin(ctx BaseModuleContext) {
-	// TODO Evaluate if we need to support PGO for host modules
-	if ctx.Host() {
-		return
-	}
-
-	// Check if PGO is needed for this module
-	pgo.Properties.PgoPresent = pgo.Properties.isPGO(ctx)
-
-	if !pgo.Properties.PgoPresent {
-		return
-	}
-
-	// This module should be instrumented if ANDROID_PGO_INSTRUMENT is set
-	// and includes 'all', 'ALL' or a benchmark listed for this module.
-	//
-	// TODO Validate that each benchmark instruments at least one module
-	pgo.Properties.ShouldProfileModule = false
-	pgoBenchmarks := ctx.Config().Getenv("ANDROID_PGO_INSTRUMENT")
-	pgoBenchmarksMap := make(map[string]bool)
-	for _, b := range strings.Split(pgoBenchmarks, ",") {
-		pgoBenchmarksMap[b] = true
-	}
-
-	if pgoBenchmarksMap["all"] == true || pgoBenchmarksMap["ALL"] == true {
-		pgo.Properties.ShouldProfileModule = true
-		pgo.Properties.PgoInstrLink = pgo.Properties.isInstrumentation()
-	} else {
-		for _, b := range pgo.Properties.Pgo.Benchmarks {
-			if pgoBenchmarksMap[b] == true {
-				pgo.Properties.ShouldProfileModule = true
-				pgo.Properties.PgoInstrLink = pgo.Properties.isInstrumentation()
-				break
-			}
-		}
-	}
-
-	// PGO profile use is not feasible for a Clang coverage build because
-	// -fprofile-use and -fprofile-instr-generate are incompatible.
-	if ctx.DeviceConfig().ClangCoverageEnabled() {
-		return
-	}
-
-	if !ctx.Config().IsEnvTrue("ANDROID_PGO_NO_PROFILE_USE") &&
-		proptools.BoolDefault(pgo.Properties.Pgo.Enable_profile_use, true) {
-		if profileFile := pgo.Properties.getPgoProfileFile(ctx); profileFile.Valid() {
-			pgo.Properties.PgoCompile = true
-		}
-	}
-}
-
-func (pgo *pgo) flags(ctx ModuleContext, flags Flags) Flags {
-	if ctx.Host() {
-		return flags
-	}
-
-	// Deduce PgoInstrLink property i.e. whether this module needs to be
-	// linked with profile-generation flags.  Here, we're setting it if any
-	// dependency needs PGO instrumentation.  It is initially set in
-	// begin() if PGO is directly enabled for this module.
-	if ctx.static() && !ctx.staticBinary() {
-		// For static libraries, check if any whole_static_libs are
-		// linked with profile generation
-		ctx.VisitDirectDeps(func(m android.Module) {
-			if depTag, ok := ctx.OtherModuleDependencyTag(m).(libraryDependencyTag); ok {
-				if depTag.static() && depTag.wholeStatic {
-					if cc, ok := m.(*Module); ok {
-						if cc.pgo.Properties.PgoInstrLink {
-							pgo.Properties.PgoInstrLink = true
-						}
-					}
-				}
-			}
-		})
-	} else {
-		// For executables and shared libraries, check all static dependencies.
-		ctx.VisitDirectDeps(func(m android.Module) {
-			if depTag, ok := ctx.OtherModuleDependencyTag(m).(libraryDependencyTag); ok {
-				if depTag.static() {
-					if cc, ok := m.(*Module); ok {
-						if cc.pgo.Properties.PgoInstrLink {
-							pgo.Properties.PgoInstrLink = true
-						}
-					}
-				}
-			}
-		})
-	}
-
-	props := pgo.Properties
-	// Add flags to profile this module based on its profile_kind
-	if (props.ShouldProfileModule && props.isInstrumentation()) || props.PgoInstrLink {
-		// Instrumentation PGO use and gather flags cannot coexist.
-		return props.addInstrumentationProfileGatherFlags(ctx, flags)
-	}
-
-	if !ctx.Config().IsEnvTrue("ANDROID_PGO_NO_PROFILE_USE") {
-		flags = props.addProfileUseFlags(ctx, flags)
-	}
-
-	return flags
-}
diff --git a/dexpreopt/config.go b/dexpreopt/config.go
index c871e85..6163952 100644
--- a/dexpreopt/config.go
+++ b/dexpreopt/config.go
@@ -184,8 +184,6 @@
 	PreoptBootClassPathDexFiles     android.Paths // file paths of boot class path files
 	PreoptBootClassPathDexLocations []string      // virtual locations of boot class path files
 
-	PreoptExtractedApk bool // Overrides OnlyPreoptModules
-
 	NoCreateAppImage    bool
 	ForceCreateAppImage bool
 
diff --git a/dexpreopt/dexpreopt.go b/dexpreopt/dexpreopt.go
index c13e14a..94707ba 100644
--- a/dexpreopt/dexpreopt.go
+++ b/dexpreopt/dexpreopt.go
@@ -124,7 +124,7 @@
 		return true
 	}
 
-	if global.OnlyPreoptArtBootImage && !module.PreoptExtractedApk {
+	if global.OnlyPreoptArtBootImage {
 		return true
 	}
 
diff --git a/dexpreopt/dexpreopt_test.go b/dexpreopt/dexpreopt_test.go
index 2b19c9d..230fbb4 100644
--- a/dexpreopt/dexpreopt_test.go
+++ b/dexpreopt/dexpreopt_test.go
@@ -87,7 +87,6 @@
 		DexPreoptImageLocationsOnHost:   []string{},
 		PreoptBootClassPathDexFiles:     nil,
 		PreoptBootClassPathDexLocations: nil,
-		PreoptExtractedApk:              false,
 		NoCreateAppImage:                false,
 		ForceCreateAppImage:             false,
 		PresignedPrebuilt:               false,
diff --git a/genrule/allowlists.go b/genrule/allowlists.go
index 844c051..d74bf61 100644
--- a/genrule/allowlists.go
+++ b/genrule/allowlists.go
@@ -24,18 +24,9 @@
 	SandboxingDenyModuleList = []string{
 		// go/keep-sorted start
 		"CtsApkVerityTestDebugFiles",
-		"ScriptGroupTest-rscript",
-		"aidl-golden-test-build-hook-gen",
 		"aidl_camera_build_version",
 		"camera-its",
-		"chre_atoms_log.h",
-		"cronet_aml_base_android_runtime_jni_headers",
-		"cronet_aml_base_android_runtime_jni_headers__testing",
-		"cronet_aml_base_android_runtime_unchecked_jni_headers",
-		"cronet_aml_base_android_runtime_unchecked_jni_headers__testing",
-		"deqp_spvtools_update_build_version",
 		"libcore-non-cts-tests-txt",
-		"seller-frontend-service-stub-lite",
 		"swiftshader_spvtools_update_build_version",
 		// go/keep-sorted end
 	}
@@ -43,7 +34,6 @@
 	SandboxingDenyPathList = []string{
 		// go/keep-sorted start
 		"art/test",
-		"external/cronet",
 		// go/keep-sorted end
 	}
 )
diff --git a/java/Android.bp b/java/Android.bp
index cf96871..9f42771 100644
--- a/java/Android.bp
+++ b/java/Android.bp
@@ -67,7 +67,6 @@
         "plugin.go",
         "prebuilt_apis.go",
         "proto.go",
-        "resourceshrinker.go",
         "robolectric.go",
         "rro.go",
         "sdk.go",
@@ -107,7 +106,6 @@
         "plugin_test.go",
         "prebuilt_apis_test.go",
         "proto_test.go",
-        "resourceshrinker_test.go",
         "rro_test.go",
         "sdk_test.go",
         "sdk_library_test.go",
diff --git a/java/aar.go b/java/aar.go
index e579008..57a05d4 100644
--- a/java/aar.go
+++ b/java/aar.go
@@ -17,6 +17,7 @@
 import (
 	"fmt"
 	"path/filepath"
+	"slices"
 	"strconv"
 	"strings"
 
@@ -108,26 +109,26 @@
 }
 
 type aapt struct {
-	aaptSrcJar                     android.Path
-	transitiveAaptRJars            android.Paths
-	transitiveAaptResourcePackages android.Paths
-	exportPackage                  android.Path
-	manifestPath                   android.Path
-	proguardOptionsFile            android.Path
-	rTxt                           android.Path
-	rJar                           android.Path
-	extraAaptPackagesFile          android.Path
-	mergedManifestFile             android.Path
-	noticeFile                     android.OptionalPath
-	assetPackage                   android.OptionalPath
-	isLibrary                      bool
-	defaultManifestVersion         string
-	useEmbeddedNativeLibs          bool
-	useEmbeddedDex                 bool
-	usesNonSdkApis                 bool
-	hasNoCode                      bool
-	LoggingParent                  string
-	resourceFiles                  android.Paths
+	aaptSrcJar                         android.Path
+	transitiveAaptRJars                android.Paths
+	transitiveAaptResourcePackagesFile android.Path
+	exportPackage                      android.Path
+	manifestPath                       android.Path
+	proguardOptionsFile                android.Path
+	rTxt                               android.Path
+	rJar                               android.Path
+	extraAaptPackagesFile              android.Path
+	mergedManifestFile                 android.Path
+	noticeFile                         android.OptionalPath
+	assetPackage                       android.OptionalPath
+	isLibrary                          bool
+	defaultManifestVersion             string
+	useEmbeddedNativeLibs              bool
+	useEmbeddedDex                     bool
+	usesNonSdkApis                     bool
+	hasNoCode                          bool
+	LoggingParent                      string
+	resourceFiles                      android.Paths
 
 	splitNames []string
 	splits     []split
@@ -552,9 +553,16 @@
 		aapt2ExtractExtraPackages(ctx, extraPackages, srcJar)
 	}
 
+	transitiveAaptResourcePackages := staticDeps.resPackages().Strings()
+	transitiveAaptResourcePackages = slices.DeleteFunc(transitiveAaptResourcePackages, func(p string) bool {
+		return p == packageRes.String()
+	})
+	transitiveAaptResourcePackagesFile := android.PathForModuleOut(ctx, "transitive-res-packages")
+	android.WriteFileRule(ctx, transitiveAaptResourcePackagesFile, strings.Join(transitiveAaptResourcePackages, "\n"))
+
 	a.aaptSrcJar = srcJar
 	a.transitiveAaptRJars = transitiveRJars
-	a.transitiveAaptResourcePackages = staticDeps.resPackages()
+	a.transitiveAaptResourcePackagesFile = transitiveAaptResourcePackagesFile
 	a.exportPackage = packageRes
 	a.manifestPath = manifestPath
 	a.proguardOptionsFile = proguardOptionsFile
@@ -820,9 +828,13 @@
 
 	proguardSpecInfo := a.collectProguardSpecInfo(ctx)
 	ctx.SetProvider(ProguardSpecInfoProvider, proguardSpecInfo)
-	a.exportedProguardFlagFiles = proguardSpecInfo.ProguardFlagsFiles.ToList()
-	a.extraProguardFlagFiles = append(a.extraProguardFlagFiles, a.exportedProguardFlagFiles...)
-	a.extraProguardFlagFiles = append(a.extraProguardFlagFiles, a.proguardOptionsFile)
+	exportedProguardFlagsFiles := proguardSpecInfo.ProguardFlagsFiles.ToList()
+	a.extraProguardFlagsFiles = append(a.extraProguardFlagsFiles, exportedProguardFlagsFiles...)
+	a.extraProguardFlagsFiles = append(a.extraProguardFlagsFiles, a.proguardOptionsFile)
+
+	combinedExportedProguardFlagFile := android.PathForModuleOut(ctx, "export_proguard_flags")
+	writeCombinedProguardFlagsFile(ctx, combinedExportedProguardFlagFile, exportedProguardFlagsFiles)
+	a.combinedExportedProguardFlagsFile = combinedExportedProguardFlagFile
 
 	var extraSrcJars android.Paths
 	var extraCombinedJars android.Paths
@@ -940,15 +952,15 @@
 
 	properties AARImportProperties
 
-	classpathFile                  android.WritablePath
-	proguardFlags                  android.WritablePath
-	exportPackage                  android.WritablePath
-	transitiveAaptResourcePackages android.Paths
-	extraAaptPackagesFile          android.WritablePath
-	manifest                       android.WritablePath
-	assetsPackage                  android.WritablePath
-	rTxt                           android.WritablePath
-	rJar                           android.WritablePath
+	classpathFile                      android.WritablePath
+	proguardFlags                      android.WritablePath
+	exportPackage                      android.WritablePath
+	transitiveAaptResourcePackagesFile android.Path
+	extraAaptPackagesFile              android.WritablePath
+	manifest                           android.WritablePath
+	assetsPackage                      android.WritablePath
+	rTxt                               android.WritablePath
+	rJar                               android.WritablePath
 
 	resourcesNodesDepSet *android.DepSet[*resourcesNode]
 	manifestsDepSet      *android.DepSet[android.Path]
@@ -1211,7 +1223,13 @@
 	_ = staticManifestsDepSet
 	a.manifestsDepSet = manifestDepSetBuilder.Build()
 
-	a.transitiveAaptResourcePackages = staticDeps.resPackages()
+	transitiveAaptResourcePackages := staticDeps.resPackages().Strings()
+	transitiveAaptResourcePackages = slices.DeleteFunc(transitiveAaptResourcePackages, func(p string) bool {
+		return p == a.exportPackage.String()
+	})
+	transitiveAaptResourcePackagesFile := android.PathForModuleOut(ctx, "transitive-res-packages")
+	android.WriteFileRule(ctx, transitiveAaptResourcePackagesFile, strings.Join(transitiveAaptResourcePackages, "\n"))
+	a.transitiveAaptResourcePackagesFile = transitiveAaptResourcePackagesFile
 
 	a.collectTransitiveHeaderJars(ctx)
 	ctx.SetProvider(JavaInfoProvider, JavaInfo{
diff --git a/java/androidmk.go b/java/androidmk.go
index 84f78c8..a15ef1a 100644
--- a/java/androidmk.go
+++ b/java/androidmk.go
@@ -274,7 +274,7 @@
 				entries.SetPath("LOCAL_SOONG_HEADER_JAR", prebuilt.classpathFile)
 				entries.SetPath("LOCAL_SOONG_CLASSES_JAR", prebuilt.classpathFile)
 				entries.SetPath("LOCAL_SOONG_RESOURCE_EXPORT_PACKAGE", prebuilt.exportPackage)
-				entries.SetPaths("LOCAL_SOONG_TRANSITIVE_RES_PACKAGES", prebuilt.transitiveAaptResourcePackages)
+				entries.SetPath("LOCAL_SOONG_TRANSITIVE_RES_PACKAGES", prebuilt.transitiveAaptResourcePackagesFile)
 				entries.SetPath("LOCAL_SOONG_EXPORT_PROGUARD_FLAGS", prebuilt.proguardFlags)
 				entries.SetPath("LOCAL_SOONG_STATIC_LIBRARY_EXTRA_PACKAGES", prebuilt.extraAaptPackagesFile)
 				entries.SetPath("LOCAL_FULL_MANIFEST_FILE", prebuilt.manifest)
@@ -532,10 +532,10 @@
 		}
 
 		entries.SetPath("LOCAL_SOONG_RESOURCE_EXPORT_PACKAGE", a.exportPackage)
-		entries.SetPaths("LOCAL_SOONG_TRANSITIVE_RES_PACKAGES", a.transitiveAaptResourcePackages)
+		entries.SetPath("LOCAL_SOONG_TRANSITIVE_RES_PACKAGES", a.transitiveAaptResourcePackagesFile)
 		entries.SetPath("LOCAL_SOONG_STATIC_LIBRARY_EXTRA_PACKAGES", a.extraAaptPackagesFile)
 		entries.SetPath("LOCAL_FULL_MANIFEST_FILE", a.mergedManifestFile)
-		entries.AddStrings("LOCAL_SOONG_EXPORT_PROGUARD_FLAGS", a.exportedProguardFlagFiles.Strings()...)
+		entries.SetPath("LOCAL_SOONG_EXPORT_PROGUARD_FLAGS", a.combinedExportedProguardFlagsFile)
 		entries.SetBoolIfTrue("LOCAL_UNINSTALLABLE_MODULE", true)
 		entries.SetOptionalPaths("LOCAL_ACONFIG_FILES", a.getTransitiveAconfigFiles().ToList())
 	})
diff --git a/java/app.go b/java/app.go
index 6d7411d..0f46b4e 100755
--- a/java/app.go
+++ b/java/app.go
@@ -526,8 +526,8 @@
 
 	staticLibProguardFlagFiles = android.FirstUniquePaths(staticLibProguardFlagFiles)
 
-	a.Module.extraProguardFlagFiles = append(a.Module.extraProguardFlagFiles, staticLibProguardFlagFiles...)
-	a.Module.extraProguardFlagFiles = append(a.Module.extraProguardFlagFiles, a.proguardOptionsFile)
+	a.Module.extraProguardFlagsFiles = append(a.Module.extraProguardFlagsFiles, staticLibProguardFlagFiles...)
+	a.Module.extraProguardFlagsFiles = append(a.Module.extraProguardFlagsFiles, a.proguardOptionsFile)
 }
 
 func (a *AndroidApp) installPath(ctx android.ModuleContext) android.InstallPath {
@@ -544,7 +544,7 @@
 	return android.PathForModuleInstall(ctx, installDir, a.installApkName+".apk")
 }
 
-func (a *AndroidApp) dexBuildActions(ctx android.ModuleContext) android.Path {
+func (a *AndroidApp) dexBuildActions(ctx android.ModuleContext) (android.Path, android.Path) {
 	a.dexpreopter.installPath = a.installPath(ctx)
 	a.dexpreopter.isApp = true
 	if a.dexProperties.Uncompress_dex == nil {
@@ -557,7 +557,15 @@
 	a.dexpreopter.manifestFile = a.mergedManifestFile
 	a.dexpreopter.preventInstall = a.appProperties.PreventInstall
 
+	var packageResources = a.exportPackage
+
 	if ctx.ModuleName() != "framework-res" {
+		if Bool(a.dexProperties.Optimize.Shrink_resources) {
+			protoFile := android.PathForModuleOut(ctx, packageResources.Base()+".proto.apk")
+			aapt2Convert(ctx, protoFile, packageResources, "proto")
+			a.dexer.resourcesInput = android.OptionalPathForPath(protoFile)
+		}
+
 		var extraSrcJars android.Paths
 		var extraClasspathJars android.Paths
 		var extraCombinedJars android.Paths
@@ -575,9 +583,14 @@
 		}
 
 		a.Module.compile(ctx, extraSrcJars, extraClasspathJars, extraCombinedJars)
+		if Bool(a.dexProperties.Optimize.Shrink_resources) {
+			binaryResources := android.PathForModuleOut(ctx, packageResources.Base()+".binary.out.apk")
+			aapt2Convert(ctx, binaryResources, a.dexer.resourcesOutput.Path(), "binary")
+			packageResources = binaryResources
+		}
 	}
 
-	return a.dexJarFile.PathOrNil()
+	return a.dexJarFile.PathOrNil(), packageResources
 }
 
 func (a *AndroidApp) jniBuildActions(jniLibs []jniLib, prebuiltJniPackages android.Paths, ctx android.ModuleContext) android.WritablePath {
@@ -762,7 +775,6 @@
 
 	// Process all building blocks, from AAPT to certificates.
 	a.aaptBuildActions(ctx)
-
 	// The decision to enforce <uses-library> checks is made before adding implicit SDK libraries.
 	a.usesLibrary.freezeEnforceUsesLibraries()
 
@@ -788,7 +800,7 @@
 	a.linter.resources = a.aapt.resourceFiles
 	a.linter.buildModuleReportZip = ctx.Config().UnbundledBuildApps()
 
-	dexJarFile := a.dexBuildActions(ctx)
+	dexJarFile, packageResources := a.dexBuildActions(ctx)
 
 	jniLibs, prebuiltJniPackages, certificates := collectAppDeps(ctx, a, a.shouldEmbedJnis(ctx), !Bool(a.appProperties.Jni_uses_platform_apis))
 	jniJarFile := a.jniBuildActions(jniLibs, prebuiltJniPackages, ctx)
@@ -812,7 +824,7 @@
 	}
 	rotationMinSdkVersion := String(a.overridableAppProperties.RotationMinSdkVersion)
 
-	CreateAndSignAppPackage(ctx, packageFile, a.exportPackage, jniJarFile, dexJarFile, certificates, apkDeps, v4SignatureFile, lineageFile, rotationMinSdkVersion, Bool(a.dexProperties.Optimize.Shrink_resources))
+	CreateAndSignAppPackage(ctx, packageFile, packageResources, jniJarFile, dexJarFile, certificates, apkDeps, v4SignatureFile, lineageFile, rotationMinSdkVersion)
 	a.outputFile = packageFile
 	if v4SigningRequested {
 		a.extraOutputFiles = append(a.extraOutputFiles, v4SignatureFile)
@@ -841,7 +853,7 @@
 		if v4SigningRequested {
 			v4SignatureFile = android.PathForModuleOut(ctx, a.installApkName+"_"+split.suffix+".apk.idsig")
 		}
-		CreateAndSignAppPackage(ctx, packageFile, split.path, nil, nil, certificates, apkDeps, v4SignatureFile, lineageFile, rotationMinSdkVersion, false)
+		CreateAndSignAppPackage(ctx, packageFile, split.path, nil, nil, certificates, apkDeps, v4SignatureFile, lineageFile, rotationMinSdkVersion)
 		a.extraOutputFiles = append(a.extraOutputFiles, packageFile)
 		if v4SigningRequested {
 			a.extraOutputFiles = append(a.extraOutputFiles, v4SignatureFile)
diff --git a/java/app_builder.go b/java/app_builder.go
index d397ff7..943ce31 100644
--- a/java/app_builder.go
+++ b/java/app_builder.go
@@ -52,7 +52,7 @@
 	})
 
 func CreateAndSignAppPackage(ctx android.ModuleContext, outputFile android.WritablePath,
-	packageFile, jniJarFile, dexJarFile android.Path, certificates []Certificate, deps android.Paths, v4SignatureFile android.WritablePath, lineageFile android.Path, rotationMinSdkVersion string, shrinkResources bool) {
+	packageFile, jniJarFile, dexJarFile android.Path, certificates []Certificate, deps android.Paths, v4SignatureFile android.WritablePath, lineageFile android.Path, rotationMinSdkVersion string) {
 
 	unsignedApkName := strings.TrimSuffix(outputFile.Base(), ".apk") + "-unsigned.apk"
 	unsignedApk := android.PathForModuleOut(ctx, unsignedApkName)
@@ -71,12 +71,6 @@
 		Output:    unsignedApk,
 		Implicits: deps,
 	})
-
-	if shrinkResources {
-		shrunkenApk := android.PathForModuleOut(ctx, "resource-shrunken", unsignedApk.Base())
-		ShrinkResources(ctx, unsignedApk, shrunkenApk)
-		unsignedApk = shrunkenApk
-	}
 	SignAppPackage(ctx, outputFile, unsignedApk, certificates, v4SignatureFile, lineageFile, rotationMinSdkVersion)
 }
 
diff --git a/java/base.go b/java/base.go
index fdc164e..734f5d4 100644
--- a/java/base.go
+++ b/java/base.go
@@ -1615,7 +1615,7 @@
 	if ctx.Device() && (Bool(j.properties.Installable) || Bool(compileDex)) {
 		if j.hasCode(ctx) {
 			if j.shouldInstrumentStatic(ctx) {
-				j.dexer.extraProguardFlagFiles = append(j.dexer.extraProguardFlagFiles,
+				j.dexer.extraProguardFlagsFiles = append(j.dexer.extraProguardFlagsFiles,
 					android.PathForSource(ctx, "build/make/core/proguard.jacoco.flags"))
 			}
 			// Dex compilation
diff --git a/java/builder.go b/java/builder.go
index ee7e225..d03c8e5 100644
--- a/java/builder.go
+++ b/java/builder.go
@@ -264,6 +264,16 @@
 			Command:     `${config.Zip2ZipCmd} -i ${in} -o ${out} -x 'META-INF/services/**/*'`,
 			CommandDeps: []string{"${config.Zip2ZipCmd}"},
 		})
+
+	writeCombinedProguardFlagsFileRule = pctx.AndroidStaticRule("writeCombinedProguardFlagsFileRule",
+		blueprint.RuleParams{
+			Command: `rm -f $out && ` +
+				`for f in $in; do ` +
+				` echo  && ` +
+				` echo "# including $$f" && ` +
+				` cat $$f; ` +
+				`done > $out`,
+		})
 )
 
 func init() {
@@ -686,6 +696,15 @@
 	})
 }
 
+func writeCombinedProguardFlagsFile(ctx android.ModuleContext, outputFile android.WritablePath, files android.Paths) {
+	ctx.Build(pctx, android.BuildParams{
+		Rule:        writeCombinedProguardFlagsFileRule,
+		Description: "write combined proguard flags file",
+		Inputs:      files,
+		Output:      outputFile,
+	})
+}
+
 type classpath android.Paths
 
 func (x *classpath) formJoinedClassPath(optName string, sep string) string {
diff --git a/java/config/config.go b/java/config/config.go
index 83c27d3..0098130 100644
--- a/java/config/config.go
+++ b/java/config/config.go
@@ -133,7 +133,12 @@
 		if override := ctx.Config().Getenv("OVERRIDE_JLINK_VERSION_NUMBER"); override != "" {
 			return override
 		}
-		return "17"
+		switch ctx.Config().Getenv("EXPERIMENTAL_USE_OPENJDK21_TOOLCHAIN") {
+		case "true":
+			return "21"
+		default:
+			return "17"
+		}
 	})
 
 	pctx.SourcePathVariable("JavaToolchain", "${JavaHome}/bin")
diff --git a/java/dex.go b/java/dex.go
index dab0836..6f1c09d 100644
--- a/java/dex.go
+++ b/java/dex.go
@@ -91,10 +91,12 @@
 	dexProperties DexProperties
 
 	// list of extra proguard flag files
-	extraProguardFlagFiles android.Paths
-	proguardDictionary     android.OptionalPath
-	proguardConfiguration  android.OptionalPath
-	proguardUsageZip       android.OptionalPath
+	extraProguardFlagsFiles android.Paths
+	proguardDictionary      android.OptionalPath
+	proguardConfiguration   android.OptionalPath
+	proguardUsageZip        android.OptionalPath
+	resourcesInput          android.OptionalPath
+	resourcesOutput         android.OptionalPath
 
 	providesTransitiveHeaderJars
 }
@@ -160,7 +162,7 @@
 		"$r8Template": &remoteexec.REParams{
 			Labels:          map[string]string{"type": "compile", "compiler": "r8"},
 			Inputs:          []string{"$implicits", "${config.R8Jar}"},
-			OutputFiles:     []string{"${outUsage}", "${outConfig}", "${outDict}"},
+			OutputFiles:     []string{"${outUsage}", "${outConfig}", "${outDict}", "${resourcesOutput}"},
 			ExecStrategy:    "${config.RER8ExecStrategy}",
 			ToolchainInputs: []string{"${config.JavaCmd}"},
 			Platform:        map[string]string{remoteexec.PoolKey: "${config.REJavaPool}"},
@@ -180,7 +182,7 @@
 			Platform:     map[string]string{remoteexec.PoolKey: "${config.REJavaPool}"},
 		},
 	}, []string{"outDir", "outDict", "outConfig", "outUsage", "outUsageZip", "outUsageDir",
-		"r8Flags", "zipFlags", "mergeZipsFlags"}, []string{"implicits"})
+		"r8Flags", "zipFlags", "mergeZipsFlags", "resourcesOutput"}, []string{"implicits"})
 
 func (d *dexer) dexCommonFlags(ctx android.ModuleContext,
 	dexParams *compileDexParams) (flags []string, deps android.Paths) {
@@ -294,7 +296,7 @@
 		android.PathForSource(ctx, "build/make/core/proguard.flags"),
 	}
 
-	flagFiles = append(flagFiles, d.extraProguardFlagFiles...)
+	flagFiles = append(flagFiles, d.extraProguardFlagsFiles...)
 	// TODO(ccross): static android library proguard files
 
 	flagFiles = append(flagFiles, android.PathsForModuleSrc(ctx, opt.Proguard_flags_files)...)
@@ -349,6 +351,12 @@
 		r8Flags = append(r8Flags, "-ignorewarnings")
 	}
 
+	if d.resourcesInput.Valid() {
+		r8Flags = append(r8Flags, "--resource-input", d.resourcesInput.Path().String())
+		r8Deps = append(r8Deps, d.resourcesInput.Path())
+		r8Flags = append(r8Flags, "--resource-output", d.resourcesOutput.Path().String())
+	}
+
 	return r8Flags, r8Deps
 }
 
@@ -390,6 +398,8 @@
 			android.ModuleNameWithPossibleOverride(ctx), "unused.txt")
 		proguardUsageZip := android.PathForModuleOut(ctx, "proguard_usage.zip")
 		d.proguardUsageZip = android.OptionalPathForPath(proguardUsageZip)
+		resourcesOutput := android.PathForModuleOut(ctx, "package-res-shrunken.apk")
+		d.resourcesOutput = android.OptionalPathForPath(resourcesOutput)
 		r8Flags, r8Deps := d.r8Flags(ctx, dexParams.flags)
 		r8Deps = append(r8Deps, commonDeps...)
 		rule := r8
@@ -408,17 +418,22 @@
 			rule = r8RE
 			args["implicits"] = strings.Join(r8Deps.Strings(), ",")
 		}
+		implicitOutputs := android.WritablePaths{
+			proguardDictionary,
+			proguardUsageZip,
+			proguardConfiguration}
+		if d.resourcesInput.Valid() {
+			implicitOutputs = append(implicitOutputs, resourcesOutput)
+			args["resourcesOutput"] = resourcesOutput.String()
+		}
 		ctx.Build(pctx, android.BuildParams{
-			Rule:        rule,
-			Description: "r8",
-			Output:      javalibJar,
-			ImplicitOutputs: android.WritablePaths{
-				proguardDictionary,
-				proguardUsageZip,
-				proguardConfiguration},
-			Input:     dexParams.classesJar,
-			Implicits: r8Deps,
-			Args:      args,
+			Rule:            rule,
+			Description:     "r8",
+			Output:          javalibJar,
+			ImplicitOutputs: implicitOutputs,
+			Input:           dexParams.classesJar,
+			Implicits:       r8Deps,
+			Args:            args,
 		})
 	} else {
 		d8Flags, d8Deps := d8Flags(dexParams.flags)
diff --git a/java/dexpreopt.go b/java/dexpreopt.go
index 998730e..fe8c5fb 100644
--- a/java/dexpreopt.go
+++ b/java/dexpreopt.go
@@ -374,8 +374,6 @@
 		PreoptBootClassPathDexFiles:     dexFiles.Paths(),
 		PreoptBootClassPathDexLocations: dexLocations,
 
-		PreoptExtractedApk: false,
-
 		NoCreateAppImage:    !BoolDefault(d.dexpreoptProperties.Dex_preopt.App_image, true),
 		ForceCreateAppImage: BoolDefault(d.dexpreoptProperties.Dex_preopt.App_image, false),
 
diff --git a/java/droidstubs.go b/java/droidstubs.go
index 180ba92..6b8d21f 100644
--- a/java/droidstubs.go
+++ b/java/droidstubs.go
@@ -19,7 +19,6 @@
 	"path/filepath"
 	"regexp"
 	"sort"
-	"strconv"
 	"strings"
 
 	"github.com/google/blueprint/proptools"
@@ -500,18 +499,20 @@
 	if metalavaUseRbe(ctx) {
 		rule.Remoteable(android.RemoteRuleSupports{RBE: true})
 		execStrategy := ctx.Config().GetenvWithDefault("RBE_METALAVA_EXEC_STRATEGY", remoteexec.LocalExecStrategy)
-		compare, _ := strconv.ParseBool(ctx.Config().GetenvWithDefault("RBE_METALAVA_COMPARE", "false"))
+		compare := ctx.Config().IsEnvTrue("RBE_METALAVA_COMPARE")
+		remoteUpdateCache := !ctx.Config().IsEnvFalse("RBE_METALAVA_REMOTE_UPDATE_CACHE")
 		labels := map[string]string{"type": "tool", "name": "metalava"}
 		// TODO: metalava pool rejects these jobs
 		pool := ctx.Config().GetenvWithDefault("RBE_METALAVA_POOL", "java16")
 		rule.Rewrapper(&remoteexec.REParams{
-			Labels:          labels,
-			ExecStrategy:    execStrategy,
-			ToolchainInputs: []string{config.JavaCmd(ctx).String()},
-			Platform:        map[string]string{remoteexec.PoolKey: pool},
-			Compare:         compare,
-			NumLocalRuns:    1,
-			NumRemoteRuns:   1,
+			Labels:              labels,
+			ExecStrategy:        execStrategy,
+			ToolchainInputs:     []string{config.JavaCmd(ctx).String()},
+			Platform:            map[string]string{remoteexec.PoolKey: pool},
+			Compare:             compare,
+			NumLocalRuns:        1,
+			NumRemoteRuns:       1,
+			NoRemoteUpdateCache: !remoteUpdateCache,
 		})
 	}
 
diff --git a/java/java.go b/java/java.go
index bb9357c..9f30ad3 100644
--- a/java/java.go
+++ b/java/java.go
@@ -639,7 +639,7 @@
 type Library struct {
 	Module
 
-	exportedProguardFlagFiles android.Paths
+	combinedExportedProguardFlagsFile android.Path
 
 	InstallMixin func(ctx android.ModuleContext, installPath android.Path) (extraInstallDeps android.InstallPaths)
 }
@@ -700,8 +700,12 @@
 
 	proguardSpecInfo := j.collectProguardSpecInfo(ctx)
 	ctx.SetProvider(ProguardSpecInfoProvider, proguardSpecInfo)
-	j.exportedProguardFlagFiles = proguardSpecInfo.ProguardFlagsFiles.ToList()
-	j.extraProguardFlagFiles = append(j.extraProguardFlagFiles, j.exportedProguardFlagFiles...)
+	exportedProguardFlagsFiles := proguardSpecInfo.ProguardFlagsFiles.ToList()
+	j.extraProguardFlagsFiles = append(j.extraProguardFlagsFiles, exportedProguardFlagsFiles...)
+
+	combinedExportedProguardFlagFile := android.PathForModuleOut(ctx, "export_proguard_flags")
+	writeCombinedProguardFlagsFile(ctx, combinedExportedProguardFlagFile, exportedProguardFlagsFiles)
+	j.combinedExportedProguardFlagsFile = combinedExportedProguardFlagFile
 
 	apexInfo := ctx.Provider(android.ApexInfoProvider).(android.ApexInfo)
 	if !apexInfo.IsForPlatform() {
diff --git a/java/resourceshrinker.go b/java/resourceshrinker.go
deleted file mode 100644
index af13aa3..0000000
--- a/java/resourceshrinker.go
+++ /dev/null
@@ -1,45 +0,0 @@
-// Copyright 2022 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 java
-
-import (
-	"android/soong/android"
-
-	"github.com/google/blueprint"
-)
-
-var shrinkResources = pctx.AndroidStaticRule("shrinkResources",
-	blueprint.RuleParams{
-		// Note that we suppress stdout to avoid successful log confirmations.
-		Command: `RESOURCESHRINKER_OPTS=-Dcom.android.tools.r8.dexContainerExperiment ` +
-			`${config.ResourceShrinkerCmd} --output $out --input $in --raw_resources $raw_resources >/dev/null`,
-		CommandDeps: []string{"${config.ResourceShrinkerCmd}"},
-	}, "raw_resources")
-
-func ShrinkResources(ctx android.ModuleContext, apk android.Path, outputFile android.WritablePath) {
-	protoFile := android.PathForModuleOut(ctx, apk.Base()+".proto.apk")
-	aapt2Convert(ctx, protoFile, apk, "proto")
-	strictModeFile := android.PathForSource(ctx, "prebuilts/cmdline-tools/shrinker.xml")
-	protoOut := android.PathForModuleOut(ctx, apk.Base()+".proto.out.apk")
-	ctx.Build(pctx, android.BuildParams{
-		Rule:   shrinkResources,
-		Input:  protoFile,
-		Output: protoOut,
-		Args: map[string]string{
-			"raw_resources": strictModeFile.String(),
-		},
-	})
-	aapt2Convert(ctx, outputFile, protoOut, "binary")
-}
diff --git a/java/resourceshrinker_test.go b/java/resourceshrinker_test.go
deleted file mode 100644
index 3bbf116..0000000
--- a/java/resourceshrinker_test.go
+++ /dev/null
@@ -1,53 +0,0 @@
-// Copyright 2022 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 java
-
-import (
-	"testing"
-
-	"android/soong/android"
-)
-
-func TestShrinkResourcesArgs(t *testing.T) {
-	result := android.GroupFixturePreparers(
-		PrepareForTestWithJavaDefaultModules,
-	).RunTestWithBp(t, `
-		android_app {
-			name: "app_shrink",
-			platform_apis: true,
-			optimize: {
-				shrink_resources: true,
-			}
-		}
-
-		android_app {
-			name: "app_no_shrink",
-			platform_apis: true,
-			optimize: {
-				shrink_resources: false,
-			}
-		}
-	`)
-
-	appShrink := result.ModuleForTests("app_shrink", "android_common")
-	appShrinkResources := appShrink.Rule("shrinkResources")
-	android.AssertStringDoesContain(t, "expected shrinker.xml in app_shrink resource shrinker flags",
-		appShrinkResources.Args["raw_resources"], "shrinker.xml")
-
-	appNoShrink := result.ModuleForTests("app_no_shrink", "android_common")
-	if appNoShrink.MaybeRule("shrinkResources").Rule != nil {
-		t.Errorf("unexpected shrinkResources rule for app_no_shrink")
-	}
-}
diff --git a/phony/phony.go b/phony/phony.go
index 760b79b..a8b651a 100644
--- a/phony/phony.go
+++ b/phony/phony.go
@@ -52,7 +52,6 @@
 			fmt.Fprintln(w, "\ninclude $(CLEAR_VARS)", " # phony.phony")
 			fmt.Fprintln(w, "LOCAL_PATH :=", moduleDir)
 			fmt.Fprintln(w, "LOCAL_MODULE :=", name)
-			data.Entries.WriteLicenseVariables(w)
 			if p.Host() {
 				fmt.Fprintln(w, "LOCAL_IS_HOST_MODULE := true")
 			}
diff --git a/remoteexec/remoteexec.go b/remoteexec/remoteexec.go
index 1e181fb..8294c3f 100644
--- a/remoteexec/remoteexec.go
+++ b/remoteexec/remoteexec.go
@@ -91,6 +91,8 @@
 	NumLocalRuns int
 	// Number of times the action should be rerun remotely.
 	NumRemoteRuns int
+	// Boolean indicating whether to update remote cache entry. Rewrapper defaults to true, so the name is negated here.
+	NoRemoteUpdateCache bool
 }
 
 func init() {
@@ -146,6 +148,10 @@
 		args += fmt.Sprintf(" --compare=true --num_local_reruns=%d --num_remote_reruns=%d", r.NumLocalRuns, r.NumRemoteRuns)
 	}
 
+	if r.NoRemoteUpdateCache {
+		args += " --remote_update_cache=false"
+	}
+
 	if len(r.Inputs) > 0 {
 		args += " --inputs=" + strings.Join(r.Inputs, ",")
 	}
diff --git a/rust/config/global.go b/rust/config/global.go
index 64c9460..3802bdd 100644
--- a/rust/config/global.go
+++ b/rust/config/global.go
@@ -25,7 +25,7 @@
 	pctx         = android.NewPackageContext("android/soong/rust/config")
 	ExportedVars = android.NewExportedVariables(pctx)
 
-	RustDefaultVersion = "1.72.1"
+	RustDefaultVersion = "1.73.0"
 	RustDefaultBase    = "prebuilts/rust/"
 	DefaultEdition     = "2021"
 	Stdlibs            = []string{
diff --git a/sysprop/sysprop_library.go b/sysprop/sysprop_library.go
index fe2cc9c..8bf5f14 100644
--- a/sysprop/sysprop_library.go
+++ b/sysprop/sysprop_library.go
@@ -351,7 +351,6 @@
 			// Actual implementation libraries are created on LoadHookMutator
 			fmt.Fprintln(w, "\ninclude $(CLEAR_VARS)", " # sysprop.syspropLibrary")
 			fmt.Fprintln(w, "LOCAL_MODULE :=", m.Name())
-			data.Entries.WriteLicenseVariables(w)
 			fmt.Fprintf(w, "LOCAL_MODULE_CLASS := FAKE\n")
 			fmt.Fprintf(w, "LOCAL_MODULE_TAGS := optional\n")
 			fmt.Fprintf(w, "include $(BUILD_SYSTEM)/base_rules.mk\n\n")
diff --git a/ui/build/config.go b/ui/build/config.go
index d345415..613fc65 100644
--- a/ui/build/config.go
+++ b/ui/build/config.go
@@ -384,10 +384,14 @@
 	// Configure Java-related variables, including adding it to $PATH
 	java8Home := filepath.Join("prebuilts/jdk/jdk8", ret.HostPrebuiltTag())
 	java17Home := filepath.Join("prebuilts/jdk/jdk17", ret.HostPrebuiltTag())
+	java21Home := filepath.Join("prebuilts/jdk/jdk21", ret.HostPrebuiltTag())
 	javaHome := func() string {
 		if override, ok := ret.environ.Get("OVERRIDE_ANDROID_JAVA_HOME"); ok {
 			return override
 		}
+		if ret.environ.IsEnvTrue("EXPERIMENTAL_USE_OPENJDK21_TOOLCHAIN") {
+			return java21Home
+		}
 		if toolchain11, ok := ret.environ.Get("EXPERIMENTAL_USE_OPENJDK11_TOOLCHAIN"); ok && toolchain11 != "true" {
 			ctx.Fatalln("The environment variable EXPERIMENTAL_USE_OPENJDK11_TOOLCHAIN is no longer supported. An OpenJDK 11 toolchain is now the global default.")
 		}
diff --git a/ui/status/ninja.go b/ui/status/ninja.go
index 7b25d50..1e97908 100644
--- a/ui/status/ninja.go
+++ b/ui/status/ninja.go
@@ -20,6 +20,7 @@
 	"io"
 	"os"
 	"regexp"
+	"runtime"
 	"strings"
 	"syscall"
 	"time"
@@ -178,7 +179,28 @@
 			// msgChan is closed
 			break
 		}
-		// Ignore msg.BuildStarted
+
+		if msg.BuildStarted != nil {
+			parallelism := uint32(runtime.NumCPU())
+			if msg.BuildStarted.GetParallelism() > 0 {
+				parallelism = msg.BuildStarted.GetParallelism()
+			}
+			// It is estimated from total time / parallelism assumming the build is packing enough.
+			estimatedDurationFromTotal := time.Duration(msg.BuildStarted.GetEstimatedTotalTime()/parallelism) * time.Millisecond
+			// It is estimated from critical path time which is useful for small size build.
+			estimatedDurationFromCriticalPath := time.Duration(msg.BuildStarted.GetCriticalPathTime()) * time.Millisecond
+			// Select the longer one.
+			estimatedDuration := max(estimatedDurationFromTotal, estimatedDurationFromCriticalPath)
+
+			if estimatedDuration > 0 {
+				n.status.SetEstimatedTime(time.Now().Add(estimatedDuration))
+				n.status.Verbose(fmt.Sprintf("parallelism: %d, estimiated from total time: %s, critical path time: %s",
+					parallelism,
+					estimatedDurationFromTotal,
+					estimatedDurationFromCriticalPath))
+
+			}
+		}
 		if msg.TotalEdges != nil {
 			n.status.SetTotalActions(int(msg.TotalEdges.GetTotalEdges()))
 		}
diff --git a/ui/status/ninja_frontend/frontend.pb.go b/ui/status/ninja_frontend/frontend.pb.go
index d0c4953..d8344c8 100644
--- a/ui/status/ninja_frontend/frontend.pb.go
+++ b/ui/status/ninja_frontend/frontend.pb.go
@@ -14,7 +14,7 @@
 
 // Code generated by protoc-gen-go. DO NOT EDIT.
 // versions:
-// 	protoc-gen-go v1.28.1
+// 	protoc-gen-go v1.30.0
 // 	protoc        v3.21.12
 // source: frontend.proto
 
@@ -240,6 +240,10 @@
 	Parallelism *uint32 `protobuf:"varint,1,opt,name=parallelism" json:"parallelism,omitempty"`
 	// Verbose value passed to ninja.
 	Verbose *bool `protobuf:"varint,2,opt,name=verbose" json:"verbose,omitempty"`
+	// Critical path's running time in milliseconds
+	CriticalPathTime *uint32 `protobuf:"varint,3,opt,name=critical_path_time,json=criticalPathTime" json:"critical_path_time,omitempty"`
+	// Total running time of every need-to-build edge in milliseconds
+	EstimatedTotalTime *uint32 `protobuf:"varint,4,opt,name=estimated_total_time,json=estimatedTotalTime" json:"estimated_total_time,omitempty"`
 }
 
 func (x *Status_BuildStarted) Reset() {
@@ -288,6 +292,20 @@
 	return false
 }
 
+func (x *Status_BuildStarted) GetCriticalPathTime() uint32 {
+	if x != nil && x.CriticalPathTime != nil {
+		return *x.CriticalPathTime
+	}
+	return 0
+}
+
+func (x *Status_BuildStarted) GetEstimatedTotalTime() uint32 {
+	if x != nil && x.EstimatedTotalTime != nil {
+		return *x.EstimatedTotalTime
+	}
+	return 0
+}
+
 type Status_BuildFinished struct {
 	state         protoimpl.MessageState
 	sizeCache     protoimpl.SizeCache
@@ -660,7 +678,7 @@
 
 var file_frontend_proto_rawDesc = []byte{
 	0x0a, 0x0e, 0x66, 0x72, 0x6f, 0x6e, 0x74, 0x65, 0x6e, 0x64, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f,
-	0x12, 0x05, 0x6e, 0x69, 0x6e, 0x6a, 0x61, 0x22, 0xc8, 0x0a, 0x0a, 0x06, 0x53, 0x74, 0x61, 0x74,
+	0x12, 0x05, 0x6e, 0x69, 0x6e, 0x6a, 0x61, 0x22, 0xa9, 0x0b, 0x0a, 0x06, 0x53, 0x74, 0x61, 0x74,
 	0x75, 0x73, 0x12, 0x39, 0x0a, 0x0b, 0x74, 0x6f, 0x74, 0x61, 0x6c, 0x5f, 0x65, 0x64, 0x67, 0x65,
 	0x73, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x18, 0x2e, 0x6e, 0x69, 0x6e, 0x6a, 0x61, 0x2e,
 	0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x2e, 0x54, 0x6f, 0x74, 0x61, 0x6c, 0x45, 0x64, 0x67, 0x65,
@@ -687,67 +705,73 @@
 	0x67, 0x65, 0x1a, 0x2d, 0x0a, 0x0a, 0x54, 0x6f, 0x74, 0x61, 0x6c, 0x45, 0x64, 0x67, 0x65, 0x73,
 	0x12, 0x1f, 0x0a, 0x0b, 0x74, 0x6f, 0x74, 0x61, 0x6c, 0x5f, 0x65, 0x64, 0x67, 0x65, 0x73, 0x18,
 	0x01, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x0a, 0x74, 0x6f, 0x74, 0x61, 0x6c, 0x45, 0x64, 0x67, 0x65,
-	0x73, 0x1a, 0x4a, 0x0a, 0x0c, 0x42, 0x75, 0x69, 0x6c, 0x64, 0x53, 0x74, 0x61, 0x72, 0x74, 0x65,
-	0x64, 0x12, 0x20, 0x0a, 0x0b, 0x70, 0x61, 0x72, 0x61, 0x6c, 0x6c, 0x65, 0x6c, 0x69, 0x73, 0x6d,
-	0x18, 0x01, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x0b, 0x70, 0x61, 0x72, 0x61, 0x6c, 0x6c, 0x65, 0x6c,
-	0x69, 0x73, 0x6d, 0x12, 0x18, 0x0a, 0x07, 0x76, 0x65, 0x72, 0x62, 0x6f, 0x73, 0x65, 0x18, 0x02,
-	0x20, 0x01, 0x28, 0x08, 0x52, 0x07, 0x76, 0x65, 0x72, 0x62, 0x6f, 0x73, 0x65, 0x1a, 0x0f, 0x0a,
-	0x0d, 0x42, 0x75, 0x69, 0x6c, 0x64, 0x46, 0x69, 0x6e, 0x69, 0x73, 0x68, 0x65, 0x64, 0x1a, 0xb6,
-	0x01, 0x0a, 0x0b, 0x45, 0x64, 0x67, 0x65, 0x53, 0x74, 0x61, 0x72, 0x74, 0x65, 0x64, 0x12, 0x0e,
-	0x0a, 0x02, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x02, 0x69, 0x64, 0x12, 0x1d,
-	0x0a, 0x0a, 0x73, 0x74, 0x61, 0x72, 0x74, 0x5f, 0x74, 0x69, 0x6d, 0x65, 0x18, 0x02, 0x20, 0x01,
-	0x28, 0x0d, 0x52, 0x09, 0x73, 0x74, 0x61, 0x72, 0x74, 0x54, 0x69, 0x6d, 0x65, 0x12, 0x16, 0x0a,
-	0x06, 0x69, 0x6e, 0x70, 0x75, 0x74, 0x73, 0x18, 0x03, 0x20, 0x03, 0x28, 0x09, 0x52, 0x06, 0x69,
-	0x6e, 0x70, 0x75, 0x74, 0x73, 0x12, 0x18, 0x0a, 0x07, 0x6f, 0x75, 0x74, 0x70, 0x75, 0x74, 0x73,
-	0x18, 0x04, 0x20, 0x03, 0x28, 0x09, 0x52, 0x07, 0x6f, 0x75, 0x74, 0x70, 0x75, 0x74, 0x73, 0x12,
-	0x12, 0x0a, 0x04, 0x64, 0x65, 0x73, 0x63, 0x18, 0x05, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x64,
-	0x65, 0x73, 0x63, 0x12, 0x18, 0x0a, 0x07, 0x63, 0x6f, 0x6d, 0x6d, 0x61, 0x6e, 0x64, 0x18, 0x06,
-	0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x63, 0x6f, 0x6d, 0x6d, 0x61, 0x6e, 0x64, 0x12, 0x18, 0x0a,
-	0x07, 0x63, 0x6f, 0x6e, 0x73, 0x6f, 0x6c, 0x65, 0x18, 0x07, 0x20, 0x01, 0x28, 0x08, 0x52, 0x07,
-	0x63, 0x6f, 0x6e, 0x73, 0x6f, 0x6c, 0x65, 0x1a, 0xf3, 0x03, 0x0a, 0x0c, 0x45, 0x64, 0x67, 0x65,
-	0x46, 0x69, 0x6e, 0x69, 0x73, 0x68, 0x65, 0x64, 0x12, 0x0e, 0x0a, 0x02, 0x69, 0x64, 0x18, 0x01,
-	0x20, 0x01, 0x28, 0x0d, 0x52, 0x02, 0x69, 0x64, 0x12, 0x19, 0x0a, 0x08, 0x65, 0x6e, 0x64, 0x5f,
-	0x74, 0x69, 0x6d, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x07, 0x65, 0x6e, 0x64, 0x54,
-	0x69, 0x6d, 0x65, 0x12, 0x16, 0x0a, 0x06, 0x73, 0x74, 0x61, 0x74, 0x75, 0x73, 0x18, 0x03, 0x20,
-	0x01, 0x28, 0x11, 0x52, 0x06, 0x73, 0x74, 0x61, 0x74, 0x75, 0x73, 0x12, 0x16, 0x0a, 0x06, 0x6f,
-	0x75, 0x74, 0x70, 0x75, 0x74, 0x18, 0x04, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x6f, 0x75, 0x74,
-	0x70, 0x75, 0x74, 0x12, 0x1b, 0x0a, 0x09, 0x75, 0x73, 0x65, 0x72, 0x5f, 0x74, 0x69, 0x6d, 0x65,
-	0x18, 0x05, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x08, 0x75, 0x73, 0x65, 0x72, 0x54, 0x69, 0x6d, 0x65,
-	0x12, 0x1f, 0x0a, 0x0b, 0x73, 0x79, 0x73, 0x74, 0x65, 0x6d, 0x5f, 0x74, 0x69, 0x6d, 0x65, 0x18,
-	0x06, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x0a, 0x73, 0x79, 0x73, 0x74, 0x65, 0x6d, 0x54, 0x69, 0x6d,
-	0x65, 0x12, 0x1c, 0x0a, 0x0a, 0x6d, 0x61, 0x78, 0x5f, 0x72, 0x73, 0x73, 0x5f, 0x6b, 0x62, 0x18,
-	0x07, 0x20, 0x01, 0x28, 0x04, 0x52, 0x08, 0x6d, 0x61, 0x78, 0x52, 0x73, 0x73, 0x4b, 0x62, 0x12,
-	0x2a, 0x0a, 0x11, 0x6d, 0x69, 0x6e, 0x6f, 0x72, 0x5f, 0x70, 0x61, 0x67, 0x65, 0x5f, 0x66, 0x61,
-	0x75, 0x6c, 0x74, 0x73, 0x18, 0x08, 0x20, 0x01, 0x28, 0x04, 0x52, 0x0f, 0x6d, 0x69, 0x6e, 0x6f,
-	0x72, 0x50, 0x61, 0x67, 0x65, 0x46, 0x61, 0x75, 0x6c, 0x74, 0x73, 0x12, 0x2a, 0x0a, 0x11, 0x6d,
-	0x61, 0x6a, 0x6f, 0x72, 0x5f, 0x70, 0x61, 0x67, 0x65, 0x5f, 0x66, 0x61, 0x75, 0x6c, 0x74, 0x73,
-	0x18, 0x09, 0x20, 0x01, 0x28, 0x04, 0x52, 0x0f, 0x6d, 0x61, 0x6a, 0x6f, 0x72, 0x50, 0x61, 0x67,
-	0x65, 0x46, 0x61, 0x75, 0x6c, 0x74, 0x73, 0x12, 0x1e, 0x0a, 0x0b, 0x69, 0x6f, 0x5f, 0x69, 0x6e,
-	0x70, 0x75, 0x74, 0x5f, 0x6b, 0x62, 0x18, 0x0a, 0x20, 0x01, 0x28, 0x04, 0x52, 0x09, 0x69, 0x6f,
-	0x49, 0x6e, 0x70, 0x75, 0x74, 0x4b, 0x62, 0x12, 0x20, 0x0a, 0x0c, 0x69, 0x6f, 0x5f, 0x6f, 0x75,
-	0x74, 0x70, 0x75, 0x74, 0x5f, 0x6b, 0x62, 0x18, 0x0b, 0x20, 0x01, 0x28, 0x04, 0x52, 0x0a, 0x69,
-	0x6f, 0x4f, 0x75, 0x74, 0x70, 0x75, 0x74, 0x4b, 0x62, 0x12, 0x3c, 0x0a, 0x1a, 0x76, 0x6f, 0x6c,
-	0x75, 0x6e, 0x74, 0x61, 0x72, 0x79, 0x5f, 0x63, 0x6f, 0x6e, 0x74, 0x65, 0x78, 0x74, 0x5f, 0x73,
-	0x77, 0x69, 0x74, 0x63, 0x68, 0x65, 0x73, 0x18, 0x0c, 0x20, 0x01, 0x28, 0x04, 0x52, 0x18, 0x76,
-	0x6f, 0x6c, 0x75, 0x6e, 0x74, 0x61, 0x72, 0x79, 0x43, 0x6f, 0x6e, 0x74, 0x65, 0x78, 0x74, 0x53,
-	0x77, 0x69, 0x74, 0x63, 0x68, 0x65, 0x73, 0x12, 0x40, 0x0a, 0x1c, 0x69, 0x6e, 0x76, 0x6f, 0x6c,
-	0x75, 0x6e, 0x74, 0x61, 0x72, 0x79, 0x5f, 0x63, 0x6f, 0x6e, 0x74, 0x65, 0x78, 0x74, 0x5f, 0x73,
-	0x77, 0x69, 0x74, 0x63, 0x68, 0x65, 0x73, 0x18, 0x0d, 0x20, 0x01, 0x28, 0x04, 0x52, 0x1a, 0x69,
-	0x6e, 0x76, 0x6f, 0x6c, 0x75, 0x6e, 0x74, 0x61, 0x72, 0x79, 0x43, 0x6f, 0x6e, 0x74, 0x65, 0x78,
-	0x74, 0x53, 0x77, 0x69, 0x74, 0x63, 0x68, 0x65, 0x73, 0x12, 0x12, 0x0a, 0x04, 0x74, 0x61, 0x67,
-	0x73, 0x18, 0x0e, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x74, 0x61, 0x67, 0x73, 0x1a, 0x92, 0x01,
-	0x0a, 0x07, 0x4d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x12, 0x37, 0x0a, 0x05, 0x6c, 0x65, 0x76,
-	0x65, 0x6c, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0e, 0x32, 0x1b, 0x2e, 0x6e, 0x69, 0x6e, 0x6a, 0x61,
-	0x2e, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x2e, 0x4d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x2e,
-	0x4c, 0x65, 0x76, 0x65, 0x6c, 0x3a, 0x04, 0x49, 0x4e, 0x46, 0x4f, 0x52, 0x05, 0x6c, 0x65, 0x76,
-	0x65, 0x6c, 0x12, 0x18, 0x0a, 0x07, 0x6d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x18, 0x02, 0x20,
-	0x01, 0x28, 0x09, 0x52, 0x07, 0x6d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x22, 0x34, 0x0a, 0x05,
-	0x4c, 0x65, 0x76, 0x65, 0x6c, 0x12, 0x08, 0x0a, 0x04, 0x49, 0x4e, 0x46, 0x4f, 0x10, 0x00, 0x12,
-	0x0b, 0x0a, 0x07, 0x57, 0x41, 0x52, 0x4e, 0x49, 0x4e, 0x47, 0x10, 0x01, 0x12, 0x09, 0x0a, 0x05,
-	0x45, 0x52, 0x52, 0x4f, 0x52, 0x10, 0x02, 0x12, 0x09, 0x0a, 0x05, 0x44, 0x45, 0x42, 0x55, 0x47,
-	0x10, 0x03, 0x42, 0x2a, 0x48, 0x03, 0x5a, 0x26, 0x61, 0x6e, 0x64, 0x72, 0x6f, 0x69, 0x64, 0x2f,
-	0x73, 0x6f, 0x6f, 0x6e, 0x67, 0x2f, 0x75, 0x69, 0x2f, 0x73, 0x74, 0x61, 0x74, 0x75, 0x73, 0x2f,
-	0x6e, 0x69, 0x6e, 0x6a, 0x61, 0x5f, 0x66, 0x72, 0x6f, 0x6e, 0x74, 0x65, 0x6e, 0x64,
+	0x73, 0x1a, 0xaa, 0x01, 0x0a, 0x0c, 0x42, 0x75, 0x69, 0x6c, 0x64, 0x53, 0x74, 0x61, 0x72, 0x74,
+	0x65, 0x64, 0x12, 0x20, 0x0a, 0x0b, 0x70, 0x61, 0x72, 0x61, 0x6c, 0x6c, 0x65, 0x6c, 0x69, 0x73,
+	0x6d, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x0b, 0x70, 0x61, 0x72, 0x61, 0x6c, 0x6c, 0x65,
+	0x6c, 0x69, 0x73, 0x6d, 0x12, 0x18, 0x0a, 0x07, 0x76, 0x65, 0x72, 0x62, 0x6f, 0x73, 0x65, 0x18,
+	0x02, 0x20, 0x01, 0x28, 0x08, 0x52, 0x07, 0x76, 0x65, 0x72, 0x62, 0x6f, 0x73, 0x65, 0x12, 0x2c,
+	0x0a, 0x12, 0x63, 0x72, 0x69, 0x74, 0x69, 0x63, 0x61, 0x6c, 0x5f, 0x70, 0x61, 0x74, 0x68, 0x5f,
+	0x74, 0x69, 0x6d, 0x65, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x10, 0x63, 0x72, 0x69, 0x74,
+	0x69, 0x63, 0x61, 0x6c, 0x50, 0x61, 0x74, 0x68, 0x54, 0x69, 0x6d, 0x65, 0x12, 0x30, 0x0a, 0x14,
+	0x65, 0x73, 0x74, 0x69, 0x6d, 0x61, 0x74, 0x65, 0x64, 0x5f, 0x74, 0x6f, 0x74, 0x61, 0x6c, 0x5f,
+	0x74, 0x69, 0x6d, 0x65, 0x18, 0x04, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x12, 0x65, 0x73, 0x74, 0x69,
+	0x6d, 0x61, 0x74, 0x65, 0x64, 0x54, 0x6f, 0x74, 0x61, 0x6c, 0x54, 0x69, 0x6d, 0x65, 0x1a, 0x0f,
+	0x0a, 0x0d, 0x42, 0x75, 0x69, 0x6c, 0x64, 0x46, 0x69, 0x6e, 0x69, 0x73, 0x68, 0x65, 0x64, 0x1a,
+	0xb6, 0x01, 0x0a, 0x0b, 0x45, 0x64, 0x67, 0x65, 0x53, 0x74, 0x61, 0x72, 0x74, 0x65, 0x64, 0x12,
+	0x0e, 0x0a, 0x02, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x02, 0x69, 0x64, 0x12,
+	0x1d, 0x0a, 0x0a, 0x73, 0x74, 0x61, 0x72, 0x74, 0x5f, 0x74, 0x69, 0x6d, 0x65, 0x18, 0x02, 0x20,
+	0x01, 0x28, 0x0d, 0x52, 0x09, 0x73, 0x74, 0x61, 0x72, 0x74, 0x54, 0x69, 0x6d, 0x65, 0x12, 0x16,
+	0x0a, 0x06, 0x69, 0x6e, 0x70, 0x75, 0x74, 0x73, 0x18, 0x03, 0x20, 0x03, 0x28, 0x09, 0x52, 0x06,
+	0x69, 0x6e, 0x70, 0x75, 0x74, 0x73, 0x12, 0x18, 0x0a, 0x07, 0x6f, 0x75, 0x74, 0x70, 0x75, 0x74,
+	0x73, 0x18, 0x04, 0x20, 0x03, 0x28, 0x09, 0x52, 0x07, 0x6f, 0x75, 0x74, 0x70, 0x75, 0x74, 0x73,
+	0x12, 0x12, 0x0a, 0x04, 0x64, 0x65, 0x73, 0x63, 0x18, 0x05, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04,
+	0x64, 0x65, 0x73, 0x63, 0x12, 0x18, 0x0a, 0x07, 0x63, 0x6f, 0x6d, 0x6d, 0x61, 0x6e, 0x64, 0x18,
+	0x06, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x63, 0x6f, 0x6d, 0x6d, 0x61, 0x6e, 0x64, 0x12, 0x18,
+	0x0a, 0x07, 0x63, 0x6f, 0x6e, 0x73, 0x6f, 0x6c, 0x65, 0x18, 0x07, 0x20, 0x01, 0x28, 0x08, 0x52,
+	0x07, 0x63, 0x6f, 0x6e, 0x73, 0x6f, 0x6c, 0x65, 0x1a, 0xf3, 0x03, 0x0a, 0x0c, 0x45, 0x64, 0x67,
+	0x65, 0x46, 0x69, 0x6e, 0x69, 0x73, 0x68, 0x65, 0x64, 0x12, 0x0e, 0x0a, 0x02, 0x69, 0x64, 0x18,
+	0x01, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x02, 0x69, 0x64, 0x12, 0x19, 0x0a, 0x08, 0x65, 0x6e, 0x64,
+	0x5f, 0x74, 0x69, 0x6d, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x07, 0x65, 0x6e, 0x64,
+	0x54, 0x69, 0x6d, 0x65, 0x12, 0x16, 0x0a, 0x06, 0x73, 0x74, 0x61, 0x74, 0x75, 0x73, 0x18, 0x03,
+	0x20, 0x01, 0x28, 0x11, 0x52, 0x06, 0x73, 0x74, 0x61, 0x74, 0x75, 0x73, 0x12, 0x16, 0x0a, 0x06,
+	0x6f, 0x75, 0x74, 0x70, 0x75, 0x74, 0x18, 0x04, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x6f, 0x75,
+	0x74, 0x70, 0x75, 0x74, 0x12, 0x1b, 0x0a, 0x09, 0x75, 0x73, 0x65, 0x72, 0x5f, 0x74, 0x69, 0x6d,
+	0x65, 0x18, 0x05, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x08, 0x75, 0x73, 0x65, 0x72, 0x54, 0x69, 0x6d,
+	0x65, 0x12, 0x1f, 0x0a, 0x0b, 0x73, 0x79, 0x73, 0x74, 0x65, 0x6d, 0x5f, 0x74, 0x69, 0x6d, 0x65,
+	0x18, 0x06, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x0a, 0x73, 0x79, 0x73, 0x74, 0x65, 0x6d, 0x54, 0x69,
+	0x6d, 0x65, 0x12, 0x1c, 0x0a, 0x0a, 0x6d, 0x61, 0x78, 0x5f, 0x72, 0x73, 0x73, 0x5f, 0x6b, 0x62,
+	0x18, 0x07, 0x20, 0x01, 0x28, 0x04, 0x52, 0x08, 0x6d, 0x61, 0x78, 0x52, 0x73, 0x73, 0x4b, 0x62,
+	0x12, 0x2a, 0x0a, 0x11, 0x6d, 0x69, 0x6e, 0x6f, 0x72, 0x5f, 0x70, 0x61, 0x67, 0x65, 0x5f, 0x66,
+	0x61, 0x75, 0x6c, 0x74, 0x73, 0x18, 0x08, 0x20, 0x01, 0x28, 0x04, 0x52, 0x0f, 0x6d, 0x69, 0x6e,
+	0x6f, 0x72, 0x50, 0x61, 0x67, 0x65, 0x46, 0x61, 0x75, 0x6c, 0x74, 0x73, 0x12, 0x2a, 0x0a, 0x11,
+	0x6d, 0x61, 0x6a, 0x6f, 0x72, 0x5f, 0x70, 0x61, 0x67, 0x65, 0x5f, 0x66, 0x61, 0x75, 0x6c, 0x74,
+	0x73, 0x18, 0x09, 0x20, 0x01, 0x28, 0x04, 0x52, 0x0f, 0x6d, 0x61, 0x6a, 0x6f, 0x72, 0x50, 0x61,
+	0x67, 0x65, 0x46, 0x61, 0x75, 0x6c, 0x74, 0x73, 0x12, 0x1e, 0x0a, 0x0b, 0x69, 0x6f, 0x5f, 0x69,
+	0x6e, 0x70, 0x75, 0x74, 0x5f, 0x6b, 0x62, 0x18, 0x0a, 0x20, 0x01, 0x28, 0x04, 0x52, 0x09, 0x69,
+	0x6f, 0x49, 0x6e, 0x70, 0x75, 0x74, 0x4b, 0x62, 0x12, 0x20, 0x0a, 0x0c, 0x69, 0x6f, 0x5f, 0x6f,
+	0x75, 0x74, 0x70, 0x75, 0x74, 0x5f, 0x6b, 0x62, 0x18, 0x0b, 0x20, 0x01, 0x28, 0x04, 0x52, 0x0a,
+	0x69, 0x6f, 0x4f, 0x75, 0x74, 0x70, 0x75, 0x74, 0x4b, 0x62, 0x12, 0x3c, 0x0a, 0x1a, 0x76, 0x6f,
+	0x6c, 0x75, 0x6e, 0x74, 0x61, 0x72, 0x79, 0x5f, 0x63, 0x6f, 0x6e, 0x74, 0x65, 0x78, 0x74, 0x5f,
+	0x73, 0x77, 0x69, 0x74, 0x63, 0x68, 0x65, 0x73, 0x18, 0x0c, 0x20, 0x01, 0x28, 0x04, 0x52, 0x18,
+	0x76, 0x6f, 0x6c, 0x75, 0x6e, 0x74, 0x61, 0x72, 0x79, 0x43, 0x6f, 0x6e, 0x74, 0x65, 0x78, 0x74,
+	0x53, 0x77, 0x69, 0x74, 0x63, 0x68, 0x65, 0x73, 0x12, 0x40, 0x0a, 0x1c, 0x69, 0x6e, 0x76, 0x6f,
+	0x6c, 0x75, 0x6e, 0x74, 0x61, 0x72, 0x79, 0x5f, 0x63, 0x6f, 0x6e, 0x74, 0x65, 0x78, 0x74, 0x5f,
+	0x73, 0x77, 0x69, 0x74, 0x63, 0x68, 0x65, 0x73, 0x18, 0x0d, 0x20, 0x01, 0x28, 0x04, 0x52, 0x1a,
+	0x69, 0x6e, 0x76, 0x6f, 0x6c, 0x75, 0x6e, 0x74, 0x61, 0x72, 0x79, 0x43, 0x6f, 0x6e, 0x74, 0x65,
+	0x78, 0x74, 0x53, 0x77, 0x69, 0x74, 0x63, 0x68, 0x65, 0x73, 0x12, 0x12, 0x0a, 0x04, 0x74, 0x61,
+	0x67, 0x73, 0x18, 0x0e, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x74, 0x61, 0x67, 0x73, 0x1a, 0x92,
+	0x01, 0x0a, 0x07, 0x4d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x12, 0x37, 0x0a, 0x05, 0x6c, 0x65,
+	0x76, 0x65, 0x6c, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0e, 0x32, 0x1b, 0x2e, 0x6e, 0x69, 0x6e, 0x6a,
+	0x61, 0x2e, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x2e, 0x4d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65,
+	0x2e, 0x4c, 0x65, 0x76, 0x65, 0x6c, 0x3a, 0x04, 0x49, 0x4e, 0x46, 0x4f, 0x52, 0x05, 0x6c, 0x65,
+	0x76, 0x65, 0x6c, 0x12, 0x18, 0x0a, 0x07, 0x6d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x18, 0x02,
+	0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x6d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x22, 0x34, 0x0a,
+	0x05, 0x4c, 0x65, 0x76, 0x65, 0x6c, 0x12, 0x08, 0x0a, 0x04, 0x49, 0x4e, 0x46, 0x4f, 0x10, 0x00,
+	0x12, 0x0b, 0x0a, 0x07, 0x57, 0x41, 0x52, 0x4e, 0x49, 0x4e, 0x47, 0x10, 0x01, 0x12, 0x09, 0x0a,
+	0x05, 0x45, 0x52, 0x52, 0x4f, 0x52, 0x10, 0x02, 0x12, 0x09, 0x0a, 0x05, 0x44, 0x45, 0x42, 0x55,
+	0x47, 0x10, 0x03, 0x42, 0x2a, 0x48, 0x03, 0x5a, 0x26, 0x61, 0x6e, 0x64, 0x72, 0x6f, 0x69, 0x64,
+	0x2f, 0x73, 0x6f, 0x6f, 0x6e, 0x67, 0x2f, 0x75, 0x69, 0x2f, 0x73, 0x74, 0x61, 0x74, 0x75, 0x73,
+	0x2f, 0x6e, 0x69, 0x6e, 0x6a, 0x61, 0x5f, 0x66, 0x72, 0x6f, 0x6e, 0x74, 0x65, 0x6e, 0x64,
 }
 
 var (
diff --git a/ui/status/ninja_frontend/frontend.proto b/ui/status/ninja_frontend/frontend.proto
index 6cb4a0d..42b251b 100644
--- a/ui/status/ninja_frontend/frontend.proto
+++ b/ui/status/ninja_frontend/frontend.proto
@@ -30,6 +30,10 @@
     optional uint32 parallelism = 1;
     // Verbose value passed to ninja.
     optional bool verbose = 2;
+    // Critical path's running time in milliseconds
+    optional uint32 critical_path_time = 3;
+    // Total running time of every need-to-build edge in milliseconds
+    optional uint32 estimated_total_time = 4;
   }
 
   message BuildFinished {
diff --git a/ui/status/status.go b/ui/status/status.go
index f3e58b6..da78994 100644
--- a/ui/status/status.go
+++ b/ui/status/status.go
@@ -19,6 +19,7 @@
 
 import (
 	"sync"
+	"time"
 )
 
 // Action describes an action taken (or as Ninja calls them, Edges).
@@ -107,6 +108,8 @@
 	// FinishedActions are the number of actions that have been finished
 	// with FinishAction.
 	FinishedActions int
+
+	EstimatedTime time.Time
 }
 
 // ToolStatus is the interface used by tools to report on their Actions, and to
@@ -118,6 +121,7 @@
 	// This call be will ignored if it sets a number that is less than the
 	// current number of started actions.
 	SetTotalActions(total int)
+	SetEstimatedTime(estimatedTime time.Time)
 
 	// StartAction specifies that the associated action has been started by
 	// the tool.
@@ -267,6 +271,13 @@
 	s.counts.TotalActions += diff
 }
 
+func (s *Status) SetEstimatedTime(estimatedTime time.Time) {
+	s.lock.Lock()
+	defer s.lock.Unlock()
+
+	s.counts.EstimatedTime = estimatedTime
+}
+
 func (s *Status) startAction(action *Action) {
 	s.lock.Lock()
 	defer s.lock.Unlock()
@@ -329,6 +340,10 @@
 	}
 }
 
+func (d *toolStatus) SetEstimatedTime(estimatedTime time.Time) {
+	d.status.SetEstimatedTime(estimatedTime)
+}
+
 func (d *toolStatus) StartAction(action *Action) {
 	totalDiff := 0
 
diff --git a/ui/terminal/format.go b/ui/terminal/format.go
index 4205bdc..5391023 100644
--- a/ui/terminal/format.go
+++ b/ui/terminal/format.go
@@ -25,6 +25,7 @@
 type formatter struct {
 	format string
 	quiet  bool
+	smart  bool
 	start  time.Time
 }
 
@@ -32,10 +33,11 @@
 // the terminal in a format similar to Ninja.
 // format takes nearly all the same options as NINJA_STATUS.
 // %c is currently unsupported.
-func newFormatter(format string, quiet bool) formatter {
+func newFormatter(format string, quiet bool, smart bool) formatter {
 	return formatter{
 		format: format,
 		quiet:  quiet,
+		smart:  smart,
 		start:  time.Now(),
 	}
 }
@@ -51,9 +53,23 @@
 	return ""
 }
 
+func remainingTimeString(t time.Time) string {
+	now := time.Now()
+	if t.After(now) {
+		return t.Sub(now).Round(time.Duration(time.Second)).String()
+	}
+	return time.Duration(0).Round(time.Duration(time.Second)).String()
+}
 func (s formatter) progress(counts status.Counts) string {
 	if s.format == "" {
-		return fmt.Sprintf("[%3d%% %d/%d] ", 100*counts.FinishedActions/counts.TotalActions, counts.FinishedActions, counts.TotalActions)
+		output := fmt.Sprintf("[%3d%% %d/%d", 100*counts.FinishedActions/counts.TotalActions, counts.FinishedActions, counts.TotalActions)
+		// Not to break parsing logic in the build bot
+		// TODO(b/313981966): make buildbot more flexible for output format
+		if s.smart && !counts.EstimatedTime.IsZero() {
+			output += fmt.Sprintf(" %s remaining", remainingTimeString(counts.EstimatedTime))
+		}
+		output += "] "
+		return output
 	}
 
 	buf := &strings.Builder{}
@@ -93,6 +109,13 @@
 			fmt.Fprintf(buf, "%3d%%", 100*counts.FinishedActions/counts.TotalActions)
 		case 'e':
 			fmt.Fprintf(buf, "%.3f", time.Since(s.start).Seconds())
+		case 'l':
+			if counts.EstimatedTime.IsZero() {
+				// No esitimated data
+				buf.WriteRune('?')
+			} else {
+				fmt.Fprintf(buf, "%s", remainingTimeString(counts.EstimatedTime))
+			}
 		default:
 			buf.WriteString("unknown placeholder '")
 			buf.WriteByte(c)
diff --git a/ui/terminal/status.go b/ui/terminal/status.go
index 2ad174f..810e3c9 100644
--- a/ui/terminal/status.go
+++ b/ui/terminal/status.go
@@ -27,9 +27,10 @@
 // statusFormat takes nearly all the same options as NINJA_STATUS.
 // %c is currently unsupported.
 func NewStatusOutput(w io.Writer, statusFormat string, forceSimpleOutput, quietBuild, forceKeepANSI bool) status.StatusOutput {
-	formatter := newFormatter(statusFormat, quietBuild)
+	useSmartStatus := !forceSimpleOutput && isSmartTerminal(w)
+	formatter := newFormatter(statusFormat, quietBuild, useSmartStatus)
 
-	if !forceSimpleOutput && isSmartTerminal(w) {
+	if useSmartStatus {
 		return NewSmartStatusOutput(w, formatter)
 	} else {
 		return NewSimpleStatusOutput(w, formatter, forceKeepANSI)