Integrate HWUI with PerformanceHintManager

PerformanceHintManager.Session is in java, so add JNI and a HintSessionWrapper
class in HardwareRenderer. Then pass the two calls as two std::functions
into DrawFrameTask.

Note Session is created per HardwareRenderer, not global (per
RenderThread).

Session includes UI thread, render thread, and the thread pool.
Desired duration is from the intended start duration to the frame
deadline. Add an actual frame start time to compute

Add system properties:
debug.hwui.use_hint_manager to enable PerformanceHintManager
debug.hwui.target_cpu_time_percent to control percentage of frame time
  to be used for target cpu duration.

Test: Manual test that there are no crashes and values make sense.
Bug: 158791282
Change-Id: I83f25433c10daa20033803fb7c4ae45eab34f1d3
diff --git a/core/java/android/view/Choreographer.java b/core/java/android/view/Choreographer.java
index 5c65c65..7b3a8a6 100644
--- a/core/java/android/view/Choreographer.java
+++ b/core/java/android/view/Choreographer.java
@@ -737,7 +737,7 @@
             }
 
             mFrameInfo.setVsync(intendedFrameTimeNanos, frameTimeNanos, vsyncEventData.id,
-                    vsyncEventData.frameDeadline);
+                    vsyncEventData.frameDeadline, startNanos);
             mFrameScheduled = false;
             mLastFrameTimeNanos = frameTimeNanos;
             mLastVsyncEventData = vsyncEventData;
diff --git a/core/java/android/view/FrameMetrics.java b/core/java/android/view/FrameMetrics.java
index eb49e52..9cdf91a 100644
--- a/core/java/android/view/FrameMetrics.java
+++ b/core/java/android/view/FrameMetrics.java
@@ -242,18 +242,19 @@
         int PERFORM_TRAVERSALS_START = 7;
         int DRAW_START = 8;
         int FRAME_DEADLINE = 9;
-        int SYNC_QUEUED = 10;
-        int SYNC_START = 11;
-        int ISSUE_DRAW_COMMANDS_START = 12;
-        int SWAP_BUFFERS = 13;
-        int FRAME_COMPLETED = 14;
-        int DEQUEUE_BUFFER_DURATION = 15;
-        int QUEUE_BUFFER_DURATION = 16;
-        int GPU_COMPLETED = 17;
-        int SWAP_BUFFERS_COMPLETED = 18;
-        int DISPLAY_PRESENT_TIME = 19;
+        int FRAME_START_TIME = 10;
+        int SYNC_QUEUED = 11;
+        int SYNC_START = 12;
+        int ISSUE_DRAW_COMMANDS_START = 13;
+        int SWAP_BUFFERS = 14;
+        int FRAME_COMPLETED = 15;
+        int DEQUEUE_BUFFER_DURATION = 16;
+        int QUEUE_BUFFER_DURATION = 17;
+        int GPU_COMPLETED = 18;
+        int SWAP_BUFFERS_COMPLETED = 19;
+        int DISPLAY_PRESENT_TIME = 20;
 
-        int FRAME_STATS_COUNT = 20; // must always be last and in sync with
+        int FRAME_STATS_COUNT = 21; // must always be last and in sync with
                                     // FrameInfoIndex::NumIndexes in libs/hwui/FrameInfo.h
     }
 
diff --git a/graphics/java/android/graphics/FrameInfo.java b/graphics/java/android/graphics/FrameInfo.java
index 189be53..786c03b 100644
--- a/graphics/java/android/graphics/FrameInfo.java
+++ b/graphics/java/android/graphics/FrameInfo.java
@@ -87,18 +87,22 @@
     // When the frame needs to be ready by
     public static final int FRAME_DEADLINE = 9;
 
+    // When frame actually started.
+    public static final int FRAME_START_TIME = 10;
+
     // Must be the last one
     // This value must be in sync with `UI_THREAD_FRAME_INFO_SIZE` in FrameInfo.h
