Merge "Load ANGLEResetDisplayPlatform via dlsym" into sc-dev
diff --git a/Android.bp b/Android.bp
index 298c01a..1393f61 100644
--- a/Android.bp
+++ b/Android.bp
@@ -85,4 +85,4 @@
 cc_library_headers{
     name: "libandroid_headers_private",
     export_include_dirs: ["include/private"],
-}
\ No newline at end of file
+}
diff --git a/cmds/dumpstate/dumpstate.cpp b/cmds/dumpstate/dumpstate.cpp
index 45959e4..3a2c769 100644
--- a/cmds/dumpstate/dumpstate.cpp
+++ b/cmds/dumpstate/dumpstate.cpp
@@ -1208,10 +1208,6 @@
 
 static void DumpPacketStats() {
     DumpFile("NETWORK DEV INFO", "/proc/net/dev");
-    DumpFile("QTAGUID NETWORK INTERFACES INFO", "/proc/net/xt_qtaguid/iface_stat_all");
-    DumpFile("QTAGUID NETWORK INTERFACES INFO (xt)", "/proc/net/xt_qtaguid/iface_stat_fmt");
-    DumpFile("QTAGUID CTRL INFO", "/proc/net/xt_qtaguid/ctrl");
-    DumpFile("QTAGUID STATS INFO", "/proc/net/xt_qtaguid/stats");
 }
 
 static void DumpIpAddrAndRules() {
@@ -2061,7 +2057,7 @@
 }
 
 Dumpstate::RunStatus Dumpstate::DumpTraces(const char** path) {
-    const std::string temp_file_pattern = "/data/anr/dumptrace_XXXXXX";
+    const std::string temp_file_pattern = ds.bugreport_internal_dir_ + "/dumptrace_XXXXXX";
     const size_t buf_size = temp_file_pattern.length() + 1;
     std::unique_ptr<char[]> file_name_buf(new char[buf_size]);
     memcpy(file_name_buf.get(), temp_file_pattern.c_str(), buf_size);
@@ -3068,6 +3064,9 @@
     android::os::UnlinkAndLogOnError(tmp_path_);
     android::os::UnlinkAndLogOnError(screenshot_path_);
     android::os::UnlinkAndLogOnError(path_);
+    if (dump_traces_path != nullptr) {
+        android::os::UnlinkAndLogOnError(dump_traces_path);
+    }
 }
 
 void Dumpstate::EnableParallelRunIfNeeded() {
diff --git a/cmds/installd/dexopt.cpp b/cmds/installd/dexopt.cpp
index 4dfd1d0..0cf50a3 100644
--- a/cmds/installd/dexopt.cpp
+++ b/cmds/installd/dexopt.cpp
@@ -2112,8 +2112,9 @@
     {
         struct stat s;
         if (stat(b_path.c_str(), &s) != 0) {
-            // Silently ignore for now. The service calling this isn't smart enough to understand
-            // lack of artifacts at the moment.
+            // Ignore for now. The service calling this isn't smart enough to
+            // understand lack of artifacts at the moment.
+            LOG(VERBOSE) << "A/B artifact " << b_path << " does not exist!";
             return false;
         }
         if (!S_ISREG(s.st_mode)) {
diff --git a/cmds/installd/otapreopt.cpp b/cmds/installd/otapreopt.cpp
index 443821c..ef052bd 100644
--- a/cmds/installd/otapreopt.cpp
+++ b/cmds/installd/otapreopt.cpp
@@ -473,24 +473,29 @@
     // Run dexopt with the parameters of parameters_.
     // TODO(calin): embed the profile name in the parameters.
     int Dexopt() {
-        std::string dummy;
-        return dexopt(parameters_.apk_path,
-                      parameters_.uid,
-                      parameters_.pkgName,
-                      parameters_.instruction_set,
-                      parameters_.dexopt_needed,
-                      parameters_.oat_dir,
-                      parameters_.dexopt_flags,
-                      parameters_.compiler_filter,
-                      parameters_.volume_uuid,
-                      parameters_.shared_libraries,
-                      parameters_.se_info,
-                      parameters_.downgrade,
-                      parameters_.target_sdk_version,
-                      parameters_.profile_name,
-                      parameters_.dex_metadata_path,
-                      parameters_.compilation_reason,
-                      &dummy);
+        std::string error;
+        int res = dexopt(parameters_.apk_path,
+                         parameters_.uid,
+                         parameters_.pkgName,
+                         parameters_.instruction_set,
+                         parameters_.dexopt_needed,
+                         parameters_.oat_dir,
+                         parameters_.dexopt_flags,
+                         parameters_.compiler_filter,
+                         parameters_.volume_uuid,
+                         parameters_.shared_libraries,
+                         parameters_.se_info,
+                         parameters_.downgrade,
+                         parameters_.target_sdk_version,
+                         parameters_.profile_name,
+                         parameters_.dex_metadata_path,
+                         parameters_.compilation_reason,
+                         &error);
+        if (res != 0) {
+            LOG(ERROR) << "During preopt of " << parameters_.apk_path << " got result " << res
+                       << " error: " << error;
+        }
+        return res;
     }
 
     int RunPreopt() {
diff --git a/cmds/surfacereplayer/proto/Android.bp b/cmds/surfacereplayer/proto/Android.bp
index dae976e..23b54ee 100644
--- a/cmds/surfacereplayer/proto/Android.bp
+++ b/cmds/surfacereplayer/proto/Android.bp
@@ -4,8 +4,6 @@
     // all of the 'license_kinds' from "frameworks_native_license"
     // to get the below license kinds:
     //   SPDX-license-identifier-Apache-2.0
-    //   SPDX-license-identifier-MIT
-    //   SPDX-license-identifier-Unicode-DFS
     default_applicable_licenses: ["frameworks_native_license"],
 }
 
diff --git a/include/android/imagedecoder.h b/include/android/imagedecoder.h
index cac67d4..ee1eee2 100644
--- a/include/android/imagedecoder.h
+++ b/include/android/imagedecoder.h
@@ -367,6 +367,10 @@
  * the first frame (e.g. before calling {@link AImageDecoder_advanceFrame} or
  * after calling {@link AImageDecoder_rewind}).
  *
+ * It is strongly recommended to use setTargetSize only for downscaling, as it
+ * is often more efficient to scale-up when rendering than up-front due to
+ * reduced overall memory.
+ *
  * Available since API level 30.
  *
  * @param width Width of the output (prior to cropping).
diff --git a/include/input/Flags.h b/include/input/Flags.h
index 072dd18..b12a9ed 100644
--- a/include/input/Flags.h
+++ b/include/input/Flags.h
@@ -84,7 +84,7 @@
 template <typename F>
 constexpr std::optional<std::string_view> flag_name(F flag) {
     using U = std::underlying_type_t<F>;
-    auto idx = __builtin_ctzl(static_cast<U>(flag));
+    auto idx = static_cast<size_t>(__builtin_ctzl(static_cast<U>(flag)));
     return details::flag_names<F>[idx];
 }
 
@@ -280,4 +280,4 @@
 } // namespace flag_operators
 } // namespace android
 
-#endif // __UI_INPUT_FLAGS_H
\ No newline at end of file
+#endif // __UI_INPUT_FLAGS_H
diff --git a/libs/binder/ndk/include_cpp/android/binder_to_string.h b/libs/binder/ndk/include_cpp/android/binder_to_string.h
index 5842925..ef71a81 100644
--- a/libs/binder/ndk/include_cpp/android/binder_to_string.h
+++ b/libs/binder/ndk/include_cpp/android/binder_to_string.h
@@ -136,8 +136,7 @@
     template <typename _U>
     static std::enable_if_t<
 #ifdef HAS_NDK_INTERFACE
-            std::is_base_of_v<::ndk::ICInterface, _U> || std::is_same_v<::ndk::SpAIBinder, _U> ||
-                    std::is_same_v<::ndk::ScopedFileDescriptor, _U> ||
+            std::is_base_of_v<::ndk::ICInterface, _U> ||
                     std::is_same_v<::ndk::AParcelableHolder, _U>
 #else
             std::is_base_of_v<IInterface, _U> || std::is_same_v<IBinder, _U> ||
@@ -168,13 +167,19 @@
         return std::to_string(t);
     } else if constexpr (std::is_same_v<std::string, _T>) {
         return t;
+#ifdef HAS_NDK_INTERFACE
+    } else if constexpr (std::is_same_v<::ndk::SpAIBinder, _T>) {
+        return (t.get() == nullptr) ? "(null)" : "";
+    } else if constexpr (std::is_same_v<::ndk::ScopedFileDescriptor, _T>) {
+        return (t.get() == -1) ? "(null)" : "";
+#endif
 #ifdef HAS_STRING16
     } else if constexpr (std::is_same_v<String16, _T>) {
         std::stringstream out;
         out << t;
         return out.str();
 #endif
-    } else if constexpr (details::IsPointerLike<_T>::value) {
+    } else if constexpr (details::IsPointerLike<_T>::value || std::is_pointer_v<_T>) {
         if (!t) return "(null)";
         std::stringstream out;
         out << ToString(*t);
diff --git a/libs/gui/Android.bp b/libs/gui/Android.bp
index dc91fc6..ff059d7 100644
--- a/libs/gui/Android.bp
+++ b/libs/gui/Android.bp
@@ -149,6 +149,10 @@
         sampling: true,
         profile_file: "libgui/libgui.profdata",
     },
+
+    lto: {
+        thin: true,
+    },
 }
 
 // Used by media codec services exclusively as a static lib for
diff --git a/libs/renderengine/RenderEngine.cpp b/libs/renderengine/RenderEngine.cpp
index b2ad22d..79839c1 100644
--- a/libs/renderengine/RenderEngine.cpp
+++ b/libs/renderengine/RenderEngine.cpp
@@ -49,7 +49,8 @@
         case RenderEngineType::THREADED:
             ALOGD("Threaded RenderEngine with GLES Backend");
             return renderengine::threaded::RenderEngineThreaded::create(
-                    [args]() { return android::renderengine::gl::GLESRenderEngine::create(args); });
+                    [args]() { return android::renderengine::gl::GLESRenderEngine::create(args); },
+                    renderEngineType);
         case RenderEngineType::SKIA_GL:
             ALOGD("RenderEngine with SkiaGL Backend");
             return renderengine::skia::SkiaGLRenderEngine::create(args);
@@ -66,12 +67,14 @@
                             .setPrecacheToneMapperShaderOnly(args.precacheToneMapperShaderOnly)
                             .setSupportsBackgroundBlur(args.supportsBackgroundBlur)
                             .setContextPriority(args.contextPriority)
-                            .setRenderEngineType(RenderEngineType::SKIA_GL_THREADED)
+                            .setRenderEngineType(renderEngineType)
                             .build();
             ALOGD("Threaded RenderEngine with SkiaGL Backend");
-            return renderengine::threaded::RenderEngineThreaded::create([skiaArgs]() {
-                return android::renderengine::skia::SkiaGLRenderEngine::create(skiaArgs);
-            });
+            return renderengine::threaded::RenderEngineThreaded::create(
+                    [skiaArgs]() {
+                        return android::renderengine::skia::SkiaGLRenderEngine::create(skiaArgs);
+                    },
+                    renderEngineType);
         }
         case RenderEngineType::GLES:
         default:
diff --git a/libs/renderengine/gl/GLESRenderEngine.cpp b/libs/renderengine/gl/GLESRenderEngine.cpp
index 70ae0b2..397f038 100644
--- a/libs/renderengine/gl/GLESRenderEngine.cpp
+++ b/libs/renderengine/gl/GLESRenderEngine.cpp
@@ -379,7 +379,8 @@
 GLESRenderEngine::GLESRenderEngine(const RenderEngineCreationArgs& args, EGLDisplay display,
                                    EGLConfig config, EGLContext ctxt, EGLSurface stub,
                                    EGLContext protectedContext, EGLSurface protectedStub)
-      : mEGLDisplay(display),
+      : RenderEngine(args.renderEngineType),
+        mEGLDisplay(display),
         mEGLConfig(config),
         mEGLContext(ctxt),
         mStubSurface(stub),
diff --git a/libs/renderengine/include/renderengine/RenderEngine.h b/libs/renderengine/include/renderengine/RenderEngine.h
index 163a163..572d348 100644
--- a/libs/renderengine/include/renderengine/RenderEngine.h
+++ b/libs/renderengine/include/renderengine/RenderEngine.h
@@ -87,6 +87,10 @@
 
     static std::unique_ptr<RenderEngine> create(const RenderEngineCreationArgs& args);
 
+    RenderEngine() : RenderEngine(RenderEngineType::GLES) {}
+
+    RenderEngine(RenderEngineType type) : mRenderEngineType(type) {}
+
     virtual ~RenderEngine() = 0;
 
     // ----- BEGIN DEPRECATED INTERFACE -----
@@ -192,8 +196,14 @@
     // also supports background blur.  If false, no blur will be applied when drawing layers.
     virtual bool supportsBackgroundBlur() = 0;
 
+    // Returns the current type of RenderEngine instance that was created.
+    // TODO(b/180767535): This is only implemented to allow for backend-specific behavior, which
+    // we should not allow in general, so remove this.
+    RenderEngineType getRenderEngineType() const { return mRenderEngineType; }
+
 protected:
     friend class threaded::RenderEngineThreaded;
+    const RenderEngineType mRenderEngineType;
 };
 
 struct RenderEngineCreationArgs {
diff --git a/libs/renderengine/skia/SkiaGLRenderEngine.cpp b/libs/renderengine/skia/SkiaGLRenderEngine.cpp
index 6c6db46..cbb02a3 100644
--- a/libs/renderengine/skia/SkiaGLRenderEngine.cpp
+++ b/libs/renderengine/skia/SkiaGLRenderEngine.cpp
@@ -266,13 +266,13 @@
 SkiaGLRenderEngine::SkiaGLRenderEngine(const RenderEngineCreationArgs& args, EGLDisplay display,
                                        EGLContext ctxt, EGLSurface placeholder,
                                        EGLContext protectedContext, EGLSurface protectedPlaceholder)
-      : mEGLDisplay(display),
+      : SkiaRenderEngine(args.renderEngineType),
+        mEGLDisplay(display),
         mEGLContext(ctxt),
         mPlaceholderSurface(placeholder),
         mProtectedEGLContext(protectedContext),
         mProtectedPlaceholderSurface(protectedPlaceholder),
-        mUseColorManagement(args.useColorManagement),
-        mRenderEngineType(args.renderEngineType) {
+        mUseColorManagement(args.useColorManagement) {
     sk_sp<const GrGLInterface> glInterface(GrGLCreateNativeInterface());
     LOG_ALWAYS_FATAL_IF(!glInterface.get());
 
diff --git a/libs/renderengine/skia/SkiaGLRenderEngine.h b/libs/renderengine/skia/SkiaGLRenderEngine.h
index ad26206..1c3a633 100644
--- a/libs/renderengine/skia/SkiaGLRenderEngine.h
+++ b/libs/renderengine/skia/SkiaGLRenderEngine.h
@@ -130,10 +130,6 @@
     bool mInProtectedContext = false;
     // Object to capture commands send to Skia.
     std::unique_ptr<SkiaCapture> mCapture;
-
-    // Keep this information as a local variable to determine whether the access of the GL
-    // operations is working on the same threads.
-    const RenderEngineType mRenderEngineType = RenderEngineType::SKIA_GL;
 };
 
 } // namespace skia
diff --git a/libs/renderengine/skia/SkiaRenderEngine.h b/libs/renderengine/skia/SkiaRenderEngine.h
index 12b8586..79a1040 100644
--- a/libs/renderengine/skia/SkiaRenderEngine.h
+++ b/libs/renderengine/skia/SkiaRenderEngine.h
@@ -36,6 +36,7 @@
 class SkiaRenderEngine : public RenderEngine {
 public:
     static std::unique_ptr<SkiaRenderEngine> create(const RenderEngineCreationArgs& args);
+    SkiaRenderEngine(RenderEngineType type) : RenderEngine(type) {}
     ~SkiaRenderEngine() override {}
 
     virtual void primeCache() const override{};
diff --git a/libs/renderengine/tests/RenderEngineThreadedTest.cpp b/libs/renderengine/tests/RenderEngineThreadedTest.cpp
index 08b672a..63aa4c8 100644
--- a/libs/renderengine/tests/RenderEngineThreadedTest.cpp
+++ b/libs/renderengine/tests/RenderEngineThreadedTest.cpp
@@ -32,7 +32,8 @@
 
     void SetUp() override {
         mThreadedRE = renderengine::threaded::RenderEngineThreaded::create(
-                [this]() { return std::unique_ptr<renderengine::RenderEngine>(mRenderEngine); });
+                [this]() { return std::unique_ptr<renderengine::RenderEngine>(mRenderEngine); },
+                renderengine::RenderEngine::RenderEngineType::THREADED);
     }
 
     std::unique_ptr<renderengine::threaded::RenderEngineThreaded> mThreadedRE;
diff --git a/libs/renderengine/threaded/RenderEngineThreaded.cpp b/libs/renderengine/threaded/RenderEngineThreaded.cpp
index f448135..7c7d165 100644
--- a/libs/renderengine/threaded/RenderEngineThreaded.cpp
+++ b/libs/renderengine/threaded/RenderEngineThreaded.cpp
@@ -34,11 +34,13 @@
 namespace renderengine {
 namespace threaded {
 
-std::unique_ptr<RenderEngineThreaded> RenderEngineThreaded::create(CreateInstanceFactory factory) {
-    return std::make_unique<RenderEngineThreaded>(std::move(factory));
+std::unique_ptr<RenderEngineThreaded> RenderEngineThreaded::create(CreateInstanceFactory factory,
+                                                                   RenderEngineType type) {
+    return std::make_unique<RenderEngineThreaded>(std::move(factory), type);
 }
 
-RenderEngineThreaded::RenderEngineThreaded(CreateInstanceFactory factory) {
+RenderEngineThreaded::RenderEngineThreaded(CreateInstanceFactory factory, RenderEngineType type)
+      : RenderEngine(type) {
     ATRACE_CALL();
 
     std::lock_guard lockThread(mThreadMutex);
diff --git a/libs/renderengine/threaded/RenderEngineThreaded.h b/libs/renderengine/threaded/RenderEngineThreaded.h
index 8279cbc..d362e17 100644
--- a/libs/renderengine/threaded/RenderEngineThreaded.h
+++ b/libs/renderengine/threaded/RenderEngineThreaded.h
@@ -37,9 +37,10 @@
  */
 class RenderEngineThreaded : public RenderEngine {
 public:
-    static std::unique_ptr<RenderEngineThreaded> create(CreateInstanceFactory factory);
+    static std::unique_ptr<RenderEngineThreaded> create(CreateInstanceFactory factory,
+                                                        RenderEngineType type);
 
-    RenderEngineThreaded(CreateInstanceFactory factory);
+    RenderEngineThreaded(CreateInstanceFactory factory, RenderEngineType type);
     ~RenderEngineThreaded() override;
     void primeCache() const override;
 
diff --git a/libs/ui/Android.bp b/libs/ui/Android.bp
index de2b9a4..74d17ce 100644
--- a/libs/ui/Android.bp
+++ b/libs/ui/Android.bp
@@ -94,6 +94,7 @@
         "libarect",
         "libmath",
     ],
+
 }
 
 cc_library_shared {
diff --git a/libs/vr/libbroadcastring/Android.bp b/libs/vr/libbroadcastring/Android.bp
index 2eb2f9f..d4538f1 100644
--- a/libs/vr/libbroadcastring/Android.bp
+++ b/libs/vr/libbroadcastring/Android.bp
@@ -4,8 +4,6 @@
     // all of the 'license_kinds' from "frameworks_native_license"
     // to get the below license kinds:
     //   SPDX-license-identifier-Apache-2.0
-    //   SPDX-license-identifier-MIT
-    //   SPDX-license-identifier-Unicode-DFS
     default_applicable_licenses: ["frameworks_native_license"],
 }
 
diff --git a/libs/vr/libbufferhub/Android.bp b/libs/vr/libbufferhub/Android.bp
index 45bdd35..583ad1d 100644
--- a/libs/vr/libbufferhub/Android.bp
+++ b/libs/vr/libbufferhub/Android.bp
@@ -18,8 +18,6 @@
     // all of the 'license_kinds' from "frameworks_native_license"
     // to get the below license kinds:
     //   SPDX-license-identifier-Apache-2.0
-    //   SPDX-license-identifier-MIT
-    //   SPDX-license-identifier-Unicode-DFS
     default_applicable_licenses: ["frameworks_native_license"],
 }
 
diff --git a/libs/vr/libbufferhubqueue/Android.bp b/libs/vr/libbufferhubqueue/Android.bp
index f372bd7..0bda798 100644
--- a/libs/vr/libbufferhubqueue/Android.bp
+++ b/libs/vr/libbufferhubqueue/Android.bp
@@ -18,8 +18,6 @@
     // all of the 'license_kinds' from "frameworks_native_license"
     // to get the below license kinds:
     //   SPDX-license-identifier-Apache-2.0
-    //   SPDX-license-identifier-MIT
-    //   SPDX-license-identifier-Unicode-DFS
     default_applicable_licenses: ["frameworks_native_license"],
 }
 
diff --git a/libs/vr/libbufferhubqueue/benchmarks/Android.bp b/libs/vr/libbufferhubqueue/benchmarks/Android.bp
index fc1f376..e33e03b 100644
--- a/libs/vr/libbufferhubqueue/benchmarks/Android.bp
+++ b/libs/vr/libbufferhubqueue/benchmarks/Android.bp
@@ -5,8 +5,6 @@
     // all of the 'license_kinds' from "frameworks_native_license"
     // to get the below license kinds:
     //   SPDX-license-identifier-Apache-2.0
-    //   SPDX-license-identifier-MIT
-    //   SPDX-license-identifier-Unicode-DFS
     default_applicable_licenses: ["frameworks_native_license"],
 }
 
diff --git a/libs/vr/libbufferhubqueue/tests/Android.bp b/libs/vr/libbufferhubqueue/tests/Android.bp
index e883916..33a0d75 100644
--- a/libs/vr/libbufferhubqueue/tests/Android.bp
+++ b/libs/vr/libbufferhubqueue/tests/Android.bp
@@ -5,8 +5,6 @@
     // all of the 'license_kinds' from "frameworks_native_license"
     // to get the below license kinds:
     //   SPDX-license-identifier-Apache-2.0
-    //   SPDX-license-identifier-MIT
-    //   SPDX-license-identifier-Unicode-DFS
     default_applicable_licenses: ["frameworks_native_license"],
 }
 
diff --git a/libs/vr/libdisplay/Android.bp b/libs/vr/libdisplay/Android.bp
index 365a676..b0ed950 100644
--- a/libs/vr/libdisplay/Android.bp
+++ b/libs/vr/libdisplay/Android.bp
@@ -18,8 +18,6 @@
     // all of the 'license_kinds' from "frameworks_native_license"
     // to get the below license kinds:
     //   SPDX-license-identifier-Apache-2.0
-    //   SPDX-license-identifier-MIT
-    //   SPDX-license-identifier-Unicode-DFS
     default_applicable_licenses: ["frameworks_native_license"],
 }
 
diff --git a/libs/vr/libdvr/Android.bp b/libs/vr/libdvr/Android.bp
index 83c30d7..96023dd 100644
--- a/libs/vr/libdvr/Android.bp
+++ b/libs/vr/libdvr/Android.bp
@@ -19,8 +19,6 @@
     // all of the 'license_kinds' from "frameworks_native_license"
     // to get the below license kinds:
     //   SPDX-license-identifier-Apache-2.0
-    //   SPDX-license-identifier-MIT
-    //   SPDX-license-identifier-Unicode-DFS
     default_applicable_licenses: ["frameworks_native_license"],
 }
 
diff --git a/libs/vr/libdvr/tests/Android.bp b/libs/vr/libdvr/tests/Android.bp
index 4ed80a4..fe7feb8 100644
--- a/libs/vr/libdvr/tests/Android.bp
+++ b/libs/vr/libdvr/tests/Android.bp
@@ -18,8 +18,6 @@
     // all of the 'license_kinds' from "frameworks_native_license"
     // to get the below license kinds:
     //   SPDX-license-identifier-Apache-2.0
-    //   SPDX-license-identifier-MIT
-    //   SPDX-license-identifier-Unicode-DFS
     default_applicable_licenses: ["frameworks_native_license"],
 }
 
diff --git a/libs/vr/libdvrcommon/Android.bp b/libs/vr/libdvrcommon/Android.bp
index 9e1e516..fe4dfc7 100644
--- a/libs/vr/libdvrcommon/Android.bp
+++ b/libs/vr/libdvrcommon/Android.bp
@@ -18,8 +18,6 @@
     // all of the 'license_kinds' from "frameworks_native_license"
     // to get the below license kinds:
     //   SPDX-license-identifier-Apache-2.0
-    //   SPDX-license-identifier-MIT
-    //   SPDX-license-identifier-Unicode-DFS
     default_applicable_licenses: ["frameworks_native_license"],
 }
 
diff --git a/libs/vr/libpdx_default_transport/Android.bp b/libs/vr/libpdx_default_transport/Android.bp
index ea73d7a..8046857 100644
--- a/libs/vr/libpdx_default_transport/Android.bp
+++ b/libs/vr/libpdx_default_transport/Android.bp
@@ -4,8 +4,6 @@
     // all of the 'license_kinds' from "frameworks_native_license"
     // to get the below license kinds:
     //   SPDX-license-identifier-Apache-2.0
-    //   SPDX-license-identifier-MIT
-    //   SPDX-license-identifier-Unicode-DFS
     default_applicable_licenses: ["frameworks_native_license"],
 }
 
diff --git a/libs/vr/libpdx_uds/Android.bp b/libs/vr/libpdx_uds/Android.bp
index 532d1a7..216ca9f 100644
--- a/libs/vr/libpdx_uds/Android.bp
+++ b/libs/vr/libpdx_uds/Android.bp
@@ -4,8 +4,6 @@
     // all of the 'license_kinds' from "frameworks_native_license"
     // to get the below license kinds:
     //   SPDX-license-identifier-Apache-2.0
-    //   SPDX-license-identifier-MIT
-    //   SPDX-license-identifier-Unicode-DFS
     default_applicable_licenses: ["frameworks_native_license"],
 }
 
diff --git a/libs/vr/libperformance/Android.bp b/libs/vr/libperformance/Android.bp
index 5beee35..38bf4ea 100644
--- a/libs/vr/libperformance/Android.bp
+++ b/libs/vr/libperformance/Android.bp
@@ -18,8 +18,6 @@
     // all of the 'license_kinds' from "frameworks_native_license"
     // to get the below license kinds:
     //   SPDX-license-identifier-Apache-2.0
-    //   SPDX-license-identifier-MIT
-    //   SPDX-license-identifier-Unicode-DFS
     default_applicable_licenses: ["frameworks_native_license"],
 }
 
diff --git a/libs/vr/libvrflinger/Android.bp b/libs/vr/libvrflinger/Android.bp
index 8aca9a5..bf848af 100644
--- a/libs/vr/libvrflinger/Android.bp
+++ b/libs/vr/libvrflinger/Android.bp
@@ -18,8 +18,6 @@
     // all of the 'license_kinds' from "frameworks_native_license"
     // to get the below license kinds:
     //   SPDX-license-identifier-Apache-2.0
-    //   SPDX-license-identifier-MIT
-    //   SPDX-license-identifier-Unicode-DFS
     default_applicable_licenses: ["frameworks_native_license"],
 }
 
diff --git a/libs/vr/libvrflinger/tests/Android.bp b/libs/vr/libvrflinger/tests/Android.bp
index dafd354..095f556 100644
--- a/libs/vr/libvrflinger/tests/Android.bp
+++ b/libs/vr/libvrflinger/tests/Android.bp
@@ -4,8 +4,6 @@
     // all of the 'license_kinds' from "frameworks_native_license"
     // to get the below license kinds:
     //   SPDX-license-identifier-Apache-2.0
-    //   SPDX-license-identifier-MIT
-    //   SPDX-license-identifier-Unicode-DFS
     default_applicable_licenses: ["frameworks_native_license"],
 }
 
diff --git a/libs/vr/libvrsensor/Android.bp b/libs/vr/libvrsensor/Android.bp
index 8f566a0..40a5099 100644
--- a/libs/vr/libvrsensor/Android.bp
+++ b/libs/vr/libvrsensor/Android.bp
@@ -18,8 +18,6 @@
     // all of the 'license_kinds' from "frameworks_native_license"
     // to get the below license kinds:
     //   SPDX-license-identifier-Apache-2.0
-    //   SPDX-license-identifier-MIT
-    //   SPDX-license-identifier-Unicode-DFS
     default_applicable_licenses: ["frameworks_native_license"],
 }
 
diff --git a/opengl/tests/configdump/Android.bp b/opengl/tests/configdump/Android.bp
index ffb0c1f..1bb5983 100644
--- a/opengl/tests/configdump/Android.bp
+++ b/opengl/tests/configdump/Android.bp
@@ -4,8 +4,6 @@
     // all of the 'license_kinds' from "frameworks_native_license"
     // to get the below license kinds:
     //   SPDX-license-identifier-Apache-2.0
-    //   SPDX-license-identifier-MIT
-    //   SPDX-license-identifier-Unicode-DFS
     default_applicable_licenses: ["frameworks_native_license"],
 }
 
diff --git a/opengl/tests/filter/Android.bp b/opengl/tests/filter/Android.bp
index 3b92b37..b93576f 100644
--- a/opengl/tests/filter/Android.bp
+++ b/opengl/tests/filter/Android.bp
@@ -4,8 +4,6 @@
     // all of the 'license_kinds' from "frameworks_native_license"
     // to get the below license kinds:
     //   SPDX-license-identifier-Apache-2.0
-    //   SPDX-license-identifier-MIT
-    //   SPDX-license-identifier-Unicode-DFS
     default_applicable_licenses: ["frameworks_native_license"],
 }
 
diff --git a/opengl/tests/gl_basic/Android.bp b/opengl/tests/gl_basic/Android.bp
index f777401..f55cd0d 100644
--- a/opengl/tests/gl_basic/Android.bp
+++ b/opengl/tests/gl_basic/Android.bp
@@ -4,8 +4,6 @@
     // all of the 'license_kinds' from "frameworks_native_license"
     // to get the below license kinds:
     //   SPDX-license-identifier-Apache-2.0
-    //   SPDX-license-identifier-MIT
-    //   SPDX-license-identifier-Unicode-DFS
     default_applicable_licenses: ["frameworks_native_license"],
 }
 
diff --git a/opengl/tests/tritex/Android.bp b/opengl/tests/tritex/Android.bp
index 759582c..87da93f 100644
--- a/opengl/tests/tritex/Android.bp
+++ b/opengl/tests/tritex/Android.bp
@@ -4,8 +4,6 @@
     // all of the 'license_kinds' from "frameworks_native_license"
     // to get the below license kinds:
     //   SPDX-license-identifier-Apache-2.0
-    //   SPDX-license-identifier-MIT
-    //   SPDX-license-identifier-Unicode-DFS
     default_applicable_licenses: ["frameworks_native_license"],
 }
 
diff --git a/services/inputflinger/dispatcher/FocusResolver.cpp b/services/inputflinger/dispatcher/FocusResolver.cpp
index ee6b38b..2db8c13 100644
--- a/services/inputflinger/dispatcher/FocusResolver.cpp
+++ b/services/inputflinger/dispatcher/FocusResolver.cpp
@@ -40,110 +40,102 @@
     return it != mFocusedWindowTokenByDisplay.end() ? it->second.second : nullptr;
 }
 
