Merge "Remove unused android-specific defines"
diff --git a/cmds/installd/dexopt.cpp b/cmds/installd/dexopt.cpp
index 50a2412..90cadb4 100644
--- a/cmds/installd/dexopt.cpp
+++ b/cmds/installd/dexopt.cpp
@@ -201,53 +201,23 @@
     }
 }
 
-// ExecVHelper prepares and holds pointers to parsed command line arguments so that no allocations
-// need to be performed between the fork and exec.
-class ExecVHelper {
-  public:
-    // Store a placeholder for the binary name.
-    ExecVHelper() : args_(1u, std::string()) {}
-
-    void PrepareArgs(const std::string& bin) {
-        CHECK(!args_.empty());
-        CHECK(args_[0].empty());
-        args_[0] = bin;
-        // Write char* into array.
-        for (const std::string& arg : args_) {
-            argv_.push_back(arg.c_str());
-        }
-        argv_.push_back(nullptr);  // Add null terminator.
+// Automatically adds binary and null terminator arg.
+static inline void ExecVWithArgs(const char* bin, const std::vector<std::string>& args) {
+    std::vector<const char*> argv = {bin};
+    for (const std::string& arg : args) {
+        argv.push_back(arg.c_str());
     }
+    // Add null terminator.
+    argv.push_back(nullptr);
+    execv(bin, (char * const *)&argv[0]);
+}
 
-    [[ noreturn ]]
-    void Exec(int exit_code) {
-        execv(argv_[0], (char * const *)&argv_[0]);
-        PLOG(ERROR) << "execv(" << argv_[0] << ") failed";
-        exit(exit_code);
+static inline void AddArgIfNonEmpty(const std::string& arg, std::vector<std::string>* args) {
+    DCHECK(args != nullptr);
+    if (!arg.empty()) {
+        args->push_back(arg);
     }
-
-    // Add an arg if it's not empty.
-    void AddArg(const std::string& arg) {
-        if (!arg.empty()) {
-            args_.push_back(arg);
-        }
-    }
-
-    // Add a runtime arg if it's not empty.
-    void AddRuntimeArg(const std::string& arg) {
-        if (!arg.empty()) {
-            args_.push_back("--runtime-arg");
-            args_.push_back(arg);
-        }
-    }
-
-  protected:
-    // Holder arrays for backing arg storage.
-    std::vector<std::string> args_;
-
-    // Argument poiners.
-    std::vector<const char*> argv_;
-};
+}
 
 static std::string MapPropertyToArg(const std::string& property,
                                     const std::string& format,
@@ -259,220 +229,212 @@
   return "";
 }
 
-class RunDex2Oat : public ExecVHelper {
-  public:
-    RunDex2Oat(int zip_fd,
-               int oat_fd,
-               int input_vdex_fd,
-               int output_vdex_fd,
-               int image_fd,
-               const char* input_file_name,
-               const char* output_file_name,
-               int swap_fd,
-               const char* instruction_set,
-               const char* compiler_filter,
-               bool debuggable,
-               bool post_bootcomplete,
-               bool background_job_compile,
-               int profile_fd,
-               const char* class_loader_context,
-               int target_sdk_version,
-               bool enable_hidden_api_checks,
-               bool generate_compact_dex,
-               int dex_metadata_fd,
-               const char* compilation_reason) {
-        // Get the relative path to the input file.
-        const char* relative_input_file_name = get_location_from_path(input_file_name);
+[[ noreturn ]]
+static void run_dex2oat(int zip_fd, int oat_fd, int input_vdex_fd, int output_vdex_fd, int image_fd,
+        const char* input_file_name, const char* output_file_name, int swap_fd,
+        const char* instruction_set, const char* compiler_filter,
+        bool debuggable, bool post_bootcomplete, bool background_job_compile, int profile_fd,
+        const char* class_loader_context, int target_sdk_version, bool enable_hidden_api_checks,
+        bool generate_compact_dex, int dex_metadata_fd, const char* compilation_reason) {
+    // Get the relative path to the input file.
+    const char* relative_input_file_name = get_location_from_path(input_file_name);
 
-        std::string dex2oat_Xms_arg = MapPropertyToArg("dalvik.vm.dex2oat-Xms", "-Xms%s");
-        std::string dex2oat_Xmx_arg = MapPropertyToArg("dalvik.vm.dex2oat-Xmx", "-Xmx%s");
+    std::string dex2oat_Xms_arg = MapPropertyToArg("dalvik.vm.dex2oat-Xms", "-Xms%s");
+    std::string dex2oat_Xmx_arg = MapPropertyToArg("dalvik.vm.dex2oat-Xmx", "-Xmx%s");
 
-        const char* threads_property = post_bootcomplete
-                ? "dalvik.vm.dex2oat-threads"
-                : "dalvik.vm.boot-dex2oat-threads";
-        std::string dex2oat_threads_arg = MapPropertyToArg(threads_property, "-j%s");
+    const char* threads_property = post_bootcomplete
+            ? "dalvik.vm.dex2oat-threads"
+            : "dalvik.vm.boot-dex2oat-threads";
+    std::string dex2oat_threads_arg = MapPropertyToArg(threads_property, "-j%s");
 
-        const std::string dex2oat_isa_features_key =
-                StringPrintf("dalvik.vm.isa.%s.features", instruction_set);
-        std::string instruction_set_features_arg =
-            MapPropertyToArg(dex2oat_isa_features_key, "--instruction-set-features=%s");
+    const std::string dex2oat_isa_features_key =
+            StringPrintf("dalvik.vm.isa.%s.features", instruction_set);
+    std::string instruction_set_features_arg =
+        MapPropertyToArg(dex2oat_isa_features_key, "--instruction-set-features=%s");
 
-        const std::string dex2oat_isa_variant_key =
-                StringPrintf("dalvik.vm.isa.%s.variant", instruction_set);
-        std::string instruction_set_variant_arg =
-            MapPropertyToArg(dex2oat_isa_variant_key, "--instruction-set-variant=%s");
+    const std::string dex2oat_isa_variant_key =
+            StringPrintf("dalvik.vm.isa.%s.variant", instruction_set);
+    std::string instruction_set_variant_arg =
+        MapPropertyToArg(dex2oat_isa_variant_key, "--instruction-set-variant=%s");
 
-        const char* dex2oat_norelocation = "-Xnorelocate";
+    const char *dex2oat_norelocation = "-Xnorelocate";
 
-        const std::string dex2oat_flags = GetProperty("dalvik.vm.dex2oat-flags", "");
-        std::vector<std::string> dex2oat_flags_args = SplitBySpaces(dex2oat_flags);
-        ALOGV("dalvik.vm.dex2oat-flags=%s\n", dex2oat_flags.c_str());
+    const std::string dex2oat_flags = GetProperty("dalvik.vm.dex2oat-flags", "");
+    std::vector<std::string> dex2oat_flags_args = SplitBySpaces(dex2oat_flags);
+    ALOGV("dalvik.vm.dex2oat-flags=%s\n", dex2oat_flags.c_str());
 
-        // If we are booting without the real /data, don't spend time compiling.
-        std::string vold_decrypt = GetProperty("vold.decrypt", "");
-        bool skip_compilation = vold_decrypt == "trigger_restart_min_framework" ||
-                                vold_decrypt == "1";
+    // If we are booting without the real /data, don't spend time compiling.
+    std::string vold_decrypt = GetProperty("vold.decrypt", "");
+    bool skip_compilation = vold_decrypt == "trigger_restart_min_framework" ||
+                            vold_decrypt == "1";
 
-        const std::string resolve_startup_string_arg  =
-                MapPropertyToArg("dalvik.vm.dex2oat-resolve-startup-strings",
-                                 "--resolve-startup-const-strings=%s");
-        const bool generate_debug_info = GetBoolProperty("debug.generate-debug-info", false);
+    const std::string resolve_startup_string_arg  =
+            MapPropertyToArg("dalvik.vm.dex2oat-resolve-startup-strings",
+                             "--resolve-startup-const-strings=%s");
+    const bool generate_debug_info = GetBoolProperty("debug.generate-debug-info", false);
 
-        std::string image_format_arg;
-        if (image_fd >= 0) {
-            image_format_arg = MapPropertyToArg("dalvik.vm.appimageformat", "--image-format=%s");
-        }
-
-        std::string dex2oat_large_app_threshold_arg =
-            MapPropertyToArg("dalvik.vm.dex2oat-very-large", "--very-large-app-threshold=%s");
-
-        // If the runtime was requested to use libartd.so, we'll run dex2oatd, otherwise dex2oat.
-        const char* dex2oat_bin = "/system/bin/dex2oat";
-        constexpr const char* kDex2oatDebugPath = "/system/bin/dex2oatd";
-        // Do not use dex2oatd for release candidates (give dex2oat more soak time).
-        bool is_release = android::base::GetProperty("ro.build.version.codename", "") == "REL";
-        if (is_debug_runtime() ||
-                (background_job_compile && is_debuggable_build() && !is_release)) {
-            if (access(kDex2oatDebugPath, X_OK) == 0) {
-                dex2oat_bin = kDex2oatDebugPath;
-            }
-        }
-
-        bool generate_minidebug_info = kEnableMinidebugInfo &&
-                GetBoolProperty(kMinidebugInfoSystemProperty, kMinidebugInfoSystemPropertyDefault);
-
-        // clang FORTIFY doesn't let us use strlen in constant array bounds, so we
-        // use arraysize instead.
-        std::string zip_fd_arg = StringPrintf("--zip-fd=%d", zip_fd);
-        std::string zip_location_arg = StringPrintf("--zip-location=%s", relative_input_file_name);
-        std::string input_vdex_fd_arg = StringPrintf("--input-vdex-fd=%d", input_vdex_fd);
-        std::string output_vdex_fd_arg = StringPrintf("--output-vdex-fd=%d", output_vdex_fd);
-        std::string oat_fd_arg = StringPrintf("--oat-fd=%d", oat_fd);
-        std::string oat_location_arg = StringPrintf("--oat-location=%s", output_file_name);
-        std::string instruction_set_arg = StringPrintf("--instruction-set=%s", instruction_set);
-        std::string dex2oat_compiler_filter_arg;
-        std::string dex2oat_swap_fd;
-        std::string dex2oat_image_fd;
-        std::string target_sdk_version_arg;
-        if (target_sdk_version != 0) {
-            StringPrintf("-Xtarget-sdk-version:%d", target_sdk_version);
-        }
-        std::string class_loader_context_arg;
-        if (class_loader_context != nullptr) {
-            class_loader_context_arg = StringPrintf("--class-loader-context=%s",
-                                                    class_loader_context);
-        }
-
-        if (swap_fd >= 0) {
-            dex2oat_swap_fd = StringPrintf("--swap-fd=%d", swap_fd);
-        }
-        if (image_fd >= 0) {
-            dex2oat_image_fd = StringPrintf("--app-image-fd=%d", image_fd);
-        }
-
-        // Compute compiler filter.
-        bool have_dex2oat_relocation_skip_flag = false;
-        if (skip_compilation) {
-            dex2oat_compiler_filter_arg = "--compiler-filter=extract";
-            have_dex2oat_relocation_skip_flag = true;
-        } else if (compiler_filter != nullptr) {
-            dex2oat_compiler_filter_arg = StringPrintf("--compiler-filter=%s", compiler_filter);
-        }
-
-        if (dex2oat_compiler_filter_arg.empty()) {
-            dex2oat_compiler_filter_arg = MapPropertyToArg("dalvik.vm.dex2oat-filter",
-                                                           "--compiler-filter=%s");
-        }
-
-        // Check whether all apps should be compiled debuggable.
-        if (!debuggable) {
-            debuggable = GetProperty("dalvik.vm.always_debuggable", "") == "1";
-        }
-        std::string profile_arg;
-        if (profile_fd != -1) {
-            profile_arg = StringPrintf("--profile-file-fd=%d", profile_fd);
-        }
-
-        // Get the directory of the apk to pass as a base classpath directory.
-        std::string base_dir;
-        std::string apk_dir(input_file_name);
-        unsigned long dir_index = apk_dir.rfind('/');
-        bool has_base_dir = dir_index != std::string::npos;
-        if (has_base_dir) {
-            apk_dir = apk_dir.substr(0, dir_index);
-            base_dir = StringPrintf("--classpath-dir=%s", apk_dir.c_str());
-        }
-
-        std::string dex_metadata_fd_arg = "--dm-fd=" + std::to_string(dex_metadata_fd);
-
-        std::string compilation_reason_arg = compilation_reason == nullptr
-                ? ""
-                : std::string("--compilation-reason=") + compilation_reason;
-
-        ALOGV("Running %s in=%s out=%s\n", dex2oat_bin, relative_input_file_name, output_file_name);
-
-        // Disable cdex if update input vdex is true since this combination of options is not
-        // supported.
-        const bool disable_cdex = !generate_compact_dex || (input_vdex_fd == output_vdex_fd);
-
-        AddArg(zip_fd_arg);
-        AddArg(zip_location_arg);
-        AddArg(input_vdex_fd_arg);
-        AddArg(output_vdex_fd_arg);
-        AddArg(oat_fd_arg);
-        AddArg(oat_location_arg);
-        AddArg(instruction_set_arg);
-
-        AddArg(instruction_set_variant_arg);
-        AddArg(instruction_set_features_arg);
-
-        AddRuntimeArg(dex2oat_Xms_arg);
-        AddRuntimeArg(dex2oat_Xmx_arg);
-
-        AddArg(resolve_startup_string_arg);
-        AddArg(dex2oat_compiler_filter_arg);
-        AddArg(dex2oat_threads_arg);
-        AddArg(dex2oat_swap_fd);
-        AddArg(dex2oat_image_fd);
-
-        if (generate_debug_info) {
-            AddArg("--generate-debug-info");
-        }
-        if (debuggable) {
-            AddArg("--debuggable");
-        }
-        AddArg(image_format_arg);
-        AddArg(dex2oat_large_app_threshold_arg);
-
-        if (have_dex2oat_relocation_skip_flag) {
-            AddRuntimeArg(dex2oat_norelocation);
-        }
-        AddArg(profile_arg);
-        AddArg(base_dir);
-        AddArg(class_loader_context_arg);
-        if (generate_minidebug_info) {
-            AddArg(kMinidebugDex2oatFlag);
-        }
-        if (disable_cdex) {
-            AddArg(kDisableCompactDexFlag);
-        }
-        AddArg(target_sdk_version_arg);
-        if (enable_hidden_api_checks) {
-            AddRuntimeArg("-Xhidden-api-checks");
-        }
-
-        if (dex_metadata_fd > -1) {
-            AddArg(dex_metadata_fd_arg);
-        }
-
-        AddArg(compilation_reason_arg);
-
-        // Do not add args after dex2oat_flags, they should override others for debugging.
-        args_.insert(args_.end(), dex2oat_flags_args.begin(), dex2oat_flags_args.end());
-
-        PrepareArgs(dex2oat_bin);
+    std::string image_format_arg;
+    if (image_fd >= 0) {
+        image_format_arg = MapPropertyToArg("dalvik.vm.appimageformat", "--image-format=%s");
     }
-};
+
+    std::string dex2oat_large_app_threshold_arg =
+        MapPropertyToArg("dalvik.vm.dex2oat-very-large", "--very-large-app-threshold=%s");
+
+    // If the runtime was requested to use libartd.so, we'll run dex2oatd, otherwise dex2oat.
+    const char* dex2oat_bin = "/system/bin/dex2oat";
+    constexpr const char* kDex2oatDebugPath = "/system/bin/dex2oatd";
+    // Do not use dex2oatd for release candidates (give dex2oat more soak time).
+    bool is_release = android::base::GetProperty("ro.build.version.codename", "") == "REL";
+    if (is_debug_runtime() || (background_job_compile && is_debuggable_build() && !is_release)) {
+        if (access(kDex2oatDebugPath, X_OK) == 0) {
+            dex2oat_bin = kDex2oatDebugPath;
+        }
+    }
+
+    bool generate_minidebug_info = kEnableMinidebugInfo &&
+            android::base::GetBoolProperty(kMinidebugInfoSystemProperty,
+                                           kMinidebugInfoSystemPropertyDefault);
+
+    // clang FORTIFY doesn't let us use strlen in constant array bounds, so we
+    // use arraysize instead.
+    std::string zip_fd_arg = StringPrintf("--zip-fd=%d", zip_fd);
+    std::string zip_location_arg = StringPrintf("--zip-location=%s", relative_input_file_name);
+    std::string input_vdex_fd_arg = StringPrintf("--input-vdex-fd=%d", input_vdex_fd);
+    std::string output_vdex_fd_arg = StringPrintf("--output-vdex-fd=%d", output_vdex_fd);
+    std::string oat_fd_arg = StringPrintf("--oat-fd=%d", oat_fd);
+    std::string oat_location_arg = StringPrintf("--oat-location=%s", output_file_name);
+    std::string instruction_set_arg = StringPrintf("--instruction-set=%s", instruction_set);
+    std::string dex2oat_compiler_filter_arg;
+    std::string dex2oat_swap_fd;
+    std::string dex2oat_image_fd;
+    std::string target_sdk_version_arg;
+    if (target_sdk_version != 0) {
+        StringPrintf("-Xtarget-sdk-version:%d", target_sdk_version);
+    }
+    std::string class_loader_context_arg;
+    if (class_loader_context != nullptr) {
+        class_loader_context_arg = StringPrintf("--class-loader-context=%s", class_loader_context);
+    }
+
+    if (swap_fd >= 0) {
+        dex2oat_swap_fd = StringPrintf("--swap-fd=%d", swap_fd);
+    }
+    if (image_fd >= 0) {
+        dex2oat_image_fd = StringPrintf("--app-image-fd=%d", image_fd);
+    }
+
+    // Compute compiler filter.
+    bool have_dex2oat_relocation_skip_flag = false;
+    if (skip_compilation) {
+        dex2oat_compiler_filter_arg = "--compiler-filter=extract";
+        have_dex2oat_relocation_skip_flag = true;
+    } else if (compiler_filter != nullptr) {
+        dex2oat_compiler_filter_arg = StringPrintf("--compiler-filter=%s", compiler_filter);
+    }
+
+    if (dex2oat_compiler_filter_arg.empty()) {
+        dex2oat_compiler_filter_arg = MapPropertyToArg("dalvik.vm.dex2oat-filter",
+                                                       "--compiler-filter=%s");
+    }
+
+    // Check whether all apps should be compiled debuggable.
+    if (!debuggable) {
+        debuggable = GetProperty("dalvik.vm.always_debuggable", "") == "1";
+    }
+    std::string profile_arg;
+    if (profile_fd != -1) {
+        profile_arg = StringPrintf("--profile-file-fd=%d", profile_fd);
+    }
+
+    // Get the directory of the apk to pass as a base classpath directory.
+    std::string base_dir;
+    std::string apk_dir(input_file_name);
+    unsigned long dir_index = apk_dir.rfind('/');
+    bool has_base_dir = dir_index != std::string::npos;
+    if (has_base_dir) {
+        apk_dir = apk_dir.substr(0, dir_index);
+        base_dir = StringPrintf("--classpath-dir=%s", apk_dir.c_str());
+    }
+
+    std::string dex_metadata_fd_arg = "--dm-fd=" + std::to_string(dex_metadata_fd);
+
+    std::string compilation_reason_arg = compilation_reason == nullptr
+            ? ""
+            : std::string("--compilation-reason=") + compilation_reason;
+
+    ALOGV("Running %s in=%s out=%s\n", dex2oat_bin, relative_input_file_name, output_file_name);
+
+    // Disable cdex if update input vdex is true since this combination of options is not
+    // supported.
+    const bool disable_cdex = !generate_compact_dex || (input_vdex_fd == output_vdex_fd);
+
+    std::vector<std::string> args = {
+        zip_fd_arg,
+        zip_location_arg,
+        input_vdex_fd_arg,
+        output_vdex_fd_arg,
+        oat_fd_arg,
+        oat_location_arg,
+        instruction_set_arg,
+    };
+    auto add_runtime_arg = [&](const std::string& arg) {
+        args.push_back("--runtime-arg");
+        args.push_back(arg);
+    };
+
+    AddArgIfNonEmpty(instruction_set_variant_arg, &args);
+    AddArgIfNonEmpty(instruction_set_features_arg, &args);
+    if (!dex2oat_Xms_arg.empty()) {
+        add_runtime_arg(dex2oat_Xms_arg);
+    }
+    if (!dex2oat_Xmx_arg.empty()) {
+        add_runtime_arg(dex2oat_Xmx_arg);
+    }
+    AddArgIfNonEmpty(resolve_startup_string_arg, &args);
+    AddArgIfNonEmpty(dex2oat_compiler_filter_arg, &args);
+    AddArgIfNonEmpty(dex2oat_threads_arg, &args);
+    AddArgIfNonEmpty(dex2oat_swap_fd, &args);
+    AddArgIfNonEmpty(dex2oat_image_fd, &args);
+
+    if (generate_debug_info) {
+        args.push_back("--generate-debug-info");
+    }
+    if (debuggable) {
+        args.push_back("--debuggable");
+    }
+    AddArgIfNonEmpty(image_format_arg, &args);
+    AddArgIfNonEmpty(dex2oat_large_app_threshold_arg, &args);
+    args.insert(args.end(), dex2oat_flags_args.begin(), dex2oat_flags_args.end());
+    if (have_dex2oat_relocation_skip_flag) {
+        add_runtime_arg(dex2oat_norelocation);
+    }
+    AddArgIfNonEmpty(profile_arg, &args);
+    AddArgIfNonEmpty(base_dir, &args);
+    AddArgIfNonEmpty(class_loader_context_arg, &args);
+    if (generate_minidebug_info) {
+        args.push_back(kMinidebugDex2oatFlag);
+    }
+    if (disable_cdex) {
+        args.push_back(kDisableCompactDexFlag);
+    }
+    AddArgIfNonEmpty(target_sdk_version_arg, &args);
+    if (enable_hidden_api_checks) {
+        add_runtime_arg("-Xhidden-api-checks");
+    }
+
+    if (dex_metadata_fd > -1) {
+        args.push_back(dex_metadata_fd_arg);
+    }
+
+    AddArgIfNonEmpty(compilation_reason_arg, &args);
+
+    // Do not add after dex2oat_flags, they should override others for debugging.
+
+    ExecVWithArgs(dex2oat_bin, args);
+    PLOG(ERROR) << "execv(" << dex2oat_bin << ") failed";
+    exit(DexoptReturnCodes::kDex2oatExec);
+}
 
 /*
  * Whether dexopt should use a swap file when compiling an APK.
@@ -648,85 +610,74 @@
 static constexpr int PROFMAN_BIN_RETURN_CODE_ERROR_IO = 3;
 static constexpr int PROFMAN_BIN_RETURN_CODE_ERROR_LOCKING = 4;
 
-class RunProfman : public ExecVHelper {
-  public:
-   void SetupArgs(const std::vector<unique_fd>& profile_fds,
-                  const unique_fd& reference_profile_fd,
-                  const std::vector<unique_fd>& apk_fds,
-                  const std::vector<std::string>& dex_locations,
-                  bool copy_and_update) {
-        const char* profman_bin =
-                is_debug_runtime() ? "/system/bin/profmand" : "/system/bin/profman";
+[[ noreturn ]]
+static void run_profman(const std::vector<unique_fd>& profile_fds,
+                        const unique_fd& reference_profile_fd,
+                        const std::vector<unique_fd>* apk_fds,
+                        const std::vector<std::string>* dex_locations,
+                        bool copy_and_update) {
+    const char* profman_bin = is_debug_runtime() ? "/system/bin/profmand" : "/system/bin/profman";
 
-        if (copy_and_update) {
-            CHECK_EQ(1u, profile_fds.size());
-            CHECK_EQ(1u, apk_fds.size());
-        }
-        if (reference_profile_fd != -1) {
-            AddArg("--reference-profile-file-fd=" + std::to_string(reference_profile_fd.get()));
-        }
+    if (copy_and_update) {
+        CHECK_EQ(1u, profile_fds.size());
+        CHECK(apk_fds != nullptr);
+        CHECK_EQ(1u, apk_fds->size());
+    }
+    std::vector<std::string> args;
+    args.push_back("--reference-profile-file-fd=" + std::to_string(reference_profile_fd.get()));
 
-        for (const unique_fd& fd : profile_fds) {
-            AddArg("--profile-file-fd=" + std::to_string(fd.get()));
-        }
-
-        for (const unique_fd& fd : apk_fds) {
-            AddArg("--apk-fd=" + std::to_string(fd.get()));
-        }
-
-        for (const std::string& dex_location : dex_locations) {
-            AddArg("--dex-location=" + dex_location);
-        }
-
-        if (copy_and_update) {
-            AddArg("--copy-and-update-profile-key");
-        }
-
-        // Do not add after dex2oat_flags, they should override others for debugging.
-        PrepareArgs(profman_bin);
+    for (const unique_fd& fd : profile_fds) {
+        args.push_back("--profile-file-fd=" + std::to_string(fd.get()));
     }
 
-    void SetupMerge(const std::vector<unique_fd>& profiles_fd,
-                    const unique_fd& reference_profile_fd,
-                    const std::vector<unique_fd>& apk_fds = std::vector<unique_fd>(),
-                    const std::vector<std::string>& dex_locations = std::vector<std::string>()) {
-        SetupArgs(profiles_fd,
-                    reference_profile_fd,
-                    apk_fds,
-                    dex_locations,
-                    /*copy_and_update=*/false);
+    if (apk_fds != nullptr) {
+        for (const unique_fd& fd : *apk_fds) {
+            args.push_back("--apk-fd=" + std::to_string(fd.get()));
+        }
     }
 
-    void SetupCopyAndUpdate(unique_fd&& profile_fd,
-                            unique_fd&& reference_profile_fd,
-                            unique_fd&& apk_fd,
-                            const std::string& dex_location) {
-        std::vector<unique_fd> profiles_fd;
-        profiles_fd.push_back(std::move(profile_fd));
-        std::vector<unique_fd> apk_fds;
-        profiles_fd.push_back(std::move(apk_fd));
-        std::vector<std::string> dex_locations = {dex_location};
-        SetupArgs(profiles_fd, reference_profile_fd, apk_fds, dex_locations,
-                  /*copy_and_update=*/true);
+    std::vector<std::string> dex_location_args;
+    if (dex_locations != nullptr) {
+        for (const std::string& dex_location : *dex_locations) {
+            args.push_back("--dex-location=" + dex_location);
+        }
     }
 
-    void SetupDump(const std::vector<unique_fd>& profiles_fd,
-                   const unique_fd& reference_profile_fd,
-                   const std::vector<std::string>& dex_locations,
-                   const std::vector<unique_fd>& apk_fds,
-                   const unique_fd& output_fd) {
-        AddArg("--dump-only");
-        AddArg(StringPrintf("--dump-output-to-fd=%d", output_fd.get()));
-        SetupArgs(profiles_fd, reference_profile_fd, apk_fds, dex_locations,
-                  /*copy_and_update=*/false);
+    if (copy_and_update) {
+        args.push_back("--copy-and-update-profile-key");
     }
 
-    void Exec() {
-        ExecVHelper::Exec(DexoptReturnCodes::kProfmanExec);
-    }
-};
+    // Do not add after dex2oat_flags, they should override others for debugging.
 
+    ExecVWithArgs(profman_bin, args);
+    PLOG(ERROR) << "execv(" << profman_bin << ") failed";
+    exit(DexoptReturnCodes::kProfmanExec);   /* only get here on exec failure */
+}
 
+[[ noreturn ]]
+static void run_profman_merge(const std::vector<unique_fd>& profiles_fd,
+                              const unique_fd& reference_profile_fd,
+                              const std::vector<unique_fd>* apk_fds = nullptr,
+                              const std::vector<std::string>* dex_locations = nullptr) {
+    run_profman(profiles_fd, reference_profile_fd, apk_fds, dex_locations,
+            /*copy_and_update*/false);
+}
+
+[[ noreturn ]]
+static void run_profman_copy_and_update(unique_fd&& profile_fd,
+                                        unique_fd&& reference_profile_fd,
+                                        unique_fd&& apk_fd,
+                                        const std::string& dex_location) {
+    std::vector<unique_fd> profiles_fd;
+    profiles_fd.push_back(std::move(profile_fd));
+    std::vector<unique_fd> apk_fds;
+    apk_fds.push_back(std::move(apk_fd));
+    std::vector<std::string> dex_locations;
+    dex_locations.push_back(dex_location);
+
+    run_profman(profiles_fd, reference_profile_fd, &apk_fds, &dex_locations,
+            /*copy_and_update*/true);
+}
 
 // Decides if profile guided compilation is needed or not based on existing profiles.
 // The location is the package name for primary apks or the dex path for secondary dex files.
@@ -746,13 +697,11 @@
         return false;
     }
 
-    RunProfman profman_merge;
-    profman_merge.SetupMerge(profiles_fd, reference_profile_fd);
     pid_t pid = fork();
     if (pid == 0) {
         /* child -- drop privileges before continuing */
         drop_capabilities(uid);
-        profman_merge.Exec();
+        run_profman_merge(profiles_fd, reference_profile_fd);
     }
     /* parent */
     int return_code = wait_child(pid);
@@ -825,6 +774,35 @@
     return analyze_profiles(uid, package_name, profile_name, /*is_secondary_dex*/false);
 }
 
+[[ noreturn ]]
+static void run_profman_dump(const std::vector<unique_fd>& profile_fds,
+                             const unique_fd& reference_profile_fd,
+                             const std::vector<std::string>& dex_locations,
+                             const std::vector<unique_fd>& apk_fds,
+                             const unique_fd& output_fd) {
+    std::vector<std::string> profman_args;
+    static const char* PROFMAN_BIN = "/system/bin/profman";
+    profman_args.push_back("--dump-only");
+    profman_args.push_back(StringPrintf("--dump-output-to-fd=%d", output_fd.get()));
+    if (reference_profile_fd != -1) {
+        profman_args.push_back(StringPrintf("--reference-profile-file-fd=%d",
+                                            reference_profile_fd.get()));
+    }
+    for (size_t i = 0; i < profile_fds.size(); i++) {
+        profman_args.push_back(StringPrintf("--profile-file-fd=%d", profile_fds[i].get()));
+    }
+    for (const std::string& dex_location : dex_locations) {
+        profman_args.push_back(StringPrintf("--dex-location=%s", dex_location.c_str()));
+    }
+    for (size_t i = 0; i < apk_fds.size(); i++) {
+        profman_args.push_back(StringPrintf("--apk-fd=%d", apk_fds[i].get()));
+    }
+
+    ExecVWithArgs(PROFMAN_BIN, profman_args);
+    PLOG(ERROR) << "execv(" << PROFMAN_BIN << ") failed";
+    exit(DexoptReturnCodes::kProfmanExec);   /* only get here on exec failure */
+}
+
 bool dump_profiles(int32_t uid, const std::string& pkgname, const std::string& profile_name,
         const std::string& code_path) {
     std::vector<unique_fd> profile_fds;
@@ -861,13 +839,12 @@
     apk_fds.push_back(std::move(apk_fd));
 
 
-    RunProfman profman_dump;
-    profman_dump.SetupDump(profile_fds, reference_profile_fd, dex_locations, apk_fds, output_fd);
     pid_t pid = fork();
     if (pid == 0) {
         /* child -- drop privileges before continuing */
         drop_capabilities(uid);
-        profman_dump.Exec();
+        run_profman_dump(profile_fds, reference_profile_fd, dex_locations,
+                         apk_fds, output_fd);
     }
     /* parent */
     int return_code = wait_child(pid);
@@ -1439,60 +1416,55 @@
 // The analyzer will check if the dex_file needs to be (re)compiled to match the compiler_filter.
 // If this is for a profile guided compilation, profile_was_updated will tell whether or not
 // the profile has changed.
-class RunDexoptAnalyzer : public ExecVHelper {
- public:
-    RunDexoptAnalyzer(const std::string& dex_file,
-                    int vdex_fd,
-                    int oat_fd,
-                    int zip_fd,
-                    const std::string& instruction_set,
-                    const std::string& compiler_filter,
-                    bool profile_was_updated,
-                    bool downgrade,
-                    const char* class_loader_context) {
-        CHECK_GE(zip_fd, 0);
-        const char* dexoptanalyzer_bin =
-                is_debug_runtime()
-                        ? "/system/bin/dexoptanalyzerd"
-                        : "/system/bin/dexoptanalyzer";
+static void exec_dexoptanalyzer(const std::string& dex_file, int vdex_fd, int oat_fd,
+        int zip_fd, const std::string& instruction_set, const std::string& compiler_filter,
+        bool profile_was_updated, bool downgrade,
+        const char* class_loader_context) {
+    CHECK_GE(zip_fd, 0);
+    const char* dexoptanalyzer_bin =
+            is_debug_runtime()
+                    ? "/system/bin/dexoptanalyzerd"
+                    : "/system/bin/dexoptanalyzer";
 
-        std::string dex_file_arg = "--dex-file=" + dex_file;
-        std::string oat_fd_arg = "--oat-fd=" + std::to_string(oat_fd);
-        std::string vdex_fd_arg = "--vdex-fd=" + std::to_string(vdex_fd);
-        std::string zip_fd_arg = "--zip-fd=" + std::to_string(zip_fd);
-        std::string isa_arg = "--isa=" + instruction_set;
-        std::string compiler_filter_arg = "--compiler-filter=" + compiler_filter;
-        const char* assume_profile_changed = "--assume-profile-changed";
-        const char* downgrade_flag = "--downgrade";
-        std::string class_loader_context_arg = "--class-loader-context=";
-        if (class_loader_context != nullptr) {
-            class_loader_context_arg += class_loader_context;
-        }
-
-        // program name, dex file, isa, filter
-        AddArg(dex_file_arg);
-        AddArg(isa_arg);
-        AddArg(compiler_filter_arg);
-        if (oat_fd >= 0) {
-            AddArg(oat_fd_arg);
-        }
-        if (vdex_fd >= 0) {
-            AddArg(vdex_fd_arg);
-        }
-        AddArg(zip_fd_arg.c_str());
-        if (profile_was_updated) {
-            AddArg(assume_profile_changed);
-        }
-        if (downgrade) {
-            AddArg(downgrade_flag);
-        }
-        if (class_loader_context != nullptr) {
-            AddArg(class_loader_context_arg.c_str());
-        }
-
-        PrepareArgs(dexoptanalyzer_bin);
+    std::string dex_file_arg = "--dex-file=" + dex_file;
+    std::string oat_fd_arg = "--oat-fd=" + std::to_string(oat_fd);
+    std::string vdex_fd_arg = "--vdex-fd=" + std::to_string(vdex_fd);
+    std::string zip_fd_arg = "--zip-fd=" + std::to_string(zip_fd);
+    std::string isa_arg = "--isa=" + instruction_set;
+    std::string compiler_filter_arg = "--compiler-filter=" + compiler_filter;
+    const char* assume_profile_changed = "--assume-profile-changed";
+    const char* downgrade_flag = "--downgrade";
+    std::string class_loader_context_arg = "--class-loader-context=";
+    if (class_loader_context != nullptr) {
+        class_loader_context_arg += class_loader_context;
     }
-};
+
+    // program name, dex file, isa, filter
+    std::vector<std::string> args = {
+      dex_file_arg,
+      isa_arg,
+      compiler_filter_arg,
+    };
+    if (oat_fd >= 0) {
+        args.push_back(oat_fd_arg);
+    }
+    if (vdex_fd >= 0) {
+        args.push_back(vdex_fd_arg);
+    }
+    args.push_back(zip_fd_arg.c_str());
+    if (profile_was_updated) {
+        args.push_back(assume_profile_changed);
+    }
+    if (downgrade) {
+        args.push_back(downgrade_flag);
+    }
+    if (class_loader_context != nullptr) {
+        args.push_back(class_loader_context_arg.c_str());
+    }
+
+    ExecVWithArgs(dexoptanalyzer_bin, args);
+    ALOGE("execv(%s) failed: %s\n", dexoptanalyzer_bin, strerror(errno));
+}
 
 // Prepares the oat dir for the secondary dex files.
 static bool prepare_secondary_dex_oat_dir(const std::string& dex_path, int uid,
@@ -1744,17 +1716,16 @@
                 /*is_secondary_dex*/true);
 
         // Run dexoptanalyzer to get dexopt_needed code. This is not expected to return.
-        // Note that we do not do it before the fork since opening the files is required to happen
-        // after forking.
-        RunDexoptAnalyzer run_dexopt_analyzer(dex_path,
-                                              vdex_file_fd.get(),
-                                              oat_file_fd.get(),
-                                              zip_fd.get(),
-                                              instruction_set,
-                                              compiler_filter, profile_was_updated,
-                                              downgrade,
-                                              class_loader_context);
-        run_dexopt_analyzer.Exec(kSecondaryDexDexoptAnalyzerSkippedFailExec);
+        exec_dexoptanalyzer(dex_path,
+                            vdex_file_fd.get(),
+                            oat_file_fd.get(),
+                            zip_fd.get(),
+                            instruction_set,
+                            compiler_filter, profile_was_updated,
+                            downgrade,
+                            class_loader_context);
+        PLOG(ERROR) << "Failed to exec dexoptanalyzer";
+        _exit(kSecondaryDexDexoptAnalyzerSkippedFailExec);
     }
 
     /* parent */
@@ -1923,27 +1894,6 @@
 
     LOG(VERBOSE) << "DexInv: --- BEGIN '" << dex_path << "' ---";
 
-    RunDex2Oat runner(input_fd.get(),
-                      out_oat_fd.get(),
-                      in_vdex_fd.get(),
-                      out_vdex_fd.get(),
-                      image_fd.get(),
-                      dex_path,
-                      out_oat_path,
-                      swap_fd.get(),
-                      instruction_set,
-                      compiler_filter,
-                      debuggable,
-                      boot_complete,
-                      background_job_compile,
-                      reference_profile_fd.get(),
-                      class_loader_context,
-                      target_sdk_version,
-                      enable_hidden_api_checks,
-                      generate_compact_dex,
-                      dex_metadata_fd.get(),
-                      compilation_reason);
-
     pid_t pid = fork();
     if (pid == 0) {
         /* child -- drop privileges before continuing */
@@ -1955,7 +1905,26 @@
             _exit(DexoptReturnCodes::kFlock);
         }
 
-        runner.Exec(DexoptReturnCodes::kDex2oatExec);
+        run_dex2oat(input_fd.get(),
+                    out_oat_fd.get(),
+                    in_vdex_fd.get(),
+                    out_vdex_fd.get(),
+                    image_fd.get(),
+                    dex_path,
+                    out_oat_path,
+                    swap_fd.get(),
+                    instruction_set,
+                    compiler_filter,
+                    debuggable,
+                    boot_complete,
+                    background_job_compile,
+                    reference_profile_fd.get(),
+                    class_loader_context,
+                    target_sdk_version,
+                    enable_hidden_api_checks,
+                    generate_compact_dex,
+                    dex_metadata_fd.get(),
+                    compilation_reason);
     } else {
         int res = wait_child(pid);
         if (res == 0) {
@@ -2519,13 +2488,11 @@
         return false;
     }
 
-    RunProfman args;
-    args.SetupMerge(profiles_fd, snapshot_fd, apk_fds, dex_locations);
     pid_t pid = fork();
     if (pid == 0) {
         /* child -- drop privileges before continuing */
         drop_capabilities(app_shared_gid);
-        args.Exec();
+        run_profman_merge(profiles_fd, snapshot_fd, &apk_fds, &dex_locations);
     }
 
     /* parent */
@@ -2605,8 +2572,6 @@
                 profiles_fd.push_back(std::move(fd));
             }
         }
-        RunProfman args;
-        args.SetupMerge(profiles_fd, snapshot_fd, apk_fds, dex_locations);
         pid_t pid = fork();
         if (pid == 0) {
             /* child -- drop privileges before continuing */
@@ -2614,7 +2579,7 @@
 
             // The introduction of new access flags into boot jars causes them to
             // fail dex file verification.
-            args.Exec();
+            run_profman_merge(profiles_fd, snapshot_fd, &apk_fds, &dex_locations);
         }
 
         /* parent */
@@ -2668,11 +2633,6 @@
         return false;
     }
 
-    RunProfman args;
-    args.SetupCopyAndUpdate(std::move(dex_metadata_fd),
-                            std::move(ref_profile_fd),
-                            std::move(apk_fd),
-                            code_path);
     pid_t pid = fork();
     if (pid == 0) {
         /* child -- drop privileges before continuing */
@@ -2680,7 +2640,10 @@
         drop_capabilities(app_shared_gid);
 
         // The copy and update takes ownership over the fds.
-        args.Exec();
+        run_profman_copy_and_update(std::move(dex_metadata_fd),
+                                    std::move(ref_profile_fd),
+                                    std::move(apk_fd),
+                                    code_path);
     }
 
     /* parent */
diff --git a/cmds/surfacereplayer/proto/src/trace.proto b/cmds/surfacereplayer/proto/src/trace.proto
index 68ddeb0..c169b76 100644
--- a/cmds/surfacereplayer/proto/src/trace.proto
+++ b/cmds/surfacereplayer/proto/src/trace.proto
@@ -160,7 +160,7 @@
 message DisplayCreation {
     required int32     id                = 1;
     required string    name              = 2;
-    required int32     type              = 3;
+    optional uint64    display_id        = 3;
     required bool      is_secure         = 4;
 }
 
diff --git a/cmds/surfacereplayer/replayer/trace_creator/trace_creator.py b/cmds/surfacereplayer/replayer/trace_creator/trace_creator.py
index a892e46..d63d97f 100644
--- a/cmds/surfacereplayer/replayer/trace_creator/trace_creator.py
+++ b/cmds/surfacereplayer/replayer/trace_creator/trace_creator.py
@@ -172,7 +172,7 @@
 def display_create(increment):
     increment.display_creation.id = int(input("Enter id: "))
     increment.display_creation.name = str(raw_input("Enter name: "))
-    increment.display_creation.type = int(input("Enter type: "))
+    increment.display_creation.display_id = int(input("Enter display ID: "))
     increment.display_creation.is_secure = bool(input("Enter if secure: "))
 
 def display_delete(increment):
diff --git a/libs/binder/include/binder/IBinder.h b/libs/binder/include/binder/IBinder.h
index 14edcbe..1674516 100644
--- a/libs/binder/include/binder/IBinder.h
+++ b/libs/binder/include/binder/IBinder.h
@@ -47,7 +47,7 @@
  * (method calls, property get and set) is down through a low-level
  * protocol implemented on top of the transact() API.
  */
-class IBinder : public virtual RefBase
+class [[clang::lto_visibility_public]] IBinder : public virtual RefBase
 {
 public:
     enum {
diff --git a/libs/binder/ndk/include_ndk/android/binder_ibinder.h b/libs/binder/ndk/include_ndk/android/binder_ibinder.h
index 5222bda..d711ad8 100644
--- a/libs/binder/ndk/include_ndk/android/binder_ibinder.h
+++ b/libs/binder/ndk/include_ndk/android/binder_ibinder.h
@@ -287,6 +287,9 @@
  * For debugging only!
  *
  * \param binder the binder object to retrieve the refcount of.
+ *
+ * \return the number of strong-refs on this binder in this process. If binder is null, this will be
+ * -1.
  */
 int32_t AIBinder_debugGetRefCount(AIBinder* binder) __INTRODUCED_IN(29);
 
diff --git a/libs/binder/ndk/include_ndk/android/binder_parcel.h b/libs/binder/ndk/include_ndk/android/binder_parcel.h
index a5842f7..4ee1b67 100644
--- a/libs/binder/ndk/include_ndk/android/binder_parcel.h
+++ b/libs/binder/ndk/include_ndk/android/binder_parcel.h
@@ -109,11 +109,11 @@
  *
  * \param arrayData some external representation of an array.
  * \param index the index at which a string should be allocated.
- * \param outLength an out parameter for the length of the string (not including the
- * null-terminator)
+ * \param outLength an out parameter for the length of the string at the specified index. This
+ * should not include the length for a null-terminator if there is one.
  *
- * \param a null-terminated buffer of size 'outLength + 1' representing the string at the provided
- * index including the null-terminator.
+ * \param a buffer of size outLength or more representing the string at the provided index. This is
+ * not required to be null-terminated.
  */
 typedef const char* (*AParcel_stringArrayElementGetter)(const void* arrayData, size_t index,
                                                         size_t* outLength);
@@ -382,8 +382,7 @@
  * Writes utf-8 string value to the next location in a non-null parcel.
  *
  * \param parcel the parcel to write to.
- * \param string the null-terminated string to write to the parcel. The buffer including the null
- * terminator should be of size 'length' + 1.
+ * \param string the null-terminated string to write to the parcel, at least of size 'length'.
  * \param length the length of the string to be written.
  *
  * \return STATUS_OK on successful write.
diff --git a/libs/gui/Android.bp b/libs/gui/Android.bp
index 98264ac..7677c3a 100644
--- a/libs/gui/Android.bp
+++ b/libs/gui/Android.bp
@@ -107,6 +107,7 @@
         "IProducerListener.cpp",
         "ISurfaceComposer.cpp",
         "ISurfaceComposerClient.cpp",
+        "ITransactionCompletedListener.cpp",
         "LayerDebugInfo.cpp",
         "LayerState.cpp",
         "OccupancyTracker.cpp",
diff --git a/libs/gui/ISurfaceComposer.cpp b/libs/gui/ISurfaceComposer.cpp
index accf72c..69e5379 100644
--- a/libs/gui/ISurfaceComposer.cpp
+++ b/libs/gui/ISurfaceComposer.cpp
@@ -634,10 +634,10 @@
             if (count > data.dataSize()) {
                 return BAD_VALUE;
             }
-            ComposerState s;
             Vector<ComposerState> state;
             state.setCapacity(count);
             for (size_t i = 0; i < count; i++) {
+                ComposerState s;
                 if (s.read(data) == BAD_VALUE) {
                     return BAD_VALUE;
                 }
diff --git a/libs/gui/ITransactionCompletedListener.cpp b/libs/gui/ITransactionCompletedListener.cpp
new file mode 100644
index 0000000..95b1038
--- /dev/null
+++ b/libs/gui/ITransactionCompletedListener.cpp
@@ -0,0 +1,167 @@
+/*
+ * Copyright 2018 The Android Open Source Project
+ *
+ * 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.
+ */
+
+#define LOG_TAG "ITransactionCompletedListener"
+//#define LOG_NDEBUG 0
+
+#include <gui/ITransactionCompletedListener.h>
+
+namespace android {
+
+namespace { // Anonymous
+
+enum class Tag : uint32_t {
+    ON_TRANSACTION_COMPLETED = IBinder::FIRST_CALL_TRANSACTION,
+    LAST = ON_TRANSACTION_COMPLETED,
+};
+
+} // Anonymous namespace
+
+status_t SurfaceStats::writeToParcel(Parcel* output) const {
+    status_t err = output->writeStrongBinder(surfaceControl);
+    if (err != NO_ERROR) {
+        return err;
+    }
+    err = output->writeInt64(acquireTime);
+    if (err != NO_ERROR) {
+        return err;
+    }
+    return output->writeBool(releasePreviousBuffer);
+}
+
+status_t SurfaceStats::readFromParcel(const Parcel* input) {
+    status_t err = input->readStrongBinder(&surfaceControl);
+    if (err != NO_ERROR) {
+        return err;
+    }
+    err = input->readInt64(&acquireTime);
+    if (err != NO_ERROR) {
+        return err;
+    }
+    return input->readBool(&releasePreviousBuffer);
+}
+
+status_t TransactionStats::writeToParcel(Parcel* output) const {
+    status_t err = output->writeInt64(latchTime);
+    if (err != NO_ERROR) {
+        return err;
+    }
+    err = output->writeInt64(presentTime);
+    if (err != NO_ERROR) {
+        return err;
+    }
+    return output->writeParcelableVector(surfaceStats);
+}
+
+status_t TransactionStats::readFromParcel(const Parcel* input) {
+    status_t err = input->readInt64(&latchTime);
+    if (err != NO_ERROR) {
+        return err;
+    }
+    err = input->readInt64(&presentTime);
+    if (err != NO_ERROR) {
+        return err;
+    }
+    return input->readParcelableVector(&surfaceStats);
+}
+
+status_t ListenerStats::writeToParcel(Parcel* output) const {
+    status_t err = output->writeInt32(static_cast<int32_t>(transactionStats.size()));
+    if (err != NO_ERROR) {
+        return err;
+    }
+
+    for (const auto& [callbackIds, stats] : transactionStats) {
+        err = output->writeParcelable(stats);
+        if (err != NO_ERROR) {
+            return err;
+        }
+        err = output->writeInt64Vector(callbackIds);
+        if (err != NO_ERROR) {
+            return err;
+        }
+    }
+    return NO_ERROR;
+}
+
+status_t ListenerStats::readFromParcel(const Parcel* input) {
+    int32_t transactionStats_size = input->readInt32();
+
+    for (int i = 0; i < transactionStats_size; i++) {
+        TransactionStats stats;
+        std::vector<CallbackId> callbackIds;
+
+        status_t err = input->readParcelable(&stats);
+        if (err != NO_ERROR) {
+            return err;
+        }
+        err = input->readInt64Vector(&callbackIds);
+        if (err != NO_ERROR) {
+            return err;
+        }
+
+        transactionStats.emplace(callbackIds, stats);
+    }
+    return NO_ERROR;
+}
+
+ListenerStats ListenerStats::createEmpty(const sp<ITransactionCompletedListener>& listener,
+                                         const std::unordered_set<CallbackId>& callbackIds) {
+    ListenerStats listenerStats;
+    listenerStats.listener = listener;
+    TransactionStats transactionStats;
+    listenerStats.transactionStats.emplace(std::piecewise_construct,
+                                           std::forward_as_tuple(callbackIds.begin(),
+                                                                 callbackIds.end()),
+                                           std::forward_as_tuple(transactionStats));
+    return listenerStats;
+}
+
+class BpTransactionCompletedListener : public SafeBpInterface<ITransactionCompletedListener> {
+public:
+    explicit BpTransactionCompletedListener(const sp<IBinder>& impl)
+          : SafeBpInterface<ITransactionCompletedListener>(impl, "BpTransactionCompletedListener") {
+    }
+
+    ~BpTransactionCompletedListener() override;
+
+    void onTransactionCompleted(ListenerStats stats) override {
+        callRemoteAsync<decltype(&ITransactionCompletedListener::
+                                         onTransactionCompleted)>(Tag::ON_TRANSACTION_COMPLETED,
+                                                                  stats);
+    }
+};
+
+// Out-of-line virtual method definitions to trigger vtable emission in this translation unit (see
+// clang warning -Wweak-vtables)
+BpTransactionCompletedListener::~BpTransactionCompletedListener() = default;
+
+IMPLEMENT_META_INTERFACE(TransactionCompletedListener, "android.gui.ITransactionComposerListener");
+
+status_t BnTransactionCompletedListener::onTransact(uint32_t code, const Parcel& data,
+                                                    Parcel* reply, uint32_t flags) {
+    if (code < IBinder::FIRST_CALL_TRANSACTION || code > static_cast<uint32_t>(Tag::LAST)) {
+        return BBinder::onTransact(code, data, reply, flags);
+    }
+    auto tag = static_cast<Tag>(code);
+    switch (tag) {
+        case Tag::ON_TRANSACTION_COMPLETED:
+            return callLocalAsync(data, reply,
+                                  &ITransactionCompletedListener::onTransactionCompleted);
+    }
+}
+
+}; // namespace android
diff --git a/libs/gui/LayerState.cpp b/libs/gui/LayerState.cpp
index 2b0a461..7b71b39 100644
--- a/libs/gui/LayerState.cpp
+++ b/libs/gui/LayerState.cpp
@@ -80,6 +80,13 @@
     memcpy(output.writeInplace(16 * sizeof(float)),
            colorTransform.asArray(), 16 * sizeof(float));
 
+    if (output.writeVectorSize(listenerCallbacks) == NO_ERROR) {
+        for (const auto& [listener, callbackIds] : listenerCallbacks) {
+            output.writeStrongBinder(IInterface::asBinder(listener));
+            output.writeInt64Vector(callbackIds);
+        }
+    }
+
     return NO_ERROR;
 }
 
@@ -135,6 +142,14 @@
 
     colorTransform = mat4(static_cast<const float*>(input.readInplace(16 * sizeof(float))));
 
+    int32_t listenersSize = input.readInt32();
+    for (int32_t i = 0; i < listenersSize; i++) {
+        auto listener = interface_cast<ITransactionCompletedListener>(input.readStrongBinder());
+        std::vector<CallbackId> callbackIds;
+        input.readInt64Vector(&callbackIds);
+        listenerCallbacks.emplace_back(listener, callbackIds);
+    }
+
     return NO_ERROR;
 }
 
@@ -323,6 +338,10 @@
         what |= eColorTransformChanged;
         colorTransform = other.colorTransform;
     }
+    if (other.what & eListenerCallbacksChanged) {
+        what |= eListenerCallbacksChanged;
+        listenerCallbacks = other.listenerCallbacks;
+    }
 
     if ((other.what & what) != other.what) {
         ALOGE("Unmerged SurfaceComposer Transaction properties. LayerState::merge needs updating? "
diff --git a/libs/gui/SurfaceComposerClient.cpp b/libs/gui/SurfaceComposerClient.cpp
index e5a2454..e10bda4 100644
--- a/libs/gui/SurfaceComposerClient.cpp
+++ b/libs/gui/SurfaceComposerClient.cpp
@@ -25,7 +25,9 @@
 #include <utils/String8.h>
 #include <utils/threads.h>
 
+#include <binder/IPCThreadState.h>
 #include <binder/IServiceManager.h>
+#include <binder/ProcessState.h>
 
 #include <system/graphics.h>
 
@@ -98,6 +100,61 @@
 
 // ---------------------------------------------------------------------------
 
+// TransactionCompletedListener does not use ANDROID_SINGLETON_STATIC_INSTANCE because it needs
+// to be able to return a sp<> to its instance to pass to SurfaceFlinger.
+// ANDROID_SINGLETON_STATIC_INSTANCE only allows a reference to an instance.
+
+// 0 is an invalid callback id
+TransactionCompletedListener::TransactionCompletedListener() : mCallbackIdCounter(1) {}
+
+CallbackId TransactionCompletedListener::getNextIdLocked() {
+    return mCallbackIdCounter++;
+}
+
+sp<TransactionCompletedListener> TransactionCompletedListener::getInstance() {
+    static sp<TransactionCompletedListener> sInstance = new TransactionCompletedListener;
+    return sInstance;
+}
+
+sp<ITransactionCompletedListener> TransactionCompletedListener::getIInstance() {
+    return static_cast<sp<ITransactionCompletedListener>>(getInstance());
+}
+
+void TransactionCompletedListener::startListeningLocked() {
+    if (mListening) {
+        return;
+    }
+    ProcessState::self()->startThreadPool();
+    mListening = true;
+}
+
+CallbackId TransactionCompletedListener::addCallback(const TransactionCompletedCallback& callback) {
+    std::lock_guard<std::mutex> lock(mMutex);
+    startListeningLocked();
+
+    CallbackId callbackId = getNextIdLocked();
+    mCallbacks.emplace(callbackId, callback);
+    return callbackId;
+}
+
+void TransactionCompletedListener::onTransactionCompleted(ListenerStats listenerStats) {
+    std::lock_guard lock(mMutex);
+
+    for (const auto& [callbackIds, transactionStats] : listenerStats.transactionStats) {
+        for (auto callbackId : callbackIds) {
+            const auto& callback = mCallbacks[callbackId];
+            if (!callback) {
+                ALOGE("cannot call null callback function, skipping");
+                continue;
+            }
+            callback(transactionStats);
+            mCallbacks.erase(callbackId);
+        }
+    }
+}
+
+// ---------------------------------------------------------------------------
+
 SurfaceComposerClient::Transaction::Transaction(const Transaction& other) :
     mForceSynchronous(other.mForceSynchronous),
     mTransactionNestCount(other.mTransactionNestCount),
@@ -127,6 +184,17 @@
     }
     other.mDisplayStates.clear();
 
+    for (const auto& [listener, callbackInfo] : other.mListenerCallbacks) {
+        auto& [callbackIds, surfaceControls] = callbackInfo;
+        mListenerCallbacks[listener].callbackIds.insert(std::make_move_iterator(
+                                                                callbackIds.begin()),
+                                                        std::make_move_iterator(callbackIds.end()));
+        mListenerCallbacks[listener]
+                .surfaceControls.insert(std::make_move_iterator(surfaceControls.begin()),
+                                        std::make_move_iterator(surfaceControls.end()));
+    }
+    other.mListenerCallbacks.clear();
+
     return *this;
 }
 
@@ -137,6 +205,32 @@
 
     sp<ISurfaceComposer> sf(ComposerService::getComposerService());
 
+    // For every listener with registered callbacks
+    for (const auto& [listener, callbackInfo] : mListenerCallbacks) {
+        auto& [callbackIds, surfaceControls] = callbackInfo;
+        if (callbackIds.empty()) {
+            continue;
+        }
+
+        // If the listener does not have any SurfaceControls set on this Transaction, send the
+        // callback now
+        if (surfaceControls.empty()) {
+            listener->onTransactionCompleted(ListenerStats::createEmpty(listener, callbackIds));
+        }
+
+        // If the listener has any SurfaceControls set on this Transaction update the surface state
+        for (const auto& surfaceControl : surfaceControls) {
+            layer_state_t* s = getLayerState(surfaceControl);
+            if (!s) {
+                ALOGE("failed to get layer state");
+                continue;
+            }
+            s->what |= layer_state_t::eListenerCallbacksChanged;
+            s->listenerCallbacks.emplace_back(listener, std::move(callbackIds));
+        }
+    }
+    mListenerCallbacks.clear();
+
     Vector<ComposerState> composerStates;
     Vector<DisplayState> displayStates;
     uint32_t flags = 0;
@@ -206,6 +300,11 @@
     return &(mComposerStates[sc].state);
 }
 
+void SurfaceComposerClient::Transaction::registerSurfaceControlForCallback(
+        const sp<SurfaceControl>& sc) {
+    mListenerCallbacks[TransactionCompletedListener::getIInstance()].surfaceControls.insert(sc);
+}
+
 SurfaceComposerClient::Transaction& SurfaceComposerClient::Transaction::setPosition(
         const sp<SurfaceControl>& sc, float x, float y) {
     layer_state_t* s = getLayerState(sc);
@@ -216,6 +315,8 @@
     s->what |= layer_state_t::ePositionChanged;
     s->x = x;
     s->y = y;
+
+    registerSurfaceControlForCallback(sc);
     return *this;
 }
 
@@ -240,6 +341,7 @@
     s->w = w;
     s->h = h;
 
+    registerSurfaceControlForCallback(sc);
     return *this;
 }
 
@@ -252,6 +354,8 @@
     }
     s->what |= layer_state_t::eLayerChanged;
     s->z = z;
+
+    registerSurfaceControlForCallback(sc);
     return *this;
 }
 
@@ -264,6 +368,8 @@
     s->what |= layer_state_t::eRelativeLayerChanged;
     s->relativeLayerHandle = relativeTo;
     s->z = z;
+
+    registerSurfaceControlForCallback(sc);
     return *this;
 }
 
@@ -283,6 +389,8 @@
     s->flags &= ~mask;
     s->flags |= (flags & mask);
     s->mask |= mask;
+
+    registerSurfaceControlForCallback(sc);
     return *this;
 }
 
@@ -296,6 +404,8 @@
     }
     s->what |= layer_state_t::eTransparentRegionChanged;
     s->transparentRegion = transparentRegion;
+
+    registerSurfaceControlForCallback(sc);
     return *this;
 }
 
@@ -308,6 +418,8 @@
     }
     s->what |= layer_state_t::eAlphaChanged;
     s->alpha = alpha;
+
+    registerSurfaceControlForCallback(sc);
     return *this;
 }
 
@@ -320,6 +432,8 @@
     }
     s->what |= layer_state_t::eLayerStackChanged;
     s->layerStack = layerStack;
+
+    registerSurfaceControlForCallback(sc);
     return *this;
 }
 
@@ -338,6 +452,8 @@
     matrix.dsdy = dsdy;
     matrix.dtdy = dtdy;
     s->matrix = matrix;
+
+    registerSurfaceControlForCallback(sc);
     return *this;
 }
 
@@ -350,6 +466,8 @@
     }
     s->what |= layer_state_t::eCropChanged_legacy;
     s->crop_legacy = crop;
+
+    registerSurfaceControlForCallback(sc);
     return *this;
 }
 
@@ -365,6 +483,8 @@
     s->what |= layer_state_t::eDeferTransaction_legacy;
     s->barrierHandle_legacy = handle;
     s->frameNumber_legacy = frameNumber;
+
+    registerSurfaceControlForCallback(sc);
     return *this;
 }
 
@@ -380,6 +500,8 @@
     s->what |= layer_state_t::eDeferTransaction_legacy;
     s->barrierGbp_legacy = barrierSurface->getIGraphicBufferProducer();
     s->frameNumber_legacy = frameNumber;
+
+    registerSurfaceControlForCallback(sc);
     return *this;
 }
 
@@ -393,6 +515,8 @@
     }
     s->what |= layer_state_t::eReparentChildren;
     s->reparentHandle = newParentHandle;
+
+    registerSurfaceControlForCallback(sc);
     return *this;
 }
 
@@ -406,6 +530,8 @@
     }
     s->what |= layer_state_t::eReparent;
     s->parentHandleForChild = newParentHandle;
+
+    registerSurfaceControlForCallback(sc);
     return *this;
 }
 
@@ -419,6 +545,8 @@
     }
     s->what |= layer_state_t::eColorChanged;
     s->color = color;
+
+    registerSurfaceControlForCallback(sc);
     return *this;
 }
 
@@ -431,6 +559,8 @@
     }
     s->what |= layer_state_t::eTransformChanged;
     s->transform = transform;
+
+    registerSurfaceControlForCallback(sc);
     return *this;
 }
 
@@ -444,6 +574,8 @@
     }
     s->what |= layer_state_t::eTransformToDisplayInverseChanged;
     s->transformToDisplayInverse = transformToDisplayInverse;
+
+    registerSurfaceControlForCallback(sc);
     return *this;
 }
 
@@ -456,6 +588,8 @@
     }
     s->what |= layer_state_t::eCropChanged;
     s->crop = crop;
+
+    registerSurfaceControlForCallback(sc);
     return *this;
 }
 
@@ -468,6 +602,8 @@
     }
     s->what |= layer_state_t::eBufferChanged;
     s->buffer = buffer;
+
+    registerSurfaceControlForCallback(sc);
     return *this;
 }
 
@@ -480,6 +616,8 @@
     }
     s->what |= layer_state_t::eAcquireFenceChanged;
     s->acquireFence = fence;
+
+    registerSurfaceControlForCallback(sc);
     return *this;
 }
 
@@ -492,6 +630,8 @@
     }
     s->what |= layer_state_t::eDataspaceChanged;
     s->dataspace = dataspace;
+
+    registerSurfaceControlForCallback(sc);
     return *this;
 }
 
@@ -504,6 +644,8 @@
     }
     s->what |= layer_state_t::eHdrMetadataChanged;
     s->hdrMetadata = hdrMetadata;
+
+    registerSurfaceControlForCallback(sc);
     return *this;
 }
 
@@ -516,6 +658,8 @@
     }
     s->what |= layer_state_t::eSurfaceDamageRegionChanged;
     s->surfaceDamageRegion = surfaceDamageRegion;
+
+    registerSurfaceControlForCallback(sc);
     return *this;
 }
 
@@ -528,6 +672,8 @@
     }
     s->what |= layer_state_t::eApiChanged;
     s->api = api;
+
+    registerSurfaceControlForCallback(sc);
     return *this;
 }
 
@@ -540,6 +686,22 @@
     }
     s->what |= layer_state_t::eSidebandStreamChanged;
     s->sidebandStream = sidebandStream;
+
+    registerSurfaceControlForCallback(sc);
+    return *this;
+}
+
+SurfaceComposerClient::Transaction&
+SurfaceComposerClient::Transaction::addTransactionCompletedCallback(
+        TransactionCompletedCallbackTakesContext callback, void* callbackContext) {
+    auto listener = TransactionCompletedListener::getInstance();
+
+    auto callbackWithContext = std::bind(callback, callbackContext, std::placeholders::_1);
+
+    CallbackId callbackId = listener->addCallback(callbackWithContext);
+
+    mListenerCallbacks[TransactionCompletedListener::getIInstance()].callbackIds.emplace(
+            callbackId);
     return *this;
 }
 
@@ -550,6 +712,8 @@
         mStatus = BAD_INDEX;
     }
     s->what |= layer_state_t::eDetachChildren;
+
+    registerSurfaceControlForCallback(sc);
     return *this;
 }
 
@@ -577,6 +741,8 @@
 
     s->what |= layer_state_t::eOverrideScalingModeChanged;
     s->overrideScalingMode = overrideScalingMode;
+
+    registerSurfaceControlForCallback(sc);
     return *this;
 }
 
@@ -588,6 +754,8 @@
         return *this;
     }
     s->what |= layer_state_t::eGeometryAppliesWithResize;
+
+    registerSurfaceControlForCallback(sc);
     return *this;
 }
 
@@ -611,6 +779,8 @@
     }
     s->what |= layer_state_t::eColorTransformChanged;
     s->colorTransform = mat4(matrix, translation);
+
+    registerSurfaceControlForCallback(sc);
     return *this;
 }
 
diff --git a/libs/gui/include/gui/ITransactionCompletedListener.h b/libs/gui/include/gui/ITransactionCompletedListener.h
new file mode 100644
index 0000000..5c41c21
--- /dev/null
+++ b/libs/gui/include/gui/ITransactionCompletedListener.h
@@ -0,0 +1,115 @@
+/*
+ * Copyright 2018 The Android Open Source Project
+ *
+ * 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.
+ */
+
+#pragma once
+
+#include <binder/IInterface.h>
+#include <binder/Parcel.h>
+#include <binder/Parcelable.h>
+#include <binder/SafeInterface.h>
+
+#include <utils/Timers.h>
+
+#include <cstdint>
+#include <unordered_map>
+#include <unordered_set>
+
+namespace android {
+
+class ITransactionCompletedListener;
+
+using CallbackId = int64_t;
+
+struct CallbackIdsHash {
+    // CallbackId vectors have several properties that let us get away with this simple hash.
+    // 1) CallbackIds are never 0 so if something has gone wrong and our CallbackId vector is
+    // empty we can still hash 0.
+    // 2) CallbackId vectors for the same listener either are identical or contain none of the
+    // same members. It is sufficient to just check the first CallbackId in the vectors. If
+    // they match, they are the same. If they do not match, they are not the same.
+    std::size_t operator()(const std::vector<CallbackId> callbackIds) const {
+        return std::hash<CallbackId>{}((callbackIds.size() == 0) ? 0 : callbackIds.front());
+    }
+};
+
+class SurfaceStats : public Parcelable {
+public:
+    status_t writeToParcel(Parcel* output) const override;
+    status_t readFromParcel(const Parcel* input) override;
+
+    SurfaceStats() = default;
+    SurfaceStats(const sp<IBinder>& sc, nsecs_t time, bool releasePrevBuffer)
+          : surfaceControl(sc), acquireTime(time), releasePreviousBuffer(releasePrevBuffer) {}
+
+    sp<IBinder> surfaceControl;
+    nsecs_t acquireTime = -1;
+    bool releasePreviousBuffer = false;
+};
+
+class TransactionStats : public Parcelable {
+public:
+    status_t writeToParcel(Parcel* output) const override;
+    status_t readFromParcel(const Parcel* input) override;
+
+    nsecs_t latchTime = -1;
+    nsecs_t presentTime = -1;
+    std::vector<SurfaceStats> surfaceStats;
+};
+
+class ListenerStats : public Parcelable {
+public:
+    status_t writeToParcel(Parcel* output) const override;
+    status_t readFromParcel(const Parcel* input) override;
+
+    static ListenerStats createEmpty(const sp<ITransactionCompletedListener>& listener,
+                                     const std::unordered_set<CallbackId>& callbackIds);
+
+    sp<ITransactionCompletedListener> listener;
+    std::unordered_map<std::vector<CallbackId>, TransactionStats, CallbackIdsHash> transactionStats;
+};
+
+class ITransactionCompletedListener : public IInterface {
+public:
+    DECLARE_META_INTERFACE(TransactionCompletedListener)
+
+    virtual void onTransactionCompleted(ListenerStats stats) = 0;
+};
+
+class BnTransactionCompletedListener : public SafeBnInterface<ITransactionCompletedListener> {
+public:
+    BnTransactionCompletedListener()
+          : SafeBnInterface<ITransactionCompletedListener>("BnTransactionCompletedListener") {}
+
+    status_t onTransact(uint32_t code, const Parcel& data, Parcel* reply,
+                        uint32_t flags = 0) override;
+};
+
+class ListenerCallbacks {
+public:
+    ListenerCallbacks(const sp<ITransactionCompletedListener>& listener,
+                      const std::unordered_set<CallbackId>& callbacks)
+          : transactionCompletedListener(listener),
+            callbackIds(callbacks.begin(), callbacks.end()) {}
+
+    ListenerCallbacks(const sp<ITransactionCompletedListener>& listener,
+                      const std::vector<CallbackId>& ids)
+          : transactionCompletedListener(listener), callbackIds(ids) {}
+
+    sp<ITransactionCompletedListener> transactionCompletedListener;
+    std::vector<CallbackId> callbackIds;
+};
+
+} // namespace android
diff --git a/libs/gui/include/gui/LayerState.h b/libs/gui/include/gui/LayerState.h
index e06e2b1..ddbac7b 100644
--- a/libs/gui/include/gui/LayerState.h
+++ b/libs/gui/include/gui/LayerState.h
@@ -23,6 +23,7 @@
 #include <utils/Errors.h>
 
 #include <gui/IGraphicBufferProducer.h>
+#include <gui/ITransactionCompletedListener.h>
 #include <math/mat4.h>
 #include <math/vec3.h>
 #include <ui/GraphicTypes.h>
@@ -74,6 +75,7 @@
         eApiChanged = 0x04000000,
         eSidebandStreamChanged = 0x08000000,
         eColorTransformChanged = 0x10000000,
+        eListenerCallbacksChanged = 0x20000000,
     };
 
     layer_state_t()
@@ -154,6 +156,8 @@
     int32_t api;
     sp<NativeHandle> sidebandStream;
     mat4 colorTransform;
+
+    std::vector<ListenerCallbacks> listenerCallbacks;
 };
 
 struct ComposerState {
diff --git a/libs/gui/include/gui/SurfaceComposerClient.h b/libs/gui/include/gui/SurfaceComposerClient.h
index 8ccee05..1cafb77 100644
--- a/libs/gui/include/gui/SurfaceComposerClient.h
+++ b/libs/gui/include/gui/SurfaceComposerClient.h
@@ -19,7 +19,9 @@
 
 #include <stdint.h>
 #include <sys/types.h>
+#include <set>
 #include <unordered_map>
+#include <unordered_set>
 
 #include <binder/IBinder.h>
 
@@ -33,9 +35,10 @@
 #include <ui/PixelFormat.h>
 
 #include <gui/CpuConsumer.h>
+#include <gui/ITransactionCompletedListener.h>
+#include <gui/LayerState.h>
 #include <gui/SurfaceControl.h>
 #include <math/vec3.h>
-#include <gui/LayerState.h>
 
 namespace android {
 
@@ -49,6 +52,37 @@
 
 // ---------------------------------------------------------------------------
 
+using TransactionCompletedCallbackTakesContext =
+        std::function<void(void* /*context*/, const TransactionStats&)>;
+using TransactionCompletedCallback = std::function<void(const TransactionStats&)>;
+
+class TransactionCompletedListener : public BnTransactionCompletedListener {
+    TransactionCompletedListener();
+
+    CallbackId getNextIdLocked() REQUIRES(mMutex);
+
+    std::mutex mMutex;
+
+    bool mListening GUARDED_BY(mMutex) = false;
+
+    CallbackId mCallbackIdCounter GUARDED_BY(mMutex) = 1;
+
+    std::map<CallbackId, TransactionCompletedCallback> mCallbacks GUARDED_BY(mMutex);
+
+public:
+    static sp<TransactionCompletedListener> getInstance();
+    static sp<ITransactionCompletedListener> getIInstance();
+
+    void startListeningLocked() REQUIRES(mMutex);
+
+    CallbackId addCallback(const TransactionCompletedCallback& callback);
+
+    // Overrides BnTransactionCompletedListener's onTransactionCompleted
+    void onTransactionCompleted(ListenerStats stats) override;
+};
+
+// ---------------------------------------------------------------------------
+
 class SurfaceComposerClient : public RefBase
 {
     friend class Composer;
@@ -158,9 +192,27 @@
         }
     };
 
+    struct TCLHash {
+        std::size_t operator()(const sp<ITransactionCompletedListener>& tcl) const {
+            return std::hash<IBinder*>{}((tcl) ? IInterface::asBinder(tcl).get() : nullptr);
+        }
+    };
+
+    struct CallbackInfo {
+        // All the callbacks that have been requested for a TransactionCompletedListener in the
+        // Transaction
+        std::unordered_set<CallbackId> callbackIds;
+        // All the SurfaceControls that have been modified in this TransactionCompletedListener's
+        // process that require a callback if there is one or more callbackIds set.
+        std::unordered_set<sp<SurfaceControl>, SCHash> surfaceControls;
+    };
+
     class Transaction {
         std::unordered_map<sp<SurfaceControl>, ComposerState, SCHash> mComposerStates;
         SortedVector<DisplayState > mDisplayStates;
+        std::unordered_map<sp<ITransactionCompletedListener>, CallbackInfo, TCLHash>
+                mListenerCallbacks;
+
         uint32_t                    mForceSynchronous = 0;
         uint32_t                    mTransactionNestCount = 0;
         bool                        mAnimation = false;
@@ -171,6 +223,8 @@
         layer_state_t* getLayerState(const sp<SurfaceControl>& sc);
         DisplayState& getDisplayState(const sp<IBinder>& token);
 
+        void registerSurfaceControlForCallback(const sp<SurfaceControl>& sc);
+
     public:
         Transaction() = default;
         virtual ~Transaction() = default;
@@ -250,6 +304,9 @@
         Transaction& setSidebandStream(const sp<SurfaceControl>& sc,
                                        const sp<NativeHandle>& sidebandStream);
 
+        Transaction& addTransactionCompletedCallback(
+                TransactionCompletedCallbackTakesContext callback, void* callbackContext);
+
         // Detaches all child surfaces (and their children recursively)
         // from their SurfaceControl.
         // The child SurfaceControls will not throw exceptions or return errors,
@@ -349,6 +406,7 @@
 };
 
 // ---------------------------------------------------------------------------
+
 }; // namespace android
 
 #endif // ANDROID_GUI_SURFACE_COMPOSER_CLIENT_H
diff --git a/libs/ui/tests/BufferHubBuffer_test.cpp b/libs/ui/tests/BufferHubBuffer_test.cpp
index a247e60..606aee60 100644
--- a/libs/ui/tests/BufferHubBuffer_test.cpp
+++ b/libs/ui/tests/BufferHubBuffer_test.cpp
@@ -16,6 +16,7 @@
 
 #define LOG_TAG "BufferHubBufferTest"
 
+#include <android/frameworks/bufferhub/1.0/IBufferClient.h>
 #include <android/frameworks/bufferhub/1.0/IBufferHub.h>
 #include <gtest/gtest.h>
 #include <hidl/ServiceManagement.h>
@@ -33,11 +34,11 @@
 const int kUsage = 0;
 const size_t kUserMetadataSize = 0;
 
-} // namespace
-
 using dvr::BufferHubDefs::IsBufferGained;
 using dvr::BufferHubDefs::kFirstClientBitMask;
 using dvr::BufferHubDefs::kMetadataHeaderSize;
+using frameworks::bufferhub::V1_0::BufferHubStatus;
+using frameworks::bufferhub::V1_0::IBufferClient;
 using frameworks::bufferhub::V1_0::IBufferHub;
 using hardware::hidl_handle;
 using hidl::base::V1_0::IBase;
@@ -129,8 +130,14 @@
 
     // TODO(b/116681016): Fill in real test once the interface gets implemented..
     hidl_handle handle;
-    sp<IBase> interface = bufferhub->importBuffer(handle);
-    EXPECT_EQ(nullptr, interface.get());
+    EXPECT_TRUE(bufferhub
+                        ->importBuffer(handle,
+                                       [](const auto& client, const auto& ret) {
+                                           EXPECT_EQ(client, nullptr);
+                                           EXPECT_EQ(ret, BufferHubStatus::NO_ERROR);
+                                       })
+                        .isOk());
 }
 
+} // namespace
 } // namespace android
diff --git a/libs/vr/libbufferhubqueue/buffer_hub_queue_client.cpp b/libs/vr/libbufferhubqueue/buffer_hub_queue_client.cpp
index e1c1aa9..f7942d0 100644
--- a/libs/vr/libbufferhubqueue/buffer_hub_queue_client.cpp
+++ b/libs/vr/libbufferhubqueue/buffer_hub_queue_client.cpp
@@ -69,7 +69,7 @@
       .data = {.u64 = Stuff(-1, BufferHubQueue::kEpollQueueEventIndex)}};
   ret = epoll_fd_.Control(EPOLL_CTL_ADD, event_fd(), &event);
   if (ret < 0) {
-    ALOGE("BufferHubQueue::Initialize: Failed to add event fd to epoll set: %s",
+    ALOGE("%s: Failed to add event fd to epoll set: %s", __FUNCTION__,
           strerror(-ret));
   }
 }
@@ -77,7 +77,7 @@
 Status<void> BufferHubQueue::ImportQueue() {
   auto status = InvokeRemoteMethod<BufferHubRPC::GetQueueInfo>();
   if (!status) {
-    ALOGE("BufferHubQueue::ImportQueue: Failed to import queue: %s",
+    ALOGE("%s: Failed to import queue: %s", __FUNCTION__,
           status.GetErrorMessage().c_str());
     return ErrorStatus(status.error());
   } else {
@@ -136,9 +136,7 @@
       consumer_queue->GetChannel()->TakeChannelParcelable());
 
   if (!queue_parcelable.IsValid()) {
-    ALOGE(
-        "BufferHubQueue::CreateConsumerQueueParcelable: Failed to create "
-        "consumer queue parcelable.");
+    ALOGE("%s: Failed to create consumer queue parcelable.", __FUNCTION__);
     return ErrorStatus(EINVAL);
   }
 
@@ -169,8 +167,7 @@
     }
 
     if (ret < 0 && ret != -EINTR) {
-      ALOGE("BufferHubQueue::WaitForBuffers: Failed to wait for buffers: %s",
-            strerror(-ret));
+      ALOGE("%s: Failed to wait for buffers: %s", __FUNCTION__, strerror(-ret));
       return false;
     }
 
@@ -264,14 +261,14 @@
     // wait will be tried again to acquire the newly imported buffer.
     auto buffer_status = OnBufferAllocated();
     if (!buffer_status) {
-      ALOGE("BufferHubQueue::HandleQueueEvent: Failed to import buffer: %s",
+      ALOGE("%s: Failed to import buffer: %s", __FUNCTION__,
             buffer_status.GetErrorMessage().c_str());
     }
   } else if (events & EPOLLHUP) {
-    ALOGD_IF(TRACE, "BufferHubQueue::HandleQueueEvent: hang up event!");
+    ALOGD_IF(TRACE, "%s: hang up event!", __FUNCTION__);
     hung_up_ = true;
   } else {
-    ALOGW("BufferHubQueue::HandleQueueEvent: Unknown epoll events=%x", events);
+    ALOGW("%s: Unknown epoll events=%x", __FUNCTION__, events);
   }
 
   return {};
@@ -279,12 +276,11 @@
 
 Status<void> BufferHubQueue::AddBuffer(
     const std::shared_ptr<BufferHubBase>& buffer, size_t slot) {
-  ALOGD_IF(TRACE, "BufferHubQueue::AddBuffer: buffer_id=%d slot=%zu",
-           buffer->id(), slot);
+  ALOGD_IF(TRACE, "%s: buffer_id=%d slot=%zu", __FUNCTION__, buffer->id(),
+           slot);
 
   if (is_full()) {
-    ALOGE("BufferHubQueue::AddBuffer queue is at maximum capacity: %zu",
-          capacity_);
+    ALOGE("%s: queue is at maximum capacity: %zu", __FUNCTION__, capacity_);
     return ErrorStatus(E2BIG);
   }
 
@@ -303,7 +299,7 @@
     const int ret =
         epoll_fd_.Control(EPOLL_CTL_ADD, event_source.event_fd, &event);
     if (ret < 0) {
-      ALOGE("BufferHubQueue::AddBuffer: Failed to add buffer to epoll set: %s",
+      ALOGE("%s: Failed to add buffer to epoll set: %s", __FUNCTION__,
             strerror(-ret));
       return ErrorStatus(-ret);
     }
@@ -315,17 +311,15 @@
 }
 
 Status<void> BufferHubQueue::RemoveBuffer(size_t slot) {
-  ALOGD_IF(TRACE, "BufferHubQueue::RemoveBuffer: slot=%zu", slot);
+  ALOGD_IF(TRACE, "%s: slot=%zu", __FUNCTION__, slot);
 
   if (buffers_[slot]) {
     for (const auto& event_source : buffers_[slot]->GetEventSources()) {
       const int ret =
           epoll_fd_.Control(EPOLL_CTL_DEL, event_source.event_fd, nullptr);
       if (ret < 0) {
-        ALOGE(
-            "BufferHubQueue::RemoveBuffer: Failed to remove buffer from epoll "
-            "set: %s",
-            strerror(-ret));
+        ALOGE("%s: Failed to remove buffer from epoll set: %s", __FUNCTION__,
+              strerror(-ret));
         return ErrorStatus(-ret);
       }
     }
@@ -343,6 +337,15 @@
 
 Status<void> BufferHubQueue::Enqueue(Entry entry) {
   if (!is_full()) {
+    // Find and remove the enqueued buffer from unavailable_buffers_slot if
+    // exist.
+    auto enqueued_buffer_iter = std::find_if(
+        unavailable_buffers_slot_.begin(), unavailable_buffers_slot_.end(),
+        [&entry](size_t slot) -> bool { return slot == entry.slot; });
+    if (enqueued_buffer_iter != unavailable_buffers_slot_.end()) {
+      unavailable_buffers_slot_.erase(enqueued_buffer_iter);
+    }
+
     available_buffers_.push(std::move(entry));
 
     // Trigger OnBufferAvailable callback if registered.
@@ -351,17 +354,16 @@
 
     return {};
   } else {
-    ALOGE("BufferHubQueue::Enqueue: Buffer queue is full!");
+    ALOGE("%s: Buffer queue is full!", __FUNCTION__);
     return ErrorStatus(E2BIG);
   }
 }
 
 Status<std::shared_ptr<BufferHubBase>> BufferHubQueue::Dequeue(int timeout,
                                                                size_t* slot) {
-  ALOGD_IF(TRACE, "BufferHubQueue::Dequeue: count=%zu, timeout=%d", count(),
-           timeout);
+  ALOGD_IF(TRACE, "%s: count=%zu, timeout=%d", __FUNCTION__, count(), timeout);
 
-  PDX_TRACE_FORMAT("BufferHubQueue::Dequeue|count=%zu|", count());
+  PDX_TRACE_FORMAT("%s|count=%zu|", __FUNCTION__, count());
 
   if (count() == 0) {
     if (!WaitForBuffers(timeout))
@@ -376,6 +378,7 @@
   *slot = entry.slot;
 
   available_buffers_.pop();
+  unavailable_buffers_slot_.push_back(*slot);
 
   return {std::move(buffer)};
 }
@@ -564,7 +567,7 @@
   auto status =
       InvokeRemoteMethod<BufferHubRPC::ProducerQueueRemoveBuffer>(slot);
   if (!status) {
-    ALOGE("ProducerQueue::RemoveBuffer: Failed to remove producer buffer: %s",
+    ALOGE("%s: Failed to remove producer buffer: %s", __FUNCTION__,
           status.GetErrorMessage().c_str());
     return status.error_status();
   }
@@ -580,31 +583,81 @@
 
 pdx::Status<std::shared_ptr<BufferProducer>> ProducerQueue::Dequeue(
     int timeout, size_t* slot, DvrNativeBufferMetadata* out_meta,
-    pdx::LocalHandle* release_fence) {
+    pdx::LocalHandle* release_fence, bool gain_posted_buffer) {
   ATRACE_NAME("ProducerQueue::Dequeue");
   if (slot == nullptr || out_meta == nullptr || release_fence == nullptr) {
-    ALOGE("ProducerQueue::Dequeue: Invalid parameter.");
+    ALOGE("%s: Invalid parameter.", __FUNCTION__);
     return ErrorStatus(EINVAL);
   }
 
-  auto status = BufferHubQueue::Dequeue(timeout, slot);
-  if (!status)
-    return status.error_status();
-
-  auto buffer = std::static_pointer_cast<BufferProducer>(status.take());
-  const int ret = buffer->GainAsync(out_meta, release_fence);
+  std::shared_ptr<BufferProducer> buffer;
+  Status<std::shared_ptr<BufferHubBase>> dequeue_status =
+      BufferHubQueue::Dequeue(timeout, slot);
+  if (dequeue_status.ok()) {
+    buffer = std::static_pointer_cast<BufferProducer>(dequeue_status.take());
+  } else {
+    if (gain_posted_buffer) {
+      Status<std::shared_ptr<BufferProducer>> dequeue_unacquired_status =
+          ProducerQueue::DequeueUnacquiredBuffer(slot);
+      if (!dequeue_unacquired_status.ok()) {
+        ALOGE("%s: DequeueUnacquiredBuffer returned error: %d", __FUNCTION__,
+              dequeue_unacquired_status.error());
+        return dequeue_unacquired_status.error_status();
+      }
+      buffer = dequeue_unacquired_status.take();
+    } else {
+      return dequeue_status.error_status();
+    }
+  }
+  const int ret =
+      buffer->GainAsync(out_meta, release_fence, gain_posted_buffer);
   if (ret < 0 && ret != -EALREADY)
     return ErrorStatus(-ret);
 
   return {std::move(buffer)};
 }
 
+Status<std::shared_ptr<BufferProducer>> ProducerQueue::DequeueUnacquiredBuffer(
+    size_t* slot) {
+  if (unavailable_buffers_slot_.size() < 1) {
+    ALOGE(
+        "%s: Failed to dequeue un-acquired buffer. All buffer(s) are in "
+        "acquired state if exist.",
+        __FUNCTION__);
+    return ErrorStatus(ENOMEM);
+  }
+
+  // Find the first buffer that is not in acquired state from
+  // unavailable_buffers_slot_.
+  for (auto iter = unavailable_buffers_slot_.begin();
+       iter != unavailable_buffers_slot_.end(); iter++) {
+    std::shared_ptr<BufferProducer> buffer = ProducerQueue::GetBuffer(*iter);
+    if (buffer == nullptr) {
+      ALOGE("%s failed. Buffer slot %d is  null.", __FUNCTION__,
+            static_cast<int>(*slot));
+      return ErrorStatus(EIO);
+    }
+    if (!BufferHubDefs::IsBufferAcquired(buffer->buffer_state())) {
+      *slot = *iter;
+      unavailable_buffers_slot_.erase(iter);
+      unavailable_buffers_slot_.push_back(*slot);
+      ALOGD("%s: Producer queue dequeue unacquired buffer in slot %d",
+            __FUNCTION__, static_cast<int>(*slot));
+      return {std::move(buffer)};
+    }
+  }
+  ALOGE(
+      "%s: Failed to dequeue un-acquired buffer. No un-acquired buffer exist.",
+      __FUNCTION__);
+  return ErrorStatus(EBUSY);
+}
+
 pdx::Status<ProducerQueueParcelable> ProducerQueue::TakeAsParcelable() {
   if (capacity() != 0) {
     ALOGE(
-        "ProducerQueue::TakeAsParcelable: producer queue can only be taken out"
-        " as a parcelable when empty. Current queue capacity: %zu",
-        capacity());
+        "%s: producer queue can only be taken out as a parcelable when empty. "
+        "Current queue capacity: %zu",
+        __FUNCTION__, capacity());
     return ErrorStatus(EINVAL);
   }
 
@@ -628,17 +681,16 @@
     : BufferHubQueue(std::move(handle)) {
   auto status = ImportQueue();
   if (!status) {
-    ALOGE("ConsumerQueue::ConsumerQueue: Failed to import queue: %s",
+    ALOGE("%s: Failed to import queue: %s", __FUNCTION__,
           status.GetErrorMessage().c_str());
     Close(-status.error());
   }
 
   auto import_status = ImportBuffers();
   if (import_status) {
-    ALOGI("ConsumerQueue::ConsumerQueue: Imported %zu buffers.",
-          import_status.get());
+    ALOGI("%s: Imported %zu buffers.", __FUNCTION__, import_status.get());
   } else {
-    ALOGE("ConsumerQueue::ConsumerQueue: Failed to import buffers: %s",
+    ALOGE("%s: Failed to import buffers: %s", __FUNCTION__,
           import_status.GetErrorMessage().c_str());
   }
 }
@@ -647,14 +699,11 @@
   auto status = InvokeRemoteMethod<BufferHubRPC::ConsumerQueueImportBuffers>();
   if (!status) {
     if (status.error() == EBADR) {
-      ALOGI(
-          "ConsumerQueue::ImportBuffers: Queue is silent, no buffers "
-          "imported.");
+      ALOGI("%s: Queue is silent, no buffers imported.", __FUNCTION__);
       return {0};
     } else {
-      ALOGE(
-          "ConsumerQueue::ImportBuffers: Failed to import consumer buffer: %s",
-          status.GetErrorMessage().c_str());
+      ALOGE("%s: Failed to import consumer buffer: %s", __FUNCTION__,
+            status.GetErrorMessage().c_str());
       return status.error_status();
     }
   }
@@ -665,13 +714,13 @@
 
   auto buffer_handle_slots = status.take();
   for (auto& buffer_handle_slot : buffer_handle_slots) {
-    ALOGD_IF(TRACE, "ConsumerQueue::ImportBuffers: buffer_handle=%d",
+    ALOGD_IF(TRACE, ": buffer_handle=%d", __FUNCTION__,
              buffer_handle_slot.first.value());
 
     std::unique_ptr<BufferConsumer> buffer_consumer =
         BufferConsumer::Import(std::move(buffer_handle_slot.first));
     if (!buffer_consumer) {
-      ALOGE("ConsumerQueue::ImportBuffers: Failed to import buffer: slot=%zu",
+      ALOGE("%s: Failed to import buffer: slot=%zu", __FUNCTION__,
             buffer_handle_slot.second);
       last_error = ErrorStatus(EPIPE);
       continue;
@@ -680,7 +729,7 @@
     auto add_status =
         AddBuffer(std::move(buffer_consumer), buffer_handle_slot.second);
     if (!add_status) {
-      ALOGE("ConsumerQueue::ImportBuffers: Failed to add buffer: %s",
+      ALOGE("%s: Failed to add buffer: %s", __FUNCTION__,
             add_status.GetErrorMessage().c_str());
       last_error = add_status;
     } else {
@@ -696,8 +745,8 @@
 
 Status<void> ConsumerQueue::AddBuffer(
     const std::shared_ptr<BufferConsumer>& buffer, size_t slot) {
-  ALOGD_IF(TRACE, "ConsumerQueue::AddBuffer: queue_id=%d buffer_id=%d slot=%zu",
-           id(), buffer->id(), slot);
+  ALOGD_IF(TRACE, "%s: queue_id=%d buffer_id=%d slot=%zu", __FUNCTION__, id(),
+           buffer->id(), slot);
   return BufferHubQueue::AddBuffer(buffer, slot);
 }
 
@@ -706,9 +755,9 @@
     LocalHandle* acquire_fence) {
   if (user_metadata_size != user_metadata_size_) {
     ALOGE(
-        "ConsumerQueue::Dequeue: Metadata size (%zu) for the dequeuing buffer "
-        "does not match metadata size (%zu) for the queue.",
-        user_metadata_size, user_metadata_size_);
+        "%s: Metadata size (%zu) for the dequeuing buffer does not match "
+        "metadata size (%zu) for the queue.",
+        __FUNCTION__, user_metadata_size, user_metadata_size_);
     return ErrorStatus(EINVAL);
   }
 
@@ -723,7 +772,7 @@
     if (metadata_src) {
       memcpy(meta, metadata_src, user_metadata_size);
     } else {
-      ALOGW("ConsumerQueue::Dequeue: no user-defined metadata.");
+      ALOGW("%s: no user-defined metadata.", __FUNCTION__);
     }
   }
 
@@ -735,7 +784,7 @@
     pdx::LocalHandle* acquire_fence) {
   ATRACE_NAME("ConsumerQueue::Dequeue");
   if (slot == nullptr || out_meta == nullptr || acquire_fence == nullptr) {
-    ALOGE("ConsumerQueue::Dequeue: Invalid parameter.");
+    ALOGE("%s: Invalid parameter.", __FUNCTION__);
     return ErrorStatus(EINVAL);
   }
 
@@ -752,19 +801,18 @@
 }
 
 Status<void> ConsumerQueue::OnBufferAllocated() {
-  ALOGD_IF(TRACE, "ConsumerQueue::OnBufferAllocated: queue_id=%d", id());
+  ALOGD_IF(TRACE, "%s: queue_id=%d", __FUNCTION__, id());
 
   auto status = ImportBuffers();
   if (!status) {
-    ALOGE("ConsumerQueue::OnBufferAllocated: Failed to import buffers: %s",
+    ALOGE("%s: Failed to import buffers: %s", __FUNCTION__,
           status.GetErrorMessage().c_str());
     return ErrorStatus(status.error());
   } else if (status.get() == 0) {
-    ALOGW("ConsumerQueue::OnBufferAllocated: No new buffers allocated!");
+    ALOGW("%s: No new buffers allocated!", __FUNCTION__);
     return ErrorStatus(ENOBUFS);
   } else {
-    ALOGD_IF(TRACE,
-             "ConsumerQueue::OnBufferAllocated: Imported %zu consumer buffers.",
+    ALOGD_IF(TRACE, "%s: Imported %zu consumer buffers.", __FUNCTION__,
              status.get());
     return {};
   }
diff --git a/libs/vr/libbufferhubqueue/include/private/dvr/buffer_hub_queue_client.h b/libs/vr/libbufferhubqueue/include/private/dvr/buffer_hub_queue_client.h
index c69002d..def7c6b 100644
--- a/libs/vr/libbufferhubqueue/include/private/dvr/buffer_hub_queue_client.h
+++ b/libs/vr/libbufferhubqueue/include/private/dvr/buffer_hub_queue_client.h
@@ -57,10 +57,10 @@
   uint32_t default_width() const { return default_width_; }
 
   // Returns the default buffer height of this buffer queue.
-  uint32_t default_height() const { return static_cast<uint32_t>(default_height_); }
+  uint32_t default_height() const { return default_height_; }
 
   // Returns the default buffer format of this buffer queue.
-  uint32_t default_format() const { return static_cast<uint32_t>(default_format_); }
+  uint32_t default_format() const { return default_format_; }
 
   // Creates a new consumer in handle form for immediate transport over RPC.
   pdx::Status<pdx::LocalChannelHandle> CreateConsumerQueueHandle(
@@ -208,6 +208,14 @@
   // Size of the metadata that buffers in this queue cary.
   size_t user_metadata_size_{0};
 
+  // Buffers and related data that are available for dequeue.
+  std::priority_queue<Entry, std::vector<Entry>, EntryComparator>
+      available_buffers_;
+
+  // Slot of the buffers that are not available for normal dequeue. For example,
+  // the slot of posted or acquired buffers in the perspective of a producer.
+  std::vector<size_t> unavailable_buffers_slot_;
+
  private:
   void Initialize();
 
@@ -252,10 +260,6 @@
   // queue regardless of its queue position or presence in the ring buffer.
   std::array<std::shared_ptr<BufferHubBase>, kMaxQueueCapacity> buffers_;
 
-  // Buffers and related data that are available for dequeue.
-  std::priority_queue<Entry, std::vector<Entry>, EntryComparator>
-      available_buffers_;
-
   // Keeps track with how many buffers have been added into the queue.
   size_t capacity_{0};
 
@@ -349,11 +353,30 @@
   // Dequeue a producer buffer to write. The returned buffer in |Gain|'ed mode,
   // and caller should call Post() once it's done writing to release the buffer
   // to the consumer side.
+  // @return a buffer in gained state, which was originally in released state.
   pdx::Status<std::shared_ptr<BufferProducer>> Dequeue(
       int timeout, size_t* slot, pdx::LocalHandle* release_fence);
+
+  // Dequeue a producer buffer to write. The returned buffer in |Gain|'ed mode,
+  // and caller should call Post() once it's done writing to release the buffer
+  // to the consumer side.
+  //
+  // @param timeout to dequeue a buffer.
+  // @param slot is the slot of the output BufferProducer.
+  // @param release_fence for gaining a buffer.
+  // @param out_meta metadata of the output buffer.
+  // @param gain_posted_buffer whether to gain posted buffer if no released
+  //     buffer is available to gain.
+  // @return a buffer in gained state, which was originally in released state if
+  //     gain_posted_buffer is false, or in posted/released state if
+  //     gain_posted_buffer is true.
+  // TODO(b/112007999): gain_posted_buffer true is only used to prevent
+  // libdvrtracking from starving when there are non-responding clients. This
+  // gain_posted_buffer param can be removed once libdvrtracking start to use
+  // the new AHardwareBuffer API.
   pdx::Status<std::shared_ptr<BufferProducer>> Dequeue(
       int timeout, size_t* slot, DvrNativeBufferMetadata* out_meta,
-      pdx::LocalHandle* release_fence);
+      pdx::LocalHandle* release_fence, bool gain_posted_buffer = false);
 
   // Enqueues a producer buffer in the queue.
   pdx::Status<void> Enqueue(const std::shared_ptr<BufferProducer>& buffer,
@@ -374,6 +397,16 @@
   // arguments as the constructors.
   explicit ProducerQueue(pdx::LocalChannelHandle handle);
   ProducerQueue(const ProducerQueueConfig& config, const UsagePolicy& usage);
+
+  // Dequeue a producer buffer to write. The returned buffer in |Gain|'ed mode,
+  // and caller should call Post() once it's done writing to release the buffer
+  // to the consumer side.
+  //
+  // @param slot the slot of the returned buffer.
+  // @return a buffer in gained state, which was originally in posted state or
+  //     released state.
+  pdx::Status<std::shared_ptr<BufferProducer>> DequeueUnacquiredBuffer(
+      size_t* slot);
 };
 
 class ConsumerQueue : public BufferHubQueue {
diff --git a/libs/vr/libbufferhubqueue/tests/buffer_hub_queue-test.cpp b/libs/vr/libbufferhubqueue/tests/buffer_hub_queue-test.cpp
index 046df54..c58f55f 100644
--- a/libs/vr/libbufferhubqueue/tests/buffer_hub_queue-test.cpp
+++ b/libs/vr/libbufferhubqueue/tests/buffer_hub_queue-test.cpp
@@ -1,5 +1,6 @@
 #include <base/logging.h>
 #include <binder/Parcel.h>
+#include <dvr/dvr_api.h>
 #include <private/dvr/buffer_hub_client.h>
 #include <private/dvr/buffer_hub_queue_client.h>
 
@@ -122,6 +123,147 @@
   }
 }
 
+TEST_F(BufferHubQueueTest,
+       TestDequeuePostedBufferIfNoAvailableReleasedBuffer_withBufferConsumer) {
+  ASSERT_TRUE(CreateQueues(config_builder_.Build(), UsagePolicy{}));
+
+  // Allocate 3 buffers to use.
+  const size_t test_queue_capacity = 3;
+  for (int64_t i = 0; i < test_queue_capacity; i++) {
+    AllocateBuffer();
+  }
+  EXPECT_EQ(producer_queue_->capacity(), test_queue_capacity);
+
+  size_t producer_slot, consumer_slot;
+  LocalHandle fence;
+  DvrNativeBufferMetadata mi, mo;
+
+  // Producer posts 2 buffers and remember their posted sequence.
+  std::deque<size_t> posted_slots;
+  for (int64_t i = 0; i < 2; i++) {
+    auto p1_status =
+        producer_queue_->Dequeue(kTimeoutMs, &producer_slot, &mo, &fence, true);
+    EXPECT_TRUE(p1_status.ok());
+    auto p1 = p1_status.take();
+    ASSERT_NE(p1, nullptr);
+
+    // Producer should not be gaining posted buffer when there are still
+    // available buffers to gain.
+    auto found_iter =
+        std::find(posted_slots.begin(), posted_slots.end(), producer_slot);
+    EXPECT_EQ(found_iter, posted_slots.end());
+    posted_slots.push_back(producer_slot);
+
+    // Producer posts the buffer.
+    mi.index = i;
+    EXPECT_EQ(0, p1->PostAsync(&mi, LocalHandle()));
+  }
+
+  // Consumer acquires one buffer.
+  auto c1_status =
+      consumer_queue_->Dequeue(kTimeoutMs, &consumer_slot, &mo, &fence);
+  EXPECT_TRUE(c1_status.ok());
+  auto c1 = c1_status.take();
+  ASSERT_NE(c1, nullptr);
+  // Consumer should get the oldest posted buffer. No checks here.
+  // posted_slots[0] should be in acquired state now.
+  EXPECT_EQ(mo.index, 0);
+  // Consumer releases the buffer.
+  EXPECT_EQ(c1->ReleaseAsync(&mi, LocalHandle()), 0);
+  // posted_slots[0] should be in released state now.
+
+  // Producer gain and post 2 buffers.
+  for (int64_t i = 0; i < 2; i++) {
+    auto p1_status =
+        producer_queue_->Dequeue(kTimeoutMs, &producer_slot, &mo, &fence, true);
+    EXPECT_TRUE(p1_status.ok());
+    auto p1 = p1_status.take();
+    ASSERT_NE(p1, nullptr);
+
+    // The gained buffer should be the one in released state or the one haven't
+    // been use.
+    EXPECT_NE(posted_slots[1], producer_slot);
+
+    mi.index = i + 2;
+    EXPECT_EQ(0, p1->PostAsync(&mi, LocalHandle()));
+  }
+
+  // Producer gains a buffer.
+  auto p1_status =
+      producer_queue_->Dequeue(kTimeoutMs, &producer_slot, &mo, &fence, true);
+  EXPECT_TRUE(p1_status.ok());
+  auto p1 = p1_status.take();
+  ASSERT_NE(p1, nullptr);
+
+  // The gained buffer should be the oldest posted buffer.
+  EXPECT_EQ(posted_slots[1], producer_slot);
+
+  // Producer posts the buffer.
+  mi.index = 4;
+  EXPECT_EQ(0, p1->PostAsync(&mi, LocalHandle()));
+}
+
+TEST_F(BufferHubQueueTest,
+       TestDequeuePostedBufferIfNoAvailableReleasedBuffer_noBufferConsumer) {
+  ASSERT_TRUE(CreateQueues(config_builder_.Build(), UsagePolicy{}));
+
+  // Allocate 4 buffers to use.
+  const size_t test_queue_capacity = 4;
+  for (int64_t i = 0; i < test_queue_capacity; i++) {
+    AllocateBuffer();
+  }
+  EXPECT_EQ(producer_queue_->capacity(), test_queue_capacity);
+
+  // Post all allowed buffers and remember their posted sequence.
+  std::deque<size_t> posted_slots;
+  for (int64_t i = 0; i < test_queue_capacity; i++) {
+    size_t slot;
+    LocalHandle fence;
+    DvrNativeBufferMetadata mi, mo;
+
+    // Producer gains a buffer.
+    auto p1_status =
+        producer_queue_->Dequeue(kTimeoutMs, &slot, &mo, &fence, true);
+    EXPECT_TRUE(p1_status.ok());
+    auto p1 = p1_status.take();
+    ASSERT_NE(p1, nullptr);
+
+    // Producer should not be gaining posted buffer when there are still
+    // available buffers to gain.
+    auto found_iter = std::find(posted_slots.begin(), posted_slots.end(), slot);
+    EXPECT_EQ(found_iter, posted_slots.end());
+    posted_slots.push_back(slot);
+
+    // Producer posts the buffer.
+    mi.index = i;
+    EXPECT_EQ(p1->PostAsync(&mi, LocalHandle()), 0);
+  }
+
+  // Gain posted buffers in sequence.
+  const int64_t nb_dequeue_all_times = 2;
+  for (int j = 0; j < nb_dequeue_all_times; ++j) {
+    for (int i = 0; i < test_queue_capacity; ++i) {
+      size_t slot;
+      LocalHandle fence;
+      DvrNativeBufferMetadata mi, mo;
+
+      // Producer gains a buffer.
+      auto p1_status =
+          producer_queue_->Dequeue(kTimeoutMs, &slot, &mo, &fence, true);
+      EXPECT_TRUE(p1_status.ok());
+      auto p1 = p1_status.take();
+      ASSERT_NE(p1, nullptr);
+
+      // The gained buffer should be the oldest posted buffer.
+      EXPECT_EQ(posted_slots[i], slot);
+
+      // Producer posts the buffer.
+      mi.index = i + test_queue_capacity * (j + 1);
+      EXPECT_EQ(p1->PostAsync(&mi, LocalHandle()), 0);
+    }
+  }
+}
+
 TEST_F(BufferHubQueueTest, TestProducerConsumer) {
   const size_t kBufferCount = 16;
   size_t slot;
@@ -245,8 +387,8 @@
 
   for (size_t i = 0; i < kBufferCount; i++) {
     Entry* entry = &buffers[i];
-    auto producer_status = producer_queue_->Dequeue(
-        kTimeoutMs, &entry->slot, &mo, &entry->fence);
+    auto producer_status =
+        producer_queue_->Dequeue(kTimeoutMs, &entry->slot, &mo, &entry->fence);
     ASSERT_TRUE(producer_status.ok());
     entry->buffer = producer_status.take();
     ASSERT_NE(nullptr, entry->buffer);
diff --git a/libs/vr/libpdx/service.cpp b/libs/vr/libpdx/service.cpp
index 68b8dd7..3769162 100644
--- a/libs/vr/libpdx/service.cpp
+++ b/libs/vr/libpdx/service.cpp
@@ -318,13 +318,7 @@
   PDX_TRACE_NAME("Message::ReplyFileHandle");
   auto svc = service_.lock();
   if (!replied_ && svc) {
-    Status<void> ret;
-
-    if (handle)
-      ret = svc->endpoint()->MessageReply(this, handle.Get());
-    else
-      ret = svc->endpoint()->MessageReply(this, handle.Get());
-
+    Status<void> ret = svc->endpoint()->MessageReply(this, handle.Get());
     replied_ = ret.ok();
     return ret;
   } else {
diff --git a/opengl/libs/EGL/Loader.cpp b/opengl/libs/EGL/Loader.cpp
index 0c43b83..e27f233 100644
--- a/opengl/libs/EGL/Loader.cpp
+++ b/opengl/libs/EGL/Loader.cpp
@@ -584,7 +584,13 @@
         // The "Developer Options" value wasn't set to force the use of ANGLE.  Need to temporarily
         // load ANGLE and call the updatable opt-in/out logic:
 
-        cnx->featureSo = load_angle_from_namespace("feature_support", ns);
+        // Check if ANGLE is enabled. Workaround for several bugs:
+        // b/119305693 b/119322355 b/119305887
+        // Something is not working correctly in the feature library
+        property_get("debug.angle.enable", prop, "0");
+        if (atoi(prop)) {
+            cnx->featureSo = load_angle_from_namespace("feature_support", ns);
+        }
         if (cnx->featureSo) {
             ALOGV("loaded ANGLE's opt-in/out logic from namespace");
             use_angle = check_angle_rules(cnx->featureSo, app_name);
diff --git a/services/bufferhub/Android.bp b/services/bufferhub/Android.bp
index d03d833..ca65e02 100644
--- a/services/bufferhub/Android.bp
+++ b/services/bufferhub/Android.bp
@@ -22,6 +22,7 @@
         "-Wextra",
     ],
     srcs: [
+        "BufferClient.cpp",
         "BufferHubService.cpp",
         "BufferNode.cpp",
     ],
diff --git a/services/bufferhub/BufferClient.cpp b/services/bufferhub/BufferClient.cpp
new file mode 100644
index 0000000..b3662b2
--- /dev/null
+++ b/services/bufferhub/BufferClient.cpp
@@ -0,0 +1,39 @@
+/*
+ * Copyright (C) 2018 The Android Open Source Project
+ *
+ * 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.
+ */
+
+#include <bufferhub/BufferClient.h>
+#include <hidl/HidlSupport.h>
+
+namespace android {
+namespace frameworks {
+namespace bufferhub {
+namespace V1_0 {
+namespace implementation {
+
+using hardware::hidl_handle;
+using hardware::Void;
+
+Return<void> BufferClient::duplicate(duplicate_cb _hidl_cb) {
+    // TODO(b/118614157): implement token generation and registration
+    _hidl_cb(/*token=*/hidl_handle(), /*status=*/BufferHubStatus::NO_ERROR);
+    return Void();
+}
+
+} // namespace implementation
+} // namespace V1_0
+} // namespace bufferhub
+} // namespace frameworks
+} // namespace android
\ No newline at end of file
diff --git a/services/bufferhub/BufferHubService.cpp b/services/bufferhub/BufferHubService.cpp
index 8be85a5..86598e0 100644
--- a/services/bufferhub/BufferHubService.cpp
+++ b/services/bufferhub/BufferHubService.cpp
@@ -22,16 +22,21 @@
 namespace V1_0 {
 namespace implementation {
 
-using ::android::status_t;
-using ::android::hardware::Void;
+using hardware::Void;
 
 Return<void> BufferHubService::allocateBuffer(const HardwareBufferDescription& /*description*/,
-                                              allocateBuffer_cb /*hidl_cb*/) {
+                                              const uint32_t /*userMetadataSize*/,
+                                              allocateBuffer_cb _hidl_cb) {
+    // TODO(b/118614333): implement buffer allocation
+    _hidl_cb(/*bufferClient=*/nullptr, /*status=*/BufferHubStatus::NO_ERROR);
     return Void();
 }
 
-Return<sp<IBase>> BufferHubService::importBuffer(const hidl_handle& /*nativeHandle*/) {
-    return nullptr;
+Return<void> BufferHubService::importBuffer(const hidl_handle& /*nativeHandle*/,
+                                            importBuffer_cb _hidl_cb) {
+    // TODO(b/118614157): implement buffer import
+    _hidl_cb(/*bufferClient=*/nullptr, /*status=*/BufferHubStatus::NO_ERROR);
+    return Void();
 }
 
 } // namespace implementation
diff --git a/services/bufferhub/BufferNode.cpp b/services/bufferhub/BufferNode.cpp
index 62583a6..53dd702 100644
--- a/services/bufferhub/BufferNode.cpp
+++ b/services/bufferhub/BufferNode.cpp
@@ -35,7 +35,7 @@
                                                      /*requestor=*/"bufferhub");
 
     if (ret != OK || buffer_handle_ == nullptr) {
-        ALOGE("BufferNode::BufferNode: Failed to allocate buffer: %s", strerror(-ret));
+        ALOGE("%s: Failed to allocate buffer: %s", __FUNCTION__, strerror(-ret));
         return;
     }
 
@@ -48,7 +48,7 @@
 
     metadata_ = BufferHubMetadata::Create(user_metadata_size);
     if (!metadata_.IsValid()) {
-        ALOGE("BufferNode::BufferNode: Failed to allocate metadata.");
+        ALOGE("%s: Failed to allocate metadata.", __FUNCTION__);
         return;
     }
     InitializeMetadata();
@@ -59,7 +59,7 @@
     if (buffer_handle_ != nullptr) {
         status_t ret = GraphicBufferAllocator::get().free(buffer_handle_);
         if (ret != OK) {
-            ALOGE("BufferNode::~BufferNode: Failed to free handle; Got error: %d", ret);
+            ALOGE("%s: Failed to free handle; Got error: %d", __FUNCTION__, ret);
         }
     }
 }
@@ -76,8 +76,7 @@
         client_state_mask = dvr::BufferHubDefs::FindNextAvailableClientStateMask(
                 current_active_clients_bit_mask);
         if (client_state_mask == 0ULL) {
-            ALOGE("BufferNode::AddNewActiveClientsBitToMask: reached the maximum "
-                  "mumber of channels per buffer node: 32.");
+            ALOGE("%s: reached the maximum number of channels per buffer node: 32.", __FUNCTION__);
             errno = E2BIG;
             return 0ULL;
         }
diff --git a/services/bufferhub/include/bufferhub/BufferClient.h b/services/bufferhub/include/bufferhub/BufferClient.h
new file mode 100644
index 0000000..14ea95c
--- /dev/null
+++ b/services/bufferhub/include/bufferhub/BufferClient.h
@@ -0,0 +1,41 @@
+/*
+ * Copyright (C) 2018 The Android Open Source Project
+ *
+ * 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.
+ */
+
+#ifndef ANDROID_FRAMEWORKS_BUFFERHUB_V1_0_BUFFER_CLIENT_H
+#define ANDROID_FRAMEWORKS_BUFFERHUB_V1_0_BUFFER_CLIENT_H
+
+#include <android/frameworks/bufferhub/1.0/IBufferClient.h>
+
+namespace android {
+namespace frameworks {
+namespace bufferhub {
+namespace V1_0 {
+namespace implementation {
+
+using hardware::Return;
+
+class BufferClient : public IBufferClient {
+public:
+    Return<void> duplicate(duplicate_cb _hidl_cb) override;
+};
+
+} // namespace implementation
+} // namespace V1_0
+} // namespace bufferhub
+} // namespace frameworks
+} // namespace android
+
+#endif
\ No newline at end of file
diff --git a/services/bufferhub/include/bufferhub/BufferHubService.h b/services/bufferhub/include/bufferhub/BufferHubService.h
index b273e5b..5e0cff0 100644
--- a/services/bufferhub/include/bufferhub/BufferHubService.h
+++ b/services/bufferhub/include/bufferhub/BufferHubService.h
@@ -17,8 +17,8 @@
 #ifndef ANDROID_FRAMEWORKS_BUFFERHUB_V1_0_BUFFER_HUB_SERVICE_H
 #define ANDROID_FRAMEWORKS_BUFFERHUB_V1_0_BUFFER_HUB_SERVICE_H
 
+#include <android/frameworks/bufferhub/1.0/IBufferClient.h>
 #include <android/frameworks/bufferhub/1.0/IBufferHub.h>
-#include <android/hardware/graphics/common/1.2/types.h>
 
 namespace android {
 namespace frameworks {
@@ -26,17 +26,16 @@
 namespace V1_0 {
 namespace implementation {
 
-using ::android::sp;
 using ::android::hardware::hidl_handle;
 using ::android::hardware::Return;
 using ::android::hardware::graphics::common::V1_2::HardwareBufferDescription;
-using ::android::hidl::base::V1_0::IBase;
 
 class BufferHubService : public IBufferHub {
 public:
-    Return<void> allocateBuffer(const HardwareBufferDescription& /*description*/,
-                                allocateBuffer_cb /*hidl_cb*/) override;
-    Return<sp<IBase>> importBuffer(const hidl_handle& /*nativeHandle*/) override;
+    Return<void> allocateBuffer(const HardwareBufferDescription& description,
+                                const uint32_t userMetadataSize,
+                                allocateBuffer_cb _hidl_cb) override;
+    Return<void> importBuffer(const hidl_handle& nativeHandle, importBuffer_cb _hidl_cb) override;
 };
 
 } // namespace implementation
diff --git a/services/gpuservice/Android.bp b/services/gpuservice/Android.bp
new file mode 100644
index 0000000..250bbee
--- /dev/null
+++ b/services/gpuservice/Android.bp
@@ -0,0 +1,74 @@
+filegroup {
+    name: "gpuservice_sources",
+    srcs: [
+        "GpuService.cpp",
+    ],
+}
+
+filegroup {
+    name: "gpuservice_binary_sources",
+    srcs: ["main_gpuservice.cpp"],
+}
+
+cc_defaults {
+    name: "gpuservice_defaults",
+    cflags: [
+        "-DLOG_TAG=\"GpuService\"",
+        "-Wall",
+        "-Werror",
+        "-Wformat",
+        "-Wthread-safety",
+        "-Wunused",
+        "-Wunreachable-code",
+    ],
+    cppflags: ["-std=c++1z"],
+    srcs: [
+        ":gpuservice_sources",
+    ],
+    include_dirs: [
+        "frameworks/native/vulkan/vkjson",
+        "frameworks/native/vulkan/include",
+    ],
+    shared_libs: [
+        "libbinder",
+        "liblog",
+        "libutils",
+        "libvulkan",
+    ],
+    static_libs: [
+        "libvkjson",
+    ],
+}
+
+cc_defaults {
+    name: "gpuservice_production_defaults",
+    defaults: ["gpuservice_defaults"],
+    cflags: [
+        "-fvisibility=hidden",
+        "-fwhole-program-vtables", // requires ThinLTO
+    ],
+    lto: {
+        thin: true,
+    },
+}
+
+cc_defaults {
+    name: "gpuservice_binary",
+    defaults: ["gpuservice_defaults"],
+    whole_static_libs: [
+        "libsigchain",
+    ],
+    shared_libs: [
+        "libbinder",
+        "liblog",
+        "libutils",
+    ],
+    ldflags: ["-Wl,--export-dynamic"],
+}
+
+cc_binary {
+    name: "gpuservice",
+    defaults: ["gpuservice_binary"],
+    init_rc: ["gpuservice.rc"],
+    srcs: [":gpuservice_binary_sources"],
+}
diff --git a/services/surfaceflinger/GpuService.cpp b/services/gpuservice/GpuService.cpp
similarity index 83%
rename from services/surfaceflinger/GpuService.cpp
rename to services/gpuservice/GpuService.cpp
index 71052fb..e4ca6bc 100644
--- a/services/surfaceflinger/GpuService.cpp
+++ b/services/gpuservice/GpuService.cpp
@@ -23,10 +23,7 @@
 
 namespace android {
 
-// ----------------------------------------------------------------------------
-
-class BpGpuService : public BpInterface<IGpuService>
-{
+class BpGpuService : public BpInterface<IGpuService> {
 public:
     explicit BpGpuService(const sp<IBinder>& impl) : BpInterface<IGpuService>(impl) {}
 };
@@ -34,19 +31,15 @@
 IMPLEMENT_META_INTERFACE(GpuService, "android.ui.IGpuService");
 
 status_t BnGpuService::onTransact(uint32_t code, const Parcel& data,
-        Parcel* reply, uint32_t flags)
-{
+        Parcel* reply, uint32_t flags) {
     status_t status;
     switch (code) {
     case SHELL_COMMAND_TRANSACTION: {
         int in = data.readFileDescriptor();
         int out = data.readFileDescriptor();
         int err = data.readFileDescriptor();
-        int argc = data.readInt32();
-        Vector<String16> args;
-        for (int i = 0; i < argc && data.dataAvail() > 0; i++) {
-           args.add(data.readString16());
-        }
+        std::vector<String16> args;
+        data.readString16Vector(&args);
         sp<IBinder> unusedCallback;
         sp<IResultReceiver> resultReceiver;
         if ((status = data.readNullableStrongBinder(&unusedCallback)) != OK)
@@ -64,20 +57,17 @@
     }
 }
 
-// ----------------------------------------------------------------------------
-
 namespace {
     status_t cmd_help(int out);
     status_t cmd_vkjson(int out, int err);
 }
 
-const char* const GpuService::SERVICE_NAME = "gpu";
+const char* const GpuService::SERVICE_NAME = "gpuservice";
 
-GpuService::GpuService() {}
+GpuService::GpuService() = default;
 
 status_t GpuService::shellCommand(int /*in*/, int out, int err,
-        Vector<String16>& args)
-{
+                                  std::vector<String16>& args) {
     ALOGV("GpuService::shellCommand");
     for (size_t i = 0, n = args.size(); i < n; i++)
         ALOGV("  arg[%zu]: '%s'", i, String8(args[i]).string());
@@ -93,8 +83,6 @@
     return BAD_VALUE;
 }
 
-// ----------------------------------------------------------------------------
-
 namespace {
 
 status_t cmd_help(int out) {
diff --git a/services/surfaceflinger/GpuService.h b/services/gpuservice/GpuService.h
similarity index 80%
rename from services/surfaceflinger/GpuService.h
rename to services/gpuservice/GpuService.h
index b8c28d2..e2b396e 100644
--- a/services/surfaceflinger/GpuService.h
+++ b/services/gpuservice/GpuService.h
@@ -17,6 +17,8 @@
 #ifndef ANDROID_GPUSERVICE_H
 #define ANDROID_GPUSERVICE_H
 
+#include <vector>
+
 #include <binder/IInterface.h>
 #include <cutils/compiler.h>
 
@@ -34,22 +36,21 @@
 class BnGpuService: public BnInterface<IGpuService> {
 protected:
     virtual status_t shellCommand(int in, int out, int err,
-        Vector<String16>& args) = 0;
+                                  std::vector<String16>& args) = 0;
 
-    virtual status_t onTransact(uint32_t code, const Parcel& data,
+    status_t onTransact(uint32_t code, const Parcel& data,
             Parcel* reply, uint32_t flags = 0) override;
 };
 
-class GpuService : public BnGpuService
-{
+class GpuService : public BnGpuService {
 public:
     static const char* const SERVICE_NAME ANDROID_API;
 
     GpuService() ANDROID_API;
 
 protected:
-    virtual status_t shellCommand(int in, int out, int err,
-        Vector<String16>& args) override;
+    status_t shellCommand(int in, int out, int err,
+                          std::vector<String16>& args) override;
 };
 
 } // namespace android
diff --git a/services/gpuservice/gpuservice.rc b/services/gpuservice/gpuservice.rc
new file mode 100644
index 0000000..d23cf46
--- /dev/null
+++ b/services/gpuservice/gpuservice.rc
@@ -0,0 +1,4 @@
+service gpuservice /system/bin/gpuservice
+    class core
+    user gpu_service
+    group graphics
diff --git a/services/gpuservice/main_gpuservice.cpp b/services/gpuservice/main_gpuservice.cpp
new file mode 100644
index 0000000..64aafca
--- /dev/null
+++ b/services/gpuservice/main_gpuservice.cpp
@@ -0,0 +1,43 @@
+/*
+ * Copyright 2018 The Android Open Source Project
+ *
+ * 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.
+ */
+
+#include <binder/IPCThreadState.h>
+#include <binder/IServiceManager.h>
+#include <binder/ProcessState.h>
+#include <sys/resource.h>
+#include "GpuService.h"
+
+using namespace android;
+
+int main(int /* argc */, char** /* argv */) {
+    signal(SIGPIPE, SIG_IGN);
+
+    // publish GpuService
+    sp<GpuService> gpuservice = new GpuService();
+    sp<IServiceManager> sm(defaultServiceManager());
+    sm->addService(String16(GpuService::SERVICE_NAME), gpuservice, false);
+
+    // limit the number of binder threads to 4.
+    ProcessState::self()->setThreadPoolMaxThreadCount(4);
+
+    // start the thread pool
+    sp<ProcessState> ps(ProcessState::self());
+    ps->startThreadPool();
+    ps->giveThreadPoolName();
+    IPCThreadState::self()->joinThreadPool();
+
+    return 0;
+}
diff --git a/services/surfaceflinger/Android.bp b/services/surfaceflinger/Android.bp
index 16003a2..f168db9 100644
--- a/services/surfaceflinger/Android.bp
+++ b/services/surfaceflinger/Android.bp
@@ -19,10 +19,6 @@
         "-DGL_GLEXT_PROTOTYPES",
         "-DEGL_EGLEXT_PROTOTYPES",
     ],
-    include_dirs: [
-        "frameworks/native/vulkan/vkjson",
-        "frameworks/native/vulkan/include",
-    ],
     shared_libs: [
         "android.frameworks.vr.composer@1.0",
         "android.hardware.configstore-utils",
@@ -59,13 +55,11 @@
         "libtimestats_proto",
         "libui",
         "libutils",
-        "libvulkan",
     ],
     static_libs: [
         "librenderengine",
         "libserviceutils",
         "libtrace_proto",
-        "libvkjson",
         "libvr_manager",
         "libvrflinger",
     ],
@@ -132,7 +126,6 @@
         "Effects/Daltonizer.cpp",
         "EventLog/EventLog.cpp",
         "FrameTracker.cpp",
-        "GpuService.cpp",
         "Layer.cpp",
         "LayerBE.cpp",
         "LayerProtoHelper.cpp",
@@ -153,6 +146,7 @@
         "SurfaceInterceptor.cpp",
         "SurfaceTracing.cpp",
         "TimeStats/TimeStats.cpp",
+        "TransactionCompletedThread.cpp",
     ],
 }
 
diff --git a/services/surfaceflinger/BufferLayer.cpp b/services/surfaceflinger/BufferLayer.cpp
index 440f1e2..c2f15dd 100644
--- a/services/surfaceflinger/BufferLayer.cpp
+++ b/services/surfaceflinger/BufferLayer.cpp
@@ -229,15 +229,14 @@
             getBE().compositionInfo.mBuffer->getPixelFormat() == HAL_PIXEL_FORMAT_RGBA_1010102);
 }
 
-void BufferLayer::setPerFrameData(const sp<const DisplayDevice>& display) {
+void BufferLayer::setPerFrameData(DisplayId displayId, const ui::Transform& transform,
+                                  const Rect& viewport, int32_t supportedPerFrameMetadata) {
     // Apply this display's projection's viewport to the visible region
     // before giving it to the HWC HAL.
-    const ui::Transform& tr = display->getTransform();
-    const auto& viewport = display->getViewport();
-    Region visible = tr.transform(visibleRegion.intersect(viewport));
-    const auto displayId = display->getId();
+    Region visible = transform.transform(visibleRegion.intersect(viewport));
+
     if (!hasHwcLayer(displayId)) {
-        ALOGE("[%s] failed to setPerFrameData: no HWC layer found (%d)",
+        ALOGE("[%s] failed to setPerFrameData: no HWC layer found for display %" PRIu64,
               mName.string(), displayId);
         return;
     }
@@ -290,7 +289,7 @@
     }
 
     const HdrMetadata& metadata = getDrawingHdrMetadata();
-    error = hwcLayer->setPerFrameMetadata(display->getSupportedPerFrameMetadata(), metadata);
+    error = hwcLayer->setPerFrameMetadata(supportedPerFrameMetadata, metadata);
     if (error != HWC2::Error::None && error != HWC2::Error::Unsupported) {
         ALOGE("[%s] Failed to set hdrMetadata: %s (%d)", mName.string(),
               to_string(error).c_str(), static_cast<int32_t>(error));
@@ -303,10 +302,10 @@
     }
     getBE().compositionInfo.hwc.dataspace = mCurrentDataSpace;
     getBE().compositionInfo.hwc.hdrMetadata = getDrawingHdrMetadata();
-    getBE().compositionInfo.hwc.supportedPerFrameMetadata = display->getSupportedPerFrameMetadata();
+    getBE().compositionInfo.hwc.supportedPerFrameMetadata = supportedPerFrameMetadata;
     getBE().compositionInfo.hwc.colorTransform = getColorTransform();
 
-    setHwcLayerBuffer(display);
+    setHwcLayerBuffer(displayId);
 }
 
 bool BufferLayer::onPreComposition(nsecs_t refreshStartTime) {
@@ -318,10 +317,10 @@
     return hasReadyFrame();
 }
 
-bool BufferLayer::onPostComposition(const std::shared_ptr<FenceTime>& glDoneFence,
+bool BufferLayer::onPostComposition(const std::optional<DisplayId>& displayId,
+                                    const std::shared_ptr<FenceTime>& glDoneFence,
                                     const std::shared_ptr<FenceTime>& presentFence,
                                     const CompositorTiming& compositorTiming) {
-
     // mFrameLatencyNeeded is true when a new frame was latched for the
     // composition.
     if (!mFrameLatencyNeeded) return false;
@@ -352,11 +351,10 @@
     if (presentFence->isValid()) {
         mTimeStats.setPresentFence(layerID, mCurrentFrameNumber, presentFence);
         mFrameTracker.setActualPresentFence(std::shared_ptr<FenceTime>(presentFence));
-    } else if (mFlinger->getHwComposer().isConnected(HWC_DISPLAY_PRIMARY)) {
+    } else if (displayId && mFlinger->getHwComposer().isConnected(*displayId)) {
         // The HWC doesn't support present fences, so use the refresh
         // timestamp instead.
-        const nsecs_t actualPresentTime =
-                mFlinger->getHwComposer().getRefreshTimestamp(HWC_DISPLAY_PRIMARY);
+        const nsecs_t actualPresentTime = mFlinger->getHwComposer().getRefreshTimestamp(*displayId);
         mTimeStats.setPresentTime(layerID, mCurrentFrameNumber, actualPresentTime);
         mFrameTracker.setActualPresentTime(actualPresentTime);
     }
@@ -522,7 +520,7 @@
 }
 
 bool BufferLayer::hasReadyFrame() const {
-    return hasDrawingBuffer() || getSidebandStreamChanged() || getAutoRefresh();
+    return hasFrameUpdate() || getSidebandStreamChanged() || getAutoRefresh();
 }
 
 uint32_t BufferLayer::getEffectiveScalingMode() const {
@@ -660,7 +658,7 @@
 }
 
 uint64_t BufferLayer::getHeadFrameNumber() const {
-    if (hasDrawingBuffer()) {
+    if (hasFrameUpdate()) {
         return getFrameNumber();
     } else {
         return mCurrentFrameNumber;
diff --git a/services/surfaceflinger/BufferLayer.h b/services/surfaceflinger/BufferLayer.h
index d000d85..f352256 100644
--- a/services/surfaceflinger/BufferLayer.h
+++ b/services/surfaceflinger/BufferLayer.h
@@ -80,10 +80,12 @@
 
     bool isHdrY410() const override;
 
-    void setPerFrameData(const sp<const DisplayDevice>& displayDevice) override;
+    void setPerFrameData(DisplayId displayId, const ui::Transform& transform, const Rect& viewport,
+                         int32_t supportedPerFrameMetadata) override;
 
     bool onPreComposition(nsecs_t refreshStartTime) override;
-    bool onPostComposition(const std::shared_ptr<FenceTime>& glDoneFence,
+    bool onPostComposition(const std::optional<DisplayId>& displayId,
+                           const std::shared_ptr<FenceTime>& glDoneFence,
                            const std::shared_ptr<FenceTime>& presentFence,
                            const CompositorTiming& compositorTiming) override;
 
@@ -134,7 +136,7 @@
 
     virtual std::optional<Region> latchSidebandStream(bool& recomputeVisibleRegions) = 0;
 
-    virtual bool hasDrawingBuffer() const = 0;
+    virtual bool hasFrameUpdate() const = 0;
 
     virtual void setFilteringEnabled(bool enabled) = 0;
 
@@ -145,7 +147,7 @@
     virtual status_t updateActiveBuffer() = 0;
     virtual status_t updateFrameNumber(nsecs_t latchTime) = 0;
 
-    virtual void setHwcLayerBuffer(const sp<const DisplayDevice>& display) = 0;
+    virtual void setHwcLayerBuffer(DisplayId displayId) = 0;
 
     // -----------------------------------------------------------------------
 
diff --git a/services/surfaceflinger/BufferQueueLayer.cpp b/services/surfaceflinger/BufferQueueLayer.cpp
index c130bc5..78ab23a 100644
--- a/services/surfaceflinger/BufferQueueLayer.cpp
+++ b/services/surfaceflinger/BufferQueueLayer.cpp
@@ -82,7 +82,7 @@
         return true;
     }
 
-    if (!hasDrawingBuffer()) {
+    if (!hasFrameUpdate()) {
         return false;
     }
 
@@ -110,7 +110,7 @@
         return true;
     }
 
-    if (!hasDrawingBuffer()) {
+    if (!hasFrameUpdate()) {
         return true;
     }
 
@@ -206,7 +206,7 @@
     return {};
 }
 
-bool BufferQueueLayer::hasDrawingBuffer() const {
+bool BufferQueueLayer::hasFrameUpdate() const {
     return mQueuedFrames > 0;
 }
 
@@ -325,8 +325,7 @@
     return NO_ERROR;
 }
 
-void BufferQueueLayer::setHwcLayerBuffer(const sp<const DisplayDevice>& display) {
-    const auto displayId = display->getId();
+void BufferQueueLayer::setHwcLayerBuffer(DisplayId displayId) {
     auto& hwcInfo = getBE().mHwcLayers[displayId];
     auto& hwcLayer = hwcInfo.layer;
 
@@ -353,6 +352,11 @@
 void BufferQueueLayer::onFrameAvailable(const BufferItem& item) {
     // Add this buffer from our internal queue tracker
     { // Autolock scope
+        // Report the timestamp to the Scheduler.
+        if (mFlinger->mUseScheduler) {
+            mFlinger->mScheduler->addNewFrameTimestamp(item.mTimestamp, item.mIsAutoTimestamp);
+        }
+
         Mutex::Autolock lock(mQueueItemLock);
         // Reset the frame number tracker when we receive the first buffer after
         // a frame number reset
@@ -403,7 +407,7 @@
             }
         }
 
-        if (!hasDrawingBuffer()) {
+        if (!hasFrameUpdate()) {
             ALOGE("Can't replace a frame on an empty queue");
             return;
         }
diff --git a/services/surfaceflinger/BufferQueueLayer.h b/services/surfaceflinger/BufferQueueLayer.h
index c9ebe04..ae0b705 100644
--- a/services/surfaceflinger/BufferQueueLayer.h
+++ b/services/surfaceflinger/BufferQueueLayer.h
@@ -83,7 +83,7 @@
 
     std::optional<Region> latchSidebandStream(bool& recomputeVisibleRegions) override;
 
-    bool hasDrawingBuffer() const override;
+    bool hasFrameUpdate() const override;
 
     void setFilteringEnabled(bool enabled) override;
 
@@ -94,8 +94,7 @@
     status_t updateActiveBuffer() override;
     status_t updateFrameNumber(nsecs_t latchTime) override;
 
-    void setHwcLayerBuffer(const sp<const DisplayDevice>& display) override;
-    // -----------------------------------------------------------------------
+    void setHwcLayerBuffer(DisplayId displayId) override;
 
     // -----------------------------------------------------------------------
     // Interface implementation for BufferLayerConsumer::ContentsChangedListener
diff --git a/services/surfaceflinger/BufferStateLayer.cpp b/services/surfaceflinger/BufferStateLayer.cpp
index 73098bf..ea64ca7 100644
--- a/services/surfaceflinger/BufferStateLayer.cpp
+++ b/services/surfaceflinger/BufferStateLayer.cpp
@@ -41,9 +41,11 @@
 // -----------------------------------------------------------------------
 // Interface implementation for Layer
 // -----------------------------------------------------------------------
-void BufferStateLayer::onLayerDisplayed(const sp<Fence>& /*releaseFence*/) {
-    // TODO(marissaw): send the release fence back to buffer owner
-    return;
+void BufferStateLayer::onLayerDisplayed(const sp<Fence>& releaseFence) {
+    // The transaction completed callback can only be sent if the release fence from the PREVIOUS
+    // frame has fired. In practice, we should never actually wait on the previous release fence
+    // but we should store it just in case.
+    mPreviousReleaseFence = releaseFence;
 }
 
 void BufferStateLayer::setTransformHint(uint32_t /*orientation*/) const {
@@ -52,7 +54,6 @@
 }
 
 void BufferStateLayer::releasePendingBuffer(nsecs_t /*dequeueReadyTime*/) {
-    // TODO(marissaw): use this to signal the buffer owner
     return;
 }
 
@@ -61,7 +62,13 @@
         return true;
     }
 
-    return hasDrawingBuffer();
+    return hasFrameUpdate();
+}
+
+bool BufferStateLayer::willPresentCurrentTransaction() const {
+    // Returns true if the most recent Transaction applied to CurrentState will be presented.
+    return getSidebandStreamChanged() || getAutoRefresh() ||
+            (mCurrentState.modified && mCurrentState.buffer != nullptr);
 }
 
 bool BufferStateLayer::getTransformToDisplayInverse() const {
@@ -81,6 +88,7 @@
     while (!mPendingStates.empty()) {
         popPendingState(stateToCommit);
     }
+    mCurrentStateModified = stateUpdateAvailable && mCurrentState.modified;
     mCurrentState.modified = false;
     return stateUpdateAvailable;
 }
@@ -118,7 +126,11 @@
     return true;
 }
 
-bool BufferStateLayer::setBuffer(sp<GraphicBuffer> buffer) {
+bool BufferStateLayer::setBuffer(const sp<GraphicBuffer>& buffer) {
+    if (mCurrentState.buffer) {
+        mReleasePreviousBuffer = true;
+    }
+
     mCurrentState.sequence++;
     mCurrentState.buffer = buffer;
     mCurrentState.modified = true;
@@ -127,6 +139,9 @@
 }
 
 bool BufferStateLayer::setAcquireFence(const sp<Fence>& fence) {
+    // The acquire fences of BufferStateLayers have already signaled before they are set
+    mCallbackHandleAcquireTime = fence->getSignalTime();
+
     mCurrentState.acquireFence = fence;
     mCurrentState.modified = true;
     setTransactionFlags(eTransactionNeeded);
@@ -182,6 +197,44 @@
     return true;
 }
 
+bool BufferStateLayer::setTransactionCompletedListeners(
+        const std::vector<sp<CallbackHandle>>& handles) {
+    // If there is no handle, we will not send a callback so reset mReleasePreviousBuffer and return
+    if (handles.empty()) {
+        mReleasePreviousBuffer = false;
+        return false;
+    }
+
+    const bool willPresent = willPresentCurrentTransaction();
+
+    for (const auto& handle : handles) {
+        // If this transaction set a buffer on this layer, release its previous buffer
+        handle->releasePreviousBuffer = mReleasePreviousBuffer;
+
+        // If this layer will be presented in this frame
+        if (willPresent) {
+            // If this transaction set an acquire fence on this layer, set its acquire time
+            handle->acquireTime = mCallbackHandleAcquireTime;
+
+            // Notify the transaction completed thread that there is a pending latched callback
+            // handle
+            mFlinger->getTransactionCompletedThread().registerPendingLatchedCallbackHandle(handle);
+
+            // Store so latched time and release fence can be set
+            mCurrentState.callbackHandles.push_back(handle);
+
+        } else { // If this layer will NOT need to be relatched and presented this frame
+            // Notify the transaction completed thread this handle is done
+            mFlinger->getTransactionCompletedThread().addUnlatchedCallbackHandle(handle);
+        }
+    }
+
+    mReleasePreviousBuffer = false;
+    mCallbackHandleAcquireTime = -1;
+
+    return willPresent;
+}
+
 bool BufferStateLayer::setSize(uint32_t w, uint32_t h) {
     if (mCurrentState.active.w == w && mCurrentState.active.h == h) return false;
     mCurrentState.active.w = w;
@@ -265,7 +318,7 @@
 }
 
 uint32_t BufferStateLayer::getDrawingScalingMode() const {
-    return NATIVE_WINDOW_SCALING_MODE_FREEZE;
+    return NATIVE_WINDOW_SCALING_MODE_SCALE_TO_WINDOW;
 }
 
 Region BufferStateLayer::getDrawingSurfaceDamage() const {
@@ -314,8 +367,8 @@
     return {};
 }
 
-bool BufferStateLayer::hasDrawingBuffer() const {
-    return getDrawingState().buffer != nullptr;
+bool BufferStateLayer::hasFrameUpdate() const {
+    return mCurrentStateModified && getCurrentState().buffer != nullptr;
 }
 
 void BufferStateLayer::setFilteringEnabled(bool enabled) {
@@ -403,6 +456,10 @@
         return BAD_VALUE;
     }
 
+    mFlinger->getTransactionCompletedThread()
+            .addLatchedCallbackHandles(getDrawingState().callbackHandles, latchTime,
+                                       mPreviousReleaseFence);
+
     // Handle sync fences
     if (SyncFeatures::getInstance().useNativeFenceSync() && releaseFence != Fence::NO_FENCE) {
         // TODO(alecmouri): Fail somewhere upstream if the fence is invalid.
@@ -497,8 +554,7 @@
     return NO_ERROR;
 }
 
-void BufferStateLayer::setHwcLayerBuffer(const sp<const DisplayDevice>& display) {
-    const auto displayId = display->getId();
+void BufferStateLayer::setHwcLayerBuffer(DisplayId displayId) {
     auto& hwcInfo = getBE().mHwcLayers[displayId];
     auto& hwcLayer = hwcInfo.layer;
 
@@ -513,6 +569,7 @@
               s.buffer->handle, to_string(error).c_str(), static_cast<int32_t>(error));
     }
 
+    mCurrentStateModified = false;
     mFrameNumber++;
 }
 
diff --git a/services/surfaceflinger/BufferStateLayer.h b/services/surfaceflinger/BufferStateLayer.h
index 0c6eaf5..315d5af 100644
--- a/services/surfaceflinger/BufferStateLayer.h
+++ b/services/surfaceflinger/BufferStateLayer.h
@@ -62,13 +62,14 @@
     bool setTransform(uint32_t transform) override;
     bool setTransformToDisplayInverse(bool transformToDisplayInverse) override;
     bool setCrop(const Rect& crop) override;
-    bool setBuffer(sp<GraphicBuffer> buffer) override;
+    bool setBuffer(const sp<GraphicBuffer>& buffer) override;
     bool setAcquireFence(const sp<Fence>& fence) override;
     bool setDataspace(ui::Dataspace dataspace) override;
     bool setHdrMetadata(const HdrMetadata& hdrMetadata) override;
     bool setSurfaceDamageRegion(const Region& surfaceDamage) override;
     bool setApi(int32_t api) override;
     bool setSidebandStream(const sp<NativeHandle>& sidebandStream) override;
+    bool setTransactionCompletedListeners(const std::vector<sp<CallbackHandle>>& handles) override;
 
     bool setSize(uint32_t w, uint32_t h) override;
     bool setPosition(float x, float y, bool immediate) override;
@@ -110,7 +111,7 @@
 
     std::optional<Region> latchSidebandStream(bool& recomputeVisibleRegions) override;
 
-    bool hasDrawingBuffer() const override;
+    bool hasFrameUpdate() const override;
 
     void setFilteringEnabled(bool enabled) override;
 
@@ -121,10 +122,11 @@
     status_t updateActiveBuffer() override;
     status_t updateFrameNumber(nsecs_t latchTime) override;
 
-    void setHwcLayerBuffer(const sp<const DisplayDevice>& display) override;
-    // -----------------------------------------------------------------------
+    void setHwcLayerBuffer(DisplayId displayId) override;
+
 private:
     void onFirstRef() override;
+    bool willPresentCurrentTransaction() const;
 
     static const std::array<float, 16> IDENTITY_MATRIX;
 
@@ -136,6 +138,12 @@
 
     uint32_t mFrameNumber{0};
 
+    sp<Fence> mPreviousReleaseFence;
+
+    bool mCurrentStateModified = false;
+    bool mReleasePreviousBuffer = false;
+    nsecs_t mCallbackHandleAcquireTime = -1;
+
     // TODO(marissaw): support sticky transform for LEGACY camera mode
 };
 
diff --git a/services/surfaceflinger/ColorLayer.cpp b/services/surfaceflinger/ColorLayer.cpp
index 263f872..ad716ef 100644
--- a/services/surfaceflinger/ColorLayer.cpp
+++ b/services/surfaceflinger/ColorLayer.cpp
@@ -57,13 +57,12 @@
     return !isHiddenByPolicy() && getAlpha() > 0.0f;
 }
 
-void ColorLayer::setPerFrameData(const sp<const DisplayDevice>& display) {
-    const ui::Transform& tr = display->getTransform();
-    const auto& viewport = display->getViewport();
-    Region visible = tr.transform(visibleRegion.intersect(viewport));
-    const auto displayId = display->getId();
+void ColorLayer::setPerFrameData(DisplayId displayId, const ui::Transform& transform,
+                                 const Rect& viewport, int32_t /* supportedPerFrameMetadata */) {
+    Region visible = transform.transform(visibleRegion.intersect(viewport));
+
     if (!hasHwcLayer(displayId)) {
-        ALOGE("[%s] failed to setPerFrameData: no HWC layer found (%d)",
+        ALOGE("[%s] failed to setPerFrameData: no HWC layer found for display %" PRIu64,
               mName.string(), displayId);
         return;
     }
diff --git a/services/surfaceflinger/ColorLayer.h b/services/surfaceflinger/ColorLayer.h
index 2febe9a..d1b1697 100644
--- a/services/surfaceflinger/ColorLayer.h
+++ b/services/surfaceflinger/ColorLayer.h
@@ -33,7 +33,8 @@
                         bool useIdentityTransform);
     bool isVisible() const override;
 
-    void setPerFrameData(const sp<const DisplayDevice>& display) override;
+    void setPerFrameData(DisplayId displayId, const ui::Transform& transform, const Rect& viewport,
+                         int32_t supportedPerFrameMetadata) override;
 
     bool onPreComposition(nsecs_t /*refreshStartTime*/) override { return false; }
 
diff --git a/services/surfaceflinger/ContainerLayer.cpp b/services/surfaceflinger/ContainerLayer.cpp
index 44e843e..ca49f6c 100644
--- a/services/surfaceflinger/ContainerLayer.cpp
+++ b/services/surfaceflinger/ContainerLayer.cpp
@@ -32,6 +32,6 @@
     return !isHiddenByPolicy();
 }
 
-void ContainerLayer::setPerFrameData(const sp<const DisplayDevice>&) {}
+void ContainerLayer::setPerFrameData(DisplayId, const ui::Transform&, const Rect&, int32_t) {}
 
 } // namespace android
diff --git a/services/surfaceflinger/ContainerLayer.h b/services/surfaceflinger/ContainerLayer.h
index 316db83..413844b 100644
--- a/services/surfaceflinger/ContainerLayer.h
+++ b/services/surfaceflinger/ContainerLayer.h
@@ -33,7 +33,8 @@
                 bool useIdentityTransform) override;
     bool isVisible() const override;
 
-    void setPerFrameData(const sp<const DisplayDevice>& display) override;
+    void setPerFrameData(DisplayId displayId, const ui::Transform& transform, const Rect& viewport,
+                         int32_t supportedPerFrameMetadata) override;
 
     bool isCreatedFromMainThread() const override { return true; }
 
diff --git a/services/surfaceflinger/DisplayDevice.cpp b/services/surfaceflinger/DisplayDevice.cpp
index 91b18c9..7c5c1bc 100644
--- a/services/surfaceflinger/DisplayDevice.cpp
+++ b/services/surfaceflinger/DisplayDevice.cpp
@@ -212,15 +212,14 @@
 
 DisplayDeviceCreationArgs::DisplayDeviceCreationArgs(const sp<SurfaceFlinger>& flinger,
                                                      const wp<IBinder>& displayToken,
-                                                     DisplayDevice::DisplayType type, int32_t id)
-      : flinger(flinger), displayToken(displayToken), type(type), id(id) {}
+                                                     const std::optional<DisplayId>& displayId)
+      : flinger(flinger), displayToken(displayToken), displayId(displayId) {}
 
 DisplayDevice::DisplayDevice(DisplayDeviceCreationArgs&& args)
       : lastCompositionHadVisibleLayers(false),
         mFlinger(args.flinger),
-        mType(args.type),
-        mId(args.id),
         mDisplayToken(args.displayToken),
+        mId(args.displayId),
         mNativeWindow(args.nativeWindow),
         mDisplaySurface(args.displaySurface),
         mSurface{std::move(args.renderSurface)},
@@ -228,6 +227,7 @@
         mDisplayHeight(args.displayHeight),
         mDisplayInstallOrientation(args.displayInstallOrientation),
         mPageFlipCount(0),
+        mIsVirtual(args.isVirtual),
         mIsSecure(args.isSecure),
         mLayerStack(NO_LAYER_STACK),
         mOrientation(),
@@ -240,7 +240,8 @@
         mHasHdr10(false),
         mHasHLG(false),
         mHasDolbyVision(false),
-        mSupportedPerFrameMetadata(args.supportedPerFrameMetadata) {
+        mSupportedPerFrameMetadata(args.supportedPerFrameMetadata),
+        mIsPrimary(args.isPrimary) {
     populateColorModes(args.hwcColorModes);
 
     ALOGE_IF(!mNativeWindow, "No native window was set for display");
@@ -293,16 +294,12 @@
 DisplayDevice::~DisplayDevice() = default;
 
 void DisplayDevice::disconnect(HWComposer& hwc) {
-    if (mId >= 0) {
-        hwc.disconnectDisplay(mId);
-        mId = -1;
+    if (mId) {
+        hwc.disconnectDisplay(*mId);
+        mId.reset();
     }
 }
 
-bool DisplayDevice::isValid() const {
-    return mFlinger != nullptr;
-}
-
 int DisplayDevice::getWidth() const {
     return mDisplayWidth;
 }
@@ -334,9 +331,11 @@
 
 status_t DisplayDevice::prepareFrame(HWComposer& hwc,
         std::vector<CompositionInfo>& compositionData) {
-    status_t error = hwc.prepare(*this, compositionData);
-    if (error != NO_ERROR) {
-        return error;
+    if (mId) {
+        status_t error = hwc.prepare(*mId, compositionData);
+        if (error != NO_ERROR) {
+            return error;
+        }
     }
 
     DisplaySurface::CompositionType compositionType;
@@ -600,7 +599,7 @@
 
     // need to take care of primary display rotation for mGlobalTransform
     // for case if the panel is not installed aligned with device orientation
-    if (mType == DisplayType::DISPLAY_PRIMARY) {
+    if (isPrimary()) {
         DisplayDevice::orientationToTransfrom(
                 (orientation + mDisplayInstallOrientation) % (DisplayState::eOrientation270 + 1),
                 w, h, &R);
@@ -648,7 +647,7 @@
 }
 
 std::string DisplayDevice::getDebugName() const {
-    const auto id = mId >= 0 ? base::StringPrintf("%d, ", mId) : std::string();
+    const auto id = mId ? base::StringPrintf("%" PRIu64 ", ", *mId) : std::string();
     return base::StringPrintf("DisplayDevice{%s%s%s\"%s\"}", id.c_str(),
                               isPrimary() ? "primary, " : "", isVirtual() ? "virtual, " : "",
                               mDisplayName.c_str());
diff --git a/services/surfaceflinger/DisplayDevice.h b/services/surfaceflinger/DisplayDevice.h
index 152d0ec..9ec7666 100644
--- a/services/surfaceflinger/DisplayDevice.h
+++ b/services/surfaceflinger/DisplayDevice.h
@@ -20,6 +20,7 @@
 #include <stdlib.h>
 
 #include <memory>
+#include <optional>
 #include <string>
 #include <unordered_map>
 
@@ -37,6 +38,7 @@
 #include <utils/String8.h>
 #include <utils/Timers.h>
 
+#include "DisplayHardware/DisplayIdentification.h"
 #include "RenderArea.h"
 
 struct ANativeWindow;
@@ -66,25 +68,15 @@
     Region undefinedRegion;
     bool lastCompositionHadVisibleLayers;
 
-    enum DisplayType {
-        DISPLAY_ID_INVALID = -1,
-        DISPLAY_PRIMARY     = HWC_DISPLAY_PRIMARY,
-        DISPLAY_EXTERNAL    = HWC_DISPLAY_EXTERNAL,
-        DISPLAY_VIRTUAL     = HWC_DISPLAY_VIRTUAL,
-        NUM_BUILTIN_DISPLAY_TYPES = HWC_NUM_PHYSICAL_DISPLAY_TYPES,
-    };
-
     enum {
         NO_LAYER_STACK = 0xFFFFFFFF,
     };
 
     explicit DisplayDevice(DisplayDeviceCreationArgs&& args);
-
     ~DisplayDevice();
 
-    // whether this is a valid object. An invalid DisplayDevice is returned
-    // when an non existing id is requested
-    bool isValid() const;
+    bool isVirtual() const { return mIsVirtual; }
+    bool isPrimary() const { return mIsPrimary; }
 
     // isSecure indicates whether this display can be trusted to display
     // secure surfaces.
@@ -118,11 +110,9 @@
     bool                    needsFiltering() const { return mNeedsFiltering; }
 
     uint32_t                getLayerStack() const { return mLayerStack; }
-    int32_t                 getDisplayType() const { return mType; }
-    bool                    isPrimary() const { return mType == DISPLAY_PRIMARY; }
-    bool                    isVirtual() const { return mType == DISPLAY_VIRTUAL; }
-    int32_t                 getId() const { return mId; }
-    const wp<IBinder>&      getDisplayToken() const { return mDisplayToken; }
+
+    const std::optional<DisplayId>& getId() const { return mId; }
+    const wp<IBinder>& getDisplayToken() const { return mDisplayToken; }
 
     int32_t getSupportedPerFrameMetadata() const { return mSupportedPerFrameMetadata; }
 
@@ -207,13 +197,10 @@
     void dump(String8& result) const;
 
 private:
-    /*
-     *  Constants, set during initialization
-     */
-    sp<SurfaceFlinger> mFlinger;
-    DisplayType mType;
-    int32_t mId;
-    wp<IBinder> mDisplayToken;
+    const sp<SurfaceFlinger> mFlinger;
+    const wp<IBinder> mDisplayToken;
+
+    std::optional<DisplayId> mId;
 
     // ANativeWindow this display is rendering into
     sp<ANativeWindow> mNativeWindow;
@@ -225,7 +212,9 @@
     const int       mDisplayInstallOrientation;
     mutable uint32_t mPageFlipCount;
     std::string     mDisplayName;
-    bool            mIsSecure;
+
+    const bool mIsVirtual;
+    const bool mIsSecure;
 
     /*
      * Can only accessed from the main thread, these members
@@ -299,13 +288,16 @@
             const ui::ColorMode mode, const ui::RenderIntent intent);
 
     std::unordered_map<ColorModeKey, ColorModeValue> mColorModes;
+
+    // TODO(b/74619554): Remove special cases for primary display.
+    const bool mIsPrimary;
 };
 
 struct DisplayDeviceState {
-    bool isVirtual() const { return type >= DisplayDevice::DISPLAY_VIRTUAL; }
+    bool isVirtual() const { return !displayId.has_value(); }
 
     int32_t sequenceId = sNextSequenceId++;
-    DisplayDevice::DisplayType type = DisplayDevice::DISPLAY_ID_INVALID;
+    std::optional<DisplayId> displayId;
     sp<IGraphicBufferProducer> surface;
     uint32_t layerStack = DisplayDevice::NO_LAYER_STACK;
     Rect viewport;
@@ -324,13 +316,13 @@
     // We use a constructor to ensure some of the values are set, without
     // assuming a default value.
     DisplayDeviceCreationArgs(const sp<SurfaceFlinger>& flinger, const wp<IBinder>& displayToken,
-                              DisplayDevice::DisplayType type, int32_t id);
+                              const std::optional<DisplayId>& displayId);
 
     const sp<SurfaceFlinger> flinger;
     const wp<IBinder> displayToken;
-    const DisplayDevice::DisplayType type;
-    const int32_t id;
+    const std::optional<DisplayId> displayId;
 
+    bool isVirtual{false};
     bool isSecure{false};
     sp<ANativeWindow> nativeWindow;
     sp<DisplaySurface> displaySurface;
@@ -343,6 +335,7 @@
     int32_t supportedPerFrameMetadata{0};
     std::unordered_map<ui::ColorMode, std::vector<ui::RenderIntent>> hwcColorModes;
     int initialPowerMode{HWC_POWER_MODE_NORMAL};
+    bool isPrimary{false};
 };
 
 class DisplayRenderArea : public RenderArea {
diff --git a/services/surfaceflinger/DisplayHardware/DisplayIdentification.cpp b/services/surfaceflinger/DisplayHardware/DisplayIdentification.cpp
index dcc4138..ec240f3 100644
--- a/services/surfaceflinger/DisplayHardware/DisplayIdentification.cpp
+++ b/services/surfaceflinger/DisplayHardware/DisplayIdentification.cpp
@@ -33,6 +33,9 @@
 
 constexpr size_t kEdidHeaderLength = 5;
 
+constexpr uint16_t kFallbackEdidManufacturerId = 0;
+constexpr uint16_t kVirtualEdidManufacturerId = 0xffffu;
+
 std::optional<uint8_t> getEdidDescriptorType(const byte_view& view) {
     if (view.size() < kEdidHeaderLength || view[0] || view[1] || view[2] || view[4]) {
         return {};
@@ -165,7 +168,8 @@
     return a && b && c ? std::make_optional(PnpId{a, b, c}) : std::nullopt;
 }
 
-std::optional<DisplayId> generateDisplayId(uint8_t port, const DisplayIdentificationData& data) {
+std::optional<DisplayIdentificationInfo> parseDisplayIdentificationData(
+        uint8_t port, const DisplayIdentificationData& data) {
     if (!isEdid(data)) {
         ALOGE("Display identification data has unknown format.");
         return {};
@@ -179,7 +183,16 @@
     // Hash display name instead of using product code or serial number, since the latter have been
     // observed to change on some displays with multiple inputs.
     const auto hash = static_cast<uint32_t>(std::hash<std::string_view>()(edid->displayName));
-    return getEdidDisplayId(port, edid->manufacturerId, hash);
+    return DisplayIdentificationInfo{getEdidDisplayId(port, edid->manufacturerId, hash),
+                                     std::string(edid->displayName)};
+}
+
+DisplayId getFallbackDisplayId(uint8_t port) {
+    return getEdidDisplayId(port, kFallbackEdidManufacturerId, 0);
+}
+
+DisplayId getVirtualDisplayId(uint32_t id) {
+    return getEdidDisplayId(0, kVirtualEdidManufacturerId, id);
 }
 
 } // namespace android
diff --git a/services/surfaceflinger/DisplayHardware/DisplayIdentification.h b/services/surfaceflinger/DisplayHardware/DisplayIdentification.h
index 379f2d3..1f2e789 100644
--- a/services/surfaceflinger/DisplayHardware/DisplayIdentification.h
+++ b/services/surfaceflinger/DisplayHardware/DisplayIdentification.h
@@ -19,6 +19,7 @@
 #include <array>
 #include <cstdint>
 #include <optional>
+#include <string>
 #include <string_view>
 #include <vector>
 
@@ -27,6 +28,11 @@
 using DisplayId = uint64_t;
 using DisplayIdentificationData = std::vector<uint8_t>;
 
+struct DisplayIdentificationInfo {
+    DisplayId id;
+    std::string name;
+};
+
 // NUL-terminated plug and play ID.
 using PnpId = std::array<char, 4>;
 
@@ -40,6 +46,10 @@
 std::optional<Edid> parseEdid(const DisplayIdentificationData&);
 std::optional<PnpId> getPnpId(uint16_t manufacturerId);
 
-std::optional<DisplayId> generateDisplayId(uint8_t port, const DisplayIdentificationData&);
+std::optional<DisplayIdentificationInfo> parseDisplayIdentificationData(
+        uint8_t port, const DisplayIdentificationData&);
+
+DisplayId getFallbackDisplayId(uint8_t port);
+DisplayId getVirtualDisplayId(uint32_t id);
 
 } // namespace android
diff --git a/services/surfaceflinger/DisplayHardware/FramebufferSurface.cpp b/services/surfaceflinger/DisplayHardware/FramebufferSurface.cpp
index e6d7834..f3f8d9d 100644
--- a/services/surfaceflinger/DisplayHardware/FramebufferSurface.cpp
+++ b/services/surfaceflinger/DisplayHardware/FramebufferSurface.cpp
@@ -52,26 +52,25 @@
  *
  */
 
-FramebufferSurface::FramebufferSurface(HWComposer& hwc, int disp,
-        const sp<IGraphicBufferConsumer>& consumer) :
-    ConsumerBase(consumer),
-    mDisplayType(disp),
-    mCurrentBufferSlot(-1),
-    mCurrentBuffer(),
-    mCurrentFence(Fence::NO_FENCE),
-    mHwc(hwc),
-    mHasPendingRelease(false),
-    mPreviousBufferSlot(BufferQueue::INVALID_BUFFER_SLOT),
-    mPreviousBuffer()
-{
-    ALOGV("Creating for display %d", disp);
+FramebufferSurface::FramebufferSurface(HWComposer& hwc, DisplayId displayId,
+                                       const sp<IGraphicBufferConsumer>& consumer)
+      : ConsumerBase(consumer),
+        mDisplayId(displayId),
+        mCurrentBufferSlot(-1),
+        mCurrentBuffer(),
+        mCurrentFence(Fence::NO_FENCE),
+        mHwc(hwc),
+        mHasPendingRelease(false),
+        mPreviousBufferSlot(BufferQueue::INVALID_BUFFER_SLOT),
+        mPreviousBuffer() {
+    ALOGV("Creating for display %" PRIu64, displayId);
 
     mName = "FramebufferSurface";
     mConsumer->setConsumerName(mName);
     mConsumer->setConsumerUsageBits(GRALLOC_USAGE_HW_FB |
                                        GRALLOC_USAGE_HW_RENDER |
                                        GRALLOC_USAGE_HW_COMPOSER);
-    const auto& activeConfig = mHwc.getActiveConfig(disp);
+    const auto& activeConfig = mHwc.getActiveConfig(displayId);
     mConsumer->setDefaultBufferSize(activeConfig->getWidth(),
             activeConfig->getHeight());
     mConsumer->setMaxAcquiredBufferCount(
@@ -142,8 +141,7 @@
     mHwcBufferCache.getHwcBuffer(mCurrentBufferSlot, mCurrentBuffer,
             &outSlot, &outBuffer);
     outDataspace = static_cast<Dataspace>(item.mDataSpace);
-    status_t result =
-            mHwc.setClientTarget(mDisplayType, outSlot, outFence, outBuffer, outDataspace);
+    status_t result = mHwc.setClientTarget(mDisplayId, outSlot, outFence, outBuffer, outDataspace);
     if (result != NO_ERROR) {
         ALOGE("error posting framebuffer: %d", result);
         return result;
@@ -161,7 +159,7 @@
 
 void FramebufferSurface::onFrameCommitted() {
     if (mHasPendingRelease) {
-        sp<Fence> fence = mHwc.getPresentFence(mDisplayType);
+        sp<Fence> fence = mHwc.getPresentFence(mDisplayId);
         if (fence->isValid()) {
             status_t result = addReleaseFence(mPreviousBufferSlot,
                     mPreviousBuffer, fence);
diff --git a/services/surfaceflinger/DisplayHardware/FramebufferSurface.h b/services/surfaceflinger/DisplayHardware/FramebufferSurface.h
index 0fd8e9e..2431dfd 100644
--- a/services/surfaceflinger/DisplayHardware/FramebufferSurface.h
+++ b/services/surfaceflinger/DisplayHardware/FramebufferSurface.h
@@ -17,6 +17,7 @@
 #ifndef ANDROID_SF_FRAMEBUFFER_SURFACE_H
 #define ANDROID_SF_FRAMEBUFFER_SURFACE_H
 
+#include "DisplayIdentification.h"
 #include "DisplaySurface.h"
 #include "HWComposerBufferCache.h"
 
@@ -38,7 +39,8 @@
 class FramebufferSurface : public ConsumerBase,
                            public DisplaySurface {
 public:
-    FramebufferSurface(HWComposer& hwc, int disp, const sp<IGraphicBufferConsumer>& consumer);
+    FramebufferSurface(HWComposer& hwc, DisplayId displayId,
+                       const sp<IGraphicBufferConsumer>& consumer);
 
     virtual status_t beginFrame(bool mustRecompose);
     virtual status_t prepareFrame(CompositionType compositionType);
@@ -63,8 +65,7 @@
     status_t nextBuffer(uint32_t& outSlot, sp<GraphicBuffer>& outBuffer,
             sp<Fence>& outFence, ui::Dataspace& outDataspace);
 
-    // mDisplayType must match one of the HWC display types
-    int mDisplayType;
+    const DisplayId mDisplayId;
 
     // mCurrentBufferIndex is the slot index of the current buffer or
     // INVALID_BUFFER_SLOT to indicate that either there is no current buffer
diff --git a/services/surfaceflinger/DisplayHardware/HWComposer.cpp b/services/surfaceflinger/DisplayHardware/HWComposer.cpp
index d827fd2..4c472b8 100644
--- a/services/surfaceflinger/DisplayHardware/HWComposer.cpp
+++ b/services/surfaceflinger/DisplayHardware/HWComposer.cpp
@@ -58,15 +58,15 @@
     ALOGE("%s failed for HWC display %" PRIu64 ": %s", __FUNCTION__, hwcDisplayId, msg)
 
 #define LOG_DISPLAY_ERROR(displayId, msg) \
-    ALOGE("%s failed for display %d: %s", __FUNCTION__, displayId, msg)
+    ALOGE("%s failed for display %" PRIu64 ": %s", __FUNCTION__, displayId, msg)
 
-#define LOG_HWC_ERROR(what, error, displayId)                                     \
-    ALOGE("%s: %s failed for display %d: %s (%d)", __FUNCTION__, what, displayId, \
+#define LOG_HWC_ERROR(what, error, displayId)                                              \
+    ALOGE("%s: %s failed for display %" PRIu64 ": %s (%d)", __FUNCTION__, what, displayId, \
           to_string(error).c_str(), static_cast<int32_t>(error))
 
 #define RETURN_IF_INVALID_DISPLAY(displayId, ...)            \
     do {                                                     \
-        if (!isValidDisplay(displayId)) {                    \
+        if (mDisplayData.count(displayId) == 0) {            \
             LOG_DISPLAY_ERROR(displayId, "Invalid display"); \
             return __VA_ARGS__;                              \
         }                                                    \
@@ -92,7 +92,9 @@
 HWComposer::HWComposer(std::unique_ptr<android::Hwc2::Composer> composer)
       : mHwcDevice(std::make_unique<HWC2::Device>(std::move(composer))) {}
 
-HWComposer::~HWComposer() = default;
+HWComposer::~HWComposer() {
+    mDisplayData.clear();
+}
 
 void HWComposer::registerCallback(HWC2::ComposerCallback* callback,
                                   int32_t sequenceId) {
@@ -116,11 +118,6 @@
     return mHwcDevice->getCapabilities().count(capability) > 0;
 }
 
-bool HWComposer::isValidDisplay(int32_t displayId) const {
-    return static_cast<size_t>(displayId) < mDisplayData.size() &&
-            mDisplayData[displayId].hwcDisplay;
-}
-
 void HWComposer::validateChange(HWC2::Composition from, HWC2::Composition to) {
     bool valid = true;
     switch (from) {
@@ -146,52 +143,50 @@
     }
 }
 
-std::optional<DisplayId> HWComposer::onHotplug(hwc2_display_t hwcDisplayId, int32_t displayType,
-                                               HWC2::Connection connection) {
-    if (displayType >= HWC_NUM_PHYSICAL_DISPLAY_TYPES) {
-        ALOGE("Invalid display type of %d", displayType);
-        return {};
-    }
+std::optional<DisplayIdentificationInfo> HWComposer::onHotplug(hwc2_display_t hwcDisplayId,
+                                                               HWC2::Connection connection) {
+    std::optional<DisplayIdentificationInfo> info;
 
-    ALOGV("hotplug: %" PRIu64 ", %s %s", hwcDisplayId,
-          displayType == DisplayDevice::DISPLAY_PRIMARY ? "primary" : "external",
-          to_string(connection).c_str());
-    mHwcDevice->onHotplug(hwcDisplayId, connection);
-
-    std::optional<DisplayId> displayId;
-
-    if (connection == HWC2::Connection::Connected) {
-        uint8_t port;
-        DisplayIdentificationData data;
-        if (getDisplayIdentificationData(hwcDisplayId, &port, &data)) {
-            displayId = generateDisplayId(port, data);
-            ALOGE_IF(!displayId, "Failed to generate stable ID for display %" PRIu64, hwcDisplayId);
+    if (const auto displayId = toPhysicalDisplayId(hwcDisplayId)) {
+        info = DisplayIdentificationInfo{*displayId, std::string()};
+    } else {
+        if (connection == HWC2::Connection::Disconnected) {
+            ALOGE("Ignoring disconnection of invalid HWC display %" PRIu64, hwcDisplayId);
+            return {};
         }
+
+        info = onHotplugConnect(hwcDisplayId);
+        if (!info) return {};
     }
 
+    ALOGV("%s: %s %s display %" PRIu64 " with HWC ID %" PRIu64, __FUNCTION__,
+          to_string(connection).c_str(),
+          hwcDisplayId == mInternalHwcDisplayId ? "internal" : "external", info->id, hwcDisplayId);
+
+    mHwcDevice->onHotplug(hwcDisplayId, connection);
+
     // Disconnect is handled through HWComposer::disconnectDisplay via
     // SurfaceFlinger's onHotplugReceived callback handling
     if (connection == HWC2::Connection::Connected) {
-        mDisplayData[displayType].hwcDisplay = mHwcDevice->getDisplayById(hwcDisplayId);
-        mHwcDisplaySlots[hwcDisplayId] = displayType;
+        mDisplayData[info->id].hwcDisplay = mHwcDevice->getDisplayById(hwcDisplayId);
+        mPhysicalDisplayIdMap[hwcDisplayId] = info->id;
     }
 
-    return displayId;
+    return info;
 }
 
-bool HWComposer::onVsync(hwc2_display_t hwcDisplayId, int64_t timestamp, int32_t* outDisplayId) {
-    const auto it = mHwcDisplaySlots.find(hwcDisplayId);
-    if (it == mHwcDisplaySlots.end()) {
-        LOG_HWC_DISPLAY_ERROR(hwcDisplayId, "Invalid display");
+bool HWComposer::onVsync(hwc2_display_t hwcDisplayId, int64_t timestamp) {
+    const auto displayId = toPhysicalDisplayId(hwcDisplayId);
+    if (!displayId) {
+        LOG_HWC_DISPLAY_ERROR(hwcDisplayId, "Invalid HWC display");
         return false;
     }
 
-    const int32_t displayId = it->second;
-    RETURN_IF_INVALID_DISPLAY(displayId, false);
+    RETURN_IF_INVALID_DISPLAY(*displayId, false);
 
-    const auto& displayData = mDisplayData[displayId];
+    const auto& displayData = mDisplayData[*displayId];
     if (displayData.isVirtual) {
-        LOG_DISPLAY_ERROR(displayId, "Invalid operation on virtual display");
+        LOG_DISPLAY_ERROR(*displayId, "Invalid operation on virtual display");
         return false;
     }
 
@@ -202,31 +197,26 @@
         // with the same timestamp when turning the display off and on. This
         // is a bug in the HWC implementation, but filter the extra events
         // out here so they don't cause havoc downstream.
-        if (timestamp == mLastHwVSync[displayId]) {
-            ALOGW("Ignoring duplicate VSYNC event from HWC (t=%" PRId64 ")",
-                    timestamp);
+        if (timestamp == mLastHwVSync[*displayId]) {
+            ALOGW("Ignoring duplicate VSYNC event from HWC for display %" PRIu64 " (t=%" PRId64 ")",
+                  *displayId, timestamp);
             return false;
         }
 
-        mLastHwVSync[displayId] = timestamp;
+        mLastHwVSync[*displayId] = timestamp;
     }
 
-    if (outDisplayId) {
-        *outDisplayId = displayId;
-    }
-
-    char tag[16];
-    snprintf(tag, sizeof(tag), "HW_VSYNC_%1u", displayId);
-    ATRACE_INT(tag, ++mVSyncCounts[displayId] & 1);
+    const auto tag = "HW_VSYNC_" + std::to_string(*displayId);
+    ATRACE_INT(tag.c_str(), ++mVSyncCounts[*displayId] & 1);
 
     return true;
 }
 
-status_t HWComposer::allocateVirtualDisplay(uint32_t width, uint32_t height,
-        ui::PixelFormat* format, int32_t *outId) {
+std::optional<DisplayId> HWComposer::allocateVirtualDisplay(uint32_t width, uint32_t height,
+                                                            ui::PixelFormat* format) {
     if (mRemainingHwcVirtualDisplays == 0) {
         ALOGE("%s: No remaining virtual displays", __FUNCTION__);
-        return NO_MEMORY;
+        return {};
     }
 
     if (SurfaceFlinger::maxVirtualDisplaySize != 0 &&
@@ -234,41 +224,33 @@
          height > SurfaceFlinger::maxVirtualDisplaySize)) {
         ALOGE("%s: Display size %ux%u exceeds maximum dimension of %" PRIu64, __FUNCTION__, width,
               height, SurfaceFlinger::maxVirtualDisplaySize);
-        return INVALID_OPERATION;
+        return {};
     }
-
     HWC2::Display* display;
     auto error = mHwcDevice->createVirtualDisplay(width, height, format,
             &display);
     if (error != HWC2::Error::None) {
         ALOGE("%s: Failed to create HWC virtual display", __FUNCTION__);
-        return NO_MEMORY;
+        return {};
     }
 
-    size_t displaySlot = 0;
-    if (!mFreeDisplaySlots.empty()) {
-        displaySlot = *mFreeDisplaySlots.begin();
-        mFreeDisplaySlots.erase(displaySlot);
-    } else if (mDisplayData.size() < INT32_MAX) {
-        // Don't bother allocating a slot larger than we can return
-        displaySlot = mDisplayData.size();
-        mDisplayData.resize(displaySlot + 1);
+    DisplayId displayId;
+    if (mFreeVirtualDisplayIds.empty()) {
+        displayId = getVirtualDisplayId(mNextVirtualDisplayId++);
     } else {
-        ALOGE("%s: Unable to allocate a display slot", __FUNCTION__);
-        return NO_MEMORY;
+        displayId = *mFreeVirtualDisplayIds.begin();
+        mFreeVirtualDisplayIds.erase(displayId);
     }
 
-    auto& displayData = mDisplayData[displaySlot];
+    auto& displayData = mDisplayData[displayId];
     displayData.hwcDisplay = display;
     displayData.isVirtual = true;
 
     --mRemainingHwcVirtualDisplays;
-    *outId = static_cast<int32_t>(displaySlot);
-
-    return NO_ERROR;
+    return displayId;
 }
 
-HWC2::Layer* HWComposer::createLayer(int32_t displayId) {
+HWC2::Layer* HWComposer::createLayer(DisplayId displayId) {
     RETURN_IF_INVALID_DISPLAY(displayId, nullptr);
 
     auto display = mDisplayData[displayId].hwcDisplay;
@@ -278,7 +260,7 @@
     return layer;
 }
 
-void HWComposer::destroyLayer(int32_t displayId, HWC2::Layer* layer) {
+void HWComposer::destroyLayer(DisplayId displayId, HWC2::Layer* layer) {
     RETURN_IF_INVALID_DISPLAY(displayId);
 
     auto display = mDisplayData[displayId].hwcDisplay;
@@ -286,7 +268,8 @@
     RETURN_IF_HWC_ERROR(error, displayId);
 }
 
-nsecs_t HWComposer::getRefreshTimestamp(int32_t displayId) const {
+nsecs_t HWComposer::getRefreshTimestamp(DisplayId displayId) const {
+    RETURN_IF_INVALID_DISPLAY(displayId, 0);
     // this returns the last refresh timestamp.
     // if the last one is not available, we estimate it based on
     // the refresh period and whatever closest timestamp we have.
@@ -296,17 +279,17 @@
     return now - ((now - mLastHwVSync[displayId]) % vsyncPeriod);
 }
 
-bool HWComposer::isConnected(int32_t displayId) const {
+bool HWComposer::isConnected(DisplayId displayId) const {
     RETURN_IF_INVALID_DISPLAY(displayId, false);
-    return mDisplayData[displayId].hwcDisplay->isConnected();
+    return mDisplayData.at(displayId).hwcDisplay->isConnected();
 }
 
-std::vector<std::shared_ptr<const HWC2::Display::Config>>
-        HWComposer::getConfigs(int32_t displayId) const {
+std::vector<std::shared_ptr<const HWC2::Display::Config>> HWComposer::getConfigs(
+        DisplayId displayId) const {
     RETURN_IF_INVALID_DISPLAY(displayId, {});
 
-    auto& displayData = mDisplayData[displayId];
-    auto configs = mDisplayData[displayId].hwcDisplay->getConfigs();
+    const auto& displayData = mDisplayData.at(displayId);
+    auto configs = displayData.hwcDisplay->getConfigs();
     if (displayData.configMap.empty()) {
         for (size_t i = 0; i < configs.size(); ++i) {
             displayData.configMap[i] = configs[i];
@@ -315,12 +298,12 @@
     return configs;
 }
 
-std::shared_ptr<const HWC2::Display::Config>
-        HWComposer::getActiveConfig(int32_t displayId) const {
+std::shared_ptr<const HWC2::Display::Config> HWComposer::getActiveConfig(
+        DisplayId displayId) const {
     RETURN_IF_INVALID_DISPLAY(displayId, nullptr);
 
     std::shared_ptr<const HWC2::Display::Config> config;
-    auto error = mDisplayData[displayId].hwcDisplay->getActiveConfig(&config);
+    auto error = mDisplayData.at(displayId).hwcDisplay->getActiveConfig(&config);
     if (error == HWC2::Error::BadConfig) {
         LOG_DISPLAY_ERROR(displayId, "No active config");
         return nullptr;
@@ -336,11 +319,11 @@
     return config;
 }
 
-int HWComposer::getActiveConfigIndex(int32_t displayId) const {
+int HWComposer::getActiveConfigIndex(DisplayId displayId) const {
     RETURN_IF_INVALID_DISPLAY(displayId, -1);
 
     int index;
-    auto error = mDisplayData[displayId].hwcDisplay->getActiveConfigIndex(&index);
+    auto error = mDisplayData.at(displayId).hwcDisplay->getActiveConfigIndex(&index);
     if (error == HWC2::Error::BadConfig) {
         LOG_DISPLAY_ERROR(displayId, "No active config");
         return -1;
@@ -356,17 +339,17 @@
     return index;
 }
 
-std::vector<ui::ColorMode> HWComposer::getColorModes(int32_t displayId) const {
+std::vector<ui::ColorMode> HWComposer::getColorModes(DisplayId displayId) const {
     RETURN_IF_INVALID_DISPLAY(displayId, {});
 
     std::vector<ui::ColorMode> modes;
-    auto error = mDisplayData[displayId].hwcDisplay->getColorModes(&modes);
+    auto error = mDisplayData.at(displayId).hwcDisplay->getColorModes(&modes);
     RETURN_IF_HWC_ERROR(error, displayId, {});
     return modes;
 }
 
-status_t HWComposer::setActiveColorMode(int32_t displayId, ui::ColorMode mode,
-        ui::RenderIntent renderIntent) {
+status_t HWComposer::setActiveColorMode(DisplayId displayId, ui::ColorMode mode,
+                                        ui::RenderIntent renderIntent) {
     RETURN_IF_INVALID_DISPLAY(displayId, BAD_INDEX);
 
     auto& displayData = mDisplayData[displayId];
@@ -379,8 +362,7 @@
     return NO_ERROR;
 }
 
-
-void HWComposer::setVsyncEnabled(int32_t displayId, HWC2::Vsync enabled) {
+void HWComposer::setVsyncEnabled(DisplayId displayId, HWC2::Vsync enabled) {
     RETURN_IF_INVALID_DISPLAY(displayId);
     auto& displayData = mDisplayData[displayId];
 
@@ -401,37 +383,30 @@
 
         displayData.vsyncEnabled = enabled;
 
-        char tag[16];
-        snprintf(tag, sizeof(tag), "HW_VSYNC_ON_%1u", displayId);
-        ATRACE_INT(tag, enabled == HWC2::Vsync::Enable ? 1 : 0);
+        const auto tag = "HW_VSYNC_ON_" + std::to_string(displayId);
+        ATRACE_INT(tag.c_str(), enabled == HWC2::Vsync::Enable ? 1 : 0);
     }
 }
 
-status_t HWComposer::setClientTarget(int32_t displayId, uint32_t slot,
-        const sp<Fence>& acquireFence, const sp<GraphicBuffer>& target,
-        ui::Dataspace dataspace) {
+status_t HWComposer::setClientTarget(DisplayId displayId, uint32_t slot,
+                                     const sp<Fence>& acquireFence, const sp<GraphicBuffer>& target,
+                                     ui::Dataspace dataspace) {
     RETURN_IF_INVALID_DISPLAY(displayId, BAD_INDEX);
 
-    ALOGV("setClientTarget for display %d", displayId);
+    ALOGV("setClientTarget for display %" PRIu64, displayId);
     auto& hwcDisplay = mDisplayData[displayId].hwcDisplay;
     auto error = hwcDisplay->setClientTarget(slot, target, acquireFence, dataspace);
     RETURN_IF_HWC_ERROR(error, displayId, BAD_VALUE);
     return NO_ERROR;
 }
 
-status_t HWComposer::prepare(DisplayDevice& display,
-        std::vector<CompositionInfo>& compositionData) {
+status_t HWComposer::prepare(DisplayId displayId, std::vector<CompositionInfo>& compositionData) {
     ATRACE_CALL();
 
-    Mutex::Autolock _l(mDisplayLock);
-    const auto displayId = display.getId();
-    if (displayId == DisplayDevice::DISPLAY_ID_INVALID) {
-        ALOGV("Skipping HWComposer prepare for non-HWC display");
-        return NO_ERROR;
-    }
-
     RETURN_IF_INVALID_DISPLAY(displayId, BAD_INDEX);
 
+    Mutex::Autolock _l(mDisplayLock);
+
     auto& displayData = mDisplayData[displayId];
     auto& hwcDisplay = displayData.hwcDisplay;
     if (!hwcDisplay->isConnected()) {
@@ -501,7 +476,8 @@
                     changedTypes[&*hwcLayer]);
             compositionInfo.compositionType = changedTypes[&*hwcLayer];
             compositionInfo.layer->mLayer->setCompositionType(displayId,
-                    compositionInfo.compositionType, false);
+                                                              compositionInfo.compositionType,
+                                                              false);
         }
 
         switch (compositionInfo.compositionType) {
@@ -540,49 +516,48 @@
     return NO_ERROR;
 }
 
-bool HWComposer::hasDeviceComposition(int32_t displayId) const {
-    if (displayId == DisplayDevice::DISPLAY_ID_INVALID) {
+bool HWComposer::hasDeviceComposition(const std::optional<DisplayId>& displayId) const {
+    if (!displayId) {
         // Displays without a corresponding HWC display are never composed by
         // the device
         return false;
     }
 
-    RETURN_IF_INVALID_DISPLAY(displayId, false);
-    return mDisplayData[displayId].hasDeviceComposition;
+    RETURN_IF_INVALID_DISPLAY(*displayId, false);
+    return mDisplayData.at(*displayId).hasDeviceComposition;
 }
 
-bool HWComposer::hasFlipClientTargetRequest(int32_t displayId) const {
-    if (displayId == DisplayDevice::DISPLAY_ID_INVALID) {
+bool HWComposer::hasFlipClientTargetRequest(const std::optional<DisplayId>& displayId) const {
+    if (!displayId) {
         // Displays without a corresponding HWC display are never composed by
         // the device
         return false;
     }
 
-    RETURN_IF_INVALID_DISPLAY(displayId, false);
-    return ((static_cast<uint32_t>(mDisplayData[displayId].displayRequests) &
+    RETURN_IF_INVALID_DISPLAY(*displayId, false);
+    return ((static_cast<uint32_t>(mDisplayData.at(*displayId).displayRequests) &
              static_cast<uint32_t>(HWC2::DisplayRequest::FlipClientTarget)) != 0);
 }
 
-bool HWComposer::hasClientComposition(int32_t displayId) const {
-    if (displayId == DisplayDevice::DISPLAY_ID_INVALID) {
+bool HWComposer::hasClientComposition(const std::optional<DisplayId>& displayId) const {
+    if (!displayId) {
         // Displays without a corresponding HWC display are always composed by
         // the client
         return true;
     }
 
-    RETURN_IF_INVALID_DISPLAY(displayId, true);
-    return mDisplayData[displayId].hasClientComposition;
+    RETURN_IF_INVALID_DISPLAY(*displayId, true);
+    return mDisplayData.at(*displayId).hasClientComposition;
 }
 
-sp<Fence> HWComposer::getPresentFence(int32_t displayId) const {
+sp<Fence> HWComposer::getPresentFence(DisplayId displayId) const {
     RETURN_IF_INVALID_DISPLAY(displayId, Fence::NO_FENCE);
-    return mDisplayData[displayId].lastPresentFence;
+    return mDisplayData.at(displayId).lastPresentFence;
 }
 
-sp<Fence> HWComposer::getLayerReleaseFence(int32_t displayId,
-        HWC2::Layer* layer) const {
+sp<Fence> HWComposer::getLayerReleaseFence(DisplayId displayId, HWC2::Layer* layer) const {
     RETURN_IF_INVALID_DISPLAY(displayId, Fence::NO_FENCE);
-    auto displayFences = mDisplayData[displayId].releaseFences;
+    auto displayFences = mDisplayData.at(displayId).releaseFences;
     if (displayFences.count(layer) == 0) {
         ALOGV("getLayerReleaseFence: Release fence not found");
         return Fence::NO_FENCE;
@@ -590,7 +565,7 @@
     return displayFences[layer];
 }
 
-status_t HWComposer::presentAndGetReleaseFences(int32_t displayId) {
+status_t HWComposer::presentAndGetReleaseFences(DisplayId displayId) {
     ATRACE_CALL();
 
     RETURN_IF_INVALID_DISPLAY(displayId, BAD_INDEX);
@@ -618,8 +593,7 @@
     return NO_ERROR;
 }
 
-status_t HWComposer::setPowerMode(int32_t displayId, int32_t intMode) {
-    ALOGV("setPowerMode(%d, %d)", displayId, intMode);
+status_t HWComposer::setPowerMode(DisplayId displayId, int32_t intMode) {
     RETURN_IF_INVALID_DISPLAY(displayId, BAD_INDEX);
 
     const auto& displayData = mDisplayData[displayId];
@@ -675,7 +649,7 @@
     return NO_ERROR;
 }
 
-status_t HWComposer::setActiveConfig(int32_t displayId, size_t configId) {
+status_t HWComposer::setActiveConfig(DisplayId displayId, size_t configId) {
     RETURN_IF_INVALID_DISPLAY(displayId, BAD_INDEX);
 
     auto& displayData = mDisplayData[displayId];
@@ -689,8 +663,7 @@
     return NO_ERROR;
 }
 
-status_t HWComposer::setColorTransform(int32_t displayId,
-        const mat4& transform) {
+status_t HWComposer::setColorTransform(DisplayId displayId, const mat4& transform) {
     RETURN_IF_INVALID_DISPLAY(displayId, BAD_INDEX);
 
     auto& displayData = mDisplayData[displayId];
@@ -702,26 +675,34 @@
     return NO_ERROR;
 }
 
-void HWComposer::disconnectDisplay(int32_t displayId) {
+void HWComposer::disconnectDisplay(DisplayId displayId) {
     RETURN_IF_INVALID_DISPLAY(displayId);
     auto& displayData = mDisplayData[displayId];
 
     // If this was a virtual display, add its slot back for reuse by future
     // virtual displays
     if (displayData.isVirtual) {
-        mFreeDisplaySlots.insert(displayId);
+        mFreeVirtualDisplayIds.insert(displayId);
         ++mRemainingHwcVirtualDisplays;
     }
 
     const auto hwcDisplayId = displayData.hwcDisplay->getId();
-    mHwcDisplaySlots.erase(hwcDisplayId);
-    displayData = DisplayData();
+    mPhysicalDisplayIdMap.erase(hwcDisplayId);
+    mDisplayData.erase(displayId);
+    mVSyncCounts.erase(displayId);
+
+    // TODO(b/74619554): Select internal/external display from remaining displays.
+    if (hwcDisplayId == mInternalHwcDisplayId) {
+        mInternalHwcDisplayId.reset();
+    } else if (hwcDisplayId == mExternalHwcDisplayId) {
+        mExternalHwcDisplayId.reset();
+    }
 
     mHwcDevice->destroyDisplay(hwcDisplayId);
 }
 
-status_t HWComposer::setOutputBuffer(int32_t displayId,
-        const sp<Fence>& acquireFence, const sp<GraphicBuffer>& buffer) {
+status_t HWComposer::setOutputBuffer(DisplayId displayId, const sp<Fence>& acquireFence,
+                                     const sp<GraphicBuffer>& buffer) {
     RETURN_IF_INVALID_DISPLAY(displayId, BAD_INDEX);
     const auto& displayData = mDisplayData[displayId];
 
@@ -735,13 +716,12 @@
     return NO_ERROR;
 }
 
-void HWComposer::clearReleaseFences(int32_t displayId) {
+void HWComposer::clearReleaseFences(DisplayId displayId) {
     RETURN_IF_INVALID_DISPLAY(displayId);
     mDisplayData[displayId].releaseFences.clear();
 }
 
-status_t HWComposer::getHdrCapabilities(
-        int32_t displayId, HdrCapabilities* outCapabilities) {
+status_t HWComposer::getHdrCapabilities(DisplayId displayId, HdrCapabilities* outCapabilities) {
     RETURN_IF_INVALID_DISPLAY(displayId, BAD_INDEX);
 
     auto& hwcDisplay = mDisplayData[displayId].hwcDisplay;
@@ -750,22 +730,22 @@
     return NO_ERROR;
 }
 
-int32_t HWComposer::getSupportedPerFrameMetadata(int32_t displayId) const {
+int32_t HWComposer::getSupportedPerFrameMetadata(DisplayId displayId) const {
     RETURN_IF_INVALID_DISPLAY(displayId, 0);
-    return mDisplayData[displayId].hwcDisplay->getSupportedPerFrameMetadata();
+    return mDisplayData.at(displayId).hwcDisplay->getSupportedPerFrameMetadata();
 }
 
-std::vector<ui::RenderIntent> HWComposer::getRenderIntents(int32_t displayId,
-        ui::ColorMode colorMode) const {
+std::vector<ui::RenderIntent> HWComposer::getRenderIntents(DisplayId displayId,
+                                                           ui::ColorMode colorMode) const {
     RETURN_IF_INVALID_DISPLAY(displayId, {});
 
     std::vector<ui::RenderIntent> renderIntents;
-    auto error = mDisplayData[displayId].hwcDisplay->getRenderIntents(colorMode, &renderIntents);
+    auto error = mDisplayData.at(displayId).hwcDisplay->getRenderIntents(colorMode, &renderIntents);
     RETURN_IF_HWC_ERROR(error, displayId, {});
     return renderIntents;
 }
 
-mat4 HWComposer::getDataspaceSaturationMatrix(int32_t displayId, ui::Dataspace dataspace) {
+mat4 HWComposer::getDataspaceSaturationMatrix(DisplayId displayId, ui::Dataspace dataspace) {
     RETURN_IF_INVALID_DISPLAY(displayId, {});
 
     mat4 matrix;
@@ -806,12 +786,67 @@
     result.append(mHwcDevice->dump().c_str());
 }
 
-std::optional<hwc2_display_t>
-HWComposer::getHwcDisplayId(int32_t displayId) const {
-    if (!isValidDisplay(displayId)) {
+std::optional<DisplayId> HWComposer::toPhysicalDisplayId(hwc2_display_t hwcDisplayId) const {
+    if (const auto it = mPhysicalDisplayIdMap.find(hwcDisplayId);
+        it != mPhysicalDisplayIdMap.end()) {
+        return it->second;
+    }
+    return {};
+}
+
+std::optional<hwc2_display_t> HWComposer::fromPhysicalDisplayId(DisplayId displayId) const {
+    if (const auto it = mDisplayData.find(displayId);
+        it != mDisplayData.end() && !it->second.isVirtual) {
+        return it->second.hwcDisplay->getId();
+    }
+    return {};
+}
+
+std::optional<DisplayIdentificationInfo> HWComposer::onHotplugConnect(hwc2_display_t hwcDisplayId) {
+    if (isUsingVrComposer() && mInternalHwcDisplayId) {
+        ALOGE("Ignoring connection of external display %" PRIu64 " in VR mode", hwcDisplayId);
         return {};
     }
-    return mDisplayData[displayId].hwcDisplay->getId();
+
+    uint8_t port;
+    DisplayIdentificationData data;
+    const bool hasMultiDisplaySupport = getDisplayIdentificationData(hwcDisplayId, &port, &data);
+
+    if (mPhysicalDisplayIdMap.empty()) {
+        mHasMultiDisplaySupport = hasMultiDisplaySupport;
+        ALOGI("Switching to %s multi-display mode",
+              hasMultiDisplaySupport ? "generalized" : "legacy");
+    } else if (mHasMultiDisplaySupport && !hasMultiDisplaySupport) {
+        ALOGE("Ignoring connection of display %" PRIu64 " without identification data",
+              hwcDisplayId);
+        return {};
+    }
+
+    std::optional<DisplayIdentificationInfo> info;
+
+    if (mHasMultiDisplaySupport) {
+        info = parseDisplayIdentificationData(port, data);
+        ALOGE_IF(!info, "Failed to parse identification data for display %" PRIu64, hwcDisplayId);
+    } else if (mInternalHwcDisplayId && mExternalHwcDisplayId) {
+        ALOGE("Ignoring connection of tertiary display %" PRIu64, hwcDisplayId);
+        return {};
+    } else {
+        ALOGW_IF(hasMultiDisplaySupport, "Ignoring identification data for display %" PRIu64,
+                 hwcDisplayId);
+        port = mInternalHwcDisplayId ? HWC_DISPLAY_EXTERNAL : HWC_DISPLAY_PRIMARY;
+    }
+
+    if (!mInternalHwcDisplayId) {
+        mInternalHwcDisplayId = hwcDisplayId;
+    } else if (!mExternalHwcDisplayId) {
+        mExternalHwcDisplayId = hwcDisplayId;
+    }
+
+    if (info) return info;
+
+    return DisplayIdentificationInfo{getFallbackDisplayId(port),
+                                     hwcDisplayId == mInternalHwcDisplayId ? "Internal display"
+                                                                           : "External display"};
 }
 
 } // namespace android
diff --git a/services/surfaceflinger/DisplayHardware/HWComposer.h b/services/surfaceflinger/DisplayHardware/HWComposer.h
index 4777ca9..5074c2c 100644
--- a/services/surfaceflinger/DisplayHardware/HWComposer.h
+++ b/services/surfaceflinger/DisplayHardware/HWComposer.h
@@ -34,7 +34,8 @@
 
 #include <memory>
 #include <optional>
-#include <set>
+#include <unordered_map>
+#include <unordered_set>
 #include <vector>
 
 #include "DisplayIdentification.h"
@@ -82,101 +83,94 @@
 
     bool hasCapability(HWC2::Capability capability) const;
 
-    // Attempts to allocate a virtual display. If the virtual display is created
-    // on the HWC device, outId will contain its HWC ID.
-    status_t allocateVirtualDisplay(uint32_t width, uint32_t height,
-            ui::PixelFormat* format, int32_t* outId);
+    // Attempts to allocate a virtual display and returns its ID if created on the HWC device.
+    std::optional<DisplayId> allocateVirtualDisplay(uint32_t width, uint32_t height,
+                                                    ui::PixelFormat* format);
 
     // Attempts to create a new layer on this display
-    HWC2::Layer* createLayer(int32_t displayId);
+    HWC2::Layer* createLayer(DisplayId displayId);
     // Destroy a previously created layer
-    void destroyLayer(int32_t displayId, HWC2::Layer* layer);
+    void destroyLayer(DisplayId displayId, HWC2::Layer* layer);
 
     // Asks the HAL what it can do
-    status_t prepare(DisplayDevice& display,
-            std::vector<CompositionInfo>& compositionData);
+    status_t prepare(DisplayId displayId, std::vector<CompositionInfo>& compositionData);
 
-    status_t setClientTarget(int32_t displayId, uint32_t slot,
-            const sp<Fence>& acquireFence,
-            const sp<GraphicBuffer>& target, ui::Dataspace dataspace);
+    status_t setClientTarget(DisplayId displayId, uint32_t slot, const sp<Fence>& acquireFence,
+                             const sp<GraphicBuffer>& target, ui::Dataspace dataspace);
 
     // Present layers to the display and read releaseFences.
-    status_t presentAndGetReleaseFences(int32_t displayId);
+    status_t presentAndGetReleaseFences(DisplayId displayId);
 
     // set power mode
-    status_t setPowerMode(int32_t displayId, int mode);
+    status_t setPowerMode(DisplayId displayId, int mode);
 
     // set active config
-    status_t setActiveConfig(int32_t displayId, size_t configId);
+    status_t setActiveConfig(DisplayId displayId, size_t configId);
 
     // Sets a color transform to be applied to the result of composition
-    status_t setColorTransform(int32_t displayId, const mat4& transform);
+    status_t setColorTransform(DisplayId displayId, const mat4& transform);
 
     // reset state when an external, non-virtual display is disconnected
-    void disconnectDisplay(int32_t displayId);
+    void disconnectDisplay(DisplayId displayId);
 
     // does this display have layers handled by HWC
-    bool hasDeviceComposition(int32_t displayId) const;
+    bool hasDeviceComposition(const std::optional<DisplayId>& displayId) const;
 
     // does this display have pending request to flip client target
-    bool hasFlipClientTargetRequest(int32_t displayId) const;
+    bool hasFlipClientTargetRequest(const std::optional<DisplayId>& displayId) const;
 
     // does this display have layers handled by GLES
-    bool hasClientComposition(int32_t displayId) const;
+    bool hasClientComposition(const std::optional<DisplayId>& displayId) const;
 
     // get the present fence received from the last call to present.
-    sp<Fence> getPresentFence(int32_t displayId) const;
+    sp<Fence> getPresentFence(DisplayId displayId) const;
 
     // Get last release fence for the given layer
-    sp<Fence> getLayerReleaseFence(int32_t displayId,
-            HWC2::Layer* layer) const;
+    sp<Fence> getLayerReleaseFence(DisplayId displayId, HWC2::Layer* layer) const;
 
     // Set the output buffer and acquire fence for a virtual display.
     // Returns INVALID_OPERATION if displayId is not a virtual display.
-    status_t setOutputBuffer(int32_t displayId, const sp<Fence>& acquireFence,
-            const sp<GraphicBuffer>& buf);
+    status_t setOutputBuffer(DisplayId displayId, const sp<Fence>& acquireFence,
+                             const sp<GraphicBuffer>& buffer);
 
     // After SurfaceFlinger has retrieved the release fences for all the frames,
     // it can call this to clear the shared pointers in the release fence map
-    void clearReleaseFences(int32_t displayId);
+    void clearReleaseFences(DisplayId displayId);
 
     // Fetches the HDR capabilities of the given display
-    status_t getHdrCapabilities(int32_t displayId, HdrCapabilities* outCapabilities);
+    status_t getHdrCapabilities(DisplayId displayId, HdrCapabilities* outCapabilities);
 
-    int32_t getSupportedPerFrameMetadata(int32_t displayId) const;
+    int32_t getSupportedPerFrameMetadata(DisplayId displayId) const;
 
     // Returns the available RenderIntent of the given display.
-    std::vector<ui::RenderIntent> getRenderIntents(int32_t displayId, ui::ColorMode colorMode) const;
+    std::vector<ui::RenderIntent> getRenderIntents(DisplayId displayId,
+                                                   ui::ColorMode colorMode) const;
 
-    mat4 getDataspaceSaturationMatrix(int32_t displayId, ui::Dataspace dataspace);
+    mat4 getDataspaceSaturationMatrix(DisplayId displayId, ui::Dataspace dataspace);
 
     // Events handling ---------------------------------------------------------
 
-    // Returns true if successful, false otherwise. The
-    // DisplayDevice::DisplayType of the display is returned as an output param.
-    bool onVsync(hwc2_display_t hwcDisplayId, int64_t timestamp, int32_t* outDisplay);
-    std::optional<DisplayId> onHotplug(hwc2_display_t hwcDisplayId, int32_t displayType,
-                                       HWC2::Connection connection);
+    // Returns stable display ID (and display name on connection of new or previously disconnected
+    // display), or std::nullopt if hotplug event was ignored.
+    std::optional<DisplayIdentificationInfo> onHotplug(hwc2_display_t hwcDisplayId,
+                                                       HWC2::Connection connection);
 
-    void setVsyncEnabled(int32_t displayId, HWC2::Vsync enabled);
+    bool onVsync(hwc2_display_t hwcDisplayId, int64_t timestamp);
+    void setVsyncEnabled(DisplayId displayId, HWC2::Vsync enabled);
 
-    // Query display parameters.  Pass in a display index (e.g.
-    // HWC_DISPLAY_PRIMARY).
-    nsecs_t getRefreshTimestamp(int32_t displayId) const;
-    bool isConnected(int32_t displayId) const;
+    nsecs_t getRefreshTimestamp(DisplayId displayId) const;
+    bool isConnected(DisplayId displayId) const;
 
     // Non-const because it can update configMap inside of mDisplayData
-    std::vector<std::shared_ptr<const HWC2::Display::Config>>
-            getConfigs(int32_t displayId) const;
+    std::vector<std::shared_ptr<const HWC2::Display::Config>> getConfigs(DisplayId displayId) const;
 
-    std::shared_ptr<const HWC2::Display::Config>
-            getActiveConfig(int32_t displayId) const;
-    int getActiveConfigIndex(int32_t displayId) const;
+    std::shared_ptr<const HWC2::Display::Config> getActiveConfig(DisplayId displayId) const;
+    int getActiveConfigIndex(DisplayId displayId) const;
 
-    std::vector<ui::ColorMode> getColorModes(int32_t displayId) const;
+    std::vector<ui::ColorMode> getColorModes(DisplayId displayId) const;
 
-    status_t setActiveColorMode(int32_t displayId, ui::ColorMode mode,
-            ui::RenderIntent renderIntent);
+    status_t setActiveColorMode(DisplayId displayId, ui::ColorMode mode,
+                                ui::RenderIntent renderIntent);
 
     bool isUsingVrComposer() const;
 
@@ -185,12 +179,19 @@
 
     android::Hwc2::Composer* getComposer() const { return mHwcDevice->getComposer(); }
 
-    std::optional<hwc2_display_t> getHwcDisplayId(int32_t displayId) const;
+    // TODO(b/74619554): Remove special cases for internal/external display.
+    std::optional<hwc2_display_t> getInternalHwcDisplayId() const { return mInternalHwcDisplayId; }
+    std::optional<hwc2_display_t> getExternalHwcDisplayId() const { return mExternalHwcDisplayId; }
+
+    std::optional<DisplayId> toPhysicalDisplayId(hwc2_display_t hwcDisplayId) const;
+    std::optional<hwc2_display_t> fromPhysicalDisplayId(DisplayId displayId) const;
+
 private:
     // For unit tests
     friend TestableSurfaceFlinger;
 
-    bool isValidDisplay(int32_t displayId) const;
+    std::optional<DisplayIdentificationInfo> onHotplugConnect(hwc2_display_t hwcDisplayId);
+
     static void validateChange(HWC2::Composition from, HWC2::Composition to);
 
     struct cb_context;
@@ -215,21 +216,30 @@
         HWC2::Error presentError;
     };
 
-    std::unique_ptr<HWC2::Device>   mHwcDevice;
-    std::vector<DisplayData> mDisplayData{HWC_NUM_PHYSICAL_DISPLAY_TYPES};
-    std::set<size_t>                mFreeDisplaySlots;
-    std::unordered_map<hwc2_display_t, int32_t> mHwcDisplaySlots;
+    std::unordered_map<DisplayId, DisplayData> mDisplayData;
+
+    // This must be destroyed before mDisplayData, because destructor may call back into HWComposer
+    // and look up DisplayData.
+    std::unique_ptr<HWC2::Device> mHwcDevice;
+
+    std::unordered_map<hwc2_display_t, DisplayId> mPhysicalDisplayIdMap;
+    std::optional<hwc2_display_t> mInternalHwcDisplayId;
+    std::optional<hwc2_display_t> mExternalHwcDisplayId;
+    bool mHasMultiDisplaySupport = false;
+
     // protect mDisplayData from races between prepare and dump
     mutable Mutex mDisplayLock;
 
     cb_context* mCBContext = nullptr;
-    size_t mVSyncCounts[HWC_NUM_PHYSICAL_DISPLAY_TYPES]{0, 0};
+    std::unordered_map<DisplayId, size_t> mVSyncCounts;
+
+    std::unordered_set<uint32_t> mFreeVirtualDisplayIds;
+    uint32_t mNextVirtualDisplayId = 0;
     uint32_t mRemainingHwcVirtualDisplays{mHwcDevice->getMaxVirtualDisplayCount()};
 
     // protected by mLock
     mutable Mutex mLock;
-    mutable std::unordered_map<int32_t, nsecs_t> mLastHwVSync{
-            {{HWC_DISPLAY_PRIMARY, 0}, {HWC_DISPLAY_EXTERNAL, 0}}};
+    mutable std::unordered_map<DisplayId, nsecs_t> mLastHwVSync;
 
     // thread-safe
     mutable Mutex mVsyncLock;
diff --git a/services/surfaceflinger/DisplayHardware/VirtualDisplaySurface.cpp b/services/surfaceflinger/DisplayHardware/VirtualDisplaySurface.cpp
index c111a27..27d3dc5 100644
--- a/services/surfaceflinger/DisplayHardware/VirtualDisplaySurface.cpp
+++ b/services/surfaceflinger/DisplayHardware/VirtualDisplaySurface.cpp
@@ -48,34 +48,34 @@
     }
 }
 
-VirtualDisplaySurface::VirtualDisplaySurface(HWComposer& hwc, int32_t dispId,
-        const sp<IGraphicBufferProducer>& sink,
-        const sp<IGraphicBufferProducer>& bqProducer,
-        const sp<IGraphicBufferConsumer>& bqConsumer,
-        const std::string& name)
-:   ConsumerBase(bqConsumer),
-    mHwc(hwc),
-    mDisplayId(dispId),
-    mDisplayName(name),
-    mSource{},
-    mDefaultOutputFormat(HAL_PIXEL_FORMAT_IMPLEMENTATION_DEFINED),
-    mOutputFormat(HAL_PIXEL_FORMAT_IMPLEMENTATION_DEFINED),
-    mOutputUsage(GRALLOC_USAGE_HW_COMPOSER),
-    mProducerSlotSource(0),
-    mProducerBuffers(),
-    mQueueBufferOutput(),
-    mSinkBufferWidth(0),
-    mSinkBufferHeight(0),
-    mCompositionType(COMPOSITION_UNKNOWN),
-    mFbFence(Fence::NO_FENCE),
-    mOutputFence(Fence::NO_FENCE),
-    mFbProducerSlot(BufferQueue::INVALID_BUFFER_SLOT),
-    mOutputProducerSlot(BufferQueue::INVALID_BUFFER_SLOT),
-    mDbgState(DBG_STATE_IDLE),
-    mDbgLastCompositionType(COMPOSITION_UNKNOWN),
-    mMustRecompose(false),
-    mForceHwcCopy(SurfaceFlinger::useHwcForRgbToYuv)
-{
+VirtualDisplaySurface::VirtualDisplaySurface(HWComposer& hwc,
+                                             const std::optional<DisplayId>& displayId,
+                                             const sp<IGraphicBufferProducer>& sink,
+                                             const sp<IGraphicBufferProducer>& bqProducer,
+                                             const sp<IGraphicBufferConsumer>& bqConsumer,
+                                             const std::string& name)
+      : ConsumerBase(bqConsumer),
+        mHwc(hwc),
+        mDisplayId(displayId),
+        mDisplayName(name),
+        mSource{},
+        mDefaultOutputFormat(HAL_PIXEL_FORMAT_IMPLEMENTATION_DEFINED),
+        mOutputFormat(HAL_PIXEL_FORMAT_IMPLEMENTATION_DEFINED),
+        mOutputUsage(GRALLOC_USAGE_HW_COMPOSER),
+        mProducerSlotSource(0),
+        mProducerBuffers(),
+        mQueueBufferOutput(),
+        mSinkBufferWidth(0),
+        mSinkBufferHeight(0),
+        mCompositionType(COMPOSITION_UNKNOWN),
+        mFbFence(Fence::NO_FENCE),
+        mOutputFence(Fence::NO_FENCE),
+        mFbProducerSlot(BufferQueue::INVALID_BUFFER_SLOT),
+        mOutputProducerSlot(BufferQueue::INVALID_BUFFER_SLOT),
+        mDbgState(DBG_STATE_IDLE),
+        mDbgLastCompositionType(COMPOSITION_UNKNOWN),
+        mMustRecompose(false),
+        mForceHwcCopy(SurfaceFlinger::useHwcForRgbToYuv) {
     mSource[SOURCE_SINK] = sink;
     mSource[SOURCE_SCRATCH] = bqProducer;
 
@@ -116,8 +116,9 @@
 }
 
 status_t VirtualDisplaySurface::beginFrame(bool mustRecompose) {
-    if (mDisplayId < 0)
+    if (!mDisplayId) {
         return NO_ERROR;
+    }
 
     mMustRecompose = mustRecompose;
 
@@ -129,8 +130,9 @@
 }
 
 status_t VirtualDisplaySurface::prepareFrame(CompositionType compositionType) {
-    if (mDisplayId < 0)
+    if (!mDisplayId) {
         return NO_ERROR;
+    }
 
     VDS_LOGW_IF(mDbgState != DBG_STATE_BEGUN,
             "Unexpected prepareFrame() in %s state", dbgStateStr());
@@ -177,8 +179,9 @@
 }
 
 status_t VirtualDisplaySurface::advanceFrame() {
-    if (mDisplayId < 0)
+    if (!mDisplayId) {
         return NO_ERROR;
+    }
 
     if (mCompositionType == COMPOSITION_HWC) {
         VDS_LOGW_IF(mDbgState != DBG_STATE_PREPARED,
@@ -211,7 +214,7 @@
 
     // At this point we know the output buffer acquire fence,
     // so update HWC state with it.
-    mHwc.setOutputBuffer(mDisplayId, mOutputFence, outBuffer);
+    mHwc.setOutputBuffer(*mDisplayId, mOutputFence, outBuffer);
 
     status_t result = NO_ERROR;
     if (fbBuffer != nullptr) {
@@ -221,22 +224,23 @@
                 &hwcSlot, &hwcBuffer);
 
         // TODO: Correctly propagate the dataspace from GL composition
-        result = mHwc.setClientTarget(mDisplayId, hwcSlot, mFbFence,
-                hwcBuffer, ui::Dataspace::UNKNOWN);
+        result = mHwc.setClientTarget(*mDisplayId, hwcSlot, mFbFence, hwcBuffer,
+                                      ui::Dataspace::UNKNOWN);
     }
 
     return result;
 }
 
 void VirtualDisplaySurface::onFrameCommitted() {
-    if (mDisplayId < 0)
+    if (!mDisplayId) {
         return;
+    }
 
     VDS_LOGW_IF(mDbgState != DBG_STATE_HWC,
             "Unexpected onFrameCommitted() in %s state", dbgStateStr());
     mDbgState = DBG_STATE_IDLE;
 
-    sp<Fence> retireFence = mHwc.getPresentFence(mDisplayId);
+    sp<Fence> retireFence = mHwc.getPresentFence(*mDisplayId);
     if (mCompositionType == COMPOSITION_MIXED && mFbProducerSlot >= 0) {
         // release the scratch buffer back to the pool
         Mutex::Autolock lock(mMutex);
@@ -291,8 +295,9 @@
 
 status_t VirtualDisplaySurface::requestBuffer(int pslot,
         sp<GraphicBuffer>* outBuf) {
-    if (mDisplayId < 0)
+    if (!mDisplayId) {
         return mSource[SOURCE_SINK]->requestBuffer(pslot, outBuf);
+    }
 
     VDS_LOGW_IF(mDbgState != DBG_STATE_GLES,
             "Unexpected requestBuffer pslot=%d in %s state",
@@ -313,7 +318,7 @@
 
 status_t VirtualDisplaySurface::dequeueBuffer(Source source,
         PixelFormat format, uint64_t usage, int* sslot, sp<Fence>* fence) {
-    LOG_FATAL_IF(mDisplayId < 0, "mDisplayId=%d but should not be < 0.", mDisplayId);
+    LOG_FATAL_IF(!mDisplayId);
 
     status_t result =
             mSource[source]->dequeueBuffer(sslot, fence, mSinkBufferWidth, mSinkBufferHeight,
@@ -359,7 +364,7 @@
                                               PixelFormat format, uint64_t usage,
                                               uint64_t* outBufferAge,
                                               FrameEventHistoryDelta* outTimestamps) {
-    if (mDisplayId < 0) {
+    if (!mDisplayId) {
         return mSource[SOURCE_SINK]->dequeueBuffer(pslot, fence, w, h, format, usage, outBufferAge,
                                                    outTimestamps);
     }
@@ -446,8 +451,9 @@
 
 status_t VirtualDisplaySurface::queueBuffer(int pslot,
         const QueueBufferInput& input, QueueBufferOutput* output) {
-    if (mDisplayId < 0)
+    if (!mDisplayId) {
         return mSource[SOURCE_SINK]->queueBuffer(pslot, input, output);
+    }
 
     VDS_LOGW_IF(mDbgState != DBG_STATE_GLES,
             "Unexpected queueBuffer(pslot=%d) in %s state", pslot,
@@ -504,8 +510,9 @@
 
 status_t VirtualDisplaySurface::cancelBuffer(int pslot,
         const sp<Fence>& fence) {
-    if (mDisplayId < 0)
+    if (!mDisplayId) {
         return mSource[SOURCE_SINK]->cancelBuffer(mapProducer2SourceSlot(SOURCE_SINK, pslot), fence);
+    }
 
     VDS_LOGW_IF(mDbgState != DBG_STATE_GLES,
             "Unexpected cancelBuffer(pslot=%d) in %s state", pslot,
@@ -616,6 +623,8 @@
 }
 
 status_t VirtualDisplaySurface::refreshOutputBuffer() {
+    LOG_FATAL_IF(!mDisplayId);
+
     if (mOutputProducerSlot >= 0) {
         mSource[SOURCE_SINK]->cancelBuffer(
                 mapProducer2SourceSlot(SOURCE_SINK, mOutputProducerSlot),
@@ -633,8 +642,8 @@
     // until after GLES calls queueBuffer(). So here we just set the buffer
     // (for use in HWC prepare) but not the fence; we'll call this again with
     // the proper fence once we have it.
-    result = mHwc.setOutputBuffer(mDisplayId, Fence::NO_FENCE,
-            mProducerBuffers[mOutputProducerSlot]);
+    result = mHwc.setOutputBuffer(*mDisplayId, Fence::NO_FENCE,
+                                  mProducerBuffers[mOutputProducerSlot]);
 
     return result;
 }
diff --git a/services/surfaceflinger/DisplayHardware/VirtualDisplaySurface.h b/services/surfaceflinger/DisplayHardware/VirtualDisplaySurface.h
index 4bd4d0f..33678df 100644
--- a/services/surfaceflinger/DisplayHardware/VirtualDisplaySurface.h
+++ b/services/surfaceflinger/DisplayHardware/VirtualDisplaySurface.h
@@ -17,8 +17,10 @@
 #ifndef ANDROID_SF_VIRTUAL_DISPLAY_SURFACE_H
 #define ANDROID_SF_VIRTUAL_DISPLAY_SURFACE_H
 
+#include <optional>
 #include <string>
 
+#include "DisplayIdentification.h"
 #include "DisplaySurface.h"
 #include "HWComposerBufferCache.h"
 
@@ -75,11 +77,10 @@
                               public BnGraphicBufferProducer,
                               private ConsumerBase {
 public:
-    VirtualDisplaySurface(HWComposer& hwc, int32_t dispId,
-            const sp<IGraphicBufferProducer>& sink,
-            const sp<IGraphicBufferProducer>& bqProducer,
-            const sp<IGraphicBufferConsumer>& bqConsumer,
-            const std::string& name);
+    VirtualDisplaySurface(HWComposer& hwc, const std::optional<DisplayId>& displayId,
+                          const sp<IGraphicBufferProducer>& sink,
+                          const sp<IGraphicBufferProducer>& bqProducer,
+                          const sp<IGraphicBufferConsumer>& bqConsumer, const std::string& name);
 
     //
     // DisplaySurface interface
@@ -154,7 +155,7 @@
     // Immutable after construction
     //
     HWComposer& mHwc;
-    const int32_t mDisplayId;
+    const std::optional<DisplayId> mDisplayId;
     const std::string mDisplayName;
     sp<IGraphicBufferProducer> mSource[2]; // indexed by SOURCE_*
     uint32_t mDefaultOutputFormat;
diff --git a/services/surfaceflinger/Layer.cpp b/services/surfaceflinger/Layer.cpp
index f29dfc0..421c9d0 100644
--- a/services/surfaceflinger/Layer.cpp
+++ b/services/surfaceflinger/Layer.cpp
@@ -196,9 +196,9 @@
 // h/w composer set-up
 // ---------------------------------------------------------------------------
 
-bool Layer::createHwcLayer(HWComposer* hwc, int32_t displayId) {
+bool Layer::createHwcLayer(HWComposer* hwc, DisplayId displayId) {
     LOG_ALWAYS_FATAL_IF(getBE().mHwcLayers.count(displayId) != 0,
-                        "Already have a layer for display %d", displayId);
+                        "Already have a layer for display %" PRIu64, displayId);
     auto layer = std::shared_ptr<HWC2::Layer>(
             hwc->createLayer(displayId),
             [hwc, displayId](HWC2::Layer* layer) {
@@ -214,7 +214,7 @@
     return true;
 }
 
-bool Layer::destroyHwcLayer(int32_t displayId) {
+bool Layer::destroyHwcLayer(DisplayId displayId) {
     if (getBE().mHwcLayers.count(displayId) == 0) {
         return false;
     }
@@ -461,12 +461,13 @@
 
 void Layer::setGeometry(const sp<const DisplayDevice>& display, uint32_t z) {
     const auto displayId = display->getId();
-    if (!hasHwcLayer(displayId)) {
-        ALOGE("[%s] failed to setGeometry: no HWC layer found (%d)",
-              mName.string(), displayId);
+    LOG_ALWAYS_FATAL_IF(!displayId);
+    if (!hasHwcLayer(*displayId)) {
+        ALOGE("[%s] failed to setGeometry: no HWC layer found for display %" PRIu64, mName.string(),
+              *displayId);
         return;
     }
-    auto& hwcInfo = getBE().mHwcLayers[displayId];
+    auto& hwcInfo = getBE().mHwcLayers[*displayId];
 
     // enable this layer
     hwcInfo.forceClientComposition = false;
@@ -618,7 +619,7 @@
     if (orientation & ui::Transform::ROT_INVALID) {
         // we can only handle simple transformation
         hwcInfo.forceClientComposition = true;
-        getBE().mHwcLayers[displayId].compositionType = HWC2::Composition::Client;
+        getBE().mHwcLayers[*displayId].compositionType = HWC2::Composition::Client;
     } else {
         auto transform = static_cast<HWC2::Transform>(orientation);
         hwcInfo.transform = transform;
@@ -632,18 +633,18 @@
     }
 }
 
-void Layer::forceClientComposition(int32_t displayId) {
+void Layer::forceClientComposition(DisplayId displayId) {
     if (getBE().mHwcLayers.count(displayId) == 0) {
-        ALOGE("forceClientComposition: no HWC layer found (%d)", displayId);
+        ALOGE("forceClientComposition: no HWC layer found (display %" PRIu64 ")", displayId);
         return;
     }
 
     getBE().mHwcLayers[displayId].forceClientComposition = true;
 }
 
-bool Layer::getForceClientComposition(int32_t displayId) {
+bool Layer::getForceClientComposition(DisplayId displayId) {
     if (getBE().mHwcLayers.count(displayId) == 0) {
-        ALOGE("getForceClientComposition: no HWC layer found (%d)", displayId);
+        ALOGE("getForceClientComposition: no HWC layer found (display %" PRIu64 ")", displayId);
         return false;
     }
 
@@ -652,7 +653,7 @@
 
 void Layer::updateCursorPosition(const sp<const DisplayDevice>& display) {
     const auto displayId = display->getId();
-    if (getBE().mHwcLayers.count(displayId) == 0 ||
+    if (getBE().mHwcLayers.count(*displayId) == 0 ||
         getCompositionType(displayId) != HWC2::Composition::Cursor) {
         return;
     }
@@ -675,8 +676,8 @@
     auto position = displayTransform.transform(frame);
 
     auto error =
-            (getBE().mHwcLayers[displayId].layer)->setCursorPosition(
-                    position.left, position.top);
+            getBE().mHwcLayers[*displayId].layer->setCursorPosition(position.left, position.top);
+
     ALOGE_IF(error != HWC2::Error::None,
              "[%s] Failed to set cursor position "
              "to (%d, %d): %s (%d)",
@@ -708,7 +709,7 @@
     clearWithOpenGL(renderArea, 0, 0, 0, 0);
 }
 
-void Layer::setCompositionType(int32_t displayId, HWC2::Composition type, bool callIntoHwc) {
+void Layer::setCompositionType(DisplayId displayId, HWC2::Composition type, bool callIntoHwc) {
     if (getBE().mHwcLayers.count(displayId) == 0) {
         ALOGE("setCompositionType called without a valid HWC layer");
         return;
@@ -731,16 +732,20 @@
     }
 }
 
-HWC2::Composition Layer::getCompositionType(int32_t displayId) const {
-    if (getBE().mHwcLayers.count(displayId) == 0) {
+HWC2::Composition Layer::getCompositionType(const std::optional<DisplayId>& displayId) const {
+    if (!displayId) {
         // If we're querying the composition type for a display that does not
         // have a HWC counterpart, then it will always be Client
         return HWC2::Composition::Client;
     }
-    return getBE().mHwcLayers[displayId].compositionType;
+    if (getBE().mHwcLayers.count(*displayId) == 0) {
+        ALOGE("getCompositionType called with an invalid HWC layer");
+        return HWC2::Composition::Invalid;
+    }
+    return getBE().mHwcLayers.at(*displayId).compositionType;
 }
 
-void Layer::setClearClientTarget(int32_t displayId, bool clear) {
+void Layer::setClearClientTarget(DisplayId displayId, bool clear) {
     if (getBE().mHwcLayers.count(displayId) == 0) {
         ALOGE("setClearClientTarget called without a valid HWC layer");
         return;
@@ -748,7 +753,7 @@
     getBE().mHwcLayers[displayId].clearClientTarget = clear;
 }
 
-bool Layer::getClearClientTarget(int32_t displayId) const {
+bool Layer::getClearClientTarget(DisplayId displayId) const {
     if (getBE().mHwcLayers.count(displayId) == 0) {
         ALOGE("getClearClientTarget called without a valid HWC layer");
         return false;
@@ -1049,6 +1054,7 @@
 
     // Commit the transaction
     commitTransaction(c);
+    mCurrentState.callbackHandles = {};
     return flags;
 }
 
@@ -1416,7 +1422,7 @@
     result.append("-----------------------------\n");
 }
 
-void Layer::miniDump(String8& result, int32_t displayId) const {
+void Layer::miniDump(String8& result, DisplayId displayId) const {
     if (getBE().mHwcLayers.count(displayId) == 0) {
         return;
     }
@@ -1980,7 +1986,7 @@
     }
 }
 
-void Layer::writeToProto(LayerProto* layerInfo, int32_t displayId) {
+void Layer::writeToProto(LayerProto* layerInfo, DisplayId displayId) {
     if (!hasHwcLayer(displayId)) {
         return;
     }
diff --git a/services/surfaceflinger/Layer.h b/services/surfaceflinger/Layer.h
index 6921ca9..e829a03 100644
--- a/services/surfaceflinger/Layer.h
+++ b/services/surfaceflinger/Layer.h
@@ -38,6 +38,7 @@
 
 #include <cstdint>
 #include <list>
+#include <optional>
 #include <vector>
 
 #include "Client.h"
@@ -47,6 +48,7 @@
 #include "MonitoredProducer.h"
 #include "SurfaceFlinger.h"
 #include "TimeStats/TimeStats.h"
+#include "TransactionCompletedThread.h"
 
 #include "DisplayHardware/HWComposer.h"
 #include "DisplayHardware/HWComposerBufferCache.h"
@@ -183,6 +185,10 @@
         sp<NativeHandle> sidebandStream;
         mat4 colorTransform;
         bool hasColorTransform;
+
+        // The deque of callback handles for this frame. The back of the deque contains the most
+        // recent callback handle.
+        std::deque<sp<CallbackHandle>> callbackHandles;
     };
 
     explicit Layer(const LayerCreationArgs& args);
@@ -265,13 +271,17 @@
     virtual bool setTransform(uint32_t /*transform*/) { return false; };
     virtual bool setTransformToDisplayInverse(bool /*transformToDisplayInverse*/) { return false; };
     virtual bool setCrop(const Rect& /*crop*/) { return false; };
-    virtual bool setBuffer(sp<GraphicBuffer> /*buffer*/) { return false; };
+    virtual bool setBuffer(const sp<GraphicBuffer>& /*buffer*/) { return false; };
     virtual bool setAcquireFence(const sp<Fence>& /*fence*/) { return false; };
     virtual bool setDataspace(ui::Dataspace /*dataspace*/) { return false; };
     virtual bool setHdrMetadata(const HdrMetadata& /*hdrMetadata*/) { return false; };
     virtual bool setSurfaceDamageRegion(const Region& /*surfaceDamage*/) { return false; };
     virtual bool setApi(int32_t /*api*/) { return false; };
     virtual bool setSidebandStream(const sp<NativeHandle>& /*sidebandStream*/) { return false; };
+    virtual bool setTransactionCompletedListeners(
+            const std::vector<sp<CallbackHandle>>& /*handles*/) {
+        return false;
+    };
 
     ui::Dataspace getDataSpace() const { return mCurrentDataSpace; }
 
@@ -351,7 +361,7 @@
     void writeToProto(LayerProto* layerInfo,
                       LayerVector::StateSet stateSet = LayerVector::StateSet::Drawing);
 
-    void writeToProto(LayerProto* layerInfo, int32_t displayId);
+    void writeToProto(LayerProto* layerInfo, DisplayId displayId);
 
     virtual Geometry getActiveGeometry(const Layer::State& s) const { return s.active_legacy; }
     virtual uint32_t getActiveWidth(const Layer::State& s) const { return s.active_legacy.w; }
@@ -377,16 +387,17 @@
     virtual bool isHdrY410() const { return false; }
 
     void setGeometry(const sp<const DisplayDevice>& display, uint32_t z);
-    void forceClientComposition(int32_t displayId);
-    bool getForceClientComposition(int32_t displayId);
-    virtual void setPerFrameData(const sp<const DisplayDevice>& display) = 0;
+    void forceClientComposition(DisplayId displayId);
+    bool getForceClientComposition(DisplayId displayId);
+    virtual void setPerFrameData(DisplayId displayId, const ui::Transform& transform,
+                                 const Rect& viewport, int32_t supportedPerFrameMetadata) = 0;
 
     // callIntoHwc exists so we can update our local state and call
     // acceptDisplayChanges without unnecessarily updating the device's state
-    void setCompositionType(int32_t displayId, HWC2::Composition type, bool callIntoHwc = true);
-    HWC2::Composition getCompositionType(int32_t displayId) const;
-    void setClearClientTarget(int32_t displayId, bool clear);
-    bool getClearClientTarget(int32_t displayId) const;
+    void setCompositionType(DisplayId displayId, HWC2::Composition type, bool callIntoHwc = true);
+    HWC2::Composition getCompositionType(const std::optional<DisplayId>& displayId) const;
+    void setClearClientTarget(DisplayId displayId, bool clear);
+    bool getClearClientTarget(DisplayId displayId) const;
     void updateCursorPosition(const sp<const DisplayDevice>& display);
 
     /*
@@ -407,7 +418,8 @@
      * called after composition.
      * returns true if the layer latched a new buffer this frame.
      */
-    virtual bool onPostComposition(const std::shared_ptr<FenceTime>& /*glDoneFence*/,
+    virtual bool onPostComposition(const std::optional<DisplayId>& /*displayId*/,
+                                   const std::shared_ptr<FenceTime>& /*glDoneFence*/,
                                    const std::shared_ptr<FenceTime>& /*presentFence*/,
                                    const CompositorTiming& /*compositorTiming*/) {
         return false;
@@ -492,24 +504,24 @@
 
     // -----------------------------------------------------------------------
 
-    bool createHwcLayer(HWComposer* hwc, int32_t displayId);
-    bool destroyHwcLayer(int32_t displayId);
+    bool createHwcLayer(HWComposer* hwc, DisplayId displayId);
+    bool destroyHwcLayer(DisplayId displayId);
     void destroyAllHwcLayers();
 
-    bool hasHwcLayer(int32_t displayId) { return getBE().mHwcLayers.count(displayId) > 0; }
+    bool hasHwcLayer(DisplayId displayId) { return getBE().mHwcLayers.count(displayId) > 0; }
 
-    HWC2::Layer* getHwcLayer(int32_t displayId) {
-        if (getBE().mHwcLayers.count(displayId) == 0) {
+    HWC2::Layer* getHwcLayer(DisplayId displayId) {
+        if (!hasHwcLayer(displayId)) {
             return nullptr;
         }
         return getBE().mHwcLayers[displayId].layer.get();
     }
 
-    bool setHwcLayer(int32_t hwcId) {
-        if (getBE().mHwcLayers.count(hwcId) == 0) {
+    bool setHwcLayer(DisplayId displayId) {
+        if (!hasHwcLayer(displayId)) {
             return false;
         }
-        getBE().compositionInfo.hwc.hwcLayer = getBE().mHwcLayers[hwcId].layer;
+        getBE().compositionInfo.hwc.hwcLayer = getBE().mHwcLayers[displayId].layer;
         return true;
     }
 
@@ -524,7 +536,7 @@
 
     /* always call base class first */
     static void miniDumpHeader(String8& result);
-    void miniDump(String8& result, int32_t displayId) const;
+    void miniDump(String8& result, DisplayId displayId) const;
     void dumpFrameStats(String8& result) const;
     void dumpFrameEvents(String8& result);
     void clearFrameStats();
diff --git a/services/surfaceflinger/LayerBE.h b/services/surfaceflinger/LayerBE.h
index 463c46c..3f5134e 100644
--- a/services/surfaceflinger/LayerBE.h
+++ b/services/surfaceflinger/LayerBE.h
@@ -24,6 +24,7 @@
 #include <renderengine/Texture.h>
 #include <ui/Region.h>
 
+#include "DisplayHardware/DisplayIdentification.h"
 #include "DisplayHardware/HWComposer.h"
 #include "DisplayHardware/HWComposerBufferCache.h"
 #include "SurfaceFlinger.h"
@@ -41,7 +42,7 @@
     std::shared_ptr<LayerBE> layer;
     struct {
         std::shared_ptr<HWC2::Layer> hwcLayer;
-        int32_t displayId = -1;
+        DisplayId displayId;
         sp<Fence> fence;
         HWC2::BlendMode blendMode = HWC2::BlendMode::Invalid;
         Rect displayFrame;
@@ -123,12 +124,11 @@
         HWC2::Transform transform;
     };
 
-
     // A layer can be attached to multiple displays when operating in mirror mode
     // (a.k.a: when several displays are attached with equal layerStack). In this
     // case we need to keep track. In non-mirror mode, a layer will have only one
-    // HWCInfo. This map key is a display layerStack.
-    std::unordered_map<int32_t, HWCInfo> mHwcLayers;
+    // HWCInfo.
+    std::unordered_map<DisplayId, HWCInfo> mHwcLayers;
 
     CompositionInfo compositionInfo;
 };
diff --git a/services/surfaceflinger/Scheduler/Scheduler.cpp b/services/surfaceflinger/Scheduler/Scheduler.cpp
index 18a8bb1..4286cc9 100644
--- a/services/surfaceflinger/Scheduler/Scheduler.cpp
+++ b/services/surfaceflinger/Scheduler/Scheduler.cpp
@@ -14,11 +14,14 @@
  * limitations under the License.
  */
 
+#define ATRACE_TAG ATRACE_TAG_GRAPHICS
+
 #include "Scheduler.h"
 
 #include <cinttypes>
 #include <cstdint>
 #include <memory>
+#include <numeric>
 
 #include <android/hardware/configstore/1.0/ISurfaceFlingerConfigs.h>
 #include <android/hardware/configstore/1.1/ISurfaceFlingerConfigs.h>
@@ -27,6 +30,7 @@
 
 #include <gui/ISurfaceComposer.h>
 #include <ui/DisplayStatInfo.h>
+#include <utils/Trace.h>
 
 #include "DispSync.h"
 #include "DispSyncSource.h"
@@ -196,4 +200,58 @@
     mPrimaryDispSync->setIgnorePresentFences(ignore);
 }
 
+void Scheduler::makeHWSyncAvailable(bool makeAvailable) {
+    std::lock_guard<std::mutex> lock(mHWVsyncLock);
+    mHWVsyncAvailable = makeAvailable;
+}
+
+void Scheduler::addNewFrameTimestamp(const nsecs_t newFrameTimestamp, bool isAutoTimestamp) {
+    ATRACE_INT("AutoTimestamp", isAutoTimestamp);
+    // Video does not have timestamp automatically set, so we discard timestamps that are
+    // coming in from other sources for now.
+    if (isAutoTimestamp) {
+        return;
+    }
+    int64_t differenceMs = (newFrameTimestamp - mPreviousFrameTimestamp) / 1000000;
+    mPreviousFrameTimestamp = newFrameTimestamp;
+
+    if (differenceMs < 10 || differenceMs > 100) {
+        // Dismiss noise.
+        return;
+    }
+    ATRACE_INT("TimestampDiff", differenceMs);
+
+    mTimeDifferences[mCounter % ARRAY_SIZE] = differenceMs;
+    mCounter++;
+    nsecs_t average = calculateAverage();
+    ATRACE_INT("TimestampAverage", average);
+
+    // TODO(b/113612090): This are current numbers from trial and error while running videos
+    // from YouTube at 24, 30, and 60 fps.
+    if (average > 14 && average < 18) {
+        ATRACE_INT("FPS", 60);
+    } else if (average > 31 && average < 34) {
+        ATRACE_INT("FPS", 30);
+        updateFrameSkipping(1);
+        return;
+    } else if (average > 39 && average < 42) {
+        ATRACE_INT("FPS", 24);
+    }
+    updateFrameSkipping(0);
+}
+
+nsecs_t Scheduler::calculateAverage() const {
+    nsecs_t sum = std::accumulate(mTimeDifferences.begin(), mTimeDifferences.end(), 0);
+    return (sum / ARRAY_SIZE);
+}
+
+void Scheduler::updateFrameSkipping(const int64_t skipCount) {
+    ATRACE_INT("FrameSkipCount", skipCount);
+    if (mSkipCount != skipCount) {
+        // Only update DispSync if it hasn't been updated yet.
+        mPrimaryDispSync->setRefreshSkipCount(skipCount);
+        mSkipCount = skipCount;
+    }
+}
+
 } // namespace android
diff --git a/services/surfaceflinger/Scheduler/Scheduler.h b/services/surfaceflinger/Scheduler/Scheduler.h
index fdafe58..dd1f24b 100644
--- a/services/surfaceflinger/Scheduler/Scheduler.h
+++ b/services/surfaceflinger/Scheduler/Scheduler.h
@@ -102,6 +102,8 @@
     void addResyncSample(const nsecs_t timestamp);
     void addPresentFence(const std::shared_ptr<FenceTime>& fenceTime);
     void setIgnorePresentFences(bool ignore);
+    void makeHWSyncAvailable(bool makeAvailable);
+    void addNewFrameTimestamp(const nsecs_t newFrameTimestamp, bool isAutoTimestamp);
 
 protected:
     virtual std::unique_ptr<EventThread> makeEventThread(
@@ -110,6 +112,9 @@
             impl::EventThread::InterceptVSyncsCallback interceptCallback);
 
 private:
+    nsecs_t calculateAverage() const;
+    void updateFrameSkipping(const int64_t skipCount);
+
     // TODO(b/113612090): Instead of letting BufferQueueLayer to access mDispSync directly, it
     // should make request to Scheduler to compute next refresh.
     friend class BufferQueueLayer;
@@ -133,6 +138,18 @@
 
     std::unique_ptr<DispSync> mPrimaryDispSync;
     std::unique_ptr<EventControlThread> mEventControlThread;
+
+    // TODO(b/113612090): The following set of variables needs to be revised. For now, this is
+    // a proof of concept. We turn on frame skipping if the difference between the timestamps
+    // is between 32 and 34ms. We expect this currently for 30fps videos, so we render them at 30Hz.
+    nsecs_t mPreviousFrameTimestamp = 0;
+    // Keeping track of whether we are skipping the refresh count. If we want to
+    // simulate 30Hz rendering, we skip every other frame, and this variable is set
+    // to 1.
+    int64_t mSkipCount = 0;
+    static constexpr size_t ARRAY_SIZE = 30;
+    std::array<int64_t, ARRAY_SIZE> mTimeDifferences;
+    size_t mCounter = 0;
 };
 
-} // namespace android
\ No newline at end of file
+} // namespace android
diff --git a/services/surfaceflinger/SurfaceFlinger.cpp b/services/surfaceflinger/SurfaceFlinger.cpp
index a1a8e22..9af5fd8 100644
--- a/services/surfaceflinger/SurfaceFlinger.cpp
+++ b/services/surfaceflinger/SurfaceFlinger.cpp
@@ -105,8 +105,6 @@
 
 #include <layerproto/LayerProtoParser.h>
 
-#define DISPLAY_COUNT       1
-
 namespace android {
 
 using namespace android::hardware::configstore;
@@ -251,7 +249,6 @@
         mLayersRemoved(false),
         mLayersAdded(false),
         mBootTime(systemTime()),
-        mDisplayTokens(),
         mVisibleRegionsDirty(false),
         mGeometryInvalid(false),
         mAnimCompositionPending(false),
@@ -488,40 +485,49 @@
     sp<BBinder> token = new DisplayToken(this);
 
     Mutex::Autolock _l(mStateLock);
-    DisplayDeviceState info;
-    info.type = DisplayDevice::DISPLAY_VIRTUAL;
-    info.displayName = displayName;
-    info.isSecure = secure;
-    mCurrentState.displays.add(token, info);
-    mInterceptor->saveDisplayCreation(info);
+    // Display ID is assigned when virtual display is allocated by HWC.
+    DisplayDeviceState state;
+    state.isSecure = secure;
+    state.displayName = displayName;
+    mCurrentState.displays.add(token, state);
+    mInterceptor->saveDisplayCreation(state);
     return token;
 }
 
 void SurfaceFlinger::destroyDisplay(const sp<IBinder>& displayToken) {
     Mutex::Autolock _l(mStateLock);
 
-    ssize_t idx = mCurrentState.displays.indexOfKey(displayToken);
-    if (idx < 0) {
+    ssize_t index = mCurrentState.displays.indexOfKey(displayToken);
+    if (index < 0) {
         ALOGE("destroyDisplay: Invalid display token %p", displayToken.get());
         return;
     }
 
-    const DisplayDeviceState& info(mCurrentState.displays.valueAt(idx));
-    if (!info.isVirtual()) {
+    const DisplayDeviceState& state = mCurrentState.displays.valueAt(index);
+    if (!state.isVirtual()) {
         ALOGE("destroyDisplay called for non-virtual display");
         return;
     }
-    mInterceptor->saveDisplayDeletion(info.sequenceId);
-    mCurrentState.displays.removeItemsAt(idx);
+    mInterceptor->saveDisplayDeletion(state.sequenceId);
+    mCurrentState.displays.removeItemsAt(index);
     setTransactionFlags(eDisplayTransactionNeeded);
 }
 
 sp<IBinder> SurfaceFlinger::getBuiltInDisplay(int32_t id) {
-    if (uint32_t(id) >= DisplayDevice::NUM_BUILTIN_DISPLAY_TYPES) {
-        ALOGE("getDefaultDisplay: id=%d is not a valid default display id", id);
+    std::optional<DisplayId> displayId;
+
+    if (id == HWC_DISPLAY_PRIMARY) {
+        displayId = getInternalDisplayId();
+    } else if (id == HWC_DISPLAY_EXTERNAL) {
+        displayId = getExternalDisplayId();
+    }
+
+    if (!displayId) {
+        ALOGE("%s: Invalid display %d", __FUNCTION__, id);
         return nullptr;
     }
-    return mDisplayTokens[id];
+
+    return getPhysicalDisplayToken(*displayId);
 }
 
 status_t SurfaceFlinger::getColorManagement(bool* outGetColorManagement) const {
@@ -604,8 +610,10 @@
 
     // start the EventThread
     if (mUseScheduler) {
-        mScheduler = getFactory().createScheduler(
-                [this](bool enabled) { setVsyncEnabled(HWC_DISPLAY_PRIMARY, enabled); });
+        mScheduler = getFactory().createScheduler([this](bool enabled) {
+            setVsyncEnabled(EventThread::DisplayType::Primary, enabled);
+        });
+
         mAppConnectionHandle =
                 mScheduler->createConnection("appConnection", SurfaceFlinger::vsyncPhaseOffsetNs,
                                              [this] { resyncWithRateLimit(); },
@@ -665,7 +673,7 @@
     processDisplayHotplugEventsLocked();
     const auto display = getDefaultDisplayDeviceLocked();
     LOG_ALWAYS_FATAL_IF(!display, "Missing internal display after registering composer callback.");
-    LOG_ALWAYS_FATAL_IF(!getHwComposer().isConnected(display->getId()),
+    LOG_ALWAYS_FATAL_IF(!getHwComposer().isConnected(*display->getId()),
                         "Internal display is disconnected.");
 
     // make the default display GLContext current so that we can create textures
@@ -688,7 +696,7 @@
         };
         mVrFlinger = dvr::VrFlinger::Create(getHwComposer().getComposer(),
                                             getHwComposer()
-                                                    .getHwcDisplayId(display->getId())
+                                                    .fromPhysicalDisplayId(*display->getId())
                                                     .value_or(0),
                                             vrFlingerRequestDisplayCallback);
         if (!mVrFlinger) {
@@ -697,7 +705,7 @@
     }
 
     mEventControlThread = getFactory().createEventControlThread(
-            [this](bool enabled) { setVsyncEnabled(HWC_DISPLAY_PRIMARY, enabled); });
+            [this](bool enabled) { setVsyncEnabled(EventThread::DisplayType::Primary, enabled); });
 
     // initialize our drawing state
     mDrawingState = mCurrentState;
@@ -723,8 +731,8 @@
     // and apply this saturation matrix on Display P3 content. Unless the risk of applying
     // such saturation matrix on Display P3 is understood fully, the API should always return
     // identify matrix.
-    mEnhancedSaturationMatrix = getBE().mHwc->getDataspaceSaturationMatrix(display->getId(),
-            Dataspace::SRGB_LINEAR);
+    mEnhancedSaturationMatrix =
+            getHwComposer().getDataspaceSaturationMatrix(*display->getId(), Dataspace::SRGB_LINEAR);
 
     // we will apply this on Display P3.
     if (mEnhancedSaturationMatrix != mat4()) {
@@ -811,16 +819,9 @@
         return BAD_VALUE;
     }
 
-    int32_t type = NAME_NOT_FOUND;
-    for (int i = 0; i < DisplayDevice::NUM_BUILTIN_DISPLAY_TYPES; ++i) {
-        if (displayToken == mDisplayTokens[i]) {
-            type = i;
-            break;
-        }
-    }
-
-    if (type < 0) {
-        return type;
+    const auto displayId = getPhysicalDisplayId(displayToken);
+    if (!displayId) {
+        return NAME_NOT_FOUND;
     }
 
     // TODO: Not sure if display density should handled by SF any longer
@@ -844,7 +845,7 @@
 
     ConditionalLock _l(mStateLock,
             std::this_thread::get_id() != mMainThreadId);
-    for (const auto& hwConfig : getHwComposer().getConfigs(type)) {
+    for (const auto& hwConfig : getHwComposer().getConfigs(*displayId)) {
         DisplayInfo info = DisplayInfo();
 
         float xdpi = hwConfig->getDpiX();
@@ -856,7 +857,7 @@
         info.viewportW = info.w;
         info.viewportH = info.h;
 
-        if (type == DisplayDevice::DISPLAY_PRIMARY) {
+        if (displayId == getInternalDisplayId()) {
             // The density of the device is provided by a build property
             float density = Density::getBuildDensity() / 160.0f;
             if (density == 0) {
@@ -912,7 +913,7 @@
         // All non-virtual displays are currently considered secure.
         info.secure = true;
 
-        if (type == DisplayDevice::DISPLAY_PRIMARY &&
+        if (displayId == getInternalDisplayId() &&
             primaryDisplayOrientation & DisplayState::eOrientationSwapMask) {
             std::swap(info.w, info.h);
         }
@@ -928,7 +929,6 @@
         return BAD_VALUE;
     }
 
-    // FIXME for now we always return stats for the primary display.
     if (mUseScheduler) {
         mScheduler->getDisplayStatInfo(stats);
     } else {
@@ -949,18 +949,21 @@
 }
 
 void SurfaceFlinger::setActiveConfigInternal(const sp<DisplayDevice>& display, int mode) {
+    if (display->isVirtual()) {
+        ALOGE("%s: Invalid operation on virtual display", __FUNCTION__);
+        return;
+    }
+
     int currentMode = display->getActiveConfig();
     if (mode == currentMode) {
         return;
     }
 
-    if (display->isVirtual()) {
-        ALOGW("Trying to set config for virtual display");
-        return;
-    }
+    const auto displayId = display->getId();
+    LOG_ALWAYS_FATAL_IF(!displayId);
 
     display->setActiveConfig(mode);
-    getHwComposer().setActiveConfig(display->getDisplayType(), mode);
+    getHwComposer().setActiveConfig(*displayId, mode);
 }
 
 status_t SurfaceFlinger::setActiveConfig(const sp<IBinder>& displayToken, int mode) {
@@ -985,29 +988,23 @@
 
     return NO_ERROR;
 }
+
 status_t SurfaceFlinger::getDisplayColorModes(const sp<IBinder>& displayToken,
                                               Vector<ColorMode>* outColorModes) {
     if (!displayToken || !outColorModes) {
         return BAD_VALUE;
     }
 
-    int32_t type = NAME_NOT_FOUND;
-    for (int i = 0; i < DisplayDevice::NUM_BUILTIN_DISPLAY_TYPES; ++i) {
-        if (displayToken == mDisplayTokens[i]) {
-            type = i;
-            break;
-        }
-    }
-
-    if (type < 0) {
-        return type;
+    const auto displayId = getPhysicalDisplayId(displayToken);
+    if (!displayId) {
+        return NAME_NOT_FOUND;
     }
 
     std::vector<ColorMode> modes;
     {
         ConditionalLock _l(mStateLock,
                 std::this_thread::get_id() != mMainThreadId);
-        modes = getHwComposer().getColorModes(type);
+        modes = getHwComposer().getColorModes(*displayId);
     }
     outColorModes->clear();
     std::copy(modes.cbegin(), modes.cend(), std::back_inserter(*outColorModes));
@@ -1024,6 +1021,11 @@
 
 void SurfaceFlinger::setActiveColorModeInternal(const sp<DisplayDevice>& display, ColorMode mode,
                                                 Dataspace dataSpace, RenderIntent renderIntent) {
+    if (display->isVirtual()) {
+        ALOGE("%s: Invalid operation on virtual display", __FUNCTION__);
+        return;
+    }
+
     ColorMode currentMode = display->getActiveColorMode();
     Dataspace currentDataSpace = display->getCompositionDataSpace();
     RenderIntent currentRenderIntent = display->getActiveRenderIntent();
@@ -1033,19 +1035,17 @@
         return;
     }
 
-    if (display->isVirtual()) {
-        ALOGW("Trying to set config for virtual display");
-        return;
-    }
-
     display->setActiveColorMode(mode);
     display->setCompositionDataSpace(dataSpace);
     display->setActiveRenderIntent(renderIntent);
-    getHwComposer().setActiveColorMode(display->getDisplayType(), mode, renderIntent);
 
-    ALOGV("Set active color mode: %s (%d), active render intent: %s (%d), type=%d",
+    const auto displayId = display->getId();
+    LOG_ALWAYS_FATAL_IF(!displayId);
+    getHwComposer().setActiveColorMode(*displayId, mode, renderIntent);
+
+    ALOGV("Set active color mode: %s (%d), active render intent: %s (%d), display=%" PRIu64,
           decodeColorMode(mode).c_str(), mode, decodeRenderIntent(renderIntent).c_str(),
-          renderIntent, display->getDisplayType());
+          renderIntent, *displayId);
 }
 
 status_t SurfaceFlinger::setActiveColorMode(const sp<IBinder>& displayToken, ColorMode mode) {
@@ -1255,18 +1255,22 @@
 
     if (makeAvailable) {
         mHWVsyncAvailable = true;
+        // TODO(b/113612090): This is silly, but necessary evil until we turn on the flag for good.
+        if (mUseScheduler) {
+            mScheduler->makeHWSyncAvailable(true);
+        }
     } else if (!mHWVsyncAvailable) {
         // Hardware vsync is not currently available, so abort the resync
         // attempt for now
         return;
     }
 
-    const auto displayId = DisplayDevice::DISPLAY_PRIMARY;
-    if (!getHwComposer().isConnected(displayId)) {
+    const auto displayId = getInternalDisplayId();
+    if (!displayId || !getHwComposer().isConnected(*displayId)) {
         return;
     }
 
-    const auto activeConfig = getHwComposer().getActiveConfig(displayId);
+    const auto activeConfig = getHwComposer().getActiveConfig(*displayId);
     const nsecs_t period = activeConfig->getVsyncPeriod();
 
     if (mUseScheduler) {
@@ -1317,12 +1321,11 @@
         return;
     }
 
-    int32_t type;
-    if (!getBE().mHwc->onVsync(hwcDisplayId, timestamp, &type)) {
+    if (!getHwComposer().onVsync(hwcDisplayId, timestamp)) {
         return;
     }
 
-    if (type != DisplayDevice::DISPLAY_PRIMARY) {
+    if (hwcDisplayId != getHwComposer().getInternalHwcDisplayId()) {
         // For now, we don't do anything with external display vsyncs.
         return;
     }
@@ -1385,11 +1388,13 @@
     repaintEverything();
 }
 
-void SurfaceFlinger::setVsyncEnabled(int disp, int enabled) {
+void SurfaceFlinger::setVsyncEnabled(EventThread::DisplayType /*displayType*/, bool enabled) {
     ATRACE_CALL();
     Mutex::Autolock lock(mStateLock);
-    getHwComposer().setVsyncEnabled(disp,
-            enabled ? HWC2::Vsync::Enable : HWC2::Vsync::Disable);
+    if (const auto displayId = getInternalDisplayId()) {
+        getHwComposer().setVsyncEnabled(*displayId,
+                                        enabled ? HWC2::Vsync::Enable : HWC2::Vsync::Disable);
+    }
 }
 
 // Note: it is assumed the caller holds |mStateLock| when this is called
@@ -1460,7 +1465,7 @@
     setPowerModeInternal(display, currentDisplayPowerMode, /*stateLockHeld*/ true);
 
     // Reset the timing values to account for the period of the swapped in HWC
-    const auto activeConfig = getHwComposer().getActiveConfig(display->getId());
+    const auto activeConfig = getHwComposer().getActiveConfig(*display->getId());
     const nsecs_t period = activeConfig->getVsyncPeriod();
     mAnimFrameTracker.setDisplayRefreshPeriod(period);
 
@@ -1601,24 +1606,25 @@
         mGeometryInvalid = false;
         for (const auto& [token, display] : mDisplays) {
             const auto displayId = display->getId();
-            if (displayId >= 0) {
-                const Vector<sp<Layer>>& currentLayers(
-                        display->getVisibleLayersSortedByZ());
-                for (size_t i = 0; i < currentLayers.size(); i++) {
-                    const auto& layer = currentLayers[i];
+            if (!displayId) {
+                continue;
+            }
 
-                    if (!layer->hasHwcLayer(displayId)) {
-                        if (!layer->createHwcLayer(getBE().mHwc.get(), displayId)) {
-                            layer->forceClientComposition(displayId);
-                            continue;
-                        }
-                    }
+            const Vector<sp<Layer>>& currentLayers = display->getVisibleLayersSortedByZ();
+            for (size_t i = 0; i < currentLayers.size(); i++) {
+                const auto& layer = currentLayers[i];
 
-                    layer->setGeometry(display, i);
-                    if (mDebugDisableHWC || mDebugRegion) {
-                        layer->forceClientComposition(displayId);
+                if (!layer->hasHwcLayer(*displayId)) {
+                    if (!layer->createHwcLayer(&getHwComposer(), *displayId)) {
+                        layer->forceClientComposition(*displayId);
+                        continue;
                     }
                 }
+
+                layer->setGeometry(display, i);
+                if (mDebugDisableHWC || mDebugRegion) {
+                    layer->forceClientComposition(*displayId);
+                }
             }
         }
     }
@@ -1626,41 +1632,43 @@
     // Set the per-frame data
     for (const auto& [token, display] : mDisplays) {
         const auto displayId = display->getId();
-        if (displayId < 0) {
+        if (!displayId) {
             continue;
         }
 
         if (mDrawingState.colorMatrixChanged) {
             display->setColorTransform(mDrawingState.colorMatrix);
-            status_t result = getBE().mHwc->setColorTransform(displayId, mDrawingState.colorMatrix);
-            ALOGE_IF(result != NO_ERROR, "Failed to set color transform on "
-                    "display %d: %d", displayId, result);
+            status_t result =
+                    getHwComposer().setColorTransform(*displayId, mDrawingState.colorMatrix);
+            ALOGE_IF(result != NO_ERROR, "Failed to set color transform on display %" PRIu64 ": %d",
+                     *displayId, result);
         }
         for (auto& layer : display->getVisibleLayersSortedByZ()) {
             if (layer->isHdrY410()) {
-                layer->forceClientComposition(displayId);
+                layer->forceClientComposition(*displayId);
             } else if ((layer->getDataSpace() == Dataspace::BT2020_PQ ||
                         layer->getDataSpace() == Dataspace::BT2020_ITU_PQ) &&
                     !display->hasHDR10Support()) {
-                layer->forceClientComposition(displayId);
+                layer->forceClientComposition(*displayId);
             } else if ((layer->getDataSpace() == Dataspace::BT2020_HLG ||
                         layer->getDataSpace() == Dataspace::BT2020_ITU_HLG) &&
                     !display->hasHLGSupport()) {
-                layer->forceClientComposition(displayId);
+                layer->forceClientComposition(*displayId);
             }
 
             // TODO(b/111562338) remove when composer 2.3 is shipped.
             if (layer->hasColorTransform()) {
-                layer->forceClientComposition(displayId);
+                layer->forceClientComposition(*displayId);
             }
 
-            if (layer->getForceClientComposition(displayId)) {
+            if (layer->getForceClientComposition(*displayId)) {
                 ALOGV("[%s] Requesting Client composition", layer->getName().string());
-                layer->setCompositionType(displayId, HWC2::Composition::Client);
+                layer->setCompositionType(*displayId, HWC2::Composition::Client);
                 continue;
             }
 
-            layer->setPerFrameData(display);
+            layer->setPerFrameData(*displayId, display->getTransform(), display->getViewport(),
+                                   display->getSupportedPerFrameMetadata());
         }
 
         if (useColorManagement) {
@@ -1680,10 +1688,14 @@
         for (auto& layer : display->getVisibleLayersSortedByZ()) {
             const auto displayId = display->getId();
             layer->getBE().compositionInfo.compositionType = layer->getCompositionType(displayId);
-            if (!layer->setHwcLayer(displayId)) {
-                ALOGV("Need to create HWCLayer for %s", layer->getName().string());
+
+            if (displayId) {
+                if (!layer->setHwcLayer(*displayId)) {
+                    ALOGV("Need to create HWCLayer for %s", layer->getName().string());
+                }
+                layer->getBE().compositionInfo.hwc.displayId = *displayId;
             }
-            layer->getBE().compositionInfo.hwc.displayId = displayId;
+
             getBE().mCompositionInfo[token].push_back(layer->getBE().compositionInfo);
             layer->getBE().compositionInfo.hwc.hwcLayer = nullptr;
         }
@@ -1843,7 +1855,7 @@
 
     getBE().mDisplayTimeline.updateSignalTimes();
     mPreviousPresentFence =
-            display ? getHwComposer().getPresentFence(display->getId()) : Fence::NO_FENCE;
+            display ? getHwComposer().getPresentFence(*display->getId()) : Fence::NO_FENCE;
     auto presentFenceTime = std::make_shared<FenceTime>(mPreviousPresentFence);
     getBE().mDisplayTimeline.push(presentFenceTime);
 
@@ -1866,8 +1878,8 @@
     }
 
     mDrawingState.traverseInZOrder([&](Layer* layer) {
-        bool frameLatched = layer->onPostComposition(glCompositionDoneFenceTime,
-                presentFenceTime, compositorTiming);
+        bool frameLatched = layer->onPostComposition(display->getId(), glCompositionDoneFenceTime,
+                                                     presentFenceTime, compositorTiming);
         if (frameLatched) {
             recordBufferingStats(layer->getName().string(),
                     layer->getOccupancyHistory(false));
@@ -1887,7 +1899,7 @@
     }
 
     if (!hasSyncFramework) {
-        if (display && getHwComposer().isConnected(display->getId()) && display->isPoweredOn()) {
+        if (display && getHwComposer().isConnected(*display->getId()) && display->isPoweredOn()) {
             if (mUseScheduler) {
                 mScheduler->enableHardwareVsync();
             } else {
@@ -1902,10 +1914,10 @@
         if (presentFenceTime->isValid()) {
             mAnimFrameTracker.setActualPresentFence(
                     std::move(presentFenceTime));
-        } else if (display && getHwComposer().isConnected(display->getId())) {
+        } else if (display && getHwComposer().isConnected(*display->getId())) {
             // The HWC doesn't support present fences, so use the refresh
             // timestamp instead.
-            const nsecs_t presentTime = getHwComposer().getRefreshTimestamp(display->getId());
+            const nsecs_t presentTime = getHwComposer().getRefreshTimestamp(*display->getId());
             mAnimFrameTracker.setActualPresentTime(presentTime);
         }
         mAnimFrameTracker.advanceFrame();
@@ -1918,8 +1930,7 @@
 
     mTimeStats.setPresentFenceGlobal(presentFenceTime);
 
-    if (display && getHwComposer().isConnected(display->getId()) &&
-        display->getPowerMode() == HWC_POWER_MODE_OFF) {
+    if (display && getHwComposer().isConnected(*display->getId()) && !display->isPoweredOn()) {
         return;
     }
 
@@ -1948,6 +1959,9 @@
             ATRACE_INT("TexturePoolSize", mTexturePool.size());
         }
     }
+
+    mTransactionCompletedThread.addPresentFence(mPreviousPresentFence);
+    mTransactionCompletedThread.sendCallbacks();
 }
 
 void SurfaceFlinger::rebuildLayerStacks() {
@@ -1973,6 +1987,7 @@
 
                 mDrawingState.traverseInZOrder([&](Layer* layer) {
                     bool hwcLayerDestroyed = false;
+                    const auto displayId = display->getId();
                     if (layer->belongsToDisplay(display->getLayerStack(), display->isPrimary())) {
                         Region drawRegion(tr.transform(
                                 layer->visibleNonTransparentRegion));
@@ -1982,13 +1997,13 @@
                         } else {
                             // Clear out the HWC layer if this layer was
                             // previously visible, but no longer is
-                            hwcLayerDestroyed = layer->destroyHwcLayer(display->getId());
+                            hwcLayerDestroyed = displayId && layer->destroyHwcLayer(*displayId);
                         }
                     } else {
                         // WM changes display->layerStack upon sleep/awake.
                         // Here we make sure we delete the HWC layers even if
                         // WM changed their layer stack.
-                        hwcLayerDestroyed = layer->destroyHwcLayer(display->getId());
+                        hwcLayerDestroyed = displayId && layer->destroyHwcLayer(*displayId);
                     }
 
                     // If a layer is not going to get a release fence because
@@ -2113,12 +2128,11 @@
     //   emit any black frames until a layer is added to the layer stack.
     bool mustRecompose = dirty && !(empty && wasEmpty);
 
-    ALOGV_IF(display->getDisplayType() == DisplayDevice::DISPLAY_VIRTUAL,
-            "id[%d]: %s composition (%sdirty %sempty %swasEmpty)", display->getId(),
-            mustRecompose ? "doing" : "skipping",
-            dirty ? "+" : "-",
-            empty ? "+" : "-",
-            wasEmpty ? "+" : "-");
+    const char flagPrefix[] = {'-', '+'};
+    static_cast<void>(flagPrefix);
+    ALOGV_IF(display->isVirtual(), "%s: %s composition for %s (%cdirty %cempty %cwasEmpty)",
+             __FUNCTION__, mustRecompose ? "doing" : "skipping", display->getDebugName().c_str(),
+             flagPrefix[dirty], flagPrefix[empty], flagPrefix[wasEmpty]);
 
     display->beginFrame(mustRecompose);
 
@@ -2135,10 +2149,8 @@
 
     status_t result = display->prepareFrame(getHwComposer(),
                                             getBE().mCompositionInfo[display->getDisplayToken()]);
-    ALOGE_IF(result != NO_ERROR,
-             "prepareFrame for display %d failed:"
-             " %d (%s)",
-             display->getId(), result, strerror(-result));
+    ALOGE_IF(result != NO_ERROR, "prepareFrame failed for %s: %d (%s)",
+             display->getDebugName().c_str(), result, strerror(-result));
 }
 
 void SurfaceFlinger::doComposition(const sp<DisplayDevice>& display, bool repaintEverything) {
@@ -2161,8 +2173,9 @@
 void SurfaceFlinger::postFrame()
 {
     // |mStateLock| not needed as we are on the main thread
-    if (getBE().mHwc->isConnected(HWC_DISPLAY_PRIMARY)) {
-        uint32_t flipCount = getDefaultDisplayDeviceLocked()->getPageFlipCount();
+    const auto display = getDefaultDisplayDeviceLocked();
+    if (display && getHwComposer().isConnected(*display->getId())) {
+        uint32_t flipCount = display->getPageFlipCount();
         if (flipCount % LOG_FRAME_STATS_PERIOD == 0) {
             logFrameStats();
         }
@@ -2178,8 +2191,8 @@
 
     if (display->isPoweredOn()) {
         const auto displayId = display->getId();
-        if (displayId >= 0) {
-            getBE().mHwc->presentAndGetReleaseFences(displayId);
+        if (displayId) {
+            getHwComposer().presentAndGetReleaseFences(*displayId);
         }
         display->onSwapBuffersCompleted();
         display->makeCurrent();
@@ -2189,9 +2202,9 @@
             // The layer buffer from the previous frame (if any) is released
             // by HWC only when the release fence from this frame (if any) is
             // signaled.  Always get the release fence from HWC first.
-            auto hwcLayer = layer->getHwcLayer(displayId);
-            if (displayId >= 0) {
-                releaseFence = getBE().mHwc->getLayerReleaseFence(displayId, hwcLayer);
+            if (displayId && layer->hasHwcLayer(*displayId)) {
+                releaseFence = getHwComposer().getLayerReleaseFence(*displayId,
+                                                                    layer->getHwcLayer(*displayId));
             }
 
             // If the layer was client composited in the previous frame, we
@@ -2211,14 +2224,15 @@
         // display->getVisibleLayersSortedByZ.  The best we can do is to
         // supply them with the present fence.
         if (!display->getLayersNeedingFences().isEmpty()) {
-            sp<Fence> presentFence = getBE().mHwc->getPresentFence(displayId);
+            sp<Fence> presentFence =
+                    displayId ? getBE().mHwc->getPresentFence(*displayId) : Fence::NO_FENCE;
             for (auto& layer : display->getLayersNeedingFences()) {
                 layer->getBE().onLayerDisplayed(presentFence);
             }
         }
 
-        if (displayId >= 0) {
-            getBE().mHwc->clearReleaseFences(displayId);
+        if (displayId) {
+            getHwComposer().clearReleaseFences(*displayId);
         }
     }
 }
@@ -2253,72 +2267,36 @@
     // here the transaction has been committed
 }
 
-DisplayDevice::DisplayType SurfaceFlinger::determineDisplayType(hwc2_display_t hwcDisplayId,
-                                                                HWC2::Connection connection) const {
-    // Figure out whether the event is for the primary display or an
-    // external display by matching the Hwc display id against one for a
-    // connected display. If we did not find a match, we then check what
-    // displays are not already connected to determine the type. If we don't
-    // have a connected primary display, we assume the new display is meant to
-    // be the primary display, and then if we don't have an external display,
-    // we assume it is that.
-    const auto primaryHwcDisplayId = getBE().mHwc->getHwcDisplayId(DisplayDevice::DISPLAY_PRIMARY);
-    const auto externalHwcDisplayId =
-            getBE().mHwc->getHwcDisplayId(DisplayDevice::DISPLAY_EXTERNAL);
-    if (primaryHwcDisplayId && primaryHwcDisplayId == hwcDisplayId) {
-        return DisplayDevice::DISPLAY_PRIMARY;
-    } else if (externalHwcDisplayId && externalHwcDisplayId == hwcDisplayId) {
-        return DisplayDevice::DISPLAY_EXTERNAL;
-    } else if (connection == HWC2::Connection::Connected && !primaryHwcDisplayId) {
-        return DisplayDevice::DISPLAY_PRIMARY;
-    } else if (connection == HWC2::Connection::Connected && !externalHwcDisplayId) {
-        return DisplayDevice::DISPLAY_EXTERNAL;
-    }
-
-    return DisplayDevice::DISPLAY_ID_INVALID;
-}
-
 void SurfaceFlinger::processDisplayHotplugEventsLocked() {
     for (const auto& event : mPendingHotplugEvents) {
-        auto displayType = determineDisplayType(event.hwcDisplayId, event.connection);
-        if (displayType == DisplayDevice::DISPLAY_ID_INVALID) {
-            ALOGW("Unable to determine the display type for display %" PRIu64, event.hwcDisplayId);
-            continue;
-        }
+        const std::optional<DisplayIdentificationInfo> info =
+                getHwComposer().onHotplug(event.hwcDisplayId, event.connection);
 
-        if (getBE().mHwc->isUsingVrComposer() && displayType == DisplayDevice::DISPLAY_EXTERNAL) {
-            ALOGE("External displays are not supported by the vr hardware composer.");
+        if (!info) {
             continue;
         }
 
-        const auto displayId =
-                getBE().mHwc->onHotplug(event.hwcDisplayId, displayType, event.connection);
-        if (displayId) {
-            ALOGV("Display %" PRIu64 " has stable ID %" PRIu64, event.hwcDisplayId, *displayId);
-        }
-
         if (event.connection == HWC2::Connection::Connected) {
-            if (!mDisplayTokens[displayType].get()) {
-                ALOGV("Creating built in display %d", displayType);
-                mDisplayTokens[displayType] = new BBinder();
-                DisplayDeviceState info;
-                info.type = displayType;
-                info.displayName = displayType == DisplayDevice::DISPLAY_PRIMARY ?
-                        "Built-in Screen" : "External Screen";
-                info.isSecure = true; // All physical displays are currently considered secure.
-                mCurrentState.displays.add(mDisplayTokens[displayType], info);
-                mInterceptor->saveDisplayCreation(info);
+            if (!mPhysicalDisplayTokens.count(info->id)) {
+                ALOGV("Creating display %" PRIu64, info->id);
+                mPhysicalDisplayTokens[info->id] = new BBinder();
+                DisplayDeviceState state;
+                state.displayId = info->id;
+                state.isSecure = true; // All physical displays are currently considered secure.
+                state.displayName = info->name;
+                mCurrentState.displays.add(mPhysicalDisplayTokens[info->id], state);
+                mInterceptor->saveDisplayCreation(state);
             }
         } else {
-            ALOGV("Removing built in display %d", displayType);
+            ALOGV("Removing display %" PRIu64, info->id);
 
-            ssize_t idx = mCurrentState.displays.indexOfKey(mDisplayTokens[displayType]);
-            if (idx >= 0) {
-                const DisplayDeviceState& info(mCurrentState.displays.valueAt(idx));
-                mInterceptor->saveDisplayDeletion(info.sequenceId);
-                mCurrentState.displays.removeItemsAt(idx);
+            ssize_t index = mCurrentState.displays.indexOfKey(mPhysicalDisplayTokens[info->id]);
+            if (index >= 0) {
+                const DisplayDeviceState& state = mCurrentState.displays.valueAt(index);
+                mInterceptor->saveDisplayDeletion(state.sequenceId);
+                mCurrentState.displays.removeItemsAt(index);
             }
-            mDisplayTokens[displayType].clear();
+            mPhysicalDisplayTokens.erase(info->id);
         }
 
         processDisplayChangesLocked();
@@ -2328,31 +2306,36 @@
 }
 
 sp<DisplayDevice> SurfaceFlinger::setupNewDisplayDeviceInternal(
-        const wp<IBinder>& displayToken, int32_t displayId, const DisplayDeviceState& state,
-        const sp<DisplaySurface>& dispSurface, const sp<IGraphicBufferProducer>& producer) {
-    DisplayDeviceCreationArgs creationArgs(this, displayToken, state.type, displayId);
+        const wp<IBinder>& displayToken, const std::optional<DisplayId>& displayId,
+        const DisplayDeviceState& state, const sp<DisplaySurface>& dispSurface,
+        const sp<IGraphicBufferProducer>& producer) {
+    DisplayDeviceCreationArgs creationArgs(this, displayToken, displayId);
+    creationArgs.isVirtual = state.isVirtual();
     creationArgs.isSecure = state.isSecure;
     creationArgs.displaySurface = dispSurface;
     creationArgs.hasWideColorGamut = false;
     creationArgs.supportedPerFrameMetadata = 0;
 
-    if (useColorManagement && displayId >= 0) {
-        std::vector<ColorMode> modes = getHwComposer().getColorModes(displayId);
+    const bool isInternalDisplay = displayId && displayId == getInternalDisplayId();
+    creationArgs.isPrimary = isInternalDisplay;
+
+    if (useColorManagement && displayId) {
+        std::vector<ColorMode> modes = getHwComposer().getColorModes(*displayId);
         for (ColorMode colorMode : modes) {
             if (isWideColorMode(colorMode)) {
                 creationArgs.hasWideColorGamut = true;
             }
 
             std::vector<RenderIntent> renderIntents =
-                    getHwComposer().getRenderIntents(displayId, colorMode);
+                    getHwComposer().getRenderIntents(*displayId, colorMode);
             creationArgs.hwcColorModes.emplace(colorMode, renderIntents);
         }
     }
 
-    if (displayId >= 0) {
-        getHwComposer().getHdrCapabilities(displayId, &creationArgs.hdrCapabilities);
+    if (displayId) {
+        getHwComposer().getHdrCapabilities(*displayId, &creationArgs.hdrCapabilities);
         creationArgs.supportedPerFrameMetadata =
-                getHwComposer().getSupportedPerFrameMetadata(displayId);
+                getHwComposer().getSupportedPerFrameMetadata(*displayId);
     }
 
     auto nativeWindowSurface = getFactory().createNativeWindowSurface(producer);
@@ -2363,7 +2346,7 @@
      * Create our display's surface
      */
     std::unique_ptr<renderengine::Surface> renderSurface = getRenderEngine().createSurface();
-    renderSurface->setCritical(state.type == DisplayDevice::DISPLAY_PRIMARY);
+    renderSurface->setCritical(isInternalDisplay);
     renderSurface->setAsync(state.isVirtual());
     renderSurface->setNativeWindow(nativeWindow.get());
     creationArgs.displayWidth = renderSurface->getWidth();
@@ -2381,9 +2364,8 @@
         nativeWindow->setSwapInterval(nativeWindow.get(), 0);
     }
 
-    creationArgs.displayInstallOrientation = state.type == DisplayDevice::DISPLAY_PRIMARY
-            ? primaryDisplayOrientation
-            : DisplayState::eOrientationDefault;
+    creationArgs.displayInstallOrientation =
+            isInternalDisplay ? primaryDisplayOrientation : DisplayState::eOrientationDefault;
 
     // virtual displays are always considered enabled
     creationArgs.initialPowerMode = state.isVirtual() ? HWC_POWER_MODE_NORMAL : HWC_POWER_MODE_OFF;
@@ -2402,9 +2384,11 @@
     }
     setActiveColorModeInternal(display, defaultColorMode, defaultDataSpace,
                                RenderIntent::COLORIMETRIC);
-    if (state.type < DisplayDevice::DISPLAY_VIRTUAL) {
-        display->setActiveConfig(getHwComposer().getActiveConfigIndex(state.type));
+    if (!state.isVirtual()) {
+        LOG_ALWAYS_FATAL_IF(!displayId);
+        display->setActiveConfig(getHwComposer().getActiveConfigIndex(*displayId));
     }
+
     display->setLayerStack(state.layerStack);
     display->setProjection(state.orientation, state.viewport, state.frame);
     display->setDisplayName(state.displayName);
@@ -2430,6 +2414,10 @@
         for (size_t i = 0; i < dc;) {
             const ssize_t j = curr.indexOfKey(draw.keyAt(i));
             if (j < 0) {
+                // Save display IDs before disconnecting.
+                const auto internalDisplayId = getInternalDisplayId();
+                const auto externalDisplayId = getExternalDisplayId();
+
                 // in drawing state but not in current state
                 // Call makeCurrent() on the primary display so we can
                 // be sure that nothing associated with this display
@@ -2440,14 +2428,14 @@
                 if (const auto display = getDisplayDeviceLocked(draw.keyAt(i))) {
                     display->disconnect(getHwComposer());
                 }
-                if (draw[i].type == DisplayDevice::DISPLAY_PRIMARY) {
+                if (internalDisplayId && internalDisplayId == draw[i].displayId) {
                     if (mUseScheduler) {
                         mScheduler->hotplugReceived(mAppConnectionHandle,
                                                     EventThread::DisplayType::Primary, false);
                     } else {
                         mEventThread->onHotplugReceived(EventThread::DisplayType::Primary, false);
                     }
-                } else if (draw[i].type == DisplayDevice::DISPLAY_EXTERNAL) {
+                } else if (externalDisplayId && externalDisplayId == draw[i].displayId) {
                     if (mUseScheduler) {
                         mScheduler->hotplugReceived(mAppConnectionHandle,
                                                     EventThread::DisplayType::External, false);
@@ -2505,14 +2493,14 @@
                 sp<IGraphicBufferConsumer> bqConsumer;
                 getFactory().createBufferQueue(&bqProducer, &bqConsumer, false);
 
-                int32_t displayId = -1;
+                std::optional<DisplayId> displayId;
                 if (state.isVirtual()) {
                     // Virtual displays without a surface are dormant:
                     // they have external state (layer stack, projection,
                     // etc.) but no internal state (i.e. a DisplayDevice).
                     if (state.surface != nullptr) {
                         // Allow VR composer to use virtual displays.
-                        if (mUseHwcVirtualDisplays || getBE().mHwc->isUsingVrComposer()) {
+                        if (mUseHwcVirtualDisplays || getHwComposer().isUsingVrComposer()) {
                             int width = 0;
                             int status = state.surface->query(NATIVE_WINDOW_WIDTH, &width);
                             ALOGE_IF(status != NO_ERROR, "Unable to query width (%d)", status);
@@ -2524,14 +2512,14 @@
                             ALOGE_IF(status != NO_ERROR, "Unable to query format (%d)", status);
                             auto format = static_cast<ui::PixelFormat>(intFormat);
 
-                            getBE().mHwc->allocateVirtualDisplay(width, height, &format,
-                                                                 &displayId);
+                            displayId =
+                                    getHwComposer().allocateVirtualDisplay(width, height, &format);
                         }
 
                         // TODO: Plumb requested format back up to consumer
 
                         sp<VirtualDisplaySurface> vds =
-                                new VirtualDisplaySurface(*getBE().mHwc, displayId, state.surface,
+                                new VirtualDisplaySurface(getHwComposer(), displayId, state.surface,
                                                           bqProducer, bqConsumer,
                                                           state.displayName);
 
@@ -2544,8 +2532,9 @@
                              "surface is provided (%p), ignoring it",
                              state.surface.get());
 
-                    displayId = state.type;
-                    dispSurface = new FramebufferSurface(*getBE().mHwc, displayId, bqConsumer);
+                    displayId = state.displayId;
+                    LOG_ALWAYS_FATAL_IF(!displayId);
+                    dispSurface = new FramebufferSurface(getHwComposer(), *displayId, bqConsumer);
                     producer = bqProducer;
                 }
 
@@ -2555,7 +2544,9 @@
                                       setupNewDisplayDeviceInternal(displayToken, displayId, state,
                                                                     dispSurface, producer));
                     if (!state.isVirtual()) {
-                        if (state.type == DisplayDevice::DISPLAY_PRIMARY) {
+                        LOG_ALWAYS_FATAL_IF(!displayId);
+
+                        if (displayId == getInternalDisplayId()) {
                             if (mUseScheduler) {
                                 mScheduler->hotplugReceived(mAppConnectionHandle,
                                                             EventThread::DisplayType::Primary,
@@ -2564,7 +2555,7 @@
                                 mEventThread->onHotplugReceived(EventThread::DisplayType::Primary,
                                                                 true);
                             }
-                        } else if (state.type == DisplayDevice::DISPLAY_EXTERNAL) {
+                        } else if (displayId == getExternalDisplayId()) {
                             if (mUseScheduler) {
                                 mScheduler->hotplugReceived(mAppConnectionHandle,
                                                             EventThread::DisplayType::External,
@@ -2718,7 +2709,7 @@
 void SurfaceFlinger::updateCursorAsync()
 {
     for (const auto& [token, display] : mDisplays) {
-        if (display->getId() < 0) {
+        if (!display->getId()) {
             continue;
         }
 
@@ -2742,7 +2733,11 @@
             // abandon the buffer queue.
             if (l->isRemovedFromCurrentState()) {
                 l->destroyAllHwcLayers();
-                l->releasePendingBuffer(systemTime());
+                // destroyAllHwcLayers traverses to children, but releasePendingBuffer
+                // doesn't in other scenarios. So we have to traverse explicitly here.
+                l->traverseInZOrder(LayerVector::StateSet::Drawing, [&](Layer* child) {
+                    child->releasePendingBuffer(systemTime());
+                });
             }
         }
         mLayersPendingRemoval.clear();
@@ -3110,8 +3105,9 @@
                 case HWC2::Composition::Device:
                 case HWC2::Composition::Sideband:
                 case HWC2::Composition::SolidColor: {
+                    LOG_ALWAYS_FATAL_IF(!displayId);
                     const Layer::State& state(layer->getDrawingState());
-                    if (layer->getClearClientTarget(displayId) && !firstLayer &&
+                    if (layer->getClearClientTarget(*displayId) && !firstLayer &&
                         layer->isOpaque(state) && (layer->getAlpha() == 1.0f) &&
                         hasClientComposition) {
                         // never clear the very first layer since we're
@@ -3301,9 +3297,15 @@
         transactionFlags |= setDisplayStateLocked(display);
     }
 
+    uint32_t clientStateFlags = 0;
     for (const ComposerState& state : states) {
-        transactionFlags |= setClientStateLocked(state);
+        clientStateFlags |= setClientStateLocked(state);
     }
+    // If the state doesn't require a traversal and there are callbacks, send them now
+    if (!(clientStateFlags & eTraversalNeeded)) {
+        mTransactionCompletedThread.sendCallbacks();
+    }
+    transactionFlags |= clientStateFlags;
 
     // Iterate through all layers again to determine if any need to be destroyed. Marking layers
     // as destroyed should only occur after setting all other states. This is to allow for a
@@ -3612,6 +3614,17 @@
     if (what & layer_state_t::eSidebandStreamChanged) {
         if (layer->setSidebandStream(s.sidebandStream)) flags |= eTraversalNeeded;
     }
+
+    std::vector<sp<CallbackHandle>> callbackHandles;
+    if ((what & layer_state_t::eListenerCallbacksChanged) && (!s.listenerCallbacks.empty())) {
+        mTransactionCompletedThread.run();
+        for (const auto& [listener, callbackIds] : s.listenerCallbacks) {
+            callbackHandles.emplace_back(new CallbackHandle(listener, callbackIds, s.surface));
+        }
+    }
+    if (layer->setTransactionCompletedListeners(callbackHandles)) flags |= eTraversalNeeded;
+    // Do not put anything that updates layer state or modifies flags after
+    // setTransactionCompletedListener
     return flags;
 }
 
@@ -3812,7 +3825,7 @@
 // ---------------------------------------------------------------------------
 
 void SurfaceFlinger::onInitializeDisplays() {
-    const auto displayToken = mDisplayTokens[DisplayDevice::DISPLAY_PRIMARY];
+    const auto displayToken = getInternalDisplayToken();
     if (!displayToken) return;
 
     // reset screen orientation and use primary layer stack
@@ -3836,7 +3849,7 @@
 
     setPowerModeInternal(display, HWC_POWER_MODE_NORMAL, /*stateLockHeld*/ false);
 
-    const auto activeConfig = getHwComposer().getActiveConfig(display->getId());
+    const auto activeConfig = getHwComposer().getActiveConfig(*display->getId());
     const nsecs_t period = activeConfig->getVsyncPeriod();
     mAnimFrameTracker.setDisplayRefreshPeriod(period);
 
@@ -3853,16 +3866,18 @@
 
 void SurfaceFlinger::setPowerModeInternal(const sp<DisplayDevice>& display, int mode,
                                           bool stateLockHeld) {
-    const int32_t displayId = display->getId();
-    ALOGD("Setting power mode %d on display %d", mode, displayId);
-
-    int currentMode = display->getPowerMode();
-    if (mode == currentMode) {
+    if (display->isVirtual()) {
+        ALOGE("%s: Invalid operation on virtual display", __FUNCTION__);
         return;
     }
 
-    if (display->isVirtual()) {
-        ALOGW("Trying to set power mode for virtual display");
+    const auto displayId = display->getId();
+    LOG_ALWAYS_FATAL_IF(!displayId);
+
+    ALOGD("Setting power mode %d on display %" PRIu64, mode, *displayId);
+
+    int currentMode = display->getPowerMode();
+    if (mode == currentMode) {
         return;
     }
 
@@ -3878,12 +3893,10 @@
         mInterceptor->savePowerModeUpdate(mCurrentState.displays.valueAt(idx).sequenceId, mode);
     }
 
-    int32_t type = display->getDisplayType();
     if (currentMode == HWC_POWER_MODE_OFF) {
         // Turn on the display
-        getHwComposer().setPowerMode(type, mode);
+        getHwComposer().setPowerMode(*displayId, mode);
         if (display->isPrimary() && mode != HWC_POWER_MODE_DOZE_SUSPEND) {
-            // FIXME: eventthread only knows about the main display right now
             if (mUseScheduler) {
                 mScheduler->onScreenAcquired(mAppConnectionHandle);
             } else {
@@ -3914,7 +3927,6 @@
             } else {
                 disableHardwareVsync(true); // also cancels any in-progress resync
             }
-            // FIXME: eventthread only knows about the main display right now
             if (mUseScheduler) {
                 mScheduler->onScreenReleased(mAppConnectionHandle);
             } else {
@@ -3922,15 +3934,14 @@
             }
         }
 
-        getHwComposer().setPowerMode(type, mode);
+        getHwComposer().setPowerMode(*displayId, mode);
         mVisibleRegionsDirty = true;
         // from this point on, SF will stop drawing on this display
     } else if (mode == HWC_POWER_MODE_DOZE ||
                mode == HWC_POWER_MODE_NORMAL) {
         // Update display while dozing
-        getHwComposer().setPowerMode(type, mode);
+        getHwComposer().setPowerMode(*displayId, mode);
         if (display->isPrimary() && currentMode == HWC_POWER_MODE_DOZE_SUSPEND) {
-            // FIXME: eventthread only knows about the main display right now
             if (mUseScheduler) {
                 mScheduler->onScreenAcquired(mAppConnectionHandle);
             } else {
@@ -3946,24 +3957,23 @@
             } else {
                 disableHardwareVsync(true); // also cancels any in-progress resync
             }
-            // FIXME: eventthread only knows about the main display right now
             if (mUseScheduler) {
                 mScheduler->onScreenReleased(mAppConnectionHandle);
             } else {
                 mEventThread->onScreenReleased();
             }
         }
-        getHwComposer().setPowerMode(type, mode);
+        getHwComposer().setPowerMode(*displayId, mode);
     } else {
         ALOGE("Attempting to set unknown power mode: %d\n", mode);
-        getHwComposer().setPowerMode(type, mode);
+        getHwComposer().setPowerMode(*displayId, mode);
     }
 
     if (display->isPrimary()) {
         mTimeStats.setPowerMode(mode);
     }
 
-    ALOGD("Finished setting power mode %d on display %d", mode, displayId);
+    ALOGD("Finished setting power mode %d on display %" PRIu64, mode, *displayId);
 }
 
 void SurfaceFlinger::setPowerMode(const sp<IBinder>& displayToken, int mode) {
@@ -4142,9 +4152,9 @@
         index++;
     }
 
-    if (const auto displayId = DisplayDevice::DISPLAY_PRIMARY;
-        getHwComposer().isConnected(displayId)) {
-        const auto activeConfig = getBE().mHwc->getActiveConfig(displayId);
+    if (const auto displayId = getInternalDisplayId();
+        displayId && getHwComposer().isConnected(*displayId)) {
+        const auto activeConfig = getHwComposer().getActiveConfig(*displayId);
         const nsecs_t period = activeConfig->getVsyncPeriod();
         result.appendFormat("%" PRId64 "\n", period);
     }
@@ -4285,13 +4295,17 @@
 
 void SurfaceFlinger::dumpDisplayIdentificationData(String8& result) const {
     for (const auto& [token, display] : mDisplays) {
-        const int32_t displayId = display->getId();
-        const auto hwcDisplayId = getHwComposer().getHwcDisplayId(displayId);
+        const auto displayId = display->getId();
+        if (!displayId) {
+            continue;
+        }
+        const auto hwcDisplayId = getHwComposer().fromPhysicalDisplayId(*displayId);
         if (!hwcDisplayId) {
             continue;
         }
 
-        result.appendFormat("Display %d (HWC display %" PRIu64 "): ", displayId, *hwcDisplayId);
+        result.appendFormat("Display %" PRIu64 " (HWC display %" PRIu64 "): ", *displayId,
+                            *hwcDisplayId);
         uint8_t port;
         DisplayIdentificationData data;
         if (!getHwComposer().getDisplayIdentificationData(*hwcDisplayId, &port, &data)) {
@@ -4322,7 +4336,6 @@
         result.append(edid->displayName.data(), edid->displayName.length());
         result.append("\"\n");
     }
-    result.append("\n");
 }
 
 void SurfaceFlinger::dumpWideColorInfo(String8& result) const {
@@ -4334,13 +4347,13 @@
     // TODO: print out if wide-color mode is active or not
 
     for (const auto& [token, display] : mDisplays) {
-        const int32_t displayId = display->getId();
-        if (displayId == DisplayDevice::DISPLAY_ID_INVALID) {
+        const auto displayId = display->getId();
+        if (!displayId) {
             continue;
         }
 
-        result.appendFormat("Display %d color modes:\n", displayId);
-        std::vector<ColorMode> modes = getHwComposer().getColorModes(displayId);
+        result.appendFormat("Display %" PRIu64 " color modes:\n", *displayId);
+        std::vector<ColorMode> modes = getHwComposer().getColorModes(*displayId);
         for (auto&& mode : modes) {
             result.appendFormat("    %s (%d)\n", decodeColorMode(mode).c_str(), mode);
         }
@@ -4396,11 +4409,12 @@
     layersProto.set_color_transform(decodeColorTransform(display.getColorTransform()));
     layersProto.set_global_transform(static_cast<int32_t>(display.getOrientationTransform()));
 
-    const int32_t displayId = display.getId();
+    const auto displayId = display.getId();
+    LOG_ALWAYS_FATAL_IF(!displayId);
     mDrawingState.traverseInZOrder([&](Layer* layer) {
-        if (!layer->visibleRegion.isEmpty() && layer->getBE().mHwcLayers.count(displayId)) {
+        if (!layer->visibleRegion.isEmpty() && layer->getBE().mHwcLayers.count(*displayId)) {
             LayerProto* layerProto = layersProto.add_layers();
-            layer->writeToProto(layerProto, displayId);
+            layer->writeToProto(layerProto, *displayId);
         }
     });
 
@@ -4454,25 +4468,19 @@
 
     const auto [sfEarlyOffset, appEarlyOffset] = mVsyncModulator.getEarlyOffsets();
     const auto [sfEarlyGlOffset, appEarlyGlOffset] = mVsyncModulator.getEarlyGlOffsets();
-    if (const auto displayId = DisplayDevice::DISPLAY_PRIMARY;
-        getHwComposer().isConnected(displayId)) {
-        const auto activeConfig = getHwComposer().getActiveConfig(displayId);
-        result.appendFormat("Display %d: "
-                "app phase %" PRId64 " ns, "
-                "sf phase %" PRId64 " ns, "
-                "early app phase %" PRId64 " ns, "
-                "early sf phase %" PRId64 " ns, "
-                "early app gl phase %" PRId64 " ns, "
-                "early sf gl phase %" PRId64 " ns, "
-                "present offset %" PRId64 " ns (refresh %" PRId64 " ns)",
-                displayId,
-                vsyncPhaseOffsetNs,
-                sfVsyncPhaseOffsetNs,
-                appEarlyOffset,
-                sfEarlyOffset,
-                appEarlyGlOffset,
-                sfEarlyGlOffset,
-                dispSyncPresentTimeOffset, activeConfig->getVsyncPeriod());
+    if (const auto displayId = getInternalDisplayId();
+        displayId && getHwComposer().isConnected(*displayId)) {
+        const auto activeConfig = getHwComposer().getActiveConfig(*displayId);
+        result.appendFormat("Display %" PRIu64 ": app phase %" PRId64 " ns, "
+                            "sf phase %" PRId64 " ns, "
+                            "early app phase %" PRId64 " ns, "
+                            "early sf phase %" PRId64 " ns, "
+                            "early app gl phase %" PRId64 " ns, "
+                            "early sf gl phase %" PRId64 " ns, "
+                            "present offset %" PRId64 " ns (refresh %" PRId64 " ns)",
+                            *displayId, vsyncPhaseOffsetNs, sfVsyncPhaseOffsetNs, appEarlyOffset,
+                            sfEarlyOffset, appEarlyGlOffset, sfEarlyGlOffset,
+                            dispSyncPresentTimeOffset, activeConfig->getVsyncPeriod());
     }
     result.append("\n");
 
@@ -4525,12 +4533,9 @@
     result.append("SurfaceFlinger global state:\n");
     colorizer.reset(result);
 
-    HWComposer& hwc(getHwComposer());
-    const auto display = getDefaultDisplayDeviceLocked();
-
     getBE().mRenderEngine->dump(result);
 
-    if (display) {
+    if (const auto display = getDefaultDisplayDeviceLocked()) {
         display->undefinedRegion.dump(result, "undefinedRegion");
         result.appendFormat("  orientation=%d, isPoweredOn=%d\n", display->getOrientation(),
                             display->isPoweredOn());
@@ -4539,8 +4544,9 @@
                         "  gpu_to_cpu_unsupported    : %d\n",
                         mTransactionFlags.load(), !mGpuToCpuSupported);
 
-    if (display) {
-        const auto activeConfig = getHwComposer().getActiveConfig(display->getId());
+    if (const auto displayId = getInternalDisplayId();
+        displayId && getHwComposer().isConnected(*displayId)) {
+        const auto activeConfig = getHwComposer().getActiveConfig(*displayId);
         result.appendFormat("  refresh-rate              : %f fps\n"
                             "  x-dpi                     : %f\n"
                             "  y-dpi                     : %f\n",
@@ -4572,14 +4578,14 @@
      * HWC layer minidump
      */
     for (const auto& [token, display] : mDisplays) {
-        const int32_t displayId = display->getId();
-        if (displayId == DisplayDevice::DISPLAY_ID_INVALID) {
+        const auto displayId = display->getId();
+        if (!displayId) {
             continue;
         }
 
-        result.appendFormat("Display %d HWC layers:\n", displayId);
+        result.appendFormat("Display %" PRIu64 " HWC layers:\n", *displayId);
         Layer::miniDumpHeader(result);
-        mCurrentState.traverseInZOrder([&](Layer* layer) { layer->miniDump(result, displayId); });
+        mCurrentState.traverseInZOrder([&](Layer* layer) { layer->miniDump(result, *displayId); });
         result.append("\n");
     }
 
@@ -4592,7 +4598,7 @@
     bool hwcDisabled = mDebugDisableHWC || mDebugRegion;
     result.appendFormat("  h/w composer %s\n",
             hwcDisabled ? "disabled" : "enabled");
-    hwc.dump(result);
+    getHwComposer().dump(result);
 
     /*
      * Dump gralloc state
@@ -4610,7 +4616,7 @@
     }
 }
 
-const Vector<sp<Layer>>& SurfaceFlinger::getLayerSortedByZForHwcDisplay(int32_t displayId) {
+const Vector<sp<Layer>>& SurfaceFlinger::getLayerSortedByZForHwcDisplay(DisplayId displayId) {
     // Note: mStateLock is held here
     for (const auto& [token, display] : mDisplays) {
         if (display->getId() == displayId) {
@@ -4618,7 +4624,7 @@
         }
     }
 
-    ALOGE("%s: Invalid display %d", __FUNCTION__, displayId);
+    ALOGE("%s: Invalid display %" PRIu64, __FUNCTION__, displayId);
     static const Vector<sp<Layer>> empty;
     return empty;
 }
diff --git a/services/surfaceflinger/SurfaceFlinger.h b/services/surfaceflinger/SurfaceFlinger.h
index 5a9093b..6ffe025 100644
--- a/services/surfaceflinger/SurfaceFlinger.h
+++ b/services/surfaceflinger/SurfaceFlinger.h
@@ -62,6 +62,7 @@
 #include "SurfaceFlingerFactory.h"
 #include "SurfaceInterceptor.h"
 #include "SurfaceTracing.h"
+#include "TransactionCompletedThread.h"
 
 #include "DisplayHardware/HWC2.h"
 #include "DisplayHardware/HWComposer.h"
@@ -76,10 +77,11 @@
 #include <map>
 #include <mutex>
 #include <queue>
+#include <set>
 #include <string>
 #include <thread>
+#include <unordered_map>
 #include <utility>
-#include "RenderArea.h"
 
 #include <layerproto/LayerProtoHeader.h>
 
@@ -348,7 +350,7 @@
 
     // enable/disable h/w composer event
     // TODO: this should be made accessible only to EventThread
-    void setVsyncEnabled(int disp, int enabled);
+    void setVsyncEnabled(EventThread::DisplayType displayType, bool enabled);
 
     // called on the main thread by MessageQueue when an internal message
     // is received
@@ -357,7 +359,7 @@
 
     // for debugging only
     // TODO: this should be made accessible only to HWComposer
-    const Vector< sp<Layer> >& getLayerSortedByZForHwcDisplay(int id);
+    const Vector<sp<Layer>>& getLayerSortedByZForHwcDisplay(DisplayId displayId);
 
     renderengine::RenderEngine& getRenderEngine() const { return *getBE().mRenderEngine; }
 
@@ -367,6 +369,10 @@
     inline void onLayerCreated() { mNumLayers++; }
     inline void onLayerDestroyed() { mNumLayers--; }
 
+    TransactionCompletedThread& getTransactionCompletedThread() {
+        return mTransactionCompletedThread;
+    }
+
 private:
     friend class Client;
     friend class DisplayEventConnection;
@@ -659,7 +665,10 @@
     }
 
     sp<DisplayDevice> getDefaultDisplayDeviceLocked() {
-        return getDisplayDeviceLocked(mDisplayTokens[DisplayDevice::DISPLAY_PRIMARY]);
+        if (const auto token = getInternalDisplayToken()) {
+            return getDisplayDeviceLocked(token);
+        }
+        return nullptr;
     }
 
     // mark a region of a layer stack dirty. this updates the dirty
@@ -724,10 +733,8 @@
     /* ------------------------------------------------------------------------
      * Display management
      */
-    DisplayDevice::DisplayType determineDisplayType(hwc2_display_t hwcDisplayId,
-                                                    HWC2::Connection connection) const;
     sp<DisplayDevice> setupNewDisplayDeviceInternal(const wp<IBinder>& displayToken,
-                                                    int32_t displayId,
+                                                    const std::optional<DisplayId>& displayId,
                                                     const DisplayDeviceState& state,
                                                     const sp<DisplaySurface>& dispSurface,
                                                     const sp<IGraphicBufferProducer>& producer);
@@ -759,6 +766,37 @@
     }
 
 private:
+    sp<IBinder> getPhysicalDisplayToken(DisplayId displayId) const {
+        const auto it = mPhysicalDisplayTokens.find(displayId);
+        return it != mPhysicalDisplayTokens.end() ? it->second : nullptr;
+    }
+
+    std::optional<DisplayId> getPhysicalDisplayId(const sp<IBinder>& displayToken) const {
+        for (const auto& [id, token] : mPhysicalDisplayTokens) {
+            if (token == displayToken) {
+                return id;
+            }
+        }
+        return {};
+    }
+
+    // TODO(b/74619554): Remove special cases for primary display.
+    sp<IBinder> getInternalDisplayToken() const {
+        const auto displayId = getInternalDisplayId();
+        return displayId ? getPhysicalDisplayToken(*displayId) : nullptr;
+    }
+
+    std::optional<DisplayId> getInternalDisplayId() const {
+        const auto hwcDisplayId = getHwComposer().getInternalHwcDisplayId();
+        return hwcDisplayId ? getHwComposer().toPhysicalDisplayId(*hwcDisplayId) : std::nullopt;
+    }
+
+    // TODO(b/74619554): Remove special cases for external display.
+    std::optional<DisplayId> getExternalDisplayId() const {
+        const auto hwcDisplayId = getHwComposer().getExternalHwcDisplayId();
+        return hwcDisplayId ? getHwComposer().toPhysicalDisplayId(*hwcDisplayId) : std::nullopt;
+    }
+
     void listLayersLocked(const Vector<String16>& args, size_t& index, String8& result) const;
     void dumpStatsLocked(const Vector<String16>& args, size_t& index, String8& result) const;
     void clearStatsLocked(const Vector<String16>& args, size_t& index, String8& result);
@@ -836,7 +874,7 @@
     std::unique_ptr<VSyncSource> mSfEventThreadSource;
     std::unique_ptr<InjectVSyncSource> mVSyncInjector;
     std::unique_ptr<EventControlThread> mEventControlThread;
-    sp<IBinder> mDisplayTokens[DisplayDevice::NUM_BUILTIN_DISPLAY_TYPES];
+    std::unordered_map<DisplayId, sp<IBinder>> mPhysicalDisplayTokens;
 
     VSyncModulator mVsyncModulator;
 
@@ -886,6 +924,8 @@
     bool mUseHwcVirtualDisplays = false;
     std::atomic<uint32_t> mFrameMissedCount{0};
 
+    TransactionCompletedThread mTransactionCompletedThread;
+
     // Restrict layers to use two buffers in their bufferqueues.
     bool mLayerTripleBufferingDisabled = false;
 
diff --git a/services/surfaceflinger/SurfaceInterceptor.cpp b/services/surfaceflinger/SurfaceInterceptor.cpp
index 57bda5a..7faaff6 100644
--- a/services/surfaceflinger/SurfaceInterceptor.cpp
+++ b/services/surfaceflinger/SurfaceInterceptor.cpp
@@ -495,8 +495,10 @@
     DisplayCreation* creation(increment->mutable_display_creation());
     creation->set_id(info.sequenceId);
     creation->set_name(info.displayName);
-    creation->set_type(info.type);
     creation->set_is_secure(info.isSecure);
+    if (info.displayId) {
+        creation->set_display_id(*info.displayId);
+    }
 }
 
 void SurfaceInterceptor::addDisplayDeletionLocked(Increment* increment, int32_t sequenceId) {
diff --git a/services/surfaceflinger/TimeStats/TimeStats.cpp b/services/surfaceflinger/TimeStats/TimeStats.cpp
index ace7c1b..9730e8c 100644
--- a/services/surfaceflinger/TimeStats/TimeStats.cpp
+++ b/services/surfaceflinger/TimeStats/TimeStats.cpp
@@ -173,8 +173,8 @@
         ALOGV("[%d]-[%" PRIu64 "]-presentFenceTime[%" PRId64 "]", layerID,
               timeRecords[0].frameTime.frameNumber, timeRecords[0].frameTime.presentTime);
 
+        const std::string& layerName = layerRecord.layerName;
         if (prevTimeRecord.ready) {
-            const std::string& layerName = layerRecord.layerName;
             if (!mTimeStats.stats.count(layerName)) {
                 mTimeStats.stats[layerName].layerName = layerName;
                 mTimeStats.stats[layerName].packageName = getPackageName(layerName);
@@ -220,6 +220,18 @@
                   timeRecords[0].frameTime.frameNumber, presentToPresentMs);
             timeStatsLayer.deltas["present2present"].insert(presentToPresentMs);
         }
+
+        // Output additional trace points to track frame time.
+        ATRACE_INT64(("TimeStats-Post - " + layerName).c_str(), timeRecords[0].frameTime.postTime);
+        ATRACE_INT64(("TimeStats-Acquire - " + layerName).c_str(),
+                     timeRecords[0].frameTime.acquireTime);
+        ATRACE_INT64(("TimeStats-Latch - " + layerName).c_str(),
+                     timeRecords[0].frameTime.latchTime);
+        ATRACE_INT64(("TimeStats-Desired - " + layerName).c_str(),
+                     timeRecords[0].frameTime.desiredTime);
+        ATRACE_INT64(("TimeStats-Present - " + layerName).c_str(),
+                     timeRecords[0].frameTime.presentTime);
+
         prevTimeRecord = timeRecords[0];
         timeRecords.pop_front();
         layerRecord.waitData--;
diff --git a/services/surfaceflinger/TransactionCompletedThread.cpp b/services/surfaceflinger/TransactionCompletedThread.cpp
new file mode 100644
index 0000000..2c81f0a
--- /dev/null
+++ b/services/surfaceflinger/TransactionCompletedThread.cpp
@@ -0,0 +1,226 @@
+/*
+ * Copyright 2018 The Android Open Source Project
+ *
+ * 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.
+ */
+
+//#define LOG_NDEBUG 0
+#undef LOG_TAG
+#define LOG_TAG "TransactionCompletedThread"
+#define ATRACE_TAG ATRACE_TAG_GRAPHICS
+
+#include "TransactionCompletedThread.h"
+
+#include <cinttypes>
+
+#include <binder/IInterface.h>
+#include <gui/ITransactionCompletedListener.h>
+#include <utils/RefBase.h>
+
+namespace android {
+
+TransactionCompletedThread::~TransactionCompletedThread() {
+    {
+        std::lock_guard lock(mMutex);
+        mKeepRunning = false;
+        mConditionVariable.notify_all();
+    }
+
+    mThread.join();
+
+    {
+        std::lock_guard lock(mMutex);
+        for (const auto& [listener, listenerStats] : mListenerStats) {
+            listener->unlinkToDeath(mDeathRecipient);
+        }
+    }
+}
+
+void TransactionCompletedThread::run() {
+    std::lock_guard lock(mMutex);
+    if (mRunning) {
+        return;
+    }
+    mDeathRecipient = new ThreadDeathRecipient();
+    mRunning = true;
+    mThread = std::thread(&TransactionCompletedThread::threadMain, this);
+}
+
+void TransactionCompletedThread::registerPendingLatchedCallbackHandle(
+        const sp<CallbackHandle>& handle) {
+    std::lock_guard lock(mMutex);
+
+    sp<IBinder> listener = IInterface::asBinder(handle->listener);
+    const auto& callbackIds = handle->callbackIds;
+
+    mPendingTransactions[listener][callbackIds]++;
+}
+
+void TransactionCompletedThread::addLatchedCallbackHandles(
+        const std::deque<sp<CallbackHandle>>& handles, nsecs_t latchTime,
+        const sp<Fence>& previousReleaseFence) {
+    std::lock_guard lock(mMutex);
+
+    // If the previous release fences have not signaled, something as probably gone wrong.
+    // Store the fences and check them again before sending a callback.
+    if (previousReleaseFence &&
+        previousReleaseFence->getSignalTime() == Fence::SIGNAL_TIME_PENDING) {
+        ALOGD("release fence from the previous frame has not signaled");
+        mPreviousReleaseFences.push_back(previousReleaseFence);
+    }
+
+    for (const auto& handle : handles) {
+        auto listener = mPendingTransactions.find(IInterface::asBinder(handle->listener));
+        auto& pendingCallbacks = listener->second;
+        auto pendingCallback = pendingCallbacks.find(handle->callbackIds);
+
+        if (pendingCallback != pendingCallbacks.end()) {
+            auto& pendingCount = pendingCallback->second;
+
+            // Decrease the pending count for this listener
+            if (--pendingCount == 0) {
+                pendingCallbacks.erase(pendingCallback);
+            }
+        } else {
+            ALOGE("there are more latched callbacks than there were registered callbacks");
+        }
+
+        addCallbackHandle(handle, latchTime);
+    }
+}
+
+void TransactionCompletedThread::addUnlatchedCallbackHandle(const sp<CallbackHandle>& handle) {
+    std::lock_guard lock(mMutex);
+    addCallbackHandle(handle);
+}
+
+void TransactionCompletedThread::addCallbackHandle(const sp<CallbackHandle>& handle,
+                                                   nsecs_t latchTime) {
+    const sp<IBinder> listener = IInterface::asBinder(handle->listener);
+
+    // If we don't already have a reference to this listener, linkToDeath so we get a notification
+    // if it dies.
+    if (mListenerStats.count(listener) == 0) {
+        status_t error = listener->linkToDeath(mDeathRecipient);
+        if (error != NO_ERROR) {
+            ALOGE("cannot add callback handle because linkToDeath failed, err: %d", error);
+            return;
+        }
+    }
+
+    auto& listenerStats = mListenerStats[listener];
+    listenerStats.listener = handle->listener;
+
+    auto& transactionStats = listenerStats.transactionStats[handle->callbackIds];
+    transactionStats.latchTime = latchTime;
+    transactionStats.surfaceStats.emplace_back(handle->surfaceControl, handle->acquireTime,
+                                               handle->releasePreviousBuffer);
+}
+
+void TransactionCompletedThread::addPresentFence(const sp<Fence>& presentFence) {
+    std::lock_guard<std::mutex> lock(mMutex);
+    mPresentFence = presentFence;
+}
+
+void TransactionCompletedThread::sendCallbacks() {
+    std::lock_guard lock(mMutex);
+    if (mRunning) {
+        mConditionVariable.notify_all();
+    }
+}
+
+void TransactionCompletedThread::threadMain() {
+    std::lock_guard lock(mMutex);
+
+    while (mKeepRunning) {
+        mConditionVariable.wait(mMutex);
+
+        // Present fence should fire almost immediately. If the fence has not signaled in 100ms,
+        // there is a major problem and it will probably never fire.
+        nsecs_t presentTime = -1;
+        if (mPresentFence) {
+            status_t status = mPresentFence->wait(100);
+            if (status == NO_ERROR) {
+                presentTime = mPresentFence->getSignalTime();
+            } else {
+                ALOGE("present fence has not signaled, err %d", status);
+            }
+        }
+
+        // We should never hit this case. The release fences from the previous frame should have
+        // signaled long before the current frame is presented.
+        for (const auto& fence : mPreviousReleaseFences) {
+            status_t status = fence->wait(100);
+            if (status != NO_ERROR) {
+                ALOGE("previous release fence has not signaled, err %d", status);
+            }
+        }
+
+        // For each listener
+        auto it = mListenerStats.begin();
+        while (it != mListenerStats.end()) {
+            auto& [listener, listenerStats] = *it;
+
+            // For each transaction
+            bool sendCallback = true;
+            for (auto& [callbackIds, transactionStats] : listenerStats.transactionStats) {
+                // If we are still waiting on the callback handles for this transaction, skip it
+                if (mPendingTransactions[listener].count(callbackIds) != 0) {
+                    sendCallback = false;
+                    break;
+                }
+
+                // If the transaction has been latched
+                if (transactionStats.latchTime >= 0) {
+                    // If the present time is < 0, this transaction has been latched but not
+                    // presented. Skip it for now. This can happen when a new transaction comes
+                    // in between the latch and present steps. sendCallbacks is called by
+                    // SurfaceFlinger when the transaction is received to ensure that if the
+                    // transaction that didn't update state it still got a callback.
+                    if (presentTime < 0) {
+                        sendCallback = false;
+                        break;
+                    }
+
+                    transactionStats.presentTime = presentTime;
+                }
+            }
+            // If the listener has no pending transactions and all latched transactions have been
+            // presented
+            if (sendCallback) {
+                // If the listener is still alive
+                if (listener->isBinderAlive()) {
+                    // Send callback
+                    listenerStats.listener->onTransactionCompleted(listenerStats);
+                    listener->unlinkToDeath(mDeathRecipient);
+                }
+                it = mListenerStats.erase(it);
+            } else {
+                it++;
+            }
+        }
+
+        if (mPresentFence) {
+            mPresentFence.clear();
+            mPreviousReleaseFences.clear();
+        }
+    }
+}
+
+// -----------------------------------------------------------------------
+
+CallbackHandle::CallbackHandle(const sp<ITransactionCompletedListener>& transactionListener,
+                               const std::vector<CallbackId>& ids, const sp<IBinder>& sc)
+      : listener(transactionListener), callbackIds(ids), surfaceControl(sc) {}
+
+} // namespace android
diff --git a/services/surfaceflinger/TransactionCompletedThread.h b/services/surfaceflinger/TransactionCompletedThread.h
new file mode 100644
index 0000000..5af4097
--- /dev/null
+++ b/services/surfaceflinger/TransactionCompletedThread.h
@@ -0,0 +1,113 @@
+/*
+ * Copyright 2018 The Android Open Source Project
+ *
+ * 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.
+ */
+
+#pragma once
+
+#include <condition_variable>
+#include <deque>
+#include <mutex>
+#include <thread>
+#include <unordered_map>
+
+#include <android-base/thread_annotations.h>
+
+#include <binder/IBinder.h>
+#include <gui/ITransactionCompletedListener.h>
+#include <ui/Fence.h>
+
+namespace android {
+
+class CallbackHandle : public RefBase {
+public:
+    CallbackHandle(const sp<ITransactionCompletedListener>& transactionListener,
+                   const std::vector<CallbackId>& ids, const sp<IBinder>& sc);
+
+    sp<ITransactionCompletedListener> listener;
+    std::vector<CallbackId> callbackIds;
+    sp<IBinder> surfaceControl;
+
+    bool releasePreviousBuffer = false;
+    nsecs_t acquireTime = -1;
+};
+
+class TransactionCompletedThread {
+public:
+    ~TransactionCompletedThread();
+
+    void run();
+
+    // Informs the TransactionCompletedThread that there is a Transaction with a CallbackHandle
+    // that needs to be latched and presented this frame. This function should be called once the
+    // layer has received the CallbackHandle so the TransactionCompletedThread knows not to send
+    // a callback for that Listener/Transaction pair until that CallbackHandle has been latched and
+    // presented.
+    void registerPendingLatchedCallbackHandle(const sp<CallbackHandle>& handle);
+    // Notifies the TransactionCompletedThread that a pending CallbackHandle has been latched.
+    void addLatchedCallbackHandles(const std::deque<sp<CallbackHandle>>& handles, nsecs_t latchTime,
+                                   const sp<Fence>& previousReleaseFence);
+
+    // Adds the Transaction CallbackHandle from a layer that does not need to be relatched and
+    // presented this frame.
+    void addUnlatchedCallbackHandle(const sp<CallbackHandle>& handle);
+
+    void addPresentFence(const sp<Fence>& presentFence);
+
+    void sendCallbacks();
+
+private:
+    void threadMain();
+
+    void addCallbackHandle(const sp<CallbackHandle>& handle, nsecs_t latchTime = -1)
+            REQUIRES(mMutex);
+
+    class ThreadDeathRecipient : public IBinder::DeathRecipient {
+    public:
+        // This function is a no-op. isBinderAlive needs a linked DeathRecipient to work.
+        // Death recipients needs a binderDied function.
+        //
+        // (isBinderAlive checks if BpBinder's mAlive is 0. mAlive is only set to 0 in sendObituary.
+        // sendObituary is only called if linkToDeath was called with a DeathRecipient.)
+        void binderDied(const wp<IBinder>& /*who*/) override {}
+    };
+    sp<ThreadDeathRecipient> mDeathRecipient;
+
+    struct IBinderHash {
+        std::size_t operator()(const sp<IBinder>& strongPointer) const {
+            return std::hash<IBinder*>{}(strongPointer.get());
+        }
+    };
+
+    std::thread mThread;
+
+    std::mutex mMutex;
+    std::condition_variable_any mConditionVariable;
+
+    std::unordered_map<
+            sp<IBinder /*listener*/>,
+            std::unordered_map<std::vector<CallbackId>, uint32_t /*count*/, CallbackIdsHash>,
+            IBinderHash>
+            mPendingTransactions GUARDED_BY(mMutex);
+    std::unordered_map<sp<IBinder /*listener*/>, ListenerStats, IBinderHash> mListenerStats
+            GUARDED_BY(mMutex);
+
+    bool mRunning GUARDED_BY(mMutex) = false;
+    bool mKeepRunning GUARDED_BY(mMutex) = true;
+
+    sp<Fence> mPresentFence GUARDED_BY(mMutex);
+    std::vector<sp<Fence>> mPreviousReleaseFences GUARDED_BY(mMutex);
+};
+
+} // namespace android
diff --git a/services/surfaceflinger/main_surfaceflinger.cpp b/services/surfaceflinger/main_surfaceflinger.cpp
index 9c2edca..92ae87b 100644
--- a/services/surfaceflinger/main_surfaceflinger.cpp
+++ b/services/surfaceflinger/main_surfaceflinger.cpp
@@ -28,7 +28,6 @@
 #include <cutils/sched_policy.h>
 #include <displayservice/DisplayService.h>
 #include <hidl/LegacySupport.h>
-#include "GpuService.h"
 #include "SurfaceFlinger.h"
 #include "SurfaceFlingerFactory.h"
 
@@ -104,10 +103,6 @@
     sm->addService(String16(SurfaceFlinger::getServiceName()), flinger, false,
                    IServiceManager::DUMP_FLAG_PRIORITY_CRITICAL | IServiceManager::DUMP_FLAG_PROTO);
 
-    // publish GpuService
-    sp<GpuService> gpuservice = new GpuService();
-    sm->addService(String16(GpuService::SERVICE_NAME), gpuservice, false);
-
     startDisplayService(); // dependency on SF getting registered above
 
     struct sched_param param = {0};
diff --git a/services/surfaceflinger/tests/Transaction_test.cpp b/services/surfaceflinger/tests/Transaction_test.cpp
index 94b33ac..be8d939 100644
--- a/services/surfaceflinger/tests/Transaction_test.cpp
+++ b/services/surfaceflinger/tests/Transaction_test.cpp
@@ -15,9 +15,12 @@
  */
 
 #include <algorithm>
+#include <chrono>
+#include <cinttypes>
 #include <functional>
 #include <limits>
 #include <ostream>
+#include <thread>
 
 #include <gtest/gtest.h>
 
@@ -64,6 +67,7 @@
 const Color Color::TRANSPARENT{0, 0, 0, 0};
 
 using android::hardware::graphics::common::V1_1::BufferUsage;
+using namespace std::chrono_literals;
 
 std::ostream& operator<<(std::ostream& os, const Color& color) {
     os << int(color.r) << ", " << int(color.g) << ", " << int(color.b) << ", " << int(color.a);
@@ -327,10 +331,11 @@
         mClient = 0;
     }
 
-    virtual sp<SurfaceControl> createLayer(const char* name, uint32_t width, uint32_t height,
+    virtual sp<SurfaceControl> createLayer(const sp<SurfaceComposerClient>& client,
+                                           const char* name, uint32_t width, uint32_t height,
                                            uint32_t flags = 0) {
         auto layer =
-                mClient->createSurface(String8(name), width, height, PIXEL_FORMAT_RGBA_8888, flags);
+                client->createSurface(String8(name), width, height, PIXEL_FORMAT_RGBA_8888, flags);
         EXPECT_NE(nullptr, layer.get()) << "failed to create SurfaceControl";
 
         status_t error = Transaction()
@@ -345,6 +350,11 @@
         return layer;
     }
 
+    virtual sp<SurfaceControl> createLayer(const char* name, uint32_t width, uint32_t height,
+                                           uint32_t flags = 0) {
+        return createLayer(mClient, name, width, height, flags);
+    }
+
     ANativeWindow_Buffer getBufferQueueLayerBuffer(const sp<SurfaceControl>& layer) {
         // wait for previous transactions (such as setSize) to complete
         Transaction().apply(true);
@@ -1953,7 +1963,7 @@
                               "test");
     fillGraphicBufferColor(buffer, Rect(0, 0, 32, 32), Color::RED);
 
-    sp<Fence> fence = new Fence(-1);
+    sp<Fence> fence = Fence::NO_FENCE;
 
     Transaction()
             .setBuffer(layer, buffer)
@@ -2156,6 +2166,627 @@
     }
 }
 
+class ExpectedResult {
+public:
+    enum Transaction {
+        NOT_PRESENTED = 0,
+        PRESENTED,
+    };
+
+    enum Buffer {
+        NOT_ACQUIRED = 0,
+        ACQUIRED,
+    };
+
+    enum PreviousBuffer {
+        NOT_RELEASED = 0,
+        RELEASED,
+    };
+
+    void reset() {
+        mTransactionResult = ExpectedResult::Transaction::NOT_PRESENTED;
+        mExpectedSurfaceResults.clear();
+    }
+
+    void addSurface(ExpectedResult::Transaction transactionResult, const sp<SurfaceControl>& layer,
+                    ExpectedResult::Buffer bufferResult = NOT_ACQUIRED,
+                    ExpectedResult::PreviousBuffer previousBufferResult = NOT_RELEASED) {
+        mTransactionResult = transactionResult;
+        mExpectedSurfaceResults.emplace(std::piecewise_construct,
+                                        std::forward_as_tuple(layer->getHandle()),
+                                        std::forward_as_tuple(bufferResult, previousBufferResult));
+    }
+
+    void addSurfaces(ExpectedResult::Transaction transactionResult,
+                     const std::vector<sp<SurfaceControl>>& layers,
+                     ExpectedResult::Buffer bufferResult = NOT_ACQUIRED,
+                     ExpectedResult::PreviousBuffer previousBufferResult = NOT_RELEASED) {
+        for (const auto& layer : layers) {
+            addSurface(transactionResult, layer, bufferResult, previousBufferResult);
+        }
+    }
+
+    void verifyTransactionStats(const TransactionStats& transactionStats) const {
+        const auto& [latchTime, presentTime, surfaceStats] = transactionStats;
+        if (mTransactionResult == ExpectedResult::Transaction::PRESENTED) {
+            ASSERT_GE(latchTime, 0) << "bad latch time";
+            ASSERT_GE(presentTime, 0) << "bad present time";
+        } else {
+            ASSERT_EQ(presentTime, -1) << "transaction shouldn't have been presented";
+            ASSERT_EQ(latchTime, -1) << "unpresented transactions shouldn't be latched";
+        }
+
+        ASSERT_EQ(surfaceStats.size(), mExpectedSurfaceResults.size())
+                << "wrong number of surfaces";
+
+        for (const auto& stats : surfaceStats) {
+            const auto& expectedSurfaceResult = mExpectedSurfaceResults.find(stats.surfaceControl);
+            ASSERT_NE(expectedSurfaceResult, mExpectedSurfaceResults.end())
+                    << "unexpected surface control";
+            expectedSurfaceResult->second.verifySurfaceStats(stats, latchTime);
+        }
+    }
+
+private:
+    class ExpectedSurfaceResult {
+    public:
+        ExpectedSurfaceResult(ExpectedResult::Buffer bufferResult,
+                              ExpectedResult::PreviousBuffer previousBufferResult)
+              : mBufferResult(bufferResult), mPreviousBufferResult(previousBufferResult) {}
+
+        void verifySurfaceStats(const SurfaceStats& surfaceStats, nsecs_t latchTime) const {
+            const auto& [surfaceControl, acquireTime, releasePreviousBuffer] = surfaceStats;
+
+            ASSERT_EQ(acquireTime > 0, mBufferResult == ExpectedResult::Buffer::ACQUIRED)
+                    << "bad acquire time";
+            ASSERT_LE(acquireTime, latchTime) << "acquire time should be <= latch time";
+            ASSERT_EQ(releasePreviousBuffer,
+                      mPreviousBufferResult == ExpectedResult::PreviousBuffer::RELEASED)
+                    << "bad previous buffer released";
+        }
+
+    private:
+        ExpectedResult::Buffer mBufferResult;
+        ExpectedResult::PreviousBuffer mPreviousBufferResult;
+    };
+
+    struct IBinderHash {
+        std::size_t operator()(const sp<IBinder>& strongPointer) const {
+            return std::hash<IBinder*>{}(strongPointer.get());
+        }
+    };
+    ExpectedResult::Transaction mTransactionResult = ExpectedResult::Transaction::NOT_PRESENTED;
+    std::unordered_map<sp<IBinder>, ExpectedSurfaceResult, IBinderHash> mExpectedSurfaceResults;
+};
+
+class CallbackHelper {
+public:
+    static void function(void* callbackContext, const TransactionStats& transactionStats) {
+        if (!callbackContext) {
+            ALOGE("failed to get callback context");
+        }
+        CallbackHelper* helper = static_cast<CallbackHelper*>(callbackContext);
+        std::lock_guard lock(helper->mMutex);
+        helper->mTransactionStatsQueue.push(transactionStats);
+        helper->mConditionVariable.notify_all();
+    }
+
+    void getTransactionStats(TransactionStats* outStats) {
+        std::unique_lock lock(mMutex);
+
+        if (mTransactionStatsQueue.empty()) {
+            ASSERT_NE(mConditionVariable.wait_for(lock, std::chrono::seconds(3)),
+                      std::cv_status::timeout)
+                    << "did not receive callback";
+        }
+
+        *outStats = std::move(mTransactionStatsQueue.front());
+        mTransactionStatsQueue.pop();
+    }
+
+    void verifyFinalState() {
+        // Wait to see if there are extra callbacks
+        std::this_thread::sleep_for(500ms);
+
+        std::lock_guard lock(mMutex);
+        EXPECT_EQ(mTransactionStatsQueue.size(), 0) << "extra callbacks received";
+        mTransactionStatsQueue = {};
+    }
+
+    void* getContext() { return static_cast<void*>(this); }
+
+    std::mutex mMutex;
+    std::condition_variable mConditionVariable;
+    std::queue<TransactionStats> mTransactionStatsQueue;
+};
+
+class LayerCallbackTest : public LayerTransactionTest {
+protected:
+    virtual sp<SurfaceControl> createBufferStateLayer() {
+        return createLayer(mClient, "test", mWidth, mHeight,
+                           ISurfaceComposerClient::eFXSurfaceBufferState);
+    }
+
+    virtual void fillTransaction(Transaction& transaction, CallbackHelper* callbackHelper,
+                                 const sp<SurfaceControl>& layer = nullptr) {
+        if (layer) {
+            sp<GraphicBuffer> buffer =
+                    new GraphicBuffer(mWidth, mHeight, PIXEL_FORMAT_RGBA_8888, 1,
+                                      BufferUsage::CPU_READ_OFTEN | BufferUsage::CPU_WRITE_OFTEN |
+                                              BufferUsage::COMPOSER_OVERLAY |
+                                              BufferUsage::GPU_TEXTURE,
+                                      "test");
+            fillGraphicBufferColor(buffer, Rect(0, 0, mWidth, mHeight), Color::RED);
+
+            sp<Fence> fence = new Fence(-1);
+
+            transaction.setBuffer(layer, buffer)
+                    .setAcquireFence(layer, fence)
+                    .setSize(layer, mWidth, mHeight);
+        }
+
+        transaction.addTransactionCompletedCallback(callbackHelper->function,
+                                                    callbackHelper->getContext());
+    }
+
+    void waitForCallback(CallbackHelper& helper, const ExpectedResult& expectedResult,
+                         bool finalState = false) {
+        TransactionStats transactionStats;
+        ASSERT_NO_FATAL_FAILURE(helper.getTransactionStats(&transactionStats));
+        EXPECT_NO_FATAL_FAILURE(expectedResult.verifyTransactionStats(transactionStats));
+
+        if (finalState) {
+            ASSERT_NO_FATAL_FAILURE(helper.verifyFinalState());
+        }
+    }
+
+    void waitForCallbacks(CallbackHelper& helper,
+                          const std::vector<ExpectedResult>& expectedResults,
+                          bool finalState = false) {
+        for (const auto& expectedResult : expectedResults) {
+            waitForCallback(helper, expectedResult);
+        }
+        if (finalState) {
+            ASSERT_NO_FATAL_FAILURE(helper.verifyFinalState());
+        }
+    }
+
+    uint32_t mWidth = 32;
+    uint32_t mHeight = 32;
+};
+
+TEST_F(LayerCallbackTest, Basic) {
+    sp<SurfaceControl> layer;
+    ASSERT_NO_FATAL_FAILURE(layer = createBufferStateLayer());
+
+    Transaction transaction;
+    CallbackHelper callback;
+    fillTransaction(transaction, &callback, layer);
+
+    transaction.apply();
+
+    ExpectedResult expected;
+    expected.addSurface(ExpectedResult::Transaction::PRESENTED, layer);
+    EXPECT_NO_FATAL_FAILURE(waitForCallback(callback, expected, true));
+}
+
+TEST_F(LayerCallbackTest, NoBuffer) {
+    sp<SurfaceControl> layer;
+    ASSERT_NO_FATAL_FAILURE(layer = createBufferStateLayer());
+
+    Transaction transaction;
+    CallbackHelper callback;
+    fillTransaction(transaction, &callback);
+
+    transaction.setPosition(layer, mWidth, mHeight).apply();
+
+    ExpectedResult expected;
+    expected.addSurface(ExpectedResult::Transaction::NOT_PRESENTED, layer);
+    EXPECT_NO_FATAL_FAILURE(waitForCallback(callback, expected, true));
+}
+
+TEST_F(LayerCallbackTest, NoStateChange) {
+    Transaction transaction;
+    CallbackHelper callback;
+    fillTransaction(transaction, &callback);
+
+    transaction.apply();
+
+    ExpectedResult expected;
+    EXPECT_NO_FATAL_FAILURE(waitForCallback(callback, expected, true));
+}
+
+TEST_F(LayerCallbackTest, OffScreen) {
+    sp<SurfaceControl> layer;
+    ASSERT_NO_FATAL_FAILURE(layer = createBufferStateLayer());
+
+    Transaction transaction;
+    CallbackHelper callback;
+    fillTransaction(transaction, &callback, layer);
+
+    transaction.setPosition(layer, -100, -100).apply();
+
+    ExpectedResult expected;
+    expected.addSurface(ExpectedResult::Transaction::PRESENTED, layer);
+    EXPECT_NO_FATAL_FAILURE(waitForCallback(callback, expected, true));
+}
+
+TEST_F(LayerCallbackTest, Merge) {
+    sp<SurfaceControl> layer1, layer2;
+    ASSERT_NO_FATAL_FAILURE(layer1 = createBufferStateLayer());
+    ASSERT_NO_FATAL_FAILURE(layer2 = createBufferStateLayer());
+
+    Transaction transaction1, transaction2;
+    CallbackHelper callback1, callback2;
+    fillTransaction(transaction1, &callback1, layer1);
+    fillTransaction(transaction2, &callback2, layer2);
+
+    transaction2.setPosition(layer2, mWidth, mHeight).merge(std::move(transaction1)).apply();
+
+    ExpectedResult expected;
+    expected.addSurfaces(ExpectedResult::Transaction::PRESENTED, {layer1, layer2});
+    EXPECT_NO_FATAL_FAILURE(waitForCallback(callback1, expected, true));
+    EXPECT_NO_FATAL_FAILURE(waitForCallback(callback2, expected, true));
+}
+
+TEST_F(LayerCallbackTest, Merge_SameCallback) {
+    sp<SurfaceControl> layer1, layer2;
+    ASSERT_NO_FATAL_FAILURE(layer1 = createBufferStateLayer());
+    ASSERT_NO_FATAL_FAILURE(layer2 = createBufferStateLayer());
+
+    Transaction transaction1, transaction2;
+    CallbackHelper callback;
+    fillTransaction(transaction1, &callback, layer1);
+    fillTransaction(transaction2, &callback, layer2);
+
+    transaction2.merge(std::move(transaction1)).apply();
+
+    ExpectedResult expected;
+    expected.addSurfaces(ExpectedResult::Transaction::PRESENTED, {layer1, layer2});
+    EXPECT_NO_FATAL_FAILURE(waitForCallback(callback, expected));
+    EXPECT_NO_FATAL_FAILURE(waitForCallback(callback, expected, true));
+}
+
+TEST_F(LayerCallbackTest, Merge_SameLayer) {
+    sp<SurfaceControl> layer;
+    ASSERT_NO_FATAL_FAILURE(layer = createBufferStateLayer());
+
+    Transaction transaction1, transaction2;
+    CallbackHelper callback1, callback2;
+    fillTransaction(transaction1, &callback1, layer);
+    fillTransaction(transaction2, &callback2, layer);
+
+    transaction2.merge(std::move(transaction1)).apply();
+
+    ExpectedResult expected;
+    expected.addSurface(ExpectedResult::Transaction::PRESENTED, layer);
+    EXPECT_NO_FATAL_FAILURE(waitForCallback(callback1, expected, true));
+    EXPECT_NO_FATAL_FAILURE(waitForCallback(callback2, expected, true));
+}
+
+TEST_F(LayerCallbackTest, Merge_SingleBuffer) {
+    sp<SurfaceControl> layer1, layer2;
+    ASSERT_NO_FATAL_FAILURE(layer1 = createBufferStateLayer());
+    ASSERT_NO_FATAL_FAILURE(layer2 = createBufferStateLayer());
+
+    Transaction transaction1, transaction2;
+    CallbackHelper callback1, callback2;
+    fillTransaction(transaction1, &callback1, layer1);
+    fillTransaction(transaction2, &callback2);
+
+    transaction2.setPosition(layer2, mWidth, mHeight).merge(std::move(transaction1)).apply();
+
+    ExpectedResult expected;
+    expected.addSurfaces(ExpectedResult::Transaction::PRESENTED, {layer1, layer2});
+    EXPECT_NO_FATAL_FAILURE(waitForCallback(callback1, expected, true));
+    EXPECT_NO_FATAL_FAILURE(waitForCallback(callback2, expected, true));
+}
+
+TEST_F(LayerCallbackTest, Merge_DifferentClients) {
+    sp<SurfaceComposerClient> client1(new SurfaceComposerClient),
+            client2(new SurfaceComposerClient);
+
+    ASSERT_EQ(NO_ERROR, client1->initCheck()) << "failed to create SurfaceComposerClient";
+    ASSERT_EQ(NO_ERROR, client2->initCheck()) << "failed to create SurfaceComposerClient";
+
+    sp<SurfaceControl> layer1, layer2;
+    ASSERT_NO_FATAL_FAILURE(layer1 = createLayer(client1, "test", mWidth, mHeight,
+                                                 ISurfaceComposerClient::eFXSurfaceBufferState));
+    ASSERT_NO_FATAL_FAILURE(layer2 = createLayer(client2, "test", mWidth, mHeight,
+                                                 ISurfaceComposerClient::eFXSurfaceBufferState));
+
+    Transaction transaction1, transaction2;
+    CallbackHelper callback1, callback2;
+    fillTransaction(transaction1, &callback1, layer1);
+    fillTransaction(transaction2, &callback2, layer2);
+
+    transaction2.setPosition(layer2, mWidth, mHeight).merge(std::move(transaction1)).apply();
+
+    ExpectedResult expected;
+    expected.addSurfaces(ExpectedResult::Transaction::PRESENTED, {layer1, layer2});
+    EXPECT_NO_FATAL_FAILURE(waitForCallback(callback1, expected, true));
+    EXPECT_NO_FATAL_FAILURE(waitForCallback(callback2, expected, true));
+}
+
+TEST_F(LayerCallbackTest, MultipleTransactions) {
+    sp<SurfaceControl> layer;
+    ASSERT_NO_FATAL_FAILURE(layer = createBufferStateLayer());
+
+    Transaction transaction;
+    CallbackHelper callback;
+    for (size_t i = 0; i < 10; i++) {
+        fillTransaction(transaction, &callback, layer);
+
+        transaction.apply();
+
+        ExpectedResult expected;
+        expected.addSurface(ExpectedResult::Transaction::PRESENTED, layer,
+                            ExpectedResult::Buffer::NOT_ACQUIRED,
+                            (i == 0) ? ExpectedResult::PreviousBuffer::NOT_RELEASED
+                                     : ExpectedResult::PreviousBuffer::RELEASED);
+        EXPECT_NO_FATAL_FAILURE(waitForCallback(callback, expected));
+    }
+    ASSERT_NO_FATAL_FAILURE(callback.verifyFinalState());
+}
+
+TEST_F(LayerCallbackTest, MultipleTransactions_NoStateChange) {
+    sp<SurfaceControl> layer;
+    ASSERT_NO_FATAL_FAILURE(layer = createBufferStateLayer());
+
+    Transaction transaction;
+    CallbackHelper callback;
+    for (size_t i = 0; i < 10; i++) {
+        ExpectedResult expected;
+
+        if (i == 0) {
+            fillTransaction(transaction, &callback, layer);
+            expected.addSurface(ExpectedResult::Transaction::PRESENTED, layer);
+        } else {
+            fillTransaction(transaction, &callback);
+        }
+
+        transaction.apply();
+
+        EXPECT_NO_FATAL_FAILURE(waitForCallback(callback, expected));
+    }
+    ASSERT_NO_FATAL_FAILURE(callback.verifyFinalState());
+}
+
+TEST_F(LayerCallbackTest, MultipleTransactions_SameStateChange) {
+    sp<SurfaceControl> layer;
+    ASSERT_NO_FATAL_FAILURE(layer = createBufferStateLayer());
+
+    Transaction transaction;
+    CallbackHelper callback;
+    for (size_t i = 0; i < 10; i++) {
+        if (i == 0) {
+            fillTransaction(transaction, &callback, layer);
+        } else {
+            fillTransaction(transaction, &callback);
+        }
+
+        transaction.setPosition(layer, mWidth, mHeight).apply();
+
+        ExpectedResult expected;
+        expected.addSurface((i == 0) ? ExpectedResult::Transaction::PRESENTED
+                                     : ExpectedResult::Transaction::NOT_PRESENTED,
+                            layer);
+        EXPECT_NO_FATAL_FAILURE(waitForCallback(callback, expected, i == 0));
+    }
+    ASSERT_NO_FATAL_FAILURE(callback.verifyFinalState());
+}
+
+TEST_F(LayerCallbackTest, MultipleTransactions_Merge) {
+    sp<SurfaceControl> layer1, layer2;
+    ASSERT_NO_FATAL_FAILURE(layer1 = createBufferStateLayer());
+    ASSERT_NO_FATAL_FAILURE(layer2 = createBufferStateLayer());
+
+    Transaction transaction1, transaction2;
+    CallbackHelper callback1, callback2;
+    for (size_t i = 0; i < 10; i++) {
+        fillTransaction(transaction1, &callback1, layer1);
+        fillTransaction(transaction2, &callback2, layer2);
+
+        transaction2.setPosition(layer2, mWidth, mHeight).merge(std::move(transaction1)).apply();
+
+        ExpectedResult expected;
+        expected.addSurfaces(ExpectedResult::Transaction::PRESENTED, {layer1, layer2},
+                             ExpectedResult::Buffer::NOT_ACQUIRED,
+                             (i == 0) ? ExpectedResult::PreviousBuffer::NOT_RELEASED
+                                      : ExpectedResult::PreviousBuffer::RELEASED);
+        EXPECT_NO_FATAL_FAILURE(waitForCallback(callback1, expected));
+        EXPECT_NO_FATAL_FAILURE(waitForCallback(callback2, expected));
+    }
+    ASSERT_NO_FATAL_FAILURE(callback1.verifyFinalState());
+    ASSERT_NO_FATAL_FAILURE(callback2.verifyFinalState());
+}
+
+TEST_F(LayerCallbackTest, MultipleTransactions_Merge_DifferentClients) {
+    sp<SurfaceComposerClient> client1(new SurfaceComposerClient),
+            client2(new SurfaceComposerClient);
+    ASSERT_EQ(NO_ERROR, client1->initCheck()) << "failed to create SurfaceComposerClient";
+    ASSERT_EQ(NO_ERROR, client2->initCheck()) << "failed to create SurfaceComposerClient";
+
+    sp<SurfaceControl> layer1, layer2;
+    ASSERT_NO_FATAL_FAILURE(layer1 = createLayer(client1, "test", mWidth, mHeight,
+                                                 ISurfaceComposerClient::eFXSurfaceBufferState));
+    ASSERT_NO_FATAL_FAILURE(layer2 = createLayer(client2, "test", mWidth, mHeight,
+                                                 ISurfaceComposerClient::eFXSurfaceBufferState));
+
+    Transaction transaction1, transaction2;
+    CallbackHelper callback1, callback2;
+    for (size_t i = 0; i < 10; i++) {
+        fillTransaction(transaction1, &callback1, layer1);
+        fillTransaction(transaction2, &callback2, layer2);
+
+        transaction2.setPosition(layer2, mWidth, mHeight).merge(std::move(transaction1)).apply();
+
+        ExpectedResult expected;
+        expected.addSurfaces(ExpectedResult::Transaction::PRESENTED, {layer1, layer2},
+                             ExpectedResult::Buffer::NOT_ACQUIRED,
+                             (i == 0) ? ExpectedResult::PreviousBuffer::NOT_RELEASED
+                                      : ExpectedResult::PreviousBuffer::RELEASED);
+        EXPECT_NO_FATAL_FAILURE(waitForCallback(callback1, expected));
+        EXPECT_NO_FATAL_FAILURE(waitForCallback(callback2, expected));
+    }
+    ASSERT_NO_FATAL_FAILURE(callback1.verifyFinalState());
+    ASSERT_NO_FATAL_FAILURE(callback2.verifyFinalState());
+}
+
+TEST_F(LayerCallbackTest, MultipleTransactions_Merge_DifferentClients_NoStateChange) {
+    sp<SurfaceComposerClient> client1(new SurfaceComposerClient),
+            client2(new SurfaceComposerClient);
+    ASSERT_EQ(NO_ERROR, client1->initCheck()) << "failed to create SurfaceComposerClient";
+    ASSERT_EQ(NO_ERROR, client2->initCheck()) << "failed to create SurfaceComposerClient";
+
+    sp<SurfaceControl> layer1, layer2;
+    ASSERT_NO_FATAL_FAILURE(layer1 = createLayer(client1, "test", mWidth, mHeight,
+                                                 ISurfaceComposerClient::eFXSurfaceBufferState));
+    ASSERT_NO_FATAL_FAILURE(layer2 = createLayer(client2, "test", mWidth, mHeight,
+                                                 ISurfaceComposerClient::eFXSurfaceBufferState));
+
+    Transaction transaction1, transaction2;
+    CallbackHelper callback1, callback2;
+
+    // Normal call to set up test
+    fillTransaction(transaction1, &callback1, layer1);
+    fillTransaction(transaction2, &callback2, layer2);
+
+    transaction2.setPosition(layer2, mWidth, mHeight).merge(std::move(transaction1)).apply();
+
+    ExpectedResult expected;
+    expected.addSurfaces(ExpectedResult::Transaction::PRESENTED, {layer1, layer2});
+    EXPECT_NO_FATAL_FAILURE(waitForCallback(callback1, expected, true));
+    EXPECT_NO_FATAL_FAILURE(waitForCallback(callback2, expected, true));
+    expected.reset();
+
+    // Test
+    fillTransaction(transaction1, &callback1);
+    fillTransaction(transaction2, &callback2);
+
+    transaction2.merge(std::move(transaction1)).apply();
+
+    EXPECT_NO_FATAL_FAILURE(waitForCallback(callback1, expected, true));
+    EXPECT_NO_FATAL_FAILURE(waitForCallback(callback2, expected, true));
+}
+
+TEST_F(LayerCallbackTest, MultipleTransactions_Merge_DifferentClients_SameStateChange) {
+    sp<SurfaceComposerClient> client1(new SurfaceComposerClient),
+            client2(new SurfaceComposerClient);
+
+    ASSERT_EQ(NO_ERROR, client1->initCheck()) << "failed to create SurfaceComposerClient";
+    ASSERT_EQ(NO_ERROR, client2->initCheck()) << "failed to create SurfaceComposerClient";
+
+    sp<SurfaceControl> layer1, layer2;
+    ASSERT_NO_FATAL_FAILURE(layer1 = createLayer(client1, "test", mWidth, mHeight,
+                                                 ISurfaceComposerClient::eFXSurfaceBufferState));
+    ASSERT_NO_FATAL_FAILURE(layer2 = createLayer(client2, "test", mWidth, mHeight,
+                                                 ISurfaceComposerClient::eFXSurfaceBufferState));
+
+    Transaction transaction1, transaction2;
+    CallbackHelper callback1, callback2;
+
+    // Normal call to set up test
+    fillTransaction(transaction1, &callback1, layer1);
+    fillTransaction(transaction2, &callback2, layer2);
+
+    transaction2.setPosition(layer2, mWidth, mHeight).merge(std::move(transaction1)).apply();
+
+    ExpectedResult expected;
+    expected.addSurfaces(ExpectedResult::Transaction::PRESENTED, {layer1, layer2});
+    EXPECT_NO_FATAL_FAILURE(waitForCallback(callback1, expected, true));
+    EXPECT_NO_FATAL_FAILURE(waitForCallback(callback2, expected, true));
+    expected.reset();
+
+    // Test
+    fillTransaction(transaction1, &callback1);
+    fillTransaction(transaction2, &callback2);
+
+    transaction2.setPosition(layer2, mWidth, mHeight).merge(std::move(transaction1)).apply();
+
+    expected.addSurface(ExpectedResult::Transaction::NOT_PRESENTED, layer2);
+    EXPECT_NO_FATAL_FAILURE(waitForCallback(callback1, expected, true));
+    EXPECT_NO_FATAL_FAILURE(waitForCallback(callback2, expected, true));
+}
+
+TEST_F(LayerCallbackTest, MultipleTransactions_SingleFrame) {
+    sp<SurfaceControl> layer;
+    ASSERT_NO_FATAL_FAILURE(layer = createBufferStateLayer());
+
+    Transaction transaction;
+    CallbackHelper callback;
+    std::vector<ExpectedResult> expectedResults(50);
+    ExpectedResult::PreviousBuffer previousBufferResult =
+            ExpectedResult::PreviousBuffer::NOT_RELEASED;
+    for (auto& expected : expectedResults) {
+        expected.reset();
+        expected.addSurface(ExpectedResult::Transaction::PRESENTED, layer,
+                            ExpectedResult::Buffer::NOT_ACQUIRED, previousBufferResult);
+        previousBufferResult = ExpectedResult::PreviousBuffer::RELEASED;
+
+        fillTransaction(transaction, &callback, layer);
+
+        transaction.apply();
+        std::this_thread::sleep_for(200ms);
+    }
+    EXPECT_NO_FATAL_FAILURE(waitForCallbacks(callback, expectedResults, true));
+}
+
+TEST_F(LayerCallbackTest, MultipleTransactions_SingleFrame_NoStateChange) {
+    sp<SurfaceControl> layer;
+    ASSERT_NO_FATAL_FAILURE(layer = createBufferStateLayer());
+
+    Transaction transaction;
+    CallbackHelper callback;
+    std::vector<ExpectedResult> expectedResults(50);
+    bool first = true;
+    for (auto& expected : expectedResults) {
+        expected.reset();
+
+        if (first) {
+            fillTransaction(transaction, &callback, layer);
+            expected.addSurface(ExpectedResult::Transaction::PRESENTED, layer);
+            first = false;
+        } else {
+            fillTransaction(transaction, &callback);
+        }
+
+        transaction.apply();
+        std::this_thread::sleep_for(200ms);
+    }
+    EXPECT_NO_FATAL_FAILURE(waitForCallbacks(callback, expectedResults, true));
+}
+
+TEST_F(LayerCallbackTest, MultipleTransactions_SingleFrame_SameStateChange) {
+    sp<SurfaceControl> layer;
+    ASSERT_NO_FATAL_FAILURE(layer = createBufferStateLayer());
+
+    // Normal call to set up test
+    Transaction transaction;
+    CallbackHelper callback;
+    fillTransaction(transaction, &callback, layer);
+
+    transaction.setPosition(layer, mWidth, mHeight).apply();
+
+    ExpectedResult expectedResult;
+    expectedResult.addSurface(ExpectedResult::Transaction::PRESENTED, layer);
+    EXPECT_NO_FATAL_FAILURE(waitForCallback(callback, expectedResult, true));
+
+    // Test
+    std::vector<ExpectedResult> expectedResults(50);
+    for (auto& expected : expectedResults) {
+        expected.reset();
+        expected.addSurface(ExpectedResult::Transaction::NOT_PRESENTED, layer);
+
+        fillTransaction(transaction, &callback);
+
+        transaction.setPosition(layer, mWidth, mHeight).apply();
+
+        std::this_thread::sleep_for(200ms);
+    }
+    EXPECT_NO_FATAL_FAILURE(waitForCallbacks(callback, expectedResults, true));
+}
+
 class LayerUpdateTest : public LayerTransactionTest {
 protected:
     virtual void SetUp() {
diff --git a/services/surfaceflinger/tests/unittests/CompositionTest.cpp b/services/surfaceflinger/tests/unittests/CompositionTest.cpp
index f1a8842..09bb8c5 100644
--- a/services/surfaceflinger/tests/unittests/CompositionTest.cpp
+++ b/services/surfaceflinger/tests/unittests/CompositionTest.cpp
@@ -65,6 +65,7 @@
 constexpr hwc2_layer_t HWC_LAYER = 5000;
 constexpr Transform DEFAULT_TRANSFORM = static_cast<Transform>(0);
 
+constexpr DisplayId DEFAULT_DISPLAY_ID = 42;
 constexpr int DEFAULT_DISPLAY_WIDTH = 1920;
 constexpr int DEFAULT_DISPLAY_HEIGHT = 1024;
 
@@ -226,12 +227,13 @@
     static constexpr int INIT_POWER_MODE = HWC_POWER_MODE_NORMAL;
 
     static void setupPreconditions(CompositionTest* test) {
-        FakeHwcDisplayInjector(DisplayDevice::DISPLAY_PRIMARY, HWC2::DisplayType::Physical)
+        FakeHwcDisplayInjector(DEFAULT_DISPLAY_ID, HWC2::DisplayType::Physical,
+                               true /* isPrimary */)
                 .setCapabilities(&test->mDefaultCapabilities)
                 .inject(&test->mFlinger, test->mComposer);
 
-        test->mDisplay = FakeDisplayDeviceInjector(test->mFlinger, DisplayDevice::DISPLAY_PRIMARY,
-                                                   DisplayDevice::DISPLAY_PRIMARY)
+        test->mDisplay = FakeDisplayDeviceInjector(test->mFlinger, DEFAULT_DISPLAY_ID,
+                                                   false /* isVirtual */, true /* isPrimary */)
                                  .setDisplaySize(DEFAULT_DISPLAY_WIDTH, DEFAULT_DISPLAY_HEIGHT)
                                  .setDisplaySurface(test->mDisplaySurface)
                                  .setRenderSurface(std::unique_ptr<renderengine::Surface>(
@@ -745,7 +747,9 @@
         EXPECT_CALL(*test->mComposer, createLayer(HWC_DISPLAY, _))
                 .WillOnce(DoAll(SetArgPointee<1>(HWC_LAYER), Return(Error::NONE)));
 
-        layer->createHwcLayer(test->mFlinger.mFlinger->getBE().mHwc.get(), test->mDisplay->getId());
+        const auto displayId = test->mDisplay->getId();
+        ASSERT_TRUE(displayId);
+        layer->createHwcLayer(test->mFlinger.mFlinger->getBE().mHwc.get(), *displayId);
 
         Mock::VerifyAndClear(test->mComposer);
 
@@ -758,8 +762,10 @@
     static void cleanupInjectedLayers(CompositionTest* test) {
         EXPECT_CALL(*test->mComposer, destroyLayer(HWC_DISPLAY, HWC_LAYER))
                 .WillOnce(Return(Error::NONE));
+        const auto displayId = test->mDisplay->getId();
+        ASSERT_TRUE(displayId);
         for (auto layer : test->mFlinger.mutableDrawingState().layersSortedByZ) {
-            layer->destroyHwcLayer(test->mDisplay->getId());
+            layer->destroyHwcLayer(*displayId);
         }
         test->mFlinger.mutableDrawingState().layersSortedByZ.clear();
     }
@@ -947,7 +953,7 @@
 
 struct ForcedClientCompositionResultVariant : public RECompositionResultVariant {
     static void setupLayerState(CompositionTest*, sp<Layer> layer) {
-        layer->forceClientComposition(DisplayDevice::DISPLAY_PRIMARY);
+        layer->forceClientComposition(DEFAULT_DISPLAY_ID);
     }
 
     template <typename Case>
diff --git a/services/surfaceflinger/tests/unittests/DisplayIdentificationTest.cpp b/services/surfaceflinger/tests/unittests/DisplayIdentificationTest.cpp
index 4f1c99e..55995d0 100644
--- a/services/surfaceflinger/tests/unittests/DisplayIdentificationTest.cpp
+++ b/services/surfaceflinger/tests/unittests/DisplayIdentificationTest.cpp
@@ -68,32 +68,44 @@
 
 } // namespace
 
+const DisplayIdentificationData& getInternalEdid() {
+    static const DisplayIdentificationData data = asDisplayIdentificationData(kInternalEdid);
+    return data;
+}
+
+const DisplayIdentificationData& getExternalEdid() {
+    static const DisplayIdentificationData data = asDisplayIdentificationData(kExternalEdid);
+    return data;
+}
+
+const DisplayIdentificationData& getExternalEedid() {
+    static const DisplayIdentificationData data = asDisplayIdentificationData(kExternalEedid);
+    return data;
+}
+
 TEST(DisplayIdentificationTest, isEdid) {
     EXPECT_FALSE(isEdid({}));
 
-    EXPECT_TRUE(isEdid(asDisplayIdentificationData(kInternalEdid)));
-    EXPECT_TRUE(isEdid(asDisplayIdentificationData(kExternalEdid)));
-    EXPECT_TRUE(isEdid(asDisplayIdentificationData(kExternalEedid)));
+    EXPECT_TRUE(isEdid(getInternalEdid()));
+    EXPECT_TRUE(isEdid(getExternalEdid()));
+    EXPECT_TRUE(isEdid(getExternalEedid()));
 }
 
 TEST(DisplayIdentificationTest, parseEdid) {
-    auto data = asDisplayIdentificationData(kInternalEdid);
-    auto edid = parseEdid(data);
+    auto edid = parseEdid(getInternalEdid());
     ASSERT_TRUE(edid);
     EXPECT_EQ(0x4ca3u, edid->manufacturerId);
     EXPECT_STREQ("SEC", edid->pnpId.data());
     // ASCII text should be used as fallback if display name and serial number are missing.
     EXPECT_EQ("121AT11-801", edid->displayName);
 
-    data = asDisplayIdentificationData(kExternalEdid);
-    edid = parseEdid(data);
+    edid = parseEdid(getExternalEdid());
     ASSERT_TRUE(edid);
     EXPECT_EQ(0x22f0u, edid->manufacturerId);
     EXPECT_STREQ("HWP", edid->pnpId.data());
     EXPECT_EQ("HP ZR30w", edid->displayName);
 
-    data = asDisplayIdentificationData(kExternalEedid);
-    edid = parseEdid(data);
+    edid = parseEdid(getExternalEedid());
     ASSERT_TRUE(edid);
     EXPECT_EQ(0x4c2du, edid->manufacturerId);
     EXPECT_STREQ("SAM", edid->pnpId.data());
@@ -105,7 +117,7 @@
     EXPECT_FALSE(parseEdid({}));
 
     // Display name must be printable.
-    auto data = asDisplayIdentificationData(kExternalEdid);
+    auto data = getExternalEdid();
     data[97] = '\x1b';
     auto edid = parseEdid(data);
     ASSERT_TRUE(edid);
@@ -128,20 +140,32 @@
     EXPECT_STREQ("SAM", getPnpId(0x4c2du).value_or(PnpId{}).data());
 }
 
-TEST(DisplayIdentificationTest, generateDisplayId) {
-    const auto primaryId = generateDisplayId(0, asDisplayIdentificationData(kInternalEdid));
-    ASSERT_TRUE(primaryId);
+TEST(DisplayIdentificationTest, parseDisplayIdentificationData) {
+    const auto primaryInfo = parseDisplayIdentificationData(0, getInternalEdid());
+    ASSERT_TRUE(primaryInfo);
 
-    const auto secondaryId = generateDisplayId(1, asDisplayIdentificationData(kExternalEdid));
-    ASSERT_TRUE(secondaryId);
+    const auto secondaryInfo = parseDisplayIdentificationData(1, getExternalEdid());
+    ASSERT_TRUE(secondaryInfo);
 
-    const auto tertiaryId = generateDisplayId(2, asDisplayIdentificationData(kExternalEedid));
-    ASSERT_TRUE(tertiaryId);
+    const auto tertiaryInfo = parseDisplayIdentificationData(2, getExternalEedid());
+    ASSERT_TRUE(tertiaryInfo);
 
     // Display IDs should be unique.
-    EXPECT_NE(primaryId, secondaryId);
-    EXPECT_NE(primaryId, tertiaryId);
-    EXPECT_NE(secondaryId, tertiaryId);
+    EXPECT_NE(primaryInfo->id, secondaryInfo->id);
+    EXPECT_NE(primaryInfo->id, tertiaryInfo->id);
+    EXPECT_NE(secondaryInfo->id, tertiaryInfo->id);
+}
+
+TEST(DisplayIdentificationTest, getFallbackDisplayId) {
+    // Manufacturer ID should be invalid.
+    ASSERT_FALSE(getPnpId(getFallbackDisplayId(0)));
+    ASSERT_FALSE(getPnpId(getFallbackDisplayId(0xffu)));
+}
+
+TEST(DisplayIdentificationTest, getVirtualDisplayId) {
+    // Manufacturer ID should be invalid.
+    ASSERT_FALSE(getPnpId(getVirtualDisplayId(0)));
+    ASSERT_FALSE(getPnpId(getVirtualDisplayId(0xffff'ffffu)));
 }
 
 } // namespace android
diff --git a/services/surfaceflinger/tests/unittests/DisplayIdentificationTest.h b/services/surfaceflinger/tests/unittests/DisplayIdentificationTest.h
new file mode 100644
index 0000000..1c8e5cc
--- /dev/null
+++ b/services/surfaceflinger/tests/unittests/DisplayIdentificationTest.h
@@ -0,0 +1,27 @@
+/*
+ * Copyright 2018 The Android Open Source Project
+ *
+ * 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.
+ */
+
+#pragma once
+
+#include "DisplayHardware/DisplayIdentification.h"
+
+namespace android {
+
+const DisplayIdentificationData& getInternalEdid();
+const DisplayIdentificationData& getExternalEdid();
+const DisplayIdentificationData& getExternalEedid();
+
+} // namespace android
diff --git a/services/surfaceflinger/tests/unittests/DisplayTransactionTest.cpp b/services/surfaceflinger/tests/unittests/DisplayTransactionTest.cpp
index d32627a..2e90a59 100644
--- a/services/surfaceflinger/tests/unittests/DisplayTransactionTest.cpp
+++ b/services/surfaceflinger/tests/unittests/DisplayTransactionTest.cpp
@@ -17,12 +17,16 @@
 #undef LOG_TAG
 #define LOG_TAG "LibSurfaceFlingerUnittests"
 
+#include <type_traits>
+
 #include <gmock/gmock.h>
 #include <gtest/gtest.h>
 
 #include <log/log.h>
 
 #include <ui/DebugUtils.h>
+
+#include "DisplayIdentificationTest.h"
 #include "TestableSurfaceFlinger.h"
 #include "mock/DisplayHardware/MockComposer.h"
 #include "mock/DisplayHardware/MockDisplaySurface.h"
@@ -75,9 +79,11 @@
 
 #define BOOL_SUBSTITUTE(TYPENAME) enum class TYPENAME : bool { FALSE = false, TRUE = true };
 
-BOOL_SUBSTITUTE(Critical);
 BOOL_SUBSTITUTE(Async);
+BOOL_SUBSTITUTE(Critical);
+BOOL_SUBSTITUTE(Primary);
 BOOL_SUBSTITUTE(Secure);
+BOOL_SUBSTITUTE(Virtual);
 
 /* ------------------------------------------------------------------------
  *
@@ -98,7 +104,7 @@
     // --------------------------------------------------------------------
     // Postcondition helpers
 
-    bool hasHwcDisplay(hwc2_display_t displayId);
+    bool hasPhysicalHwcDisplay(hwc2_display_t hwcDisplayId);
     bool hasTransactionFlagSet(int flag);
     bool hasDisplayDevice(sp<IBinder> displayToken);
     sp<DisplayDevice> getDisplayDevice(sp<IBinder> displayToken);
@@ -204,8 +210,8 @@
     });
 }
 
-bool DisplayTransactionTest::hasHwcDisplay(hwc2_display_t displayId) {
-    return mFlinger.mutableHwcDisplaySlots().count(displayId) == 1;
+bool DisplayTransactionTest::hasPhysicalHwcDisplay(hwc2_display_t hwcDisplayId) {
+    return mFlinger.mutableHwcPhysicalDisplayIdMap().count(hwcDisplayId) == 1;
 }
 
 bool DisplayTransactionTest::hasTransactionFlagSet(int flag) {
@@ -240,20 +246,67 @@
  *
  */
 
-template <DisplayDevice::DisplayType type, DisplayDevice::DisplayType displayId, int width,
-          int height, Critical critical, Async async, Secure secure, int grallocUsage>
+template <typename PhysicalDisplay>
+struct PhysicalDisplayId {};
+
+template <DisplayId displayId>
+using VirtualDisplayId = std::integral_constant<DisplayId, displayId>;
+
+struct NoDisplayId {};
+
+template <typename>
+struct IsPhysicalDisplayId : std::bool_constant<false> {};
+
+template <typename PhysicalDisplay>
+struct IsPhysicalDisplayId<PhysicalDisplayId<PhysicalDisplay>> : std::bool_constant<true> {};
+
+template <typename>
+struct DisplayIdGetter;
+
+template <typename PhysicalDisplay>
+struct DisplayIdGetter<PhysicalDisplayId<PhysicalDisplay>> {
+    static std::optional<DisplayId> get() {
+        if (!PhysicalDisplay::HAS_IDENTIFICATION_DATA) {
+            return getFallbackDisplayId(static_cast<bool>(PhysicalDisplay::PRIMARY)
+                                                ? HWC_DISPLAY_PRIMARY
+                                                : HWC_DISPLAY_EXTERNAL);
+        }
+
+        const auto info =
+                parseDisplayIdentificationData(PhysicalDisplay::PORT,
+                                               PhysicalDisplay::GET_IDENTIFICATION_DATA());
+        return info ? std::make_optional(info->id) : std::nullopt;
+    }
+};
+
+template <DisplayId displayId>
+struct DisplayIdGetter<VirtualDisplayId<displayId>> {
+    static std::optional<DisplayId> get() { return displayId; }
+};
+
+template <>
+struct DisplayIdGetter<NoDisplayId> {
+    static std::optional<DisplayId> get() { return {}; }
+};
+
+// DisplayIdType can be:
+//     1) PhysicalDisplayId<...> for generated ID of physical display backed by HWC.
+//     2) VirtualDisplayId<...> for hard-coded ID of virtual display backed by HWC.
+//     3) NoDisplayId for virtual display without HWC backing.
+template <typename DisplayIdType, int width, int height, Critical critical, Async async,
+          Secure secure, Primary primary, int grallocUsage>
 struct DisplayVariant {
+    using DISPLAY_ID = DisplayIdGetter<DisplayIdType>;
+
     // The display width and height
     static constexpr int WIDTH = width;
     static constexpr int HEIGHT = height;
 
     static constexpr int GRALLOC_USAGE = grallocUsage;
 
-    // The type for this display
-    static constexpr DisplayDevice::DisplayType TYPE = type;
-    static_assert(TYPE != DisplayDevice::DISPLAY_ID_INVALID);
-
-    static constexpr DisplayDevice::DisplayType DISPLAY_ID = displayId;
+    // Whether the display is virtual or physical
+    static constexpr Virtual VIRTUAL =
+            IsPhysicalDisplayId<DisplayIdType>{} ? Virtual::FALSE : Virtual::TRUE;
 
     // When creating native window surfaces for the framebuffer, whether those should be critical
     static constexpr Critical CRITICAL = critical;
@@ -264,8 +317,14 @@
     // Whether the display should be treated as secure
     static constexpr Secure SECURE = secure;
 
+    // Whether the display is primary
+    static constexpr Primary PRIMARY = primary;
+
     static auto makeFakeExistingDisplayInjector(DisplayTransactionTest* test) {
-        auto injector = FakeDisplayDeviceInjector(test->mFlinger, TYPE, DISPLAY_ID);
+        auto injector =
+                FakeDisplayDeviceInjector(test->mFlinger, DISPLAY_ID::get(),
+                                          static_cast<bool>(VIRTUAL), static_cast<bool>(PRIMARY));
+
         injector.setSecure(static_cast<bool>(SECURE));
         return injector;
     }
@@ -306,7 +365,8 @@
     }
 };
 
-template <hwc2_display_t hwcDisplayId, HWC2::DisplayType hwcDisplayType, typename DisplayVariant>
+template <hwc2_display_t hwcDisplayId, HWC2::DisplayType hwcDisplayType, typename DisplayVariant,
+          typename PhysicalDisplay = void>
 struct HwcDisplayVariant {
     // The display id supplied by the HWC
     static constexpr hwc2_display_t HWC_DISPLAY_ID = hwcDisplayId;
@@ -325,7 +385,10 @@
 
     // Called by tests to inject a HWC display setup
     static void injectHwcDisplay(DisplayTransactionTest* test) {
-        FakeHwcDisplayInjector(DisplayVariant::TYPE, HWC_DISPLAY_TYPE)
+        const auto displayId = DisplayVariant::DISPLAY_ID::get();
+        ASSERT_TRUE(displayId);
+        FakeHwcDisplayInjector(*displayId, HWC_DISPLAY_TYPE,
+                               static_cast<bool>(DisplayVariant::PRIMARY))
                 .setHwcDisplayId(HWC_DISPLAY_ID)
                 .setWidth(DisplayVariant::WIDTH)
                 .setHeight(DisplayVariant::HEIGHT)
@@ -362,8 +425,16 @@
                     getDisplayAttribute(HWC_DISPLAY_ID, HWC_ACTIVE_CONFIG_ID,
                                         IComposerClient::Attribute::DPI_Y, _))
                 .WillOnce(DoAll(SetArgPointee<3>(DEFAULT_DPI), Return(Error::NONE)));
-        EXPECT_CALL(*test->mComposer, getDisplayIdentificationData(HWC_DISPLAY_ID, _, _))
-                .WillRepeatedly(Return(Error::UNSUPPORTED));
+
+        if (PhysicalDisplay::HAS_IDENTIFICATION_DATA) {
+            EXPECT_CALL(*test->mComposer, getDisplayIdentificationData(HWC_DISPLAY_ID, _, _))
+                    .WillOnce(DoAll(SetArgPointee<1>(PhysicalDisplay::PORT),
+                                    SetArgPointee<2>(PhysicalDisplay::GET_IDENTIFICATION_DATA()),
+                                    Return(Error::NONE)));
+        } else {
+            EXPECT_CALL(*test->mComposer, getDisplayIdentificationData(HWC_DISPLAY_ID, _, _))
+                    .WillOnce(Return(Error::UNSUPPORTED));
+        }
     }
 
     // Called by tests to set up HWC call expectations
@@ -373,50 +444,67 @@
     }
 };
 
-struct NonHwcDisplayVariant {
-    static void injectHwcDisplay(DisplayTransactionTest*) {}
-
-    static void setupHwcGetActiveConfigCallExpectations(DisplayTransactionTest* test) {
-        EXPECT_CALL(*test->mComposer, getActiveConfig(_, _)).Times(0);
-    }
-};
-
 // Physical displays are expected to be synchronous, secure, and have a HWC display for output.
 constexpr uint32_t GRALLOC_USAGE_PHYSICAL_DISPLAY =
         GRALLOC_USAGE_HW_RENDER | GRALLOC_USAGE_HW_COMPOSER | GRALLOC_USAGE_HW_FB;
 
-template <hwc2_display_t hwcDisplayId, DisplayDevice::DisplayType type, int width, int height,
+template <hwc2_display_t hwcDisplayId, typename PhysicalDisplay, int width, int height,
           Critical critical>
 struct PhysicalDisplayVariant
-      : public DisplayVariant<type, type, width, height, critical, Async::FALSE, Secure::TRUE,
-                              GRALLOC_USAGE_PHYSICAL_DISPLAY>,
-        public HwcDisplayVariant<hwcDisplayId, HWC2::DisplayType::Physical,
-                                 DisplayVariant<type, type, width, height, critical, Async::FALSE,
-                                                Secure::TRUE, GRALLOC_USAGE_PHYSICAL_DISPLAY>> {};
+      : DisplayVariant<PhysicalDisplayId<PhysicalDisplay>, width, height, critical, Async::FALSE,
+                       Secure::TRUE, PhysicalDisplay::PRIMARY, GRALLOC_USAGE_PHYSICAL_DISPLAY>,
+        HwcDisplayVariant<hwcDisplayId, HWC2::DisplayType::Physical,
+                          DisplayVariant<PhysicalDisplayId<PhysicalDisplay>, width, height,
+                                         critical, Async::FALSE, Secure::TRUE,
+                                         PhysicalDisplay::PRIMARY, GRALLOC_USAGE_PHYSICAL_DISPLAY>,
+                          PhysicalDisplay> {};
+
+template <bool hasIdentificationData>
+struct PrimaryDisplay {
+    static constexpr Primary PRIMARY = Primary::TRUE;
+    static constexpr uint8_t PORT = 255;
+    static constexpr bool HAS_IDENTIFICATION_DATA = hasIdentificationData;
+    static constexpr auto GET_IDENTIFICATION_DATA = getInternalEdid;
+};
+
+template <bool hasIdentificationData>
+struct ExternalDisplay {
+    static constexpr Primary PRIMARY = Primary::FALSE;
+    static constexpr uint8_t PORT = 254;
+    static constexpr bool HAS_IDENTIFICATION_DATA = hasIdentificationData;
+    static constexpr auto GET_IDENTIFICATION_DATA = getExternalEdid;
+};
+
+struct TertiaryDisplay {
+    static constexpr Primary PRIMARY = Primary::FALSE;
+};
 
 // A primary display is a physical display that is critical
 using PrimaryDisplayVariant =
-        PhysicalDisplayVariant<1001, DisplayDevice::DISPLAY_PRIMARY, 3840, 2160, Critical::TRUE>;
+        PhysicalDisplayVariant<1001, PrimaryDisplay<false>, 3840, 2160, Critical::TRUE>;
 
 // An external display is physical display that is not critical.
 using ExternalDisplayVariant =
-        PhysicalDisplayVariant<1002, DisplayDevice::DISPLAY_EXTERNAL, 1920, 1280, Critical::FALSE>;
+        PhysicalDisplayVariant<1002, ExternalDisplay<false>, 1920, 1280, Critical::FALSE>;
 
 using TertiaryDisplayVariant =
-        PhysicalDisplayVariant<1003, DisplayDevice::DISPLAY_EXTERNAL, 1600, 1200, Critical::FALSE>;
+        PhysicalDisplayVariant<1003, TertiaryDisplay, 1600, 1200, Critical::FALSE>;
 
 // A virtual display not supported by the HWC.
 constexpr uint32_t GRALLOC_USAGE_NONHWC_VIRTUAL_DISPLAY = 0;
 
 template <int width, int height, Secure secure>
 struct NonHwcVirtualDisplayVariant
-      : public DisplayVariant<DisplayDevice::DISPLAY_VIRTUAL, DisplayDevice::DISPLAY_ID_INVALID,
-                              width, height, Critical::FALSE, Async::TRUE, secure,
-                              GRALLOC_USAGE_NONHWC_VIRTUAL_DISPLAY>,
-        public NonHwcDisplayVariant {
-    using Base = DisplayVariant<DisplayDevice::DISPLAY_VIRTUAL, DisplayDevice::DISPLAY_ID_INVALID,
-                                width, height, Critical::FALSE, Async::TRUE, secure,
-                                GRALLOC_USAGE_NONHWC_VIRTUAL_DISPLAY>;
+      : DisplayVariant<NoDisplayId, width, height, Critical::FALSE, Async::TRUE, secure,
+                       Primary::FALSE, GRALLOC_USAGE_NONHWC_VIRTUAL_DISPLAY> {
+    using Base = DisplayVariant<NoDisplayId, width, height, Critical::FALSE, Async::TRUE, secure,
+                                Primary::FALSE, GRALLOC_USAGE_NONHWC_VIRTUAL_DISPLAY>;
+
+    static void injectHwcDisplay(DisplayTransactionTest*) {}
+
+    static void setupHwcGetActiveConfigCallExpectations(DisplayTransactionTest* test) {
+        EXPECT_CALL(*test->mComposer, getActiveConfig(_, _)).Times(0);
+    }
 
     static void setupNativeWindowSurfaceCreationCallExpectations(DisplayTransactionTest* test) {
         Base::setupNativeWindowSurfaceCreationCallExpectations(test);
@@ -429,14 +517,14 @@
 
 template <int width, int height, Secure secure>
 struct HwcVirtualDisplayVariant
-      : public DisplayVariant<DisplayDevice::DISPLAY_VIRTUAL, DisplayDevice::DISPLAY_VIRTUAL, width,
-                              height, Critical::FALSE, Async::TRUE, secure,
-                              GRALLOC_USAGE_HWC_VIRTUAL_DISPLAY>,
-        public HwcDisplayVariant<1010, HWC2::DisplayType::Virtual,
-                                 NonHwcVirtualDisplayVariant<width, height, secure>> {
-    using Base =
-            DisplayVariant<DisplayDevice::DISPLAY_VIRTUAL, DisplayDevice::DISPLAY_VIRTUAL, width,
-                           height, Critical::FALSE, Async::TRUE, secure, GRALLOC_USAGE_HW_COMPOSER>;
+      : DisplayVariant<VirtualDisplayId<42>, width, height, Critical::FALSE, Async::TRUE, secure,
+                       Primary::FALSE, GRALLOC_USAGE_HWC_VIRTUAL_DISPLAY>,
+        HwcDisplayVariant<
+                1010, HWC2::DisplayType::Virtual,
+                DisplayVariant<VirtualDisplayId<42>, width, height, Critical::FALSE, Async::TRUE,
+                               secure, Primary::FALSE, GRALLOC_USAGE_HWC_VIRTUAL_DISPLAY>> {
+    using Base = DisplayVariant<VirtualDisplayId<42>, width, height, Critical::FALSE, Async::TRUE,
+                                secure, Primary::FALSE, GRALLOC_USAGE_HW_COMPOSER>;
     using Self = HwcVirtualDisplayVariant<width, height, secure>;
 
     static void setupNativeWindowSurfaceCreationCallExpectations(DisplayTransactionTest* test) {
@@ -850,8 +938,8 @@
     // The display should have been added to the current state
     ASSERT_TRUE(hasCurrentDisplayState(displayToken));
     const auto& display = getCurrentDisplayState(displayToken);
-    EXPECT_EQ(DisplayDevice::DISPLAY_VIRTUAL, display.type);
-    EXPECT_EQ(false, display.isSecure);
+    EXPECT_TRUE(display.isVirtual());
+    EXPECT_FALSE(display.isSecure);
     EXPECT_EQ(name.string(), display.displayName);
 
     // --------------------------------------------------------------------
@@ -881,8 +969,8 @@
     // The display should have been added to the current state
     ASSERT_TRUE(hasCurrentDisplayState(displayToken));
     const auto& display = getCurrentDisplayState(displayToken);
-    EXPECT_EQ(DisplayDevice::DISPLAY_VIRTUAL, display.type);
-    EXPECT_EQ(true, display.isSecure);
+    EXPECT_TRUE(display.isVirtual());
+    EXPECT_TRUE(display.isSecure);
     EXPECT_EQ(name.string(), display.displayName);
 
     // --------------------------------------------------------------------
@@ -1002,9 +1090,12 @@
  */
 class GetBestColorModeTest : public DisplayTransactionTest {
 public:
+    static constexpr DisplayId DEFAULT_DISPLAY_ID = 777;
+
     GetBestColorModeTest()
           : DisplayTransactionTest(),
-            mInjector(FakeDisplayDeviceInjector(mFlinger, DisplayDevice::DISPLAY_PRIMARY, 0)) {}
+            mInjector(FakeDisplayDeviceInjector(mFlinger, DEFAULT_DISPLAY_ID, false /* isVirtual */,
+                                                true /* isPrimary */)) {}
 
     void setHasWideColorGamut(bool hasWideColorGamut) { mHasWideColorGamut = hasWideColorGamut; }
 
@@ -1127,18 +1218,22 @@
     // Invocation
 
     DisplayDeviceState state;
-    state.type = Case::Display::TYPE;
+    state.displayId = static_cast<bool>(Case::Display::VIRTUAL) ? std::nullopt
+                                                                : Case::Display::DISPLAY_ID::get();
     state.isSecure = static_cast<bool>(Case::Display::SECURE);
 
-    auto device = mFlinger.setupNewDisplayDeviceInternal(displayToken, Case::Display::TYPE, state,
-                                                         displaySurface, producer);
+    auto device =
+            mFlinger.setupNewDisplayDeviceInternal(displayToken, Case::Display::DISPLAY_ID::get(),
+                                                   state, displaySurface, producer);
 
     // --------------------------------------------------------------------
     // Postconditions
 
     ASSERT_TRUE(device != nullptr);
-    EXPECT_EQ(Case::Display::TYPE, device->getDisplayType());
+    EXPECT_EQ(Case::Display::DISPLAY_ID::get(), device->getId());
+    EXPECT_EQ(static_cast<bool>(Case::Display::VIRTUAL), device->isVirtual());
     EXPECT_EQ(static_cast<bool>(Case::Display::SECURE), device->isSecure());
+    EXPECT_EQ(static_cast<bool>(Case::Display::PRIMARY), device->isPrimary());
     EXPECT_EQ(Case::Display::WIDTH, device->getWidth());
     EXPECT_EQ(Case::Display::HEIGHT, device->getHeight());
     EXPECT_EQ(Case::WideColorSupport::WIDE_COLOR_SUPPORTED, device->hasWideColorGamut());
@@ -1166,11 +1261,14 @@
 }
 
 TEST_F(SetupNewDisplayDeviceInternalTest, createHwcVirtualDisplay) {
-    // We need to resize this so that the HWC thinks the virtual display
-    // is something it created.
-    mFlinger.mutableHwcDisplayData().resize(3);
+    using Case = HwcVirtualDisplayCase;
 
-    setupNewDisplayDeviceInternalTest<HwcVirtualDisplayCase>();
+    // Insert display data so that the HWC thinks it created the virtual display.
+    const auto displayId = Case::Display::DISPLAY_ID::get();
+    ASSERT_TRUE(displayId);
+    mFlinger.mutableHwcDisplayData()[*displayId] = {};
+
+    setupNewDisplayDeviceInternalTest<Case>();
 }
 
 TEST_F(SetupNewDisplayDeviceInternalTest, createWideColorP3Display) {
@@ -1258,7 +1356,7 @@
 
     EXPECT_CALL(*mSurfaceInterceptor, saveDisplayCreation(_)).Times(1);
     EXPECT_CALL(*mEventThread,
-                onHotplugReceived(Case::Display::TYPE == DisplayDevice::DISPLAY_PRIMARY
+                onHotplugReceived(static_cast<bool>(Case::Display::PRIMARY)
                                           ? EventThread::DisplayType::Primary
                                           : EventThread::DisplayType::External,
                                   true))
@@ -1269,7 +1367,7 @@
 void HandleTransactionLockedTest::setupCommonCallExpectationsForDisconnectProcessing() {
     EXPECT_CALL(*mSurfaceInterceptor, saveDisplayDeletion(_)).Times(1);
     EXPECT_CALL(*mEventThread,
-                onHotplugReceived(Case::Display::TYPE == DisplayDevice::DISPLAY_PRIMARY
+                onHotplugReceived(static_cast<bool>(Case::Display::PRIMARY)
                                           ? EventThread::DisplayType::Primary
                                           : EventThread::DisplayType::External,
                                   false))
@@ -1282,30 +1380,35 @@
     ASSERT_TRUE(hasDisplayDevice(displayToken));
     const auto& device = getDisplayDevice(displayToken);
     EXPECT_EQ(static_cast<bool>(Case::Display::SECURE), device->isSecure());
-    EXPECT_EQ(Case::Display::TYPE == DisplayDevice::DISPLAY_PRIMARY, device->isPrimary());
+    EXPECT_EQ(static_cast<bool>(Case::Display::PRIMARY), device->isPrimary());
 
     // The display should have been set up in the current display state
     ASSERT_TRUE(hasCurrentDisplayState(displayToken));
     const auto& current = getCurrentDisplayState(displayToken);
-    EXPECT_EQ(Case::Display::TYPE, current.type);
+    EXPECT_EQ(static_cast<bool>(Case::Display::VIRTUAL), current.isVirtual());
+    EXPECT_EQ(static_cast<bool>(Case::Display::VIRTUAL) ? std::nullopt
+                                                        : Case::Display::DISPLAY_ID::get(),
+              current.displayId);
 
     // The display should have been set up in the drawing display state
     ASSERT_TRUE(hasDrawingDisplayState(displayToken));
     const auto& draw = getDrawingDisplayState(displayToken);
-    EXPECT_EQ(Case::Display::TYPE, draw.type);
+    EXPECT_EQ(static_cast<bool>(Case::Display::VIRTUAL), draw.isVirtual());
+    EXPECT_EQ(static_cast<bool>(Case::Display::VIRTUAL) ? std::nullopt
+                                                        : Case::Display::DISPLAY_ID::get(),
+              draw.displayId);
 }
 
 template <typename Case>
 void HandleTransactionLockedTest::verifyPhysicalDisplayIsConnected() {
     // HWComposer should have an entry for the display
-    EXPECT_TRUE(hasHwcDisplay(Case::Display::HWC_DISPLAY_ID));
+    EXPECT_TRUE(hasPhysicalHwcDisplay(Case::Display::HWC_DISPLAY_ID));
 
-    // The display should be set up as a built-in display.
-    static_assert(0 <= Case::Display::TYPE &&
-                          Case::Display::TYPE < DisplayDevice::NUM_BUILTIN_DISPLAY_TYPES,
-                  "Must use a valid physical display type index for the fixed-size array");
-    auto& displayToken = mFlinger.mutableDisplayTokens()[Case::Display::TYPE];
-    ASSERT_TRUE(displayToken != nullptr);
+    // SF should have a display token.
+    const auto displayId = Case::Display::DISPLAY_ID::get();
+    ASSERT_TRUE(displayId);
+    ASSERT_TRUE(mFlinger.mutablePhysicalDisplayTokens().count(*displayId) == 1);
+    auto& displayToken = mFlinger.mutablePhysicalDisplayTokens()[*displayId];
 
     verifyDisplayIsConnected<Case>(displayToken);
 }
@@ -1371,7 +1474,7 @@
     // Postconditions
 
     // HWComposer should not have an entry for the display
-    EXPECT_FALSE(hasHwcDisplay(Case::Display::HWC_DISPLAY_ID));
+    EXPECT_FALSE(hasPhysicalHwcDisplay(Case::Display::HWC_DISPLAY_ID));
 }
 
 template <typename Case>
@@ -1407,13 +1510,12 @@
     // Postconditions
 
     // HWComposer should not have an entry for the display
-    EXPECT_FALSE(hasHwcDisplay(Case::Display::HWC_DISPLAY_ID));
+    EXPECT_FALSE(hasPhysicalHwcDisplay(Case::Display::HWC_DISPLAY_ID));
 
-    // The display should not be set up as a built-in display.
-    ASSERT_TRUE(0 <= Case::Display::TYPE &&
-                Case::Display::TYPE < DisplayDevice::NUM_BUILTIN_DISPLAY_TYPES);
-    auto displayToken = mFlinger.mutableDisplayTokens()[Case::Display::TYPE];
-    EXPECT_TRUE(displayToken == nullptr);
+    // SF should not have a display token.
+    const auto displayId = Case::Display::DISPLAY_ID::get();
+    ASSERT_TRUE(displayId);
+    ASSERT_TRUE(mFlinger.mutablePhysicalDisplayTokens().count(*displayId) == 0);
 
     // The existing token should have been removed
     verifyDisplayIsNotConnected(existing.token());
@@ -1500,13 +1602,12 @@
     // Postconditions
 
     // HWComposer should not have an entry for the display
-    EXPECT_FALSE(hasHwcDisplay(Case::Display::HWC_DISPLAY_ID));
+    EXPECT_FALSE(hasPhysicalHwcDisplay(Case::Display::HWC_DISPLAY_ID));
 
-    // The display should not be set up as a primary built-in display.
-    ASSERT_TRUE(0 <= Case::Display::TYPE &&
-                Case::Display::TYPE < DisplayDevice::NUM_BUILTIN_DISPLAY_TYPES);
-    auto displayToken = mFlinger.mutableDisplayTokens()[Case::Display::TYPE];
-    EXPECT_TRUE(displayToken == nullptr);
+    // SF should not have a display token.
+    const auto displayId = Case::Display::DISPLAY_ID::get();
+    ASSERT_TRUE(displayId);
+    ASSERT_TRUE(mFlinger.mutablePhysicalDisplayTokens().count(*displayId) == 0);
 }
 
 TEST_F(HandleTransactionLockedTest, processesHotplugDisconnectThenConnectPrimary) {
@@ -1545,10 +1646,10 @@
 
     // The existing token should have been removed
     verifyDisplayIsNotConnected(existing.token());
-    static_assert(0 <= Case::Display::TYPE &&
-                          Case::Display::TYPE < DisplayDevice::NUM_BUILTIN_DISPLAY_TYPES,
-                  "Display type must be a built-in display");
-    EXPECT_NE(existing.token(), mFlinger.mutableDisplayTokens()[Case::Display::TYPE]);
+    const auto displayId = Case::Display::DISPLAY_ID::get();
+    ASSERT_TRUE(displayId);
+    ASSERT_TRUE(mFlinger.mutablePhysicalDisplayTokens().count(*displayId) == 1);
+    EXPECT_NE(existing.token(), mFlinger.mutablePhysicalDisplayTokens()[*displayId]);
 
     // A new display should be connected in its place
 
@@ -1578,13 +1679,12 @@
     // surface(producer)
     sp<BBinder> displayToken = new BBinder();
 
-    DisplayDeviceState info;
-    info.type = Case::Display::TYPE;
-    info.isSecure = static_cast<bool>(Case::Display::SECURE);
+    DisplayDeviceState state;
+    state.isSecure = static_cast<bool>(Case::Display::SECURE);
 
     sp<mock::GraphicBufferProducer> surface{new mock::GraphicBufferProducer()};
-    info.surface = surface;
-    mFlinger.mutableCurrentState().displays.add(displayToken, info);
+    state.surface = surface;
+    mFlinger.mutableCurrentState().displays.add(displayToken, state);
 
     // --------------------------------------------------------------------
     // Call Expectations
@@ -1646,11 +1746,10 @@
     // surface.
     sp<BBinder> displayToken = new BBinder();
 
-    DisplayDeviceState info;
-    info.type = Case::Display::TYPE;
-    info.isSecure = static_cast<bool>(Case::Display::SECURE);
+    DisplayDeviceState state;
+    state.isSecure = static_cast<bool>(Case::Display::SECURE);
 
-    mFlinger.mutableCurrentState().displays.add(displayToken, info);
+    mFlinger.mutableCurrentState().displays.add(displayToken, state);
 
     // --------------------------------------------------------------------
     // Call Expectations
@@ -1669,7 +1768,7 @@
     // The drawing display state will be set from the current display state.
     ASSERT_TRUE(hasDrawingDisplayState(displayToken));
     const auto& draw = getDrawingDisplayState(displayToken);
-    EXPECT_EQ(Case::Display::TYPE, draw.type);
+    EXPECT_EQ(static_cast<bool>(Case::Display::VIRTUAL), draw.isVirtual());
 }
 
 TEST_F(HandleTransactionLockedTest, processesVirtualDisplayRemoval) {
@@ -1679,7 +1778,9 @@
     // Preconditions
 
     // A virtual display is set up but is removed from the current state.
-    mFlinger.mutableHwcDisplayData().resize(3);
+    const auto displayId = Case::Display::DISPLAY_ID::get();
+    ASSERT_TRUE(displayId);
+    mFlinger.mutableHwcDisplayData()[*displayId] = {};
     Case::Display::injectHwcDisplay(this);
     auto existing = Case::Display::makeFakeExistingDisplayInjector(this);
     existing.inject();
@@ -2799,9 +2900,10 @@
     // --------------------------------------------------------------------
     // Preconditions
 
-    // We need to resize this so that the HWC thinks the virtual display
-    // is something it created.
-    mFlinger.mutableHwcDisplayData().resize(3);
+    // Insert display data so that the HWC thinks it created the virtual display.
+    const auto displayId = Case::Display::DISPLAY_ID::get();
+    ASSERT_TRUE(displayId);
+    mFlinger.mutableHwcDisplayData()[*displayId] = {};
 
     // A virtual display device is set up
     Case::Display::injectHwcDisplay(this);
diff --git a/services/surfaceflinger/tests/unittests/TestableSurfaceFlinger.h b/services/surfaceflinger/tests/unittests/TestableSurfaceFlinger.h
index c3534e8..b046e4a 100644
--- a/services/surfaceflinger/tests/unittests/TestableSurfaceFlinger.h
+++ b/services/surfaceflinger/tests/unittests/TestableSurfaceFlinger.h
@@ -178,10 +178,6 @@
         layer->getBE().compositionInfo.hwc.sidebandStream = sidebandStream;
     }
 
-    void setLayerCompositionType(sp<Layer> layer, HWC2::Composition type) {
-        layer->getBE().mHwcLayers[DisplayDevice::DISPLAY_PRIMARY].compositionType = type;
-    };
-
     void setLayerPotentialCursor(sp<Layer> layer, bool potentialCursor) {
         layer->mPotentialCursor = potentialCursor;
     }
@@ -200,7 +196,8 @@
 
     auto resetDisplayState() { return mFlinger->resetDisplayState(); }
 
-    auto setupNewDisplayDeviceInternal(const wp<IBinder>& displayToken, int32_t displayId,
+    auto setupNewDisplayDeviceInternal(const wp<IBinder>& displayToken,
+                                       const std::optional<DisplayId>& displayId,
                                        const DisplayDeviceState& state,
                                        const sp<DisplaySurface>& dispSurface,
                                        const sp<IGraphicBufferProducer>& producer) {
@@ -263,7 +260,6 @@
     auto& mutableCurrentState() { return mFlinger->mCurrentState; }
     auto& mutableDisplayColorSetting() { return mFlinger->mDisplayColorSetting; }
     auto& mutableDisplays() { return mFlinger->mDisplays; }
-    auto& mutableDisplayTokens() { return mFlinger->mDisplayTokens; }
     auto& mutableDrawingState() { return mFlinger->mDrawingState; }
     auto& mutableEventControlThread() { return mFlinger->mEventControlThread; }
     auto& mutableEventQueue() { return mFlinger->mEventQueue; }
@@ -273,6 +269,7 @@
     auto& mutableInterceptor() { return mFlinger->mInterceptor; }
     auto& mutableMainThreadId() { return mFlinger->mMainThreadId; }
     auto& mutablePendingHotplugEvents() { return mFlinger->mPendingHotplugEvents; }
+    auto& mutablePhysicalDisplayTokens() { return mFlinger->mPhysicalDisplayTokens; }
     auto& mutablePrimaryDispSync() { return mFlinger->mPrimaryDispSync; }
     auto& mutablePrimaryHWVsyncEnabled() { return mFlinger->mPrimaryHWVsyncEnabled; }
     auto& mutableTexturePool() { return mFlinger->mTexturePool; }
@@ -280,8 +277,13 @@
     auto& mutableUseHwcVirtualDisplays() { return mFlinger->mUseHwcVirtualDisplays; }
 
     auto& mutableComposerSequenceId() { return mFlinger->getBE().mComposerSequenceId; }
-    auto& mutableHwcDisplayData() { return mFlinger->getBE().mHwc->mDisplayData; }
-    auto& mutableHwcDisplaySlots() { return mFlinger->getBE().mHwc->mHwcDisplaySlots; }
+    auto& mutableHwcDisplayData() { return mFlinger->getHwComposer().mDisplayData; }
+    auto& mutableHwcPhysicalDisplayIdMap() {
+        return mFlinger->getHwComposer().mPhysicalDisplayIdMap;
+    }
+
+    auto& mutableInternalHwcDisplayId() { return mFlinger->getHwComposer().mInternalHwcDisplayId; }
+    auto& mutableExternalHwcDisplayId() { return mFlinger->getHwComposer().mExternalHwcDisplayId; }
 
     ~TestableSurfaceFlinger() {
         // All these pointer and container clears help ensure that GMock does
@@ -333,8 +335,9 @@
         static constexpr int32_t DEFAULT_DPI = 320;
         static constexpr int32_t DEFAULT_ACTIVE_CONFIG = 0;
 
-        FakeHwcDisplayInjector(DisplayDevice::DisplayType type, HWC2::DisplayType hwcDisplayType)
-              : mType(type), mHwcDisplayType(hwcDisplayType) {}
+        FakeHwcDisplayInjector(DisplayId displayId, HWC2::DisplayType hwcDisplayType,
+                               bool isPrimary)
+              : mDisplayId(displayId), mHwcDisplayType(hwcDisplayType), mIsPrimary(isPrimary) {}
 
         auto& setHwcDisplayId(hwc2_display_t displayId) {
             mHwcDisplayId = displayId;
@@ -403,17 +406,22 @@
             display->mutableConfigs().emplace(mActiveConfig, config.build());
             display->mutableIsConnected() = true;
 
-            ASSERT_TRUE(flinger->mutableHwcDisplayData().size() > static_cast<size_t>(mType));
-            flinger->mutableHwcDisplayData()[mType] = HWComposer::DisplayData();
-            flinger->mutableHwcDisplayData()[mType].hwcDisplay = display.get();
-            flinger->mutableHwcDisplaySlots().emplace(mHwcDisplayId, mType);
+            flinger->mutableHwcDisplayData()[mDisplayId].hwcDisplay = display.get();
+
+            if (mHwcDisplayType == HWC2::DisplayType::Physical) {
+                flinger->mutableHwcPhysicalDisplayIdMap().emplace(mHwcDisplayId, mDisplayId);
+                (mIsPrimary ? flinger->mutableInternalHwcDisplayId()
+                            : flinger->mutableExternalHwcDisplayId()) = mHwcDisplayId;
+            }
 
             flinger->mFakeHwcDisplays.push_back(std::move(display));
         }
 
     private:
-        DisplayDevice::DisplayType mType;
-        HWC2::DisplayType mHwcDisplayType;
+        const DisplayId mDisplayId;
+        const HWC2::DisplayType mHwcDisplayType;
+        const bool mIsPrimary;
+
         hwc2_display_t mHwcDisplayId = DEFAULT_HWC_DISPLAY_ID;
         int32_t mWidth = DEFAULT_WIDTH;
         int32_t mHeight = DEFAULT_HEIGHT;
@@ -427,10 +435,13 @@
 
     class FakeDisplayDeviceInjector {
     public:
-        FakeDisplayDeviceInjector(TestableSurfaceFlinger& flinger, DisplayDevice::DisplayType type,
-                                  int32_t displayId)
-              : mFlinger(flinger),
-                mCreationArgs(flinger.mFlinger.get(), mDisplayToken, type, displayId) {}
+        FakeDisplayDeviceInjector(TestableSurfaceFlinger& flinger,
+                                  const std::optional<DisplayId>& displayId, bool isVirtual,
+                                  bool isPrimary)
+              : mFlinger(flinger), mCreationArgs(flinger.mFlinger.get(), mDisplayToken, displayId) {
+            mCreationArgs.isVirtual = isVirtual;
+            mCreationArgs.isPrimary = isPrimary;
+        }
 
         sp<IBinder> token() const { return mDisplayToken; }
 
@@ -497,7 +508,7 @@
 
         sp<DisplayDevice> inject() {
             DisplayDeviceState state;
-            state.type = mCreationArgs.type;
+            state.displayId = mCreationArgs.isVirtual ? std::nullopt : mCreationArgs.displayId;
             state.isSecure = mCreationArgs.isSecure;
 
             sp<DisplayDevice> device = new DisplayDevice(std::move(mCreationArgs));
@@ -505,9 +516,9 @@
             mFlinger.mutableCurrentState().displays.add(mDisplayToken, state);
             mFlinger.mutableDrawingState().displays.add(mDisplayToken, state);
 
-            if (state.type >= DisplayDevice::DISPLAY_PRIMARY &&
-                state.type < DisplayDevice::DISPLAY_VIRTUAL) {
-                mFlinger.mutableDisplayTokens()[state.type] = mDisplayToken;
+            if (!mCreationArgs.isVirtual) {
+                LOG_ALWAYS_FATAL_IF(!state.displayId);
+                mFlinger.mutablePhysicalDisplayTokens()[*state.displayId] = mDisplayToken;
             }
 
             return device;
diff --git a/services/vr/bufferhubd/consumer_queue_channel.cpp b/services/vr/bufferhubd/consumer_queue_channel.cpp
index 74b549d..5d7d4e9 100644
--- a/services/vr/bufferhubd/consumer_queue_channel.cpp
+++ b/services/vr/bufferhubd/consumer_queue_channel.cpp
@@ -80,27 +80,35 @@
 }
 
 void ConsumerQueueChannel::RegisterNewBuffer(
-    const std::shared_ptr<ProducerChannel>& producer_channel, size_t slot) {
-  ALOGD_IF(TRACE,
-           "ConsumerQueueChannel::RegisterNewBuffer: queue_id=%d buffer_id=%d "
-           "slot=%zu silent=%d",
-           buffer_id(), producer_channel->buffer_id(), slot, silent_);
+    const std::shared_ptr<ProducerChannel>& producer_channel,
+    size_t producer_slot) {
+  ALOGD_IF(TRACE, "%s: queue_id=%d buffer_id=%d slot=%zu silent=%d",
+           __FUNCTION__, buffer_id(), producer_channel->buffer_id(),
+           producer_slot, silent_);
   // Only register buffers if the queue is not silent.
-  if (!silent_) {
-    pending_buffer_slots_.emplace(producer_channel, slot);
-
-    // Signal the client that there is new buffer available.
-    SignalAvailable();
+  if (silent_) {
+    return;
   }
+
+  auto status = producer_channel->CreateConsumerStateMask();
+  if (!status.ok()) {
+    ALOGE("%s: Failed to create consumer state mask: %s", __FUNCTION__,
+          status.GetErrorMessage().c_str());
+    return;
+  }
+  uint64_t consumer_state_mask = status.get();
+
+  pending_buffer_slots_.emplace(producer_channel, producer_slot,
+                                consumer_state_mask);
+  // Signal the client that there is new buffer available.
+  SignalAvailable();
 }
 
 Status<std::vector<std::pair<RemoteChannelHandle, size_t>>>
 ConsumerQueueChannel::OnConsumerQueueImportBuffers(Message& message) {
   std::vector<std::pair<RemoteChannelHandle, size_t>> buffer_handles;
-  ATRACE_NAME("ConsumerQueueChannel::OnConsumerQueueImportBuffers");
-  ALOGD_IF(TRACE,
-           "ConsumerQueueChannel::OnConsumerQueueImportBuffers: "
-           "pending_buffer_slots=%zu",
+  ATRACE_NAME(__FUNCTION__);
+  ALOGD_IF(TRACE, "%s: pending_buffer_slots=%zu", __FUNCTION__,
            pending_buffer_slots_.size());
 
   // Indicate this is a silent queue that will not import buffers.
@@ -108,30 +116,30 @@
     return ErrorStatus(EBADR);
 
   while (!pending_buffer_slots_.empty()) {
-    auto producer_channel = pending_buffer_slots_.front().first.lock();
-    size_t producer_slot = pending_buffer_slots_.front().second;
+    auto producer_channel =
+        pending_buffer_slots_.front().producer_channel.lock();
+    size_t producer_slot = pending_buffer_slots_.front().producer_slot;
+    uint64_t consumer_state_mask =
+        pending_buffer_slots_.front().consumer_state_mask;
     pending_buffer_slots_.pop();
 
     // It's possible that the producer channel has expired. When this occurs,
     // ignore the producer channel.
     if (producer_channel == nullptr) {
-      ALOGW(
-          "ConsumerQueueChannel::OnConsumerQueueImportBuffers: producer "
-          "channel has already been expired.");
+      ALOGW("%s: producer channel has already been expired.", __FUNCTION__);
       continue;
     }
 
-    auto status = producer_channel->CreateConsumer(message);
+    auto status =
+        producer_channel->CreateConsumer(message, consumer_state_mask);
 
     // If no buffers are imported successfully, clear available and return an
     // error. Otherwise, return all consumer handles already imported
     // successfully, but keep available bits on, so that the client can retry
     // importing remaining consumer buffers.
     if (!status) {
-      ALOGE(
-          "ConsumerQueueChannel::OnConsumerQueueImportBuffers: Failed create "
-          "consumer: %s",
-          status.GetErrorMessage().c_str());
+      ALOGE("%s: Failed create consumer: %s", __FUNCTION__,
+            status.GetErrorMessage().c_str());
       if (buffer_handles.empty()) {
         ClearAvailable();
         return status.error_status();
diff --git a/services/vr/bufferhubd/include/private/dvr/consumer_queue_channel.h b/services/vr/bufferhubd/include/private/dvr/consumer_queue_channel.h
index 8f35437..3a81b03 100644
--- a/services/vr/bufferhubd/include/private/dvr/consumer_queue_channel.h
+++ b/services/vr/bufferhubd/include/private/dvr/consumer_queue_channel.h
@@ -3,8 +3,8 @@
 
 #include <queue>
 
-#include <private/dvr/bufferhub_rpc.h>
 #include <private/dvr/buffer_hub.h>
+#include <private/dvr/bufferhub_rpc.h>
 #include <private/dvr/consumer_channel.h>
 #include <private/dvr/producer_queue_channel.h>
 
@@ -28,7 +28,8 @@
   // Called by ProdcuerQueueChannel to notify consumer queue that a new
   // buffer has been allocated.
   void RegisterNewBuffer(
-      const std::shared_ptr<ProducerChannel>& producer_channel, size_t slot);
+      const std::shared_ptr<ProducerChannel>& producer_channel,
+      size_t producer_slot);
 
   // Called after clients been signaled by service that new buffer has been
   // allocated. Clients uses kOpConsumerQueueImportBuffers to import new
@@ -40,14 +41,29 @@
   void OnProducerClosed();
 
  private:
+  // Data structure to store relavant info of a newly allocated producer buffer
+  // so that consumer channel and buffer can be created later.
+  struct PendingBuffer {
+    PendingBuffer(std::shared_ptr<ProducerChannel> channel, size_t slot,
+                  uint64_t mask) {
+      producer_channel = channel;
+      producer_slot = slot;
+      consumer_state_mask = mask;
+    }
+    PendingBuffer() = delete;
+
+    std::weak_ptr<ProducerChannel> producer_channel;
+    size_t producer_slot;
+    uint64_t consumer_state_mask;
+  };
+
   std::shared_ptr<ProducerQueueChannel> GetProducer() const;
 
   // Pointer to the producer channel.
   std::weak_ptr<Channel> producer_;
 
   // Tracks newly allocated buffer producers along with it's slot number.
-  std::queue<std::pair<std::weak_ptr<ProducerChannel>, size_t>>
-      pending_buffer_slots_;
+  std::queue<PendingBuffer> pending_buffer_slots_;
 
   // Tracks how many buffers have this queue imported.
   size_t capacity_;
diff --git a/services/vr/bufferhubd/include/private/dvr/producer_channel.h b/services/vr/bufferhubd/include/private/dvr/producer_channel.h
index 5868b09..3ad9c70 100644
--- a/services/vr/bufferhubd/include/private/dvr/producer_channel.h
+++ b/services/vr/bufferhubd/include/private/dvr/producer_channel.h
@@ -53,7 +53,9 @@
 
   BufferDescription<BorrowedHandle> GetBuffer(uint64_t client_state_mask);
 
-  pdx::Status<RemoteChannelHandle> CreateConsumer(Message& message);
+  pdx::Status<RemoteChannelHandle> CreateConsumer(Message& message,
+                                                  uint64_t consumer_state_mask);
+  pdx::Status<uint64_t> CreateConsumerStateMask();
   pdx::Status<RemoteChannelHandle> OnNewConsumer(Message& message);
 
   pdx::Status<LocalFence> OnConsumerAcquire(Message& message);
@@ -93,7 +95,7 @@
   LocalFence post_fence_;
   LocalFence returned_fence_;
   size_t user_metadata_size_;  // size of user requested buffer buffer size.
-  size_t metadata_buf_size_;  // size of the ion buffer that holds metadata.
+  size_t metadata_buf_size_;   // size of the ion buffer that holds metadata.
 
   pdx::LocalHandle acquire_fence_fd_;
   pdx::LocalHandle release_fence_fd_;
@@ -111,6 +113,10 @@
   pdx::Status<void> OnProducerPost(Message& message, LocalFence acquire_fence);
   pdx::Status<LocalFence> OnProducerGain(Message& message);
 
+  // Remove consumer from atomics in shared memory based on consumer_state_mask.
+  // This function is used for clean up for failures in CreateConsumer method.
+  void RemoveConsumerClientMask(uint64_t consumer_state_mask);
+
   ProducerChannel(const ProducerChannel&) = delete;
   void operator=(const ProducerChannel&) = delete;
 };
diff --git a/services/vr/bufferhubd/producer_channel.cpp b/services/vr/bufferhubd/producer_channel.cpp
index 397c0ae..c6e8ea9 100644
--- a/services/vr/bufferhubd/producer_channel.cpp
+++ b/services/vr/bufferhubd/producer_channel.cpp
@@ -248,21 +248,7 @@
   return {GetBuffer(BufferHubDefs::kFirstClientBitMask)};
 }
 
-Status<RemoteChannelHandle> ProducerChannel::CreateConsumer(Message& message) {
-  ATRACE_NAME("ProducerChannel::CreateConsumer");
-  ALOGD_IF(TRACE,
-           "ProducerChannel::CreateConsumer: buffer_id=%d, producer_owns=%d",
-           buffer_id(), producer_owns_);
-
-  int channel_id;
-  auto status = message.PushChannel(0, nullptr, &channel_id);
-  if (!status) {
-    ALOGE(
-        "ProducerChannel::CreateConsumer: Failed to push consumer channel: %s",
-        status.GetErrorMessage().c_str());
-    return ErrorStatus(ENOMEM);
-  }
-
+Status<uint64_t> ProducerChannel::CreateConsumerStateMask() {
   // Try find the next consumer state bit which has not been claimed by any
   // consumer yet.
   // memory_order_acquire is chosen here because all writes in other threads
@@ -277,7 +263,6 @@
         "consumers per producer: 63.");
     return ErrorStatus(E2BIG);
   }
-
   uint64_t updated_active_clients_bit_mask =
       current_active_clients_bit_mask | client_state_mask;
   // Set the updated value only if the current value stays the same as what was
@@ -286,28 +271,71 @@
   // thread, and the modification will be visible in other threads that acquire
   // active_clients_bit_mask_. If the comparison fails, load the result of
   // all writes from all threads to updated_active_clients_bit_mask.
-  if (!active_clients_bit_mask_->compare_exchange_weak(
-          current_active_clients_bit_mask, updated_active_clients_bit_mask,
-          std::memory_order_acq_rel, std::memory_order_acquire)) {
+  // Keep on finding the next available slient state mask until succeed or out
+  // of memory.
+  while (!active_clients_bit_mask_->compare_exchange_weak(
+      current_active_clients_bit_mask, updated_active_clients_bit_mask,
+      std::memory_order_acq_rel, std::memory_order_acquire)) {
     ALOGE("Current active clients bit mask is changed to %" PRIx64
-          ", which was expected to be %" PRIx64 ".",
+          ", which was expected to be %" PRIx64
+          ". Trying to generate a new client state mask to resolve race "
+          "condition.",
           updated_active_clients_bit_mask, current_active_clients_bit_mask);
-    return ErrorStatus(EBUSY);
+    client_state_mask = BufferHubDefs::FindNextAvailableClientStateMask(
+        current_active_clients_bit_mask | orphaned_consumer_bit_mask_);
+    if (client_state_mask == 0ULL) {
+      ALOGE(
+          "ProducerChannel::CreateConsumer: reached the maximum mumber of "
+          "consumers per producer: 63.");
+      return ErrorStatus(E2BIG);
+    }
+    updated_active_clients_bit_mask =
+        current_active_clients_bit_mask | client_state_mask;
   }
 
-  auto consumer =
-      std::make_shared<ConsumerChannel>(service(), buffer_id(), channel_id,
-                                        client_state_mask, shared_from_this());
+  return {client_state_mask};
+}
+
+void ProducerChannel::RemoveConsumerClientMask(uint64_t consumer_state_mask) {
+  // Clear up the buffer state and fence state in case there is already
+  // something there due to possible race condition between producer post and
+  // consumer failed to create channel.
+  buffer_state_->fetch_and(~consumer_state_mask, std::memory_order_release);
+  fence_state_->fetch_and(~consumer_state_mask, std::memory_order_release);
+
+  // Restore the consumer state bit and make it visible in other threads that
+  // acquire the active_clients_bit_mask_.
+  active_clients_bit_mask_->fetch_and(~consumer_state_mask,
+                                      std::memory_order_release);
+}
+
+Status<RemoteChannelHandle> ProducerChannel::CreateConsumer(
+    Message& message, uint64_t consumer_state_mask) {
+  ATRACE_NAME("ProducerChannel::CreateConsumer");
+  ALOGD_IF(TRACE,
+           "ProducerChannel::CreateConsumer: buffer_id=%d, producer_owns=%d",
+           buffer_id(), producer_owns_);
+
+  int channel_id;
+  auto status = message.PushChannel(0, nullptr, &channel_id);
+  if (!status) {
+    ALOGE(
+        "ProducerChannel::CreateConsumer: Failed to push consumer channel: %s",
+        status.GetErrorMessage().c_str());
+    RemoveConsumerClientMask(consumer_state_mask);
+    return ErrorStatus(ENOMEM);
+  }
+
+  auto consumer = std::make_shared<ConsumerChannel>(
+      service(), buffer_id(), channel_id, consumer_state_mask,
+      shared_from_this());
   const auto channel_status = service()->SetChannel(channel_id, consumer);
   if (!channel_status) {
     ALOGE(
         "ProducerChannel::CreateConsumer: failed to set new consumer channel: "
         "%s",
         channel_status.GetErrorMessage().c_str());
-    // Restore the consumer state bit and make it visible in other threads that
-    // acquire the active_clients_bit_mask_.
-    active_clients_bit_mask_->fetch_and(~client_state_mask,
-                                        std::memory_order_release);
+    RemoveConsumerClientMask(consumer_state_mask);
     return ErrorStatus(ENOMEM);
   }
 
@@ -327,7 +355,11 @@
 Status<RemoteChannelHandle> ProducerChannel::OnNewConsumer(Message& message) {
   ATRACE_NAME("ProducerChannel::OnNewConsumer");
   ALOGD_IF(TRACE, "ProducerChannel::OnNewConsumer: buffer_id=%d", buffer_id());
-  return CreateConsumer(message);
+  auto status = CreateConsumerStateMask();
+  if (!status.ok()) {
+    return status.error_status();
+  }
+  return CreateConsumer(message, /*consumer_state_mask=*/status.get());
 }
 
 Status<void> ProducerChannel::OnProducerPost(Message&,
diff --git a/services/vr/hardware_composer/Android.bp b/services/vr/hardware_composer/Android.bp
index 003775b..258b45b 100644
--- a/services/vr/hardware_composer/Android.bp
+++ b/services/vr/hardware_composer/Android.bp
@@ -55,7 +55,6 @@
     "-DATRACE_TAG=ATRACE_TAG_GRAPHICS",
     "-Wall",
     "-Werror",
-    // mVrClient unused in vr_composer_client.cpp
     "-Wno-error=unused-private-field",
     // Warnings in vr_hwc.cpp to be fixed after sync of goog/master.
     "-Wno-sign-compare",
diff --git a/services/vr/hardware_composer/impl/vr_composer_client.cpp b/services/vr/hardware_composer/impl/vr_composer_client.cpp
index d775711..786d5fa 100644
--- a/services/vr/hardware_composer/impl/vr_composer_client.cpp
+++ b/services/vr/hardware_composer/impl/vr_composer_client.cpp
@@ -45,7 +45,7 @@
 }
 
 VrComposerClient::VrCommandEngine::VrCommandEngine(VrComposerClient& client)
-    : ComposerCommandEngine(client.mHal, client.mResources.get()), mVrClient(client),
+    : ComposerCommandEngine(client.mHal, client.mResources.get()),
       mVrHal(client.mVrHal) {}
 
 VrComposerClient::VrCommandEngine::~VrCommandEngine() {}
diff --git a/services/vr/hardware_composer/impl/vr_composer_client.h b/services/vr/hardware_composer/impl/vr_composer_client.h
index 76b1c4f..de22640 100644
--- a/services/vr/hardware_composer/impl/vr_composer_client.h
+++ b/services/vr/hardware_composer/impl/vr_composer_client.h
@@ -54,7 +54,6 @@
 
     IVrComposerClient::BufferMetadata readBufferMetadata();
 
-    VrComposerClient& mVrClient;
     android::dvr::VrHwc& mVrHal;
 
     VrCommandEngine(const VrCommandEngine&) = delete;
diff --git a/services/vr/performanced/performance_service_tests.cpp b/services/vr/performanced/performance_service_tests.cpp
index 4065785..a24c889 100644
--- a/services/vr/performanced/performance_service_tests.cpp
+++ b/services/vr/performanced/performance_service_tests.cpp
@@ -12,16 +12,16 @@
 #include <thread>
 #include <utility>
 
+#include <android-base/strings.h>
 #include <android-base/unique_fd.h>
 #include <dvr/performance_client_api.h>
 #include <gtest/gtest.h>
 #include <private/android_filesystem_config.h>
 
 #include "stdio_filebuf.h"
-#include "string_trim.h"
 #include "unique_file.h"
 
-using android::dvr::Trim;
+using android::base::Trim;
 using android::dvr::UniqueFile;
 using android::dvr::stdio_filebuf;
 
diff --git a/services/vr/performanced/string_trim.h b/services/vr/performanced/string_trim.h
deleted file mode 100644
index 7094e9f..0000000
--- a/services/vr/performanced/string_trim.h
+++ /dev/null
@@ -1,46 +0,0 @@
-#ifndef ANDROID_DVR_PERFORMANCED_STRING_TRIM_H_
-#define ANDROID_DVR_PERFORMANCED_STRING_TRIM_H_
-
-#include <functional>
-#include <locale>
-#include <string>
-
-namespace android {
-namespace dvr {
-
-// Trims whitespace from the left side of |subject| and returns the result as a
-// new string.
-inline std::string LeftTrim(std::string subject) {
-  subject.erase(subject.begin(),
-                std::find_if(subject.begin(), subject.end(),
-                             std::not1(std::ptr_fun<int, int>(std::isspace))));
-  return subject;
-}
-
-// Trims whitespace from the right side of |subject| and returns the result as a
-// new string.
-inline std::string RightTrim(std::string subject) {
-  subject.erase(std::find_if(subject.rbegin(), subject.rend(),
-                             std::not1(std::ptr_fun<int, int>(std::isspace)))
-                    .base(),
-                subject.end());
-  return subject;
-}
-
-// Trims whitespace from the both sides of |subject| and returns the result as a
-// new string.
-inline std::string Trim(std::string subject) {
-  subject.erase(subject.begin(),
-                std::find_if(subject.begin(), subject.end(),
-                             std::not1(std::ptr_fun<int, int>(std::isspace))));
-  subject.erase(std::find_if(subject.rbegin(), subject.rend(),
-                             std::not1(std::ptr_fun<int, int>(std::isspace)))
-                    .base(),
-                subject.end());
-  return subject;
-}
-
-}  // namespace dvr
-}  // namespace android
-
-#endif  // ANDROID_DVR_PERFORMANCED_STRING_TRIM_H_
diff --git a/services/vr/performanced/task.cpp b/services/vr/performanced/task.cpp
index bda1682..2fc96bf 100644
--- a/services/vr/performanced/task.cpp
+++ b/services/vr/performanced/task.cpp
@@ -10,10 +10,10 @@
 #include <memory>
 #include <sstream>
 
+#include <android-base/strings.h>
 #include <android-base/unique_fd.h>
 
 #include "stdio_filebuf.h"
-#include "string_trim.h"
 
 namespace {
 
@@ -102,7 +102,7 @@
 
       // The status file has lines with the format <field>:<value>. Extract the
       // value after the colon.
-      return Trim(line.substr(offset + field.size() + 1));
+      return android::base::Trim(line.substr(offset + field.size() + 1));
     }
   }
 
@@ -123,7 +123,7 @@
       }
 
       std::string key = line.substr(0, offset);
-      std::string value = Trim(line.substr(offset + 1));
+      std::string value = android::base::Trim(line.substr(offset + 1));
 
       ALOGD_IF(TRACE, "Task::ReadStatusFields: key=\"%s\" value=\"%s\"",
                key.c_str(), value.c_str());
@@ -156,7 +156,7 @@
     std::string line = "";
     std::getline(file_stream, line);
 
-    return Trim(line);
+    return android::base::Trim(line);
   } else {
     return "";
   }