transcoding: add thermal status listener

Register thermal status listener with thermal manager, and
pause/resume transcoding activity when thermal status goes
above certain threshold (currently set at CRITICAL).

Add unit tests with mock thermal policy. Also add unit test
that exercise the actual transcoding service, but use adb
shell cmd to fake thermal status change.

bug: 169452730
test: unit testing added in the CL; manual testing using
"adb shell cmd thermalservice override-status #" and observe
thermal throttling logs from transcoding service.

Change-Id: I7d28ed3069bdd73e8984ad138e2edba9c9a2360a
diff --git a/media/libmediatranscoding/Android.bp b/media/libmediatranscoding/Android.bp
index 094d7c3..23e83f9 100644
--- a/media/libmediatranscoding/Android.bp
+++ b/media/libmediatranscoding/Android.bp
@@ -71,11 +71,12 @@
     ],
 
     srcs: [
-        "TranscodingClientManager.cpp",
-        "TranscodingSessionController.cpp",
-        "TranscodingResourcePolicy.cpp",
-        "TranscodingUidPolicy.cpp",
         "TranscoderWrapper.cpp",
+        "TranscodingClientManager.cpp",
+        "TranscodingResourcePolicy.cpp",
+        "TranscodingSessionController.cpp",
+        "TranscodingThermalPolicy.cpp",
+        "TranscodingUidPolicy.cpp",
     ],
 
     shared_libs: [
diff --git a/media/libmediatranscoding/TranscodingSessionController.cpp b/media/libmediatranscoding/TranscodingSessionController.cpp
index b77a3a4..09ad3cd 100644
--- a/media/libmediatranscoding/TranscodingSessionController.cpp
+++ b/media/libmediatranscoding/TranscodingSessionController.cpp
@@ -63,10 +63,12 @@
 TranscodingSessionController::TranscodingSessionController(
         const std::shared_ptr<TranscoderInterface>& transcoder,
         const std::shared_ptr<UidPolicyInterface>& uidPolicy,
-        const std::shared_ptr<ResourcePolicyInterface>& resourcePolicy)
+        const std::shared_ptr<ResourcePolicyInterface>& resourcePolicy,
+        const std::shared_ptr<ThermalPolicyInterface>& thermalPolicy)
       : mTranscoder(transcoder),
         mUidPolicy(uidPolicy),
         mResourcePolicy(resourcePolicy),
+        mThermalPolicy(thermalPolicy),
         mCurrentSession(nullptr),
         mResourceLost(false) {
     // Only push empty offline queue initially. Realtime queues are added when requests come in.
@@ -74,6 +76,7 @@
     mOfflineUidIterator = mUidSortedList.begin();
     mSessionQueues.emplace(OFFLINE_UID, SessionQueueType());
     mUidPackageNames[OFFLINE_UID] = "(offline)";
+    mThermalThrottling = thermalPolicy->getThrottlingStatus();
 }
 
 TranscodingSessionController::~TranscodingSessionController() {}
@@ -141,7 +144,7 @@
 
     snprintf(buffer, SIZE, "\n========== Dumping past sessions =========\n");
     result.append(buffer);
-    for (auto &session : mSessionHistory) {
+    for (auto& session : mSessionHistory) {
         dumpSession_l(session, result, true /*closedSession*/);
     }
 
@@ -192,18 +195,27 @@
           topSession == nullptr ? "null" : sessionToString(topSession->key).c_str(),
           curSession == nullptr ? "null" : sessionToString(curSession->key).c_str());
 
+    if (topSession == nullptr) {
+        mCurrentSession = nullptr;
+        return;
+    }
+
+    bool shouldBeRunning = !((mResourcePolicy != nullptr && mResourceLost) ||
+                             (mThermalPolicy != nullptr && mThermalThrottling));
     // If we found a topSession that should be run, and it's not already running,
     // take some actions to ensure it's running.