-std::optional<FocusRequest> FocusResolver::getPendingRequest(int32_t displayId) {
-    auto it = mPendingFocusRequests.find(displayId);
-    return it != mPendingFocusRequests.end() ? std::make_optional<>(it->second) : std::nullopt;
+std::optional<FocusRequest> FocusResolver::getFocusRequest(int32_t displayId) {
+    auto it = mFocusRequestByDisplay.find(displayId);
+    return it != mFocusRequestByDisplay.end() ? std::make_optional<>(it->second) : std::nullopt;
 }
 
+/**
+ * 'setInputWindows' is called when the window properties change. Here we will check whether the
+ * currently focused window can remain focused. If the currently focused window remains eligible
+ * for focus ('isTokenFocusable' returns OK), then we will continue to grant it focus otherwise
+ * we will check if the previous focus request is eligible to receive focus.
+ */
 std::optional<FocusResolver::FocusChanges> FocusResolver::setInputWindows(
         int32_t displayId, const std::vector<sp<InputWindowHandle>>& windows) {
-    // If the current focused window becomes unfocusable, remove focus.
-    sp<IBinder> currentFocus = getFocusedWindowToken(displayId);
+    std::string removeFocusReason;
+
+    // Check if the currently focused window is still focusable.
+    const sp<IBinder> currentFocus = getFocusedWindowToken(displayId);
     if (currentFocus) {
-        FocusResult result = isTokenFocusable(currentFocus, windows);
-        if (result != FocusResult::OK) {
-            return updateFocusedWindow(displayId, NamedEnum::string(result), nullptr);
+        Focusability result = isTokenFocusable(currentFocus, windows);
+        if (result == Focusability::OK) {
+            return std::nullopt;
+        }
+        removeFocusReason = NamedEnum::string(result);
+    }
+
+    // We don't have a focused window or the currently focused window is no longer focusable. Check
+    // to see if we can grant focus to the window that previously requested focus.
+    const std::optional<FocusRequest> request = getFocusRequest(displayId);
+    if (request) {
+        sp<IBinder> requestedFocus = request->token;
+        const Focusability result = isTokenFocusable(requestedFocus, windows);
+        const Focusability previousResult = mLastFocusResultByDisplay[displayId];
+        mLastFocusResultByDisplay[displayId] = result;
+        if (result == Focusability::OK) {
+            return updateFocusedWindow(displayId,
+                                       "Window became focusable. Previous reason: " +
+                                               NamedEnum::string(previousResult),
+                                       requestedFocus, request->windowName);
         }
     }
 
-    // Check if any pending focus requests can be resolved.
-    std::optional<FocusRequest> pendingRequest = getPendingRequest(displayId);
-    if (!pendingRequest) {
-        return std::nullopt;
-    }
-
-    sp<IBinder> requestedFocus = pendingRequest->token;
-    std::string windowName = pendingRequest->windowName;
-    if (currentFocus == requestedFocus) {
-        ALOGD_IF(DEBUG_FOCUS,
-                 "setFocusedWindow %s on display %" PRId32 " ignored, reason: already focused",
-                 windowName.c_str(), displayId);
-        mPendingFocusRequests.erase(displayId);
-        return std::nullopt;
-    }
-
-    FocusResult result = isTokenFocusable(requestedFocus, windows);
-    // If the window from the pending request is now visible, provide it focus.
-    if (result == FocusResult::OK) {
-        mPendingFocusRequests.erase(displayId);
-        return updateFocusedWindow(displayId, "Window became visible", requestedFocus, windowName);
-    }
-
-    if (result != FocusResult::NOT_VISIBLE) {
-        // Drop the request if we are unable to change the focus for a reason other than visibility.
-        ALOGW("Focus request %s on display %" PRId32 " ignored, reason:%s", windowName.c_str(),
-              displayId, NamedEnum::string(result).c_str());
-        mPendingFocusRequests.erase(displayId);
-    }
-    return std::nullopt;
+    // Focused window is no longer focusable and we don't have a suitable focus request to grant.
+    // Remove focus if needed.
+    return updateFocusedWindow(displayId, removeFocusReason, nullptr);
 }
 
 std::optional<FocusResolver::FocusChanges> FocusResolver::setFocusedWindow(
         const FocusRequest& request, const std::vector<sp<InputWindowHandle>>& windows) {
     const int32_t displayId = request.displayId;
     const sp<IBinder> currentFocus = getFocusedWindowToken(displayId);
-    if (request.focusedToken && currentFocus != request.focusedToken) {
-        ALOGW("setFocusedWindow %s on display %" PRId32
-              " ignored, reason: focusedToken  %s is not focused",
-              request.windowName.c_str(), displayId, request.focusedWindowName.c_str());
-        return std::nullopt;
-    }
-
-    std::optional<FocusRequest> pendingRequest = getPendingRequest(displayId);
-    if (pendingRequest) {
-        ALOGW("Pending focus request %s on display %" PRId32
-              " ignored, reason:replaced by new request",
-              pendingRequest->windowName.c_str(), displayId);
-
-        // clear any pending focus requests
-        mPendingFocusRequests.erase(displayId);
-    }
-
     if (currentFocus == request.token) {
         ALOGD_IF(DEBUG_FOCUS,
-                 "setFocusedWindow %s on display %" PRId32 " ignored, reason:already focused",
+                 "setFocusedWindow %s on display %" PRId32 " ignored, reason: already focused",
                  request.windowName.c_str(), displayId);
         return std::nullopt;
     }
 
-    FocusResult result = isTokenFocusable(request.token, windows);
-    if (result == FocusResult::OK) {
-        std::string reason =
-                (request.focusedToken) ? "setFocusedWindow with focus check" : "setFocusedWindow";
-        return updateFocusedWindow(displayId, reason, request.token, request.windowName);
-    }
-
-    if (result == FocusResult::NOT_VISIBLE) {
-        // The requested window is not currently visible. Wait for the window to become visible
-        // and then provide it focus. This is to handle situations where a user action triggers
-        // a new window to appear. We want to be able to queue any key events after the user
-        // action and deliver it to the newly focused window. In order for this to happen, we
-        // take focus from the currently focused window so key events can be queued.
-        ALOGD_IF(DEBUG_FOCUS,
-                 "setFocusedWindow %s on display %" PRId32
-                 " pending, reason: window is not visible",
-                 request.windowName.c_str(), displayId);
-        mPendingFocusRequests[displayId] = request;
-        return updateFocusedWindow(displayId, "Waiting for window to be visible", nullptr);
-    } else {
-        ALOGW("setFocusedWindow %s on display %" PRId32 " ignored, reason:%s",
+    // Handle conditional focus requests, i.e. requests that have a focused token. These requests
+    // are not persistent. If the window is no longer focusable, we expect focus to go back to the
+    // previously focused window.
+    if (request.focusedToken) {
+        if (currentFocus != request.focusedToken) {
+            ALOGW("setFocusedWindow %s on display %" PRId32
+                  " ignored, reason: focusedToken %s is not focused",
+                  request.windowName.c_str(), displayId, request.focusedWindowName.c_str());
+            return std::nullopt;
+        }
+        Focusability result = isTokenFocusable(request.token, windows);
+        if (result == Focusability::OK) {
+            return updateFocusedWindow(displayId, "setFocusedWindow with focus check",
+                                       request.token, request.windowName);
+        }
+        ALOGW("setFocusedWindow %s on display %" PRId32 " ignored, reason: %s",
               request.windowName.c_str(), displayId, NamedEnum::string(result).c_str());
+        return std::nullopt;
     }
 
-    return std::nullopt;
+    Focusability result = isTokenFocusable(request.token, windows);
+    // Update focus request. The focus resolver will always try to handle this request if there is
+    // no focused window on the display.
+    mFocusRequestByDisplay[displayId] = request;
+    mLastFocusResultByDisplay[displayId] = result;
+
+    if (result == Focusability::OK) {
+        return updateFocusedWindow(displayId, "setFocusedWindow", request.token,
+                                   request.windowName);
+    }
+
+    // The requested window is not currently focusable. Wait for the window to become focusable
+    // but remove focus from the current window so that input events can go into a pending queue
+    // and be sent to the window when it becomes focused.
+    return updateFocusedWindow(displayId, "Waiting for window because " + NamedEnum::string(result),
+                               nullptr);
 }
 
-FocusResolver::FocusResult FocusResolver::isTokenFocusable(
+FocusResolver::Focusability FocusResolver::isTokenFocusable(
         const sp<IBinder>& token, const std::vector<sp<InputWindowHandle>>& windows) {
     bool allWindowsAreFocusable = true;
     bool visibleWindowFound = false;
@@ -165,16 +157,16 @@
     }
 
     if (!windowFound) {
-        return FocusResult::NO_WINDOW;
+        return Focusability::NO_WINDOW;
     }
     if (!allWindowsAreFocusable) {
-        return FocusResult::NOT_FOCUSABLE;
+        return Focusability::NOT_FOCUSABLE;
     }
     if (!visibleWindowFound) {
-        return FocusResult::NOT_VISIBLE;
+        return Focusability::NOT_VISIBLE;
     }
 
-    return FocusResult::OK;
+    return Focusability::OK;
 }
 
 std::optional<FocusResolver::FocusChanges> FocusResolver::updateFocusedWindow(
@@ -209,15 +201,17 @@
 
 std::string FocusResolver::dump() const {
     std::string dump = dumpFocusedWindows();
-
-    if (mPendingFocusRequests.empty()) {
-        return dump + INDENT "PendingFocusRequests: <none>\n";
+    if (mFocusRequestByDisplay.empty()) {
+        return dump + INDENT "FocusRequests: <none>\n";
     }
 
-    dump += INDENT "PendingFocusRequests:\n";
-    for (const auto& [displayId, request] : mPendingFocusRequests) {
-        dump += base::StringPrintf(INDENT2 "displayId=%" PRId32 ", name='%s'\n", displayId,
-                                   request.windowName.c_str());
+    dump += INDENT "FocusRequests:\n";
+    for (const auto& [displayId, request] : mFocusRequestByDisplay) {
+        auto it = mLastFocusResultByDisplay.find(displayId);
+        std::string result =
+                it != mLastFocusResultByDisplay.end() ? NamedEnum::string(it->second) : "";
+        dump += base::StringPrintf(INDENT2 "displayId=%" PRId32 ", name='%s' result='%s'\n",
+                                   displayId, request.windowName.c_str(), result.c_str());
     }
     return dump;
 }
diff --git a/services/inputflinger/dispatcher/FocusResolver.h b/services/inputflinger/dispatcher/FocusResolver.h
index e067ad9..dc5eeeb 100644
--- a/services/inputflinger/dispatcher/FocusResolver.h
+++ b/services/inputflinger/dispatcher/FocusResolver.h
@@ -34,11 +34,18 @@
 //   is visible with the same token and all window handles with the same token are focusable.
 //   See FocusResolver::isTokenFocusable
 //
-//   Focus request - Request will be granted if the window is focusable. If the window is not
-//   visible, then the request is kept in a pending state and granted when it becomes visible.
-//   If window becomes not focusable, or another request comes in, the pending request is dropped.
+//   Focus request - Request will be granted if the window is focusable. If it's not
+//   focusable, then the request is persisted and granted when it becomes focusable. The currently
+//   focused window will lose focus and any pending keys will be added to a queue so it can be sent
+//   to the window when it gets focus.
+//
+//   Condition focus request - Request with a focus token specified. Request will be granted if the
+//   window is focusable and the focus token is the currently focused. Otherwise, the request is
+//   dropped. Conditional focus requests are not persisted. The window will lose focus and go back
+//   to the focus token if it becomes not focusable.
 //
 //   Window handle updates - Focus is lost when the currently focused window becomes not focusable.
+//   If the previous focus request is focusable, then we will try to grant that window focus.
 class FocusResolver {
 public:
     // Returns the focused window token on the specified display.
@@ -61,7 +68,7 @@
     std::string dump() const;
 
 private:
-    enum class FocusResult {
+    enum class Focusability {
         OK,
         NO_WINDOW,
         NOT_FOCUSABLE,
@@ -77,8 +84,8 @@
     // we expect the focusability of the windows to match since its hard to reason why one window
     // can receive focus events and the other cannot when both are backed by the same input channel.
     //
-    static FocusResult isTokenFocusable(const sp<IBinder>& token,
-                                        const std::vector<sp<InputWindowHandle>>& windows);
+    static Focusability isTokenFocusable(const sp<IBinder>& token,
+                                         const std::vector<sp<InputWindowHandle>>& windows);
 
     // Focus tracking for keys, trackball, etc. A window token can be associated with one or
     // more InputWindowHandles. If a window is mirrored, the window and its mirror will share
@@ -87,15 +94,18 @@
     typedef std::pair<std::string /* name */, sp<IBinder>> NamedToken;
     std::unordered_map<int32_t /* displayId */, NamedToken> mFocusedWindowTokenByDisplay;
 
-    // This map will store a single pending focus request per display that cannot be currently
-    // processed. This can happen if the window requested to be focused is not currently visible.
-    // Such a window might become visible later, and these requests would be processed at that time.
-    std::unordered_map<int32_t /* displayId */, FocusRequest> mPendingFocusRequests;
+    // This map will store the focus request per display. When the input window handles are updated,
+    // the current request will be checked to see if it can be processed at that time.
+    std::unordered_map<int32_t /* displayId */, FocusRequest> mFocusRequestByDisplay;
+
+    // Last reason for not granting a focus request. This is used to add more debug information
+    // in the event logs.
+    std::unordered_map<int32_t /* displayId */, Focusability> mLastFocusResultByDisplay;
 
     std::optional<FocusResolver::FocusChanges> updateFocusedWindow(
             int32_t displayId, const std::string& reason, const sp<IBinder>& token,
             const std::string& tokenName = "");
-    std::optional<FocusRequest> getPendingRequest(int32_t displayId);
+    std::optional<FocusRequest> getFocusRequest(int32_t displayId);
 };
 
 } // namespace android::inputdispatcher
\ No newline at end of file
diff --git a/services/inputflinger/dispatcher/InputDispatcher.cpp b/services/inputflinger/dispatcher/InputDispatcher.cpp
index d0a5c09..465e8be 100644
--- a/services/inputflinger/dispatcher/InputDispatcher.cpp
+++ b/services/inputflinger/dispatcher/InputDispatcher.cpp
@@ -1439,7 +1439,7 @@
     ALOGD("notifySensorEvent eventTime=%" PRId64 ", hwTimestamp=%" PRId64 ", deviceId=%d, "
           "source=0x%x, sensorType=%s",
           entry->eventTime, entry->hwTimestamp, entry->deviceId, entry->source,
-          NamedEnum::string(sensorType).c_str());
+          NamedEnum::string(entry->sensorType).c_str());
 #endif
     std::unique_ptr<CommandEntry> commandEntry =
             std::make_unique<CommandEntry>(&InputDispatcher::doNotifySensorLockedInterruptible);
diff --git a/services/inputflinger/reader/EventHub.cpp b/services/inputflinger/reader/EventHub.cpp
index d0a9ca7..1a7fcd5 100644
--- a/services/inputflinger/reader/EventHub.cpp
+++ b/services/inputflinger/reader/EventHub.cpp
@@ -1771,7 +1771,17 @@
     }
 }
 
-status_t EventHub::openDeviceLocked(const std::string& devicePath) {
+void EventHub::openDeviceLocked(const std::string& devicePath) {
+    // If an input device happens to register around the time when EventHub's constructor runs, it
+    // is possible that the same input event node (for example, /dev/input/event3) will be noticed
+    // in both 'inotify' callback and also in the 'scanDirLocked' pass. To prevent duplicate devices
+    // from getting registered, ensure that this path is not already covered by an existing device.
+    for (const auto& [deviceId, device] : mDevices) {
+        if (device->path == devicePath) {
+            return; // device was already registered
+        }
+    }
+
     char buffer[80];
 
     ALOGV("Opening device: %s", devicePath.c_str());
@@ -1779,7 +1789,7 @@
     int fd = open(devicePath.c_str(), O_RDWR | O_CLOEXEC | O_NONBLOCK);
     if (fd < 0) {
         ALOGE("could not open %s, %s\n", devicePath.c_str(), strerror(errno));
-        return -1;
+        return;
     }
 
     InputDeviceIdentifier identifier;
@@ -1798,7 +1808,7 @@
         if (identifier.name == item) {
             ALOGI("ignoring event id %s driver %s\n", devicePath.c_str(), item.c_str());
             close(fd);
-            return -1;
+            return;
         }
     }
 
@@ -1807,7 +1817,7 @@
     if (ioctl(fd, EVIOCGVERSION, &driverVersion)) {
         ALOGE("could not get driver version for %s, %s\n", devicePath.c_str(), strerror(errno));
         close(fd);
-        return -1;
+        return;
     }
 
     // Get device identifier.
@@ -1815,7 +1825,7 @@
     if (ioctl(fd, EVIOCGID, &inputId)) {
         ALOGE("could not get device input id for %s, %s\n", devicePath.c_str(), strerror(errno));
         close(fd);
-        return -1;
+        return;
     }
     identifier.bus = inputId.bustype;
     identifier.product = inputId.product;
@@ -2013,7 +2023,7 @@
     if (device->classes == Flags<InputDeviceClass>(0)) {
         ALOGV("Dropping device: id=%d, path='%s', name='%s'", deviceId, devicePath.c_str(),
               device->identifier.name.c_str());
-        return -1;
+        return;
     }
 
     // Classify InputDeviceClass::BATTERY.
@@ -2043,7 +2053,7 @@
     }
 
     if (registerDeviceForEpollLocked(*device) != OK) {
-        return -1;
+        return;
     }
 
     device->configureFd();
@@ -2056,7 +2066,6 @@
           toString(mBuiltInKeyboardId == deviceId));
 
     addDeviceLocked(std::move(device));
-    return OK;
 }
 
 void EventHub::openVideoDeviceLocked(const std::string& devicePath) {
diff --git a/services/inputflinger/reader/include/EventHub.h b/services/inputflinger/reader/include/EventHub.h
index 4059729..5e5f85e 100644
--- a/services/inputflinger/reader/include/EventHub.h
+++ b/services/inputflinger/reader/include/EventHub.h
@@ -567,7 +567,10 @@
         bool configureLightsLocked();
     };
 
-    status_t openDeviceLocked(const std::string& devicePath);
+    /**
+     * Create a new device for the provided path.
+     */
+    void openDeviceLocked(const std::string& devicePath);
     void openVideoDeviceLocked(const std::string& devicePath);
     /**
      * Try to associate a video device with an input device. If the association succeeds,
diff --git a/services/inputflinger/tests/FocusResolver_test.cpp b/services/inputflinger/tests/FocusResolver_test.cpp
index ef3dd65..17efb5b 100644
--- a/services/inputflinger/tests/FocusResolver_test.cpp
+++ b/services/inputflinger/tests/FocusResolver_test.cpp
@@ -18,6 +18,12 @@
 
 #include "../FocusResolver.h"
 
+#define ASSERT_FOCUS_CHANGE(_changes, _oldFocus, _newFocus) \
+    {                                                       \
+        ASSERT_EQ(_oldFocus, _changes->oldFocus);           \
+        ASSERT_EQ(_newFocus, _changes->newFocus);           \
+    }
+
 // atest inputflinger_tests:FocusResolverTest
 
 namespace android::inputdispatcher {
@@ -56,8 +62,7 @@
     FocusResolver focusResolver;
     std::optional<FocusResolver::FocusChanges> changes =
             focusResolver.setFocusedWindow(request, windows);
-    ASSERT_EQ(nullptr, changes->oldFocus);
-    ASSERT_EQ(focusableWindowToken, changes->newFocus);
+    ASSERT_FOCUS_CHANGE(changes, /*from*/ nullptr, /*to*/ focusableWindowToken);
     ASSERT_EQ(request.displayId, changes->displayId);
 
     // invisible window cannot get focused
@@ -65,6 +70,7 @@
     changes = focusResolver.setFocusedWindow(request, windows);
     ASSERT_EQ(focusableWindowToken, changes->oldFocus);
     ASSERT_EQ(nullptr, changes->newFocus);
+    ASSERT_FOCUS_CHANGE(changes, /*from*/ focusableWindowToken, /*to*/ nullptr);
 
     // unfocusableWindowToken window cannot get focused
     request.token = unfocusableWindowToken;
@@ -99,19 +105,17 @@
     FocusResolver focusResolver;
     std::optional<FocusResolver::FocusChanges> changes =
             focusResolver.setFocusedWindow(request, windows);
-    ASSERT_EQ(nullptr, changes->oldFocus);
-    ASSERT_EQ(focusableWindowToken, changes->newFocus);
+    ASSERT_FOCUS_CHANGE(changes, /*from*/ nullptr, /*to*/ focusableWindowToken);
 
     // mirrored window with one visible window can get focused
     request.token = invisibleWindowToken;
     changes = focusResolver.setFocusedWindow(request, windows);
-    ASSERT_EQ(focusableWindowToken, changes->oldFocus);
-    ASSERT_EQ(invisibleWindowToken, changes->newFocus);
+    ASSERT_FOCUS_CHANGE(changes, /*from*/ focusableWindowToken, /*to*/ invisibleWindowToken);
 
     // mirrored window with one or more unfocusable window cannot get focused
     request.token = unfocusableWindowToken;
     changes = focusResolver.setFocusedWindow(request, windows);
-    ASSERT_FALSE(changes);
+    ASSERT_FOCUS_CHANGE(changes, /*from*/ invisibleWindowToken, /*to*/ nullptr);
 }
 
 TEST(FocusResolverTest, SetInputWindows) {
@@ -130,11 +134,10 @@
             focusResolver.setFocusedWindow(request, windows);
     ASSERT_EQ(focusableWindowToken, changes->newFocus);
 
-    // Window visibility changes and the window loses focused
+    // Window visibility changes and the window loses focus
     window->setVisible(false);
     changes = focusResolver.setInputWindows(request.displayId, windows);
-    ASSERT_EQ(nullptr, changes->newFocus);
-    ASSERT_EQ(focusableWindowToken, changes->oldFocus);
+    ASSERT_FOCUS_CHANGE(changes, /*from*/ focusableWindowToken, /*to*/ nullptr);
 }
 
 TEST(FocusResolverTest, FocusRequestsCanBePending) {
@@ -158,8 +161,100 @@
     // Window visibility changes and the window gets focused
     invisibleWindow->setVisible(true);
     changes = focusResolver.setInputWindows(request.displayId, windows);
-    ASSERT_EQ(nullptr, changes->oldFocus);
-    ASSERT_EQ(invisibleWindowToken, changes->newFocus);
+    ASSERT_FOCUS_CHANGE(changes, /*from*/ nullptr, /*to*/ invisibleWindowToken);
+}
+
+TEST(FocusResolverTest, FocusRequestsArePersistent) {
+    sp<IBinder> windowToken = new BBinder();
+    std::vector<sp<InputWindowHandle>> windows;
+
+    sp<FakeWindowHandle> window = new FakeWindowHandle("Test Window", windowToken,
+                                                       false /* focusable */, true /* visible */);
+    windows.push_back(window);
+
+    // non-focusable window cannot get focused
+    FocusRequest request;
+    request.displayId = 42;
+    request.token = windowToken;
+    FocusResolver focusResolver;
+    std::optional<FocusResolver::FocusChanges> changes =
+            focusResolver.setFocusedWindow(request, windows);
+    ASSERT_FALSE(changes);
+
+    // Focusability changes and the window gets focused
+    window->setFocusable(true);
+    changes = focusResolver.setInputWindows(request.displayId, windows);
+    ASSERT_FOCUS_CHANGE(changes, /*from*/ nullptr, /*to*/ windowToken);
+
+    // Visibility changes and the window loses focus
+    window->setVisible(false);
+    changes = focusResolver.setInputWindows(request.displayId, windows);
+    ASSERT_FOCUS_CHANGE(changes, /*from*/ windowToken, /*to*/ nullptr);
+
+    // Visibility changes and the window gets focused
+    window->setVisible(true);
+    changes = focusResolver.setInputWindows(request.displayId, windows);
+    ASSERT_FOCUS_CHANGE(changes, /*from*/ nullptr, /*to*/ windowToken);
+
+    // Window is gone and the window loses focus
+    changes = focusResolver.setInputWindows(request.displayId, {});
+    ASSERT_FOCUS_CHANGE(changes, /*from*/ windowToken, /*to*/ nullptr);
+
+    // Window returns and the window gains focus
+    changes = focusResolver.setInputWindows(request.displayId, windows);
+    ASSERT_FOCUS_CHANGE(changes, /*from*/ nullptr, /*to*/ windowToken);
+}
+
+TEST(FocusResolverTest, ConditionalFocusRequestsAreNotPersistent) {
+    sp<IBinder> hostWindowToken = new BBinder();
+    std::vector<sp<InputWindowHandle>> windows;
+
+    sp<FakeWindowHandle> hostWindow =
+            new FakeWindowHandle("Host Window", hostWindowToken, true /* focusable */,
+                                 true /* visible */);
+    windows.push_back(hostWindow);
+    sp<IBinder> embeddedWindowToken = new BBinder();
+    sp<FakeWindowHandle> embeddedWindow =
+            new FakeWindowHandle("Embedded Window", embeddedWindowToken, true /* focusable */,
+                                 true /* visible */);
+    windows.push_back(embeddedWindow);
+
+    FocusRequest request;
+    request.displayId = 42;
+    request.token = hostWindowToken;
+    FocusResolver focusResolver;
+    std::optional<FocusResolver::FocusChanges> changes =
+            focusResolver.setFocusedWindow(request, windows);
+    ASSERT_FOCUS_CHANGE(changes, /*from*/ nullptr, /*to*/ hostWindowToken);
+
+    request.focusedToken = hostWindow->getToken();
+    request.token = embeddedWindowToken;
+    changes = focusResolver.setFocusedWindow(request, windows);
+    ASSERT_FOCUS_CHANGE(changes, /*from*/ hostWindowToken, /*to*/ embeddedWindowToken);
+
+    embeddedWindow->setFocusable(false);
+    changes = focusResolver.setInputWindows(request.displayId, windows);
+    // The embedded window is no longer focusable, provide focus back to the original focused
+    // window.
+    ASSERT_FOCUS_CHANGE(changes, /*from*/ embeddedWindowToken, /*to*/ hostWindowToken);
+
+    embeddedWindow->setFocusable(true);
+    changes = focusResolver.setInputWindows(request.displayId, windows);
+    // The embedded window is focusable again, but we it cannot gain focus unless there is another
+    // focus request.
+    ASSERT_FALSE(changes);
+
+    embeddedWindow->setVisible(false);
+    changes = focusResolver.setFocusedWindow(request, windows);
+    // If the embedded window is not visible/focusable, then we do not grant it focus and the
+    // request is dropped.
+    ASSERT_FALSE(changes);
+
+    embeddedWindow->setVisible(true);
+    changes = focusResolver.setInputWindows(request.displayId, windows);
+    // If the embedded window becomes visble/focusable, nothing changes since the request has been
+    // dropped.
+    ASSERT_FALSE(changes);
 }
 
 } // namespace android::inputdispatcher
diff --git a/services/inputflinger/tests/InputDispatcher_test.cpp b/services/inputflinger/tests/InputDispatcher_test.cpp
index 118a24f..e027c5e 100644
--- a/services/inputflinger/tests/InputDispatcher_test.cpp
+++ b/services/inputflinger/tests/InputDispatcher_test.cpp
@@ -908,6 +908,8 @@
         mInfo.addTouchableRegion(frame);
     }
 
+    void addFlags(Flags<InputWindowInfo::Flag> flags) { mInfo.flags |= flags; }
+
     void setFlags(Flags<InputWindowInfo::Flag> flags) { mInfo.flags = flags; }
 
     void setInputFeatures(InputWindowInfo::Feature features) { mInfo.inputFeatures = features; }
@@ -4318,6 +4320,17 @@
     mTouchWindow->assertNoEvents();
 }
 
+TEST_F(InputDispatcherUntrustedTouchesTest,
+       WindowWithBlockUntrustedOcclusionMode_DoesNotReceiveTouch) {
+    const sp<FakeWindowHandle>& w =
+            getOccludingWindow(APP_B_UID, "B", TouchOcclusionMode::BLOCK_UNTRUSTED);
+    mDispatcher->setInputWindows({{ADISPLAY_ID_DEFAULT, {w, mTouchWindow}}});
+
+    touch();
+
+    w->assertNoEvents();
+}
+
 TEST_F(InputDispatcherUntrustedTouchesTest, WindowWithAllowOcclusionMode_AllowsTouch) {
     const sp<FakeWindowHandle>& w = getOccludingWindow(APP_B_UID, "B", TouchOcclusionMode::ALLOW);
     mDispatcher->setInputWindows({{ADISPLAY_ID_DEFAULT, {w, mTouchWindow}}});
@@ -4358,6 +4371,48 @@
     mTouchWindow->consumeAnyMotionDown();
 }
 
+TEST_F(InputDispatcherUntrustedTouchesTest, WindowWithZeroOpacity_DoesNotReceiveTouch) {
+    const sp<FakeWindowHandle>& w =
+            getOccludingWindow(APP_B_UID, "B", TouchOcclusionMode::BLOCK_UNTRUSTED, 0.0f);
+    mDispatcher->setInputWindows({{ADISPLAY_ID_DEFAULT, {w, mTouchWindow}}});
+
+    touch();
+
+    w->assertNoEvents();
+}
+
+/**
+ * This is important to make sure apps can't indirectly learn the position of touches (outside vs
+ * inside) while letting them pass-through. Note that even though touch passes through the occluding
+ * window, the occluding window will still receive ACTION_OUTSIDE event.
+ */
+TEST_F(InputDispatcherUntrustedTouchesTest,
+       WindowWithZeroOpacityAndWatchOutside_ReceivesOutsideEvent) {
+    const sp<FakeWindowHandle>& w =
+            getOccludingWindow(APP_B_UID, "B", TouchOcclusionMode::BLOCK_UNTRUSTED, 0.0f);
+    w->addFlags(InputWindowInfo::Flag::WATCH_OUTSIDE_TOUCH);
+    mDispatcher->setInputWindows({{ADISPLAY_ID_DEFAULT, {w, mTouchWindow}}});
+
+    touch();
+
+    w->consumeMotionOutside();
+}
+
+TEST_F(InputDispatcherUntrustedTouchesTest, OutsideEvent_HasZeroCoordinates) {
+    const sp<FakeWindowHandle>& w =
+            getOccludingWindow(APP_B_UID, "B", TouchOcclusionMode::BLOCK_UNTRUSTED, 0.0f);
+    w->addFlags(InputWindowInfo::Flag::WATCH_OUTSIDE_TOUCH);
+    mDispatcher->setInputWindows({{ADISPLAY_ID_DEFAULT, {w, mTouchWindow}}});
+
+    touch();
+
+    InputEvent* event = w->consume();
+    ASSERT_EQ(AINPUT_EVENT_TYPE_MOTION, event->getType());
+    MotionEvent& motionEvent = static_cast<MotionEvent&>(*event);
+    EXPECT_EQ(0.0f, motionEvent.getRawPointerCoords(0)->getX());
+    EXPECT_EQ(0.0f, motionEvent.getRawPointerCoords(0)->getY());
+}
+
 TEST_F(InputDispatcherUntrustedTouchesTest, WindowWithOpacityBelowThreshold_AllowsTouch) {
     const sp<FakeWindowHandle>& w =
             getOccludingWindow(APP_B_UID, "B", TouchOcclusionMode::USE_OPACITY, 0.7f);
diff --git a/services/surfaceflinger/BufferLayer.cpp b/services/surfaceflinger/BufferLayer.cpp
index 097f7de..a96dffd 100644
--- a/services/surfaceflinger/BufferLayer.cpp
+++ b/services/surfaceflinger/BufferLayer.cpp
@@ -414,12 +414,6 @@
         return true;
     }
 