-    private static final int FRAME_INFO_SIZE = FRAME_DEADLINE + 1;
+    private static final int FRAME_INFO_SIZE = FRAME_START_TIME + 1;
 
     /** checkstyle */
     public void setVsync(long intendedVsync, long usedVsync, long frameTimelineVsyncId,
-            long frameDeadline) {
+            long frameDeadline, long frameStartTime) {
         frameInfo[FRAME_TIMELINE_VSYNC_ID] = frameTimelineVsyncId;
         frameInfo[INTENDED_VSYNC] = intendedVsync;
         frameInfo[VSYNC] = usedVsync;
         frameInfo[FLAGS] = 0;
         frameInfo[FRAME_DEADLINE] = frameDeadline;
+        frameInfo[FRAME_START_TIME] = frameStartTime;
     }
 
     /** checkstyle */
diff --git a/graphics/java/android/graphics/HardwareRenderer.java b/graphics/java/android/graphics/HardwareRenderer.java
index 88cf96a..7589435 100644
--- a/graphics/java/android/graphics/HardwareRenderer.java
+++ b/graphics/java/android/graphics/HardwareRenderer.java
@@ -28,6 +28,7 @@
 import android.hardware.display.DisplayManager;
 import android.os.IBinder;
 import android.os.ParcelFileDescriptor;
+import android.os.PerformanceHintManager;
 import android.os.RemoteException;
 import android.os.ServiceManager;
 import android.util.Log;
@@ -165,7 +166,7 @@
      * to opaque with no light source configured.
      */
     public HardwareRenderer() {
-        ProcessInitializer.sInstance.initDisplayInfo();
+        ProcessInitializer.sInstance.initUsingContext();
         mRootNode = RenderNode.adopt(nCreateRootRenderNode());
         mRootNode.setClipToBounds(false);
         mNativeProxy = nCreateProxy(!mOpaque, mRootNode.mNativeRenderNode);
@@ -365,7 +366,8 @@
          */
         public @NonNull FrameRenderRequest setVsyncTime(long vsyncTime) {
             // TODO(b/168552873): populate vsync Id once available to Choreographer public API
-            mFrameInfo.setVsync(vsyncTime, vsyncTime, FrameInfo.INVALID_VSYNC_ID, Long.MAX_VALUE);
+            mFrameInfo.setVsync(vsyncTime, vsyncTime, FrameInfo.INVALID_VSYNC_ID, Long.MAX_VALUE,
+                    vsyncTime);
             mFrameInfo.addFlags(FrameInfo.FLAG_SURFACE_CANVAS);
             return this;
         }
@@ -835,6 +837,36 @@
         callback.onPictureCaptured(picture);
     }
 
