Merge "[native] Migrate deprecated Vk GrBackendSurface related functions" into main
diff --git a/cmds/atrace/atrace.cpp b/cmds/atrace/atrace.cpp
index 8105626..5719a09 100644
--- a/cmds/atrace/atrace.cpp
+++ b/cmds/atrace/atrace.cpp
@@ -692,7 +692,7 @@
     while (func) {
         if (!strchr(func, '*')) {
             String8 fancyFunc = String8::format("\n%s\n", func);
-            bool found = funcList.find(fancyFunc.string(), 0) >= 0;
+            bool found = funcList.find(fancyFunc.c_str(), 0) >= 0;
             if (!found || func[0] == '\0') {
                 fprintf(stderr, "error: \"%s\" is not a valid kernel function "
                         "to trace.\n", func);
@@ -796,11 +796,11 @@
     bool ok = true;
     while (!tokenizer->isEol()) {
         String8 token = tokenizer->nextToken(" ");
-        if (token.isEmpty()) {
+        if (token.empty()) {
             tokenizer->skipDelimiters(" ");
             continue;
         }
-        ok &= setCategoryEnable(token.string());
+        ok &= setCategoryEnable(token.c_str());
     }
     delete tokenizer;
     return ok;
diff --git a/cmds/cmd/cmd.cpp b/cmds/cmd/cmd.cpp
index 8f1c01a..b727398 100644
--- a/cmds/cmd/cmd.cpp
+++ b/cmds/cmd/cmd.cpp
@@ -78,7 +78,7 @@
             return -EPERM;
         }
 #if DEBUG
-        ALOGD("openFile: %s, full=%s", path8.string(), fullPath.string());
+        ALOGD("openFile: %s, full=%s", path8.c_str(), fullPath.c_str());
 #endif
         int flags = 0;
         bool checkRead = false;
@@ -96,10 +96,10 @@
             flags = O_RDWR;
             checkRead = checkWrite = true;
         } else {
-            mErrorLog << "Invalid mode requested: " << mode.string() << endl;
+            mErrorLog << "Invalid mode requested: " << mode.c_str() << endl;
             return -EINVAL;
         }
-        int fd = open(fullPath.string(), flags, S_IRWXU|S_IRWXG);
+        int fd = open(fullPath.c_str(), flags, S_IRWXU|S_IRWXG);
 #if DEBUG
         ALOGD("openFile: fd=%d", fd);
 #endif
@@ -109,29 +109,29 @@
         if (is_selinux_enabled() && seLinuxContext.size() > 0) {
             String8 seLinuxContext8(seLinuxContext);
             char* tmp = nullptr;
-            getfilecon(fullPath.string(), &tmp);
+            getfilecon(fullPath.c_str(), &tmp);
             Unique_SecurityContext context(tmp);
             if (checkWrite) {
-                int accessGranted = selinux_check_access(seLinuxContext8.string(), context.get(),
+                int accessGranted = selinux_check_access(seLinuxContext8.c_str(), context.get(),
                         "file", "write", nullptr);
                 if (accessGranted != 0) {
 #if DEBUG
                     ALOGD("openFile: failed selinux write check!");
 #endif
                     close(fd);
-                    mErrorLog << "System server has no access to write file context " << context.get() << " (from path " << fullPath.string() << ", context " << seLinuxContext8.string() << ")" << endl;
+                    mErrorLog << "System server has no access to write file context " << context.get() << " (from path " << fullPath.c_str() << ", context " << seLinuxContext8.c_str() << ")" << endl;
                     return -EPERM;
                 }
             }
             if (checkRead) {
-                int accessGranted = selinux_check_access(seLinuxContext8.string(), context.get(),
+                int accessGranted = selinux_check_access(seLinuxContext8.c_str(), context.get(),
                         "file", "read", nullptr);
                 if (accessGranted != 0) {
 #if DEBUG
                     ALOGD("openFile: failed selinux read check!");
 #endif
                     close(fd);
-                    mErrorLog << "System server has no access to read file context " << context.get() << " (from path " << fullPath.string() << ", context " << seLinuxContext8.string() << ")" << endl;
+                    mErrorLog << "System server has no access to read file context " << context.get() << " (from path " << fullPath.c_str() << ", context " << seLinuxContext8.c_str() << ")" << endl;
                     return -EPERM;
                 }
             }
diff --git a/cmds/dumpsys/dumpsys.cpp b/cmds/dumpsys/dumpsys.cpp
index 3d2bdf1..6c4e4b3 100644
--- a/cmds/dumpsys/dumpsys.cpp
+++ b/cmds/dumpsys/dumpsys.cpp
@@ -543,7 +543,7 @@
 
     if ((status == TIMED_OUT) && (!asProto)) {
         std::string msg = StringPrintf("\n*** SERVICE '%s' DUMP TIMEOUT (%llums) EXPIRED ***\n\n",
-                                       String8(serviceName).string(), timeout.count());
+                                       String8(serviceName).c_str(), timeout.count());
         WriteStringToFd(msg, fd);
     }
 
@@ -562,6 +562,6 @@
     oss << std::put_time(&finish_tm, "%Y-%m-%d %H:%M:%S");
     std::string msg =
         StringPrintf("--------- %.3fs was the duration of dumpsys %s, ending at: %s\n",
-                     elapsedDuration.count(), String8(serviceName).string(), oss.str().c_str());
+                     elapsedDuration.count(), String8(serviceName).c_str(), oss.str().c_str());
     WriteStringToFd(msg, fd);
 }
diff --git a/libs/binder/rust/tests/parcel_fuzzer/read_utils.rs b/libs/binder/rust/tests/parcel_fuzzer/read_utils.rs
index a2d48b6..2c8d05f 100644
--- a/libs/binder/rust/tests/parcel_fuzzer/read_utils.rs
+++ b/libs/binder/rust/tests/parcel_fuzzer/read_utils.rs
@@ -89,14 +89,17 @@
     read_parcel_interface!(Option<Vec<u64>>),
     read_parcel_interface!(Option<Vec<String>>),
     read_parcel_interface!(ParcelFileDescriptor),
+    read_parcel_interface!(Vec<ParcelFileDescriptor>),
     read_parcel_interface!(Vec<Option<ParcelFileDescriptor>>),
     read_parcel_interface!(Option<Vec<ParcelFileDescriptor>>),
     read_parcel_interface!(Option<Vec<Option<ParcelFileDescriptor>>>),
     read_parcel_interface!(SpIBinder),
+    read_parcel_interface!(Vec<SpIBinder>),
     read_parcel_interface!(Vec<Option<SpIBinder>>),
     read_parcel_interface!(Option<Vec<SpIBinder>>),
     read_parcel_interface!(Option<Vec<Option<SpIBinder>>>),
     read_parcel_interface!(SomeParcelable),
+    read_parcel_interface!(Vec<SomeParcelable>),
     read_parcel_interface!(Vec<Option<SomeParcelable>>),
     read_parcel_interface!(Option<Vec<SomeParcelable>>),
     read_parcel_interface!(Option<Vec<Option<SomeParcelable>>>),
diff --git a/libs/gui/BufferQueueProducer.cpp b/libs/gui/BufferQueueProducer.cpp
index 10f5899..920b83d 100644
--- a/libs/gui/BufferQueueProducer.cpp
+++ b/libs/gui/BufferQueueProducer.cpp
@@ -508,13 +508,13 @@
         {
             if (CC_UNLIKELY(ATRACE_ENABLED())) {
                 if (buffer == nullptr) {
-                    ATRACE_FORMAT_INSTANT("%s buffer reallocation: null", mConsumerName.string());
+                    ATRACE_FORMAT_INSTANT("%s buffer reallocation: null", mConsumerName.c_str());
                 } else {
                     ATRACE_FORMAT_INSTANT("%s buffer reallocation actual %dx%d format:%d "
                                           "layerCount:%d "
                                           "usage:%d requested: %dx%d format:%d layerCount:%d "
                                           "usage:%d ",
-                                          mConsumerName.string(), width, height, format,
+                                          mConsumerName.c_str(), width, height, format,
                                           BQ_LAYER_COUNT, usage, buffer->getWidth(),
                                           buffer->getHeight(), buffer->getPixelFormat(),
                                           buffer->getLayerCount(), buffer->getUsage());
diff --git a/libs/gui/SurfaceComposerClient.cpp b/libs/gui/SurfaceComposerClient.cpp
index 92589c5..4db960e 100644
--- a/libs/gui/SurfaceComposerClient.cpp
+++ b/libs/gui/SurfaceComposerClient.cpp
@@ -2431,7 +2431,7 @@
 
     if (mStatus == NO_ERROR) {
         gui::CreateSurfaceResult result;
-        binder::Status status = mClient->createSurface(std::string(name.string()), flags,
+        binder::Status status = mClient->createSurface(std::string(name.c_str()), flags,
                                                        parentHandle, std::move(metadata), &result);
         err = statusTFromBinderStatus(status);
         if (outTransformHint) {
@@ -2768,6 +2768,20 @@
     return statusTFromBinderStatus(status);
 }
 
+status_t SurfaceComposerClient::updateSmallAreaDetection(std::vector<int32_t>& uids,
+                                                         std::vector<float>& thresholds) {
+    binder::Status status =
+            ComposerServiceAIDL::getComposerService()->updateSmallAreaDetection(uids, thresholds);
+    return statusTFromBinderStatus(status);
+}
+
+status_t SurfaceComposerClient::setSmallAreaDetectionThreshold(uid_t uid, float threshold) {
+    binder::Status status =
+            ComposerServiceAIDL::getComposerService()->setSmallAreaDetectionThreshold(uid,
+                                                                                      threshold);
+    return statusTFromBinderStatus(status);
+}
+
 void SurfaceComposerClient::setAutoLowLatencyMode(const sp<IBinder>& display, bool on) {
     ComposerServiceAIDL::getComposerService()->setAutoLowLatencyMode(display, on);
 }
diff --git a/libs/gui/aidl/android/gui/ISurfaceComposer.aidl b/libs/gui/aidl/android/gui/ISurfaceComposer.aidl
index c2f47fc..1c604a1 100644
--- a/libs/gui/aidl/android/gui/ISurfaceComposer.aidl
+++ b/libs/gui/aidl/android/gui/ISurfaceComposer.aidl
@@ -479,6 +479,15 @@
      */
     void setOverrideFrameRate(int uid, float frameRate);
 
+    oneway void updateSmallAreaDetection(in int[] uids, in float[] thresholds);
+
+    /**
+     * Set the small area detection threshold for a specified uid by SmallAreaDetectionController.
+     * Passing the threshold and uid to SurfaceFlinger to update the uid-threshold mapping
+     * in the scheduler.
+     */
+    oneway void setSmallAreaDetectionThreshold(int uid, float threshold);
+
     /**
      * Enables or disables the frame rate overlay in the top left corner.
      * Requires root or android.permission.HARDWARE_TEST
diff --git a/libs/gui/fuzzer/libgui_fuzzer_utils.h b/libs/gui/fuzzer/libgui_fuzzer_utils.h
index 2643fa7..177d5f8 100644
--- a/libs/gui/fuzzer/libgui_fuzzer_utils.h
+++ b/libs/gui/fuzzer/libgui_fuzzer_utils.h
@@ -154,6 +154,9 @@
     MOCK_METHOD(binder::Status, setDebugFlash, (int), (override));
     MOCK_METHOD(binder::Status, scheduleComposite, (), (override));
     MOCK_METHOD(binder::Status, scheduleCommit, (), (override));
+    MOCK_METHOD(binder::Status, updateSmallAreaDetection,
+                (const std::vector<int32_t>&, const std::vector<float>&), (override));
+    MOCK_METHOD(binder::Status, setSmallAreaDetectionThreshold, (int32_t, float), (override));
     MOCK_METHOD(binder::Status, getGpuContextPriority, (int32_t*), (override));
     MOCK_METHOD(binder::Status, getMaxAcquiredBufferCount, (int32_t*), (override));
     MOCK_METHOD(binder::Status, addWindowInfosListener,
diff --git a/libs/gui/include/gui/SurfaceComposerClient.h b/libs/gui/include/gui/SurfaceComposerClient.h
index fd9f186..6fef5d2 100644
--- a/libs/gui/include/gui/SurfaceComposerClient.h
+++ b/libs/gui/include/gui/SurfaceComposerClient.h
@@ -203,6 +203,16 @@
     // by GameManager.
     static status_t setOverrideFrameRate(uid_t uid, float frameRate);
 
+    // Update the small area detection whole uid-threshold mappings by same size uid and threshold
+    // vector.
+    // Ref:setSmallAreaDetectionThreshold.
+    static status_t updateSmallAreaDetection(std::vector<int32_t>& uids,
+                                             std::vector<float>& thresholds);
+
+    // Sets the small area detection threshold to particular apps (uid). Passing value 0 means
+    // to disable small area detection to the app.
+    static status_t setSmallAreaDetectionThreshold(uid_t uid, float threshold);
+
     // Switches on/off Auto Low Latency Mode on the connected display. This should only be
     // called if the connected display supports Auto Low Latency Mode as reported by
     // #getAutoLowLatencyModeSupport
diff --git a/libs/gui/tests/GLTest.cpp b/libs/gui/tests/GLTest.cpp
index ae79e5b..40af8e8 100644
--- a/libs/gui/tests/GLTest.cpp
+++ b/libs/gui/tests/GLTest.cpp
@@ -183,24 +183,24 @@
         msg += String8::format("r(%d isn't %d)", pixel[0], r);
     }
     if (g >= 0 && abs(g - int(pixel[1])) > tolerance) {
-        if (!msg.isEmpty()) {
+        if (!msg.empty()) {
             msg += " ";
         }
         msg += String8::format("g(%d isn't %d)", pixel[1], g);
     }
     if (b >= 0 && abs(b - int(pixel[2])) > tolerance) {
-        if (!msg.isEmpty()) {
+        if (!msg.empty()) {
             msg += " ";
         }
         msg += String8::format("b(%d isn't %d)", pixel[2], b);
     }
     if (a >= 0 && abs(a - int(pixel[3])) > tolerance) {
-        if (!msg.isEmpty()) {
+        if (!msg.empty()) {
             msg += " ";
         }
         msg += String8::format("a(%d isn't %d)", pixel[3], a);
     }
-    if (!msg.isEmpty()) {
+    if (!msg.empty()) {
         return ::testing::AssertionFailure(::testing::Message(msg.c_str()));
     } else {
         return ::testing::AssertionSuccess();
@@ -215,24 +215,24 @@
         msg += String8::format("left(%d isn't %d)", r1.left, r2.left);
     }
     if (abs(r1.top - r2.top) > tolerance) {
-        if (!msg.isEmpty()) {
+        if (!msg.empty()) {
             msg += " ";
         }
         msg += String8::format("top(%d isn't %d)", r1.top, r2.top);
     }
     if (abs(r1.right - r2.right) > tolerance) {
-        if (!msg.isEmpty()) {
+        if (!msg.empty()) {
             msg += " ";
         }
         msg += String8::format("right(%d isn't %d)", r1.right, r2.right);
     }
     if (abs(r1.bottom - r2.bottom) > tolerance) {
-        if (!msg.isEmpty()) {
+        if (!msg.empty()) {
             msg += " ";
         }
         msg += String8::format("bottom(%d isn't %d)", r1.bottom, r2.bottom);
     }
-    if (!msg.isEmpty()) {
+    if (!msg.empty()) {
         msg += String8::format(" R1: [%d %d %d %d] R2: [%d %d %d %d]",
                                r1.left, r1.top, r1.right, r1.bottom,
                                r2.left, r2.top, r2.right, r2.bottom);
diff --git a/libs/gui/tests/Surface_test.cpp b/libs/gui/tests/Surface_test.cpp
index ffb8622..daed764 100644
--- a/libs/gui/tests/Surface_test.cpp
+++ b/libs/gui/tests/Surface_test.cpp
@@ -999,6 +999,15 @@
 
     binder::Status scheduleCommit() override { return binder::Status::ok(); }
 
+    binder::Status updateSmallAreaDetection(const std::vector<int32_t>& /*uids*/,
+                                            const std::vector<float>& /*thresholds*/) {
+        return binder::Status::ok();
+    }
+
+    binder::Status setSmallAreaDetectionThreshold(int32_t /*uid*/, float /*threshold*/) {
+        return binder::Status::ok();
+    }
+
     binder::Status getGpuContextPriority(int32_t* /*outPriority*/) override {
         return binder::Status::ok();
     }
diff --git a/libs/input/KeyCharacterMap.cpp b/libs/input/KeyCharacterMap.cpp
index d571917..a4cd239 100644
--- a/libs/input/KeyCharacterMap.cpp
+++ b/libs/input/KeyCharacterMap.cpp
@@ -1247,7 +1247,7 @@
     }
 
     // Ensure that we consumed the entire token.
-    if (mTokenizer->nextToken(WHITESPACE).isEmpty()) {
+    if (mTokenizer->nextToken(WHITESPACE).empty()) {
         return NO_ERROR;
     }
 
diff --git a/libs/input/PropertyMap.cpp b/libs/input/PropertyMap.cpp
index 315f5a6..5f6f9e2 100644
--- a/libs/input/PropertyMap.cpp
+++ b/libs/input/PropertyMap.cpp
@@ -171,7 +171,7 @@
 
         if (!mTokenizer->isEol() && mTokenizer->peekChar() != '#') {
             String8 keyToken = mTokenizer->nextToken(WHITESPACE_OR_PROPERTY_DELIMITER);
-            if (keyToken.isEmpty()) {
+            if (keyToken.empty()) {
                 ALOGE("%s: Expected non-empty property key.", mTokenizer->getLocation().c_str());
                 return BAD_VALUE;
             }
@@ -200,13 +200,13 @@
                 return BAD_VALUE;
             }
 
-            if (mMap->hasProperty(keyToken.string())) {
+            if (mMap->hasProperty(keyToken.c_str())) {
                 ALOGE("%s: Duplicate property value for key '%s'.",
                       mTokenizer->getLocation().c_str(), keyToken.c_str());
                 return BAD_VALUE;
             }
 
-            mMap->addProperty(keyToken.string(), valueToken.string());
+            mMap->addProperty(keyToken.c_str(), valueToken.c_str());
         }
 
         mTokenizer->nextLine();
diff --git a/libs/input/VirtualKeyMap.cpp b/libs/input/VirtualKeyMap.cpp
index de62c87..8b8af42 100644
--- a/libs/input/VirtualKeyMap.cpp
+++ b/libs/input/VirtualKeyMap.cpp
@@ -146,7 +146,7 @@
     String8 token = mTokenizer->nextToken(WHITESPACE_OR_FIELD_DELIMITER);
     char* end;
     *outValue = strtol(token.c_str(), &end, 0);
-    if (token.isEmpty() || *end != '\0') {
+    if (token.empty() || *end != '\0') {
         ALOGE("Expected an integer, got '%s'.", token.c_str());
         return false;
     }
diff --git a/libs/sensor/Sensor.cpp b/libs/sensor/Sensor.cpp
index 9127b37..a1549ea 100644
--- a/libs/sensor/Sensor.cpp
+++ b/libs/sensor/Sensor.cpp
@@ -627,7 +627,7 @@
     if (size < len) {
         return false;
     }
-    outputString8.setTo(static_cast<char const*>(buffer), len);
+    outputString8 = String8(static_cast<char const*>(buffer), len);
 
     if (size < FlattenableUtils::align<4>(len)) {
         ALOGE("Malformed Sensor String8 field. Should be in a 4-byte aligned buffer but is not.");
diff --git a/libs/shaders/tests/Android.bp b/libs/shaders/tests/Android.bp
index 1e4f45a..5639d74 100644
--- a/libs/shaders/tests/Android.bp
+++ b/libs/shaders/tests/Android.bp
@@ -37,6 +37,7 @@
     shared_libs: [
         "android.hardware.graphics.common@1.2",
         "libnativewindow",
+        "libbase",
     ],
     static_libs: [
         "libarect",
diff --git a/libs/tonemap/tests/Android.bp b/libs/tonemap/tests/Android.bp
index 2abf515..5c5fc6c 100644
--- a/libs/tonemap/tests/Android.bp
+++ b/libs/tonemap/tests/Android.bp
@@ -36,6 +36,7 @@
     ],
     shared_libs: [
         "libnativewindow",
+        "libbase",
     ],
     static_libs: [
         "libmath",
diff --git a/libs/ultrahdr/include/ultrahdr/jpegdecoderhelper.h b/libs/ultrahdr/include/ultrahdr/jpegdecoderhelper.h
index 8b5499a..30149c1 100644
--- a/libs/ultrahdr/include/ultrahdr/jpegdecoderhelper.h
+++ b/libs/ultrahdr/include/ultrahdr/jpegdecoderhelper.h
@@ -26,6 +26,8 @@
 #include <utils/Errors.h>
 #include <vector>
 
+// constraint on max width and max height is only due to device alloc constraints
+// Can tune these values basing on the target device
 static const int kMaxWidth = 8192;
 static const int kMaxHeight = 8192;
 
@@ -74,15 +76,27 @@
      */
     size_t getXMPSize();
     /*
+     * Extracts EXIF package and updates the EXIF position / length without decoding the image.
+     */
+    bool extractEXIF(const void* image, int length);
+    /*
      * Returns the EXIF data from the image.
+     * This method must be called after extractEXIF() or decompressImage().
      */
     void* getEXIFPtr();
     /*
      * Returns the decompressed EXIF buffer size. This method must be called only after
-     * calling decompressImage() or getCompressedImageParameters().
+     * calling decompressImage(), extractEXIF() or getCompressedImageParameters().
      */
     size_t getEXIFSize();
     /*
+     * Returns the position offset of EXIF package
+     * (4 bypes offset to FF sign, the byte after FF E1 XX XX <this byte>),
+     * or -1  if no EXIF exists.
+     * This method must be called after extractEXIF() or decompressImage().
+     */
+    int getEXIFPos() { return mExifPos; }
+    /*
      * Returns the ICC data from the image.
      */
     void* getICCPtr();
@@ -94,9 +108,8 @@
     /*
      * Decompresses metadata of the image. All vectors are owned by the caller.
      */
-    bool getCompressedImageParameters(const void* image, int length,
-                                      size_t* pWidth, size_t* pHeight,
-                                      std::vector<uint8_t>* iccData,
+    bool getCompressedImageParameters(const void* image, int length, size_t* pWidth,
+                                      size_t* pHeight, std::vector<uint8_t>* iccData,
                                       std::vector<uint8_t>* exifData);
 
 private:
@@ -121,6 +134,9 @@
     // Resolution of the decompressed image.
     size_t mWidth;
     size_t mHeight;
+
+    // Position of EXIF package, default value is -1 which means no EXIF package appears.
+    size_t mExifPos;
 };
 } /* namespace android::ultrahdr  */
 
diff --git a/libs/ultrahdr/include/ultrahdr/jpegr.h b/libs/ultrahdr/include/ultrahdr/jpegr.h
index 850cb32..114c81d 100644
--- a/libs/ultrahdr/include/ultrahdr/jpegr.h
+++ b/libs/ultrahdr/include/ultrahdr/jpegr.h
@@ -366,22 +366,24 @@
      * the compressed gain map and optionally the exif package as inputs, and generate the XMP
      * metadata, and finally append everything in the order of:
      *     SOI, APP2(EXIF) (if EXIF is from outside), APP2(XMP), primary image, gain map
-     * Note that EXIF package is only available for encoding API-0 and API-1. For encoding API-2 and
-     * API-3 this parameter is null, but the primary image in JPEG/R may still have EXIF as long as
-     * the input JPEG has EXIF.
      *
-
+     * Note that in the final JPEG/R output, EXIF package will appear if ONLY ONE of the following
+     * conditions is fulfilled:
+     *  (1) EXIF package is available from outside input. I.e. pExif != nullptr.
+     *  (2) Input JPEG has EXIF.
+     * If both conditions are fulfilled, this method will return ERROR_JPEGR_INVALID_INPUT_TYPE
+     *
      * @param primary_jpg_image_ptr destination of primary image
      * @param gainmap_jpg_image_ptr destination of compressed gain map image
-     * @param (nullable) exif EXIF package
-     * @param (nullable) icc ICC package
+     * @param (nullable) pExif EXIF package
+     * @param (nullable) pIcc ICC package
      * @param icc_size length in bytes of ICC package
      * @param metadata JPEG/R metadata to encode in XMP of the jpeg
      * @param dest compressed JPEGR image
      * @return NO_ERROR if calculation succeeds, error code if error occurs.
      */
     status_t appendGainMap(jr_compressed_ptr primary_jpg_image_ptr,
-                           jr_compressed_ptr gainmap_jpg_image_ptr, jr_exif_ptr exif, void* icc,
+                           jr_compressed_ptr gainmap_jpg_image_ptr, jr_exif_ptr pExif, void* pIcc,
                            size_t icc_size, ultrahdr_metadata_ptr metadata, jr_compressed_ptr dest);
 
     /*
diff --git a/libs/ultrahdr/include/ultrahdr/ultrahdr.h b/libs/ultrahdr/include/ultrahdr/ultrahdr.h
index 66f7088..0252391 100644
--- a/libs/ultrahdr/include/ultrahdr/ultrahdr.h
+++ b/libs/ultrahdr/include/ultrahdr/ultrahdr.h
@@ -30,6 +30,7 @@
 } ultrahdr_color_gamut;
 
 // Transfer functions for image data
+// TODO: TF LINEAR is deprecated, remove this enum and the code surrounding it.
 typedef enum {
   ULTRAHDR_TF_UNSPECIFIED = -1,
   ULTRAHDR_TF_LINEAR = 0,
diff --git a/libs/ultrahdr/jpegdecoderhelper.cpp b/libs/ultrahdr/jpegdecoderhelper.cpp
index 33bf9ef..2e7940c 100644
--- a/libs/ultrahdr/jpegdecoderhelper.cpp
+++ b/libs/ultrahdr/jpegdecoderhelper.cpp
@@ -26,18 +26,23 @@
 
 namespace android::ultrahdr {
 
-#define ALIGNM(x, m)  ((((x) + ((m) - 1)) / (m)) * (m))
+#define ALIGNM(x, m) ((((x) + ((m)-1)) / (m)) * (m))
 
-const uint32_t kAPP0Marker = JPEG_APP0;      // JFIF
-const uint32_t kAPP1Marker = JPEG_APP0 + 1;  // EXIF, XMP
-const uint32_t kAPP2Marker = JPEG_APP0 + 2;  // ICC
+const uint32_t kAPP0Marker = JPEG_APP0;     // JFIF
+const uint32_t kAPP1Marker = JPEG_APP0 + 1; // EXIF, XMP
+const uint32_t kAPP2Marker = JPEG_APP0 + 2; // ICC
 
-const std::string kXmpNameSpace = "http://ns.adobe.com/xap/1.0/";
-const std::string kExifIdCode = "Exif";
 constexpr uint32_t kICCMarkerHeaderSize = 14;
 constexpr uint8_t kICCSig[] = {
         'I', 'C', 'C', '_', 'P', 'R', 'O', 'F', 'I', 'L', 'E', '\0',
 };
+constexpr uint8_t kXmpNameSpace[] = {
+        'h', 't', 't', 'p', ':', '/', '/', 'n', 's', '.', 'a', 'd', 'o', 'b', 'e',
+        '.', 'c', 'o', 'm', '/', 'x', 'a', 'p', '/', '1', '.', '0', '/', '\0',
+};
+constexpr uint8_t kExifIdCode[] = {
+        'E', 'x', 'i', 'f', '\0', '\0',
+};
 
 struct jpegr_source_mgr : jpeg_source_mgr {
     jpegr_source_mgr(const uint8_t* ptr, int len);
@@ -76,8 +81,8 @@
 
 static void jpegr_term_source(j_decompress_ptr /*cinfo*/) {}
 
-jpegr_source_mgr::jpegr_source_mgr(const uint8_t* ptr, int len) :
-        mBufferPtr(ptr), mBufferLength(len) {
+jpegr_source_mgr::jpegr_source_mgr(const uint8_t* ptr, int len)
+      : mBufferPtr(ptr), mBufferLength(len) {
     init_source = jpegr_init_source;
     fill_input_buffer = jpegr_fill_input_buffer;
     skip_input_data = jpegr_skip_input_data;
@@ -92,25 +97,18 @@
     longjmp(err->setjmp_buffer, 1);
 }
 
-JpegDecoderHelper::JpegDecoderHelper() {
-}
+JpegDecoderHelper::JpegDecoderHelper() {}
 
-JpegDecoderHelper::~JpegDecoderHelper() {
-}
+JpegDecoderHelper::~JpegDecoderHelper() {}
 
 bool JpegDecoderHelper::decompressImage(const void* image, int length, bool decodeToRGBA) {
     if (image == nullptr || length <= 0) {
         ALOGE("Image size can not be handled: %d", length);
         return false;
     }
-
     mResultBuffer.clear();
     mXMPBuffer.clear();
-    if (!decode(image, length, decodeToRGBA)) {
-        return false;
-    }
-
-    return true;
+    return decode(image, length, decodeToRGBA);
 }
 
 void* JpegDecoderHelper::getDecompressedImagePtr() {
@@ -153,11 +151,15 @@
     return mHeight;
 }
 
-bool JpegDecoderHelper::decode(const void* image, int length, bool decodeToRGBA) {
+// Here we only handle the first EXIF package, and in theary EXIF (or JFIF) must be the first
+// in the image file.
+// We assume that all packages are starting with two bytes marker (eg FF E1 for EXIF package),
+// two bytes of package length which is stored in marker->original_length, and the real data
+// which is stored in marker->data.
+bool JpegDecoderHelper::extractEXIF(const void* image, int length) {
     jpeg_decompress_struct cinfo;
     jpegr_source_mgr mgr(static_cast<const uint8_t*>(image), length);
     jpegrerror_mgr myerr;
-    bool status = true;
 
     cinfo.err = jpeg_std_error(&myerr.pub);
     myerr.pub.error_exit = jpegrerror_exit;
@@ -170,11 +172,61 @@
 
     jpeg_save_markers(&cinfo, kAPP0Marker, 0xFFFF);
     jpeg_save_markers(&cinfo, kAPP1Marker, 0xFFFF);
-    jpeg_save_markers(&cinfo, kAPP2Marker, 0xFFFF);
 
     cinfo.src = &mgr;
     jpeg_read_header(&cinfo, TRUE);
 
+    size_t pos = 2;  // position after SOI
+    for (jpeg_marker_struct* marker = cinfo.marker_list;
+         marker;
+         marker = marker->next) {
+
+        pos += 4;
+        pos += marker->original_length;
+
+        if (marker->marker != kAPP1Marker) {
+            continue;
+        }
+
+        const unsigned int len = marker->data_length;
+
+        if (len > sizeof(kExifIdCode) &&
+            !memcmp(marker->data, kExifIdCode, sizeof(kExifIdCode))) {
+            mEXIFBuffer.resize(len, 0);
+            memcpy(static_cast<void*>(mEXIFBuffer.data()), marker->data, len);
+            mExifPos = pos - marker->original_length;
+            break;
+        }
+    }
+
+    jpeg_destroy_decompress(&cinfo);
+    return true;
+}
+
+bool JpegDecoderHelper::decode(const void* image, int length, bool decodeToRGBA) {
+    bool status = true;
+    jpeg_decompress_struct cinfo;
+    jpegrerror_mgr myerr;
+    cinfo.err = jpeg_std_error(&myerr.pub);
+    myerr.pub.error_exit = jpegrerror_exit;
+    if (setjmp(myerr.setjmp_buffer)) {
+        jpeg_destroy_decompress(&cinfo);
+        return false;
+    }
+
+    jpeg_create_decompress(&cinfo);
+
+    jpeg_save_markers(&cinfo, kAPP0Marker, 0xFFFF);
+    jpeg_save_markers(&cinfo, kAPP1Marker, 0xFFFF);
+    jpeg_save_markers(&cinfo, kAPP2Marker, 0xFFFF);
+
+    jpegr_source_mgr mgr(static_cast<const uint8_t*>(image), length);
+    cinfo.src = &mgr;
+    if (jpeg_read_header(&cinfo, TRUE) != JPEG_HEADER_OK) {
+        jpeg_destroy_decompress(&cinfo);
+        return false;
+    }
+
     // Save XMP data, EXIF data, and ICC data.
     // Here we only handle the first XMP / EXIF / ICC package.
     // We assume that all packages are starting with two bytes marker (eg FF E1 for EXIF package),
@@ -183,30 +235,29 @@
     bool exifAppears = false;
     bool xmpAppears = false;
     bool iccAppears = false;
+    size_t pos = 2;  // position after SOI
     for (jpeg_marker_struct* marker = cinfo.marker_list;
          marker && !(exifAppears && xmpAppears && iccAppears);
          marker = marker->next) {
-
+         pos += 4;
+         pos += marker->original_length;
         if (marker->marker != kAPP1Marker && marker->marker != kAPP2Marker) {
             continue;
         }
         const unsigned int len = marker->data_length;
         if (!xmpAppears &&
-            len > kXmpNameSpace.size() &&
-            !strncmp(reinterpret_cast<const char*>(marker->data),
-                     kXmpNameSpace.c_str(),
-                     kXmpNameSpace.size())) {
+            len > sizeof(kXmpNameSpace) &&
+            !memcmp(marker->data, kXmpNameSpace, sizeof(kXmpNameSpace))) {
             mXMPBuffer.resize(len+1, 0);
             memcpy(static_cast<void*>(mXMPBuffer.data()), marker->data, len);
             xmpAppears = true;
         } else if (!exifAppears &&
-                   len > kExifIdCode.size() &&
-                   !strncmp(reinterpret_cast<const char*>(marker->data),
-                            kExifIdCode.c_str(),
-                            kExifIdCode.size())) {
+                   len > sizeof(kExifIdCode) &&
+                   !memcmp(marker->data, kExifIdCode, sizeof(kExifIdCode))) {
             mEXIFBuffer.resize(len, 0);
             memcpy(static_cast<void*>(mEXIFBuffer.data()), marker->data, len);
             exifAppears = true;
+            mExifPos = pos - marker->original_length;
         } else if (!iccAppears &&
                    len > sizeof(kICCSig) &&
                    !memcmp(marker->data, kICCSig, sizeof(kICCSig))) {
@@ -216,31 +267,25 @@
         }
     }
 
-    if (cinfo.image_width > kMaxWidth || cinfo.image_height > kMaxHeight) {
-        // constraint on max width and max height is only due to alloc constraints
-        // tune these values basing on the target device
+    mWidth = cinfo.image_width;
+    mHeight = cinfo.image_height;
+    if (mWidth > kMaxWidth || mHeight > kMaxHeight) {
         status = false;
         goto CleanUp;
     }
 
-    mWidth = cinfo.image_width;
-    mHeight = cinfo.image_height;
-
     if (decodeToRGBA) {
         // The primary image is expected to be yuv420 sampling
-            if (cinfo.jpeg_color_space != JCS_YCbCr) {
-                status = false;
-                ALOGE("%s: decodeToRGBA unexpected jpeg color space ", __func__);
-                goto CleanUp;
-            }
-            if (cinfo.comp_info[0].h_samp_factor != 2 ||
-                    cinfo.comp_info[1].h_samp_factor != 1 ||
-                    cinfo.comp_info[2].h_samp_factor != 1 ||
-                    cinfo.comp_info[0].v_samp_factor != 2 ||
-                    cinfo.comp_info[1].v_samp_factor != 1 ||
-                    cinfo.comp_info[2].v_samp_factor != 1 ) {
-                status = false;
-                ALOGE("%s: decodeToRGBA unexpected primary image sub-sampling", __func__);
+        if (cinfo.jpeg_color_space != JCS_YCbCr) {
+            status = false;
+            ALOGE("%s: decodeToRGBA unexpected jpeg color space ", __func__);
+            goto CleanUp;
+        }
+        if (cinfo.comp_info[0].h_samp_factor != 2 || cinfo.comp_info[0].v_samp_factor != 2 ||
+            cinfo.comp_info[1].h_samp_factor != 1 || cinfo.comp_info[1].v_samp_factor != 1 ||
+            cinfo.comp_info[2].h_samp_factor != 1 || cinfo.comp_info[2].v_samp_factor != 1) {
+            status = false;
+            ALOGE("%s: decodeToRGBA unexpected primary image sub-sampling", __func__);
             goto CleanUp;
         }
         // 4 bytes per pixel
@@ -248,12 +293,9 @@
         cinfo.out_color_space = JCS_EXT_RGBA;
     } else {
         if (cinfo.jpeg_color_space == JCS_YCbCr) {
-            if (cinfo.comp_info[0].h_samp_factor != 2 ||
-                cinfo.comp_info[1].h_samp_factor != 1 ||
-                cinfo.comp_info[2].h_samp_factor != 1 ||
-                cinfo.comp_info[0].v_samp_factor != 2 ||
-                cinfo.comp_info[1].v_samp_factor != 1 ||
-                cinfo.comp_info[2].v_samp_factor != 1) {
+            if (cinfo.comp_info[0].h_samp_factor != 2 || cinfo.comp_info[0].v_samp_factor != 2 ||
+                cinfo.comp_info[1].h_samp_factor != 1 || cinfo.comp_info[1].v_samp_factor != 1 ||
+                cinfo.comp_info[2].h_samp_factor != 1 || cinfo.comp_info[2].v_samp_factor != 1) {
                 status = false;
                 ALOGE("%s: decoding to YUV only supports 4:2:0 subsampling", __func__);
                 goto CleanUp;
@@ -271,11 +313,9 @@
     }
 
     cinfo.dct_method = JDCT_ISLOW;
-
     jpeg_start_decompress(&cinfo);
-
     if (!decompress(&cinfo, static_cast<const uint8_t*>(mResultBuffer.data()),
-            cinfo.jpeg_color_space == JCS_GRAYSCALE)) {
+                    cinfo.jpeg_color_space == JCS_GRAYSCALE)) {
         status = false;
         goto CleanUp;
     }
@@ -288,25 +328,20 @@
 }
 
 bool JpegDecoderHelper::decompress(jpeg_decompress_struct* cinfo, const uint8_t* dest,
-        bool isSingleChannel) {
-    if (isSingleChannel) {
-        return decompressSingleChannel(cinfo, dest);
-    }
-    if (cinfo->out_color_space == JCS_EXT_RGBA)
-        return decompressRGBA(cinfo, dest);
-    else
-        return decompressYUV(cinfo, dest);
+                                   bool isSingleChannel) {
+    return isSingleChannel
+            ? decompressSingleChannel(cinfo, dest)
+            : ((cinfo->out_color_space == JCS_EXT_RGBA) ? decompressRGBA(cinfo, dest)
+                                                        : decompressYUV(cinfo, dest));
 }
 
-bool JpegDecoderHelper::getCompressedImageParameters(const void* image, int length,
-                              size_t *pWidth, size_t *pHeight,
-                              std::vector<uint8_t> *iccData , std::vector<uint8_t> *exifData) {
+bool JpegDecoderHelper::getCompressedImageParameters(const void* image, int length, size_t* pWidth,
+                                                     size_t* pHeight, std::vector<uint8_t>* iccData,
+                                                     std::vector<uint8_t>* exifData) {
     jpeg_decompress_struct cinfo;
-    jpegr_source_mgr mgr(static_cast<const uint8_t*>(image), length);
     jpegrerror_mgr myerr;
     cinfo.err = jpeg_std_error(&myerr.pub);
     myerr.pub.error_exit = jpegrerror_exit;
-
     if (setjmp(myerr.setjmp_buffer)) {
         jpeg_destroy_decompress(&cinfo);
         return false;
@@ -316,6 +351,7 @@
     jpeg_save_markers(&cinfo, kAPP1Marker, 0xFFFF);
     jpeg_save_markers(&cinfo, kAPP2Marker, 0xFFFF);
 
+    jpegr_source_mgr mgr(static_cast<const uint8_t*>(image), length);
     cinfo.src = &mgr;
     if (jpeg_read_header(&cinfo, TRUE) != JPEG_HEADER_OK) {
         jpeg_destroy_decompress(&cinfo);
@@ -330,8 +366,7 @@
     }
 
     if (iccData != nullptr) {
-        for (jpeg_marker_struct* marker = cinfo.marker_list; marker;
-             marker = marker->next) {
+        for (jpeg_marker_struct* marker = cinfo.marker_list; marker; marker = marker->next) {
             if (marker->marker != kAPP2Marker) {
                 continue;
             }
@@ -353,9 +388,8 @@
             }
 
             const unsigned int len = marker->data_length;
-            if (len >= kExifIdCode.size() &&
-                !strncmp(reinterpret_cast<const char*>(marker->data), kExifIdCode.c_str(),
-                         kExifIdCode.size())) {
+            if (len >= sizeof(kExifIdCode) &&
+                !memcmp(marker->data, kExifIdCode, sizeof(kExifIdCode))) {
                 exifData->resize(len, 0);
                 memcpy(static_cast<void*>(exifData->data()), marker->data, len);
                 exifAppears = true;
@@ -368,25 +402,20 @@
 }
 
 bool JpegDecoderHelper::decompressRGBA(jpeg_decompress_struct* cinfo, const uint8_t* dest) {
-    JSAMPLE* decodeDst = (JSAMPLE*) dest;
-    uint32_t lines = 0;
-    // TODO: use batches for more effectiveness
-    while (lines < cinfo->image_height) {
-        uint32_t ret = jpeg_read_scanlines(cinfo, &decodeDst, 1);
-        if (ret == 0) {
-            break;
-        }
-        decodeDst += cinfo->image_width * 4;
-        lines++;
+    JSAMPLE* out = (JSAMPLE*)dest;
+
+    while (cinfo->output_scanline < cinfo->image_height) {
+        if (1 != jpeg_read_scanlines(cinfo, &out, 1)) return false;
+        out += cinfo->image_width * 4;
     }
-    return lines == cinfo->image_height;
+    return true;
 }
 
 bool JpegDecoderHelper::decompressYUV(jpeg_decompress_struct* cinfo, const uint8_t* dest) {
     JSAMPROW y[kCompressBatchSize];
     JSAMPROW cb[kCompressBatchSize / 2];
     JSAMPROW cr[kCompressBatchSize / 2];
-    JSAMPARRAY planes[3] {y, cb, cr};
+    JSAMPARRAY planes[3]{y, cb, cr};
 
     size_t y_plane_size = cinfo->image_width * cinfo->image_height;
     size_t uv_plane_size = y_plane_size / 4;
@@ -405,7 +434,7 @@
     JSAMPROW y_intrm[kCompressBatchSize];
     JSAMPROW cb_intrm[kCompressBatchSize / 2];
     JSAMPROW cr_intrm[kCompressBatchSize / 2];
-    JSAMPARRAY planes_intrm[3] {y_intrm, cb_intrm, cr_intrm};
+    JSAMPARRAY planes_intrm[3]{y_intrm, cb_intrm, cr_intrm};
     if (!is_width_aligned) {
         size_t mcu_row_size = aligned_width * kCompressBatchSize * 3 / 2;
         buffer_intrm = std::make_unique<uint8_t[]>(mcu_row_size);
@@ -462,9 +491,10 @@
     return true;
 }
 
-bool JpegDecoderHelper::decompressSingleChannel(jpeg_decompress_struct* cinfo, const uint8_t* dest) {
+bool JpegDecoderHelper::decompressSingleChannel(jpeg_decompress_struct* cinfo,
+                                                const uint8_t* dest) {
     JSAMPROW y[kCompressBatchSize];
-    JSAMPARRAY planes[1] {y};
+    JSAMPARRAY planes[1]{y};
 
     uint8_t* y_plane = const_cast<uint8_t*>(dest);
     std::unique_ptr<uint8_t[]> empty = std::make_unique<uint8_t[]>(cinfo->image_width);
@@ -475,7 +505,7 @@
     std::unique_ptr<uint8_t[]> buffer_intrm = nullptr;
     uint8_t* y_plane_intrm = nullptr;
     JSAMPROW y_intrm[kCompressBatchSize];
-    JSAMPARRAY planes_intrm[1] {y_intrm};
+    JSAMPARRAY planes_intrm[1]{y_intrm};
     if (!is_width_aligned) {
         size_t mcu_row_size = aligned_width * kCompressBatchSize;
         buffer_intrm = std::make_unique<uint8_t[]>(mcu_row_size);
@@ -510,4 +540,4 @@
     return true;
 }
 
-} // namespace ultrahdr
+} // namespace android::ultrahdr
diff --git a/libs/ultrahdr/jpegr.cpp b/libs/ultrahdr/jpegr.cpp
index ae81845..74760d9 100644
--- a/libs/ultrahdr/jpegr.cpp
+++ b/libs/ultrahdr/jpegr.cpp
@@ -72,6 +72,32 @@
   return cpuCoreCount;
 }
 
+/*
+ * Helper function copies the JPEG image from without EXIF.
+ *
+ * @param pDest destination of the data to be written.
+ * @param pSource source of data being written.
+ * @param exif_pos position of the EXIF package, which is aligned with jpegdecoder.getEXIFPos().
+ *                 (4 bytes offset to FF sign, the byte after FF E1 XX XX <this byte>).
+ * @param exif_size exif size without the initial 4 bytes, aligned with jpegdecoder.getEXIFSize().
+ */
+static void copyJpegWithoutExif(jr_compressed_ptr pDest,
+                                jr_compressed_ptr pSource,
+                                size_t exif_pos,
+                                size_t exif_size) {
+  memcpy(pDest, pSource, sizeof(jpegr_compressed_struct));
+
+  const size_t exif_offset = 4; //exif_pos has 4 bytes offset to the FF sign
+  pDest->length = pSource->length - exif_size - exif_offset;
+  pDest->data = new uint8_t[pDest->length];
+  std::unique_ptr<uint8_t[]> dest_data;
+  dest_data.reset(reinterpret_cast<uint8_t*>(pDest->data));
+  memcpy(pDest->data, pSource->data, exif_pos - exif_offset);
+  memcpy((uint8_t*)pDest->data + exif_pos - exif_offset,
+         (uint8_t*)pSource->data + exif_pos + exif_size,
+         pSource->length - exif_pos - exif_size);
+}
+
 status_t JpegR::areInputArgumentsValid(jr_uncompressed_ptr p010_image_ptr,
                                        jr_uncompressed_ptr yuv420_image_ptr,
                                        ultrahdr_transfer_function hdr_tf,
@@ -815,10 +841,13 @@
   map_data.reset(reinterpret_cast<uint8_t*>(dest->data));
 
   ColorTransformFn hdrInvOetf = nullptr;
-  float hdr_white_nits = kSdrWhiteNits;
+  float hdr_white_nits;
   switch (hdr_tf) {
     case ULTRAHDR_TF_LINEAR:
       hdrInvOetf = identityConversion;
+      // Note: this will produce clipping if the input exceeds kHlgMaxNits.
+      // TODO: TF LINEAR will be deprecated.
+      hdr_white_nits = kHlgMaxNits;
       break;
     case ULTRAHDR_TF_HLG:
 #if USE_HLG_INVOETF_LUT
@@ -1149,7 +1178,8 @@
 // JPEG/R structure:
 // SOI (ff d8)
 //
-// (Optional, only if EXIF package is from outside)
+// (Optional, if EXIF package is from outside (Encode API-0 API-1), or if EXIF package presents
+// in the JPEG input (Encode API-2, API-3, API-4))
 // APP1 (ff e1)
 // 2 bytes of length (2 + length of exif package)
 // EXIF package (this includes the first two bytes representing the package length)
@@ -1163,7 +1193,7 @@
 // 2 bytes of length
 // MPF
 //
-// (Required) primary image (without the first two bytes (SOI), may have other packages)
+// (Required) primary image (without the first two bytes (SOI) and EXIF, may have other packages)
 //
 // SOI (ff d8)
 //
@@ -1180,8 +1210,8 @@
 // Adobe XMP spec part 3 for XMP marker
 // ICC v4.3 spec for ICC
 status_t JpegR::appendGainMap(jr_compressed_ptr primary_jpg_image_ptr,
-                              jr_compressed_ptr gainmap_jpg_image_ptr, jr_exif_ptr exif, void* icc,
-                              size_t icc_size, ultrahdr_metadata_ptr metadata,
+                              jr_compressed_ptr gainmap_jpg_image_ptr, jr_exif_ptr pExif,
+                              void* pIcc, size_t icc_size, ultrahdr_metadata_ptr metadata,
                               jr_compressed_ptr dest) {
   if (primary_jpg_image_ptr == nullptr || gainmap_jpg_image_ptr == nullptr || metadata == nullptr ||
       dest == nullptr) {
@@ -1226,6 +1256,35 @@
   // same as primary
   const int xmp_primary_length = 2 + nameSpaceLength + xmp_primary.size();
 
+  // Check if EXIF package presents in the JPEG input.
+  // If so, extract and remove the EXIF package.
+  JpegDecoderHelper decoder;
+  if (!decoder.extractEXIF(primary_jpg_image_ptr->data, primary_jpg_image_ptr->length)) {
+    return ERROR_JPEGR_DECODE_ERROR;
+  }
+  jpegr_exif_struct exif_from_jpg;
+  exif_from_jpg.data = nullptr;
+  exif_from_jpg.length = 0;
+  jpegr_compressed_struct new_jpg_image;
+  new_jpg_image.data = nullptr;
+  new_jpg_image.length = 0;
+  if (decoder.getEXIFPos() != 0) {
+    if (pExif != nullptr) {
+      ALOGE("received EXIF from outside while the primary image already contains EXIF");
+      return ERROR_JPEGR_INVALID_INPUT_TYPE;
+    }
+    copyJpegWithoutExif(&new_jpg_image,
+                        primary_jpg_image_ptr,
+                        decoder.getEXIFPos(),
+                        decoder.getEXIFSize());
+    exif_from_jpg.data = decoder.getEXIFPtr();
+    exif_from_jpg.length = decoder.getEXIFSize();
+    pExif = &exif_from_jpg;
+  }
+
+  jr_compressed_ptr final_primary_jpg_image_ptr =
+          new_jpg_image.length == 0 ? primary_jpg_image_ptr : &new_jpg_image;
+
   int pos = 0;
   // Begin primary image
   // Write SOI
@@ -1233,15 +1292,15 @@
   JPEGR_CHECK(Write(dest, &photos_editing_formats::image_io::JpegMarker::kSOI, 1, pos));
 
   // Write EXIF
-  if (exif != nullptr) {
-    const int length = 2 + exif->length;
+  if (pExif != nullptr) {
+    const int length = 2 + pExif->length;
     const uint8_t lengthH = ((length >> 8) & 0xff);
     const uint8_t lengthL = (length & 0xff);
     JPEGR_CHECK(Write(dest, &photos_editing_formats::image_io::JpegMarker::kStart, 1, pos));
     JPEGR_CHECK(Write(dest, &photos_editing_formats::image_io::JpegMarker::kAPP1, 1, pos));
     JPEGR_CHECK(Write(dest, &lengthH, 1, pos));
     JPEGR_CHECK(Write(dest, &lengthL, 1, pos));
-    JPEGR_CHECK(Write(dest, exif->data, exif->length, pos));
+    JPEGR_CHECK(Write(dest, pExif->data, pExif->length, pos));
   }
 
   // Prepare and write XMP
@@ -1258,7 +1317,7 @@
   }
 
   // Write ICC
-  if (icc != nullptr && icc_size > 0) {
+  if (pIcc != nullptr && icc_size > 0) {
     const int length = icc_size + 2;
     const uint8_t lengthH = ((length >> 8) & 0xff);
     const uint8_t lengthL = (length & 0xff);
@@ -1266,7 +1325,7 @@
     JPEGR_CHECK(Write(dest, &photos_editing_formats::image_io::JpegMarker::kAPP2, 1, pos));
     JPEGR_CHECK(Write(dest, &lengthH, 1, pos));
     JPEGR_CHECK(Write(dest, &lengthL, 1, pos));
-    JPEGR_CHECK(Write(dest, icc, icc_size, pos));
+    JPEGR_CHECK(Write(dest, pIcc, icc_size, pos));
   }
 
   // Prepare and write MPF
@@ -1274,7 +1333,7 @@
     const int length = 2 + calculateMpfSize();
     const uint8_t lengthH = ((length >> 8) & 0xff);
     const uint8_t lengthL = (length & 0xff);
-    int primary_image_size = pos + length + primary_jpg_image_ptr->length;
+    int primary_image_size = pos + length + final_primary_jpg_image_ptr->length;
     // between APP2 + package size + signature
     // ff e2 00 58 4d 50 46 00
     // 2 + 2 + 4 = 8 (bytes)
@@ -1290,8 +1349,8 @@
   }
 
   // Write primary image
-  JPEGR_CHECK(Write(dest, (uint8_t*)primary_jpg_image_ptr->data + 2,
-                    primary_jpg_image_ptr->length - 2, pos));
+  JPEGR_CHECK(Write(dest, (uint8_t*)final_primary_jpg_image_ptr->data + 2,
+                    final_primary_jpg_image_ptr->length - 2, pos));
   // Finish primary image
 
   // Begin secondary image (gain map)
diff --git a/libs/ultrahdr/tests/Android.bp b/libs/ultrahdr/tests/Android.bp
index 004a582..bda804a 100644
--- a/libs/ultrahdr/tests/Android.bp
+++ b/libs/ultrahdr/tests/Android.bp
@@ -28,6 +28,8 @@
         "gainmapmath_test.cpp",
         "icchelper_test.cpp",
         "jpegr_test.cpp",
+        "jpegencoderhelper_test.cpp",
+        "jpegdecoderhelper_test.cpp",
     ],
     shared_libs: [
         "libimage_io",
@@ -42,38 +44,7 @@
         "libultrahdr",
         "libutils",
     ],
-}
-
-cc_test {
-    name: "jpegencoderhelper_test",
-    test_suites: ["device-tests"],
-    srcs: [
-        "jpegencoderhelper_test.cpp",
-    ],
-    shared_libs: [
-        "libjpeg",
-        "liblog",
-    ],
-    static_libs: [
-        "libgtest",
-        "libjpegencoder",
-    ],
-}
-
-cc_test {
-    name: "jpegdecoderhelper_test",
-    test_suites: ["device-tests"],
-    srcs: [
-        "jpegdecoderhelper_test.cpp",
-    ],
-    shared_libs: [
-        "libjpeg",
-        "liblog",
-    ],
-    static_libs: [
-        "libgtest",
-        "libjpegdecoder",
-        "libultrahdr",
-        "libutils",
+    data: [
+        "./data/*.*",
     ],
 }
diff --git a/libs/ultrahdr/tests/AndroidTest.xml b/libs/ultrahdr/tests/AndroidTest.xml
new file mode 100644
index 0000000..1754a5c
--- /dev/null
+++ b/libs/ultrahdr/tests/AndroidTest.xml
@@ -0,0 +1,26 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2023 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.
+-->
+<configuration description="Unit test configuration for ultrahdr_unit_test">
+    <target_preparer class="com.android.tradefed.targetprep.PushFilePreparer">
+        <option name="cleanup" value="true" />
+        <option name="push-file" key="ultrahdr_unit_test" value="/data/local/tmp/ultrahdr_unit_test" />
+        <option name="push" value="data/*->/data/local/tmp/" />
+    </target_preparer>
+    <test class="com.android.tradefed.testtype.GTest" >
+        <option name="native-test-device-path" value="/data/local/tmp" />
+        <option name="module-name" value="ultrahdr_unit_test" />
+    </test>
+</configuration>
diff --git a/libs/ultrahdr/tests/jpegdecoderhelper_test.cpp b/libs/ultrahdr/tests/jpegdecoderhelper_test.cpp
index e2da01c..af0d59e 100644
--- a/libs/ultrahdr/tests/jpegdecoderhelper_test.cpp
+++ b/libs/ultrahdr/tests/jpegdecoderhelper_test.cpp
@@ -14,9 +14,9 @@
  * limitations under the License.
  */
 
-#include <ultrahdr/jpegdecoderhelper.h>
-#include <ultrahdr/icc.h>
 #include <gtest/gtest.h>
+#include <ultrahdr/icc.h>
+#include <ultrahdr/jpegdecoderhelper.h>
 #include <utils/Log.h>
 
 #include <fcntl.h>
@@ -24,13 +24,13 @@
 namespace android::ultrahdr {
 
 // No ICC or EXIF
-#define YUV_IMAGE "/sdcard/Documents/minnie-320x240-yuv.jpg"
+#define YUV_IMAGE "/data/local/tmp/minnie-320x240-yuv.jpg"
 #define YUV_IMAGE_SIZE 20193
 // Has ICC and EXIF
-#define YUV_ICC_IMAGE "/sdcard/Documents/minnie-320x240-yuv-icc.jpg"
+#define YUV_ICC_IMAGE "/data/local/tmp/minnie-320x240-yuv-icc.jpg"
 #define YUV_ICC_IMAGE_SIZE 34266
 // No ICC or EXIF
-#define GREY_IMAGE "/sdcard/Documents/minnie-320x240-y.jpg"
+#define GREY_IMAGE "/data/local/tmp/minnie-320x240-y.jpg"
 #define GREY_IMAGE_SIZE 20193
 
 #define IMAGE_WIDTH 320
@@ -44,6 +44,7 @@
     };
     JpegDecoderHelperTest();
     ~JpegDecoderHelperTest();
+
 protected:
     virtual void SetUp();
     virtual void TearDown();
@@ -127,8 +128,8 @@
     std::vector<uint8_t> icc, exif;
 
     JpegDecoderHelper decoder;
-    EXPECT_TRUE(decoder.getCompressedImageParameters(mYuvImage.buffer.get(), mYuvImage.size,
-                                                     &width, &height, &icc, &exif));
+    EXPECT_TRUE(decoder.getCompressedImageParameters(mYuvImage.buffer.get(), mYuvImage.size, &width,
+                                                     &height, &icc, &exif));
 
     EXPECT_EQ(width, IMAGE_WIDTH);
     EXPECT_EQ(height, IMAGE_HEIGHT);
@@ -149,8 +150,7 @@
     EXPECT_GT(icc.size(), 0);
     EXPECT_GT(exif.size(), 0);
 
-    EXPECT_EQ(IccHelper::readIccColorGamut(icc.data(), icc.size()),
-              ULTRAHDR_COLORGAMUT_BT709);
+    EXPECT_EQ(IccHelper::readIccColorGamut(icc.data(), icc.size()), ULTRAHDR_COLORGAMUT_BT709);
 }
 
-}  // namespace android::ultrahdr
+} // namespace android::ultrahdr
diff --git a/libs/ultrahdr/tests/jpegencoderhelper_test.cpp b/libs/ultrahdr/tests/jpegencoderhelper_test.cpp
index 33cb9f6..af54eb2 100644
--- a/libs/ultrahdr/tests/jpegencoderhelper_test.cpp
+++ b/libs/ultrahdr/tests/jpegencoderhelper_test.cpp
@@ -22,13 +22,13 @@
 
 namespace android::ultrahdr {
 
-#define ALIGNED_IMAGE "/sdcard/Documents/minnie-320x240.yu12"
+#define ALIGNED_IMAGE "/data/local/tmp/minnie-320x240.yu12"
 #define ALIGNED_IMAGE_WIDTH 320
 #define ALIGNED_IMAGE_HEIGHT 240
-#define SINGLE_CHANNEL_IMAGE "/sdcard/Documents/minnie-320x240.y"
+#define SINGLE_CHANNEL_IMAGE "/data/local/tmp/minnie-320x240.y"
 #define SINGLE_CHANNEL_IMAGE_WIDTH ALIGNED_IMAGE_WIDTH
 #define SINGLE_CHANNEL_IMAGE_HEIGHT ALIGNED_IMAGE_HEIGHT
-#define UNALIGNED_IMAGE "/sdcard/Documents/minnie-318x240.yu12"
+#define UNALIGNED_IMAGE "/data/local/tmp/minnie-318x240.yu12"
 #define UNALIGNED_IMAGE_WIDTH 318
 #define UNALIGNED_IMAGE_HEIGHT 240
 #define JPEG_QUALITY 90
diff --git a/libs/ultrahdr/tests/jpegr_test.cpp b/libs/ultrahdr/tests/jpegr_test.cpp
index a750867..5fa758e 100644
--- a/libs/ultrahdr/tests/jpegr_test.cpp
+++ b/libs/ultrahdr/tests/jpegr_test.cpp
@@ -30,9 +30,9 @@
 namespace android::ultrahdr {
 
 // resources used by unit tests
-const char* kYCbCrP010FileName = "raw_p010_image.p010";
-const char* kYCbCr420FileName = "raw_yuv420_image.yuv420";
-const char* kSdrJpgFileName = "jpeg_image.jpg";
+const char* kYCbCrP010FileName = "/data/local/tmp/raw_p010_image.p010";
+const char* kYCbCr420FileName = "/data/local/tmp/raw_yuv420_image.yuv420";
+const char* kSdrJpgFileName = "/data/local/tmp/jpeg_image.jpg";
 const int kImageWidth = 1280;
 const int kImageHeight = 720;
 const int kQuality = 90;
diff --git a/services/sensorservice/SensorEventConnection.cpp b/services/sensorservice/SensorEventConnection.cpp
index d469ff4..dc86577 100644
--- a/services/sensorservice/SensorEventConnection.cpp
+++ b/services/sensorservice/SensorEventConnection.cpp
@@ -854,7 +854,7 @@
                 }
                 if (!mService->isAllowListedPackage(mPackageName)) {
                     ALOGE("App not allowed to inject data, dropping event"
-                          "package=%s uid=%d", mPackageName.string(), mUid);
+                          "package=%s uid=%d", mPackageName.c_str(), mUid);
                     return 0;
                 }
                 sensors_event_t sensor_event;
diff --git a/services/sensorservice/SensorService.cpp b/services/sensorservice/SensorService.cpp
index 9e6f563..9c51fd9 100644
--- a/services/sensorservice/SensorService.cpp
+++ b/services/sensorservice/SensorService.cpp
@@ -584,7 +584,7 @@
         }
         if (args.size() > 0) {
             Mode targetOperatingMode = NORMAL;
-            std::string inputStringMode = String8(args[0]).string();
+            std::string inputStringMode = String8(args[0]).c_str();
             if (getTargetOperatingMode(inputStringMode, &targetOperatingMode)) {
               status_t error = changeOperatingMode(args, targetOperatingMode);
               // Dump the latest state only if no error was encountered.
@@ -623,7 +623,7 @@
             for (auto&& i : mRecentEvent) {
                 std::shared_ptr<SensorInterface> s = getSensorInterfaceFromHandle(i.first);
                 if (!i.second->isEmpty() && s != nullptr) {
-                    if (privileged || s->getSensor().getRequiredPermission().isEmpty()) {
+                    if (privileged || s->getSensor().getRequiredPermission().empty()) {
                         i.second->setFormat("normal");
                     } else {
                         i.second->setFormat("mask_data");
@@ -749,7 +749,7 @@
     for (auto&& i : mRecentEvent) {
         std::shared_ptr<SensorInterface> s = getSensorInterfaceFromHandle(i.first);
         if (!i.second->isEmpty() && s != nullptr) {
-            i.second->setFormat(privileged || s->getSensor().getRequiredPermission().isEmpty() ?
+            i.second->setFormat(privileged || s->getSensor().getRequiredPermission().empty() ?
                     "normal" : "mask_data");
             const uint64_t mToken = proto.start(service::SensorEventsProto::RECENT_EVENTS_LOGS);
             proto.write(service::SensorEventsProto::RecentEventsLog::NAME,
@@ -1495,7 +1495,7 @@
         accessibleSensorList.add(sensor);
     } else if (sensor.getType() != SENSOR_TYPE_HEAD_TRACKER) {
         ALOGI("Skipped sensor %s because it requires permission %s and app op %" PRId32,
-        sensor.getName().string(), sensor.getRequiredPermission().string(),
+        sensor.getName().c_str(), sensor.getRequiredPermission().c_str(),
         sensor.getRequiredAppOp());
     }
 }
@@ -2371,7 +2371,7 @@
         mCurrentOperatingMode = RESTRICTED;
         // temporarily stop all sensor direct report and disable sensors
         disableAllSensorsLocked(&connLock);
-        mAllowListedPackage.setTo(String8(args[1]));
+        mAllowListedPackage = String8(args[1]);
         return status_t(NO_ERROR);
       case REPLAY_DATA_INJECTION:
         if (SensorServiceUtil::isUserBuild()) {
@@ -2391,7 +2391,7 @@
                 // Re-enable sensors.
                 dev.enableAllSensors();
             }
-            mAllowListedPackage.setTo(String8(args[1]));
+            mAllowListedPackage = String8(args[1]);
             return NO_ERROR;
         } else {
             // Transition to data injection mode supported only from NORMAL mode.
diff --git a/services/surfaceflinger/Android.bp b/services/surfaceflinger/Android.bp
index 0101c17..eda52bf 100644
--- a/services/surfaceflinger/Android.bp
+++ b/services/surfaceflinger/Android.bp
@@ -187,6 +187,7 @@
         "Scheduler/MessageQueue.cpp",
         "Scheduler/RefreshRateSelector.cpp",
         "Scheduler/Scheduler.cpp",
+        "Scheduler/SmallAreaDetectionAllowMappings.cpp",
         "Scheduler/VSyncDispatchTimerQueue.cpp",
         "Scheduler/VSyncPredictor.cpp",
         "Scheduler/VSyncReactor.cpp",
diff --git a/services/surfaceflinger/FrontEnd/RequestedLayerState.cpp b/services/surfaceflinger/FrontEnd/RequestedLayerState.cpp
index 453b51e..de32951 100644
--- a/services/surfaceflinger/FrontEnd/RequestedLayerState.cpp
+++ b/services/surfaceflinger/FrontEnd/RequestedLayerState.cpp
@@ -150,6 +150,8 @@
             ? ui::Size(externalTexture->getWidth(), externalTexture->getHeight())
             : ui::Size();
     const uint64_t oldUsageFlags = hadBuffer ? externalTexture->getUsage() : 0;
+    const bool oldBufferFormatOpaque = LayerSnapshot::isOpaqueFormat(
+            externalTexture ? externalTexture->getPixelFormat() : PIXEL_FORMAT_NONE);
 
     const bool hadSideStream = sidebandStream != nullptr;
     const layer_state_t& clientState = resolvedComposerState.state;
@@ -160,7 +162,7 @@
     LLOGV(layerId, "requested=%" PRIu64 "flags=%" PRIu64, clientState.what, clientChanges);
 
     if (clientState.what & layer_state_t::eFlagsChanged) {
-        if ((oldFlags ^ flags) & layer_state_t::eLayerHidden) {
+        if ((oldFlags ^ flags) & (layer_state_t::eLayerHidden | layer_state_t::eLayerOpaque)) {
             changes |= RequestedLayerState::Changes::Visibility |
                     RequestedLayerState::Changes::VisibleRegion;
         }
@@ -214,6 +216,13 @@
             barrierProducerId = std::max(bufferData->producerId, barrierProducerId);
             barrierFrameNumber = std::max(bufferData->frameNumber, barrierFrameNumber);
         }
+
+        const bool newBufferFormatOpaque = LayerSnapshot::isOpaqueFormat(
+                externalTexture ? externalTexture->getPixelFormat() : PIXEL_FORMAT_NONE);
+        if (newBufferFormatOpaque != oldBufferFormatOpaque) {
+            changes |= RequestedLayerState::Changes::Visibility |
+                    RequestedLayerState::Changes::VisibleRegion;
+        }
     }
 
     if (clientState.what & layer_state_t::eSidebandStreamChanged) {
diff --git a/services/surfaceflinger/Layer.cpp b/services/surfaceflinger/Layer.cpp
index 50f24a7..cdf7cff 100644
--- a/services/surfaceflinger/Layer.cpp
+++ b/services/surfaceflinger/Layer.cpp
@@ -3212,6 +3212,14 @@
     }
 
     mDrawingState.releaseBufferEndpoint = bufferData.releaseBufferEndpoint;
+
+    // If the layer had been updated a TextureView, this would make sure the present time could be
+    // same to TextureView update when it's a small dirty, and get the correct heuristic rate.
+    if (mFlinger->mScheduler->supportSmallDirtyDetection()) {
+        if (mDrawingState.useVsyncIdForRefreshRateSelection) {
+            mUsedVsyncIdForRefreshRateSelection = true;
+        }
+    }
     return true;
 }
 
@@ -3234,10 +3242,38 @@
                             mDrawingState.latchedVsyncId);
             if (prediction.has_value()) {
                 ATRACE_FORMAT_INSTANT("predictedPresentTime");
+                mMaxTimeForUseVsyncId = prediction->presentTime +
+                        scheduler::LayerHistory::kMaxPeriodForHistory.count();
                 return prediction->presentTime;
             }
         }
 
+        if (!mFlinger->mScheduler->supportSmallDirtyDetection()) {
+            return static_cast<nsecs_t>(0);
+        }
+
+        // If the layer is not an application and didn't set an explicit rate or desiredPresentTime,
+        // return "0" to tell the layer history that it will use the max refresh rate without
+        // calculating the adaptive rate.
+        if (mWindowType != WindowInfo::Type::APPLICATION &&
+            mWindowType != WindowInfo::Type::BASE_APPLICATION) {
+            return static_cast<nsecs_t>(0);
+        }
+
+        // Return the valid present time only when the layer potentially updated a TextureView so
+        // LayerHistory could heuristically calculate the rate if the UI is continually updating.
+        if (mUsedVsyncIdForRefreshRateSelection) {
+            const auto prediction =
+                    mFlinger->mFrameTimeline->getTokenManager()->getPredictionsForToken(
+                            mDrawingState.latchedVsyncId);
+            if (prediction.has_value()) {
+                if (mMaxTimeForUseVsyncId >= prediction->presentTime) {
+                    return prediction->presentTime;
+                }
+                mUsedVsyncIdForRefreshRateSelection = false;
+            }
+        }
+
         return static_cast<nsecs_t>(0);
     }();
 
@@ -3297,6 +3333,7 @@
     mDrawingState.surfaceDamageRegion = surfaceDamage;
     mDrawingState.modified = true;
     setTransactionFlags(eTransactionNeeded);
+    setIsSmallDirty();
     return true;
 }
 
@@ -3997,7 +4034,7 @@
         const std::optional<Fps> renderRate =
                 mFlinger->mScheduler->getFrameRateOverride(getOwnerUid());
 
-        const auto vote = frameRateToSetFrameRateVotePayload(mDrawingState.frameRate);
+        const auto vote = frameRateToSetFrameRateVotePayload(getFrameRateForLayerTree());
         const auto gameMode = getGameMode();
 
         if (presentFence->isValid()) {
@@ -4335,6 +4372,26 @@
     mLastLatchTime = latchTime;
 }
 
+void Layer::setIsSmallDirty() {
+    if (!mFlinger->mScheduler->supportSmallDirtyDetection()) {
+        return;
+    }
+
+    if (mWindowType != WindowInfo::Type::APPLICATION &&
+        mWindowType != WindowInfo::Type::BASE_APPLICATION) {
+        return;
+    }
+    Rect bounds = mDrawingState.surfaceDamageRegion.getBounds();
+    if (!bounds.isValid()) {
+        return;
+    }
+
+    // If the damage region is a small dirty, this could give the hint for the layer history that
+    // it could suppress the heuristic rate when calculating.
+    mSmallDirty = mFlinger->mScheduler->isSmallDirtyArea(mOwnerUid,
+                                                         bounds.getWidth() * bounds.getHeight());
+}
+
 } // namespace android
 
 #if defined(__gl_h_)
diff --git a/services/surfaceflinger/Layer.h b/services/surfaceflinger/Layer.h
index 1f2485f..7b6c56b 100644
--- a/services/surfaceflinger/Layer.h
+++ b/services/surfaceflinger/Layer.h
@@ -841,6 +841,14 @@
     mutable bool contentDirty{false};
     Region surfaceDamageRegion;
 
+    // True when the surfaceDamageRegion is recognized as a small area update.
+    bool mSmallDirty{false};
+    // Used to check if mUsedVsyncIdForRefreshRateSelection should be expired when it stop updating.
+    nsecs_t mMaxTimeForUseVsyncId = 0;
+    // True when DrawState.useVsyncIdForRefreshRateSelection previously set to true during updating
+    // buffer.
+    bool mUsedVsyncIdForRefreshRateSelection{false};
+
     // Layer serial number.  This gives layers an explicit ordering, so we
     // have a stable sort order when their layer stack and Z-order are
     // the same.
@@ -903,6 +911,7 @@
                 .transform = getTransform(),
                 .setFrameRateVote = getFrameRateForLayerTree(),
                 .frameRateSelectionPriority = getFrameRateSelectionPriority(),
+                .isSmallDirty = mSmallDirty,
         };
     };
     bool hasBuffer() const { return mBufferInfo.mBuffer != nullptr; }
@@ -917,6 +926,9 @@
     // Exposed so SurfaceFlinger can assert that it's held
     const sp<SurfaceFlinger> mFlinger;
 
+    // Check if the damage region is a small dirty.
+    void setIsSmallDirty();
+
 protected:
     // For unit tests
     friend class TestableSurfaceFlinger;
diff --git a/services/surfaceflinger/Scheduler/LayerHistory.cpp b/services/surfaceflinger/Scheduler/LayerHistory.cpp
index 565a490..c92e670 100644
--- a/services/surfaceflinger/Scheduler/LayerHistory.cpp
+++ b/services/surfaceflinger/Scheduler/LayerHistory.cpp
@@ -306,4 +306,11 @@
     return {LayerStatus::NotFound, nullptr};
 }
 
+bool LayerHistory::isSmallDirtyArea(uint32_t dirtyArea, float threshold) const {
+    const float ratio = (float)dirtyArea / mDisplayArea;
+    const bool isSmallDirty = ratio <= threshold;
+    ATRACE_FORMAT_INSTANT("small dirty=%s, ratio=%.3f", isSmallDirty ? "true" : "false", ratio);
+    return isSmallDirty;
+}
+
 } // namespace android::scheduler
diff --git a/services/surfaceflinger/Scheduler/LayerHistory.h b/services/surfaceflinger/Scheduler/LayerHistory.h
index d083fa2..5750ea7 100644
--- a/services/surfaceflinger/Scheduler/LayerHistory.h
+++ b/services/surfaceflinger/Scheduler/LayerHistory.h
@@ -43,6 +43,7 @@
 class LayerHistory {
 public:
     using LayerVoteType = RefreshRateSelector::LayerVoteType;
+    static constexpr std::chrono::nanoseconds kMaxPeriodForHistory = 1s;
 
     LayerHistory();
     ~LayerHistory();
@@ -84,6 +85,8 @@
     // return the frames per second of the layer with the given sequence id.
     float getLayerFramerate(nsecs_t now, int32_t id) const;
 
+    bool isSmallDirtyArea(uint32_t dirtyArea, float threshold) const;
+
 private:
     friend class LayerHistoryTest;
     friend class TestableScheduler;
diff --git a/services/surfaceflinger/Scheduler/LayerInfo.cpp b/services/surfaceflinger/Scheduler/LayerInfo.cpp
index 750803b..e4df494 100644
--- a/services/surfaceflinger/Scheduler/LayerInfo.cpp
+++ b/services/surfaceflinger/Scheduler/LayerInfo.cpp
@@ -65,7 +65,8 @@
         case LayerUpdateType::Buffer:
             FrameTimeData frameTime = {.presentTime = lastPresentTime,
                                        .queueTime = mLastUpdatedTime,
-                                       .pendingModeChange = pendingModeChange};
+                                       .pendingModeChange = pendingModeChange,
+                                       .isSmallDirty = props.isSmallDirty};
             mFrameTimes.push_back(frameTime);
             if (mFrameTimes.size() > HISTORY_SIZE) {
                 mFrameTimes.pop_front();
@@ -101,11 +102,15 @@
     // classification.
     bool isFrequent = true;
     bool isInfrequent = true;
+    int32_t smallDirtyCount = 0;
     const auto n = mFrameTimes.size() - 1;
     for (size_t i = 0; i < kFrequentLayerWindowSize - 1; i++) {
         if (mFrameTimes[n - i].queueTime - mFrameTimes[n - i - 1].queueTime <
             kMaxPeriodForFrequentLayerNs.count()) {
             isInfrequent = false;
+            if (mFrameTimes[n - i].presentTime == 0 && mFrameTimes[n - i].isSmallDirty) {
+                smallDirtyCount++;
+            }
         } else {
             isFrequent = false;
         }
@@ -115,7 +120,8 @@
         // If the layer was previously inconclusive, we clear
         // the history as indeterminate layers changed to frequent,
         // and we should not look at the stale data.
-        return {isFrequent, isFrequent && !mIsFrequencyConclusive, /* isConclusive */ true};
+        return {isFrequent, isFrequent && !mIsFrequencyConclusive, /* isConclusive */ true,
+                /* isSmallDirty */ smallDirtyCount >= kNumSmallDirtyThreshold};
     }
 
     // If we can't determine whether the layer is frequent or not, we return
@@ -204,6 +210,7 @@
 
     nsecs_t totalDeltas = 0;
     int numDeltas = 0;
+    int32_t smallDirtyCount = 0;
     auto prevFrame = mFrameTimes.begin();
     for (auto it = mFrameTimes.begin() + 1; it != mFrameTimes.end(); ++it) {
         const auto currDelta = getFrameTime(*it) - getFrameTime(*prevFrame);
@@ -212,6 +219,13 @@
             continue;
         }
 
+        // If this is a small area update, we don't want to consider it for calculating the average
+        // frame time. Instead, we let the bigger frame updates to drive the calculation.
+        if (it->isSmallDirty && currDelta < kMinPeriodBetweenSmallDirtyFrames) {
+            smallDirtyCount++;
+            continue;
+        }
+
         prevFrame = it;
 
         if (currDelta > kMaxPeriodBetweenFrames) {
@@ -223,6 +237,10 @@
         numDeltas++;
     }
 
+    if (smallDirtyCount > 0) {
+        ATRACE_FORMAT_INSTANT("small dirty = %" PRIu32, smallDirtyCount);
+    }
+
     if (numDeltas == 0) {
         return std::nullopt;
     }
@@ -313,6 +331,14 @@
         clearHistory(now);
     }
 
+    // Return no vote if the latest frames are small dirty.
+    if (frequent.isSmallDirty && !mLastRefreshRate.reported.isValid()) {
+        ATRACE_FORMAT_INSTANT("NoVote (small dirty)");
+        ALOGV("%s is small dirty", mName.c_str());
+        votes.push_back({LayerHistory::LayerVoteType::NoVote, Fps()});
+        return votes;
+    }
+
     auto refreshRate = calculateRefreshRateIfPossible(selector, now);
     if (refreshRate.has_value()) {
         ALOGV("%s calculated refresh rate: %s", mName.c_str(), to_string(*refreshRate).c_str());
diff --git a/services/surfaceflinger/Scheduler/LayerInfo.h b/services/surfaceflinger/Scheduler/LayerInfo.h
index 7d2444c..1e08ec8 100644
--- a/services/surfaceflinger/Scheduler/LayerInfo.h
+++ b/services/surfaceflinger/Scheduler/LayerInfo.h
@@ -58,6 +58,7 @@
     static constexpr Fps kMinFpsForFrequentLayer = 10_Hz;
     static constexpr auto kMaxPeriodForFrequentLayerNs =
             std::chrono::nanoseconds(kMinFpsForFrequentLayer.getPeriodNsecs()) + 1ms;
+    static constexpr size_t kNumSmallDirtyThreshold = 2;
 
     friend class LayerHistoryTest;
     friend class LayerInfoTest;
@@ -235,6 +236,7 @@
         nsecs_t presentTime; // desiredPresentTime, if provided
         nsecs_t queueTime;  // buffer queue time
         bool pendingModeChange;
+        bool isSmallDirty;
     };
 
     // Holds information about the calculated and reported refresh rate
@@ -299,6 +301,8 @@
         bool clearHistory;
         // Represents whether we were able to determine isFrequent conclusively
         bool isConclusive;
+        // Represents whether the latest frames are small dirty.
+        bool isSmallDirty = false;
     };
     Frequent isFrequent(nsecs_t now) const;
     bool isAnimating(nsecs_t now) const;
@@ -317,6 +321,11 @@
     // this period apart from each other, the interval between them won't be
     // taken into account when calculating average frame rate.
     static constexpr nsecs_t kMaxPeriodBetweenFrames = kMinFpsForFrequentLayer.getPeriodNsecs();
+    // Used for sanitizing the heuristic data. If frames are small dirty updating and are less
+    // than this period apart from each other, the interval between them won't be
+    // taken into account when calculating average frame rate.
+    static constexpr nsecs_t kMinPeriodBetweenSmallDirtyFrames = (60_Hz).getPeriodNsecs();
+
     LayerHistory::LayerVoteType mDefaultVote;
 
     LayerVote mLayerVote;
@@ -331,7 +340,7 @@
     std::chrono::time_point<std::chrono::steady_clock> mFrameTimeValidSince =
             std::chrono::steady_clock::now();
     static constexpr size_t HISTORY_SIZE = RefreshRateHistory::HISTORY_SIZE;
-    static constexpr std::chrono::nanoseconds HISTORY_DURATION = 1s;
+    static constexpr std::chrono::nanoseconds HISTORY_DURATION = LayerHistory::kMaxPeriodForHistory;
 
     std::unique_ptr<LayerProps> mLayerProps;
 
@@ -349,6 +358,7 @@
     ui::Transform transform;
     LayerInfo::FrameRate setFrameRateVote;
     int32_t frameRateSelectionPriority = -1;
+    bool isSmallDirty = false;
 };
 
 } // namespace scheduler
diff --git a/services/surfaceflinger/Scheduler/Scheduler.cpp b/services/surfaceflinger/Scheduler/Scheduler.cpp
index 5a19ec5..27c96f7 100644
--- a/services/surfaceflinger/Scheduler/Scheduler.cpp
+++ b/services/surfaceflinger/Scheduler/Scheduler.cpp
@@ -1179,4 +1179,20 @@
     mFrameRateOverrideMappings.setPreferredRefreshRateForUid(frameRateOverride);
 }
 
+void Scheduler::updateSmallAreaDetection(
+        std::vector<std::pair<uid_t, float>>& uidThresholdMappings) {
+    mSmallAreaDetectionAllowMappings.update(uidThresholdMappings);
+}
+
+void Scheduler::setSmallAreaDetectionThreshold(uid_t uid, float threshold) {
+    mSmallAreaDetectionAllowMappings.setThesholdForUid(uid, threshold);
+}
+
+bool Scheduler::isSmallDirtyArea(uid_t uid, uint32_t dirtyArea) {
+    std::optional<float> oThreshold = mSmallAreaDetectionAllowMappings.getThresholdForUid(uid);
+    if (oThreshold) return mLayerHistory.isSmallDirtyArea(dirtyArea, oThreshold.value());
+
+    return false;
+}
+
 } // namespace android::scheduler
diff --git a/services/surfaceflinger/Scheduler/Scheduler.h b/services/surfaceflinger/Scheduler/Scheduler.h
index 85d0f9a..d65df2a 100644
--- a/services/surfaceflinger/Scheduler/Scheduler.h
+++ b/services/surfaceflinger/Scheduler/Scheduler.h
@@ -49,6 +49,7 @@
 #include "MessageQueue.h"
 #include "OneShotTimer.h"
 #include "RefreshRateSelector.h"
+#include "SmallAreaDetectionAllowMappings.h"
 #include "Utils/Dumper.h"
 #include "VsyncModulator.h"
 
@@ -291,6 +292,13 @@
 
     void setGameModeRefreshRateForUid(FrameRateOverride);
 
+    void updateSmallAreaDetection(std::vector<std::pair<uid_t, float>>& uidThresholdMappings);
+
+    void setSmallAreaDetectionThreshold(uid_t uid, float threshold);
+
+    // Returns true if the dirty area is less than threshold.
+    bool isSmallDirtyArea(uid_t uid, uint32_t dirtyArea);
+
     // Retrieves the overridden refresh rate for a given uid.
     std::optional<Fps> getFrameRateOverride(uid_t) const EXCLUDES(mDisplayLock);
 
@@ -309,6 +317,11 @@
 
     bool updateFrameRateOverrides(GlobalSignals, Fps displayRefreshRate) EXCLUDES(mPolicyLock);
 
+    // Returns true if the small dirty detection is enabled.
+    bool supportSmallDirtyDetection() const {
+        return mFeatures.test(Feature::kSmallDirtyContentDetection);
+    }
+
 private:
     friend class TestableScheduler;
 
@@ -547,6 +560,7 @@
     static constexpr std::chrono::nanoseconds MAX_VSYNC_APPLIED_TIME = 200ms;
 
     FrameRateOverrideMappings mFrameRateOverrideMappings;
+    SmallAreaDetectionAllowMappings mSmallAreaDetectionAllowMappings;
 };
 
 } // namespace scheduler
diff --git a/services/surfaceflinger/Scheduler/SmallAreaDetectionAllowMappings.cpp b/services/surfaceflinger/Scheduler/SmallAreaDetectionAllowMappings.cpp
new file mode 100644
index 0000000..95cd5d1
--- /dev/null
+++ b/services/surfaceflinger/Scheduler/SmallAreaDetectionAllowMappings.cpp
@@ -0,0 +1,47 @@
+/*
+ * Copyright 2023 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 <sys/types.h>
+
+#include "SmallAreaDetectionAllowMappings.h"
+
+namespace android::scheduler {
+void SmallAreaDetectionAllowMappings::update(
+        std::vector<std::pair<uid_t, float>>& uidThresholdMappings) {
+    std::lock_guard lock(mLock);
+    mMap.clear();
+    for (std::pair<uid_t, float> row : uidThresholdMappings) {
+        if (!isValidThreshold(row.second)) continue;
+
+        mMap.emplace(row.first, row.second);
+    }
+}
+
+void SmallAreaDetectionAllowMappings::setThesholdForUid(uid_t uid, float threshold) {
+    if (!isValidThreshold(threshold)) return;
+
+    std::lock_guard lock(mLock);
+    mMap.emplace(uid, threshold);
+}
+
+std::optional<float> SmallAreaDetectionAllowMappings::getThresholdForUid(uid_t uid) {
+    std::lock_guard lock(mLock);
+    const auto iter = mMap.find(uid);
+    if (iter != mMap.end()) {
+        return iter->second;
+    }
+    return std::nullopt;
+}
+} // namespace android::scheduler
diff --git a/services/surfaceflinger/Scheduler/SmallAreaDetectionAllowMappings.h b/services/surfaceflinger/Scheduler/SmallAreaDetectionAllowMappings.h
new file mode 100644
index 0000000..cbab690
--- /dev/null
+++ b/services/surfaceflinger/Scheduler/SmallAreaDetectionAllowMappings.h
@@ -0,0 +1,39 @@
+/*
+ * Copyright 2023 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/thread_annotations.h>
+#include <sys/types.h>
+#include <optional>
+#include <unordered_map>
+#include <vector>
+
+namespace android::scheduler {
+class SmallAreaDetectionAllowMappings {
+    using UidThresholdMap = std::unordered_map<uid_t, float>;
+
+public:
+    void update(std::vector<std::pair<uid_t, float>>& uidThresholdMappings);
+    void setThesholdForUid(uid_t uid, float threshold) EXCLUDES(mLock);
+    std::optional<float> getThresholdForUid(uid_t uid) EXCLUDES(mLock);
+
+private:
+    static bool isValidThreshold(float threshold) { return threshold >= 0.0f && threshold <= 1.0f; }
+    mutable std::mutex mLock;
+    UidThresholdMap mMap GUARDED_BY(mLock);
+};
+} // namespace android::scheduler
diff --git a/services/surfaceflinger/Scheduler/include/scheduler/Features.h b/services/surfaceflinger/Scheduler/include/scheduler/Features.h
index 200407d..7c72ac6 100644
--- a/services/surfaceflinger/Scheduler/include/scheduler/Features.h
+++ b/services/surfaceflinger/Scheduler/include/scheduler/Features.h
@@ -28,6 +28,7 @@
     kContentDetection = 1 << 2,
     kTracePredictedVsync = 1 << 3,
     kBackpressureGpuComposition = 1 << 4,
+    kSmallDirtyContentDetection = 1 << 5,
 };
 
 using FeatureFlags = ftl::Flags<Feature>;
diff --git a/services/surfaceflinger/SurfaceFlinger.cpp b/services/surfaceflinger/SurfaceFlinger.cpp
index e2df772..bc626f3 100644
--- a/services/surfaceflinger/SurfaceFlinger.cpp
+++ b/services/surfaceflinger/SurfaceFlinger.cpp
@@ -488,7 +488,7 @@
     mIgnoreHdrCameraLayers = ignore_hdr_camera_layers(false);
 
     mLayerLifecycleManagerEnabled =
-            base::GetBoolProperty("persist.debug.sf.enable_layer_lifecycle_manager"s, false);
+            base::GetBoolProperty("persist.debug.sf.enable_layer_lifecycle_manager"s, true);
     mLegacyFrontEndEnabled = !mLayerLifecycleManagerEnabled ||
             base::GetBoolProperty("persist.debug.sf.enable_legacy_frontend"s, false);
 }
@@ -3989,6 +3989,9 @@
 
     if (sysprop::use_content_detection_for_refresh_rate(false)) {
         features |= Feature::kContentDetection;
+        if (base::GetBoolProperty("debug.sf.enable_small_dirty_detection"s, false)) {
+            features |= Feature::kSmallDirtyContentDetection;
+        }
     }
     if (base::GetBoolProperty("debug.sf.show_predicted_vsync"s, false)) {
         features |= Feature::kTracePredictedVsync;
@@ -8197,6 +8200,17 @@
     return NO_ERROR;
 }
 
+status_t SurfaceFlinger::updateSmallAreaDetection(
+        std::vector<std::pair<uid_t, float>>& uidThresholdMappings) {
+    mScheduler->updateSmallAreaDetection(uidThresholdMappings);
+    return NO_ERROR;
+}
+
+status_t SurfaceFlinger::setSmallAreaDetectionThreshold(uid_t uid, float threshold) {
+    mScheduler->setSmallAreaDetectionThreshold(uid, threshold);
+    return NO_ERROR;
+}
+
 void SurfaceFlinger::enableRefreshRateOverlay(bool enable) {
     bool setByHwc = getHwComposer().hasCapability(Capability::REFRESH_RATE_CHANGED_CALLBACK_DEBUG);
     for (const auto& [id, display] : mPhysicalDisplays) {
@@ -8334,6 +8348,15 @@
 void SurfaceFlinger::onActiveDisplaySizeChanged(const DisplayDevice& activeDisplay) {
     mScheduler->onActiveDisplayAreaChanged(activeDisplay.getWidth() * activeDisplay.getHeight());
     getRenderEngine().onActiveDisplaySizeChanged(activeDisplay.getSize());
+
+    // Notify layers to update small dirty flag.
+    if (mScheduler->supportSmallDirtyDetection()) {
+        mCurrentState.traverse([&](Layer* layer) {
+            if (layer->getLayerStack() == activeDisplay.getLayerStack()) {
+                layer->setIsSmallDirty();
+            }
+        });
+    }
 }
 
 sp<DisplayDevice> SurfaceFlinger::getActivatableDisplay() const {
@@ -8471,7 +8494,13 @@
         // Set mirror layer's default layer stack to -1 so it doesn't end up rendered on a display
         // accidentally.
         sp<Layer> rootMirrorLayer = LayerHandle::getLayer(mirrorDisplay.rootHandle);
-        rootMirrorLayer->setLayerStack(ui::LayerStack::fromValue(-1));
+        ssize_t idx = mCurrentState.layersSortedByZ.indexOf(rootMirrorLayer);
+        bool ret = rootMirrorLayer->setLayerStack(ui::LayerStack::fromValue(-1));
+        if (idx >= 0 && ret) {
+            mCurrentState.layersSortedByZ.removeAt(idx);
+            mCurrentState.layersSortedByZ.add(rootMirrorLayer);
+        }
+
         for (const auto& layer : mDrawingState.layersSortedByZ) {
             if (layer->getLayerStack() != mirrorDisplay.layerStack ||
                 layer->isInternalDisplayOverlay()) {
@@ -9514,6 +9543,40 @@
     return binder::Status::ok();
 }
 
+binder::Status SurfaceComposerAIDL::updateSmallAreaDetection(const std::vector<int32_t>& uids,
+                                                             const std::vector<float>& thresholds) {
+    status_t status;
+    const int c_uid = IPCThreadState::self()->getCallingUid();
+    if (c_uid == AID_ROOT || c_uid == AID_SYSTEM) {
+        if (uids.size() != thresholds.size()) return binderStatusFromStatusT(BAD_VALUE);
+
+        std::vector<std::pair<uid_t, float>> mappings;
+        const size_t size = uids.size();
+        mappings.reserve(size);
+        for (int i = 0; i < size; i++) {
+            auto row = std::make_pair(static_cast<uid_t>(uids[i]), thresholds[i]);
+            mappings.push_back(row);
+        }
+        status = mFlinger->updateSmallAreaDetection(mappings);
+    } else {
+        ALOGE("updateSmallAreaDetection() permission denied for uid: %d", c_uid);
+        status = PERMISSION_DENIED;
+    }
+    return binderStatusFromStatusT(status);
+}
+
+binder::Status SurfaceComposerAIDL::setSmallAreaDetectionThreshold(int32_t uid, float threshold) {
+    status_t status;
+    const int c_uid = IPCThreadState::self()->getCallingUid();
+    if (c_uid == AID_ROOT || c_uid == AID_SYSTEM) {
+        status = mFlinger->setSmallAreaDetectionThreshold(uid, threshold);
+    } else {
+        ALOGE("setSmallAreaDetectionThreshold() permission denied for uid: %d", c_uid);
+        status = PERMISSION_DENIED;
+    }
+    return binderStatusFromStatusT(status);
+}
+
 binder::Status SurfaceComposerAIDL::getGpuContextPriority(int32_t* outPriority) {
     *outPriority = mFlinger->getGpuContextPriority();
     return binder::Status::ok();
diff --git a/services/surfaceflinger/SurfaceFlinger.h b/services/surfaceflinger/SurfaceFlinger.h
index 6ff9fd1..79dcd0d 100644
--- a/services/surfaceflinger/SurfaceFlinger.h
+++ b/services/surfaceflinger/SurfaceFlinger.h
@@ -607,6 +607,10 @@
 
     status_t setOverrideFrameRate(uid_t uid, float frameRate);
 
+    status_t updateSmallAreaDetection(std::vector<std::pair<uid_t, float>>& uidThresholdMappings);
+
+    status_t setSmallAreaDetectionThreshold(uid_t uid, float threshold);
+
     int getGpuContextPriority();
 
     status_t getMaxAcquiredBufferCount(int* buffers) const;
@@ -1557,6 +1561,9 @@
     binder::Status setDebugFlash(int delay) override;
     binder::Status scheduleComposite() override;
     binder::Status scheduleCommit() override;
+    binder::Status updateSmallAreaDetection(const std::vector<int32_t>& uids,
+                                            const std::vector<float>& thresholds) override;
+    binder::Status setSmallAreaDetectionThreshold(int32_t uid, float threshold) override;
     binder::Status getGpuContextPriority(int32_t* outPriority) override;
     binder::Status getMaxAcquiredBufferCount(int32_t* buffers) override;
     binder::Status addWindowInfosListener(const sp<gui::IWindowInfosListener>& windowInfosListener,
diff --git a/services/surfaceflinger/tests/unittests/Android.bp b/services/surfaceflinger/tests/unittests/Android.bp
index 7d8796f..8deff85 100644
--- a/services/surfaceflinger/tests/unittests/Android.bp
+++ b/services/surfaceflinger/tests/unittests/Android.bp
@@ -101,6 +101,7 @@
         "LayerTestUtils.cpp",
         "MessageQueueTest.cpp",
         "PowerAdvisorTest.cpp",
+        "SmallAreaDetectionAllowMappingsTest.cpp",
         "SurfaceFlinger_CreateDisplayTest.cpp",
         "SurfaceFlinger_DestroyDisplayTest.cpp",
         "SurfaceFlinger_DisplayModeSwitching.cpp",
diff --git a/services/surfaceflinger/tests/unittests/LayerHistoryTest.cpp b/services/surfaceflinger/tests/unittests/LayerHistoryTest.cpp
index 51b5b05..b67494f 100644
--- a/services/surfaceflinger/tests/unittests/LayerHistoryTest.cpp
+++ b/services/surfaceflinger/tests/unittests/LayerHistoryTest.cpp
@@ -1070,6 +1070,77 @@
     recordFramesAndExpect(layer, time, 27.1_Hz, 30_Hz, PRESENT_TIME_HISTORY_SIZE);
 }
 
+TEST_F(LayerHistoryTest, smallDirtyLayer) {
+    auto layer = createLayer();
+
+    EXPECT_CALL(*layer, isVisible()).WillRepeatedly(Return(true));
+    EXPECT_CALL(*layer, getFrameRateForLayerTree()).WillRepeatedly(Return(Layer::FrameRate()));
+
+    nsecs_t time = systemTime();
+
+    EXPECT_EQ(1, layerCount());
+    EXPECT_EQ(0, activeLayerCount());
+    EXPECT_EQ(0, frequentLayerCount(time));
+
+    LayerHistory::Summary summary;
+
+    // layer is active but infrequent.
+    for (int i = 0; i < PRESENT_TIME_HISTORY_SIZE; i++) {
+        auto props = layer->getLayerProps();
+        if (i % 3 == 0) {
+            props.isSmallDirty = false;
+        } else {
+            props.isSmallDirty = true;
+        }
+
+        history().record(layer->getSequence(), props, time, time,
+                         LayerHistory::LayerUpdateType::Buffer);
+        time += HI_FPS_PERIOD;
+        summary = summarizeLayerHistory(time);
+    }
+
+    ASSERT_EQ(1, summary.size());
+    ASSERT_EQ(LayerHistory::LayerVoteType::Heuristic, summary[0].vote);
+    EXPECT_GE(HI_FPS, summary[0].desiredRefreshRate);
+}
+
+TEST_F(LayerHistoryTest, smallDirtyInMultiLayer) {
+    auto layer1 = createLayer("UI");
+    auto layer2 = createLayer("Video");
+
+    EXPECT_CALL(*layer1, isVisible()).WillRepeatedly(Return(true));
+    EXPECT_CALL(*layer1, getFrameRateForLayerTree()).WillRepeatedly(Return(Layer::FrameRate()));
+
+    EXPECT_CALL(*layer2, isVisible()).WillRepeatedly(Return(true));
+    EXPECT_CALL(*layer2, getFrameRateForLayerTree())
+            .WillRepeatedly(
+                    Return(Layer::FrameRate(30_Hz, Layer::FrameRateCompatibility::Default)));
+
+    nsecs_t time = systemTime();
+
+    EXPECT_EQ(2, layerCount());
+    EXPECT_EQ(0, activeLayerCount());
+    EXPECT_EQ(0, frequentLayerCount(time));
+
+    LayerHistory::Summary summary;
+
+    // layer1 is active but infrequent.
+    for (int i = 0; i < PRESENT_TIME_HISTORY_SIZE; i++) {
+        auto props = layer1->getLayerProps();
+        props.isSmallDirty = true;
+        history().record(layer1->getSequence(), props, 0 /*presentTime*/, time,
+                         LayerHistory::LayerUpdateType::Buffer);
+        history().record(layer2->getSequence(), layer2->getLayerProps(), time, time,
+                         LayerHistory::LayerUpdateType::Buffer);
+        time += HI_FPS_PERIOD;
+        summary = summarizeLayerHistory(time);
+    }
+
+    ASSERT_EQ(1, summary.size());
+    ASSERT_EQ(LayerHistory::LayerVoteType::ExplicitDefault, summary[0].vote);
+    ASSERT_EQ(30_Hz, summary[0].desiredRefreshRate);
+}
+
 class LayerHistoryTestParameterized : public LayerHistoryTest,
                                       public testing::WithParamInterface<std::chrono::nanoseconds> {
 };
diff --git a/services/surfaceflinger/tests/unittests/SmallAreaDetectionAllowMappingsTest.cpp b/services/surfaceflinger/tests/unittests/SmallAreaDetectionAllowMappingsTest.cpp
new file mode 100644
index 0000000..b910485
--- /dev/null
+++ b/services/surfaceflinger/tests/unittests/SmallAreaDetectionAllowMappingsTest.cpp
@@ -0,0 +1,61 @@
+/*
+ * Copyright 2023 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 "SmallAreaDetectionAllowMappingsTest"
+
+#include <gtest/gtest.h>
+
+#include "Scheduler/SmallAreaDetectionAllowMappings.h"
+
+namespace android::scheduler {
+
+class SmallAreaDetectionMappingsAllowTest : public testing::Test {
+protected:
+    SmallAreaDetectionAllowMappings mMappings;
+};
+
+namespace {
+TEST_F(SmallAreaDetectionMappingsAllowTest, testUpdate) {
+    const uid_t uid1 = 10100;
+    const uid_t uid2 = 10101;
+    const float threshold1 = 0.05f;
+    const float threshold2 = 0.07f;
+    std::vector<std::pair<uid_t, float>> mappings;
+    mappings.reserve(2);
+    mappings.push_back(std::make_pair(uid1, threshold1));
+    mappings.push_back(std::make_pair(uid2, threshold2));
+
+    mMappings.update(mappings);
+    ASSERT_EQ(mMappings.getThresholdForUid(uid1).value(), threshold1);
+    ASSERT_EQ(mMappings.getThresholdForUid(uid2).value(), threshold2);
+}
+
+TEST_F(SmallAreaDetectionMappingsAllowTest, testSetThesholdForUid) {
+    const uid_t uid = 10111;
+    const float threshold = 0.05f;
+
+    mMappings.setThesholdForUid(uid, threshold);
+    ASSERT_EQ(mMappings.getThresholdForUid(uid), threshold);
+}
+
+TEST_F(SmallAreaDetectionMappingsAllowTest, testUidNotInTheMappings) {
+    const uid_t uid = 10222;
+    ASSERT_EQ(mMappings.getThresholdForUid(uid), std::nullopt);
+}
+
+} // namespace
+} // namespace android::scheduler