-    // If the next planned present time is not current, return and try again later
-    if (frameIsEarly(expectedPresentTime)) {
-        ATRACE_NAME("frameIsEarly()");
-        return false;
-    }
-
     // If this layer doesn't have a frame is shouldn't be presented
     if (!hasFrameUpdate()) {
         return false;
diff --git a/services/surfaceflinger/CompositionEngine/Android.bp b/services/surfaceflinger/CompositionEngine/Android.bp
index 50bc5ed..f9e5b9a 100644
--- a/services/surfaceflinger/CompositionEngine/Android.bp
+++ b/services/surfaceflinger/CompositionEngine/Android.bp
@@ -29,6 +29,7 @@
         "liblog",
         "libnativewindow",
         "libprotobuf-cpp-lite",
+        "libSurfaceFlingerProp",
         "libtimestats",
         "libui",
         "libutils",
@@ -51,6 +52,11 @@
     name: "libcompositionengine",
     defaults: ["libcompositionengine_defaults"],
     srcs: [
+        "src/planner/CachedSet.cpp",
+        "src/planner/Flattener.cpp",
+        "src/planner/LayerState.cpp",
+        "src/planner/Planner.cpp",
+        "src/planner/Predictor.cpp",
         "src/ClientCompositionRequestCache.cpp",
         "src/CompositionEngine.cpp",
         "src/Display.cpp",
diff --git a/services/surfaceflinger/CompositionEngine/include/compositionengine/LayerFE.h b/services/surfaceflinger/CompositionEngine/include/compositionengine/LayerFE.h
index 018a687..1fd07b0 100644
--- a/services/surfaceflinger/CompositionEngine/include/compositionengine/LayerFE.h
+++ b/services/surfaceflinger/CompositionEngine/include/compositionengine/LayerFE.h
@@ -135,6 +135,9 @@
 
     // Gets some kind of identifier for the layer for debug purposes.
     virtual const char* getDebugName() const = 0;
+
+    // Gets the sequence number: a serial number that uniquely identifies a Layer
+    virtual int32_t getSequence() const = 0;
 };
 
 // TODO(b/121291683): Specialize std::hash<> for sp<T> so these and others can
diff --git a/services/surfaceflinger/CompositionEngine/include/compositionengine/Output.h b/services/surfaceflinger/CompositionEngine/include/compositionengine/Output.h
index 3be1cc4..4976213 100644
--- a/services/surfaceflinger/CompositionEngine/include/compositionengine/Output.h
+++ b/services/surfaceflinger/CompositionEngine/include/compositionengine/Output.h
@@ -31,6 +31,7 @@
 #include <ui/Region.h>
 #include <ui/Transform.h>
 #include <utils/StrongPointer.h>
+#include <utils/Vector.h>
 
 #include "DisplayHardware/DisplayIdentification.h"
 
@@ -183,6 +184,9 @@
     // Outputs a string with a state dump
     virtual void dump(std::string&) const = 0;
 
+    // Outputs planner information
+    virtual void dumpPlannerInfo(const Vector<String16>& args, std::string&) const = 0;
+
     // Gets the debug name for the output
     virtual const std::string& getName() const = 0;
 
@@ -264,7 +268,9 @@
     virtual void ensureOutputLayerIfVisible(sp<LayerFE>&, CoverageState&) = 0;
     virtual void setReleasedLayers(const CompositionRefreshArgs&) = 0;
 
-    virtual void updateAndWriteCompositionState(const CompositionRefreshArgs&) = 0;
+    virtual void updateCompositionState(const CompositionRefreshArgs&) = 0;
+    virtual void planComposition() = 0;
+    virtual void writeCompositionState(const CompositionRefreshArgs&) = 0;
     virtual void setColorTransform(const CompositionRefreshArgs&) = 0;
     virtual void updateColorProfile(const CompositionRefreshArgs&) = 0;
     virtual void beginFrame() = 0;
@@ -274,6 +280,7 @@
     virtual std::optional<base::unique_fd> composeSurfaces(
             const Region&, const compositionengine::CompositionRefreshArgs& refreshArgs) = 0;
     virtual void postFramebuffer() = 0;
+    virtual void renderCachedSets() = 0;
     virtual void chooseCompositionStrategy() = 0;
     virtual bool getSkipColorTransform() const = 0;
     virtual FrameFences presentAndGetFrameFences() = 0;
diff --git a/services/surfaceflinger/CompositionEngine/include/compositionengine/OutputLayer.h b/services/surfaceflinger/CompositionEngine/include/compositionengine/OutputLayer.h
index fb19216..3a84327 100644
--- a/services/surfaceflinger/CompositionEngine/include/compositionengine/OutputLayer.h
+++ b/services/surfaceflinger/CompositionEngine/include/compositionengine/OutputLayer.h
@@ -30,6 +30,8 @@
 #include "DisplayHardware/ComposerHal.h"
 #include "DisplayHardware/DisplayIdentification.h"
 
+#include "LayerFE.h"
+
 // TODO(b/129481165): remove the #pragma below and fix conversion issues
 #pragma clang diagnostic pop // ignored "-Wconversion -Wextra"
 
@@ -43,7 +45,6 @@
 
 class CompositionEngine;
 class Output;
-class LayerFE;
 
 namespace impl {
 struct OutputLayerCompositionState;
@@ -88,8 +89,9 @@
 
     // Writes the geometry state to the HWC, or does nothing if this layer does
     // not use the HWC. If includeGeometry is false, the geometry state can be
-    // skipped.
-    virtual void writeStateToHWC(bool includeGeometry) = 0;
+    // skipped. If skipLayer is true, then the alpha of the layer is forced to
+    // 0 so that HWC will ignore it.
+    virtual void writeStateToHWC(bool includeGeometry, bool skipLayer) = 0;
 
     // Updates the cursor position with the HWC
     virtual void writeCursorPositionToHWC() const = 0;
@@ -115,6 +117,10 @@
     // Returns true if the composition settings scale pixels
     virtual bool needsFiltering() const = 0;
 
+    // Returns a composition list to be used by RenderEngine if the layer has been overridden
+    // during the composition process
+    virtual std::vector<LayerFE::LayerSettings> getOverrideCompositionList() const = 0;
+
     // Debugging
     virtual void dump(std::string& result) const = 0;
 };
diff --git a/services/surfaceflinger/CompositionEngine/include/compositionengine/impl/Output.h b/services/surfaceflinger/CompositionEngine/include/compositionengine/impl/Output.h
index 651230c..eeb20fc 100644
--- a/services/surfaceflinger/CompositionEngine/include/compositionengine/impl/Output.h
+++ b/services/surfaceflinger/CompositionEngine/include/compositionengine/impl/Output.h
@@ -28,10 +28,15 @@
 
 namespace android::compositionengine::impl {
 
+namespace planner {
+class Planner;
+} // namespace planner
+
 // The implementation class contains the common implementation, but does not
 // actually contain the final output state.
 class Output : public virtual compositionengine::Output {
 public:
+    Output();
     ~Output() override;
 
     // compositionengine::Output overrides
@@ -48,6 +53,7 @@
     void setColorProfile(const ColorProfile&) override;
 
     void dump(std::string&) const override;
+    void dumpPlannerInfo(const Vector<String16>& args, std::string&) const override;
 
     const std::string& getName() const override;
     void setName(const std::string&) override;
@@ -77,7 +83,9 @@
     void setReleasedLayers(const compositionengine::CompositionRefreshArgs&) override;
 
     void updateLayerStateFromFE(const CompositionRefreshArgs&) const override;
-    void updateAndWriteCompositionState(const compositionengine::CompositionRefreshArgs&) override;
+    void updateCompositionState(const compositionengine::CompositionRefreshArgs&) override;
+    void planComposition() override;
+    void writeCompositionState(const compositionengine::CompositionRefreshArgs&) override;
     void updateColorProfile(const compositionengine::CompositionRefreshArgs&) override;
     void beginFrame() override;
     void prepareFrame() override;
@@ -86,12 +94,14 @@
     std::optional<base::unique_fd> composeSurfaces(
             const Region&, const compositionengine::CompositionRefreshArgs& refreshArgs) override;
     void postFramebuffer() override;
+    void renderCachedSets() override;
     void cacheClientCompositionRequests(uint32_t) override;
 
     // Testing
     const ReleasedLayers& getReleasedLayersForTest() const;
     void setDisplayColorProfileForTest(std::unique_ptr<compositionengine::DisplayColorProfile>);
     void setRenderSurfaceForTest(std::unique_ptr<compositionengine::RenderSurface>);
+    bool plannerEnabled() const { return mPlanner != nullptr; }
 
 protected:
     std::unique_ptr<compositionengine::OutputLayer> createOutputLayer(const sp<LayerFE>&) const;
@@ -130,6 +140,7 @@
     ReleasedLayers mReleasedLayers;
     OutputLayer* mLayerRequestingBackgroundBlur = nullptr;
     std::unique_ptr<ClientCompositionRequestCache> mClientCompositionRequestCache;
+    std::unique_ptr<planner::Planner> mPlanner;
 };
 
 // This template factory function standardizes the implementation details of the
diff --git a/services/surfaceflinger/CompositionEngine/include/compositionengine/impl/OutputLayer.h b/services/surfaceflinger/CompositionEngine/include/compositionengine/impl/OutputLayer.h
index 8cb5ae8..f113c34 100644
--- a/services/surfaceflinger/CompositionEngine/include/compositionengine/impl/OutputLayer.h
+++ b/services/surfaceflinger/CompositionEngine/include/compositionengine/impl/OutputLayer.h
@@ -19,6 +19,7 @@
 #include <memory>
 #include <string>
 
+#include <compositionengine/LayerFE.h>
 #include <compositionengine/OutputLayer.h>
 #include <ui/FloatRect.h>
 #include <ui/Rect.h>
@@ -41,7 +42,7 @@
 
     void updateCompositionState(bool includeGeometry, bool forceClientComposition,
                                 ui::Transform::RotationFlags) override;
-    void writeStateToHWC(bool) override;
+    void writeStateToHWC(bool includeGeometry, bool skipLayer) override;
     void writeCursorPositionToHWC() const override;
 
     HWC2::Layer* getHwcLayer() const override;
@@ -51,6 +52,7 @@
     void prepareForDeviceLayerRequests() override;
     void applyDeviceLayerRequest(Hwc2::IComposerClient::LayerRequest request) override;
     bool needsFiltering() const override;
+    std::vector<LayerFE::LayerSettings> getOverrideCompositionList() const override;
 
     void dump(std::string&) const override;
 
@@ -66,7 +68,8 @@
 private:
     Rect calculateInitialCrop() const;
     void writeOutputDependentGeometryStateToHWC(HWC2::Layer*, Hwc2::IComposerClient::Composition);
-    void writeOutputIndependentGeometryStateToHWC(HWC2::Layer*, const LayerFECompositionState&);
+    void writeOutputIndependentGeometryStateToHWC(HWC2::Layer*, const LayerFECompositionState&,
+                                                  bool skipLayer);
     void writeOutputDependentPerFrameStateToHWC(HWC2::Layer*);
     void writeOutputIndependentPerFrameStateToHWC(HWC2::Layer*, const LayerFECompositionState&);
     void writeSolidColorStateToHWC(HWC2::Layer*, const LayerFECompositionState&);
diff --git a/services/surfaceflinger/CompositionEngine/include/compositionengine/impl/OutputLayerCompositionState.h b/services/surfaceflinger/CompositionEngine/include/compositionengine/impl/OutputLayerCompositionState.h
index 9a118d3..5f834be 100644
--- a/services/surfaceflinger/CompositionEngine/include/compositionengine/impl/OutputLayerCompositionState.h
+++ b/services/surfaceflinger/CompositionEngine/include/compositionengine/impl/OutputLayerCompositionState.h
@@ -84,6 +84,13 @@
     // The Z order index of this layer on this output
     uint32_t z{0};
 
+    // Overrides the buffer, acquire fence, and display frame stored in LayerFECompositionState
+    struct {
+        sp<GraphicBuffer> buffer = nullptr;
+        sp<Fence> acquireFence = nullptr;
+        Rect displayFrame = {};
+    } overrideInfo;
+
     /*
      * HWC state
      */
diff --git a/services/surfaceflinger/CompositionEngine/include/compositionengine/impl/planner/CachedSet.h b/services/surfaceflinger/CompositionEngine/include/compositionengine/impl/planner/CachedSet.h
new file mode 100644
index 0000000..00424b2
--- /dev/null
+++ b/services/surfaceflinger/CompositionEngine/include/compositionengine/impl/planner/CachedSet.h
@@ -0,0 +1,115 @@
+/*
+ * Copyright 2021 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 <compositionengine/impl/planner/LayerState.h>
+
+#include <chrono>
+
+namespace android {
+
+namespace renderengine {
+class RenderEngine;
+} // namespace renderengine
+
+namespace compositionengine::impl::planner {
+
+std::string durationString(std::chrono::milliseconds duration);
+
+class LayerState;
+
+class CachedSet {
+public:
+    class Layer {
+    public:
+        Layer(const LayerState*, std::chrono::steady_clock::time_point lastUpdate);
+
+        const LayerState* getState() const { return mState; }
+        const std::string& getName() const { return mState->getName(); }
+        Rect getDisplayFrame() const { return mState->getDisplayFrame(); }
+        const sp<GraphicBuffer>& getBuffer() const { return mState->getBuffer(); }
+        int64_t getFramesSinceBufferUpdate() const { return mState->getFramesSinceBufferUpdate(); }
+        NonBufferHash getHash() const { return mHash; }
+        std::chrono::steady_clock::time_point getLastUpdate() const { return mLastUpdate; }
+
+    private:
+        const LayerState* mState;
+        NonBufferHash mHash;
+        std::chrono::steady_clock::time_point mLastUpdate;
+    };
+
+    CachedSet(const LayerState*, std::chrono::steady_clock::time_point lastUpdate);
+    CachedSet(Layer layer);
+
+    void addLayer(const LayerState*, std::chrono::steady_clock::time_point lastUpdate);
+
+    std::chrono::steady_clock::time_point getLastUpdate() const { return mLastUpdate; }
+    NonBufferHash getFingerprint() const { return mFingerprint; }
+    size_t getLayerCount() const { return mLayers.size(); }
+    const Layer& getFirstLayer() const { return mLayers[0]; }
+    const Rect& getBounds() const { return mBounds; }
+    size_t getAge() const { return mAge; }
+    const sp<GraphicBuffer>& getBuffer() const { return mBuffer; }
+    const sp<Fence>& getDrawFence() const { return mDrawFence; }
+
+    NonBufferHash getNonBufferHash() const;
+
+    size_t getComponentDisplayCost() const;
+    size_t getCreationCost() const;
+    size_t getDisplayCost() const;
+
+    bool hasBufferUpdate(std::vector<const LayerState*>::const_iterator layers) const;
+    bool hasReadyBuffer() const;
+
+    // Decomposes this CachedSet into a vector of its layers as individual CachedSets
+    std::vector<CachedSet> decompose() const;
+
+    void updateAge(std::chrono::steady_clock::time_point now);
+
+    void setLastUpdate(std::chrono::steady_clock::time_point now) { mLastUpdate = now; }
+    void append(const CachedSet& other) {
+        mBuffer = nullptr;
+        mDrawFence = nullptr;
+
+        mLayers.insert(mLayers.end(), other.mLayers.cbegin(), other.mLayers.cend());
+        Region boundingRegion;
+        boundingRegion.orSelf(mBounds);
+        boundingRegion.orSelf(other.mBounds);
+        mBounds = boundingRegion.getBounds();
+    }
+    void incrementAge() { ++mAge; }
+
+    void render(renderengine::RenderEngine&);
+
+    void dump(std::string& result) const;
+
+private:
+    CachedSet() = default;
+
+    NonBufferHash mFingerprint = 0;
+    std::chrono::steady_clock::time_point mLastUpdate = std::chrono::steady_clock::now();
+    std::vector<Layer> mLayers;
+    Rect mBounds = Rect::EMPTY_RECT;
+    size_t mAge = 0;
+    sp<GraphicBuffer> mBuffer;
+    sp<Fence> mDrawFence;
+
+    static const bool sDebugHighlighLayers;
+};
+
+} // namespace compositionengine::impl::planner
+} // namespace android
diff --git a/services/surfaceflinger/CompositionEngine/include/compositionengine/impl/planner/Flattener.h b/services/surfaceflinger/CompositionEngine/include/compositionengine/impl/planner/Flattener.h
new file mode 100644
index 0000000..6c86408
--- /dev/null
+++ b/services/surfaceflinger/CompositionEngine/include/compositionengine/impl/planner/Flattener.h
@@ -0,0 +1,86 @@
+/*
+ * Copyright 2021 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 <compositionengine/impl/planner/CachedSet.h>
+#include <compositionengine/impl/planner/LayerState.h>
+
+#include <vector>
+
+namespace android {
+
+namespace renderengine {
+class RenderEngine;
+} // namespace renderengine
+
+namespace compositionengine::impl::planner {
+using namespace std::chrono_literals;
+
+class LayerState;
+class Predictor;
+
+class Flattener {
+public:
+    Flattener(Predictor& predictor) : mPredictor(predictor) {}
+
+    void setDisplaySize(ui::Size size) { mDisplaySize = size; }
+
+    NonBufferHash flattenLayers(const std::vector<const LayerState*>& layers, NonBufferHash);
+
+    void renderCachedSets(renderengine::RenderEngine&);
+
+    void reset();
+
+    void dump(std::string& result) const;
+
+private:
+    size_t calculateDisplayCost(const std::vector<const LayerState*>& layers) const;
+
+    void resetActivities(NonBufferHash, std::chrono::steady_clock::time_point now);
+
+    void updateLayersHash();
+
+    bool mergeWithCachedSets(const std::vector<const LayerState*>& layers,
+                             std::chrono::steady_clock::time_point now);
+
+    void buildCachedSets(std::chrono::steady_clock::time_point now);
+
+    Predictor& mPredictor;
+
+    ui::Size mDisplaySize;
+
+    NonBufferHash mCurrentGeometry;
+    std::chrono::steady_clock::time_point mLastGeometryUpdate;
+
+    std::vector<CachedSet> mLayers;
+    NonBufferHash mLayersHash = 0;
+    std::optional<CachedSet> mNewCachedSet;
+
+    // Statistics
+    size_t mUnflattenedDisplayCost = 0;
+    size_t mFlattenedDisplayCost = 0;
+    std::unordered_map<size_t, size_t> mInitialLayerCounts;
+    std::unordered_map<size_t, size_t> mFinalLayerCounts;
+    size_t mCachedSetCreationCount = 0;
+    size_t mCachedSetCreationCost = 0;
+    std::unordered_map<size_t, size_t> mInvalidatedCachedSetAges;
+
+    static constexpr auto kActiveLayerTimeout = std::chrono::nanoseconds(150ms);
+};
+
+} // namespace compositionengine::impl::planner
+} // namespace android
diff --git a/services/surfaceflinger/CompositionEngine/include/compositionengine/impl/planner/LayerState.h b/services/surfaceflinger/CompositionEngine/include/compositionengine/impl/planner/LayerState.h
new file mode 100644
index 0000000..d19ac62
--- /dev/null
+++ b/services/surfaceflinger/CompositionEngine/include/compositionengine/impl/planner/LayerState.h
@@ -0,0 +1,361 @@
+/*
+ * Copyright 2021 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 <android-base/strings.h>
+#include <compositionengine/LayerFE.h>
+#include <compositionengine/LayerFECompositionState.h>
+#include <compositionengine/OutputLayer.h>
+#include <compositionengine/impl/OutputLayerCompositionState.h>
+#include <input/Flags.h>
+
+#include <string>
+
+#include "DisplayHardware/Hal.h"
+
+namespace std {
+template <typename T>
+struct hash<android::sp<T>> {
+    size_t operator()(const android::sp<T>& p) { return std::hash<void*>()(p.get()); }
+};
+} // namespace std
+
+namespace android::compositionengine::impl::planner {
+
+using LayerId = int32_t;
+
+// clang-format off
+enum class LayerStateField : uint32_t {
+    Id              = 1u << 0,
+    Name            = 1u << 1,
+    DisplayFrame    = 1u << 2,
+    SourceCrop      = 1u << 3,
+    ZOrder          = 1u << 4,
+    BufferTransform = 1u << 5,
+    BlendMode       = 1u << 6,
+    Alpha           = 1u << 7,
+    VisibleRegion   = 1u << 8,
+    Dataspace       = 1u << 9,
+    ColorTransform  = 1u << 10,
+    CompositionType = 1u << 11,
+    SidebandStream  = 1u << 12,
+    Buffer          = 1u << 13,
+    SolidColor      = 1u << 14,
+};
+// clang-format on
+
+std::string to_string(LayerStateField field);
+
+// An abstract interface allows us to iterate over all of the OutputLayerState fields
+// without having to worry about their templated types.
+// See `LayerState::getNonUniqueFields` below.
+class StateInterface {
+public:
+    virtual ~StateInterface() = default;
+
+    virtual Flags<LayerStateField> update(const compositionengine::OutputLayer* layer) = 0;
+
+    virtual size_t getHash(Flags<LayerStateField> skipFields) const = 0;
+
+    virtual LayerStateField getField() const = 0;
+
+    virtual Flags<LayerStateField> getFieldIfDifferent(const StateInterface* other) const = 0;
+
+    virtual bool equals(const StateInterface* other) const = 0;
+
+    virtual std::vector<std::string> toStrings() const = 0;
+};
+
+template <typename T, LayerStateField FIELD>
+class OutputLayerState : public StateInterface {
+public:
+    using ReadFromLayerState = std::function<T(const compositionengine::OutputLayer* layer)>;
+    using ToStrings = std::function<std::vector<std::string>(const T&)>;
+    using Equals = std::function<bool(const T&, const T&)>;
+
+    static ToStrings getDefaultToStrings() {
+        return [](const T& value) {
+            using std::to_string;
+            return std::vector<std::string>{to_string(value)};
+        };
+    }
+
+    static ToStrings getHalToStrings() {
+        return [](const T& value) { return std::vector<std::string>{toString(value)}; };
+    }
+
+    static Equals getDefaultEquals() {
+        return [](const T& lhs, const T& rhs) { return lhs == rhs; };
+    }
+
+    OutputLayerState(ReadFromLayerState reader,
+                     ToStrings toStrings = OutputLayerState::getDefaultToStrings(),
+                     Equals equals = OutputLayerState::getDefaultEquals())
+          : mReader(reader), mToStrings(toStrings), mEquals(equals) {}
+
+    ~OutputLayerState() override = default;
+
+    // Returns this member's field flag if it was changed
+    Flags<LayerStateField> update(const compositionengine::OutputLayer* layer) override {
+        T newValue = mReader(layer);
+        if (!mEquals(mValue, newValue)) {
+            mValue = newValue;
+            mHash = {};
+            return FIELD;
+        }
+        return {};
+    }
+
+    LayerStateField getField() const override { return FIELD; }
+    const T& get() const { return mValue; }
+
+    size_t getHash(Flags<LayerStateField> skipFields) const override {
+        if (skipFields.test(FIELD)) {
+            return 0;
+        }
+        if (!mHash) {
+            mHash = std::hash<T>{}(mValue);
+        }
+        return *mHash;
+    }
+
+    Flags<LayerStateField> getFieldIfDifferent(const StateInterface* other) const override {
+        if (other->getField() != FIELD) {
+            return {};
+        }
+
+        // The early return ensures that this downcast is sound
+        const OutputLayerState* otherState = static_cast<const OutputLayerState*>(other);
+        return *this != *otherState ? FIELD : Flags<LayerStateField>{};
+    }
+
+    bool equals(const StateInterface* other) const override {
+        if (other->getField() != FIELD) {
+            return false;
+        }
+
+        // The early return ensures that this downcast is sound
+        const OutputLayerState* otherState = static_cast<const OutputLayerState*>(other);
+        return *this == *otherState;
+    }
+
+    std::vector<std::string> toStrings() const override { return mToStrings(mValue); }
+
+    bool operator==(const OutputLayerState& other) const { return mEquals(mValue, other.mValue); }
+    bool operator!=(const OutputLayerState& other) const { return !(*this == other); }
+
+private:
+    const ReadFromLayerState mReader;
+    const ToStrings mToStrings;
+    const Equals mEquals;
+    T mValue = {};
+    mutable std::optional<size_t> mHash = {};
+};
+
+class LayerState {
+public:
+    LayerState(compositionengine::OutputLayer* layer);
+
+    // Returns which fields were updated
+    Flags<LayerStateField> update(compositionengine::OutputLayer*);
+
+    size_t getHash(Flags<LayerStateField> skipFields) const;
+
+    Flags<LayerStateField> getDifferingFields(const LayerState& other,
+                                              Flags<LayerStateField> skipFields) const;
+
+    compositionengine::OutputLayer* getOutputLayer() const { return mOutputLayer; }
+    int32_t getId() const { return mId.get(); }
+    const std::string& getName() const { return mName.get(); }
+    Rect getDisplayFrame() const { return mDisplayFrame.get(); }
+    hardware::graphics::composer::hal::Composition getCompositionType() const {
+        return mCompositionType.get();
+    }
+    const sp<GraphicBuffer>& getBuffer() const { return mBuffer.get(); }
+
+    void incrementFramesSinceBufferUpdate() { ++mFramesSinceBufferUpdate; }
+    void resetFramesSinceBufferUpdate() { mFramesSinceBufferUpdate = 0; }
+    int64_t getFramesSinceBufferUpdate() const { return mFramesSinceBufferUpdate; }
+
+    void dump(std::string& result) const;
+    std::optional<std::string> compare(const LayerState& other) const;
+
+    // This makes LayerState's private members accessible to the operator
+    friend bool operator==(const LayerState& lhs, const LayerState& rhs);
+    friend bool operator!=(const LayerState& lhs, const LayerState& rhs) { return !(lhs == rhs); }
+
+private:
+    compositionengine::OutputLayer* mOutputLayer = nullptr;
+
+    OutputLayerState<LayerId, LayerStateField::Id> mId{
+            [](const compositionengine::OutputLayer* layer) {
+                return layer->getLayerFE().getSequence();
+            }};
+
+    OutputLayerState<std::string, LayerStateField::Name>
+            mName{[](auto layer) { return layer->getLayerFE().getDebugName(); },
+                  [](const std::string& name) { return std::vector<std::string>{name}; }};
+
+    // Output-dependent geometry state
+
+    OutputLayerState<Rect, LayerStateField::DisplayFrame>
+            mDisplayFrame{[](auto layer) { return layer->getState().displayFrame; },
+                          [](const Rect& rect) {
+                              return std::vector<std::string>{
+                                      base::StringPrintf("[%d, %d, %d, %d]", rect.left, rect.top,
+                                                         rect.right, rect.bottom)};
+                          }};
+
+    OutputLayerState<FloatRect, LayerStateField::SourceCrop>
+            mSourceCrop{[](auto layer) { return layer->getState().sourceCrop; },
+                        [](const FloatRect& rect) {
+                            return std::vector<std::string>{
+                                    base::StringPrintf("[%.2f, %.2f, %.2f, %.2f]", rect.left,
+                                                       rect.top, rect.right, rect.bottom)};
+                        }};
+
+    OutputLayerState<uint32_t, LayerStateField::ZOrder> mZOrder{
+            [](auto layer) { return layer->getState().z; }};
+
+    using BufferTransformState = OutputLayerState<hardware::graphics::composer::hal::Transform,
+                                                  LayerStateField::BufferTransform>;
+    BufferTransformState mBufferTransform{[](auto layer) {
+                                              return layer->getState().bufferTransform;
+                                          },
+                                          BufferTransformState::getHalToStrings()};
+
+    // Output-independent geometry state
+
+    using BlendModeState = OutputLayerState<hardware::graphics::composer::hal::BlendMode,
+                                            LayerStateField::BlendMode>;
+    BlendModeState mBlendMode{[](auto layer) {
+                                  return layer->getLayerFE().getCompositionState()->blendMode;
+                              },
+                              BlendModeState::getHalToStrings()};
+
+    OutputLayerState<float, LayerStateField::Alpha> mAlpha{
+            [](auto layer) { return layer->getLayerFE().getCompositionState()->alpha; }};
+
+    // TODO(b/180638831): Generic layer metadata
+
+    // Output-dependent per-frame state
+
+    OutputLayerState<Region, LayerStateField::VisibleRegion>
+            mVisibleRegion{[](auto layer) { return layer->getState().visibleRegion; },
+                           [](const Region& region) {
+                               using namespace std::string_literals;
+                               std::string dump;
+                               region.dump(dump, "");
+                               std::vector<std::string> split = base::Split(dump, "\n"s);
+                               split.erase(split.begin()); // Strip the header
+                               split.pop_back();           // Strip the last (empty) line
+                               for (std::string& line : split) {
+                                   line.erase(0, 4); // Strip leading padding before each rect
+                               }
+                               return split;
+                           },
+                           [](const Region& lhs, const Region& rhs) {
+                               return lhs.hasSameRects(rhs);
+                           }};
+
+    using DataspaceState = OutputLayerState<ui::Dataspace, LayerStateField::Dataspace>;
+    DataspaceState mDataspace{[](auto layer) { return layer->getState().dataspace; },
+                              DataspaceState::getHalToStrings()};
+
+    // TODO(b/180638831): Buffer format
+
+    // Output-independent per-frame state
+
+    OutputLayerState<mat4, LayerStateField::ColorTransform>
+            mColorTransform{[](auto layer) {
+                                const auto state = layer->getLayerFE().getCompositionState();
+                                return state->colorTransformIsIdentity ? mat4{}
+                                                                       : state->colorTransform;
+                            },
+                            [](const mat4& mat) {
+                                using namespace std::string_literals;
+                                std::vector<std::string> split =
+                                        base::Split(std::string(mat.asString().string()), "\n"s);
+                                split.pop_back(); // Strip the last (empty) line
+                                return split;
+                            }};
+
+    // TODO(b/180638831): Surface damage
+
+    using CompositionTypeState = OutputLayerState<hardware::graphics::composer::hal::Composition,
+                                                  LayerStateField::CompositionType>;
+    CompositionTypeState
+            mCompositionType{[](auto layer) {
+                                 return layer->getState().forceClientComposition
+                                         ? hardware::graphics::composer::hal::Composition::CLIENT
+                                         : layer->getLayerFE()
+                                                   .getCompositionState()
+                                                   ->compositionType;
+                             },
+                             CompositionTypeState::getHalToStrings()};
+
+    OutputLayerState<void*, LayerStateField::SidebandStream>
+            mSidebandStream{[](auto layer) {
+                                return layer->getLayerFE()
+                                        .getCompositionState()
+                                        ->sidebandStream.get();
+                            },
+                            [](void* p) {
+                                return std::vector<std::string>{base::StringPrintf("%p", p)};
+                            }};
+
+    OutputLayerState<sp<GraphicBuffer>, LayerStateField::Buffer>
+            mBuffer{[](auto layer) { return layer->getLayerFE().getCompositionState()->buffer; },
+                    [](const sp<GraphicBuffer>& buffer) {
+                        return std::vector<std::string>{base::StringPrintf("%p", buffer.get())};
+                    }};
+
+    int64_t mFramesSinceBufferUpdate = 0;
+
+    OutputLayerState<half4, LayerStateField::SolidColor>
+            mSolidColor{[](auto layer) { return layer->getLayerFE().getCompositionState()->color; },
+                        [](const half4& vec) {
+                            std::stringstream stream;
+                            stream << vec;
+                            return std::vector<std::string>{stream.str()};
+                        }};
+
+    std::array<StateInterface*, 13> getNonUniqueFields() {
+        std::array<const StateInterface*, 13> constFields =
+                const_cast<const LayerState*>(this)->getNonUniqueFields();
+        std::array<StateInterface*, 13> fields;
+        std::transform(constFields.cbegin(), constFields.cend(), fields.begin(),
+                       [](const StateInterface* constField) {
+                           return const_cast<StateInterface*>(constField);
+                       });
+        return fields;
+    }
+
+    std::array<const StateInterface*, 13> getNonUniqueFields() const {
+        return {
+                &mDisplayFrame,   &mSourceCrop,      &mZOrder,         &mBufferTransform,
+                &mBlendMode,      &mAlpha,           &mVisibleRegion,  &mDataspace,
+                &mColorTransform, &mCompositionType, &mSidebandStream, &mBuffer,
+                &mSolidColor,
+        };
+    }
+};
+
+using NonBufferHash = size_t;
+NonBufferHash getNonBufferHash(const std::vector<const LayerState*>&);
+
+} // namespace android::compositionengine::impl::planner
diff --git a/services/surfaceflinger/CompositionEngine/include/compositionengine/impl/planner/Planner.h b/services/surfaceflinger/CompositionEngine/include/compositionengine/impl/planner/Planner.h
new file mode 100644
index 0000000..e96abb7
--- /dev/null
+++ b/services/surfaceflinger/CompositionEngine/include/compositionengine/impl/planner/Planner.h
@@ -0,0 +1,81 @@
+/*
+ * Copyright 2021 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 <compositionengine/Output.h>
+#include <compositionengine/impl/planner/Flattener.h>
+#include <compositionengine/impl/planner/LayerState.h>
+#include <compositionengine/impl/planner/Predictor.h>
+#include <utils/String16.h>
+#include <utils/Vector.h>
+
+#include <optional>
+#include <string>
+#include <unordered_map>
+
+namespace android {
+
+namespace renderengine {
+class RenderEngine;
+} // namespace renderengine
+
+namespace compositionengine::impl::planner {
+
+// This is the top level class for layer caching. It is responsible for
+// heuristically determining the composition strategy of the current layer stack,
+// and flattens inactive layers into an override buffer so it can be used
+// as a more efficient representation of parts of the layer stack.
+class Planner {
+public:
+    Planner() : mFlattener(mPredictor) {}
+
+    void setDisplaySize(ui::Size);
+
+    // Updates the Planner with the current set of layers before a composition strategy is
+    // determined.
+    // The Planner will call to the Flattener to determine to:
+    // 1. Replace any cached sets with a newly available flattened cached set
+    // 2. Create a new cached set if possible
+    void plan(
+            compositionengine::Output::OutputLayersEnumerator<compositionengine::Output>&& layers);
+
+    // Updates the Planner with the current set of layers after a composition strategy is
+    // determined.
+    void reportFinalPlan(
+            compositionengine::Output::OutputLayersEnumerator<compositionengine::Output>&& layers);
+
+    // The planner will call to the Flattener to render any pending cached set
+    void renderCachedSets(renderengine::RenderEngine&);
+
+    void dump(const Vector<String16>& args, std::string&);
+
+private:
+    void dumpUsage(std::string&) const;
+
+    std::unordered_map<LayerId, LayerState> mPreviousLayers;
+
+    std::vector<const LayerState*> mCurrentLayers;
+
+    Predictor mPredictor;
+    Flattener mFlattener;
+
+    std::optional<Predictor::PredictedPlan> mPredictedPlan;
+    NonBufferHash mFlattenedHash = 0;
+};
+
+} // namespace compositionengine::impl::planner
+} // namespace android
diff --git a/services/surfaceflinger/CompositionEngine/include/compositionengine/impl/planner/Predictor.h b/services/surfaceflinger/CompositionEngine/include/compositionengine/impl/planner/Predictor.h
new file mode 100644
index 0000000..422af77
--- /dev/null
+++ b/services/surfaceflinger/CompositionEngine/include/compositionengine/impl/planner/Predictor.h
@@ -0,0 +1,278 @@
+/*
+ * Copyright 2021 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 <compositionengine/impl/planner/LayerState.h>
+
+namespace android::compositionengine::impl::planner {
+
+class LayerStack {
+public:
+    LayerStack(const std::vector<const LayerState*>& layers) : mLayers(copyLayers(layers)) {}
+
+    struct ApproximateMatch {
+        bool operator==(const ApproximateMatch& other) const {
+            return differingIndex == other.differingIndex &&
+                    differingFields == other.differingFields;
+        }
+
+        size_t differingIndex;
+        Flags<LayerStateField> differingFields;
+    };
+
+    std::optional<ApproximateMatch> getApproximateMatch(
+            const std::vector<const LayerState*>& other) const;
+
+    void compare(const LayerStack& other, std::string& result) const {
+        if (mLayers.size() != other.mLayers.size()) {
+            base::StringAppendF(&result, "Cannot compare stacks of different sizes (%zd vs. %zd)\n",
+                                mLayers.size(), other.mLayers.size());
+            return;
+        }
+
+        for (size_t l = 0; l < mLayers.size(); ++l) {
+            const auto& thisLayer = mLayers[l];
+            const auto& otherLayer = other.mLayers[l];
+            base::StringAppendF(&result, "\n+ - - - - - - - - - Layer %d [%s]\n", thisLayer.getId(),
+                                thisLayer.getName().c_str());
+            auto comparisonOpt = thisLayer.compare(otherLayer);
+            base::StringAppendF(&result,
+                                "    %s     + - - - - - - - - - - - - - - - - - - - - - - - "
+                                "- Layer %d [%s]\n",
+                                comparisonOpt ? "         " : "Identical", otherLayer.getId(),
+                                otherLayer.getName().c_str());
+            if (comparisonOpt) {
+                result.append(*comparisonOpt);
+            }
+        }
+    }
+
+    void dump(std::string& result) const {
+        for (const LayerState& layer : mLayers) {
+            base::StringAppendF(&result, "+ - - - - - - - - - Layer %d [%s]\n", layer.getId(),
+                                layer.getName().c_str());
+            layer.dump(result);
+        }
+    }
+
+    void dumpLayerNames(std::string& result, const std::string& prefix = "  ") const {
+        for (const LayerState& layer : mLayers) {
+            result.append(prefix);
+            result.append(layer.getName());
+            result.append("\n");
+        }
+    }
+
+private:
+    std::vector<const LayerState> copyLayers(const std::vector<const LayerState*>& layers) {
+        std::vector<const LayerState> copiedLayers;
+        copiedLayers.reserve(layers.size());
+        std::transform(layers.cbegin(), layers.cend(), std::back_inserter(copiedLayers),
+                       [](const LayerState* layerState) { return *layerState; });
+        return copiedLayers;
+    }
+
+    std::vector<const LayerState> mLayers;
+
+    // TODO(b/180976743): Tune kMaxDifferingFields
+    constexpr static int kMaxDifferingFields = 6;
+};
+
+class Plan {
+public:
+    static std::optional<Plan> fromString(const std::string&);
+
+    void reset() { mLayerTypes.clear(); }
+    void addLayerType(hardware::graphics::composer::hal::Composition type) {
+        mLayerTypes.emplace_back(type);
+    }
+
+    friend std::string to_string(const Plan& plan);
+
+    friend bool operator==(const Plan& lhs, const Plan& rhs) {
+        return lhs.mLayerTypes == rhs.mLayerTypes;
+    }
+    friend bool operator!=(const Plan& lhs, const Plan& rhs) { return !(lhs == rhs); }
+
+private:
+    std::vector<hardware::graphics::composer::hal::Composition> mLayerTypes;
+};
+
+} // namespace android::compositionengine::impl::planner
+
+namespace std {
+template <>
+struct hash<android::compositionengine::impl::planner::Plan> {
+    size_t operator()(const android::compositionengine::impl::planner::Plan& plan) const {
+        return std::hash<std::string>{}(to_string(plan));
+    }
+};
+} // namespace std
+
+namespace android::compositionengine::impl::planner {
+
+class Prediction {
+public:
+    enum class Type {
+        Exact,
+        Approximate,
+        Total,
+    };
+
+    friend std::string to_string(Type type) {
+        using namespace std::string_literals;
+
+        switch (type) {
+            case Type::Exact:
+                return "Exact";
+            case Type::Approximate:
+                return "Approximate";
+            case Type::Total:
+                return "Total";
+        }
+    }
+
+    Prediction(const std::vector<const LayerState*>& layers, Plan plan)
+          : mExampleLayerStack(layers), mPlan(std::move(plan)) {}
+
+    const LayerStack& getExampleLayerStack() const { return mExampleLayerStack; }
+    const Plan& getPlan() const { return mPlan; }
+
+    size_t getHitCount(Type type) const {
+        if (type == Type::Total) {
+            return getHitCount(Type::Exact) + getHitCount(Type::Approximate);
+        }
+        return getStatsForType(type).hitCount;
+    }
+
+    size_t getMissCount(Type type) const {
+        if (type == Type::Total) {
+            return getMissCount(Type::Exact) + getMissCount(Type::Approximate);
+        }
+        return getStatsForType(type).missCount;
+    }
+
+    void recordHit(Type type) { ++getStatsForType(type).hitCount; }
+
+    void recordMiss(Type type) { ++getStatsForType(type).missCount; }
+
+    void dump(std::string&) const;
+
+private:
+    struct Stats {
+        void dump(std::string& result) const {
+            const size_t totalAttempts = hitCount + missCount;
+            base::StringAppendF(&result, "%.2f%% (%zd/%zd)", 100.0f * hitCount / totalAttempts,
+                                hitCount, totalAttempts);
+        }
+
+        size_t hitCount = 0;
+        size_t missCount = 0;
+    };
+
+    const Stats& getStatsForType(Type type) const {
+        return (type == Type::Exact) ? mExactStats : mApproximateStats;
+    }
+
+    Stats& getStatsForType(Type type) {
+        return const_cast<Stats&>(const_cast<const Prediction*>(this)->getStatsForType(type));
+    }
+
+    LayerStack mExampleLayerStack;
+    Plan mPlan;
+
+    Stats mExactStats;
+    Stats mApproximateStats;
+};
+
+class Predictor {
+public:
+    struct PredictedPlan {
+        NonBufferHash hash;
+        Plan plan;
+        Prediction::Type type;
+    };
+
+    std::optional<PredictedPlan> getPredictedPlan(const std::vector<const LayerState*>&,
+                                                  NonBufferHash) const;
+
+    void recordResult(std::optional<PredictedPlan> predictedPlan, NonBufferHash flattenedHash,
+                      const std::vector<const LayerState*>&, bool hasSkippedLayers, Plan result);
+
+    void dump(std::string&) const;
+
+    void compareLayerStacks(NonBufferHash leftHash, NonBufferHash rightHash, std::string&) const;
+    void describeLayerStack(NonBufferHash, std::string&) const;
+    void listSimilarStacks(Plan, std::string&) const;
+
+private:
+    // Retrieves a prediction from either the main prediction list or from the candidate list
+    const Prediction& getPrediction(NonBufferHash) const;
+    Prediction& getPrediction(NonBufferHash);
+
+    std::optional<Plan> getExactMatch(NonBufferHash) const;
+    std::optional<NonBufferHash> getApproximateMatch(
+            const std::vector<const LayerState*>& layers) const;
+
+    void promoteIfCandidate(NonBufferHash);
+    void recordPredictedResult(PredictedPlan, const std::vector<const LayerState*>& layers,
+                               Plan result);
+    bool findSimilarPrediction(const std::vector<const LayerState*>& layers, Plan result);
+
+    void dumpPredictionsByFrequency(std::string&) const;
+
+    struct PromotionCandidate {
+        PromotionCandidate(NonBufferHash hash, Prediction&& prediction)
+              : hash(hash), prediction(std::move(prediction)) {}
+
+        NonBufferHash hash;
+        Prediction prediction;
+    };
+
+    static constexpr const size_t MAX_CANDIDATES = 4;
+    std::deque<PromotionCandidate> mCandidates;
+    decltype(mCandidates)::const_iterator getCandidateEntryByHash(NonBufferHash hash) const {
+        const auto candidateMatches = [&](const PromotionCandidate& candidate) {
+            return candidate.hash == hash;
+        };
+
+        return std::find_if(mCandidates.cbegin(), mCandidates.cend(), candidateMatches);
+    }
+
+    std::unordered_map<NonBufferHash, Prediction> mPredictions;
+    std::unordered_map<Plan, std::vector<NonBufferHash>> mSimilarStacks;
+
+    struct ApproximateStack {
+        ApproximateStack(NonBufferHash hash, LayerStack::ApproximateMatch match)
+              : hash(hash), match(match) {}
+
+        bool operator==(const ApproximateStack& other) const {
+            return hash == other.hash && match == other.match;
+        }
+
+        NonBufferHash hash;
+        LayerStack::ApproximateMatch match;
+    };
+
+    std::vector<ApproximateStack> mApproximateStacks;
+
+    mutable size_t mExactHitCount = 0;
+    mutable size_t mApproximateHitCount = 0;
+    mutable size_t mMissCount = 0;
+};
+
+} // namespace android::compositionengine::impl::planner
diff --git a/services/surfaceflinger/CompositionEngine/include/compositionengine/mock/LayerFE.h b/services/surfaceflinger/CompositionEngine/include/compositionengine/mock/LayerFE.h
index 45891a7..dde8999 100644
--- a/services/surfaceflinger/CompositionEngine/include/compositionengine/mock/LayerFE.h
+++ b/services/surfaceflinger/CompositionEngine/include/compositionengine/mock/LayerFE.h
@@ -42,6 +42,7 @@
     MOCK_METHOD1(onLayerDisplayed, void(const sp<Fence>&));
 
     MOCK_CONST_METHOD0(getDebugName, const char*());
+    MOCK_CONST_METHOD0(getSequence, int32_t());
 };
 
 } // namespace android::compositionengine::mock
diff --git a/services/surfaceflinger/CompositionEngine/include/compositionengine/mock/Output.h b/services/surfaceflinger/CompositionEngine/include/compositionengine/mock/Output.h
index 95db4da..5aa53e5 100644
--- a/services/surfaceflinger/CompositionEngine/include/compositionengine/mock/Output.h
+++ b/services/surfaceflinger/CompositionEngine/include/compositionengine/mock/Output.h
@@ -45,6 +45,7 @@
     MOCK_METHOD1(setColorProfile, void(const ColorProfile&));
 
     MOCK_CONST_METHOD1(dump, void(std::string&));
+    MOCK_CONST_METHOD2(dumpPlannerInfo, void(const Vector<String16>&, std::string&));
     MOCK_CONST_METHOD0(getName, const std::string&());
     MOCK_METHOD1(setName, void(const std::string&));
 
@@ -85,7 +86,9 @@
     MOCK_METHOD1(setReleasedLayers, void(const compositionengine::CompositionRefreshArgs&));
 
     MOCK_CONST_METHOD1(updateLayerStateFromFE, void(const CompositionRefreshArgs&));
-    MOCK_METHOD1(updateAndWriteCompositionState, void(const CompositionRefreshArgs&));
+    MOCK_METHOD1(updateCompositionState, void(const CompositionRefreshArgs&));
+    MOCK_METHOD0(planComposition, void());
+    MOCK_METHOD1(writeCompositionState, void(const CompositionRefreshArgs&));
     MOCK_METHOD1(updateColorProfile, void(const compositionengine::CompositionRefreshArgs&));
 
     MOCK_METHOD0(beginFrame, void());
@@ -104,6 +107,7 @@
     MOCK_CONST_METHOD0(getSkipColorTransform, bool());
 
     MOCK_METHOD0(postFramebuffer, void());
+    MOCK_METHOD0(renderCachedSets, void());
     MOCK_METHOD0(presentAndGetFrameFences, compositionengine::Output::FrameFences());
 
     MOCK_METHOD3(generateClientCompositionRequests,
diff --git a/services/surfaceflinger/CompositionEngine/include/compositionengine/mock/OutputLayer.h b/services/surfaceflinger/CompositionEngine/include/compositionengine/mock/OutputLayer.h
index 81e1fc7..2454ff7 100644
--- a/services/surfaceflinger/CompositionEngine/include/compositionengine/mock/OutputLayer.h
+++ b/services/surfaceflinger/CompositionEngine/include/compositionengine/mock/OutputLayer.h
@@ -39,7 +39,7 @@
     MOCK_METHOD0(editState, impl::OutputLayerCompositionState&());
 
     MOCK_METHOD3(updateCompositionState, void(bool, bool, ui::Transform::RotationFlags));
-    MOCK_METHOD1(writeStateToHWC, void(bool));
+    MOCK_METHOD2(writeStateToHWC, void(bool, bool));
     MOCK_CONST_METHOD0(writeCursorPositionToHWC, void());
 
     MOCK_CONST_METHOD0(getHwcLayer, HWC2::Layer*());
@@ -49,6 +49,7 @@
     MOCK_METHOD0(prepareForDeviceLayerRequests, void());
     MOCK_METHOD1(applyDeviceLayerRequest, void(Hwc2::IComposerClient::LayerRequest request));
     MOCK_CONST_METHOD0(needsFiltering, bool());
+    MOCK_CONST_METHOD0(getOverrideCompositionList, std::vector<LayerFE::LayerSettings>());
 
     MOCK_CONST_METHOD1(dump, void(std::string&));
 };
diff --git a/services/surfaceflinger/CompositionEngine/src/Output.cpp b/services/surfaceflinger/CompositionEngine/src/Output.cpp
index 3907ac5..dc1aacc 100644
--- a/services/surfaceflinger/CompositionEngine/src/Output.cpp
+++ b/services/surfaceflinger/CompositionEngine/src/Output.cpp
@@ -27,6 +27,9 @@
 #include <compositionengine/impl/OutputCompositionState.h>
 #include <compositionengine/impl/OutputLayer.h>
 #include <compositionengine/impl/OutputLayerCompositionState.h>
+#include <compositionengine/impl/planner/Planner.h>
+
+#include <SurfaceFlingerProperties.sysprop.h>
 
 // TODO(b/129481165): remove the #pragma below and fix conversion issues
 #pragma clang diagnostic push
@@ -38,6 +41,7 @@
 // TODO(b/129481165): remove the #pragma below and fix conversion issues
 #pragma clang diagnostic pop // ignored "-Wconversion"
 
+#include <android-base/properties.h>
 #include <ui/DebugUtils.h>
 #include <ui/HdrCapabilities.h>
 #include <utils/Trace.h>
@@ -50,6 +54,18 @@
 
 namespace impl {
 
+Output::Output() {
+    const bool enableLayerCaching = [] {
+        const bool enable =
+                android::sysprop::SurfaceFlingerProperties::enable_layer_caching().value_or(false);
+        return base::GetBoolProperty(std::string("debug.sf.enable_layer_caching"), enable);
+    }();
+
+    if (enableLayerCaching) {
+        mPlanner = std::make_unique<planner::Planner>();
+    }
+}
+
 namespace {
 
 template <typename T>
@@ -182,6 +198,10 @@
     const Rect newOrientedBounds(orientedSize);
     state.orientedDisplaySpace.bounds = newOrientedBounds;
 
+    if (mPlanner) {
+        mPlanner->setDisplaySize(size);
+    }
+
     dirtyEntireOutput();
 }
 
@@ -269,6 +289,15 @@
     }
 }
 
+void Output::dumpPlannerInfo(const Vector<String16>& args, std::string& out) const {
+    if (!mPlanner) {
+        base::StringAppendF(&out, "Planner is disabled\n");
+        return;
+    }
+    base::StringAppendF(&out, "Planner info for display [%s]\n", mName.c_str());
+    mPlanner->dump(args, out);
+}
+
 compositionengine::DisplayColorProfile* Output::getDisplayColorProfile() const {
     return mDisplayColorProfile.get();
 }
@@ -292,7 +321,11 @@
 
 void Output::setRenderSurface(std::unique_ptr<compositionengine::RenderSurface> surface) {
     mRenderSurface = std::move(surface);
-    editState().framebufferSpace.bounds = Rect(mRenderSurface->getSize());
+    const auto size = mRenderSurface->getSize();
+    editState().framebufferSpace.bounds = Rect(size);
+    if (mPlanner) {
+        mPlanner->setDisplaySize(size);
+    }
     dirtyEntireOutput();
 }
 
@@ -368,13 +401,16 @@
     ALOGV(__FUNCTION__);
 
     updateColorProfile(refreshArgs);
-    updateAndWriteCompositionState(refreshArgs);
+    updateCompositionState(refreshArgs);
+    planComposition();
+    writeCompositionState(refreshArgs);
     setColorTransform(refreshArgs);
     beginFrame();
     prepareFrame();
     devOptRepaintFlash(refreshArgs);
     finishFrame(refreshArgs);
     postFramebuffer();
+    renderCachedSets();
 }
 
 void Output::rebuildLayerStacks(const compositionengine::CompositionRefreshArgs& refreshArgs,
@@ -632,8 +668,7 @@
     }
 }
 
-void Output::updateAndWriteCompositionState(
-        const compositionengine::CompositionRefreshArgs& refreshArgs) {
+void Output::updateCompositionState(const compositionengine::CompositionRefreshArgs& refreshArgs) {
     ATRACE_CALL();
     ALOGV(__FUNCTION__);
 
@@ -653,9 +688,45 @@
         if (mLayerRequestingBackgroundBlur == layer) {
             forceClientComposition = false;
         }
+    }
+}
 
-        // Send the updated state to the HWC, if appropriate.
-        layer->writeStateToHWC(refreshArgs.updatingGeometryThisFrame);
+void Output::planComposition() {
+    if (!mPlanner || !getState().isEnabled) {
+        return;
+    }
+
+    ATRACE_CALL();
+    ALOGV(__FUNCTION__);
+
+    mPlanner->plan(getOutputLayersOrderedByZ());
+}
+
+void Output::writeCompositionState(const compositionengine::CompositionRefreshArgs& refreshArgs) {
+    ATRACE_CALL();
+    ALOGV(__FUNCTION__);
+
+    if (!getState().isEnabled) {
+        return;
+    }
+
+    sp<GraphicBuffer> previousOverride = nullptr;
+    for (auto* layer : getOutputLayersOrderedByZ()) {
+        bool skipLayer = false;
+        if (layer->getState().overrideInfo.buffer != nullptr) {
+            if (previousOverride != nullptr &&
+                layer->getState().overrideInfo.buffer == previousOverride) {
+                ALOGV("Skipping redundant buffer");
+                skipLayer = true;
+            }
+            previousOverride = layer->getState().overrideInfo.buffer;
+        }
+
+        // TODO(b/181172795): We now update geometry for all flattened layers. We should update it
+        // only when the geometry actually changes
+        const bool includeGeometry = refreshArgs.updatingGeometryThisFrame ||
+                layer->getState().overrideInfo.buffer != nullptr || skipLayer;
+        layer->writeStateToHWC(includeGeometry, skipLayer);
     }
 }
 
@@ -826,6 +897,10 @@
 
     chooseCompositionStrategy();
 
+    if (mPlanner) {
+        mPlanner->reportFinalPlan(getOutputLayersOrderedByZ());
+    }
+
     mRenderSurface->prepareFrame(outputState.usesClientComposition,
                                  outputState.usesDeviceComposition);
 }
@@ -1075,10 +1150,16 @@
                                    .realContentIsVisible = realContentIsVisible,
                                    .clearContent = !clientComposition,
                                    .disableBlurs = disableBlurs};
-            std::vector<LayerFE::LayerSettings> results =
-                    layerFE.prepareClientCompositionList(targetSettings);
-            if (realContentIsVisible && !results.empty()) {
-                layer->editState().clientCompositionTimestamp = systemTime();
+
+            std::vector<LayerFE::LayerSettings> results;
+            if (layer->getState().overrideInfo.buffer != nullptr) {
+                results = layer->getOverrideCompositionList();
+                ALOGV("Replacing [%s] with override in RE", layer->getLayerFE().getDebugName());
+            } else {
+                results = layerFE.prepareClientCompositionList(targetSettings);
+                if (realContentIsVisible && !results.empty()) {
+                    layer->editState().clientCompositionTimestamp = systemTime();
+                }
             }
 
             clientCompositionLayers.insert(clientCompositionLayers.end(),
@@ -1169,6 +1250,12 @@
     mReleasedLayers.clear();
 }
 
+void Output::renderCachedSets() {
+    if (mPlanner) {
+        mPlanner->renderCachedSets(getCompositionEngine().getRenderEngine());
+    }
+}
+
 void Output::dirtyEntireOutput() {
     auto& outputState = editState();
     outputState.dirtyRegion.set(outputState.displaySpace.bounds);
diff --git a/services/surfaceflinger/CompositionEngine/src/OutputLayer.cpp b/services/surfaceflinger/CompositionEngine/src/OutputLayer.cpp
index 0faab6f..54784a2 100644
--- a/services/surfaceflinger/CompositionEngine/src/OutputLayer.cpp
+++ b/services/surfaceflinger/CompositionEngine/src/OutputLayer.cpp
@@ -16,7 +16,6 @@
 
 #include <android-base/stringprintf.h>
 #include <compositionengine/DisplayColorProfile.h>
-#include <compositionengine/LayerFE.h>
 #include <compositionengine/LayerFECompositionState.h>
 #include <compositionengine/Output.h>
 #include <compositionengine/impl/OutputCompositionState.h>
@@ -313,7 +312,7 @@
     }
 }
 
-void OutputLayer::writeStateToHWC(bool includeGeometry) {
+void OutputLayer::writeStateToHWC(bool includeGeometry, bool skipLayer) {
     const auto& state = getState();
     // Skip doing this if there is no HWC interface
     if (!state.hwc) {
@@ -336,7 +335,8 @@
 
     if (includeGeometry) {
         writeOutputDependentGeometryStateToHWC(hwcLayer.get(), requestedCompositionType);
-        writeOutputIndependentGeometryStateToHWC(hwcLayer.get(), *outputIndependentState);
+        writeOutputIndependentGeometryStateToHWC(hwcLayer.get(), *outputIndependentState,
+                                                 skipLayer);
     }
 
     writeOutputDependentPerFrameStateToHWC(hwcLayer.get());
@@ -352,23 +352,27 @@
         HWC2::Layer* hwcLayer, hal::Composition requestedCompositionType) {
     const auto& outputDependentState = getState();
 
-    if (auto error = hwcLayer->setDisplayFrame(outputDependentState.displayFrame);
-        error != hal::Error::NONE) {
-        ALOGE("[%s] Failed to set display frame [%d, %d, %d, %d]: %s (%d)",
-              getLayerFE().getDebugName(), outputDependentState.displayFrame.left,
-              outputDependentState.displayFrame.top, outputDependentState.displayFrame.right,
-              outputDependentState.displayFrame.bottom, to_string(error).c_str(),
-              static_cast<int32_t>(error));
+    Rect displayFrame = outputDependentState.displayFrame;
+    FloatRect sourceCrop = outputDependentState.sourceCrop;
+    if (outputDependentState.overrideInfo.buffer != nullptr) { // adyabr
+        displayFrame = outputDependentState.overrideInfo.displayFrame;
+        sourceCrop = displayFrame.toFloatRect();
     }
 
-    if (auto error = hwcLayer->setSourceCrop(outputDependentState.sourceCrop);
-        error != hal::Error::NONE) {
+    ALOGV("Writing display frame [%d, %d, %d, %d]", displayFrame.left, displayFrame.top,
+          displayFrame.right, displayFrame.bottom);
+
+    if (auto error = hwcLayer->setDisplayFrame(displayFrame); error != hal::Error::NONE) {
+        ALOGE("[%s] Failed to set display frame [%d, %d, %d, %d]: %s (%d)",
+              getLayerFE().getDebugName(), displayFrame.left, displayFrame.top, displayFrame.right,
+              displayFrame.bottom, to_string(error).c_str(), static_cast<int32_t>(error));
+    }
+
+    if (auto error = hwcLayer->setSourceCrop(sourceCrop); error != hal::Error::NONE) {
         ALOGE("[%s] Failed to set source crop [%.3f, %.3f, %.3f, %.3f]: "
               "%s (%d)",
-              getLayerFE().getDebugName(), outputDependentState.sourceCrop.left,
-              outputDependentState.sourceCrop.top, outputDependentState.sourceCrop.right,
-              outputDependentState.sourceCrop.bottom, to_string(error).c_str(),
-              static_cast<int32_t>(error));
+              getLayerFE().getDebugName(), sourceCrop.left, sourceCrop.top, sourceCrop.right,
+              sourceCrop.bottom, to_string(error).c_str(), static_cast<int32_t>(error));
     }
 
     if (auto error = hwcLayer->setZOrder(outputDependentState.z); error != hal::Error::NONE) {
@@ -389,7 +393,8 @@
 }
 
 void OutputLayer::writeOutputIndependentGeometryStateToHWC(
-        HWC2::Layer* hwcLayer, const LayerFECompositionState& outputIndependentState) {
+        HWC2::Layer* hwcLayer, const LayerFECompositionState& outputIndependentState,
+        bool skipLayer) {
     if (auto error = hwcLayer->setBlendMode(outputIndependentState.blendMode);
         error != hal::Error::NONE) {
         ALOGE("[%s] Failed to set blend mode %s: %s (%d)", getLayerFE().getDebugName(),
@@ -397,10 +402,12 @@
               static_cast<int32_t>(error));
     }
 
-    if (auto error = hwcLayer->setPlaneAlpha(outputIndependentState.alpha);
-        error != hal::Error::NONE) {
-        ALOGE("[%s] Failed to set plane alpha %.3f: %s (%d)", getLayerFE().getDebugName(),
-              outputIndependentState.alpha, to_string(error).c_str(), static_cast<int32_t>(error));
+    const float alpha = skipLayer ? 0.0f : outputIndependentState.alpha;
+    ALOGV("Writing alpha %f", alpha);
+
+    if (auto error = hwcLayer->setPlaneAlpha(alpha); error != hal::Error::NONE) {
+        ALOGE("[%s] Failed to set plane alpha %.3f: %s (%d)", getLayerFE().getDebugName(), alpha,
+              to_string(error).c_str(), static_cast<int32_t>(error));
     }
 
     for (const auto& [name, entry] : outputIndependentState.metadata) {
@@ -509,19 +516,26 @@
               to_string(error).c_str(), static_cast<int32_t>(error));
     }
 
+    sp<GraphicBuffer> buffer = outputIndependentState.buffer;
+    sp<Fence> acquireFence = outputIndependentState.acquireFence;
+    if (getState().overrideInfo.buffer != nullptr) {
+        buffer = getState().overrideInfo.buffer;
+        acquireFence = getState().overrideInfo.acquireFence;
+    }
+
+    ALOGV("Writing buffer %p", buffer.get());
+
     uint32_t hwcSlot = 0;
     sp<GraphicBuffer> hwcBuffer;
     // We need access to the output-dependent state for the buffer cache there,
     // though otherwise the buffer is not output-dependent.
-    editState().hwc->hwcBufferCache.getHwcBuffer(outputIndependentState.bufferSlot,
-                                                 outputIndependentState.buffer, &hwcSlot,
-                                                 &hwcBuffer);
+    editState().hwc->hwcBufferCache.getHwcBuffer(outputIndependentState.bufferSlot, buffer,
+                                                 &hwcSlot, &hwcBuffer);
 
-    if (auto error = hwcLayer->setBuffer(hwcSlot, hwcBuffer, outputIndependentState.acquireFence);
+    if (auto error = hwcLayer->setBuffer(hwcSlot, hwcBuffer, acquireFence);
         error != hal::Error::NONE) {
-        ALOGE("[%s] Failed to set buffer %p: %s (%d)", getLayerFE().getDebugName(),
-              outputIndependentState.buffer->handle, to_string(error).c_str(),
-              static_cast<int32_t>(error));
+        ALOGE("[%s] Failed to set buffer %p: %s (%d)", getLayerFE().getDebugName(), buffer->handle,
+              to_string(error).c_str(), static_cast<int32_t>(error));
     }
 }
 
@@ -652,6 +666,26 @@
             sourceCrop.getWidth() != displayFrame.getWidth();
 }
 
+std::vector<LayerFE::LayerSettings> OutputLayer::getOverrideCompositionList() const {
+    if (getState().overrideInfo.buffer == nullptr) {
+        return {};
+    }
+
+    LayerFE::LayerSettings settings;
+    settings.geometry = renderengine::Geometry{
+            .boundaries = getState().overrideInfo.displayFrame.toFloatRect(),
+    };
+    settings.bufferId = getState().overrideInfo.buffer->getId();
+    settings.source =
+            renderengine::PixelSource{.buffer = renderengine::Buffer{
+                                              .buffer = getState().overrideInfo.buffer,
+                                              .fence = getState().overrideInfo.acquireFence,
+                                      }};
+    settings.alpha = 1.0f;
+
+    return {static_cast<LayerFE::LayerSettings>(settings)};
+}
+
 void OutputLayer::dump(std::string& out) const {
     using android::base::StringAppendF;
 
diff --git a/services/surfaceflinger/CompositionEngine/src/planner/CachedSet.cpp b/services/surfaceflinger/CompositionEngine/src/planner/CachedSet.cpp
new file mode 100644
index 0000000..ab3fe9e
--- /dev/null
+++ b/services/surfaceflinger/CompositionEngine/src/planner/CachedSet.cpp
@@ -0,0 +1,243 @@
+/*
+ * Copyright 2021 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.
+ */
+
+#undef LOG_TAG
+#define LOG_TAG "Planner"
+// #define LOG_NDEBUG 0
+
+#include <android-base/properties.h>
+#include <compositionengine/impl/planner/CachedSet.h>
+#include <math/HashCombine.h>
+#include <renderengine/DisplaySettings.h>
+#include <renderengine/RenderEngine.h>
+
+namespace android::compositionengine::impl::planner {
+
+const bool CachedSet::sDebugHighlighLayers =
+        base::GetBoolProperty(std::string("debug.sf.layer_caching_highlight"), false);
+
+std::string durationString(std::chrono::milliseconds duration) {
+    using namespace std::chrono_literals;
+
+    std::string result;
+
+    if (duration >= 1h) {
+        const auto hours = std::chrono::duration_cast<std::chrono::hours>(duration);
+        base::StringAppendF(&result, "%d hr ", static_cast<int>(hours.count()));
+        duration -= hours;
+    }
+    if (duration >= 1min) {
+        const auto minutes = std::chrono::duration_cast<std::chrono::minutes>(duration);
+        base::StringAppendF(&result, "%d min ", static_cast<int>(minutes.count()));
+        duration -= minutes;
+    }
+    base::StringAppendF(&result, "%.3f sec ", duration.count() / 1000.0f);
+
+    return result;
+}
+
+CachedSet::Layer::Layer(const LayerState* state, std::chrono::steady_clock::time_point lastUpdate)
+      : mState(state), mHash(state->getHash(LayerStateField::Buffer)), mLastUpdate(lastUpdate) {}
+
+CachedSet::CachedSet(const LayerState* layer, std::chrono::steady_clock::time_point lastUpdate)
+      : mFingerprint(layer->getHash(LayerStateField::Buffer)), mLastUpdate(lastUpdate) {
+    addLayer(layer, lastUpdate);
+}
+
+CachedSet::CachedSet(Layer layer)
+      : mFingerprint(layer.getHash()),
+        mLastUpdate(layer.getLastUpdate()),
+        mBounds(layer.getDisplayFrame()) {
+    mLayers.emplace_back(std::move(layer));
+}
+
+void CachedSet::addLayer(const LayerState* layer,
+                         std::chrono::steady_clock::time_point lastUpdate) {
+    mLayers.emplace_back(layer, lastUpdate);
+
+    Region boundingRegion;
+    boundingRegion.orSelf(mBounds);
+    boundingRegion.orSelf(layer->getDisplayFrame());
+    mBounds = boundingRegion.getBounds();
+}
+
+NonBufferHash CachedSet::getNonBufferHash() const {
+    if (mLayers.size() == 1) {
+        return mFingerprint;
+    }
+
+    // TODO(b/181192080): Add all fields which contribute to geometry of override layer (e.g.,
+    // dataspace)
+    size_t hash = 0;
+    android::hashCombineSingle(hash, mBounds);
+    return hash;
+}
+
+size_t CachedSet::getComponentDisplayCost() const {
+    size_t displayCost = 0;
+
+    for (const Layer& layer : mLayers) {
+        displayCost += static_cast<size_t>(layer.getDisplayFrame().width() *
+                                           layer.getDisplayFrame().height());
+    }
+
+    return displayCost;
+}
+
+size_t CachedSet::getCreationCost() const {
+    if (mLayers.size() == 1) {
+        return 0;
+    }
+
+    // Reads
+    size_t creationCost = getComponentDisplayCost();
+
+    // Write - assumes that the output buffer only gets written once per pixel
+    creationCost += static_cast<size_t>(mBounds.width() * mBounds.height());
+
+    return creationCost;
+}
+
+size_t CachedSet::getDisplayCost() const {
+    return static_cast<size_t>(mBounds.width() * mBounds.height());
+}
+
+bool CachedSet::hasBufferUpdate(std::vector<const LayerState*>::const_iterator layers) const {
+    for (const Layer& layer : mLayers) {
+        if (layer.getFramesSinceBufferUpdate() == 0) {
+            return true;
+        }
+        ++layers;
+    }
+    return false;
+}
+
+bool CachedSet::hasReadyBuffer() const {
+    return mBuffer != nullptr && mDrawFence->getStatus() == Fence::Status::Signaled;
+}
+
+std::vector<CachedSet> CachedSet::decompose() const {
+    std::vector<CachedSet> layers;
+
+    std::transform(mLayers.begin(), mLayers.end(), std::back_inserter(layers),
+                   [](Layer layer) { return CachedSet(std::move(layer)); });
+
+    return layers;
+}
+
+void CachedSet::updateAge(std::chrono::steady_clock::time_point now) {
+    LOG_ALWAYS_FATAL_IF(mLayers.size() > 1, "[%s] This should only be called on single-layer sets",
+                        __func__);
+
+    if (mLayers[0].getFramesSinceBufferUpdate() == 0) {
+        mLastUpdate = now;
+        mAge = 0;
+    }
+}
+
+void CachedSet::render(renderengine::RenderEngine& renderEngine) {
+    renderengine::DisplaySettings displaySettings{
+            .physicalDisplay = Rect(0, 0, mBounds.getWidth(), mBounds.getHeight()),
+            .clip = mBounds,
+    };
+
+    Region clearRegion = Region::INVALID_REGION;
+    Rect viewport = mBounds;
+    LayerFE::ClientCompositionTargetSettings targetSettings{
+            .clip = Region(mBounds),
+            .needsFiltering = false,
+            .isSecure = true,
+            .supportsProtectedContent = false,
+            .clearRegion = clearRegion,
+            .viewport = viewport,
+            // TODO(181192086): Propagate the Output's dataspace instead of using UNKNOWN
+            .dataspace = ui::Dataspace::UNKNOWN,
+            .realContentIsVisible = true,
+            .clearContent = false,
+            .disableBlurs = false,
+    };
+
+    std::vector<renderengine::LayerSettings> layerSettings;
+    for (const auto& layer : mLayers) {
+        const auto clientCompositionList =
+                layer.getState()->getOutputLayer()->getLayerFE().prepareClientCompositionList(
+                        targetSettings);
+        layerSettings.insert(layerSettings.end(), clientCompositionList.cbegin(),
+                             clientCompositionList.cend());
+    }
+
+    std::vector<const renderengine::LayerSettings*> layerSettingsPointers;
+    std::transform(layerSettings.cbegin(), layerSettings.cend(),
+                   std::back_inserter(layerSettingsPointers),
+                   [](const renderengine::LayerSettings& settings) { return &settings; });
+
+    if (sDebugHighlighLayers) {
+        renderengine::LayerSettings highlight{
+                .geometry =
+                        renderengine::Geometry{
+                                .boundaries = FloatRect(0.0f, 0.0f,
+                                                        static_cast<float>(mBounds.getWidth()),
+                                                        static_cast<float>(mBounds.getHeight())),
+                        },
+                .source =
+                        renderengine::PixelSource{
+                                .solidColor = half3(0.25f, 0.0f, 0.5f),
+                        },
+                .alpha = half(0.05f),
+        };
+
+        layerSettingsPointers.emplace_back(&highlight);
+    }
+
+    const uint64_t usageFlags = GraphicBuffer::USAGE_HW_RENDER | GraphicBuffer::USAGE_HW_COMPOSER |
+            GraphicBuffer::USAGE_HW_TEXTURE;
+    sp<GraphicBuffer> buffer = new GraphicBuffer(static_cast<uint32_t>(mBounds.getWidth()),
+                                                 static_cast<uint32_t>(mBounds.getHeight()),
+                                                 HAL_PIXEL_FORMAT_RGBA_8888, 1, usageFlags);
+    LOG_ALWAYS_FATAL_IF(buffer->initCheck() != OK);
+    base::unique_fd drawFence;
+    status_t result = renderEngine.drawLayers(displaySettings, layerSettingsPointers, buffer, false,
+                                              base::unique_fd(), &drawFence);
+
+    if (result == NO_ERROR) {
+        mBuffer = buffer;
+        mDrawFence = new Fence(drawFence.release());
+    }
+}
+
+void CachedSet::dump(std::string& result) const {
+    const auto now = std::chrono::steady_clock::now();
+
+    const auto lastUpdate =
+            std::chrono::duration_cast<std::chrono::milliseconds>(now - mLastUpdate);
+    base::StringAppendF(&result, "  + Fingerprint %016zx, last update %sago, age %zd\n",
+                        mFingerprint, durationString(lastUpdate).c_str(), mAge);
+
+    if (mLayers.size() == 1) {
+        base::StringAppendF(&result, "    Layer [%s]\n", mLayers[0].getName().c_str());
+        base::StringAppendF(&result, "    Buffer %p", mLayers[0].getBuffer().get());
+    } else {
+        result.append("    Cached set of:");
+        for (const Layer& layer : mLayers) {
+            base::StringAppendF(&result, "\n      Layer [%s]", layer.getName().c_str());
+        }
+    }
+
+    base::StringAppendF(&result, "\n    Creation cost: %zd", getCreationCost());
+    base::StringAppendF(&result, "\n    Display cost: %zd\n", getDisplayCost());
+}
+
+} // namespace android::compositionengine::impl::planner
diff --git a/services/surfaceflinger/CompositionEngine/src/planner/Flattener.cpp b/services/surfaceflinger/CompositionEngine/src/planner/Flattener.cpp
new file mode 100644
index 0000000..0c09714
--- /dev/null
+++ b/services/surfaceflinger/CompositionEngine/src/planner/Flattener.cpp
@@ -0,0 +1,355 @@
+/*
+ * Copyright 2021 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.
+ */
+
+#undef LOG_TAG
+#define LOG_TAG "Planner"
+// #define LOG_NDEBUG 0
+
+#include <compositionengine/impl/planner/Flattener.h>
+#include <compositionengine/impl/planner/LayerState.h>
+#include <compositionengine/impl/planner/Predictor.h>
+
+using time_point = std::chrono::steady_clock::time_point;
+using namespace std::chrono_literals;
+
+namespace android::compositionengine::impl::planner {
+
+NonBufferHash Flattener::flattenLayers(const std::vector<const LayerState*>& layers,
+                                       NonBufferHash hash) {
+    const auto now = std::chrono::steady_clock::now();
+
+    const size_t unflattenedDisplayCost = calculateDisplayCost(layers);
+    mUnflattenedDisplayCost += unflattenedDisplayCost;
+
+    if (mCurrentGeometry != hash) {
+        resetActivities(hash, now);
+        mFlattenedDisplayCost += unflattenedDisplayCost;
+        return hash;
+    }
+
+    ++mInitialLayerCounts[layers.size()];
+
+    if (mergeWithCachedSets(layers, now)) {
+        hash = mLayersHash;
+    }
+
+    ++mFinalLayerCounts[mLayers.size()];
+
+    buildCachedSets(now);
+
+    return hash;
+}
+
+void Flattener::renderCachedSets(renderengine::RenderEngine& renderEngine) {
+    if (!mNewCachedSet) {
+        return;
+    }
+
+    mNewCachedSet->render(renderEngine);
+}
+
+void Flattener::reset() {
+    resetActivities(0, std::chrono::steady_clock::now());
+
+    mUnflattenedDisplayCost = 0;
+    mFlattenedDisplayCost = 0;
+    mInitialLayerCounts.clear();
+    mFinalLayerCounts.clear();
+    mCachedSetCreationCount = 0;
+    mCachedSetCreationCost = 0;
+    mInvalidatedCachedSetAges.clear();
+}
+
+void Flattener::dump(std::string& result) const {
+    const auto now = std::chrono::steady_clock::now();
+
+    base::StringAppendF(&result, "Flattener state:\n");
+
+    result.append("\n  Statistics:\n");
+
+    result.append("    Display cost (in screen-size buffers):\n");
+    const size_t displayArea = static_cast<size_t>(mDisplaySize.width * mDisplaySize.height);
+    base::StringAppendF(&result, "      Unflattened: %.2f\n",
+                        static_cast<float>(mUnflattenedDisplayCost) / displayArea);
+    base::StringAppendF(&result, "      Flattened:   %.2f\n",
+                        static_cast<float>(mFlattenedDisplayCost) / displayArea);
+
+    const auto compareLayerCounts = [](const std::pair<size_t, size_t>& left,
+                                       const std::pair<size_t, size_t>& right) {
+        return left.first < right.first;
+    };
+
+    const size_t maxLayerCount = std::max_element(mInitialLayerCounts.cbegin(),
+                                                  mInitialLayerCounts.cend(), compareLayerCounts)
+                                         ->first;
+
+    result.append("\n    Initial counts:\n");
+    for (size_t count = 1; count < maxLayerCount; ++count) {
+        size_t initial = mInitialLayerCounts.count(count) > 0 ? mInitialLayerCounts.at(count) : 0;
+        base::StringAppendF(&result, "      % 2zd: %zd\n", count, initial);
+    }
+
+    result.append("\n    Final counts:\n");
+    for (size_t count = 1; count < maxLayerCount; ++count) {
+        size_t final = mFinalLayerCounts.count(count) > 0 ? mFinalLayerCounts.at(count) : 0;
+        base::StringAppendF(&result, "      % 2zd: %zd\n", count, final);
+    }
+
+    base::StringAppendF(&result, "\n    Cached sets created: %zd\n", mCachedSetCreationCount);
+    base::StringAppendF(&result, "    Cost: %.2f\n",
+                        static_cast<float>(mCachedSetCreationCost) / displayArea);
+
+    const auto lastUpdate =
+            std::chrono::duration_cast<std::chrono::milliseconds>(now - mLastGeometryUpdate);
+    base::StringAppendF(&result, "\n  Current hash %016zx, last update %sago\n\n", mCurrentGeometry,
+                        durationString(lastUpdate).c_str());
+
+    result.append("  Current layers:");
+    for (const CachedSet& layer : mLayers) {
+        result.append("\n");
+        layer.dump(result);
+    }
+}
+
+size_t Flattener::calculateDisplayCost(const std::vector<const LayerState*>& layers) const {
+    Region coveredRegion;
+    size_t displayCost = 0;
+    bool hasClientComposition = false;
+
+    for (const LayerState* layer : layers) {
+        coveredRegion.orSelf(layer->getDisplayFrame());
+
+        // Regardless of composition type, we always have to read each input once
+        displayCost += static_cast<size_t>(layer->getDisplayFrame().width() *
+                                           layer->getDisplayFrame().height());
+
+        hasClientComposition |= layer->getCompositionType() == hal::Composition::CLIENT;
+    }
+
+    if (hasClientComposition) {
+        // If there is client composition, the client target buffer has to be both written by the
+        // GPU and read by the DPU, so we pay its cost twice
+        displayCost += 2 *
+                static_cast<size_t>(coveredRegion.bounds().width() *
+                                    coveredRegion.bounds().height());
+    }
+
+    return displayCost;
+}
+
+void Flattener::resetActivities(NonBufferHash hash, time_point now) {
+    ALOGV("[%s]", __func__);
+
+    mCurrentGeometry = hash;
+    mLastGeometryUpdate = now;
+
+    for (const CachedSet& cachedSet : mLayers) {
+        if (cachedSet.getLayerCount() > 1) {
+            ++mInvalidatedCachedSetAges[cachedSet.getAge()];
+        }
+    }
+
+    mLayers.clear();
+
+    if (mNewCachedSet) {
+        ++mInvalidatedCachedSetAges[mNewCachedSet->getAge()];
+        mNewCachedSet = std::nullopt;
+    }
+}
+
+void Flattener::updateLayersHash() {
+    size_t hash = 0;
+    for (const auto& layer : mLayers) {
+        android::hashCombineSingleHashed(hash, layer.getNonBufferHash());
+    }
+    mLayersHash = hash;
+}
+
+bool Flattener::mergeWithCachedSets(const std::vector<const LayerState*>& layers, time_point now) {
+    std::vector<CachedSet> merged;
+
+    if (mLayers.empty()) {
+        merged.reserve(layers.size());
+        for (const LayerState* layer : layers) {
+            merged.emplace_back(layer, now);
+            mFlattenedDisplayCost += merged.back().getDisplayCost();
+        }
+        mLayers = std::move(merged);
+        return false;
+    }
+
+    ALOGV("[%s] Incoming layers:", __func__);
+    for (const LayerState* layer : layers) {
+        ALOGV("%s", layer->getName().c_str());
+    }
+
+    ALOGV("[%s] Current layers:", __func__);
+    for (const CachedSet& layer : mLayers) {
+        std::string dump;
+        layer.dump(dump);
+        ALOGV("%s", dump.c_str());
+    }
+
+    auto currentLayerIter = mLayers.begin();
+    auto incomingLayerIter = layers.begin();
+    while (incomingLayerIter != layers.end()) {
+        if (mNewCachedSet &&
+            mNewCachedSet->getFingerprint() ==
+                    (*incomingLayerIter)->getHash(LayerStateField::Buffer)) {
+            if (mNewCachedSet->hasBufferUpdate(incomingLayerIter)) {
+                ALOGV("[%s] Dropping new cached set", __func__);
+                ++mInvalidatedCachedSetAges[0];
+                mNewCachedSet = std::nullopt;
+            } else if (mNewCachedSet->hasReadyBuffer()) {
+                ALOGV("[%s] Found ready buffer", __func__);
+                size_t skipCount = mNewCachedSet->getLayerCount();
+                while (skipCount != 0) {
+                    const size_t layerCount = currentLayerIter->getLayerCount();
+                    for (size_t i = 0; i < layerCount; ++i) {
+                        OutputLayer::CompositionState& state =
+                                (*incomingLayerIter)->getOutputLayer()->editState();
+                        state.overrideInfo = {
+                                .buffer = mNewCachedSet->getBuffer(),
+                                .acquireFence = mNewCachedSet->getDrawFence(),
+                                .displayFrame = mNewCachedSet->getBounds(),
+                        };
+                        ++incomingLayerIter;
+                    }
+
+                    if (currentLayerIter->getLayerCount() > 1) {
+                        ++mInvalidatedCachedSetAges[currentLayerIter->getAge()];
+                    }
+                    ++currentLayerIter;
+
+                    skipCount -= layerCount;
+                }
+                merged.emplace_back(std::move(*mNewCachedSet));
+                mNewCachedSet = std::nullopt;
+                continue;
+            }
+        }
+
+        if (!currentLayerIter->hasBufferUpdate(incomingLayerIter)) {
+            currentLayerIter->incrementAge();
+            merged.emplace_back(*currentLayerIter);
+
+            // Skip the incoming layers corresponding to this valid current layer
+            const size_t layerCount = currentLayerIter->getLayerCount();
+            for (size_t i = 0; i < layerCount; ++i) {
+                OutputLayer::CompositionState& state =
+                        (*incomingLayerIter)->getOutputLayer()->editState();
+                state.overrideInfo = {
+                        .buffer = currentLayerIter->getBuffer(),
+                        .acquireFence = currentLayerIter->getDrawFence(),
+                        .displayFrame = currentLayerIter->getBounds(),
+                };
+                ++incomingLayerIter;
+            }
+        } else if (currentLayerIter->getLayerCount() > 1) {
+            // Break the current layer into its constituent layers
+            ++mInvalidatedCachedSetAges[currentLayerIter->getAge()];
+            for (CachedSet& layer : currentLayerIter->decompose()) {
+                layer.updateAge(now);
+                merged.emplace_back(layer);
+                ++incomingLayerIter;
+            }
+        } else {
+            currentLayerIter->updateAge(now);
+            merged.emplace_back(*currentLayerIter);
+            ++incomingLayerIter;
+        }
+        ++currentLayerIter;
+    }
+
+    for (const CachedSet& layer : merged) {
+        mFlattenedDisplayCost += layer.getDisplayCost();
+    }
+
+    mLayers = std::move(merged);
+    updateLayersHash();
+    return true;
+}
+
+void Flattener::buildCachedSets(time_point now) {
+    struct Run {
+        Run(std::vector<CachedSet>::const_iterator start, size_t length)
+              : start(start), length(length) {}
+
+        std::vector<CachedSet>::const_iterator start;
+        size_t length;
+    };
+
+    if (mLayers.empty()) {
+        ALOGV("[%s] No layers found, returning", __func__);
+        return;
+    }
+
+    std::vector<Run> runs;
+    bool isPartOfRun = false;
+    for (auto currentSet = mLayers.cbegin(); currentSet != mLayers.cend(); ++currentSet) {
+        if (now - currentSet->getLastUpdate() > kActiveLayerTimeout) {
+            // Layer is inactive
+            if (isPartOfRun) {
+                runs.back().length += currentSet->getLayerCount();
+            } else {
+                // Runs can't start with a non-buffer layer
+                if (currentSet->getFirstLayer().getBuffer() == nullptr) {
+                    ALOGV("[%s] Skipping initial non-buffer layer", __func__);
+                } else {
+                    runs.emplace_back(currentSet, currentSet->getLayerCount());
+                    isPartOfRun = true;
+                }
+            }
+        } else {
+            // Runs must be at least 2 sets long or there's nothing to combine
+            if (isPartOfRun && runs.back().start->getLayerCount() == runs.back().length) {
+                runs.pop_back();
+            }
+
+            isPartOfRun = false;
+        }
+    }
+
+    // Check for at least 2 sets one more time in case the set includes the last layer
+    if (isPartOfRun && runs.back().start->getLayerCount() == runs.back().length) {
+        runs.pop_back();
+    }
+
+    ALOGV("[%s] Found %zu candidate runs", __func__, runs.size());
+
+    if (runs.empty()) {
+        return;
+    }
+
+    mNewCachedSet.emplace(*runs[0].start);
+    mNewCachedSet->setLastUpdate(now);
+    auto currentSet = runs[0].start;
+    while (mNewCachedSet->getLayerCount() < runs[0].length) {
+        ++currentSet;
+        mNewCachedSet->append(*currentSet);
+    }
+
+    // TODO(b/181192467): Actually compute new LayerState vector and corresponding hash for each run
+    mPredictor.getPredictedPlan({}, 0);
+
+    ++mCachedSetCreationCount;
+    mCachedSetCreationCost += mNewCachedSet->getCreationCost();
+    std::string setDump;
+    mNewCachedSet->dump(setDump);
+    ALOGV("[%s] Added new cached set:\n%s", __func__, setDump.c_str());
+}
+
+} // namespace android::compositionengine::impl::planner
diff --git a/services/surfaceflinger/CompositionEngine/src/planner/LayerState.cpp b/services/surfaceflinger/CompositionEngine/src/planner/LayerState.cpp
new file mode 100644
index 0000000..7cf4819
--- /dev/null
+++ b/services/surfaceflinger/CompositionEngine/src/planner/LayerState.cpp
@@ -0,0 +1,158 @@
+/*
+ * Copyright 2021 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 <compositionengine/impl/planner/LayerState.h>
+
+namespace android::compositionengine::impl::planner {
+
+LayerState::LayerState(compositionengine::OutputLayer* layer) : mOutputLayer(layer) {
+    update(layer);
+}
+
+Flags<LayerStateField> LayerState::update(compositionengine::OutputLayer* layer) {
+    ALOGE_IF(layer != mOutputLayer, "[%s] Expected mOutputLayer to never change", __func__);
+
+    Flags<LayerStateField> differences;
+
+    // Update the unique fields as well, since we have to set them at least
+    // once from the OutputLayer
+    differences |= mId.update(layer);
+    differences |= mName.update(layer);
+
+    for (StateInterface* field : getNonUniqueFields()) {
+        differences |= field->update(layer);
+    }
+
+    return differences;
+}
+
+size_t LayerState::getHash(
+        Flags<LayerStateField> skipFields = static_cast<LayerStateField>(0)) const {
+    size_t hash = 0;
+    for (const StateInterface* field : getNonUniqueFields()) {
+        android::hashCombineSingleHashed(hash, field->getHash(skipFields));
+    }
+
+    return hash;
+}
+
+Flags<LayerStateField> LayerState::getDifferingFields(
+        const LayerState& other,
+        Flags<LayerStateField> skipFields = static_cast<LayerStateField>(0)) const {
+    Flags<LayerStateField> differences;
+    auto myFields = getNonUniqueFields();
+    auto otherFields = other.getNonUniqueFields();
+    for (size_t i = 0; i < myFields.size(); ++i) {
+        if (skipFields.test(myFields[i]->getField())) {
+            continue;
+        }
+
+        differences |= myFields[i]->getFieldIfDifferent(otherFields[i]);
+    }
+
+    return differences;
+}
+
+void LayerState::dump(std::string& result) const {
+    for (const StateInterface* field : getNonUniqueFields()) {
+        if (auto viewOpt = flag_name(field->getField()); viewOpt) {
+            base::StringAppendF(&result, "  %16s: ", std::string(*viewOpt).c_str());
+        } else {
+            result.append("<UNKNOWN FIELD>:\n");
+        }
+
+        bool first = true;
+        for (const std::string& line : field->toStrings()) {
+            base::StringAppendF(&result, "%s%s\n", first ? "" : "                    ",
+                                line.c_str());
+            first = false;
+        }
+    }
+    result.append("\n");
+}
+
+std::optional<std::string> LayerState::compare(const LayerState& other) const {
+    std::string result;
+
+    const auto& thisFields = getNonUniqueFields();
+    const auto& otherFields = other.getNonUniqueFields();
+    for (size_t f = 0; f < thisFields.size(); ++f) {
+        const auto& thisField = thisFields[f];
+        const auto& otherField = otherFields[f];
+        // Skip comparing buffers
+        if (thisField->getField() == LayerStateField::Buffer) {
+            continue;
+        }
+
+        if (thisField->equals(otherField)) {
+            continue;
+        }
+
+        if (auto viewOpt = flag_name(thisField->getField()); viewOpt) {
+            base::StringAppendF(&result, "  %16s: ", std::string(*viewOpt).c_str());
+        } else {
+            result.append("<UNKNOWN FIELD>:\n");
+        }
+
+        const auto& thisStrings = thisField->toStrings();
+        const auto& otherStrings = otherField->toStrings();
+        bool first = true;
+        for (size_t line = 0; line < std::max(thisStrings.size(), otherStrings.size()); ++line) {
+            if (!first) {
+                result.append("                    ");
+            }
+            first = false;
+
+            if (line < thisStrings.size()) {
+                base::StringAppendF(&result, "%-48.48s", thisStrings[line].c_str());
+            } else {
+                result.append("                                                ");
+            }
+
+            if (line < otherStrings.size()) {
+                base::StringAppendF(&result, "%-48.48s", otherStrings[line].c_str());
+            } else {
+                result.append("                                                ");
+            }
+            result.append("\n");
+        }
+    }
+
+    return result.empty() ? std::nullopt : std::make_optional(result);
+}
+
+bool operator==(const LayerState& lhs, const LayerState& rhs) {
+    return lhs.mId == rhs.mId && lhs.mName == rhs.mName && lhs.mDisplayFrame == rhs.mDisplayFrame &&
+            lhs.mSourceCrop == rhs.mSourceCrop && lhs.mZOrder == rhs.mZOrder &&
+            lhs.mBufferTransform == rhs.mBufferTransform && lhs.mBlendMode == rhs.mBlendMode &&
+            lhs.mAlpha == rhs.mAlpha && lhs.mVisibleRegion == rhs.mVisibleRegion &&
+            lhs.mDataspace == rhs.mDataspace && lhs.mColorTransform == rhs.mColorTransform &&
+            lhs.mCompositionType == rhs.mCompositionType &&
+            lhs.mSidebandStream == rhs.mSidebandStream && lhs.mBuffer == rhs.mBuffer &&
+            (lhs.mCompositionType.get() != hal::Composition::SOLID_COLOR ||
+             lhs.mSolidColor == rhs.mSolidColor);
+}
+
+NonBufferHash getNonBufferHash(const std::vector<const LayerState*>& layers) {
+    size_t hash = 0;
+    for (const auto layer : layers) {
+        android::hashCombineSingleHashed(hash, layer->getHash(LayerStateField::Buffer));
+    }
+
+    return hash;
+}
+
+} // namespace android::compositionengine::impl::planner
diff --git a/services/surfaceflinger/CompositionEngine/src/planner/Planner.cpp b/services/surfaceflinger/CompositionEngine/src/planner/Planner.cpp
new file mode 100644
index 0000000..52efff5
--- /dev/null
+++ b/services/surfaceflinger/CompositionEngine/src/planner/Planner.cpp
@@ -0,0 +1,261 @@
+/*
+ * Copyright 2021 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 "Planner"
+
+#include <compositionengine/LayerFECompositionState.h>
+#include <compositionengine/impl/OutputLayerCompositionState.h>
+#include <compositionengine/impl/planner/Planner.h>
+
+namespace android::compositionengine::impl::planner {
+
+void Planner::setDisplaySize(ui::Size size) {
+    mFlattener.setDisplaySize(size);
+}
+
+void Planner::plan(
+        compositionengine::Output::OutputLayersEnumerator<compositionengine::Output>&& layers) {
+    std::unordered_set<LayerId> removedLayers;
+    removedLayers.reserve(mPreviousLayers.size());
+
+    std::transform(mPreviousLayers.begin(), mPreviousLayers.end(),
+                   std::inserter(removedLayers, removedLayers.begin()),
+                   [](const auto& layer) { return layer.first; });
+
+    std::vector<LayerId> currentLayerIds;
+    for (auto layer : layers) {
+        LayerId id = layer->getLayerFE().getSequence();
+        if (const auto layerEntry = mPreviousLayers.find(id); layerEntry != mPreviousLayers.end()) {
+            // Track changes from previous info
+            LayerState& state = layerEntry->second;
+            Flags<LayerStateField> differences = state.update(layer);
+            if (differences.get() == 0) {
+                state.incrementFramesSinceBufferUpdate();
+            } else {
+                ALOGV("Layer %s changed: %s", state.getName().c_str(),
+                      differences.string().c_str());
+
+                if (differences.test(LayerStateField::Buffer)) {
+                    state.resetFramesSinceBufferUpdate();
+                } else {
+                    state.incrementFramesSinceBufferUpdate();
+                }
+            }
+        } else {
+            LayerState state(layer);
+            ALOGV("Added layer %s", state.getName().c_str());
+            mPreviousLayers.emplace(std::make_pair(id, std::move(state)));
+        }
+
+        currentLayerIds.emplace_back(id);
+
+        if (const auto found = removedLayers.find(id); found != removedLayers.end()) {
+            removedLayers.erase(found);
+        }
+    }
+
+    for (LayerId removedLayer : removedLayers) {
+        if (const auto layerEntry = mPreviousLayers.find(removedLayer);
+            layerEntry != mPreviousLayers.end()) {
+            const auto& [id, state] = *layerEntry;
+            ALOGV("Removed layer %s", state.getName().c_str());
+            mPreviousLayers.erase(removedLayer);
+        }
+    }
+
+    mCurrentLayers.clear();
+    mCurrentLayers.reserve(currentLayerIds.size());
+    std::transform(currentLayerIds.cbegin(), currentLayerIds.cend(),
+                   std::back_inserter(mCurrentLayers), [this](LayerId id) {
+                       LayerState* state = &mPreviousLayers.at(id);
+                       state->getOutputLayer()->editState().overrideInfo = {};
+                       return state;
+                   });
+
+    const NonBufferHash hash = getNonBufferHash(mCurrentLayers);
+    mFlattenedHash = mFlattener.flattenLayers(mCurrentLayers, hash);
+    const bool layersWereFlattened = hash != mFlattenedHash;
+    ALOGV("[%s] Initial hash %zx flattened hash %zx", __func__, hash, mFlattenedHash);
+
+    mPredictedPlan =
+            mPredictor.getPredictedPlan(layersWereFlattened ? std::vector<const LayerState*>()
+                                                            : mCurrentLayers,
+                                        mFlattenedHash);
+    if (mPredictedPlan) {
+        ALOGV("[%s] Predicting plan %s", __func__, to_string(mPredictedPlan->plan).c_str());
+    } else {
+        ALOGV("[%s] No prediction found\n", __func__);
+    }
+}
+
+void Planner::reportFinalPlan(
+        compositionengine::Output::OutputLayersEnumerator<compositionengine::Output>&& layers) {
+    Plan finalPlan;
+    const GraphicBuffer* currentOverrideBuffer = nullptr;
+    bool hasSkippedLayers = false;
+    for (auto layer : layers) {
+        const GraphicBuffer* overrideBuffer = layer->getState().overrideInfo.buffer.get();
+        if (overrideBuffer != nullptr && overrideBuffer == currentOverrideBuffer) {
+            // Skip this layer since it is part of a previous cached set
+            hasSkippedLayers = true;
+            continue;
+        }
+
+        currentOverrideBuffer = overrideBuffer;
+
+        const bool forcedOrRequestedClient =
+                layer->getState().forceClientComposition || layer->requiresClientComposition();
+
+        finalPlan.addLayerType(
+                forcedOrRequestedClient
+                        ? hardware::graphics::composer::hal::Composition::CLIENT
+                        : layer->getLayerFE().getCompositionState()->compositionType);
+    }
+
+    mPredictor.recordResult(mPredictedPlan, mFlattenedHash, mCurrentLayers, hasSkippedLayers,
+                            finalPlan);
+}
+
+void Planner::renderCachedSets(renderengine::RenderEngine& renderEngine) {
+    mFlattener.renderCachedSets(renderEngine);
+}
+
+void Planner::dump(const Vector<String16>& args, std::string& result) {
+    if (args.size() > 1) {
+        const String8 command(args[1]);
+        if (command == "--compare" || command == "-c") {
+            if (args.size() < 4) {
+                base::StringAppendF(&result,
+                                    "Expected two layer stack hashes, e.g. '--planner %s "
+                                    "<left_hash> <right_hash>'\n",
+                                    command.string());
+                return;
+            }
+            if (args.size() > 4) {
+                base::StringAppendF(&result,
+                                    "Too many arguments found, expected '--planner %s <left_hash> "
+                                    "<right_hash>'\n",
+                                    command.string());
+                return;
+            }
+
+            const String8 leftHashString(args[2]);
+            size_t leftHash = 0;
+            int fieldsRead = sscanf(leftHashString.string(), "%zx", &leftHash);
+            if (fieldsRead != 1) {
+                base::StringAppendF(&result, "Failed to parse %s as a size_t\n",
+                                    leftHashString.string());
+                return;
+            }
+
+            const String8 rightHashString(args[3]);
+            size_t rightHash = 0;
+            fieldsRead = sscanf(rightHashString.string(), "%zx", &rightHash);
+            if (fieldsRead != 1) {
+                base::StringAppendF(&result, "Failed to parse %s as a size_t\n",
+                                    rightHashString.string());
+                return;
+            }
+
+            mPredictor.compareLayerStacks(leftHash, rightHash, result);
+        } else if (command == "--describe" || command == "-d") {
+            if (args.size() < 3) {
+                base::StringAppendF(&result,
+                                    "Expected a layer stack hash, e.g. '--planner %s <hash>'\n",
+                                    command.string());
+                return;
+            }
+            if (args.size() > 3) {
+                base::StringAppendF(&result,
+                                    "Too many arguments found, expected '--planner %s <hash>'\n",
+                                    command.string());
+                return;
+            }
+
+            const String8 hashString(args[2]);
+            size_t hash = 0;
+            const int fieldsRead = sscanf(hashString.string(), "%zx", &hash);
+            if (fieldsRead != 1) {
+                base::StringAppendF(&result, "Failed to parse %s as a size_t\n",
+                                    hashString.string());
+                return;
+            }
+
+            mPredictor.describeLayerStack(hash, result);
+        } else if (command == "--help" || command == "-h") {
+            dumpUsage(result);
+        } else if (command == "--similar" || command == "-s") {
+            if (args.size() < 3) {
+                base::StringAppendF(&result, "Expected a plan string, e.g. '--planner %s <plan>'\n",
+                                    command.string());
+                return;
+            }
+            if (args.size() > 3) {
+                base::StringAppendF(&result,
+                                    "Too many arguments found, expected '--planner %s <plan>'\n",
+                                    command.string());
+                return;
+            }
+
+            const String8 planString(args[2]);
+            std::optional<Plan> plan = Plan::fromString(std::string(planString.string()));
+            if (!plan) {
+                base::StringAppendF(&result, "Failed to parse %s as a Plan\n", planString.string());
+                return;
+            }
+
+            mPredictor.listSimilarStacks(*plan, result);
+        } else {
+            base::StringAppendF(&result, "Unknown command '%s'\n\n", command.string());
+            dumpUsage(result);
+        }
+        return;
+    }
+
+    // If there are no specific commands, dump the usual state
+
+    mFlattener.dump(result);
+    result.append("\n");
+
+    mPredictor.dump(result);
+}
+
+void Planner::dumpUsage(std::string& result) const {
+    result.append("Planner command line interface usage\n");
+    result.append("  dumpsys SurfaceFlinger --planner <command> [arguments]\n\n");
+
+    result.append("If run without a command, dumps current Planner state\n\n");
+
+    result.append("Commands:\n");
+
+    result.append("[--compare|-c] <left_hash> <right_hash>\n");
+    result.append("  Compares the predictions <left_hash> and <right_hash> by showing differences"
+                  " in their example layer stacks\n");
+
+    result.append("[--describe|-d] <hash>\n");
+    result.append("  Prints the example layer stack and prediction statistics for <hash>\n");
+
+    result.append("[--help|-h]\n");
+    result.append("  Shows this message\n");
+
+    result.append("[--similar|-s] <plan>\n");
+    result.append("  Prints the example layer names for similar stacks matching <plan>\n");
+}
+
+} // namespace android::compositionengine::impl::planner
diff --git a/services/surfaceflinger/CompositionEngine/src/planner/Predictor.cpp b/services/surfaceflinger/CompositionEngine/src/planner/Predictor.cpp
new file mode 100644
index 0000000..ba5e64d
--- /dev/null
+++ b/services/surfaceflinger/CompositionEngine/src/planner/Predictor.cpp
@@ -0,0 +1,479 @@
+/*
+ * Copyright 2021 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 "Planner"
+
+#include <compositionengine/impl/planner/Predictor.h>
+
+namespace android::compositionengine::impl::planner {
+
+std::optional<LayerStack::ApproximateMatch> LayerStack::getApproximateMatch(
+        const std::vector<const LayerState*>& other) const {
+    // Differing numbers of layers are never an approximate match
+    if (mLayers.size() != other.size()) {
+        return std::nullopt;
+    }
+
+    std::optional<ApproximateMatch> approximateMatch = {};
+    for (size_t i = 0; i < mLayers.size(); ++i) {
+        // Skip identical layers
+        if (mLayers[i].getHash(LayerStateField::Buffer) ==
+            other[i]->getHash(LayerStateField::Buffer)) {
+            continue;
+        }
+
+        // Skip layers where both are client-composited, since that doesn't change the
+        // composition plan
+        if (mLayers[i].getCompositionType() == hal::Composition::CLIENT &&
+            other[i]->getCompositionType() == hal::Composition::CLIENT) {
+            continue;
+        }
+
+        // If layers differ in composition type, their stacks are too different
+        if (mLayers[i].getCompositionType() != other[i]->getCompositionType()) {
+            return std::nullopt;
+        }
+
+        // If layers are not identical, but we already have a prior approximate match,
+        // the LayerStacks differ by too much, so return nothing
+        if (approximateMatch) {
+            return std::nullopt;
+        }
+
+        Flags<LayerStateField> differingFields =
+                mLayers[i].getDifferingFields(*other[i], LayerStateField::Buffer);
+
+        // If we don't find an approximate match on this layer, then the LayerStacks differ
+        // by too much, so return nothing
+        const int differingFieldCount = __builtin_popcount(differingFields.get());
+        if (differingFieldCount <= kMaxDifferingFields) {
+            approximateMatch = ApproximateMatch{
+                    .differingIndex = i,
+                    .differingFields = differingFields,
+            };
+        } else {
+            return std::nullopt;
+        }
+    }
+
+    // If we make it through the layer-by-layer comparison without an approximate match,
+    // it means that all layers were either identical or had client-composited layers in common,
+    // which don't affect the composition strategy, so return a successful result with
+    // no differences.
+    return ApproximateMatch{
+            .differingIndex = 0,
+            .differingFields = {},
+    };
+}
+
+std::optional<Plan> Plan::fromString(const std::string& string) {
+    Plan plan;
+    for (char c : string) {
+        switch (c) {
+            case 'C':
+                plan.addLayerType(hal::Composition::CLIENT);
+                continue;
+            case 'U':
+                plan.addLayerType(hal::Composition::CURSOR);
+                continue;
+            case 'D':
+                plan.addLayerType(hal::Composition::DEVICE);
+                continue;
+            case 'I':
+                plan.addLayerType(hal::Composition::INVALID);
+                continue;
+            case 'B':
+                plan.addLayerType(hal::Composition::SIDEBAND);
+                continue;
+            case 'S':
+                plan.addLayerType(hal::Composition::SOLID_COLOR);
+                continue;
+            default:
+                return std::nullopt;
+        }
+    }
+    return plan;
+}
+
+std::string to_string(const Plan& plan) {
+    std::string result;
+    for (auto type : plan.mLayerTypes) {
+        switch (type) {
+            case hal::Composition::CLIENT:
+                result.append("C");
+                break;
+            case hal::Composition::CURSOR:
+                result.append("U");
+                break;
+            case hal::Composition::DEVICE:
+                result.append("D");
+                break;
+            case hal::Composition::INVALID:
+                result.append("I");
+                break;
+            case hal::Composition::SIDEBAND:
+                result.append("B");
+                break;
+            case hal::Composition::SOLID_COLOR:
+                result.append("S");
+                break;
+        }
+    }
+    return result;
+}
+
+void Prediction::dump(std::string& result) const {
+    result.append(to_string(mPlan));
+    result.append(" [Exact ");
+    mExactStats.dump(result);
+    result.append("] [Approximate ");
+    mApproximateStats.dump(result);
+    result.append("]");
+}
+
+std::optional<Predictor::PredictedPlan> Predictor::getPredictedPlan(
+        const std::vector<const LayerState*>& layers, NonBufferHash hash) const {
+    // First check for an exact match
+    if (std::optional<Plan> exactMatch = getExactMatch(hash); exactMatch) {
+        ALOGV("[%s] Found an exact match for %zx", __func__, hash);
+        return PredictedPlan{.hash = hash, .plan = *exactMatch, .type = Prediction::Type::Exact};
+    }
+
+    // If only a hash was passed in for a layer stack with a cached set, don't perform
+    // approximate matches and return early
+    if (layers.empty()) {
+        ALOGV("[%s] Only hash was passed, but no exact match was found", __func__);
+        return std::nullopt;
+    }
+
+    // Then check for approximate matches
+    if (std::optional<NonBufferHash> approximateMatch = getApproximateMatch(layers);
+        approximateMatch) {
+        ALOGV("[%s] Found an approximate match for %zx", __func__, *approximateMatch);
+        const Prediction& prediction = getPrediction(*approximateMatch);
+        return PredictedPlan{.hash = *approximateMatch,
+                             .plan = prediction.getPlan(),
+                             .type = Prediction::Type::Approximate};
+    }
+
+    return std::nullopt;
+}
+
+void Predictor::recordResult(std::optional<PredictedPlan> predictedPlan,
+                             NonBufferHash flattenedHash,
+                             const std::vector<const LayerState*>& layers, bool hasSkippedLayers,
+                             Plan result) {
+    if (predictedPlan) {
+        recordPredictedResult(*predictedPlan, layers, std::move(result));
+        return;
+    }
+
+    ++mMissCount;
+
+    if (!hasSkippedLayers && findSimilarPrediction(layers, result)) {
+        return;
+    }
+
+    ALOGV("[%s] Adding novel candidate %zx", __func__, flattenedHash);
+    mCandidates.emplace_front(flattenedHash, Prediction(layers, result));
+    if (mCandidates.size() > MAX_CANDIDATES) {
+        mCandidates.pop_back();
+    }
+}
+
+void Predictor::dump(std::string& result) const {
+    result.append("Predictor state:\n");
+
+    const size_t hitCount = mExactHitCount + mApproximateHitCount;
+    const size_t totalAttempts = hitCount + mMissCount;
+    base::StringAppendF(&result, "Global non-skipped hit rate: %.2f%% (%zd/%zd)\n",
+                        100.0f * hitCount / totalAttempts, hitCount, totalAttempts);
+    base::StringAppendF(&result, "  Exact hits: %zd\n", mExactHitCount);
+    base::StringAppendF(&result, "  Approximate hits: %zd\n", mApproximateHitCount);
+    base::StringAppendF(&result, "  Misses: %zd\n\n", mMissCount);
+
+    dumpPredictionsByFrequency(result);
+}
+
+void Predictor::compareLayerStacks(NonBufferHash leftHash, NonBufferHash rightHash,
+                                   std::string& result) const {
+    const auto& [leftPredictionEntry, rightPredictionEntry] =
+            std::make_tuple(mPredictions.find(leftHash), mPredictions.find(rightHash));
+    if (leftPredictionEntry == mPredictions.end()) {
+        base::StringAppendF(&result, "No prediction found for %zx\n", leftHash);
+        return;
+    }
+    if (rightPredictionEntry == mPredictions.end()) {
+        base::StringAppendF(&result, "No prediction found for %zx\n", rightHash);
+        return;
+    }
+
+    base::StringAppendF(&result,
+                        "Comparing           %-16zx                                %-16zx\n",
+                        leftHash, rightHash);
+
+    const auto& [leftPrediction, rightPrediction] =
+            std::make_tuple(leftPredictionEntry->second, rightPredictionEntry->second);
+    const auto& [leftStack, rightStack] = std::make_tuple(leftPrediction.getExampleLayerStack(),
+                                                          rightPrediction.getExampleLayerStack());
+    leftStack.compare(rightStack, result);
+}
+
+void Predictor::describeLayerStack(NonBufferHash hash, std::string& result) const {
+    base::StringAppendF(&result, "Describing %zx:\n\n", hash);
+
+    if (const auto predictionsEntry = mPredictions.find(hash);
+        predictionsEntry != mPredictions.cend()) {
+        const auto& [hash, prediction] = *predictionsEntry;
+
+        prediction.getExampleLayerStack().dump(result);
+
+        result.append("Prediction: ");
+        prediction.dump(result);
+        result.append("\n");
+    } else {
+        result.append("No predictions found\n");
+    }
+}
+
+void Predictor::listSimilarStacks(Plan plan, std::string& result) const {
+    base::StringAppendF(&result, "Similar stacks for plan %s:\n", to_string(plan).c_str());
+
+    if (const auto similarStacksEntry = mSimilarStacks.find(plan);
+        similarStacksEntry != mSimilarStacks.end()) {
+        const auto& [_, similarStacks] = *similarStacksEntry;
+        for (NonBufferHash hash : similarStacks) {
+            base::StringAppendF(&result, "\nPrediction hash %zx:\n", hash);
+            const Prediction& prediction = mPredictions.at(hash);
+            prediction.getExampleLayerStack().dumpLayerNames(result);
+        }
+    } else {
+        result.append("No similar stacks found\n");
+    }
+}
+
+const Prediction& Predictor::getPrediction(NonBufferHash hash) const {
+    if (const auto predictionEntry = mPredictions.find(hash);
+        predictionEntry != mPredictions.end()) {
+        const auto& [_, prediction] = *predictionEntry;
+        return prediction;
+    } else {
+        const auto candidateEntry = getCandidateEntryByHash(hash);
+        ALOGE_IF(candidateEntry == mCandidates.cend(),
+                 "Hash should have been found in either predictions or candidates");
+        const auto& [_, prediction] = *candidateEntry;
+        return prediction;
+    }
+}
+
+Prediction& Predictor::getPrediction(NonBufferHash hash) {
+    return const_cast<Prediction&>(const_cast<const Predictor*>(this)->getPrediction(hash));
+}
+
+std::optional<Plan> Predictor::getExactMatch(NonBufferHash hash) const {
+    const Prediction* match = nullptr;
+    if (const auto predictionEntry = mPredictions.find(hash);
+        predictionEntry != mPredictions.end()) {
+        const auto& [hash, prediction] = *predictionEntry;
+        match = &prediction;
+    } else if (const auto candidateEntry = getCandidateEntryByHash(hash);
+               candidateEntry != mCandidates.cend()) {
+        match = &(candidateEntry->prediction);
+    }
+
+    if (match == nullptr) {
+        return std::nullopt;
+    }
+
+    if (match->getMissCount(Prediction::Type::Exact) != 0) {
+        ALOGV("[%s] Skipping exact match for %zx because of prior miss", __func__, hash);
+        return std::nullopt;
+    }
+
+    return match->getPlan();
+}
+
+std::optional<NonBufferHash> Predictor::getApproximateMatch(
+        const std::vector<const LayerState*>& layers) const {
+    const auto approximateStackMatches = [&](const ApproximateStack& approximateStack) {
+        const auto& exampleStack = mPredictions.at(approximateStack.hash).getExampleLayerStack();
+        if (const auto approximateMatchOpt = exampleStack.getApproximateMatch(layers);
+            approximateMatchOpt) {
+            return *approximateMatchOpt == approximateStack.match;
+        }
+        return false;
+    };
+
+    const auto candidateMatches = [&](const PromotionCandidate& candidate) {
+        ALOGV("[getApproximateMatch] checking against %zx", candidate.hash);
+        return candidate.prediction.getExampleLayerStack().getApproximateMatch(layers) !=
+                std::nullopt;
+    };
+
+    const Prediction* match = nullptr;
+    NonBufferHash hash;
+    if (const auto approximateStackIter =
+                std::find_if(mApproximateStacks.cbegin(), mApproximateStacks.cend(),
+                             approximateStackMatches);
+        approximateStackIter != mApproximateStacks.cend()) {
+        match = &mPredictions.at(approximateStackIter->hash);
+        hash = approximateStackIter->hash;
+    } else if (const auto candidateEntry =
+                       std::find_if(mCandidates.cbegin(), mCandidates.cend(), candidateMatches);
+               candidateEntry != mCandidates.cend()) {
+        match = &(candidateEntry->prediction);
+        hash = candidateEntry->hash;
+    }
+
+    if (match == nullptr) {
+        return std::nullopt;
+    }
+
+    if (match->getMissCount(Prediction::Type::Approximate) != 0) {
+        ALOGV("[%s] Skipping approximate match for %zx because of prior miss", __func__, hash);
+        return std::nullopt;
+    }
+
+    return hash;
+}
+
+void Predictor::promoteIfCandidate(NonBufferHash predictionHash) {
+    // Return if the candidate has already been promoted
+    if (mPredictions.count(predictionHash) != 0) {
+        return;
+    }
+
+    ALOGV("[%s] Promoting %zx from candidate to prediction", __func__, predictionHash);
+
+    auto candidateEntry = getCandidateEntryByHash(predictionHash);
+    ALOGE_IF(candidateEntry == mCandidates.end(), "Expected to find candidate");
+
+    mSimilarStacks[candidateEntry->prediction.getPlan()].push_back(predictionHash);
+    mPredictions.emplace(predictionHash, std::move(candidateEntry->prediction));
+    mCandidates.erase(candidateEntry);
+}
+
+void Predictor::recordPredictedResult(PredictedPlan predictedPlan,
+                                      const std::vector<const LayerState*>& layers, Plan result) {
+    Prediction& prediction = getPrediction(predictedPlan.hash);
+    if (prediction.getPlan() != result) {
+        ALOGV("[%s] %s prediction missed, expected %s, found %s", __func__,
+              to_string(predictedPlan.type).c_str(), to_string(prediction.getPlan()).c_str(),
+              to_string(result).c_str());
+        prediction.recordMiss(predictedPlan.type);
+        ++mMissCount;
+        return;
+    }
+
+    switch (predictedPlan.type) {
+        case Prediction::Type::Approximate:
+            ++mApproximateHitCount;
+            break;
+        case Prediction::Type::Exact:
+            ++mExactHitCount;
+            break;
+        default:
+            break;
+    }
+
+    ALOGV("[%s] %s prediction hit", __func__, to_string(predictedPlan.type).c_str());
+    ALOGV("[%s] Plan: %s", __func__, to_string(result).c_str());
+    prediction.recordHit(predictedPlan.type);
+
+    const auto stackMatchesHash = [hash = predictedPlan.hash](const ApproximateStack& stack) {
+        return stack.hash == hash;
+    };
+
+    if (predictedPlan.type == Prediction::Type::Approximate) {
+        // If this approximate match is not already in the list of approximate stacks, add it
+        if (std::find_if(mApproximateStacks.cbegin(), mApproximateStacks.cend(),
+                         stackMatchesHash) == mApproximateStacks.cend()) {
+            ALOGV("[%s] Adding approximate match to list", __func__);
+            const auto approximateMatchOpt =
+                    prediction.getExampleLayerStack().getApproximateMatch(layers);
+            ALOGE_IF(!approximateMatchOpt, "Expected an approximate match");
+            mApproximateStacks.emplace_back(predictedPlan.hash, *approximateMatchOpt);
+        }
+    }
+
+    promoteIfCandidate(predictedPlan.hash);
+}
+
+bool Predictor::findSimilarPrediction(const std::vector<const LayerState*>& layers, Plan result) {
+    const auto stacksEntry = mSimilarStacks.find(result);
+    if (stacksEntry == mSimilarStacks.end()) {
+        return false;
+    }
+
+    std::optional<ApproximateStack> bestMatch;
+    const auto& [plan, similarStacks] = *stacksEntry;
+    for (NonBufferHash hash : similarStacks) {
+        const Prediction& prediction = mPredictions.at(hash);
+        auto approximateMatch = prediction.getExampleLayerStack().getApproximateMatch(layers);
+        if (!approximateMatch) {
+            continue;
+        }
+
+        const int differingFieldCount = __builtin_popcount(approximateMatch->differingFields.get());
+        if (!bestMatch ||
+            differingFieldCount < __builtin_popcount(bestMatch->match.differingFields.get())) {
+            bestMatch = {hash, *approximateMatch};
+        }
+    }
+
+    if (!bestMatch) {
+        return false;
+    }
+
+    ALOGV("[%s] Adding %zx to approximate stacks", __func__, bestMatch->hash);
+
+    mApproximateStacks.emplace_back(*bestMatch);
+    return true;
+}
+
+void Predictor::dumpPredictionsByFrequency(std::string& result) const {
+    struct HashFrequency {
+        HashFrequency(NonBufferHash hash, size_t totalAttempts)
+              : hash(hash), totalAttempts(totalAttempts) {}
+
+        NonBufferHash hash;
+        size_t totalAttempts;
+    };
+
+    std::vector<HashFrequency> hashFrequencies;
+    for (const auto& [hash, prediction] : mPredictions) {
+        hashFrequencies.emplace_back(hash,
+                                     prediction.getHitCount(Prediction::Type::Total) +
+                                             prediction.getMissCount(Prediction::Type::Total));
+    }
+
+    std::sort(hashFrequencies.begin(), hashFrequencies.end(),
+              [](const HashFrequency& lhs, const HashFrequency& rhs) {
+                  return lhs.totalAttempts > rhs.totalAttempts;
+              });
+
+    result.append("Predictions:\n");
+    for (const auto& [hash, totalAttempts] : hashFrequencies) {
+        base::StringAppendF(&result, "  %016zx ", hash);
+        mPredictions.at(hash).dump(result);
+        result.append("\n");
+    }
+}
+
+} // namespace android::compositionengine::impl::planner
diff --git a/services/surfaceflinger/CompositionEngine/tests/OutputLayerTest.cpp b/services/surfaceflinger/CompositionEngine/tests/OutputLayerTest.cpp
index dcfc162..9dd199d 100644
--- a/services/surfaceflinger/CompositionEngine/tests/OutputLayerTest.cpp
+++ b/services/surfaceflinger/CompositionEngine/tests/OutputLayerTest.cpp
@@ -840,19 +840,19 @@
 TEST_F(OutputLayerWriteStateToHWCTest, doesNothingIfNoFECompositionState) {
     EXPECT_CALL(*mLayerFE, getCompositionState()).WillOnce(Return(nullptr));
 
-    mOutputLayer.writeStateToHWC(true);
+    mOutputLayer.writeStateToHWC(/*includeGeometry*/ true, /*skipLayer*/ false);
 }
 
 TEST_F(OutputLayerWriteStateToHWCTest, doesNothingIfNoHWCState) {
     mOutputLayer.editState().hwc.reset();
 
-    mOutputLayer.writeStateToHWC(true);
+    mOutputLayer.writeStateToHWC(/*includeGeometry*/ true, /*skipLayer*/ false);
 }
 
 TEST_F(OutputLayerWriteStateToHWCTest, doesNothingIfNoHWCLayer) {
     mOutputLayer.editState().hwc = impl::OutputLayerCompositionState::Hwc(nullptr);
 
-    mOutputLayer.writeStateToHWC(true);
+    mOutputLayer.writeStateToHWC(/*includeGeometry*/ true, /*skipLayer*/ false);
 }
 
 TEST_F(OutputLayerWriteStateToHWCTest, canSetAllState) {
@@ -861,7 +861,7 @@
 
     expectNoSetCompositionTypeCall();
 
-    mOutputLayer.writeStateToHWC(true);
+    mOutputLayer.writeStateToHWC(/*includeGeometry*/ true, /*skipLayer*/ false);
 }
 
 TEST_F(OutputLayerTest, displayInstallOrientationBufferTransformSetTo90) {
@@ -891,7 +891,7 @@
     expectSetCompositionTypeCall(Hwc2::IComposerClient::Composition::SOLID_COLOR);
     expectSetColorCall();
 
-    mOutputLayer.writeStateToHWC(false);
+    mOutputLayer.writeStateToHWC(/*includeGeometry*/ false, /*skipLayer*/ false);
 }
 
 TEST_F(OutputLayerWriteStateToHWCTest, canSetPerFrameStateForSideband) {
@@ -901,7 +901,7 @@
     expectSetSidebandHandleCall();
     expectSetCompositionTypeCall(Hwc2::IComposerClient::Composition::SIDEBAND);
 
-    mOutputLayer.writeStateToHWC(false);
+    mOutputLayer.writeStateToHWC(/*includeGeometry*/ false, /*skipLayer*/ false);
 }
 
 TEST_F(OutputLayerWriteStateToHWCTest, canSetPerFrameStateForCursor) {
@@ -911,7 +911,7 @@
     expectSetHdrMetadataAndBufferCalls();
     expectSetCompositionTypeCall(Hwc2::IComposerClient::Composition::CURSOR);
 
-    mOutputLayer.writeStateToHWC(false);
+    mOutputLayer.writeStateToHWC(/*includeGeometry*/ false, /*skipLayer*/ false);
 }
 
 TEST_F(OutputLayerWriteStateToHWCTest, canSetPerFrameStateForDevice) {
@@ -921,7 +921,7 @@
     expectSetHdrMetadataAndBufferCalls();
     expectSetCompositionTypeCall(Hwc2::IComposerClient::Composition::DEVICE);
 
-    mOutputLayer.writeStateToHWC(false);
+    mOutputLayer.writeStateToHWC(/*includeGeometry*/ false, /*skipLayer*/ false);
 }
 
 TEST_F(OutputLayerWriteStateToHWCTest, compositionTypeIsNotSetIfUnchanged) {
@@ -934,7 +934,7 @@
     expectSetColorCall();
     expectNoSetCompositionTypeCall();
 
-    mOutputLayer.writeStateToHWC(false);
+    mOutputLayer.writeStateToHWC(/*includeGeometry*/ false, /*skipLayer*/ false);
 }
 
 TEST_F(OutputLayerWriteStateToHWCTest, compositionTypeIsSetToClientIfColorTransformNotSupported) {
@@ -944,7 +944,7 @@
     expectSetColorCall();
     expectSetCompositionTypeCall(Hwc2::IComposerClient::Composition::CLIENT);
 
-    mOutputLayer.writeStateToHWC(false);
+    mOutputLayer.writeStateToHWC(/*includeGeometry*/ false, /*skipLayer*/ false);
 }
 
 TEST_F(OutputLayerWriteStateToHWCTest, compositionTypeIsSetToClientIfClientCompositionForced) {
@@ -956,7 +956,7 @@
     expectSetColorCall();
     expectSetCompositionTypeCall(Hwc2::IComposerClient::Composition::CLIENT);
 
-    mOutputLayer.writeStateToHWC(false);
+    mOutputLayer.writeStateToHWC(/*includeGeometry*/ false, /*skipLayer*/ false);
 }
 
 TEST_F(OutputLayerWriteStateToHWCTest, allStateIncludesMetadataIfPresent) {
@@ -969,7 +969,7 @@
     expectGenericLayerMetadataCalls();
     expectSetCompositionTypeCall(Hwc2::IComposerClient::Composition::DEVICE);
 
-    mOutputLayer.writeStateToHWC(true);
+    mOutputLayer.writeStateToHWC(/*includeGeometry*/ true, /*skipLayer*/ false);
 }
 
 TEST_F(OutputLayerWriteStateToHWCTest, perFrameStateDoesNotIncludeMetadataIfPresent) {
@@ -980,7 +980,7 @@
     expectSetHdrMetadataAndBufferCalls();
     expectSetCompositionTypeCall(Hwc2::IComposerClient::Composition::DEVICE);
 
-    mOutputLayer.writeStateToHWC(false);
+    mOutputLayer.writeStateToHWC(/*includeGeometry*/ false, /*skipLayer*/ false);
 }
 
 /*
diff --git a/services/surfaceflinger/CompositionEngine/tests/OutputTest.cpp b/services/surfaceflinger/CompositionEngine/tests/OutputTest.cpp
index c4ae3a7..3059beb 100644
--- a/services/surfaceflinger/CompositionEngine/tests/OutputTest.cpp
+++ b/services/surfaceflinger/CompositionEngine/tests/OutputTest.cpp
@@ -96,6 +96,8 @@
         EXPECT_CALL(*outputLayer, editState()).WillRepeatedly(ReturnRef(outputLayerState));
 
         EXPECT_CALL(*layerFE, getCompositionState()).WillRepeatedly(Return(&layerFEState));
+        EXPECT_CALL(*layerFE, getSequence()).WillRepeatedly(Return(0));
+        EXPECT_CALL(*layerFE, getDebugName()).WillRepeatedly(Return("InjectedLayer"));
     }
 
     mock::OutputLayer* outputLayer = {new StrictMock<mock::OutputLayer>};
@@ -111,6 +113,8 @@
         EXPECT_CALL(outputLayer, editState()).WillRepeatedly(ReturnRef(outputLayerState));
 
         EXPECT_CALL(*layerFE, getCompositionState()).WillRepeatedly(Return(&layerFEState));
+        EXPECT_CALL(*layerFE, getSequence()).WillRepeatedly(Return(0));
+        EXPECT_CALL(*layerFE, getDebugName()).WillRepeatedly(Return("NonInjectedLayer"));
     }
 
     mock::OutputLayer outputLayer;
@@ -751,7 +755,9 @@
     mOutput->editState().isEnabled = true;
 
     CompositionRefreshArgs args;
-    mOutput->updateAndWriteCompositionState(args);
+    mOutput->updateCompositionState(args);
+    mOutput->planComposition();
+    mOutput->writeCompositionState(args);
 }
 
 TEST_F(OutputUpdateAndWriteCompositionStateTest, doesNothingIfOutputNotEnabled) {
@@ -766,7 +772,9 @@
     injectOutputLayer(layer3);
 
     CompositionRefreshArgs args;
-    mOutput->updateAndWriteCompositionState(args);
+    mOutput->updateCompositionState(args);
+    mOutput->planComposition();
+    mOutput->writeCompositionState(args);
 }
 
 TEST_F(OutputUpdateAndWriteCompositionStateTest, updatesLayerContentForAllLayers) {
@@ -775,11 +783,14 @@
     InjectedLayer layer3;
 
     EXPECT_CALL(*layer1.outputLayer, updateCompositionState(false, false, ui::Transform::ROT_180));
-    EXPECT_CALL(*layer1.outputLayer, writeStateToHWC(false));
+    EXPECT_CALL(*layer1.outputLayer,
+                writeStateToHWC(/*includeGeometry*/ false, /*skipLayer*/ false));
     EXPECT_CALL(*layer2.outputLayer, updateCompositionState(false, false, ui::Transform::ROT_180));