+    /** called by native */
+    static PerformanceHintManager.Session createHintSession(int[] tids) {
+        PerformanceHintManager performanceHintManager =
+                ProcessInitializer.sInstance.getHintManager();
+        if (performanceHintManager == null) {
+            return null;
+        }
+        // Native code will always set a target duration before reporting actual durations.
+        // So this is just a placeholder value that's never used.
+        long targetDurationNanos = 16666667;
+        return performanceHintManager.createHintSession(tids, targetDurationNanos);
+    }
+
+    /** called by native */
+    static void updateTargetWorkDuration(PerformanceHintManager.Session session,
+            long targetDurationNanos) {
+        session.updateTargetWorkDuration(targetDurationNanos);
+    }
+
+    /** called by native */
+    static void reportActualWorkDuration(PerformanceHintManager.Session session,
+            long actualDurationNanos) {
+        session.reportActualWorkDuration(actualDurationNanos);
+    }
+
+    /** called by native */
+    static void closeHintSession(PerformanceHintManager.Session session) {
+        session.close();
+    }
+
     /**
      * Interface used to receive callbacks when a frame is being drawn.
      *
@@ -1071,6 +1103,7 @@
         private boolean mIsolated = false;
         private Context mContext;
         private String mPackageName;
+        private PerformanceHintManager mPerformanceHintManager;
         private IGraphicsStats mGraphicsStatsService;
         private IGraphicsStatsCallback mGraphicsStatsCallback = new IGraphicsStatsCallback.Stub() {
             @Override
@@ -1082,6 +1115,10 @@
         private ProcessInitializer() {
         }
 
+        synchronized PerformanceHintManager getHintManager() {
+            return mPerformanceHintManager;
+        }
+
         synchronized void setPackageName(String name) {
             if (mInitialized) return;
             mPackageName = name;
@@ -1127,15 +1164,23 @@
             }
         }
 
-        synchronized void initDisplayInfo() {
-            if (mDisplayInitialized) return;
+        synchronized void initUsingContext() {
             if (mContext == null) return;
 
-            // If we're in an isolated sandbox mode then we shouldn't try to communicate with DMS
+            initDisplayInfo();
+
+            // HintManager and HintSession are designed to be accessible from isoalted processes
+            // so not checking for isolated process here.
+            initHintSession();
+
+            // Defensively clear out the context in case we were passed a context that can leak
+            // if we live longer than it, e.g. an activity context.
+            mContext = null;
+        }
+
+        private void initDisplayInfo() {
+            if (mDisplayInitialized) return;
             if (mIsolated) {
-                // Defensively clear out the context in case we were passed a context that can leak
-                // if we live longer than it, e.g. an activity context.
-                mContext = null;
                 mDisplayInitialized = true;
                 return;
             }
@@ -1167,11 +1212,14 @@
                     display.getRefreshRate(), wideColorDataspace.mNativeDataspace,
                     display.getAppVsyncOffsetNanos(), display.getPresentationDeadlineNanos());
 
-            // Defensively clear out the context
-            mContext = null;
             mDisplayInitialized = true;
         }
 
+        private void initHintSession() {
+            if (mContext == null) return;
+            mPerformanceHintManager = mContext.getSystemService(PerformanceHintManager.class);
+        }
+
         private void rotateBuffer() {
             nRotateProcessStatsBuffer();
             requestBuffer();
diff --git a/libs/hwui/FrameInfo.cpp b/libs/hwui/FrameInfo.cpp
index 2448cc9..51fbf36 100644
--- a/libs/hwui/FrameInfo.cpp
+++ b/libs/hwui/FrameInfo.cpp
@@ -21,29 +21,16 @@
 namespace uirenderer {
 
 const std::array FrameInfoNames{
-        "Flags",
-        "FrameTimelineVsyncId",
-        "IntendedVsync",
-        "Vsync",
-        "InputEventId",
-        "HandleInputStart",
-        "AnimationStart",
-        "PerformTraversalsStart",
-        "DrawStart",
-        "FrameDeadline",
-        "SyncQueued",
-        "SyncStart",
-        "IssueDrawCommandsStart",
-        "SwapBuffers",
-        "FrameCompleted",
-        "DequeueBufferDuration",
-        "QueueBufferDuration",
-        "GpuCompleted",
-        "SwapBuffersCompleted",
-        "DisplayPresentTime",
+        "Flags",          "FrameTimelineVsyncId",   "IntendedVsync",
+        "Vsync",          "InputEventId",           "HandleInputStart",
+        "AnimationStart", "PerformTraversalsStart", "DrawStart",
+        "FrameDeadline",  "FrameStartTime",         "SyncQueued",
+        "SyncStart",      "IssueDrawCommandsStart", "SwapBuffers",
+        "FrameCompleted", "DequeueBufferDuration",  "QueueBufferDuration",
+        "GpuCompleted",   "SwapBuffersCompleted",   "DisplayPresentTime",
 };
 
-static_assert(static_cast<int>(FrameInfoIndex::NumIndexes) == 20,
+static_assert(static_cast<int>(FrameInfoIndex::NumIndexes) == 21,
               "Must update value in FrameMetrics.java#FRAME_STATS_COUNT (and here)");
 
 void FrameInfo::importUiThreadInfo(int64_t* info) {
diff --git a/libs/hwui/FrameInfo.h b/libs/hwui/FrameInfo.h
index e9b2f4a..62ac4ca 100644
--- a/libs/hwui/FrameInfo.h
+++ b/libs/hwui/FrameInfo.h
@@ -28,7 +28,7 @@
 namespace android {
 namespace uirenderer {
 
-static constexpr size_t UI_THREAD_FRAME_INFO_SIZE = 10;
+static constexpr size_t UI_THREAD_FRAME_INFO_SIZE = 11;
 
 enum class FrameInfoIndex {
     Flags = 0,
@@ -41,6 +41,7 @@
     PerformTraversalsStart,
     DrawStart,
     FrameDeadline,
+    FrameStartTime,
     // End of UI frame info
 
     SyncQueued,
diff --git a/libs/hwui/Properties.cpp b/libs/hwui/Properties.cpp
index e58f31f..7af0a22 100644
--- a/libs/hwui/Properties.cpp
+++ b/libs/hwui/Properties.cpp
@@ -81,6 +81,9 @@
 int Properties::contextPriority = 0;
 float Properties::defaultSdrWhitePoint = 200.f;
 
+bool Properties::useHintManager = true;
+int Properties::targetCpuTimePercentage = 70;
+
 bool Properties::load() {
     bool prevDebugLayersUpdates = debugLayersUpdates;
     bool prevDebugOverdraw = debugOverdraw;
@@ -128,6 +131,10 @@
 
     runningInEmulator = base::GetBoolProperty(PROPERTY_IS_EMULATOR, false);
 
+    useHintManager = base::GetBoolProperty(PROPERTY_USE_HINT_MANAGER, true);
+    targetCpuTimePercentage = base::GetIntProperty(PROPERTY_TARGET_CPU_TIME_PERCENTAGE, 70);
+    if (targetCpuTimePercentage <= 0 || targetCpuTimePercentage > 100) targetCpuTimePercentage = 70;
+
     return (prevDebugLayersUpdates != debugLayersUpdates) || (prevDebugOverdraw != debugOverdraw);
 }
 
diff --git a/libs/hwui/Properties.h b/libs/hwui/Properties.h
index ea9cbd5..1cb87be 100644
--- a/libs/hwui/Properties.h
+++ b/libs/hwui/Properties.h
@@ -158,6 +158,20 @@
 #define PROPERTY_CAPTURE_SKP_FILENAME "debug.hwui.skp_filename"
 
 /**
+ * Controls whether HWUI will send timing hints to HintManager for
+ * better CPU scheduling. Accepted values are "true" and "false".
+ */
+#define PROPERTY_USE_HINT_MANAGER "debug.hwui.use_hint_manager"
+
+/**
+ * Percentage of frame time that's used for CPU work. The rest is
+ * reserved for GPU work. This is used with use_hint_manager to
+ * provide timing hints to HintManager. Accepted values are
+ * integer from 1-100.
+ */
+#define PROPERTY_TARGET_CPU_TIME_PERCENTAGE "debug.hwui.target_cpu_time_percent"
+
+/**
  * Property for whether this is running in the emulator.
  */
 #define PROPERTY_IS_EMULATOR "ro.boot.qemu"