-    if (topSession != nullptr &&
-        (topSession != curSession || topSession->getState() != Session::RUNNING)) {
-        // If another session is currently running, pause it first.
+    if (topSession != curSession ||
+        (shouldBeRunning ^ (topSession->getState() == Session::RUNNING))) {
+        // If current session is running, pause it first. Note this is true for either
+        // cases: 1) If top session is changing, or 2) if top session is not changing but
+        // the topSession's state is changing.
         if (curSession != nullptr && curSession->getState() == Session::RUNNING) {
             mTranscoder->pause(curSession->key.first, curSession->key.second);
             curSession->setState(Session::PAUSED);
         }
-        // If we are not experiencing resource loss, we can start or resume
-        // the topSession now.
-        if (!mResourceLost) {
+        // If we are not experiencing resource loss nor thermal throttling, we can start
+        // or resume the topSession now.
+        if (shouldBeRunning) {
             if (topSession->getState() == Session::NOT_STARTED) {
                 mTranscoder->start(topSession->key.first, topSession->key.second,
                                    topSession->request, topSession->callback.lock());
@@ -566,7 +578,9 @@
         if (clientCallback != nullptr) {
             clientCallback->onTranscodingPaused(sessionKey.second);
         }
-        mResourcePolicy->setPidResourceLost(resourceLostSession->request.clientPid);
+        if (mResourcePolicy != nullptr) {
+            mResourcePolicy->setPidResourceLost(resourceLostSession->request.clientPid);
+        }
         mResourceLost = true;
 
         validateState_l();
@@ -613,6 +627,36 @@
     validateState_l();
 }
 
+void TranscodingSessionController::onThrottlingStarted() {
+    std::scoped_lock lock{mLock};
+
+    if (mThermalThrottling) {
+        return;
+    }
+
+    ALOGI("%s", __FUNCTION__);
+
+    mThermalThrottling = true;
+    updateCurrentSession_l();
+
+    validateState_l();
+}
+
+void TranscodingSessionController::onThrottlingStopped() {
+    std::scoped_lock lock{mLock};
+
+    if (!mThermalThrottling) {
+        return;
+    }
+
+    ALOGI("%s", __FUNCTION__);
+
+    mThermalThrottling = false;
+    updateCurrentSession_l();
+
+    validateState_l();
+}
+
 void TranscodingSessionController::validateState_l() {
 #ifdef VALIDATE_STATE
     LOG_ALWAYS_FATAL_IF(mSessionQueues.count(OFFLINE_UID) != 1,
diff --git a/media/libmediatranscoding/TranscodingThermalPolicy.cpp b/media/libmediatranscoding/TranscodingThermalPolicy.cpp
new file mode 100644
index 0000000..7f585c7
--- /dev/null
+++ b/media/libmediatranscoding/TranscodingThermalPolicy.cpp
@@ -0,0 +1,125 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+//#define LOG_NDEBUG 0
+#define LOG_TAG "TranscodingThermalPolicy"
+
+#include <media/TranscodingThermalPolicy.h>
+#include <utils/Log.h>
+
+namespace android {
+
+static bool needThrottling(AThermalStatus status) {
+    return (status >= ATHERMAL_STATUS_SEVERE);
+}
+
+//static
+void TranscodingThermalPolicy::onStatusChange(void* data, AThermalStatus status) {
+    TranscodingThermalPolicy* policy = static_cast<TranscodingThermalPolicy*>(data);
+    policy->onStatusChange(status);
+}
+
+TranscodingThermalPolicy::TranscodingThermalPolicy()
+      : mRegistered(false), mThermalManager(nullptr), mIsThrottling(false) {
+    registerSelf();
+}
+
+TranscodingThermalPolicy::~TranscodingThermalPolicy() {
+    unregisterSelf();
+}
+
+void TranscodingThermalPolicy::registerSelf() {
+    ALOGI("TranscodingThermalPolicy: registerSelf");
+
+    std::scoped_lock lock{mRegisteredLock};
+
+    if (mRegistered) {
+        return;
+    }
+
+    AThermalManager* thermalManager = AThermal_acquireManager();
+    if (thermalManager == nullptr) {
+        ALOGE("Failed to acquire thermal manager");
+        return;
+    }
+
+    int ret = AThermal_registerThermalStatusListener(thermalManager, onStatusChange, this);
+    if (ret != 0) {
+        ALOGE("Failed to register thermal status listener");
+        AThermal_releaseManager(thermalManager);
+        return;
+    }
+
+    mIsThrottling = needThrottling(AThermal_getCurrentThermalStatus(thermalManager));
+    mThermalManager = thermalManager;
+    mRegistered = true;
+}
+
+void TranscodingThermalPolicy::unregisterSelf() {
+    ALOGI("TranscodingThermalPolicy: unregisterSelf");
+
+    std::scoped_lock lock{mRegisteredLock};
+
+    if (!mRegistered) {
+        return;
+    }
+
+    if (mThermalManager != nullptr) {
+        // Unregister listener
+        int ret = AThermal_unregisterThermalStatusListener(mThermalManager, onStatusChange, this);
+        if (ret != 0) {
+            ALOGW("Failed to unregister thermal status listener");
+        }
+        AThermal_releaseManager(mThermalManager);
+        mThermalManager = nullptr;
+    }
+    mRegistered = false;
+}
+
+void TranscodingThermalPolicy::setCallback(
+        const std::shared_ptr<ThermalPolicyCallbackInterface>& cb) {
+    std::scoped_lock lock{mCallbackLock};
+    mThermalPolicyCallback = cb;
+}
+
+bool TranscodingThermalPolicy::getThrottlingStatus() {
+    std::scoped_lock lock{mRegisteredLock};
+    return mIsThrottling;
+}
+
+void TranscodingThermalPolicy::onStatusChange(AThermalStatus status) {
+    bool isThrottling = needThrottling(status);
+
+    {
+        std::scoped_lock lock{mRegisteredLock};
+        if (isThrottling == mIsThrottling) {
+            return;
+        }
+        ALOGI("Transcoding thermal throttling changed: %d", isThrottling);
+        mIsThrottling = isThrottling;
+    }
+
+    std::scoped_lock lock{mCallbackLock};
+    std::shared_ptr<ThermalPolicyCallbackInterface> cb;
+    if ((cb = mThermalPolicyCallback.lock()) != nullptr) {
+        if (isThrottling) {
+            cb->onThrottlingStarted();
+        } else {
+            cb->onThrottlingStopped();
+        }
+    }
+}
+}  // namespace android
diff --git a/media/libmediatranscoding/TranscodingUidPolicy.cpp b/media/libmediatranscoding/TranscodingUidPolicy.cpp
index b0fa545..11c8ab3 100644
--- a/media/libmediatranscoding/TranscodingUidPolicy.cpp
+++ b/media/libmediatranscoding/TranscodingUidPolicy.cpp
@@ -32,9 +32,7 @@
 constexpr static int32_t IMPORTANCE_UNKNOWN = INT32_MAX;
 
 TranscodingUidPolicy::TranscodingUidPolicy()
-      : mUidObserver(nullptr),
-        mRegistered(false),
-        mTopUidState(IMPORTANCE_UNKNOWN) {
+      : mUidObserver(nullptr), mRegistered(false), mTopUidState(IMPORTANCE_UNKNOWN) {
     registerSelf();
 }
 
@@ -129,8 +127,7 @@
 bool TranscodingUidPolicy::isUidOnTop(uid_t uid) {
     Mutex::Autolock _l(mUidLock);
 
-    return mTopUidState != IMPORTANCE_UNKNOWN &&
-           mTopUidState == getProcState_l(uid);
+    return mTopUidState != IMPORTANCE_UNKNOWN && mTopUidState == getProcState_l(uid);
 }
 
 std::unordered_set<uid_t> TranscodingUidPolicy::getTopUids() const {
@@ -155,12 +152,10 @@
             // Top set changed if 1) the uid is in the current top uid set, or 2) the
             // new procState is at least the same priority as the current top uid state.
             bool isUidCurrentTop =
-                    mTopUidState != IMPORTANCE_UNKNOWN &&
-                    mStateUidMap[mTopUidState].count(uid) > 0;
+                    mTopUidState != IMPORTANCE_UNKNOWN && mStateUidMap[mTopUidState].count(uid) > 0;
             bool isNewStateHigherThanTop =
                     procState != IMPORTANCE_UNKNOWN &&
-                    (procState <= mTopUidState ||
-                     mTopUidState == IMPORTANCE_UNKNOWN);
+                    (procState <= mTopUidState || mTopUidState == IMPORTANCE_UNKNOWN);
             topUidSetChanged = (isUidCurrentTop || isNewStateHigherThanTop);
 
             // Move uid to the new procState.
@@ -192,8 +187,7 @@
 
     // Find the lowest uid state (ignoring PROCESS_STATE_UNKNOWN) with some monitored uids.
     for (auto stateIt = mStateUidMap.begin(); stateIt != mStateUidMap.end(); stateIt++) {
-        if (stateIt->first != IMPORTANCE_UNKNOWN &&
-            !stateIt->second.empty()) {
+        if (stateIt->first != IMPORTANCE_UNKNOWN && !stateIt->second.empty()) {
             mTopUidState = stateIt->first;
             break;
         }
diff --git a/media/libmediatranscoding/include/media/ThermalPolicyInterface.h b/media/libmediatranscoding/include/media/ThermalPolicyInterface.h
new file mode 100644
index 0000000..1cc0cc1
--- /dev/null
+++ b/media/libmediatranscoding/include/media/ThermalPolicyInterface.h
@@ -0,0 +1,52 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef ANDROID_MEDIA_THERMAL_POLICY_INTERFACE_H
+#define ANDROID_MEDIA_THERMAL_POLICY_INTERFACE_H
+#include <memory>
+
+namespace android {
+
+class ThermalPolicyCallbackInterface;
+
+// Interface for the SessionController to control the thermal policy.
+class ThermalPolicyInterface {
+public:
+    // Set the associated callback interface to send the events when the thermal
+    // status changes.
+    virtual void setCallback(const std::shared_ptr<ThermalPolicyCallbackInterface>& cb) = 0;
+
+    // Get the current thermal throttling status. Returns true if throttling is on,
+    // false otherwise.
+    virtual bool getThrottlingStatus() = 0;
+
+protected:
+    virtual ~ThermalPolicyInterface() = default;
+};
+
+// Interface for notifying the SessionController of thermal throttling status.
+class ThermalPolicyCallbackInterface {
+public:
+    // Called when the session controller should start or stop thermal throttling.
+    virtual void onThrottlingStarted() = 0;
+    virtual void onThrottlingStopped() = 0;
+
+protected:
+    virtual ~ThermalPolicyCallbackInterface() = default;
+};
+
+}  // namespace android
+#endif  // ANDROID_MEDIA_THERMAL_POLICY_INTERFACE_H
diff --git a/media/libmediatranscoding/include/media/TranscodingSessionController.h b/media/libmediatranscoding/include/media/TranscodingSessionController.h
index a443265..4fcc423 100644
--- a/media/libmediatranscoding/include/media/TranscodingSessionController.h
+++ b/media/libmediatranscoding/include/media/TranscodingSessionController.h
@@ -20,6 +20,7 @@
 #include <aidl/android/media/TranscodingSessionPriority.h>
 #include <media/ControllerClientInterface.h>
 #include <media/ResourcePolicyInterface.h>
+#include <media/ThermalPolicyInterface.h>
 #include <media/TranscoderInterface.h>
 #include <media/TranscodingRequest.h>
 #include <media/UidPolicyInterface.h>
@@ -38,7 +39,8 @@
 class TranscodingSessionController : public UidPolicyCallbackInterface,
                                      public ControllerClientInterface,
                                      public TranscoderCallbackInterface,
-                                     public ResourcePolicyCallbackInterface {
+                                     public ResourcePolicyCallbackInterface,
+                                     public ThermalPolicyCallbackInterface {
 public:
     virtual ~TranscodingSessionController();
 
@@ -70,6 +72,11 @@
     void onResourceAvailable() override;
     // ~ResourcePolicyCallbackInterface
 
+    // ThermalPolicyCallbackInterface
+    void onThrottlingStarted() override;
+    void onThrottlingStopped() override;
+    // ~ResourcePolicyCallbackInterface
+
     /**
      * Dump all the session information to the fd.
      */
@@ -88,6 +95,8 @@
             NOT_STARTED = 0,
             RUNNING,
             PAUSED,
+            // The following states would not appear in live sessions map, but could
+            // appear in past sessions map for logging purpose.
             FINISHED,
             CANCELED,
             ERROR,
@@ -130,15 +139,18 @@
     std::shared_ptr<TranscoderInterface> mTranscoder;
     std::shared_ptr<UidPolicyInterface> mUidPolicy;
     std::shared_ptr<ResourcePolicyInterface> mResourcePolicy;
+    std::shared_ptr<ThermalPolicyInterface> mThermalPolicy;
 
     Session* mCurrentSession;
     bool mResourceLost;
+    bool mThermalThrottling;
     std::list<Session> mSessionHistory;
 
     // Only allow MediaTranscodingService and unit tests to instantiate.
     TranscodingSessionController(const std::shared_ptr<TranscoderInterface>& transcoder,
                                  const std::shared_ptr<UidPolicyInterface>& uidPolicy,
-                                 const std::shared_ptr<ResourcePolicyInterface>& resourcePolicy);
+                                 const std::shared_ptr<ResourcePolicyInterface>& resourcePolicy,
+                                 const std::shared_ptr<ThermalPolicyInterface>& thermalPolicy);
 
     void dumpSession_l(const Session& session, String8& result, bool closedSession = false);
     Session* getTopSession_l();
diff --git a/media/libmediatranscoding/include/media/TranscodingThermalPolicy.h b/media/libmediatranscoding/include/media/TranscodingThermalPolicy.h
new file mode 100644
index 0000000..81c6eac
--- /dev/null
+++ b/media/libmediatranscoding/include/media/TranscodingThermalPolicy.h
@@ -0,0 +1,53 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef ANDROID_MEDIA_TRANSCODING_THERMAL_POLICY_H
+#define ANDROID_MEDIA_TRANSCODING_THERMAL_POLICY_H
+
+#include <android/thermal.h>
+#include <media/ThermalPolicyInterface.h>
+#include <utils/Condition.h>
+
+#include <mutex>
+
+namespace android {
+
+class TranscodingThermalPolicy : public ThermalPolicyInterface {
+public:
+    explicit TranscodingThermalPolicy();
+    ~TranscodingThermalPolicy();
+
+    void setCallback(const std::shared_ptr<ThermalPolicyCallbackInterface>& cb) override;
+    bool getThrottlingStatus() override;
+
+private:
+    mutable std::mutex mRegisteredLock;
+    bool mRegistered GUARDED_BY(mRegisteredLock);
+
+    mutable std::mutex mCallbackLock;
+    std::weak_ptr<ThermalPolicyCallbackInterface> mThermalPolicyCallback GUARDED_BY(mCallbackLock);
+
+    AThermalManager* mThermalManager;
+    bool mIsThrottling;
+
+    static void onStatusChange(void* data, AThermalStatus status);
+    void onStatusChange(AThermalStatus status);
+    void registerSelf();
+    void unregisterSelf();
+};
+
+}  // namespace android
+#endif  // ANDROID_MEDIA_TRANSCODING_THERMAL_POLICY_H
diff --git a/media/libmediatranscoding/tests/TranscodingSessionController_tests.cpp b/media/libmediatranscoding/tests/TranscodingSessionController_tests.cpp
index fa52f63..9a1c272 100644
--- a/media/libmediatranscoding/tests/TranscodingSessionController_tests.cpp
+++ b/media/libmediatranscoding/tests/TranscodingSessionController_tests.cpp
@@ -89,9 +89,7 @@
 
     // ResourcePolicyInterface
     void setCallback(const std::shared_ptr<ResourcePolicyCallbackInterface>& /*cb*/) override {}
-    void setPidResourceLost(pid_t pid) override {
-        mResourceLostPid = pid;
-    }
+    void setPidResourceLost(pid_t pid) override { mResourceLostPid = pid; }
     // ~ResourcePolicyInterface
 
     pid_t getPid() {
@@ -101,12 +99,23 @@
     }
 
 private:
-    void reset() {
-        mResourceLostPid = kInvalidPid;
-    }
+    void reset() { mResourceLostPid = kInvalidPid; }
     pid_t mResourceLostPid;
 };
 
+class TestThermalPolicy : public ThermalPolicyInterface {
+public:
+    TestThermalPolicy() = default;
+    virtual ~TestThermalPolicy() = default;
+
+    // ThermalPolicyInterface
+    void setCallback(const std::shared_ptr<ThermalPolicyCallbackInterface>& /*cb*/) override {}
+    bool getThrottlingStatus() { return false; }
+    // ~ThermalPolicyInterface
+
+private:
+};
+
 class TestTranscoder : public TranscoderInterface {
 public:
     TestTranscoder() : mLastError(TranscodingErrorCode::kUnknown) {}
@@ -245,8 +254,9 @@
         mTranscoder.reset(new TestTranscoder());
         mUidPolicy.reset(new TestUidPolicy());
         mResourcePolicy.reset(new TestResourcePolicy());
-        mController.reset(
-                new TranscodingSessionController(mTranscoder, mUidPolicy, mResourcePolicy));
+        mThermalPolicy.reset(new TestThermalPolicy());
+        mController.reset(new TranscodingSessionController(mTranscoder, mUidPolicy, mResourcePolicy,
+                                                           mThermalPolicy));
         mUidPolicy->setCallback(mController);
 
         // Set priority only, ignore other fields for now.
@@ -269,6 +279,7 @@
     std::shared_ptr<TestTranscoder> mTranscoder;
     std::shared_ptr<TestUidPolicy> mUidPolicy;
     std::shared_ptr<TestResourcePolicy> mResourcePolicy;
+    std::shared_ptr<TestThermalPolicy> mThermalPolicy;
     std::shared_ptr<TranscodingSessionController> mController;
     TranscodingRequestParcel mOfflineRequest;
     TranscodingRequestParcel mRealtimeRequest;
@@ -577,6 +588,7 @@
     EXPECT_EQ(mTranscoder->popEvent(), TestTranscoder::Start(CLIENT(1), SESSION(0)));
 }
 
+/* Test resource lost without thermal throttling */
 TEST_F(TranscodingSessionControllerTest, TestResourceLost) {
     ALOGD("TestResourceLost");
 
@@ -633,11 +645,22 @@
     mController->onResourceAvailable();
     EXPECT_EQ(mTranscoder->popEvent(), TestTranscoder::Resume(CLIENT(0), SESSION(0)));
 
-    // Test 3: Adding new queue during resource loss.
-    // Signal resource lost.
+    // Test 3:
     mController->onResourceLost(CLIENT(0), SESSION(0));
     EXPECT_EQ(mResourcePolicy->getPid(), PID(0));
     EXPECT_EQ(mTranscoder->popEvent(), TestTranscoder::NoEvent);
+    // Cancel the paused top session during resource lost.
+    EXPECT_TRUE(mController->cancel(CLIENT(0), SESSION(0)));
+    EXPECT_EQ(mTranscoder->popEvent(), TestTranscoder::Stop(CLIENT(0), SESSION(0)));
+    // Signal resource available, CLIENT(2)'s session should start.
+    mController->onResourceAvailable();
+    EXPECT_EQ(mTranscoder->popEvent(), TestTranscoder::Resume(CLIENT(2), SESSION(0)));
+
+    // Test 4: Adding new queue during resource loss.
+    // Signal resource lost.
+    mController->onResourceLost(CLIENT(2), SESSION(0));
+    EXPECT_EQ(mResourcePolicy->getPid(), PID(1));
+    EXPECT_EQ(mTranscoder->popEvent(), TestTranscoder::NoEvent);
 
     // Move UID(2) to top.
     mUidPolicy->setTop(UID(2));
@@ -652,4 +675,131 @@
     EXPECT_EQ(mTranscoder->popEvent(), TestTranscoder::Start(CLIENT(3), SESSION(0)));
 }
 
+/* Test thermal throttling without resource lost */
+TEST_F(TranscodingSessionControllerTest, TestThermalCallback) {
+    ALOGD("TestThermalCallback");
+
+    // Start with unspecified top UID.
+    // Submit real-time session to CLIENT(0), session should start immediately.
+    mRealtimeRequest.clientPid = PID(0);
+    mController->submit(CLIENT(0), SESSION(0), UID(0), mRealtimeRequest, mClientCallback0);
+    EXPECT_EQ(mTranscoder->popEvent(), TestTranscoder::Start(CLIENT(0), SESSION(0)));
+
+    // Submit offline session to CLIENT(0), should not start.
+    mOfflineRequest.clientPid = PID(0);
+    mController->submit(CLIENT(1), SESSION(0), UID(0), mOfflineRequest, mClientCallback1);
+    EXPECT_EQ(mTranscoder->popEvent(), TestTranscoder::NoEvent);
+
+    // Move UID(1) to top.
+    mUidPolicy->setTop(UID(1));
+    EXPECT_EQ(mTranscoder->popEvent(), TestTranscoder::NoEvent);
+
+    // Submit real-time session to CLIENT(2) in different uid UID(1).
+    // Should pause previous session and start new session.
+    mRealtimeRequest.clientPid = PID(1);
+    mController->submit(CLIENT(2), SESSION(0), UID(1), mRealtimeRequest, mClientCallback2);
+    EXPECT_EQ(mTranscoder->popEvent(), TestTranscoder::Pause(CLIENT(0), SESSION(0)));
+    EXPECT_EQ(mTranscoder->popEvent(), TestTranscoder::Start(CLIENT(2), SESSION(0)));
+
+    // Test 0: Basic case, no queue change during throttling, top session should pause/resume
+    // with throttling.
+    mController->onThrottlingStarted();
+    EXPECT_EQ(mTranscoder->popEvent(), TestTranscoder::Pause(CLIENT(2), SESSION(0)));
+    mController->onThrottlingStopped();
+    EXPECT_EQ(mTranscoder->popEvent(), TestTranscoder::Resume(CLIENT(2), SESSION(0)));
+
+    // Test 1: Change of queue order during thermal throttling, when throttling stops,
+    // new top session should resume.
+    mController->onThrottlingStarted();
+    EXPECT_EQ(mTranscoder->popEvent(), TestTranscoder::Pause(CLIENT(2), SESSION(0)));
+    mUidPolicy->setTop(UID(0));
+    EXPECT_EQ(mTranscoder->popEvent(), TestTranscoder::NoEvent);
+    mController->onThrottlingStopped();
+    EXPECT_EQ(mTranscoder->popEvent(), TestTranscoder::Resume(CLIENT(0), SESSION(0)));
+
+    // Test 2: Cancel session during throttling, when throttling stops, new top
+    // session should resume.
+    mController->onThrottlingStarted();
+    EXPECT_EQ(mTranscoder->popEvent(), TestTranscoder::Pause(CLIENT(0), SESSION(0)));
+    // Cancel the paused top session during throttling.
+    EXPECT_TRUE(mController->cancel(CLIENT(0), SESSION(0)));
+    EXPECT_EQ(mTranscoder->popEvent(), TestTranscoder::Stop(CLIENT(0), SESSION(0)));
+    // Throttling stops, CLIENT(2)'s session should start.
+    mController->onThrottlingStopped();
+    EXPECT_EQ(mTranscoder->popEvent(), TestTranscoder::Resume(CLIENT(2), SESSION(0)));
+
+    // Test 3: Add new queue during throttling, when throttling stops, new top
+    // session should resume.
+    mController->onThrottlingStarted();
+    EXPECT_EQ(mTranscoder->popEvent(), TestTranscoder::Pause(CLIENT(2), SESSION(0)));
+    // Move UID(2) to top.
+    mUidPolicy->setTop(UID(2));
+    // Submit real-time session to CLIENT(3) in UID(2), session shouldn't start during throttling.
+    mRealtimeRequest.clientPid = PID(2);
+    mController->submit(CLIENT(3), SESSION(0), UID(2), mRealtimeRequest, mClientCallback3);
+    EXPECT_EQ(mTranscoder->popEvent(), TestTranscoder::NoEvent);
+    // Throttling stops, CLIENT(3)'s session should start.
+    mController->onThrottlingStopped();
+    EXPECT_EQ(mTranscoder->popEvent(), TestTranscoder::Start(CLIENT(3), SESSION(0)));
+}
+
+/* Test resource lost and thermal throttling happening simultaneously */
+TEST_F(TranscodingSessionControllerTest, TestResourceLostAndThermalCallback) {
+    ALOGD("TestResourceLostAndThermalCallback");
+
+    // Start with unspecified top UID.
+    // Submit real-time session to CLIENT(0), session should start immediately.
+    mRealtimeRequest.clientPid = PID(0);
+    mController->submit(CLIENT(0), SESSION(0), UID(0), mRealtimeRequest, mClientCallback0);
+    EXPECT_EQ(mTranscoder->popEvent(), TestTranscoder::Start(CLIENT(0), SESSION(0)));
+
+    // Submit offline session to CLIENT(0), should not start.
+    mOfflineRequest.clientPid = PID(0);
+    mController->submit(CLIENT(1), SESSION(0), UID(0), mOfflineRequest, mClientCallback1);
+    EXPECT_EQ(mTranscoder->popEvent(), TestTranscoder::NoEvent);
+
+    // Move UID(1) to top.
+    mUidPolicy->setTop(UID(1));
+    EXPECT_EQ(mTranscoder->popEvent(), TestTranscoder::NoEvent);
+
+    // Submit real-time session to CLIENT(2) in different uid UID(1).
+    // Should pause previous session and start new session.
+    mRealtimeRequest.clientPid = PID(1);
+    mController->submit(CLIENT(2), SESSION(0), UID(1), mRealtimeRequest, mClientCallback2);
+    EXPECT_EQ(mTranscoder->popEvent(), TestTranscoder::Pause(CLIENT(0), SESSION(0)));
+    EXPECT_EQ(mTranscoder->popEvent(), TestTranscoder::Start(CLIENT(2), SESSION(0)));
+
+    // Test 0: Resource lost during throttling.
+    // Throttling starts, top session should pause.
+    mController->onThrottlingStarted();
+    EXPECT_EQ(mTranscoder->popEvent(), TestTranscoder::Pause(CLIENT(2), SESSION(0)));
+    // Signal resource lost, this should get ignored because the session is now paused.
+    mController->onResourceLost(CLIENT(2), SESSION(0));
+    EXPECT_EQ(mResourcePolicy->getPid(), kInvalidPid);
+    // Signal resource available, CLIENT(2) shouldn't resume.
+    mController->onResourceAvailable();
+    EXPECT_EQ(mTranscoder->popEvent(), TestTranscoder::NoEvent);
+    // Throttling ends, top session should resume.
+    mController->onThrottlingStopped();
+    EXPECT_EQ(mTranscoder->popEvent(), TestTranscoder::Resume(CLIENT(2), SESSION(0)));
+
+    // Test 1: Throttling during resource lost.
+    mController->onResourceLost(CLIENT(2), SESSION(0));
+    EXPECT_EQ(mResourcePolicy->getPid(), PID(1));
+    mController->onThrottlingStarted();
+    mController->onThrottlingStopped();
+    EXPECT_EQ(mTranscoder->popEvent(), TestTranscoder::NoEvent);
+    mController->onResourceAvailable();
+    EXPECT_EQ(mTranscoder->popEvent(), TestTranscoder::Resume(CLIENT(2), SESSION(0)));
+
+    // Test 2: Interleaving resource lost and throttling.
+    mController->onResourceLost(CLIENT(2), SESSION(0));
+    EXPECT_EQ(mResourcePolicy->getPid(), PID(1));
+    mController->onThrottlingStarted();
+    mController->onResourceAvailable();
+    EXPECT_EQ(mTranscoder->popEvent(), TestTranscoder::NoEvent);
+    mController->onThrottlingStopped();
+    EXPECT_EQ(mTranscoder->popEvent(), TestTranscoder::Resume(CLIENT(2), SESSION(0)));
+}
+
 }  // namespace android