-    EXPECT_CALL(*layer2.outputLayer, writeStateToHWC(false));
+    EXPECT_CALL(*layer2.outputLayer,
+                writeStateToHWC(/*includeGeometry*/ false, /*skipLayer*/ false));
     EXPECT_CALL(*layer3.outputLayer, updateCompositionState(false, false, ui::Transform::ROT_180));
-    EXPECT_CALL(*layer3.outputLayer, writeStateToHWC(false));
+    EXPECT_CALL(*layer3.outputLayer,
+                writeStateToHWC(/*includeGeometry*/ false, /*skipLayer*/ false));
 
     injectOutputLayer(layer1);
     injectOutputLayer(layer2);
@@ -791,7 +802,9 @@
     args.updatingGeometryThisFrame = false;
     args.devOptForceClientComposition = false;
     args.internalDisplayRotationFlags = ui::Transform::ROT_180;
-    mOutput->updateAndWriteCompositionState(args);
+    mOutput->updateCompositionState(args);
+    mOutput->planComposition();
+    mOutput->writeCompositionState(args);
 }
 
 TEST_F(OutputUpdateAndWriteCompositionStateTest, updatesLayerGeometryAndContentForAllLayers) {
@@ -800,11 +813,14 @@
     InjectedLayer layer3;
 
     EXPECT_CALL(*layer1.outputLayer, updateCompositionState(true, false, ui::Transform::ROT_0));
-    EXPECT_CALL(*layer1.outputLayer, writeStateToHWC(true));
+    EXPECT_CALL(*layer1.outputLayer,
+                writeStateToHWC(/*includeGeometry*/ true, /*skipLayer*/ false));
     EXPECT_CALL(*layer2.outputLayer, updateCompositionState(true, false, ui::Transform::ROT_0));
-    EXPECT_CALL(*layer2.outputLayer, writeStateToHWC(true));
+    EXPECT_CALL(*layer2.outputLayer,
+                writeStateToHWC(/*includeGeometry*/ true, /*skipLayer*/ false));
     EXPECT_CALL(*layer3.outputLayer, updateCompositionState(true, false, ui::Transform::ROT_0));
-    EXPECT_CALL(*layer3.outputLayer, writeStateToHWC(true));
+    EXPECT_CALL(*layer3.outputLayer,
+                writeStateToHWC(/*includeGeometry*/ true, /*skipLayer*/ false));
 
     injectOutputLayer(layer1);
     injectOutputLayer(layer2);
@@ -815,7 +831,9 @@
     CompositionRefreshArgs args;
     args.updatingGeometryThisFrame = true;
     args.devOptForceClientComposition = false;
-    mOutput->updateAndWriteCompositionState(args);
+    mOutput->updateCompositionState(args);
+    mOutput->planComposition();
+    mOutput->writeCompositionState(args);
 }
 
 TEST_F(OutputUpdateAndWriteCompositionStateTest, forcesClientCompositionForAllLayers) {
@@ -824,11 +842,14 @@
     InjectedLayer layer3;
 
     EXPECT_CALL(*layer1.outputLayer, updateCompositionState(false, true, ui::Transform::ROT_0));
-    EXPECT_CALL(*layer1.outputLayer, writeStateToHWC(false));
+    EXPECT_CALL(*layer1.outputLayer,
+                writeStateToHWC(/*includeGeometry*/ false, /*skipLayer*/ false));
     EXPECT_CALL(*layer2.outputLayer, updateCompositionState(false, true, ui::Transform::ROT_0));
-    EXPECT_CALL(*layer2.outputLayer, writeStateToHWC(false));
+    EXPECT_CALL(*layer2.outputLayer,
+                writeStateToHWC(/*includeGeometry*/ false, /*skipLayer*/ false));
     EXPECT_CALL(*layer3.outputLayer, updateCompositionState(false, true, ui::Transform::ROT_0));
-    EXPECT_CALL(*layer3.outputLayer, writeStateToHWC(false));
+    EXPECT_CALL(*layer3.outputLayer,
+                writeStateToHWC(/*includeGeometry*/ false, /*skipLayer*/ false));
 
     injectOutputLayer(layer1);
     injectOutputLayer(layer2);
@@ -839,7 +860,9 @@
     CompositionRefreshArgs args;
     args.updatingGeometryThisFrame = false;
     args.devOptForceClientComposition = true;
-    mOutput->updateAndWriteCompositionState(args);
+    mOutput->updateCompositionState(args);
+    mOutput->planComposition();
+    mOutput->writeCompositionState(args);
 }
 
 /*
@@ -877,6 +900,9 @@
     mOutput.editState().usesDeviceComposition = true;
 
     EXPECT_CALL(mOutput, chooseCompositionStrategy()).Times(1);
+    if (mOutput.plannerEnabled()) {
+        EXPECT_CALL(mOutput, getOutputLayerCount()).WillOnce(Return(0u));
+    }
     EXPECT_CALL(*mRenderSurface, prepareFrame(false, true));
 
     mOutput.prepareFrame();
@@ -1621,14 +1647,17 @@
         // Sets up the helper functions called by the function under test to use
         // mock implementations.
         MOCK_METHOD1(updateColorProfile, void(const compositionengine::CompositionRefreshArgs&));
-        MOCK_METHOD1(updateAndWriteCompositionState,
+        MOCK_METHOD1(updateCompositionState,
                      void(const compositionengine::CompositionRefreshArgs&));
+        MOCK_METHOD0(planComposition, void());
+        MOCK_METHOD1(writeCompositionState, void(const compositionengine::CompositionRefreshArgs&));
         MOCK_METHOD1(setColorTransform, void(const compositionengine::CompositionRefreshArgs&));
         MOCK_METHOD0(beginFrame, void());
         MOCK_METHOD0(prepareFrame, void());
         MOCK_METHOD1(devOptRepaintFlash, void(const compositionengine::CompositionRefreshArgs&));
         MOCK_METHOD1(finishFrame, void(const compositionengine::CompositionRefreshArgs&));
         MOCK_METHOD0(postFramebuffer, void());
+        MOCK_METHOD0(renderCachedSets, void());
     };
 
     StrictMock<OutputPartialMock> mOutput;
@@ -1639,13 +1668,16 @@
 
     InSequence seq;
     EXPECT_CALL(mOutput, updateColorProfile(Ref(args)));
-    EXPECT_CALL(mOutput, updateAndWriteCompositionState(Ref(args)));
+    EXPECT_CALL(mOutput, updateCompositionState(Ref(args)));
+    EXPECT_CALL(mOutput, planComposition());
+    EXPECT_CALL(mOutput, writeCompositionState(Ref(args)));
     EXPECT_CALL(mOutput, setColorTransform(Ref(args)));
     EXPECT_CALL(mOutput, beginFrame());
     EXPECT_CALL(mOutput, prepareFrame());
     EXPECT_CALL(mOutput, devOptRepaintFlash(Ref(args)));
     EXPECT_CALL(mOutput, finishFrame(Ref(args)));
     EXPECT_CALL(mOutput, postFramebuffer());
+    EXPECT_CALL(mOutput, renderCachedSets());
 
     mOutput.present(args);
 }
@@ -3461,7 +3493,8 @@
         mOutput.editState().isEnabled = true;
 
         EXPECT_CALL(mLayer.outputLayer, updateCompositionState(false, true, ui::Transform::ROT_0));
-        EXPECT_CALL(mLayer.outputLayer, writeStateToHWC(false));
+        EXPECT_CALL(mLayer.outputLayer,
+                    writeStateToHWC(/*includeGeometry*/ false, /*skipLayer*/ false));
         EXPECT_CALL(mOutput, generateClientCompositionRequests(_, _, kDefaultOutputDataspace))
                 .WillOnce(Return(std::vector<LayerFE::LayerSettings>{}));
         EXPECT_CALL(mRenderEngine, drawLayers(_, _, _, false, _, _)).WillOnce(Return(NO_ERROR));