@@ -253,6 +267,9 @@
 
     static float defaultSdrWhitePoint;
 
+    static bool useHintManager;
+    static int targetCpuTimePercentage;
+
 private:
     static ProfileType sProfileType;
     static bool sDisableProfileBars;
diff --git a/libs/hwui/jni/android_graphics_HardwareRenderer.cpp b/libs/hwui/jni/android_graphics_HardwareRenderer.cpp
index f24ba5c..bd1da98 100644
--- a/libs/hwui/jni/android_graphics_HardwareRenderer.cpp
+++ b/libs/hwui/jni/android_graphics_HardwareRenderer.cpp
@@ -34,14 +34,18 @@
 #include <renderthread/RenderProxy.h>
 #include <renderthread/RenderTask.h>
 #include <renderthread/RenderThread.h>
+#include <thread/CommonPool.h>
 #include <utils/Color.h>
 #include <utils/RefBase.h>
 #include <utils/StrongPointer.h>
 #include <utils/Timers.h>
 #include <utils/TraceUtils.h>
 
+#include <pthread.h>
+
 #include <algorithm>
 #include <atomic>
+#include <vector>
 
 #include "android_graphics_HardwareRendererObserver.h"
 
@@ -53,6 +57,10 @@
 struct {
     jclass clazz;
     jmethodID invokePictureCapturedCallback;
+    jmethodID createHintSession;
+    jmethodID updateTargetWorkDuration;
+    jmethodID reportActualWorkDuration;
+    jmethodID closeHintSession;
 } gHardwareRenderer;
 
 struct {
@@ -71,6 +79,14 @@
     return env;
 }
 