@@ -3476,7 +3509,9 @@
 
 TEST_F(OutputComposeSurfacesTest_SetsExpensiveRendering_ForBlur, IfBlursAreExpensive) {
     mRefreshArgs.blursAreExpensive = true;
-    mOutput.updateAndWriteCompositionState(mRefreshArgs);
+    mOutput.updateCompositionState(mRefreshArgs);
+    mOutput.planComposition();
+    mOutput.writeCompositionState(mRefreshArgs);
 
     EXPECT_CALL(mOutput, setExpensiveRenderingExpected(true));
     mOutput.composeSurfaces(kDebugRegion, mRefreshArgs);
@@ -3484,7 +3519,9 @@
 
 TEST_F(OutputComposeSurfacesTest_SetsExpensiveRendering_ForBlur, IfBlursAreNotExpensive) {
     mRefreshArgs.blursAreExpensive = false;
-    mOutput.updateAndWriteCompositionState(mRefreshArgs);
+    mOutput.updateCompositionState(mRefreshArgs);
+    mOutput.planComposition();
+    mOutput.writeCompositionState(mRefreshArgs);
 
     EXPECT_CALL(mOutput, setExpensiveRenderingExpected(true)).Times(0);
     mOutput.composeSurfaces(kDebugRegion, mRefreshArgs);
@@ -4038,11 +4075,14 @@
 
     // Layer requesting blur, or below, should request client composition.
     EXPECT_CALL(*layer1.outputLayer, updateCompositionState(false, true, ui::Transform::ROT_0));
-    EXPECT_CALL(*layer1.outputLayer, writeStateToHWC(false));
+    EXPECT_CALL(*layer1.outputLayer,
+                writeStateToHWC(/*includeGeometry*/ false, /*skipLayer*/ false));
     EXPECT_CALL(*layer2.outputLayer, updateCompositionState(false, true, ui::Transform::ROT_0));
-    EXPECT_CALL(*layer2.outputLayer, writeStateToHWC(false));
+    EXPECT_CALL(*layer2.outputLayer,
+                writeStateToHWC(/*includeGeometry*/ false, /*skipLayer*/ false));
     EXPECT_CALL(*layer3.outputLayer, updateCompositionState(false, false, ui::Transform::ROT_0));
-    EXPECT_CALL(*layer3.outputLayer, writeStateToHWC(false));
+    EXPECT_CALL(*layer3.outputLayer,
+                writeStateToHWC(/*includeGeometry*/ false, /*skipLayer*/ false));
 
     layer2.layerFEState.backgroundBlurRadius = 10;
 
@@ -4055,7 +4095,9 @@
     CompositionRefreshArgs args;
     args.updatingGeometryThisFrame = false;
     args.devOptForceClientComposition = false;
-    mOutput->updateAndWriteCompositionState(args);
+    mOutput->updateCompositionState(args);
+    mOutput->planComposition();
+    mOutput->writeCompositionState(args);
 }
 
 TEST_F(OutputUpdateAndWriteCompositionStateTest, handlesBlurRegionRequests) {
@@ -4065,11 +4107,14 @@
 
     // Layer requesting blur, or below, should request client composition.
     EXPECT_CALL(*layer1.outputLayer, updateCompositionState(false, true, ui::Transform::ROT_0));
-    EXPECT_CALL(*layer1.outputLayer, writeStateToHWC(false));
+    EXPECT_CALL(*layer1.outputLayer,
+                writeStateToHWC(/*includeGeometry*/ false, /*skipLayer*/ false));
     EXPECT_CALL(*layer2.outputLayer, updateCompositionState(false, true, ui::Transform::ROT_0));
-    EXPECT_CALL(*layer2.outputLayer, writeStateToHWC(false));
+    EXPECT_CALL(*layer2.outputLayer,
+                writeStateToHWC(/*includeGeometry*/ false, /*skipLayer*/ false));
     EXPECT_CALL(*layer3.outputLayer, updateCompositionState(false, false, ui::Transform::ROT_0));
-    EXPECT_CALL(*layer3.outputLayer, writeStateToHWC(false));
+    EXPECT_CALL(*layer3.outputLayer,
+                writeStateToHWC(/*includeGeometry*/ false, /*skipLayer*/ false));
 
     BlurRegion region;
     layer2.layerFEState.blurRegions.push_back(region);
@@ -4083,7 +4128,9 @@
     CompositionRefreshArgs args;
     args.updatingGeometryThisFrame = false;
     args.devOptForceClientComposition = false;
-    mOutput->updateAndWriteCompositionState(args);
+    mOutput->updateCompositionState(args);
+    mOutput->planComposition();
+    mOutput->writeCompositionState(args);
 }
 
 TEST_F(GenerateClientCompositionRequestsTest, handlesLandscapeModeSplitScreenRequests) {
diff --git a/services/surfaceflinger/FrameTimeline/FrameTimeline.cpp b/services/surfaceflinger/FrameTimeline/FrameTimeline.cpp
index ff000c9..75229e9 100644
--- a/services/surfaceflinger/FrameTimeline/FrameTimeline.cpp
+++ b/services/surfaceflinger/FrameTimeline/FrameTimeline.cpp
@@ -504,6 +504,17 @@
         if (mLastLatchTime != 0 && mPredictions.endTime <= mLastLatchTime) {
             // Buffer Stuffing.
             mJankType |= JankType::BufferStuffing;
+            // In a stuffed state, the frame could be stuck on a dequeue wait for quite some time.
+            // Because of this dequeue wait, it can be hard to tell if a frame was genuinely late.
+            // We try to do this by moving the deadline. Since the queue could be stuffed by more
+            // than one buffer, we take the last latch time as reference and give one vsync
+            // worth of time for the frame to be ready.
+            nsecs_t adjustedDeadline = mLastLatchTime + refreshRate.getPeriodNsecs();
+            if (adjustedDeadline > mActuals.endTime) {
+                mFrameReadyMetadata = FrameReadyMetadata::OnTimeFinish;
+            } else {
+                mFrameReadyMetadata = FrameReadyMetadata::LateFinish;
+            }
         }
         if (mFrameReadyMetadata == FrameReadyMetadata::OnTimeFinish) {
             // Finish on time, Present late
@@ -511,14 +522,18 @@
                 // Propagate displayFrame's jank if it exists
                 mJankType |= displayFrameJankType;
             } else {
-                if (deltaToVsync < mJankClassificationThresholds.presentThreshold ||
-                    deltaToVsync >= refreshRate.getPeriodNsecs() -
-                                    mJankClassificationThresholds.presentThreshold) {
-                    // Delta factor of vsync
-                    mJankType |= JankType::SurfaceFlingerScheduling;
-                } else {
-                    // Delta not a factor of vsync
-                    mJankType |= JankType::PredictionError;
+                if (!(mJankType & JankType::BufferStuffing)) {
+                    // In a stuffed state, if the app finishes on time and there is no display frame
+                    // jank, only buffer stuffing is the root cause of the jank.
+                    if (deltaToVsync < mJankClassificationThresholds.presentThreshold ||
+                        deltaToVsync >= refreshRate.getPeriodNsecs() -
+                                        mJankClassificationThresholds.presentThreshold) {
+                        // Delta factor of vsync
+                        mJankType |= JankType::SurfaceFlingerScheduling;
+                    } else {
+                        // Delta not a factor of vsync
+                        mJankType |= JankType::PredictionError;
+                    }
                 }
             }
         } else if (mFrameReadyMetadata == FrameReadyMetadata::LateFinish) {
@@ -683,13 +698,14 @@
 }
 
 FrameTimeline::FrameTimeline(std::shared_ptr<TimeStats> timeStats, pid_t surfaceFlingerPid,
-                             JankClassificationThresholds thresholds)
+                             JankClassificationThresholds thresholds, nsecs_t hwcDuration)
       : mMaxDisplayFrames(kDefaultMaxDisplayFrames),
         mTimeStats(std::move(timeStats)),
         mSurfaceFlingerPid(surfaceFlingerPid),
-        mJankClassificationThresholds(thresholds) {
-    mCurrentDisplayFrame =
-            std::make_shared<DisplayFrame>(mTimeStats, thresholds, &mTraceCookieCounter);
+        mJankClassificationThresholds(thresholds),
+        mHwcDuration(hwcDuration) {
+    mCurrentDisplayFrame = std::make_shared<DisplayFrame>(mTimeStats, thresholds, hwcDuration,
+                                                          &mTraceCookieCounter);
 }
 
 void FrameTimeline::onBootFinished() {
@@ -732,11 +748,13 @@
 
 FrameTimeline::DisplayFrame::DisplayFrame(std::shared_ptr<TimeStats> timeStats,
                                           JankClassificationThresholds thresholds,
+                                          nsecs_t hwcDuration,
                                           TraceCookieCounter* traceCookieCounter)
       : mSurfaceFlingerPredictions(TimelineItem()),
         mSurfaceFlingerActuals(TimelineItem()),
         mTimeStats(timeStats),
         mJankClassificationThresholds(thresholds),
+        mHwcDuration(hwcDuration),
         mTraceCookieCounter(*traceCookieCounter) {
     mSurfaceFrames.reserve(kNumSurfaceFramesInitial);
 }
@@ -808,7 +826,7 @@
     const nsecs_t presentDelta =
             mSurfaceFlingerActuals.presentTime - mSurfaceFlingerPredictions.presentTime;
     const nsecs_t deadlineDelta =
-            mSurfaceFlingerActuals.endTime - mSurfaceFlingerPredictions.endTime;
+            mSurfaceFlingerActuals.endTime - (mSurfaceFlingerPredictions.endTime - mHwcDuration);
 
     // How far off was the presentDelta when compared to the vsyncPeriod. Used in checking if there
     // was a prediction error or not.
@@ -822,8 +840,9 @@
         mFramePresentMetadata = FramePresentMetadata::OnTimePresent;
     }
 
-    if (mSurfaceFlingerActuals.endTime - mSurfaceFlingerPredictions.endTime >
-        mJankClassificationThresholds.deadlineThreshold) {
+    if (mSurfaceFlingerActuals.endTime > mSurfaceFlingerPredictions.endTime - mHwcDuration) {
+        // SF needs to have finished at least mHwcDuration ahead of the deadline for it to be
+        // on time.
         mFrameReadyMetadata = FrameReadyMetadata::LateFinish;
     } else {
         mFrameReadyMetadata = FrameReadyMetadata::OnTimeFinish;
@@ -875,8 +894,13 @@
                     mJankType = JankType::PredictionError;
                 }
             } else if (mFrameReadyMetadata == FrameReadyMetadata::LateFinish) {
-                // Finish late, Present late
-                mJankType = JankType::SurfaceFlingerCpuDeadlineMissed;
+                if (mFrameStartMetadata == FrameStartMetadata::LateStart) {
+                    // Late start, Late finish, Late Present
+                    mJankType = JankType::SurfaceFlingerScheduling;
+                } else {
+                    // OnTime start, Finish late, Present late
+                    mJankType = JankType::SurfaceFlingerCpuDeadlineMissed;
+                }
             } else {
                 // Finish time unknown
                 mJankType = JankType::Unknown;
@@ -1066,7 +1090,7 @@
     mDisplayFrames.push_back(mCurrentDisplayFrame);
     mCurrentDisplayFrame.reset();
     mCurrentDisplayFrame = std::make_shared<DisplayFrame>(mTimeStats, mJankClassificationThresholds,
-                                                          &mTraceCookieCounter);
+                                                          mHwcDuration, &mTraceCookieCounter);
 }
 
 nsecs_t FrameTimeline::DisplayFrame::getBaseTime() const {
diff --git a/services/surfaceflinger/FrameTimeline/FrameTimeline.h b/services/surfaceflinger/FrameTimeline/FrameTimeline.h
index d65769b..7c6a0cc 100644
--- a/services/surfaceflinger/FrameTimeline/FrameTimeline.h
+++ b/services/surfaceflinger/FrameTimeline/FrameTimeline.h
@@ -342,7 +342,7 @@
     class DisplayFrame {
     public:
         DisplayFrame(std::shared_ptr<TimeStats> timeStats, JankClassificationThresholds thresholds,
-                     TraceCookieCounter* traceCookieCounter);
+                     nsecs_t hwcDuration, TraceCookieCounter* traceCookieCounter);
         virtual ~DisplayFrame() = default;
         // Dumpsys interface - dumps only if the DisplayFrame itself is janky or is at least one
         // SurfaceFrame is janky.
@@ -371,6 +371,7 @@
         // Functions to be used only in testing.
         TimelineItem getActuals() const { return mSurfaceFlingerActuals; };
         TimelineItem getPredictions() const { return mSurfaceFlingerPredictions; };
+        FrameStartMetadata getFrameStartMetadata() const { return mFrameStartMetadata; };
         FramePresentMetadata getFramePresentMetadata() const { return mFramePresentMetadata; };
         FrameReadyMetadata getFrameReadyMetadata() const { return mFrameReadyMetadata; };
         int32_t getJankType() const { return mJankType; }
@@ -394,6 +395,7 @@
         TimelineItem mSurfaceFlingerActuals;
         std::shared_ptr<TimeStats> mTimeStats;
         const JankClassificationThresholds mJankClassificationThresholds;
+        const nsecs_t mHwcDuration;
 
         // Collection of predictions and actual values sent over by Layers
         std::vector<std::shared_ptr<SurfaceFrame>> mSurfaceFrames;
@@ -419,7 +421,8 @@
     };
 
     FrameTimeline(std::shared_ptr<TimeStats> timeStats, pid_t surfaceFlingerPid,
-                  JankClassificationThresholds thresholds = {});
+                  JankClassificationThresholds thresholds = {},
+                  nsecs_t hwcDuration = kDefaultHwcDuration);
     ~FrameTimeline() = default;
 
     frametimeline::TokenManager* getTokenManager() override { return &mTokenManager; }
@@ -464,6 +467,11 @@
     std::shared_ptr<TimeStats> mTimeStats;
     const pid_t mSurfaceFlingerPid;
     const JankClassificationThresholds mJankClassificationThresholds;
+    // In SF Predictions, both end & present are the same. The predictions consider the time used by
+    // composer as well, but we have no way to estimate how much time the composer needs. We are
+    // assuming an arbitrary time for the composer work.
+    const nsecs_t mHwcDuration;
+    static constexpr nsecs_t kDefaultHwcDuration = std::chrono::nanoseconds(3ms).count();
     static constexpr uint32_t kDefaultMaxDisplayFrames = 64;
     // The initial container size for the vector<SurfaceFrames> inside display frame. Although
     // this number doesn't represent any bounds on the number of surface frames that can go in a
diff --git a/services/surfaceflinger/Layer.h b/services/surfaceflinger/Layer.h
index 664c10b..a072554 100644
--- a/services/surfaceflinger/Layer.h
+++ b/services/surfaceflinger/Layer.h
@@ -724,7 +724,7 @@
     // Returns the bounds of the layer without any buffer scaling.
     FloatRect getBoundsPreScaling(const ui::Transform& bufferScaleTransform) const;
 
-    int32_t getSequence() const { return sequence; }
+    int32_t getSequence() const override { return sequence; }
 
     // For tracing.
     // TODO: Replace with raw buffer id from buffer metadata when that becomes available.