+static bool hasExceptionAndClear(JNIEnv* env) {
+    if (GraphicsJNI::hasException(env)) {
+        env->ExceptionClear();
+        return true;
+    }
+    return false;
+}
+
 typedef ANativeWindow* (*ANW_fromSurface)(JNIEnv* env, jobject surface);
 ANW_fromSurface fromSurface;
 
@@ -120,6 +136,67 @@
     }
 };
 
+class HintSessionWrapper : public LightRefBase<HintSessionWrapper> {
+public:
+    static sp<HintSessionWrapper> create(JNIEnv* env, RenderProxy* proxy) {
+        if (!Properties::useHintManager) return nullptr;
+
+        // Include UI thread (self), render thread, and thread pool.
+        std::vector<int> tids = CommonPool::getThreadIds();
+        tids.push_back(proxy->getRenderThreadTid());
+        tids.push_back(pthread_gettid_np(pthread_self()));
+
+        jintArray tidsArray = env->NewIntArray(tids.size());
+        if (hasExceptionAndClear(env)) return nullptr;
+        env->SetIntArrayRegion(tidsArray, 0, tids.size(), reinterpret_cast<jint*>(tids.data()));
+        if (hasExceptionAndClear(env)) return nullptr;
+        jobject session = env->CallStaticObjectMethod(
+                gHardwareRenderer.clazz, gHardwareRenderer.createHintSession, tidsArray);
+        if (hasExceptionAndClear(env) || !session) return nullptr;
+        return new HintSessionWrapper(env, session);
+    }
+
+    ~HintSessionWrapper() {
+        if (!mSession) return;
+        JNIEnv* env = getenv(mVm);
+        env->CallStaticVoidMethod(gHardwareRenderer.clazz, gHardwareRenderer.closeHintSession,
+                                  mSession);
+        hasExceptionAndClear(env);
+        env->DeleteGlobalRef(mSession);
+        mSession = nullptr;
+    }
+
+    void updateTargetWorkDuration(long targetDurationNanos) {
+        if (!mSession) return;
+        JNIEnv* env = getenv(mVm);
+        env->CallStaticVoidMethod(gHardwareRenderer.clazz,
+                                  gHardwareRenderer.updateTargetWorkDuration, mSession,
+                                  static_cast<jlong>(targetDurationNanos));
+        hasExceptionAndClear(env);
+    }
+
+    void reportActualWorkDuration(long actualDurationNanos) {
+        if (!mSession) return;
+        JNIEnv* env = getenv(mVm);
+        env->CallStaticVoidMethod(gHardwareRenderer.clazz,
+                                  gHardwareRenderer.reportActualWorkDuration, mSession,
+                                  static_cast<jlong>(actualDurationNanos));
+        hasExceptionAndClear(env);
+    }
+
+private:
+    HintSessionWrapper(JNIEnv* env, jobject jobject) {
+        env->GetJavaVM(&mVm);
+        if (jobject) {
+            mSession = env->NewGlobalRef(jobject);
+            LOG_ALWAYS_FATAL_IF(!mSession, "Failed to make global ref");
+        }
+    }
+
+    JavaVM* mVm = nullptr;
+    jobject mSession = nullptr;
+};
+
 static void android_view_ThreadedRenderer_rotateProcessStatsBuffer(JNIEnv* env, jobject clazz) {
     RenderProxy::rotateProcessStatsBuffer();
 }
@@ -147,6 +224,12 @@
     RootRenderNode* rootRenderNode = reinterpret_cast<RootRenderNode*>(rootRenderNodePtr);
     ContextFactoryImpl factory(rootRenderNode);
     RenderProxy* proxy = new RenderProxy(translucent, rootRenderNode, &factory);
+    sp<HintSessionWrapper> wrapper = HintSessionWrapper::create(env, proxy);
+    if (wrapper) {
+        proxy->setHintSessionCallbacks(
+                [wrapper](int64_t nanos) { wrapper->updateTargetWorkDuration(nanos); },
+                [wrapper](int64_t nanos) { wrapper->reportActualWorkDuration(nanos); });
+    }
     return (jlong) proxy;
 }
 
@@ -769,6 +852,18 @@
     gHardwareRenderer.invokePictureCapturedCallback = GetStaticMethodIDOrDie(env, hardwareRenderer,
             "invokePictureCapturedCallback",
             "(JLandroid/graphics/HardwareRenderer$PictureCapturedCallback;)V");
+    gHardwareRenderer.createHintSession =
+            GetStaticMethodIDOrDie(env, hardwareRenderer, "createHintSession",
+                                   "([I)Landroid/os/PerformanceHintManager$Session;");
+    gHardwareRenderer.updateTargetWorkDuration =
+            GetStaticMethodIDOrDie(env, hardwareRenderer, "updateTargetWorkDuration",
+                                   "(Landroid/os/PerformanceHintManager$Session;J)V");
+    gHardwareRenderer.reportActualWorkDuration =
+            GetStaticMethodIDOrDie(env, hardwareRenderer, "reportActualWorkDuration",
+                                   "(Landroid/os/PerformanceHintManager$Session;J)V");
+    gHardwareRenderer.closeHintSession =
+            GetStaticMethodIDOrDie(env, hardwareRenderer, "closeHintSession",
+                                   "(Landroid/os/PerformanceHintManager$Session;)V");
 
     jclass frameCallbackClass = FindClassOrDie(env,
             "android/graphics/HardwareRenderer$FrameDrawingCallback");
diff --git a/libs/hwui/renderthread/DrawFrameTask.cpp b/libs/hwui/renderthread/DrawFrameTask.cpp
index 3408ffd..7a38a3b 100644
--- a/libs/hwui/renderthread/DrawFrameTask.cpp
+++ b/libs/hwui/renderthread/DrawFrameTask.cpp
@@ -21,6 +21,7 @@
 
 #include "../DeferredLayerUpdater.h"
 #include "../DisplayList.h"
+#include "../Properties.h"
 #include "../RenderNode.h"
 #include "CanvasContext.h"
 #include "RenderThread.h"
@@ -44,6 +45,12 @@
     mTargetNode = targetNode;
 }
 