@@ -924,6 +924,8 @@
 
     pid_t getOwnerPid() { return mOwnerPid; }
 
+    virtual bool frameIsEarly(nsecs_t /*expectedPresentTime*/) const { return false; }
+
     // This layer is not a clone, but it's the parent to the cloned hierarchy. The
     // variable mClonedChild represents the top layer that will be cloned so this
     // layer will be the parent of mClonedChild.
diff --git a/services/surfaceflinger/RegionSamplingThread.cpp b/services/surfaceflinger/RegionSamplingThread.cpp
index 09615f9..e06bc88 100644
--- a/services/surfaceflinger/RegionSamplingThread.cpp
+++ b/services/surfaceflinger/RegionSamplingThread.cpp
@@ -481,6 +481,12 @@
     // 1) The region sampling thread is the last owner of the buffer, and the freeing of the buffer
     // happens in this thread, as opposed to the main thread.
     // 2) The listener(s) receive their notifications prior to freeing the buffer.
+    if (mCachedBuffer != nullptr && mCachedBuffer != buffer) {
+        if (mFlinger.getRenderEngine().getRenderEngineType() ==
+            renderengine::RenderEngine::RenderEngineType::SKIA_GL_THREADED) {
+            mFlinger.getRenderEngine().unbindExternalTextureBuffer(mCachedBuffer->getId());
+        }
+    }
     mCachedBuffer = buffer;
     ATRACE_INT(lumaSamplingStepTag, static_cast<int>(samplingStep::noWorkNeeded));
 }
diff --git a/services/surfaceflinger/SurfaceFlinger.cpp b/services/surfaceflinger/SurfaceFlinger.cpp
index 6a98e36..8007dd3 100644
--- a/services/surfaceflinger/SurfaceFlinger.cpp
+++ b/services/surfaceflinger/SurfaceFlinger.cpp
@@ -3307,6 +3307,7 @@
                     transactions.push_back(transaction);
                 }
                 mTransactionQueue.pop();
+                ATRACE_INT("TransactionQueue", mTransactionQueue.size());
             }
         }
 
@@ -3360,6 +3361,10 @@
         if (!layer) {
             continue;
         }
+        if (layer->frameIsEarly(expectedPresentTime)) {
+            ATRACE_NAME("frameIsEarly()");
+            return false;
+        }
 
         if (!mScheduler->isVsyncValid(expectedPresentTime, layer->getOwnerUid())) {
             ATRACE_NAME("!isVsyncValidForUid");
@@ -3381,6 +3386,76 @@
     return ready;
 }
 
+void SurfaceFlinger::queueTransaction(TransactionState state) {
+    Mutex::Autolock _l(mQueueLock);
+
+    // If its TransactionQueue already has a pending TransactionState or if it is pending
+    auto itr = mPendingTransactionQueues.find(state.applyToken);
+    // if this is an animation frame, wait until prior animation frame has
+    // been applied by SF
+    if (state.flags & eAnimation) {
+        while (itr != mPendingTransactionQueues.end()) {
+            status_t err = mTransactionQueueCV.waitRelative(mQueueLock, s2ns(5));
+            if (CC_UNLIKELY(err != NO_ERROR)) {
+                ALOGW_IF(err == TIMED_OUT,
+                         "setTransactionState timed out "
+                         "waiting for animation frame to apply");
+                break;
+            }
+            itr = mPendingTransactionQueues.find(state.applyToken);
+        }
+    }
+
+    mTransactionQueue.emplace(state);
+    ATRACE_INT("TransactionQueue", mTransactionQueue.size());
+
+    // TODO(b/159125966): Remove eEarlyWakeup completely as no client should use this flag
+    if (state.flags & eEarlyWakeup) {
+        ALOGW("eEarlyWakeup is deprecated. Use eExplicitEarlyWakeup[Start|End]");
+    }
+
+    if (!(state.permissions & Permission::ACCESS_SURFACE_FLINGER) &&
+        (state.flags & (eExplicitEarlyWakeupStart | eExplicitEarlyWakeupEnd))) {
+        ALOGE("Only WindowManager is allowed to use eExplicitEarlyWakeup[Start|End] flags");
+        state.flags &= ~(eExplicitEarlyWakeupStart | eExplicitEarlyWakeupEnd);
+    }
+
+    const auto schedule = [](uint32_t flags) {
+        if (flags & eEarlyWakeup) return TransactionSchedule::Early;
+        if (flags & eExplicitEarlyWakeupEnd) return TransactionSchedule::EarlyEnd;
+        if (flags & eExplicitEarlyWakeupStart) return TransactionSchedule::EarlyStart;
+        return TransactionSchedule::Late;
+    }(state.flags);
+
+    setTransactionFlags(eTransactionFlushNeeded, schedule);
+}
+
+void SurfaceFlinger::waitForSynchronousTransaction(bool synchronous, bool syncInput) {
+    Mutex::Autolock _l(mStateLock);
+    if (synchronous) {
+        mTransactionPending = true;
+    }
+    if (syncInput) {
+        mPendingSyncInputWindows = true;
+    }
+
+    // applyTransactionState can be called by either the main SF thread or by
+    // another process through setTransactionState.  While a given process may wish
+    // to wait on synchronous transactions, the main SF thread should never
+    // be blocked.  Therefore, we only wait if isMainThread is false.
+    while (mTransactionPending || mPendingSyncInputWindows) {
+        status_t err = mTransactionCV.waitRelative(mStateLock, s2ns(5));
+        if (CC_UNLIKELY(err != NO_ERROR)) {
+            // just in case something goes wrong in SF, return to the
+            // called after a few seconds.
+            ALOGW_IF(err == TIMED_OUT, "setTransactionState timed out!");
+            mTransactionPending = false;
+            mPendingSyncInputWindows = false;
+            break;
+        }
+    }
+}
+
 status_t SurfaceFlinger::setTransactionState(
         const FrameTimelineInfo& frameTimelineInfo, const Vector<ComposerState>& states,
         const Vector<DisplayState>& displays, uint32_t flags, const sp<IBinder>& applyToken,
@@ -3404,101 +3479,15 @@
     const int originPid = ipc->getCallingPid();
     const int originUid = ipc->getCallingUid();
 
-    {
-        Mutex::Autolock _l(mQueueLock);
-        // If its TransactionQueue already has a pending TransactionState or if it is pending
-        auto itr = mPendingTransactionQueues.find(applyToken);
-        // if this is an animation frame, wait until prior animation frame has
-        // been applied by SF
-        if (flags & eAnimation) {
-            while (itr != mPendingTransactionQueues.end()) {
-                status_t err = mTransactionQueueCV.waitRelative(mQueueLock, s2ns(5));
-                if (CC_UNLIKELY(err != NO_ERROR)) {
-                    ALOGW_IF(err == TIMED_OUT,
-                             "setTransactionState timed out "
-                             "waiting for animation frame to apply");
-                    break;
-                }
-                itr = mPendingTransactionQueues.find(applyToken);
-            }
-        }
+    queueTransaction({frameTimelineInfo, states, displays, flags, applyToken, inputWindowCommands,
+                      desiredPresentTime, isAutoTimestamp, uncacheBuffer, postTime, permissions,
+                      hasListenerCallbacks, listenerCallbacks, originPid, originUid,
+                      transactionId});
 
-        const bool pendingTransactions = itr != mPendingTransactionQueues.end();
-        // Expected present time is computed and cached on invalidate, so it may be stale.
-        if (!pendingTransactions) {
-            const auto now = systemTime();
-            const bool nextVsyncPending = now < mExpectedPresentTime.load();
-            const DisplayStatInfo stats = mScheduler->getDisplayStatInfo(now);
-            mExpectedPresentTime = calculateExpectedPresentTime(stats);
-            // The transaction might arrive just before the next vsync but after
-            // invalidate was called. In that case we need to get the next vsync
-            // afterwards.
-            if (nextVsyncPending) {
-                mExpectedPresentTime += stats.vsyncPeriod;
-            }
-        }
-
-        mTransactionQueue.emplace(frameTimelineInfo, states, displays, flags, applyToken,
-                                  inputWindowCommands, desiredPresentTime, isAutoTimestamp,
-                                  uncacheBuffer, postTime, permissions, hasListenerCallbacks,
-                                  listenerCallbacks, originPid, originUid, transactionId);
-
-        if (pendingTransactions ||
-            (!isAutoTimestamp && desiredPresentTime > mExpectedPresentTime.load())) {
-            setTransactionFlags(eTransactionFlushNeeded);
-            return NO_ERROR;
-        }
-
-        // TODO(b/159125966): Remove eEarlyWakeup completely as no client should use this flag
-        if (flags & eEarlyWakeup) {
-            ALOGW("eEarlyWakeup is deprecated. Use eExplicitEarlyWakeup[Start|End]");
-        }
-
-        if (!(permissions & Permission::ACCESS_SURFACE_FLINGER) &&
-            (flags & (eExplicitEarlyWakeupStart | eExplicitEarlyWakeupEnd))) {
-            ALOGE("Only WindowManager is allowed to use eExplicitEarlyWakeup[Start|End] flags");
-            flags &= ~(eExplicitEarlyWakeupStart | eExplicitEarlyWakeupEnd);
-        }
-
-        const auto schedule = [](uint32_t flags) {
-            if (flags & eEarlyWakeup) return TransactionSchedule::Early;
-            if (flags & eExplicitEarlyWakeupEnd) return TransactionSchedule::EarlyEnd;
-            if (flags & eExplicitEarlyWakeupStart) return TransactionSchedule::EarlyStart;
-            return TransactionSchedule::Late;
-        }(flags);
-        setTransactionFlags(eTransactionFlushNeeded, schedule);
-    }
-
-    // if this is a synchronous transaction, wait for it to take effect
-    // before returning.
     const bool synchronous = flags & eSynchronous;
     const bool syncInput = inputWindowCommands.syncInputWindows;
-    if (!synchronous && !syncInput) {
-        return NO_ERROR;
-    }
-
-    Mutex::Autolock _l(mStateLock);
-    if (synchronous) {
-        mTransactionPending = true;
-    }
-    if (syncInput) {
-        mPendingSyncInputWindows = true;
-    }
-
-    // applyTransactionState can be called by either the main SF thread or by
-    // another process through setTransactionState.  While a given process may wish
-    // to wait on synchronous transactions, the main SF thread should never
-    // be blocked.  Therefore, we only wait if isMainThread is false.
-    while (mTransactionPending || mPendingSyncInputWindows) {
-        status_t err = mTransactionCV.waitRelative(mStateLock, s2ns(5));
-        if (CC_UNLIKELY(err != NO_ERROR)) {
-            // just in case something goes wrong in SF, return to the
-            // called after a few seconds.
-            ALOGW_IF(err == TIMED_OUT, "setTransactionState timed out!");
-            mTransactionPending = false;
-            mPendingSyncInputWindows = false;
-            break;
-        }
+    if (synchronous || syncInput) {
+        waitForSynchronousTransaction(synchronous, syncInput);
     }
 
     return NO_ERROR;
@@ -4387,6 +4376,7 @@
                 {"--latency"s, argsDumper(&SurfaceFlinger::dumpStatsLocked)},
                 {"--latency-clear"s, argsDumper(&SurfaceFlinger::clearStatsLocked)},
                 {"--list"s, dumper(&SurfaceFlinger::listLayersLocked)},
+                {"--planner"s, argsDumper(&SurfaceFlinger::dumpPlannerInfo)},
                 {"--static-screen"s, dumper(&SurfaceFlinger::dumpStaticScreenStats)},
                 {"--timestats"s, protoDumper(&SurfaceFlinger::dumpTimeStats)},
                 {"--vsync"s, dumper(&SurfaceFlinger::dumpVSync)},
@@ -4523,6 +4513,13 @@
     mScheduler->dumpVsync(result);
 }
 
+void SurfaceFlinger::dumpPlannerInfo(const DumpArgs& args, std::string& result) const {
+    for (const auto& [token, display] : mDisplays) {
+        const auto compositionDisplay = display->getCompositionDisplay();
+        compositionDisplay->dumpPlannerInfo(args, result);
+    }
+}
+
 void SurfaceFlinger::dumpStaticScreenStats(std::string& result) const {
     result.append("Static screen stats:\n");
     for (size_t b = 0; b < SurfaceFlingerBE::NUM_BUCKETS - 1; ++b) {
@@ -5872,6 +5869,15 @@
                                             regionSampling, grayscale, captureResults);
         });
 
+        // TODO(b/180767535): Remove this once we optimize buffer lifecycle for RenderEngine
+        // Only do this when we're not doing region sampling, to allow the region sampling thread to
+        // manage buffer lifecycle itself.
+        if (!regionSampling &&
+            getRenderEngine().getRenderEngineType() ==
+                    renderengine::RenderEngine::RenderEngineType::SKIA_GL_THREADED) {
+            getRenderEngine().unbindExternalTextureBuffer(buffer->getId());
+        }
+
         captureResults.result = result;
         captureListener->onScreenCaptureCompleted(captureResults);
     }));
@@ -5991,8 +5997,12 @@
     base::unique_fd bufferFence;
     base::unique_fd drawFence;
     getRenderEngine().useProtectedContext(useProtected);
+
+    // TODO(b/180767535): Remove this once we optimize buffer lifecycle for RenderEngine
+    const bool useFramebufferCache = getRenderEngine().getRenderEngineType() ==
+            renderengine::RenderEngine::RenderEngineType::SKIA_GL_THREADED;
     getRenderEngine().drawLayers(clientCompositionDisplay, clientCompositionLayerPointers, buffer,
-                                 /*useFramebufferCache=*/false, std::move(bufferFence), &drawFence);
+                                 useFramebufferCache, std::move(bufferFence), &drawFence);
 
     if (drawFence >= 0) {
         sp<Fence> releaseFence = new Fence(dup(drawFence));
diff --git a/services/surfaceflinger/SurfaceFlinger.h b/services/surfaceflinger/SurfaceFlinger.h
index 68f22e8..1615ab6 100644
--- a/services/surfaceflinger/SurfaceFlinger.h
+++ b/services/surfaceflinger/SurfaceFlinger.h
@@ -1017,6 +1017,7 @@
     LayersProto dumpProtoFromMainThread(uint32_t traceFlags = SurfaceTracing::TRACE_ALL)
             EXCLUDES(mStateLock);
     void dumpOffscreenLayers(std::string& result) EXCLUDES(mStateLock);
+    void dumpPlannerInfo(const DumpArgs& args, std::string& result) const REQUIRES(mStateLock);
 
     bool isLayerTripleBufferingDisabled() const {
         return this->mLayerTripleBufferingDisabled;
@@ -1040,6 +1041,10 @@
     // either AID_GRAPHICS or AID_SYSTEM.
     status_t CheckTransactCodeCredentials(uint32_t code);
 
+    // Add transaction to the Transaction Queue
+    void queueTransaction(TransactionState state) EXCLUDES(mQueueLock);
+    void waitForSynchronousTransaction(bool synchronous, bool syncInput) EXCLUDES(mStateLock);
+
     /*
      * Generic Layer Metadata
      */
diff --git a/services/surfaceflinger/SurfaceFlingerProperties.cpp b/services/surfaceflinger/SurfaceFlingerProperties.cpp
index c043866..b3dca78 100644
--- a/services/surfaceflinger/SurfaceFlingerProperties.cpp
+++ b/services/surfaceflinger/SurfaceFlingerProperties.cpp
@@ -380,5 +380,9 @@
     return SurfaceFlingerProperties::enable_frame_rate_override().value_or(defaultValue);
 }
 
+bool enable_layer_caching(bool defaultValue) {
+    return SurfaceFlingerProperties::enable_layer_caching().value_or(defaultValue);
+}
+
 } // namespace sysprop
 } // namespace android
diff --git a/services/surfaceflinger/SurfaceFlingerProperties.h b/services/surfaceflinger/SurfaceFlingerProperties.h
index 816cb09..b19d216 100644
--- a/services/surfaceflinger/SurfaceFlingerProperties.h
+++ b/services/surfaceflinger/SurfaceFlingerProperties.h
@@ -100,6 +100,8 @@
 
 bool enable_frame_rate_override(bool defaultValue);
 
+bool enable_layer_caching(bool defaultValue);
+
 } // namespace sysprop
 } // namespace android
 #endif // SURFACEFLINGERPROPERTIES_H_
diff --git a/services/surfaceflinger/sysprop/SurfaceFlingerProperties.sysprop b/services/surfaceflinger/sysprop/SurfaceFlingerProperties.sysprop
index 4d25a7a..ee5542d 100644
--- a/services/surfaceflinger/sysprop/SurfaceFlingerProperties.sysprop
+++ b/services/surfaceflinger/sysprop/SurfaceFlingerProperties.sysprop
@@ -454,3 +454,12 @@
     access: Readonly
     prop_name: "ro.surface_flinger.enable_frame_rate_override"
 }
+
+# Enables Layer Caching
+prop {
+    api_name: "enable_layer_caching"
+    type: Boolean
+    scope: Public
+    access: Readonly
+    prop_name: "ro.surface_flinger.enable_layer_caching"
+}
diff --git a/services/surfaceflinger/sysprop/api/SurfaceFlingerProperties-current.txt b/services/surfaceflinger/sysprop/api/SurfaceFlingerProperties-current.txt
index 0e0be09..47e14f6 100644
--- a/services/surfaceflinger/sysprop/api/SurfaceFlingerProperties-current.txt
+++ b/services/surfaceflinger/sysprop/api/SurfaceFlingerProperties-current.txt
@@ -45,6 +45,10 @@
     prop_name: "ro.surface_flinger.enable_frame_rate_override"
   }
   prop {
+    api_name: "enable_layer_caching"
+    prop_name: "ro.surface_flinger.enable_layer_caching"
+  }
+  prop {
     api_name: "enable_protected_contents"
     prop_name: "ro.surface_flinger.protected_contents"
   }
diff --git a/services/surfaceflinger/tests/unittests/FrameTimelineTest.cpp b/services/surfaceflinger/tests/unittests/FrameTimelineTest.cpp
index 9a4e020..7e6141e 100644
--- a/services/surfaceflinger/tests/unittests/FrameTimelineTest.cpp
+++ b/services/surfaceflinger/tests/unittests/FrameTimelineTest.cpp
@@ -67,7 +67,7 @@
     void SetUp() override {
         mTimeStats = std::make_shared<mock::TimeStats>();
         mFrameTimeline = std::make_unique<impl::FrameTimeline>(mTimeStats, kSurfaceFlingerPid,
-                                                               kTestThresholds);
+                                                               kTestThresholds, kHwcDuration);
         mFrameTimeline->registerDataSource();
         mTokenManager = &mFrameTimeline->mTokenManager;
         mTraceCookieCounter = &mFrameTimeline->mTraceCookieCounter;
@@ -162,6 +162,7 @@
     static constexpr JankClassificationThresholds kTestThresholds{kPresentThreshold,
                                                                   kDeadlineThreshold,
                                                                   kStartThreshold};
+    static constexpr nsecs_t kHwcDuration = std::chrono::nanoseconds(3ns).count();
 };
 
 static const std::string sLayerNameOne = "layer1";
@@ -420,37 +421,29 @@
 
 // Tests related to TimeStats
 TEST_F(FrameTimelineTest, presentFenceSignaled_reportsLongSfCpu) {
-    Fps refreshRate = Fps(11);
+    Fps refreshRate = Fps::fromPeriodNsecs(11);
+    // Deadline delta is 2ms because, sf's adjusted deadline is 60 - composerTime(3) = 57ms.
     EXPECT_CALL(*mTimeStats,
                 incrementJankyFrames(
                         TimeStats::JankyFramesInfo{refreshRate, std::nullopt, sUidOne,
                                                    sLayerNameOne,
-                                                   JankType::SurfaceFlingerCpuDeadlineMissed,
-                                                   std::chrono::duration_cast<
-                                                           std::chrono::nanoseconds>(3ms)
-                                                           .count(),
-                                                   std::chrono::duration_cast<
-                                                           std::chrono::nanoseconds>(10ms)
-                                                           .count(),
+                                                   JankType::SurfaceFlingerCpuDeadlineMissed, 2, 10,
                                                    0}));
     auto presentFence1 = fenceFactory.createFenceTimeForTest(Fence::NO_FENCE);
-    int64_t surfaceFrameToken1 = mTokenManager->generateTokenForPredictions(
-            {std::chrono::nanoseconds(10ms).count(), std::chrono::nanoseconds(20ms).count(),
-             std::chrono::nanoseconds(60ms).count()});
-    int64_t sfToken1 = mTokenManager->generateTokenForPredictions(
-            {std::chrono::nanoseconds(52ms).count(), std::chrono::nanoseconds(56ms).count(),
-             std::chrono::nanoseconds(60ms).count()});
+    int64_t surfaceFrameToken1 = mTokenManager->generateTokenForPredictions({10, 20, 60});
+    int64_t sfToken1 = mTokenManager->generateTokenForPredictions({52, 60, 60});
+
     auto surfaceFrame1 =
             mFrameTimeline->createSurfaceFrameForToken({surfaceFrameToken1, sInputEventId}, sPidOne,
                                                        sUidOne, sLayerIdOne, sLayerNameOne,
                                                        sLayerNameOne);
-    mFrameTimeline->setSfWakeUp(sfToken1, std::chrono::nanoseconds(52ms).count(), refreshRate);
-    surfaceFrame1->setAcquireFenceTime(std::chrono::nanoseconds(20ms).count());
+    mFrameTimeline->setSfWakeUp(sfToken1, 52, refreshRate);
+    surfaceFrame1->setAcquireFenceTime(20);
     surfaceFrame1->setPresentState(SurfaceFrame::PresentState::Presented);
     mFrameTimeline->addSurfaceFrame(surfaceFrame1);
-    presentFence1->signalForTest(std::chrono::nanoseconds(70ms).count());
+    presentFence1->signalForTest(70);
 
-    mFrameTimeline->setSfPresent(std::chrono::nanoseconds(59ms).count(), presentFence1);
+    mFrameTimeline->setSfPresent(59, presentFence1);
 }
 
 TEST_F(FrameTimelineTest, presentFenceSignaled_reportsDisplayMiss) {
@@ -458,186 +451,153 @@
     EXPECT_CALL(*mTimeStats,
                 incrementJankyFrames(TimeStats::JankyFramesInfo{refreshRate, std::nullopt, sUidOne,
                                                                 sLayerNameOne, JankType::DisplayHAL,
-                                                                0, 0, 0}));
+                                                                -1, 0, 0}));
 
     auto presentFence1 = fenceFactory.createFenceTimeForTest(Fence::NO_FENCE);
-    int64_t surfaceFrameToken1 = mTokenManager->generateTokenForPredictions(
-            {std::chrono::nanoseconds(10ms).count(), std::chrono::nanoseconds(20ms).count(),
-             std::chrono::nanoseconds(60ms).count()});
-    int64_t sfToken1 = mTokenManager->generateTokenForPredictions(
-            {std::chrono::nanoseconds(52ms).count(), std::chrono::nanoseconds(56ms).count(),
-             std::chrono::nanoseconds(60ms).count()});
+    int64_t surfaceFrameToken1 = mTokenManager->generateTokenForPredictions({10, 20, 60});
+    int64_t sfToken1 = mTokenManager->generateTokenForPredictions({52, 60, 60});
+
     auto surfaceFrame1 =
             mFrameTimeline->createSurfaceFrameForToken({surfaceFrameToken1, sInputEventId}, sPidOne,
                                                        sUidOne, sLayerIdOne, sLayerNameOne,
                                                        sLayerNameOne);
-    mFrameTimeline->setSfWakeUp(sfToken1, std::chrono::nanoseconds(52ms).count(), refreshRate);
+    mFrameTimeline->setSfWakeUp(sfToken1, 52, refreshRate);
     surfaceFrame1->setPresentState(SurfaceFrame::PresentState::Presented);
-    surfaceFrame1->setAcquireFenceTime(std::chrono::nanoseconds(20ms).count());
+    surfaceFrame1->setAcquireFenceTime(20);
     mFrameTimeline->addSurfaceFrame(surfaceFrame1);
-    presentFence1->signalForTest(std::chrono::nanoseconds(90ms).count());
-    mFrameTimeline->setSfPresent(std::chrono::nanoseconds(56ms).count(), presentFence1);
+    presentFence1->signalForTest(90);
+    mFrameTimeline->setSfPresent(56, presentFence1);
     EXPECT_EQ(surfaceFrame1->getJankType(), JankType::DisplayHAL);
 }
 
 TEST_F(FrameTimelineTest, presentFenceSignaled_reportsAppMiss) {
     Fps refreshRate = Fps(11.0);
     EXPECT_CALL(*mTimeStats,
-                incrementJankyFrames(
-                        TimeStats::JankyFramesInfo{refreshRate, std::nullopt, sUidOne,
-                                                   sLayerNameOne, JankType::AppDeadlineMissed, 0, 0,
-                                                   std::chrono::duration_cast<
-                                                           std::chrono::nanoseconds>(25ms)
-                                                           .count()}));
+                incrementJankyFrames(TimeStats::JankyFramesInfo{refreshRate, std::nullopt, sUidOne,
+                                                                sLayerNameOne,
+                                                                JankType::AppDeadlineMissed, -1, 0,
+                                                                25}));
     auto presentFence1 = fenceFactory.createFenceTimeForTest(Fence::NO_FENCE);
-    int64_t surfaceFrameToken1 = mTokenManager->generateTokenForPredictions(
-            {std::chrono::nanoseconds(10ms).count(), std::chrono::nanoseconds(20ms).count(),
-             std::chrono::nanoseconds(60ms).count()});
-    int64_t sfToken1 = mTokenManager->generateTokenForPredictions(
-            {std::chrono::nanoseconds(82ms).count(), std::chrono::nanoseconds(86ms).count(),
-             std::chrono::nanoseconds(90ms).count()});
+    int64_t surfaceFrameToken1 = mTokenManager->generateTokenForPredictions({10, 20, 60});
+    int64_t sfToken1 = mTokenManager->generateTokenForPredictions({82, 90, 90});
+
     auto surfaceFrame1 =
             mFrameTimeline->createSurfaceFrameForToken({surfaceFrameToken1, sInputEventId}, sPidOne,
                                                        sUidOne, sLayerIdOne, sLayerNameOne,
                                                        sLayerNameOne);
-    surfaceFrame1->setAcquireFenceTime(std::chrono::nanoseconds(45ms).count());
-    mFrameTimeline->setSfWakeUp(sfToken1, std::chrono::nanoseconds(52ms).count(), refreshRate);
+    surfaceFrame1->setAcquireFenceTime(45);
+    mFrameTimeline->setSfWakeUp(sfToken1, 52, refreshRate);
 
     surfaceFrame1->setPresentState(SurfaceFrame::PresentState::Presented);
     mFrameTimeline->addSurfaceFrame(surfaceFrame1);
-    presentFence1->signalForTest(std::chrono::nanoseconds(90ms).count());
-    mFrameTimeline->setSfPresent(std::chrono::nanoseconds(86ms).count(), presentFence1);
+    presentFence1->signalForTest(90);
+    mFrameTimeline->setSfPresent(86, presentFence1);
 
     EXPECT_EQ(surfaceFrame1->getJankType(), JankType::AppDeadlineMissed);
 }
 
 TEST_F(FrameTimelineTest, presentFenceSignaled_reportsSfScheduling) {
-    Fps refreshRate = Fps::fromPeriodNsecs(std::chrono::nanoseconds(32ms).count());
+    Fps refreshRate = Fps::fromPeriodNsecs(32);
     EXPECT_CALL(*mTimeStats,
-                incrementJankyFrames(
-                        TimeStats::JankyFramesInfo{refreshRate, std::nullopt, sUidOne,
-                                                   sLayerNameOne,
-                                                   JankType::SurfaceFlingerScheduling, 0, 0,
-                                                   std::chrono::duration_cast<
-                                                           std::chrono::nanoseconds>(-10ms)
-                                                           .count()}));
+                incrementJankyFrames(TimeStats::JankyFramesInfo{refreshRate, std::nullopt, sUidOne,
+                                                                sLayerNameOne,
+                                                                JankType::SurfaceFlingerScheduling,
+                                                                -1, 0, -10}));
     auto presentFence1 = fenceFactory.createFenceTimeForTest(Fence::NO_FENCE);
-    int64_t surfaceFrameToken1 = mTokenManager->generateTokenForPredictions(
-            {std::chrono::nanoseconds(40ms).count(), std::chrono::nanoseconds(60ms).count(),
-             std::chrono::nanoseconds(92ms).count()});
-    int64_t sfToken1 = mTokenManager->generateTokenForPredictions(
-            {std::chrono::nanoseconds(52ms).count(), std::chrono::nanoseconds(56ms).count(),
-             std::chrono::nanoseconds(60ms).count()});
+    int64_t surfaceFrameToken1 = mTokenManager->generateTokenForPredictions({40, 60, 92});
+    int64_t sfToken1 = mTokenManager->generateTokenForPredictions({52, 60, 60});
+
     auto surfaceFrame1 =
             mFrameTimeline->createSurfaceFrameForToken({surfaceFrameToken1, sInputEventId}, sPidOne,
                                                        sUidOne, sLayerIdOne, sLayerNameOne,
                                                        sLayerNameOne);
-    surfaceFrame1->setAcquireFenceTime(std::chrono::nanoseconds(50ms).count());
-    mFrameTimeline->setSfWakeUp(sfToken1, std::chrono::nanoseconds(52ms).count(), refreshRate);
+    surfaceFrame1->setAcquireFenceTime(50);
+    mFrameTimeline->setSfWakeUp(sfToken1, 52, refreshRate);
 
     surfaceFrame1->setPresentState(SurfaceFrame::PresentState::Presented);
     mFrameTimeline->addSurfaceFrame(surfaceFrame1);
-    presentFence1->signalForTest(std::chrono::nanoseconds(60ms).count());
-    mFrameTimeline->setSfPresent(std::chrono::nanoseconds(56ms).count(), presentFence1);
+    presentFence1->signalForTest(60);
+    mFrameTimeline->setSfPresent(56, presentFence1);
 
     EXPECT_EQ(surfaceFrame1->getJankType(), JankType::SurfaceFlingerScheduling);
 }
 
 TEST_F(FrameTimelineTest, presentFenceSignaled_reportsSfPredictionError) {
-    Fps refreshRate = Fps(16.66f);
+    Fps refreshRate = Fps::fromPeriodNsecs(16);
     EXPECT_CALL(*mTimeStats,
-                incrementJankyFrames(
-                        TimeStats::JankyFramesInfo{refreshRate, std::nullopt, sUidOne,
-                                                   sLayerNameOne, JankType::PredictionError, 0,
-                                                   std::chrono::duration_cast<
-                                                           std::chrono::nanoseconds>(5ms)
-                                                           .count(),
-                                                   0}));
+                incrementJankyFrames(TimeStats::JankyFramesInfo{refreshRate, std::nullopt, sUidOne,
+                                                                sLayerNameOne,
+                                                                JankType::PredictionError, -1, 5,
+                                                                0}));
     auto presentFence1 = fenceFactory.createFenceTimeForTest(Fence::NO_FENCE);
-    int64_t surfaceFrameToken1 = mTokenManager->generateTokenForPredictions(
-            {std::chrono::nanoseconds(30ms).count(), std::chrono::nanoseconds(40ms).count(),
-             std::chrono::nanoseconds(60ms).count()});
-    int64_t sfToken1 = mTokenManager->generateTokenForPredictions(
-            {std::chrono::nanoseconds(52ms).count(), std::chrono::nanoseconds(56ms).count(),
-             std::chrono::nanoseconds(60ms).count()});
+    int64_t surfaceFrameToken1 = mTokenManager->generateTokenForPredictions({30, 40, 60});
+    int64_t sfToken1 = mTokenManager->generateTokenForPredictions({52, 60, 60});
+
     auto surfaceFrame1 =
             mFrameTimeline->createSurfaceFrameForToken({surfaceFrameToken1, sInputEventId}, sPidOne,
                                                        sUidOne, sLayerIdOne, sLayerNameOne,
                                                        sLayerNameOne);
-    surfaceFrame1->setAcquireFenceTime(std::chrono::nanoseconds(40ms).count());
-    mFrameTimeline->setSfWakeUp(sfToken1, std::chrono::nanoseconds(52ms).count(), refreshRate);
+    surfaceFrame1->setAcquireFenceTime(40);
+    mFrameTimeline->setSfWakeUp(sfToken1, 52, refreshRate);
 
     surfaceFrame1->setPresentState(SurfaceFrame::PresentState::Presented);
     mFrameTimeline->addSurfaceFrame(surfaceFrame1);