+void DrawFrameTask::setHintSessionCallbacks(std::function<void(int64_t)> updateTargetWorkDuration,
+                                            std::function<void(int64_t)> reportActualWorkDuration) {
+    mUpdateTargetWorkDuration = std::move(updateTargetWorkDuration);
+    mReportActualWorkDuration = std::move(reportActualWorkDuration);
+}
+
 void DrawFrameTask::pushLayerUpdate(DeferredLayerUpdater* layer) {
     LOG_ALWAYS_FATAL_IF(!mContext,
                         "Lifecycle violation, there's no context to pushLayerUpdate with!");
@@ -102,6 +109,9 @@
     CanvasContext* context = mContext;
     std::function<void(int64_t)> callback = std::move(mFrameCallback);
     mFrameCallback = nullptr;
+    int64_t intendedVsync = mFrameInfo[static_cast<int>(FrameInfoIndex::IntendedVsync)];
+    int64_t frameDeadline = mFrameInfo[static_cast<int>(FrameInfoIndex::FrameDeadline)];
+    int64_t frameStartTime = mFrameInfo[static_cast<int>(FrameInfoIndex::FrameStartTime)];
 
     // From this point on anything in "this" is *UNSAFE TO ACCESS*
     if (canUnblockUiThread) {
@@ -124,6 +134,25 @@
     if (!canUnblockUiThread) {
         unblockUiThread();
     }
+
+    // These member callbacks are effectively const as they are set once during init, so it's safe
+    // to use these directly instead of making local copies.
+    if (mUpdateTargetWorkDuration && mReportActualWorkDuration) {
+        constexpr int64_t kSanityCheckLowerBound = 100000;       // 0.1ms
+        constexpr int64_t kSanityCheckUpperBound = 10000000000;  // 10s
+        int64_t targetWorkDuration = frameDeadline - intendedVsync;
+        targetWorkDuration = targetWorkDuration * Properties::targetCpuTimePercentage / 100;
+        if (targetWorkDuration > kSanityCheckLowerBound &&
+            targetWorkDuration < kSanityCheckUpperBound &&
+            targetWorkDuration != mLastTargetWorkDuration) {
+            mLastTargetWorkDuration = targetWorkDuration;
+            mUpdateTargetWorkDuration(targetWorkDuration);
+        }
+        int64_t frameDuration = systemTime(SYSTEM_TIME_MONOTONIC) - frameStartTime;
+        if (frameDuration > kSanityCheckLowerBound && frameDuration < kSanityCheckUpperBound) {
+            mReportActualWorkDuration(frameDuration);
+        }
+    }
 }
 
 bool DrawFrameTask::syncFrameState(TreeInfo& info) {
diff --git a/libs/hwui/renderthread/DrawFrameTask.h b/libs/hwui/renderthread/DrawFrameTask.h
index 696cfaef..3bb574a 100644
--- a/libs/hwui/renderthread/DrawFrameTask.h
+++ b/libs/hwui/renderthread/DrawFrameTask.h
@@ -61,6 +61,8 @@
     virtual ~DrawFrameTask();
 
     void setContext(RenderThread* thread, CanvasContext* context, RenderNode* targetNode);
+    void setHintSessionCallbacks(std::function<void(int64_t)> updateTargetWorkDuration,
+                                 std::function<void(int64_t)> reportActualWorkDuration);
     void setContentDrawBounds(int left, int top, int right, int bottom) {
         mContentDrawBounds.set(left, top, right, bottom);
     }
@@ -107,6 +109,10 @@
 
     std::function<void(int64_t)> mFrameCallback;
     std::function<void(int64_t)> mFrameCompleteCallback;
+
+    nsecs_t mLastTargetWorkDuration = 0;
+    std::function<void(int64_t)> mUpdateTargetWorkDuration;
+    std::function<void(int64_t)> mReportActualWorkDuration;
 };
 
 } /* namespace renderthread */
diff --git a/libs/hwui/renderthread/RenderProxy.cpp b/libs/hwui/renderthread/RenderProxy.cpp
index 423cc08..9361abd 100644
--- a/libs/hwui/renderthread/RenderProxy.cpp
+++ b/libs/hwui/renderthread/RenderProxy.cpp
@@ -76,6 +76,12 @@
     mRenderThread.queue().runSync([this, name]() { mContext->setName(std::string(name)); });
 }
 