-    presentFence1->signalForTest(std::chrono::nanoseconds(65ms).count());
-    mFrameTimeline->setSfPresent(std::chrono::nanoseconds(56ms).count(), presentFence1);
+    presentFence1->signalForTest(65);
+    mFrameTimeline->setSfPresent(56, presentFence1);
 
     EXPECT_EQ(surfaceFrame1->getJankType(), JankType::PredictionError);
 }
 
 TEST_F(FrameTimelineTest, presentFenceSignaled_reportsAppBufferStuffing) {
-    Fps refreshRate = Fps::fromPeriodNsecs(std::chrono::nanoseconds(32ms).count());
+    Fps refreshRate = Fps::fromPeriodNsecs(32);
     EXPECT_CALL(*mTimeStats,
-                incrementJankyFrames(
-                        TimeStats::JankyFramesInfo{refreshRate, std::nullopt, sUidOne,
-                                                   sLayerNameOne,
-                                                   JankType::BufferStuffing |
-                                                           JankType::SurfaceFlingerScheduling,
-                                                   0, 0, 0}));
+                incrementJankyFrames(TimeStats::JankyFramesInfo{refreshRate, std::nullopt, sUidOne,
+                                                                sLayerNameOne,
+                                                                JankType::BufferStuffing, -1, 0,
+                                                                0}));
     auto presentFence1 = fenceFactory.createFenceTimeForTest(Fence::NO_FENCE);
-    int64_t surfaceFrameToken1 = mTokenManager->generateTokenForPredictions(
-            {std::chrono::nanoseconds(30ms).count(), std::chrono::nanoseconds(40ms).count(),
-             std::chrono::nanoseconds(58ms).count()});
-    int64_t sfToken1 = mTokenManager->generateTokenForPredictions(
-            {std::chrono::nanoseconds(82ms).count(), std::chrono::nanoseconds(86ms).count(),
-             std::chrono::nanoseconds(90ms).count()});
+    int64_t surfaceFrameToken1 = mTokenManager->generateTokenForPredictions({30, 40, 58});
+    int64_t sfToken1 = mTokenManager->generateTokenForPredictions({82, 90, 90});
+
     auto surfaceFrame1 =
             mFrameTimeline->createSurfaceFrameForToken({surfaceFrameToken1, sInputEventId}, sPidOne,
                                                        sUidOne, sLayerIdOne, sLayerNameOne,
                                                        sLayerNameOne);
-    surfaceFrame1->setAcquireFenceTime(std::chrono::nanoseconds(40ms).count());
-    mFrameTimeline->setSfWakeUp(sfToken1, std::chrono::nanoseconds(82ms).count(), refreshRate);
+    surfaceFrame1->setAcquireFenceTime(40);
+    mFrameTimeline->setSfWakeUp(sfToken1, 82, refreshRate);
 
     surfaceFrame1->setPresentState(SurfaceFrame::PresentState::Presented,
-                                   /*previousLatchTime*/
-                                   std::chrono::nanoseconds(56ms).count());
+                                   /*previousLatchTime*/ 56);
     mFrameTimeline->addSurfaceFrame(surfaceFrame1);
-    presentFence1->signalForTest(std::chrono::nanoseconds(90ms).count());
-    mFrameTimeline->setSfPresent(std::chrono::nanoseconds(86ms).count(), presentFence1);
+    presentFence1->signalForTest(90);
+    mFrameTimeline->setSfPresent(86, presentFence1);
 
-    EXPECT_EQ(surfaceFrame1->getJankType(),
-              JankType::BufferStuffing | JankType::SurfaceFlingerScheduling);
+    EXPECT_EQ(surfaceFrame1->getJankType(), JankType::BufferStuffing);
 }
 
 TEST_F(FrameTimelineTest, presentFenceSignaled_reportsAppMissWithRenderRate) {
-    Fps refreshRate = Fps(11.0);
-    Fps renderRate = Fps(30.0);
+    Fps refreshRate = Fps::fromPeriodNsecs(11);
+    Fps renderRate = Fps::fromPeriodNsecs(30);
     EXPECT_CALL(*mTimeStats,
                 incrementJankyFrames(
                         TimeStats::JankyFramesInfo{refreshRate, renderRate, sUidOne, sLayerNameOne,
-                                                   JankType::AppDeadlineMissed, 0, 0,
-                                                   std::chrono::duration_cast<
-                                                           std::chrono::nanoseconds>(25ms)
-                                                           .count()}));
+                                                   JankType::AppDeadlineMissed, -1, 0, 25}));
     auto presentFence1 = fenceFactory.createFenceTimeForTest(Fence::NO_FENCE);
-    int64_t surfaceFrameToken1 = mTokenManager->generateTokenForPredictions(
-            {std::chrono::nanoseconds(10ms).count(), std::chrono::nanoseconds(20ms).count(),
-             std::chrono::nanoseconds(60ms).count()});
-    int64_t sfToken1 = mTokenManager->generateTokenForPredictions(
-            {std::chrono::nanoseconds(82ms).count(), std::chrono::nanoseconds(86ms).count(),
-             std::chrono::nanoseconds(90ms).count()});
+    int64_t surfaceFrameToken1 = mTokenManager->generateTokenForPredictions({10, 20, 60});
+    int64_t sfToken1 = mTokenManager->generateTokenForPredictions({82, 90, 90});
+
     auto surfaceFrame1 =
             mFrameTimeline->createSurfaceFrameForToken({surfaceFrameToken1, sInputEventId}, sPidOne,
                                                        sUidOne, sLayerIdOne, sLayerNameOne,
                                                        sLayerNameOne);
-    surfaceFrame1->setAcquireFenceTime(std::chrono::nanoseconds(45ms).count());
-    mFrameTimeline->setSfWakeUp(sfToken1, std::chrono::nanoseconds(52ms).count(), refreshRate);
+    surfaceFrame1->setAcquireFenceTime(45);
+    mFrameTimeline->setSfWakeUp(sfToken1, 52, refreshRate);
 
     surfaceFrame1->setPresentState(SurfaceFrame::PresentState::Presented);
     surfaceFrame1->setRenderRate(renderRate);
     mFrameTimeline->addSurfaceFrame(surfaceFrame1);
-    presentFence1->signalForTest(std::chrono::nanoseconds(90ms).count());
-    mFrameTimeline->setSfPresent(std::chrono::nanoseconds(86ms).count(), presentFence1);
+    presentFence1->signalForTest(90);
+    mFrameTimeline->setSfPresent(86, presentFence1);
 
     EXPECT_EQ(surfaceFrame1->getJankType(), JankType::AppDeadlineMissed);
 }
@@ -893,7 +853,7 @@
     auto presentFence1 = fenceFactory.createFenceTimeForTest(Fence::NO_FENCE);
 
     tracingSession->StartBlocking();
-    int64_t displayFrameToken1 = mTokenManager->generateTokenForPredictions({10, 25, 30});
+    int64_t displayFrameToken1 = mTokenManager->generateTokenForPredictions({10, 30, 30});
 
     // Set up the display frame
     mFrameTimeline->setSfWakeUp(displayFrameToken1, 20, Fps::fromPeriodNsecs(11));
@@ -933,7 +893,7 @@
     // Packet - 1 : FrameEnd (ExpectedDisplayFrame)
     const auto& packet1 = packets[1];
     ASSERT_TRUE(packet1.has_timestamp());
-    EXPECT_EQ(packet1.timestamp(), 25u);
+    EXPECT_EQ(packet1.timestamp(), 30u);
     ASSERT_TRUE(packet1.has_frame_timeline_event());
 
     const auto& event1 = packet1.frame_timeline_event();
@@ -1179,12 +1139,9 @@
     auto presentFence1 = fenceFactory.createFenceTimeForTest(Fence::NO_FENCE);
 
     tracingSession->StartBlocking();
-    constexpr nsecs_t appStartTime =
-            std::chrono::duration_cast<std::chrono::nanoseconds>(10ms).count();
-    constexpr nsecs_t appEndTime =
-            std::chrono::duration_cast<std::chrono::nanoseconds>(20ms).count();
-    constexpr nsecs_t appPresentTime =
-            std::chrono::duration_cast<std::chrono::nanoseconds>(30ms).count();
+    constexpr nsecs_t appStartTime = std::chrono::nanoseconds(10ms).count();
+    constexpr nsecs_t appEndTime = std::chrono::nanoseconds(20ms).count();
+    constexpr nsecs_t appPresentTime = std::chrono::nanoseconds(30ms).count();
     int64_t surfaceFrameToken =
             mTokenManager->generateTokenForPredictions({appStartTime, appEndTime, appPresentTime});
 
@@ -1197,12 +1154,9 @@
     surfaceFrame1->setActualQueueTime(appEndTime);
     surfaceFrame1->setAcquireFenceTime(appEndTime);
 
-    constexpr nsecs_t sfStartTime =
-            std::chrono::duration_cast<std::chrono::nanoseconds>(20ms).count();
-    constexpr nsecs_t sfEndTime =
-            std::chrono::duration_cast<std::chrono::nanoseconds>(30ms).count();
-    constexpr nsecs_t sfPresentTime =
-            std::chrono::duration_cast<std::chrono::nanoseconds>(30ms).count();
+    constexpr nsecs_t sfStartTime = std::chrono::nanoseconds(20ms).count();
+    constexpr nsecs_t sfEndTime = std::chrono::nanoseconds(30ms).count();
+    constexpr nsecs_t sfPresentTime = std::chrono::nanoseconds(30ms).count();
     int64_t displayFrameToken =
             mTokenManager->generateTokenForPredictions({sfStartTime, sfEndTime, sfPresentTime});
 
@@ -1261,7 +1215,7 @@
     EXPECT_CALL(*mTimeStats, incrementJankyFrames(_));
     auto presentFence1 = fenceFactory.createFenceTimeForTest(Fence::NO_FENCE);
     int64_t surfaceFrameToken = mTokenManager->generateTokenForPredictions({10, 20, 30});
-    int64_t sfToken1 = mTokenManager->generateTokenForPredictions({22, 26, 30});
+    int64_t sfToken1 = mTokenManager->generateTokenForPredictions({22, 30, 30});
     auto surfaceFrame =
             mFrameTimeline->createSurfaceFrameForToken({surfaceFrameToken, sInputEventId}, sPidOne,
                                                        sUidOne, sLayerIdOne, sLayerNameOne,
@@ -1292,8 +1246,8 @@
 TEST_F(FrameTimelineTest, jankClassification_displayFrameOnTimeFinishEarlyPresent) {
     Fps vsyncRate = Fps::fromPeriodNsecs(11);
     auto presentFence1 = fenceFactory.createFenceTimeForTest(Fence::NO_FENCE);
-    int64_t sfToken1 = mTokenManager->generateTokenForPredictions({22, 26, 40});
-    int64_t sfToken2 = mTokenManager->generateTokenForPredictions({52, 56, 70});
+    int64_t sfToken1 = mTokenManager->generateTokenForPredictions({22, 30, 40});
+    int64_t sfToken2 = mTokenManager->generateTokenForPredictions({52, 60, 70});
     mFrameTimeline->setSfWakeUp(sfToken1, 22, vsyncRate);
     mFrameTimeline->setSfPresent(26, presentFence1);
     auto displayFrame = getDisplayFrame(0);
@@ -1331,8 +1285,8 @@
 TEST_F(FrameTimelineTest, jankClassification_displayFrameOnTimeFinishLatePresent) {
     Fps vsyncRate = Fps::fromPeriodNsecs(11);
     auto presentFence1 = fenceFactory.createFenceTimeForTest(Fence::NO_FENCE);
-    int64_t sfToken1 = mTokenManager->generateTokenForPredictions({22, 26, 40});
-    int64_t sfToken2 = mTokenManager->generateTokenForPredictions({52, 56, 70});
+    int64_t sfToken1 = mTokenManager->generateTokenForPredictions({22, 30, 40});
+    int64_t sfToken2 = mTokenManager->generateTokenForPredictions({52, 60, 70});
     mFrameTimeline->setSfWakeUp(sfToken1, 22, vsyncRate);
     mFrameTimeline->setSfPresent(26, presentFence1);
     auto displayFrame = getDisplayFrame(0);
@@ -1411,11 +1365,33 @@
     EXPECT_EQ(displayFrame->getJankType(), JankType::SurfaceFlingerCpuDeadlineMissed);
 }
 
+TEST_F(FrameTimelineTest, jankClassification_displayFrameLateStartLateFinishLatePresent) {
+    auto presentFence1 = fenceFactory.createFenceTimeForTest(Fence::NO_FENCE);
+    int64_t sfToken1 = mTokenManager->generateTokenForPredictions({22, 26, 40});
+    mFrameTimeline->setSfWakeUp(sfToken1, 26, Fps::fromPeriodNsecs(11));
+    mFrameTimeline->setSfPresent(36, presentFence1);
+    auto displayFrame = getDisplayFrame(0);
+    presentFence1->signalForTest(52);
+
+    // Fences haven't been flushed yet, so it should be 0
+    EXPECT_EQ(displayFrame->getActuals().presentTime, 0);
+
+    addEmptyDisplayFrame();
+    displayFrame = getDisplayFrame(0);
+
+    // Fences have flushed, so the present timestamps should be updated
+    EXPECT_EQ(displayFrame->getActuals().presentTime, 52);
+    EXPECT_EQ(displayFrame->getFrameStartMetadata(), FrameStartMetadata::LateStart);
+    EXPECT_EQ(displayFrame->getFramePresentMetadata(), FramePresentMetadata::LatePresent);
+    EXPECT_EQ(displayFrame->getFrameReadyMetadata(), FrameReadyMetadata::LateFinish);
+    EXPECT_EQ(displayFrame->getJankType(), JankType::SurfaceFlingerScheduling);
+}
+
 TEST_F(FrameTimelineTest, jankClassification_surfaceFrameOnTimeFinishEarlyPresent) {
     EXPECT_CALL(*mTimeStats, incrementJankyFrames(_));
     auto presentFence1 = fenceFactory.createFenceTimeForTest(Fence::NO_FENCE);
-    int64_t sfToken1 = mTokenManager->generateTokenForPredictions({22, 26, 40});
-    int64_t sfToken2 = mTokenManager->generateTokenForPredictions({52, 56, 70});
+    int64_t sfToken1 = mTokenManager->generateTokenForPredictions({22, 30, 40});
+    int64_t sfToken2 = mTokenManager->generateTokenForPredictions({52, 60, 70});
     int64_t surfaceFrameToken1 = mTokenManager->generateTokenForPredictions({5, 16, 40});
     int64_t surfaceFrameToken2 = mTokenManager->generateTokenForPredictions({25, 36, 70});
     auto surfaceFrame1 =
@@ -1426,7 +1402,7 @@
     mFrameTimeline->setSfWakeUp(sfToken1, 22, Fps::fromPeriodNsecs(11));
     surfaceFrame1->setPresentState(SurfaceFrame::PresentState::Presented);
     mFrameTimeline->addSurfaceFrame(surfaceFrame1);
-    mFrameTimeline->setSfPresent(26, presentFence1);
+    mFrameTimeline->setSfPresent(27, presentFence1);
     auto displayFrame1 = getDisplayFrame(0);
     auto& presentedSurfaceFrame1 = getSurfaceFrame(0, 0);
     presentFence1->signalForTest(30);
@@ -1446,7 +1422,7 @@
     mFrameTimeline->setSfWakeUp(sfToken2, 52, Fps::fromPeriodNsecs(11));
     surfaceFrame2->setPresentState(SurfaceFrame::PresentState::Presented);
     mFrameTimeline->addSurfaceFrame(surfaceFrame2);
-    mFrameTimeline->setSfPresent(56, presentFence2);
+    mFrameTimeline->setSfPresent(57, presentFence2);
     auto displayFrame2 = getDisplayFrame(1);
     auto& presentedSurfaceFrame2 = getSurfaceFrame(1, 0);
 
@@ -1494,8 +1470,8 @@
 TEST_F(FrameTimelineTest, jankClassification_surfaceFrameOnTimeFinishLatePresent) {
     EXPECT_CALL(*mTimeStats, incrementJankyFrames(_));
     auto presentFence1 = fenceFactory.createFenceTimeForTest(Fence::NO_FENCE);
-    int64_t sfToken1 = mTokenManager->generateTokenForPredictions({22, 26, 40});
-    int64_t sfToken2 = mTokenManager->generateTokenForPredictions({52, 56, 70});
+    int64_t sfToken1 = mTokenManager->generateTokenForPredictions({22, 30, 40});
+    int64_t sfToken2 = mTokenManager->generateTokenForPredictions({52, 60, 70});
     int64_t surfaceFrameToken1 = mTokenManager->generateTokenForPredictions({5, 16, 40});
     int64_t surfaceFrameToken2 = mTokenManager->generateTokenForPredictions({25, 36, 70});
     auto surfaceFrame1 =
@@ -1526,7 +1502,7 @@
     mFrameTimeline->setSfWakeUp(sfToken2, 52, Fps::fromPeriodNsecs(11));
     surfaceFrame2->setPresentState(SurfaceFrame::PresentState::Presented);
     mFrameTimeline->addSurfaceFrame(surfaceFrame2);
-    mFrameTimeline->setSfPresent(56, presentFence2);
+    mFrameTimeline->setSfPresent(57, presentFence2);
     auto displayFrame2 = getDisplayFrame(1);
     auto& presentedSurfaceFrame2 = getSurfaceFrame(1, 0);
 
@@ -1575,7 +1551,7 @@
     EXPECT_CALL(*mTimeStats, incrementJankyFrames(_));
 
     auto presentFence1 = fenceFactory.createFenceTimeForTest(Fence::NO_FENCE);
-    int64_t sfToken1 = mTokenManager->generateTokenForPredictions({42, 46, 50});
+    int64_t sfToken1 = mTokenManager->generateTokenForPredictions({42, 50, 50});
     int64_t surfaceFrameToken1 = mTokenManager->generateTokenForPredictions({5, 26, 60});
     auto surfaceFrame1 =
             mFrameTimeline->createSurfaceFrameForToken({surfaceFrameToken1, sInputEventId}, sPidOne,
@@ -1617,8 +1593,8 @@
 
     EXPECT_CALL(*mTimeStats, incrementJankyFrames(_)).Times(2);
     auto presentFence1 = fenceFactory.createFenceTimeForTest(Fence::NO_FENCE);
-    int64_t sfToken1 = mTokenManager->generateTokenForPredictions({32, 36, 40});
-    int64_t sfToken2 = mTokenManager->generateTokenForPredictions({42, 46, 50});
+    int64_t sfToken1 = mTokenManager->generateTokenForPredictions({32, 40, 40});
+    int64_t sfToken2 = mTokenManager->generateTokenForPredictions({42, 50, 50});
     int64_t surfaceFrameToken1 = mTokenManager->generateTokenForPredictions({5, 16, 30});
     int64_t surfaceFrameToken2 = mTokenManager->generateTokenForPredictions({25, 36, 50});
     auto surfaceFrame1 =
@@ -1693,8 +1669,8 @@
     int64_t surfaceFrameToken1 = mTokenManager->generateTokenForPredictions({10, 20, 30});
     int64_t surfaceFrameToken2 = mTokenManager->generateTokenForPredictions({40, 50, 60});
 
-    int64_t sfToken1 = mTokenManager->generateTokenForPredictions({52, 56, 60});
-    int64_t sfToken2 = mTokenManager->generateTokenForPredictions({112, 116, 120});
+    int64_t sfToken1 = mTokenManager->generateTokenForPredictions({52, 60, 60});
+    int64_t sfToken2 = mTokenManager->generateTokenForPredictions({112, 120, 120});
     auto surfaceFrame1 =
             mFrameTimeline->createSurfaceFrameForToken({surfaceFrameToken1, sInputEventId}, sPidOne,
                                                        sUidOne, sLayerIdOne, sLayerNameOne,
@@ -1764,12 +1740,90 @@
               JankType::AppDeadlineMissed | JankType::BufferStuffing);
 }
 
+TEST_F(FrameTimelineTest, jankClassification_appDeadlineAdjustedForBufferStuffing) {
+    // Layer specific increment
+    EXPECT_CALL(*mTimeStats, incrementJankyFrames(_)).Times(2);
+    auto presentFence1 = fenceFactory.createFenceTimeForTest(Fence::NO_FENCE);
+    int64_t surfaceFrameToken1 = mTokenManager->generateTokenForPredictions({10, 20, 30});
+    int64_t surfaceFrameToken2 = mTokenManager->generateTokenForPredictions({40, 50, 60});
+
+    int64_t sfToken1 = mTokenManager->generateTokenForPredictions({52, 60, 60});
+    int64_t sfToken2 = mTokenManager->generateTokenForPredictions({82, 90, 90});
+    auto surfaceFrame1 =
+            mFrameTimeline->createSurfaceFrameForToken({surfaceFrameToken1, sInputEventId}, sPidOne,
+                                                       sUidOne, sLayerIdOne, sLayerNameOne,
+                                                       sLayerNameOne);
+    surfaceFrame1->setAcquireFenceTime(50);
+    mFrameTimeline->setSfWakeUp(sfToken1, 52, Fps::fromPeriodNsecs(30));
+    surfaceFrame1->setPresentState(SurfaceFrame::PresentState::Presented);
+    mFrameTimeline->addSurfaceFrame(surfaceFrame1);
+    mFrameTimeline->setSfPresent(56, presentFence1);
+    auto displayFrame1 = getDisplayFrame(0);
+    auto& presentedSurfaceFrame1 = getSurfaceFrame(0, 0);
+    presentFence1->signalForTest(60);
+
+    // Fences for the first frame haven't been flushed yet, so it should be 0
+    EXPECT_EQ(displayFrame1->getActuals().presentTime, 0);
+    auto actuals1 = presentedSurfaceFrame1.getActuals();
+    EXPECT_EQ(actuals1.presentTime, 0);
+
+    // Trigger a flush by finalizing the next DisplayFrame
+    auto presentFence2 = fenceFactory.createFenceTimeForTest(Fence::NO_FENCE);
+    auto surfaceFrame2 =
+            mFrameTimeline->createSurfaceFrameForToken({surfaceFrameToken2, sInputEventId}, sPidOne,
+                                                       sUidOne, sLayerIdOne, sLayerNameOne,
+                                                       sLayerNameOne);
+    surfaceFrame2->setAcquireFenceTime(80);
+    mFrameTimeline->setSfWakeUp(sfToken2, 82, Fps::fromPeriodNsecs(30));
+    // Setting previous latch time to 54, adjusted deadline will be 54 + vsyncTime(30) = 84
+    surfaceFrame2->setPresentState(SurfaceFrame::PresentState::Presented, 54);
+    mFrameTimeline->addSurfaceFrame(surfaceFrame2);
+    mFrameTimeline->setSfPresent(86, presentFence2);
+    auto displayFrame2 = getDisplayFrame(1);
+    auto& presentedSurfaceFrame2 = getSurfaceFrame(1, 0);
+    presentFence2->signalForTest(90);
+
+    // Fences for the first frame have flushed, so the present timestamps should be updated
+    EXPECT_EQ(displayFrame1->getActuals().presentTime, 60);
+    actuals1 = presentedSurfaceFrame1.getActuals();
+    EXPECT_EQ(actuals1.endTime, 50);
+    EXPECT_EQ(actuals1.presentTime, 60);
+
+    EXPECT_EQ(displayFrame1->getFramePresentMetadata(), FramePresentMetadata::OnTimePresent);
+    EXPECT_EQ(displayFrame1->getFrameReadyMetadata(), FrameReadyMetadata::OnTimeFinish);
+    EXPECT_EQ(displayFrame1->getJankType(), JankType::None);
+
+    EXPECT_EQ(presentedSurfaceFrame1.getFramePresentMetadata(), FramePresentMetadata::LatePresent);
+    EXPECT_EQ(presentedSurfaceFrame1.getFrameReadyMetadata(), FrameReadyMetadata::LateFinish);
+    EXPECT_EQ(presentedSurfaceFrame1.getJankType(), JankType::AppDeadlineMissed);
+
+    // Fences for the second frame haven't been flushed yet, so it should be 0
+    EXPECT_EQ(displayFrame2->getActuals().presentTime, 0);
+    auto actuals2 = presentedSurfaceFrame2.getActuals();
+    EXPECT_EQ(actuals2.presentTime, 0);
+
+    addEmptyDisplayFrame();
+
+    // Fences for the second frame have flushed, so the present timestamps should be updated
+    EXPECT_EQ(displayFrame2->getActuals().presentTime, 90);
+    actuals2 = presentedSurfaceFrame2.getActuals();
+    EXPECT_EQ(actuals2.presentTime, 90);
+
+    EXPECT_EQ(displayFrame2->getFramePresentMetadata(), FramePresentMetadata::OnTimePresent);
+    EXPECT_EQ(displayFrame2->getFrameReadyMetadata(), FrameReadyMetadata::OnTimeFinish);
+    EXPECT_EQ(displayFrame2->getJankType(), JankType::None);
+
+    EXPECT_EQ(presentedSurfaceFrame2.getFramePresentMetadata(), FramePresentMetadata::LatePresent);
+    EXPECT_EQ(presentedSurfaceFrame2.getFrameReadyMetadata(), FrameReadyMetadata::OnTimeFinish);
+    EXPECT_EQ(presentedSurfaceFrame2.getJankType(), JankType::BufferStuffing);
+}
+
 TEST_F(FrameTimelineTest, computeFps_noLayerIds_returnsZero) {
     EXPECT_EQ(mFrameTimeline->computeFps({}), 0.0f);
 }
 
 TEST_F(FrameTimelineTest, computeFps_singleDisplayFrame_returnsZero) {
-    const auto oneHundredMs = std::chrono::duration_cast<std::chrono::nanoseconds>(100ms).count();
+    const auto oneHundredMs = std::chrono::nanoseconds(100ms).count();
 
     auto surfaceFrame1 =
             mFrameTimeline->createSurfaceFrameForToken(FrameTimelineInfo(), sPidOne, sUidOne,
@@ -1784,8 +1838,8 @@
 }
 
 TEST_F(FrameTimelineTest, computeFps_twoDisplayFrames_oneLayer) {
-    const auto oneHundredMs = std::chrono::duration_cast<std::chrono::nanoseconds>(100ms).count();
-    const auto twoHundredMs = std::chrono::duration_cast<std::chrono::nanoseconds>(200ms).count();
+    const auto oneHundredMs = std::chrono::nanoseconds(100ms).count();
+    const auto twoHundredMs = std::chrono::nanoseconds(200ms).count();
     auto surfaceFrame1 =
             mFrameTimeline->createSurfaceFrameForToken(FrameTimelineInfo(), sPidOne, sUidOne,
                                                        sLayerIdOne, sLayerNameOne, sLayerNameOne);
@@ -1808,8 +1862,8 @@
 }
 
 TEST_F(FrameTimelineTest, computeFps_twoDisplayFrames_twoLayers) {
-    const auto oneHundredMs = std::chrono::duration_cast<std::chrono::nanoseconds>(100ms).count();
-    const auto twoHundredMs = std::chrono::duration_cast<std::chrono::nanoseconds>(200ms).count();
+    const auto oneHundredMs = std::chrono::nanoseconds(100ms).count();
+    const auto twoHundredMs = std::chrono::nanoseconds(200ms).count();
     auto surfaceFrame1 =
             mFrameTimeline->createSurfaceFrameForToken(FrameTimelineInfo(), sPidOne, sUidOne,
                                                        sLayerIdOne, sLayerNameOne, sLayerNameOne);
@@ -1832,8 +1886,8 @@
 }
 
 TEST_F(FrameTimelineTest, computeFps_filtersOutLayers) {
-    const auto oneHundredMs = std::chrono::duration_cast<std::chrono::nanoseconds>(100ms).count();
-    const auto twoHundredMs = std::chrono::duration_cast<std::chrono::nanoseconds>(200ms).count();
+    const auto oneHundredMs = std::chrono::nanoseconds(100ms).count();
+    const auto twoHundredMs = std::chrono::nanoseconds(200ms).count();
     auto surfaceFrame1 =
             mFrameTimeline->createSurfaceFrameForToken(FrameTimelineInfo(), sPidOne, sUidOne,
                                                        sLayerIdOne, sLayerNameOne, sLayerNameOne);
@@ -1856,11 +1910,11 @@
 }
 
 TEST_F(FrameTimelineTest, computeFps_averagesOverMultipleFrames) {
-    const auto oneHundredMs = std::chrono::duration_cast<std::chrono::nanoseconds>(100ms).count();
-    const auto twoHundredMs = std::chrono::duration_cast<std::chrono::nanoseconds>(200ms).count();
-    const auto threeHundredMs = std::chrono::duration_cast<std::chrono::nanoseconds>(300ms).count();
-    const auto fiveHundredMs = std::chrono::duration_cast<std::chrono::nanoseconds>(500ms).count();
-    const auto sixHundredMs = std::chrono::duration_cast<std::chrono::nanoseconds>(600ms).count();
+    const auto oneHundredMs = std::chrono::nanoseconds(100ms).count();
+    const auto twoHundredMs = std::chrono::nanoseconds(200ms).count();
+    const auto threeHundredMs = std::chrono::nanoseconds(300ms).count();
+    const auto fiveHundredMs = std::chrono::nanoseconds(500ms).count();
+    const auto sixHundredMs = std::chrono::nanoseconds(600ms).count();
     auto surfaceFrame1 =
             mFrameTimeline->createSurfaceFrameForToken(FrameTimelineInfo(), sPidOne, sUidOne,
                                                        sLayerIdOne, sLayerNameOne, sLayerNameOne);
diff --git a/services/surfaceflinger/tests/unittests/TransactionApplicationTest.cpp b/services/surfaceflinger/tests/unittests/TransactionApplicationTest.cpp
index eb2c1ba..7c431a0 100644
--- a/services/surfaceflinger/tests/unittests/TransactionApplicationTest.cpp
+++ b/services/surfaceflinger/tests/unittests/TransactionApplicationTest.cpp
@@ -127,7 +127,6 @@
         ASSERT_EQ(0u, mFlinger.getTransactionQueue().size());
         // called in SurfaceFlinger::signalTransaction
         EXPECT_CALL(*mMessageQueue, invalidate()).Times(1);
-        EXPECT_CALL(*mVSyncTracker, nextAnticipatedVSyncTimeFrom(_)).WillOnce(Return(systemTime()));
         TransactionInfo transaction;
         setupSingle(transaction, flags, syncInputWindows,
                     /*desiredPresentTime*/ systemTime(), /*isAutoTimestamp*/ true,
@@ -163,8 +162,6 @@
         // first check will see desired present time has not passed,
         // but afterwards it will look like the desired present time has passed
         nsecs_t time = systemTime();
-        EXPECT_CALL(*mVSyncTracker, nextAnticipatedVSyncTimeFrom(_))
-                .WillOnce(Return(time + nsecs_t(5 * 1e8)));
         TransactionInfo transaction;
         setupSingle(transaction, flags, syncInputWindows,
                     /*desiredPresentTime*/ time + s2ns(1), false, FrameTimelineInfo{});
@@ -177,7 +174,11 @@
                                      transaction.id);
 
         nsecs_t returnedTime = systemTime();
-        EXPECT_LE(returnedTime, applicationSentTime + s2ns(5));
+        if ((flags & ISurfaceComposer::eSynchronous) || syncInputWindows) {
+            EXPECT_GE(systemTime(), applicationSentTime + s2ns(5));
+        } else {
+            EXPECT_LE(returnedTime, applicationSentTime + s2ns(5));
+        }
         // This transaction should have been placed on the transaction queue
         auto transactionQueue = mFlinger.getTransactionQueue();
         EXPECT_EQ(1u, transactionQueue.size());
@@ -187,9 +188,11 @@
         ASSERT_EQ(0u, mFlinger.getTransactionQueue().size());
         // called in SurfaceFlinger::signalTransaction
         nsecs_t time = systemTime();
-        EXPECT_CALL(*mMessageQueue, invalidate()).Times(1);
-        EXPECT_CALL(*mVSyncTracker, nextAnticipatedVSyncTimeFrom(_))
-                .WillOnce(Return(time + nsecs_t(5 * 1e8)));
+        if (!syncInputWindows) {
+            EXPECT_CALL(*mMessageQueue, invalidate()).Times(2);
+        } else {
+            EXPECT_CALL(*mMessageQueue, invalidate()).Times(1);
+        }
         // transaction that should go on the pending thread
         TransactionInfo transactionA;
         setupSingle(transactionA, /*flags*/ 0, /*syncInputWindows*/ false,
@@ -229,7 +232,8 @@
         // if this is an animation, this thread should be blocked for 5s
         // in setTransactionState waiting for transactionA to flush.  Otherwise,
         // the transaction should be placed on the pending queue
-        if (flags & ISurfaceComposer::eAnimation) {
+        if (flags & (ISurfaceComposer::eAnimation | ISurfaceComposer::eSynchronous) ||
+            syncInputWindows) {
             EXPECT_GE(systemTime(), applicationSentTime + s2ns(5));
         } else {
             EXPECT_LE(systemTime(), applicationSentTime + s2ns(5));
@@ -238,18 +242,9 @@
         // transaction that would goes to pending transaciton queue.
         mFlinger.flushTransactionQueues();
 
-        // check that there is one binder on the pending queue.
+        // check that the transaction was applied.
         auto transactionQueue = mFlinger.getPendingTransactionQueue();
-        EXPECT_EQ(1u, transactionQueue.size());
-
-        auto& [applyToken, transactionStates] = *(transactionQueue.begin());
-        EXPECT_EQ(2u, transactionStates.size());
-
-        auto& transactionStateA = transactionStates.front();
-        transactionStates.pop();
-        checkEqual(transactionA, transactionStateA);
-        auto& transactionStateB = transactionStates.front();
-        checkEqual(transactionB, transactionStateB);
+        EXPECT_EQ(0u, transactionQueue.size());
     }
 
     bool mHasListenerCallbacks = false;
@@ -262,10 +257,6 @@
     // called in SurfaceFlinger::signalTransaction
     EXPECT_CALL(*mMessageQueue, invalidate()).Times(1);
 
-    // nsecs_t time = systemTime();
-    EXPECT_CALL(*mVSyncTracker, nextAnticipatedVSyncTimeFrom(_))
-            .WillOnce(Return(nsecs_t(5 * 1e8)))
-            .WillOnce(Return(s2ns(2)));
     TransactionInfo transactionA; // transaction to go on pending queue
     setupSingle(transactionA, /*flags*/ 0, /*syncInputWindows*/ false,
                 /*desiredPresentTime*/ s2ns(1), false, FrameTimelineInfo{});
diff --git a/services/vr/bufferhubd/Android.bp b/services/vr/bufferhubd/Android.bp
index 8523bb2..f5491cf 100644
--- a/services/vr/bufferhubd/Android.bp
+++ b/services/vr/bufferhubd/Android.bp
@@ -18,8 +18,6 @@
     // all of the 'license_kinds' from "frameworks_native_license"
     // to get the below license kinds:
     //   SPDX-license-identifier-Apache-2.0
-    //   SPDX-license-identifier-MIT
-    //   SPDX-license-identifier-Unicode-DFS
     default_applicable_licenses: ["frameworks_native_license"],
 }
 
diff --git a/services/vr/hardware_composer/aidl/Android.bp b/services/vr/hardware_composer/aidl/Android.bp
index 98afdec..fa71ed7 100644
--- a/services/vr/hardware_composer/aidl/Android.bp
+++ b/services/vr/hardware_composer/aidl/Android.bp
@@ -4,8 +4,6 @@
     // all of the 'license_kinds' from "frameworks_native_license"
     // to get the below license kinds:
     //   SPDX-license-identifier-Apache-2.0
-    //   SPDX-license-identifier-MIT
-    //   SPDX-license-identifier-Unicode-DFS
     default_applicable_licenses: ["frameworks_native_license"],
 }