+void RenderProxy::setHintSessionCallbacks(std::function<void(int64_t)> updateTargetWorkDuration,
+                                          std::function<void(int64_t)> reportActualWorkDuration) {
+    mDrawFrameTask.setHintSessionCallbacks(std::move(updateTargetWorkDuration),
+                                           std::move(reportActualWorkDuration));
+}
+
 void RenderProxy::setSurface(ANativeWindow* window, bool enableTimeout) {
     if (window) { ANativeWindow_acquire(window); }
     mRenderThread.queue().post([this, win = window, enableTimeout]() mutable {
diff --git a/libs/hwui/renderthread/RenderProxy.h b/libs/hwui/renderthread/RenderProxy.h
index 366d6b5..8d55d3c 100644
--- a/libs/hwui/renderthread/RenderProxy.h
+++ b/libs/hwui/renderthread/RenderProxy.h
@@ -71,6 +71,8 @@
     void setSwapBehavior(SwapBehavior swapBehavior);
     bool loadSystemProperties();
     void setName(const char* name);
+    void setHintSessionCallbacks(std::function<void(int64_t)> updateTargetWorkDuration,
+                                 std::function<void(int64_t)> reportActualWorkDuration);
 
     void setSurface(ANativeWindow* window, bool enableTimeout = true);
     void setSurfaceControl(ASurfaceControl* surfaceControl);
diff --git a/libs/hwui/thread/CommonPool.cpp b/libs/hwui/thread/CommonPool.cpp
index d011bdf..dc92f9f 100644
--- a/libs/hwui/thread/CommonPool.cpp
+++ b/libs/hwui/thread/CommonPool.cpp
@@ -29,14 +29,23 @@
     ATRACE_CALL();
 
     CommonPool* pool = this;
+    std::mutex mLock;
+    std::vector<int> tids(THREAD_COUNT);
+    std::vector<std::condition_variable> tidConditionVars(THREAD_COUNT);
+
     // Create 2 workers
     for (int i = 0; i < THREAD_COUNT; i++) {
-        std::thread worker([pool, i] {
+        std::thread worker([pool, i, &mLock, &tids, &tidConditionVars] {
             {
                 std::array<char, 20> name{"hwuiTask"};
                 snprintf(name.data(), name.size(), "hwuiTask%d", i);
                 auto self = pthread_self();
                 pthread_setname_np(self, name.data());
+                {
+                    std::unique_lock lock(mLock);
+                    tids[i] = pthread_gettid_np(self);
+                    tidConditionVars[i].notify_one();
+                }
                 setpriority(PRIO_PROCESS, 0, PRIORITY_FOREGROUND);
                 auto startHook = renderthread::RenderThread::getOnStartHook();
                 if (startHook) {
@@ -47,6 +56,15 @@
         });
         worker.detach();
     }
+    {
+        std::unique_lock lock(mLock);
+        for (int i = 0; i < THREAD_COUNT; i++) {
+            while (!tids[i]) {
+                tidConditionVars[i].wait(lock);
+            }
+        }
+    }
+    mWorkerThreadIds = std::move(tids);
 }
 
 CommonPool& CommonPool::instance() {
@@ -58,6 +76,10 @@
     instance().enqueue(std::move(task));
 }
 
+std::vector<int> CommonPool::getThreadIds() {
+    return instance().mWorkerThreadIds;
+}
+
 void CommonPool::enqueue(Task&& task) {
     std::unique_lock lock(mLock);
     while (!mWorkQueue.hasSpace()) {
@@ -104,4 +126,4 @@
 }
 
 }  // namespace uirenderer
-}  // namespace android
\ No newline at end of file
+}  // namespace android
diff --git a/libs/hwui/thread/CommonPool.h b/libs/hwui/thread/CommonPool.h
index 7603eee..74f852b 100644
--- a/libs/hwui/thread/CommonPool.h
+++ b/libs/hwui/thread/CommonPool.h
@@ -25,6 +25,7 @@
 #include <functional>
 #include <future>
 #include <mutex>
+#include <vector>
 
 namespace android {
 namespace uirenderer {
@@ -97,6 +98,8 @@
         return task.get_future().get();
     };
 
+    static std::vector<int> getThreadIds();
+
     // For testing purposes only, blocks until all worker threads are parked.
     static void waitForIdle();
 
@@ -111,6 +114,8 @@
 
     void workerLoop();
 
+    std::vector<int> mWorkerThreadIds;
+
     std::mutex mLock;
     std::condition_variable mCondition;
     int mWaitingThreads = 0;