[ICD] Update and Document  ICDManager interface (#33081)

* Update and Document  ICDManager interface

* Apply suggestions from code review

Co-authored-by: Boris Zbarsky <bzbarsky@apple.com>

* Update ICDManagementCluster test to cover off the Check-In message randomness

* Address comments and update comments

* restyle

---------

Co-authored-by: Boris Zbarsky <bzbarsky@apple.com>
diff --git a/examples/lit-icd-app/nrfconnect/main/AppTask.cpp b/examples/lit-icd-app/nrfconnect/main/AppTask.cpp
index d7d35bb..336024f 100644
--- a/examples/lit-icd-app/nrfconnect/main/AppTask.cpp
+++ b/examples/lit-icd-app/nrfconnect/main/AppTask.cpp
@@ -296,7 +296,8 @@
 
 void AppTask::IcdUatEventHandler(const AppEvent &)
 {
-    Server::GetInstance().GetICDManager().UpdateOperationState(ICDManager::OperationalState::ActiveMode);
+    // Temporarily claim network activity, until we implement a "user trigger" reason for ICD wakeups.
+    PlatformMgr().ScheduleWork([](intptr_t) { ICDNotifier::GetInstance().NotifyNetworkActivityNotification(); });
 }
 
 void AppTask::FunctionTimerTimeoutCallback(k_timer * timer)
diff --git a/examples/platform/silabs/BaseApplication.cpp b/examples/platform/silabs/BaseApplication.cpp
index a6986db..f02826f 100644
--- a/examples/platform/silabs/BaseApplication.cpp
+++ b/examples/platform/silabs/BaseApplication.cpp
@@ -515,9 +515,7 @@
                 SILABS_LOG("Network is already provisioned, Ble advertisement not enabled");
 #if CHIP_CONFIG_ENABLE_ICD_SERVER
                 // Temporarily claim network activity, until we implement a "user trigger" reason for ICD wakeups.
-                PlatformMgr().LockChipStack();
-                ICDNotifier::GetInstance().NotifyNetworkActivityNotification();
-                PlatformMgr().UnlockChipStack();
+                PlatformMgr().ScheduleWork([](intptr_t) { ICDNotifier::GetInstance().NotifyNetworkActivityNotification(); });
 #endif // CHIP_CONFIG_ENABLE_ICD_SERVER
             }
         }
diff --git a/src/app/icd/server/ICDManager.cpp b/src/app/icd/server/ICDManager.cpp
index 5f93946..8691a4d 100644
--- a/src/app/icd/server/ICDManager.cpp
+++ b/src/app/icd/server/ICDManager.cpp
@@ -378,12 +378,13 @@
     if (ICDConfigurationData::GetInstance().GetICDMode() != tempMode)
     {
         ICDConfigurationData::GetInstance().SetICDMode(tempMode);
-        postObserverEvent(ObserverEventType::ICDModeChange);
 
         // Can't use attribute accessors/Attributes::OperatingMode::Set in unit tests
 #if !CONFIG_BUILD_FOR_HOST_UNIT_TEST
         Attributes::OperatingMode::Set(kRootEndpointId, static_cast<OperatingModeEnum>(tempMode));
 #endif
+
+        postObserverEvent(ObserverEventType::ICDModeChange);
     }
 
     // When in SIT mode, the slow poll interval SHOULDN'T be greater than the SIT mode polling threshold, per spec.
@@ -433,6 +434,8 @@
         {
             ChipLogError(AppServer, "Failed to set Slow Polling Interval: err %" CHIP_ERROR_FORMAT, err.Format());
         }
+
+        postObserverEvent(ObserverEventType::EnterIdleMode);
     }
     else if (state == OperationalState::ActiveMode)
     {
@@ -447,7 +450,7 @@
 
             if (activeModeDuration == kZero && !mKeepActiveFlags.HasAny())
             {
-                // A Network Activity triggered the active mode and activeModeDuration is 0.
+                // Network Activity triggered the active mode and activeModeDuration is 0.
                 // Stay active for at least Active Mode Threshold.
                 activeModeDuration = ICDConfigurationData::GetInstance().GetActiveModeThreshold();
             }
@@ -455,9 +458,14 @@
             DeviceLayer::SystemLayer().StartTimer(activeModeDuration, OnActiveModeDone, this);
 
             Milliseconds32 activeModeJitterInterval = Milliseconds32(ICD_ACTIVE_TIME_JITTER_MS);
+            // TODO(#33074): Edge case when we transition to IdleMode with this condition being true
+            // (activeModeDuration == kZero && !mKeepActiveFlags.HasAny())
             activeModeJitterInterval =
                 (activeModeDuration >= activeModeJitterInterval) ? activeModeDuration - activeModeJitterInterval : kZero;
 
+            // Reset this flag when we enter ActiveMode to avoid having a feedback loop that keeps us indefinitly in
+            // ActiveMode.
+            mTransitionToIdleCalled = false;
             DeviceLayer::SystemLayer().StartTimer(activeModeJitterInterval, OnTransitionToIdle, this);
 
             CHIP_ERROR err =
@@ -504,10 +512,6 @@
 {
     ICDManager * pICDManager = reinterpret_cast<ICDManager *>(appState);
     pICDManager->UpdateOperationState(OperationalState::ActiveMode);
-
-    // We only reset this flag when idle mode is complete to avoid re-triggering the check when an event brings us back to active,
-    // which could cause a loop.
-    pICDManager->mTransitionToIdleCalled = false;
 }
 
 void ICDManager::OnActiveModeDone(System::Layer * aLayer, void * appState)
@@ -532,10 +536,10 @@
 }
 
 /* ICDListener functions. */
+
 void ICDManager::OnKeepActiveRequest(KeepActiveFlags request)
 {
     assertChipStackLockedByCurrentThread();
-
     VerifyOrReturn(request < KeepActiveFlagsValues::kInvalidFlag);
 
     if (request.Has(KeepActiveFlag::kExchangeContextOpen))
@@ -560,7 +564,6 @@
 void ICDManager::OnActiveRequestWithdrawal(KeepActiveFlags request)
 {
     assertChipStackLockedByCurrentThread();
-
     VerifyOrReturn(request < KeepActiveFlagsValues::kInvalidFlag);
 
     if (request.Has(KeepActiveFlag::kExchangeContextOpen))
@@ -697,6 +700,10 @@
             obs->mObserver->OnEnterActiveMode();
             return Loop::Continue;
         }
+        case ObserverEventType::EnterIdleMode: {
+            obs->mObserver->OnEnterIdleMode();
+            return Loop::Continue;
+        }
         case ObserverEventType::TransitionToIdle: {
             obs->mObserver->OnTransitionToIdle();
             return Loop::Continue;
diff --git a/src/app/icd/server/ICDManager.h b/src/app/icd/server/ICDManager.h
index 69dfdbf..bf6e439 100644
--- a/src/app/icd/server/ICDManager.h
+++ b/src/app/icd/server/ICDManager.h
@@ -57,7 +57,9 @@
 class ICDManager : public ICDListener, public TestEventTriggerHandler
 {
 public:
-    // This structure is used for the creation an ObjectPool of ICDStateObserver pointers
+    /**
+     * @brief This structure is used for the creation an ObjectPool of ICDStateObserver pointers
+     */
     struct ObserverPointer
     {
         ObserverPointer(ICDStateObserver * obs) : mObserver(obs) {}
@@ -71,11 +73,31 @@
         ActiveMode,
     };
 
-    // This enum class represents to all ICDStateObserver callbacks available from the
-    // mStateObserverPool for the ICDManager.
+    /**
+     * @brief This enum class represents to all ICDStateObserver callbacks available from the
+     *        mStateObserverPool for the ICDManager.
+     *
+     *        EnterActiveMode, TransitionToIdle and EnterIdleMode will always be called as a trio in the same order.
+     *        Each event will only be called once per cycle.
+     *        EnterActiveMode will always be called first, when the ICD has transitioned to ActiveMode.
+     *        TransitionToIdle will always be second. This event will only be called the first time there is
+     *        `ICD_ACTIVE_TIME_JITTER_MS` remaining to the ActiveMode timer.
+     *         When this event is called, the ICD is still in ActiveMode.
+     *        If the ActiveMode timer is increased due to the TransitionToIdle event, the event will not be called a second time in
+     *        a given cycle.
+     *        OnEnterIdleMode will always the third when the ICD has transitioned to IdleMode.
+     *
+     *        The ICDModeChange event can occur independently from the EnterActiveMode, TransitionToIdle and EnterIdleMode.
+     *        It will typpically hapen at the ICDManager init when a client is already registered with the ICD before the
+     *        OnEnterIdleMode event or when a client send a register command after the OnEnterActiveMode event. Nothing prevents the
+     *        ICDModeChange event to happen multiple times per cycle or while the ICD is in IdleMode.
+     *
+     *        See src/app/icd/server/ICDStateObserver.h for more information on the APIs each event triggers
+     */
     enum class ObserverEventType : uint8_t
     {
         EnterActiveMode,
+        EnterIdleMode,
         TransitionToIdle,
         ICDModeChange,
     };
@@ -85,22 +107,31 @@
      *        This type can be used to implement specific verifiers that can be used in the CheckInMessagesWouldBeSent function.
      *        The goal is to avoid having multiple functions that implement the iterator loop with only the check changing.
      *
-     * @return true if at least one Check-In message would be sent
-     *         false No Check-In messages would be sent
+     * @return true: if at least one Check-In message would be sent
+     *         false: No Check-In messages would be sent
      */
-
     using ShouldCheckInMsgsBeSentFunction = bool(FabricIndex aFabricIndex, NodeId subjectID);
 
-    ICDManager() {}
+    ICDManager()  = default;
+    ~ICDManager() = default;
+
     void Init(PersistentStorageDelegate * storage, FabricTable * fabricTable, Crypto::SymmetricKeystore * symmetricKeyStore,
-              Messaging::ExchangeManager * exchangeManager, SubscriptionsInfoProvider * manager);
+              Messaging::ExchangeManager * exchangeManager, SubscriptionsInfoProvider * subInfoProvider);
     void Shutdown();
-    void UpdateICDMode();
-    void UpdateOperationState(OperationalState state);
-    void SetKeepActiveModeRequirements(KeepActiveFlags flag, bool state);
-    bool IsKeepActive() { return mKeepActiveFlags.HasAny(); }
+
+    /**
+     * @brief SupportsFeature verifies if a given FeatureMap bit is enabled
+     *
+     * @param[in] feature FeatureMap bit to verify
+     *
+     * @return true: if the FeatureMap bit is enabled in the ICDM cluster attribute.
+     *         false: if the FeatureMap bit is not enabled in the ICDM cluster attribute.
+     *                if we failed to read the FeatureMap attribute.
+     */
     bool SupportsFeature(Clusters::IcdManagement::Feature feature);
+
     ICDConfigurationData::ICDMode GetICDMode() { return ICDConfigurationData::GetInstance().GetICDMode(); };
+
     /**
      * @brief Adds the referenced observer in parameters to the mStateObserverPool
      * A maximum of CHIP_CONFIG_ICD_OBSERVERS_POOL_SIZE observers can be concurrently registered
@@ -111,20 +142,14 @@
 
     /**
      * @brief Remove the referenced observer in parameters from the mStateObserverPool
+     *        If the observer is not present in the object pool, we do nothing
      */
     void ReleaseObserver(ICDStateObserver * observer);
 
     /**
-     * @brief Associates the ObserverEventType parameters to the correct
-     *  ICDStateObservers function and calls it for all observers in the mStateObserverPool
-     */
-    void postObserverEvent(ObserverEventType event);
-    OperationalState GetOperationalState() { return mOperationalState; }
-
-    /**
      * @brief Ensures that the remaining Active Mode duration is at least the smaller of 30000 milliseconds and stayActiveDuration.
      *
-     * @param stayActiveDuration The duration (in milliseconds) requested by the client to stay in Active Mode
+     * @param[in] stayActiveDuration The duration (in milliseconds) requested by the client to stay in Active Mode
      * @return The duration (in milliseconds) the device will stay in Active Mode
      */
     uint32_t StayActiveRequest(uint32_t stayActiveDuration);
@@ -132,15 +157,14 @@
     /**
      * @brief TestEventTriggerHandler for the ICD feature set
      *
-     * @param eventTrigger Event trigger to handle.
+     * @param[in] eventTrigger Event trigger to handle.
+     *
      * @return CHIP_ERROR CHIP_NO_ERROR - No erros during the processing
      *                    CHIP_ERROR_INVALID_ARGUMENT - eventTrigger isn't a valid value
      */
     CHIP_ERROR HandleEventTrigger(uint64_t eventTrigger) override;
 
 #if CHIP_CONFIG_ENABLE_ICD_CIP
-    void SendCheckInMsgs();
-
     /**
      * @brief Trigger the ICDManager to send Check-In message if necessary
      *
@@ -160,47 +184,108 @@
 
 #if CONFIG_BUILD_FOR_HOST_UNIT_TEST
     void SetTestFeatureMapValue(uint32_t featureMap) { mFeatureMap = featureMap; };
-#if !CHIP_CONFIG_SUBSCRIPTION_TIMEOUT_RESUMPTION && CHIP_CONFIG_PERSIST_SUBSCRIPTIONS
+#if CHIP_CONFIG_PERSIST_SUBSCRIPTIONS && !CHIP_CONFIG_SUBSCRIPTION_TIMEOUT_RESUMPTION
     bool GetIsBootUpResumeSubscriptionExecuted() { return mIsBootUpResumeSubscriptionExecuted; };
 #endif // !CHIP_CONFIG_SUBSCRIPTION_TIMEOUT_RESUMPTION && CHIP_CONFIG_PERSIST_SUBSCRIPTIONS
 #endif
 
     // Implementation of ICDListener functions.
     // Callers must origin from the chip task context or hold the ChipStack lock.
+
     void OnNetworkActivity() override;
     void OnKeepActiveRequest(KeepActiveFlags request) override;
     void OnActiveRequestWithdrawal(KeepActiveFlags request) override;
     void OnICDManagementServerEvent(ICDManagementEvents event) override;
     void OnSubscriptionReport() override;
 
-protected:
+private:
     friend class TestICDManager;
+    /**
+     * @brief UpdateICDMode evaluates in which mode the ICD can be in; SIT or LIT mode.
+     *        If the current operating mode does not match the evaluated operating mode, function updates the ICDMode and triggers
+     *        all necessary operations.
+     *        For a SIT ICD, this function does nothing.
+     *        For a LIT ICD, the function checks if the ICD has a registration in the ICDMonitoringTable to determine which ICDMode
+     *        the ICD must be in.
+     */
+    void UpdateICDMode();
 
     /**
-     * @brief Hepler function that extends the Active Mode duration as well as the Active Mode Jitter timer for the transition to
-     * iddle mode.
+     * @brief UpdateOperationState updates the OperationState of the ICD to the requested one.
+     *        IdleMode -> IdleMode     : No actions are necessary, do nothing.
+     *        IdleMode -> ActiveMode   : Transition the device to ActiveMode, start the  ActiveMode timer and trigger all necessary
+     *                                   operations. These operations could be : Send Check-In messages
+     *                                                                           Send subscription reports
+     *                                                                           Process user actions
+     *        ActiveMode -> ActiveMode : Increase remaining ActiveMode timer to one ActiveModeThreshold.
+     *                                   If ActiveModeThreshold is 0, do nothing.
+     *        ActiveMode -> IdleMode   : Transition ICD to IdleMode and start the IdleMode timer.
+     *
+     * @param state requested OperationalState for the ICD to transition to
+     */
+    void UpdateOperationState(OperationalState state);
+
+    /**
+     * @brief Set or Remove a keep ActiveMode requirement for the given flag
+     *        If state is true and the ICD is in IdleMode, transition the ICD to ActiveMode
+     *        If state is false and the ICD is in ActiveMode, check whether we can transition the ICD to IdleMode.
+     *        If we can, transition the ICD to IdleMode.
+     *
+     * @param flag KeepActiveFlag to remove or add
+     * @param state true: adding a flag requirement
+     *              false: removing a flag requirement
+     */
+    void SetKeepActiveModeRequirements(KeepActiveFlags flag, bool state);
+
+    /**
+     * @brief Associates the ObserverEventType parameters to the correct
+     *  ICDStateObservers function and calls it for all observers in the mStateObserverPool
+     */
+    void postObserverEvent(ObserverEventType event);
+
+    /**
+     * @brief Hepler function that extends the ActiveMode timer as well as the Active Mode Jitter timer for the transition to
+     *        idle mode event.
      */
     void ExtendActiveMode(System::Clock::Milliseconds16 extendDuration);
 
+    /**
+     * @brief Timer callback function for when the IdleMode timer expires
+     *
+     * @param appState pointer to the ICDManager
+     */
     static void OnIdleModeDone(System::Layer * aLayer, void * appState);
+
+    /**
+     * @brief Timer callback function for when the ActiveMode timer expires
+     *
+     * @param appState pointer to the ICDManager
+     */
     static void OnActiveModeDone(System::Layer * aLayer, void * appState);
 
     /**
-     * @brief Callback function called shortly before the device enters idle mode to allow checks to be made. This is currently only
-     * called once to prevent entering in a loop if some events re-trigger this check (for instance if a check for subscription
-     * before entering idle mode leads to emiting a report, we will re-enter UpdateOperationState and check again for subscription,
-     * etc.)
+     * @brief Timer Callback function called shortly before the device enters idle mode to allow checks to be made.
+     *        This is currently only called once to prevent entering in a loop if some events re-trigger this check (for instance if
+     *        a check for subscriptions before entering idle mode leads to emiting a report, we will re-enter UpdateOperationState
+     *        and check again for subscription, etc.)
+     *
+     * @param appState pointer to the ICDManager
      */
     static void OnTransitionToIdle(System::Layer * aLayer, void * appState);
 
 #if CHIP_CONFIG_ENABLE_ICD_CIP
-    uint8_t mCheckInRequestCount = 0;
-#endif // CHIP_CONFIG_ENABLE_ICD_CIP
+    /**
+     * @brief Function triggers all necessary Check-In messages to be sent.
+     *
+     * @note For each ICDMonitoring entry, we check if should send a Check-In message with
+     *       ShouldCheckInMsgsBeSentAtActiveModeFunction. If we should, we allocate an ICDCheckInSender which tries to send a
+     *       Check-In message to the registered client.
+     */
+    void SendCheckInMsgs();
 
-    uint8_t mOpenExchangeContextCount = 0;
-
-private:
-#if CHIP_CONFIG_ENABLE_ICD_CIP
+    /**
+     * @brief See function implementation in .cpp for details on this function.
+     */
     bool ShouldCheckInMsgsBeSentAtActiveModeFunction(FabricIndex aFabricIndex, NodeId subjectID);
 
     /**
@@ -221,11 +306,15 @@
     OperationalState mOperationalState = OperationalState::ActiveMode;
     bool mTransitionToIdleCalled       = false;
     ObjectPool<ObserverPointer, CHIP_CONFIG_ICD_OBSERVERS_POOL_SIZE> mStateObserverPool;
+    uint8_t mOpenExchangeContextCount = 0;
 
 #if CHIP_CONFIG_ENABLE_ICD_CIP
+    uint8_t mCheckInRequestCount = 0;
+
 #if !CHIP_CONFIG_SUBSCRIPTION_TIMEOUT_RESUMPTION && CHIP_CONFIG_PERSIST_SUBSCRIPTIONS
     bool mIsBootUpResumeSubscriptionExecuted = false;
 #endif // !CHIP_CONFIG_SUBSCRIPTION_TIMEOUT_RESUMPTION && CHIP_CONFIG_PERSIST_SUBSCRIPTIONS
+
     PersistentStorageDelegate * mStorage           = nullptr;
     FabricTable * mFabricTable                     = nullptr;
     Messaging::ExchangeManager * mExchangeManager  = nullptr;
diff --git a/src/app/icd/server/ICDStateObserver.h b/src/app/icd/server/ICDStateObserver.h
index 996dad0..34fc309 100644
--- a/src/app/icd/server/ICDStateObserver.h
+++ b/src/app/icd/server/ICDStateObserver.h
@@ -27,13 +27,43 @@
 namespace chip {
 namespace app {
 
+/**
+ * @brief Public API used by the ICDManager to expose when different events occur.
+ *        ICDManager::RegisterObserver can be used to register as an Observer to be notified when these events occur.
+ *        These functions are called synchronously.
+ */
 class ICDStateObserver
 {
 public:
     virtual ~ICDStateObserver() {}
-    virtual void OnEnterActiveMode()  = 0;
+
+    /**
+     * @brief API called when the ICD enters ActiveMode. API isn't called if we need to extend the remaining active mode timer
+     * duration. API is called after the ICDManager has finished executing its internal actions.
+     */
+    virtual void OnEnterActiveMode() = 0;
+
+    /**
+     * @brief API called when the ICD enters IdleMode.
+     *        API is called after the ICDManager has finished executing its internal actions.
+     */
+    virtual void OnEnterIdleMode() = 0;
+
+    /**
+     * @brief API is called when the ICD is about to enter IdleMode. API is called when there is `ICD_ACTIVE_TIME_JITTER_MS` of time
+     *        remaining to the active mode timer.
+     *        This API is only called once per transition from ActiveMode to IdleMode.
+     *        If OnTransitionToIdle triggers the active mode timer to increase, the next time we are about to enter IdleMode,
+     *        this API will not be called.
+     */
     virtual void OnTransitionToIdle() = 0;
-    virtual void OnICDModeChange()    = 0;
+
+    /**
+     * @brief API is called when the ICD changes operating mode. This API is only called if the ICD changes state, not when it
+     *        remains in the same state.
+     *        API is called after the ICDManager has finished executing its internal actions.
+     */
+    virtual void OnICDModeChange() = 0;
 };
 
 } // namespace app
diff --git a/src/app/reporting/ReportSchedulerImpl.h b/src/app/reporting/ReportSchedulerImpl.h
index 38fcc2a..f7889a5 100644
--- a/src/app/reporting/ReportSchedulerImpl.h
+++ b/src/app/reporting/ReportSchedulerImpl.h
@@ -89,6 +89,12 @@
      */
     void OnICDModeChange() override{};
 
+    /**
+     * @brief This implementation does not attempt any synchronization on this ICD event, therefore no action is needed on
+     * ICDEnterIdleMode()
+     */
+    void OnEnterIdleMode() override{};
+
     // ReadHandlerObserver
 
     /**
diff --git a/src/app/server/Dnssd.h b/src/app/server/Dnssd.h
index e669f4d..105318c 100644
--- a/src/app/server/Dnssd.h
+++ b/src/app/server/Dnssd.h
@@ -126,12 +126,26 @@
      */
     CHIP_ERROR SetEphemeralDiscriminator(Optional<uint16_t> discriminator);
 
-    // ICDStateObserver
-    // No action is needed by the DnssdServer on active or idle state entries
-    void OnEnterActiveMode() override{};
-    void OnTransitionToIdle() override{};
+    /**
+     * @brief When the ICD changes operating mode, the dnssd server needs to restart its DNS-SD advertising to update the TXT keys.
+     */
     void OnICDModeChange() override;
 
+    /**
+     * @brief dnssd server has no action to do on this ICD event. Do nothing.
+     */
+    void OnEnterActiveMode() override{};
+
+    /**
+     * @brief dnssd server has no action to do on this ICD event. Do nothing.
+     */
+    void OnTransitionToIdle() override{};
+
+    /**
+     * @brief dnssd server has no action to do on this ICD event. Do nothing.
+     */
+    void OnEnterIdleMode() override{};
+
 private:
     /// Overloaded utility method for commissioner and commissionable advertisement
     /// This method is used for both commissioner discovery and commissionable node discovery since
diff --git a/src/app/tests/TestICDManager.cpp b/src/app/tests/TestICDManager.cpp
index fd93a14..6e814eb 100644
--- a/src/app/tests/TestICDManager.cpp
+++ b/src/app/tests/TestICDManager.cpp
@@ -77,9 +77,27 @@
 class TestICDStateObserver : public app::ICDStateObserver
 {
 public:
-    void OnEnterActiveMode() {}
-    void OnTransitionToIdle() {}
-    void OnICDModeChange() {}
+    void OnEnterActiveMode() { mOnEnterActiveModeCalled = true; }
+    void OnEnterIdleMode() { mOnEnterIdleModeCalled = true; }
+    void OnTransitionToIdle() { mOnTransitionToIdleCalled = true; }
+    void OnICDModeChange() { mOnICDModeChangeCalled = true; }
+
+    void ResetOnEnterActiveMode() { mOnEnterActiveModeCalled = false; }
+    void ResetOnEnterIdleMode() { mOnEnterIdleModeCalled = false; }
+    void ResetOnTransitionToIdle() { mOnTransitionToIdleCalled = false; }
+    void ResetOnICDModeChange() { mOnICDModeChangeCalled = false; }
+    void ResetAll()
+    {
+        ResetOnEnterActiveMode();
+        ResetOnEnterIdleMode();
+        ResetOnTransitionToIdle();
+        ResetOnICDModeChange();
+    }
+
+    bool mOnEnterActiveModeCalled  = false;
+    bool mOnEnterIdleModeCalled    = false;
+    bool mOnICDModeChangeCalled    = false;
+    bool mOnTransitionToIdleCalled = false;
 };
 
 class TestSubscriptionsInfoProvider : public SubscriptionsInfoProvider
@@ -123,10 +141,10 @@
     // Performs setup for each individual test in the test suite
     CHIP_ERROR SetUp() override
     {
-
         ReturnErrorOnFailure(chip::Test::AppContext::SetUp());
-        mICDManager.Init(&testStorage, &GetFabricTable(), &mKeystore, &GetExchangeManager(), &mSubInfoProvider);
+        mICDStateObserver.ResetAll();
         mICDManager.RegisterObserver(&mICDStateObserver);
+        mICDManager.Init(&testStorage, &GetFabricTable(), &mKeystore, &GetExchangeManager(), &mSubInfoProvider);
         return CHIP_NO_ERROR;
     }
 
@@ -196,7 +214,7 @@
 
     /**
      * @brief Test verifies that the ICDManager starts its timers correctly based on if it will have any messages to send
-     *        when the IdleModeDuration expires
+     *        when the IdleMode timer expires
      */
     static void TestICDModeDurationsWith0ActiveModeDurationWithoutActiveSub(nlTestSuite * aSuite, void * aContext)
     {
@@ -254,7 +272,7 @@
         AdvanceClockAndRunEventLoop(ctx, icdConfigData.GetActiveModeThreshold() + 1_ms16);
         NL_TEST_ASSERT(aSuite, ctx->mICDManager.mOperationalState == ICDManager::OperationalState::IdleMode);
 
-        // Expire IdleModeDuration - Device should be in ActiveMode since it has an ICDM registration
+        // Expire IdleMode timer - Device should be in ActiveMode since it has an ICDM registration
         AdvanceClockAndRunEventLoop(ctx, icdConfigData.GetIdleModeDuration() + 1_s);
         NL_TEST_ASSERT(aSuite, ctx->mICDManager.mOperationalState == ICDManager::OperationalState::ActiveMode);
 
@@ -276,7 +294,7 @@
 
     /**
      * @brief Test verifies that the ICDManager remains in IdleMode since it will not have any messages to send
-     *        when the IdleModeDuration expires
+     *        when the IdleMode timer expires
      */
     static void TestICDModeDurationsWith0ActiveModeDurationWithActiveSub(nlTestSuite * aSuite, void * aContext)
     {
@@ -334,7 +352,7 @@
         AdvanceClockAndRunEventLoop(ctx, icdConfigData.GetActiveModeThreshold() + 1_ms16);
         NL_TEST_ASSERT(aSuite, ctx->mICDManager.mOperationalState == ICDManager::OperationalState::IdleMode);
 
-        // Expire IdleModeDuration - Device stay in IdleMode since it has an active subscription for the ICDM entry
+        // Expire IdleMode timer - Device stay in IdleMode since it has an active subscription for the ICDM entry
         AdvanceClockAndRunEventLoop(ctx, icdConfigData.GetIdleModeDuration() + 1_s);
         NL_TEST_ASSERT(aSuite, ctx->mICDManager.mOperationalState == ICDManager::OperationalState::IdleMode);
 
@@ -414,9 +432,9 @@
         NL_TEST_ASSERT(aSuite, ctx->mICDManager.mOperationalState == ICDManager::OperationalState::IdleMode);
     }
 
-    /*
-     * Test that verifies that the ICDManager is the correct operating mode based on entries
-     * in the ICDMonitoringTable
+    /**
+     * @brief Test that verifies that the ICDManager is in the correct operating mode based on entries
+     *        in the ICDMonitoringTable
      */
     static void TestICDMRegisterUnregisterEvents(nlTestSuite * aSuite, void * aContext)
     {
@@ -554,7 +572,9 @@
         NL_TEST_ASSERT(aSuite, ctx->mICDManager.mOperationalState == ICDManager::OperationalState::IdleMode);
     }
 
-    /* Test that verifies the logic of the ICDManager when it receives a StayActiveRequest*/
+    /**
+     * @brief Test verifies the logic of the ICDManager when it receives a StayActiveRequest
+     */
     static void TestICDMStayActive(nlTestSuite * aSuite, void * aContext)
     {
         TestContext * ctx                    = static_cast<TestContext *>(aContext);
@@ -568,7 +588,7 @@
         notifier.NotifySubscriptionReport();
         NL_TEST_ASSERT(aSuite, ctx->mICDManager.mOperationalState == ICDManager::OperationalState::ActiveMode);
 
-        // Advance time by the ActiveModeDuration - 1
+        // Advance time just before ActiveMode timer expires
         AdvanceClockAndRunEventLoop(ctx, icdConfigData.GetActiveModeDuration() - 1_ms32);
         // Confirm ICD manager is in active mode
         NL_TEST_ASSERT(aSuite, ctx->mICDManager.mOperationalState == ICDManager::OperationalState::ActiveMode);
@@ -580,7 +600,7 @@
         NL_TEST_ASSERT(aSuite, stayActivePromisedMs == stayActiveRequestedMs);
 
         // Advance time by the duration of the stay stayActiveRequestedMs - 1 ms
-        AdvanceClockAndRunEventLoop(ctx, System::Clock::Milliseconds32(stayActiveRequestedMs) - 1_ms32);
+        AdvanceClockAndRunEventLoop(ctx, Milliseconds32(stayActiveRequestedMs) - 1_ms32);
         // Confirm ICD manager is in active mode
         NL_TEST_ASSERT(aSuite, ctx->mICDManager.mOperationalState == ICDManager::OperationalState::ActiveMode);
 
@@ -601,7 +621,7 @@
         NL_TEST_ASSERT(aSuite, stayActivePromisedMs == 30000);
 
         // Advance time by the duration of the max stay active duration - 1 ms
-        AdvanceClockAndRunEventLoop(ctx, System::Clock::Milliseconds32(30000) - 1_ms32);
+        AdvanceClockAndRunEventLoop(ctx, Milliseconds32(30000) - 1_ms32);
         NL_TEST_ASSERT(aSuite, ctx->mICDManager.mOperationalState == ICDManager::OperationalState::ActiveMode);
 
         // Advance time by 1ms and Confirm ICD manager is in idle mode
@@ -621,7 +641,7 @@
         NL_TEST_ASSERT(aSuite, stayActivePromisedMs == 30000);
 
         // Advance time by the duration of the stay active request - 20000 ms
-        AdvanceClockAndRunEventLoop(ctx, System::Clock::Milliseconds32(stayActiveRequestedMs) - 20000_ms32);
+        AdvanceClockAndRunEventLoop(ctx, Milliseconds32(stayActiveRequestedMs) - 20000_ms32);
         // Confirm ICD manager is in active mode, we should have 20000 seconds left at that point
         NL_TEST_ASSERT(aSuite, ctx->mICDManager.mOperationalState == ICDManager::OperationalState::ActiveMode);
 
@@ -721,6 +741,329 @@
         ctx->mICDManager.HandleEventTrigger(static_cast<uint64_t>(ICDTestEventTriggerEvent::kRemoveActiveModeReq));
         NL_TEST_ASSERT(aSuite, ctx->mICDManager.mOperationalState == ICDManager::OperationalState::IdleMode);
     }
+
+    /**
+     * @brief Test verifies when OnEnterIdleMode is called during normal operations.
+     *        Without the ActiveMode timer being extended
+     */
+    static void TestICDStateObserverOnEnterIdleModeActiveModeDuration(nlTestSuite * aSuite, void * aContext)
+    {
+        TestContext * ctx = static_cast<TestContext *>(aContext);
+
+        // Verify that ICDManager starts in IdleMode and calls OnEnterIdleMode
+        NL_TEST_ASSERT(aSuite, ctx->mICDStateObserver.mOnEnterIdleModeCalled);
+        ctx->mICDStateObserver.ResetOnEnterIdleMode();
+
+        // Advance clock just before IdleMode timer expires
+        AdvanceClockAndRunEventLoop(ctx, ICDConfigurationData::GetInstance().GetIdleModeDuration() - 1_s);
+        NL_TEST_ASSERT(aSuite, !ctx->mICDStateObserver.mOnEnterIdleModeCalled);
+
+        // Expire IdleModeInterval
+        AdvanceClockAndRunEventLoop(ctx, 1_s);
+        NL_TEST_ASSERT(aSuite, !ctx->mICDStateObserver.mOnEnterIdleModeCalled);
+
+        // Advance clock Just before ActiveMode timer expires
+        AdvanceClockAndRunEventLoop(ctx, ICDConfigurationData::GetInstance().GetActiveModeDuration() - 1_ms32);
+        NL_TEST_ASSERT(aSuite, !ctx->mICDStateObserver.mOnEnterIdleModeCalled);
+
+        // Expire ActiveMode timer
+        AdvanceClockAndRunEventLoop(ctx, 1_ms32);
+        NL_TEST_ASSERT(aSuite, ctx->mICDStateObserver.mOnEnterIdleModeCalled);
+    }
+
+    /**
+     * @brief Test verifies when OnEnterIdleMode is called with the ActiveMode timer gets extended
+     */
+    static void TestICDStateObserverOnEnterIdleModeActiveModeThreshold(nlTestSuite * aSuite, void * aContext)
+    {
+        TestContext * ctx = static_cast<TestContext *>(aContext);
+
+        // Verify that ICDManager starts in IdleMode and calls OnEnterIdleMode
+        NL_TEST_ASSERT(aSuite, ctx->mICDStateObserver.mOnEnterIdleModeCalled);
+        ctx->mICDStateObserver.ResetOnEnterIdleMode();
+
+        // Advance clock just before the IdleMode timer expires
+        AdvanceClockAndRunEventLoop(ctx, ICDConfigurationData::GetInstance().GetIdleModeDuration() - 1_s);
+        NL_TEST_ASSERT(aSuite, !ctx->mICDStateObserver.mOnEnterIdleModeCalled);
+
+        // Expire IdleMode timer
+        AdvanceClockAndRunEventLoop(ctx, 1_s);
+        NL_TEST_ASSERT(aSuite, !ctx->mICDStateObserver.mOnEnterIdleModeCalled);
+
+        // Advance clock Just before ActiveMode timer expires
+        AdvanceClockAndRunEventLoop(ctx, ICDConfigurationData::GetInstance().GetActiveModeDuration() - 1_ms32);
+        NL_TEST_ASSERT(aSuite, !ctx->mICDStateObserver.mOnEnterIdleModeCalled);
+
+        // Increase ActiveMode timer by one ActiveModeThreshold
+        ICDNotifier::GetInstance().NotifyNetworkActivityNotification();
+        NL_TEST_ASSERT(aSuite, !ctx->mICDStateObserver.mOnEnterIdleModeCalled);
+
+        // Advance clock Just before ActiveMode timer expires
+        AdvanceClockAndRunEventLoop(ctx, ICDConfigurationData::GetInstance().GetActiveModeThreshold() - 1_ms32);
+        NL_TEST_ASSERT(aSuite, !ctx->mICDStateObserver.mOnEnterIdleModeCalled);
+
+        // Expire ActiveMode timer
+        AdvanceClockAndRunEventLoop(ctx, 1_ms32);
+        NL_TEST_ASSERT(aSuite, ctx->mICDStateObserver.mOnEnterIdleModeCalled);
+    }
+
+    static void TestICDStateObserverOnEnterActiveMode(nlTestSuite * aSuite, void * aContext)
+    {
+        TestContext * ctx = static_cast<TestContext *>(aContext);
+
+        // Verify OnEnterActiveMode wasn't called at Init
+        NL_TEST_ASSERT(aSuite, !(ctx->mICDStateObserver.mOnEnterActiveModeCalled));
+
+        // Advance clock just before IdleMode timer expires
+        AdvanceClockAndRunEventLoop(ctx, ICDConfigurationData::GetInstance().GetIdleModeDuration() - 1_s);
+        NL_TEST_ASSERT(aSuite, !(ctx->mICDStateObserver.mOnEnterActiveModeCalled));
+
+        // Expire IdleMode timer and check wether OnEnterActiveMode was called
+        AdvanceClockAndRunEventLoop(ctx, 1_s);
+        NL_TEST_ASSERT(aSuite, ctx->mICDStateObserver.mOnEnterActiveModeCalled);
+        ctx->mICDStateObserver.ResetOnEnterActiveMode();
+
+        // Advance clock just before the ActiveMode timer expires
+        AdvanceClockAndRunEventLoop(ctx, ICDConfigurationData::GetInstance().GetActiveModeDuration() - 1_ms32);
+
+        // Verify OnEnterActiveMde wasn't called
+        NL_TEST_ASSERT(aSuite, !(ctx->mICDStateObserver.mOnEnterActiveModeCalled));
+
+        // Increase ActiveMode timer by one ActiveModeThreshold
+        ICDNotifier::GetInstance().NotifyNetworkActivityNotification();
+
+        // Verify OnEnterActiveMde wasn't called
+        NL_TEST_ASSERT(aSuite, !(ctx->mICDStateObserver.mOnEnterActiveModeCalled));
+
+        // Advance clock just before ActiveMode timer expires
+        AdvanceClockAndRunEventLoop(ctx, ICDConfigurationData::GetInstance().GetActiveModeThreshold() - 1_ms32);
+
+        // Verify OnEnterActiveMde wasn't called
+        NL_TEST_ASSERT(aSuite, !(ctx->mICDStateObserver.mOnEnterActiveModeCalled));
+
+        // Expire ActiveMode timer
+        AdvanceClockAndRunEventLoop(ctx, 1_ms32);
+
+        // Verify OnEnterActiveMde wasn't called
+        NL_TEST_ASSERT(aSuite, !(ctx->mICDStateObserver.mOnEnterActiveModeCalled));
+
+        // Advance clock just before IdleMode timer expires
+        AdvanceClockAndRunEventLoop(ctx, ICDConfigurationData::GetInstance().GetIdleModeDuration() - 1_s);
+        NL_TEST_ASSERT(aSuite, !(ctx->mICDStateObserver.mOnEnterActiveModeCalled));
+
+        // Expire IdleMode timer and check OnEnterActiveMode was called
+        AdvanceClockAndRunEventLoop(ctx, 1_s);
+        NL_TEST_ASSERT(aSuite, ctx->mICDStateObserver.mOnEnterActiveModeCalled);
+    }
+
+    static void TestICDStateObserverOnICDModeChange(nlTestSuite * aSuite, void * aContext)
+    {
+        TestContext * ctx = static_cast<TestContext *>(aContext);
+        typedef ICDListener::ICDManagementEvents ICDMEvent;
+
+        // Since we don't have a registration, we stay in SIT mode. No changes
+        NL_TEST_ASSERT(aSuite, !(ctx->mICDStateObserver.mOnICDModeChangeCalled));
+
+        // Trigger register event to force ICDManager to re-evaluate OperatingMode
+        ICDNotifier::GetInstance().NotifyICDManagementEvent(ICDMEvent::kTableUpdated);
+
+        // Since we don't have a registration, we stay in SIT mode. No changes
+        NL_TEST_ASSERT(aSuite, !(ctx->mICDStateObserver.mOnICDModeChangeCalled));
+
+        // Add an entry to the ICDMonitoringTable
+        ICDMonitoringTable table(ctx->testStorage, kTestFabricIndex1, kMaxTestClients, &(ctx->mKeystore));
+
+        ICDMonitoringEntry entry(&(ctx->mKeystore));
+        entry.checkInNodeID    = kClientNodeId11;
+        entry.monitoredSubject = kClientNodeId11;
+        NL_TEST_ASSERT(aSuite, CHIP_NO_ERROR == entry.SetKey(ByteSpan(kKeyBuffer1a)));
+        NL_TEST_ASSERT(aSuite, CHIP_NO_ERROR == table.Set(0, entry));
+
+        // Trigger register event after first entry was added
+        ICDNotifier::GetInstance().NotifyICDManagementEvent(ICDMEvent::kTableUpdated);
+
+        // We have a registration. Transition to LIT mode
+        NL_TEST_ASSERT(aSuite, ctx->mICDStateObserver.mOnICDModeChangeCalled);
+        ctx->mICDStateObserver.ResetOnICDModeChange();
+
+        // Trigger register event to force ICDManager to re-evaluate OperatingMode
+        ICDNotifier::GetInstance().NotifyICDManagementEvent(ICDMEvent::kTableUpdated);
+
+        // We have a registration. We stay in LIT mode. No changes.
+        NL_TEST_ASSERT(aSuite, !(ctx->mICDStateObserver.mOnICDModeChangeCalled));
+
+        // Remove entry from the ICDMonitoringTable
+        NL_TEST_ASSERT(aSuite, CHIP_NO_ERROR == table.Remove(0));
+        ICDNotifier::GetInstance().NotifyICDManagementEvent(ICDMEvent::kTableUpdated);
+
+        // Since we don't have a registration anymore. Transition to SIT mode.
+        NL_TEST_ASSERT(aSuite, ctx->mICDStateObserver.mOnICDModeChangeCalled);
+        ctx->mICDStateObserver.ResetOnICDModeChange();
+    }
+
+    static void TestICDStateObserverOnICDModeChangeOnInit(nlTestSuite * aSuite, void * aContext)
+    {
+        TestContext * ctx = static_cast<TestContext *>(aContext);
+
+        ICDMonitoringTable table(ctx->testStorage, kTestFabricIndex1, kMaxTestClients, &(ctx->mKeystore));
+
+        // Since we don't have a registration, we stay in SIT mode. No changes
+        NL_TEST_ASSERT(aSuite, !(ctx->mICDStateObserver.mOnICDModeChangeCalled));
+
+        // Add an entry to the ICDMonitoringTable
+        ICDMonitoringEntry entry(&(ctx->mKeystore));
+        entry.checkInNodeID    = kClientNodeId11;
+        entry.monitoredSubject = kClientNodeId11;
+        NL_TEST_ASSERT(aSuite, CHIP_NO_ERROR == entry.SetKey(ByteSpan(kKeyBuffer1a)));
+        NL_TEST_ASSERT(aSuite, CHIP_NO_ERROR == table.Set(0, entry));
+
+        // Shut down and reinit ICDManager - We should go to LIT mode since we have a registration
+        ctx->mICDManager.Shutdown();
+        ctx->mICDManager.RegisterObserver(&(ctx->mICDStateObserver));
+        ctx->mICDManager.Init(&(ctx->testStorage), &(ctx->GetFabricTable()), &(ctx->mKeystore), &(ctx->GetExchangeManager()),
+                              &(ctx->mSubInfoProvider));
+
+        // We have a registration, transition to LIT mode
+        NL_TEST_ASSERT(aSuite, ctx->mICDStateObserver.mOnICDModeChangeCalled);
+        ctx->mICDStateObserver.ResetOnICDModeChange();
+
+        // Remove entry from the ICDMonitoringTable
+        NL_TEST_ASSERT(aSuite, CHIP_NO_ERROR == table.Remove(0));
+    }
+
+    /**
+     * @brief Test verifies the OnTransitionToIdleMode event when the ActiveModeDuration is greater than the
+     *        ICD_ACTIVE_TIME_JITTER_MS
+     */
+    static void TestICDStateObserverOnTransitionToIdleModeGreaterActiveModeDuration(nlTestSuite * aSuite, void * aContext)
+    {
+        TestContext * ctx = static_cast<TestContext *>(aContext);
+
+        // Set New durations for test case - ActiveModeDuration must be longuer than ICD_ACTIVE_TIME_JITTER_MS
+        Milliseconds32 oldActiveModeDuration = ICDConfigurationData::GetInstance().GetActiveModeDuration();
+        ICDConfigurationData::GetInstance().SetModeDurations(
+            MakeOptional<Milliseconds32>(Milliseconds32(200) + Milliseconds32(ICD_ACTIVE_TIME_JITTER_MS)), NullOptional);
+
+        // Advance clock just before IdleMode timer expires
+        AdvanceClockAndRunEventLoop(ctx, ICDConfigurationData::GetInstance().GetIdleModeDuration() - 1_s);
+        NL_TEST_ASSERT(aSuite, !(ctx->mICDStateObserver.mOnTransitionToIdleCalled));
+
+        // Expire IdleMode timer
+        AdvanceClockAndRunEventLoop(ctx, 1_s);
+        NL_TEST_ASSERT(aSuite, !(ctx->mICDStateObserver.mOnTransitionToIdleCalled));
+
+        // Advance time just before OnTransitionToIdleMode is called
+        AdvanceClockAndRunEventLoop(
+            ctx, ICDConfigurationData::GetInstance().GetActiveModeDuration() - Milliseconds32(ICD_ACTIVE_TIME_JITTER_MS) - 1_ms32);
+
+        // Check mOnTransitionToIdleCalled has not been called
+        NL_TEST_ASSERT(aSuite, !(ctx->mICDStateObserver.mOnTransitionToIdleCalled));
+
+        // Increase ActiveMode timer by one ActiveModeThreshold
+        ICDNotifier::GetInstance().NotifyNetworkActivityNotification();
+        NL_TEST_ASSERT(aSuite, !(ctx->mICDStateObserver.mOnTransitionToIdleCalled));
+
+        // Advance time just before OnTransitionToIdleMode is called
+        AdvanceClockAndRunEventLoop(
+            ctx, ICDConfigurationData::GetInstance().GetActiveModeThreshold() - Milliseconds32(ICD_ACTIVE_TIME_JITTER_MS) - 1_ms32);
+        NL_TEST_ASSERT(aSuite, !(ctx->mICDStateObserver.mOnTransitionToIdleCalled));
+
+        // Expire OnTransitionToIdleMode
+        AdvanceClockAndRunEventLoop(ctx, 1_ms32);
+        // Check mOnTransitionToIdleCalled has been called
+        NL_TEST_ASSERT(aSuite, ctx->mICDStateObserver.mOnTransitionToIdleCalled);
+        ctx->mICDStateObserver.ResetOnTransitionToIdle();
+
+        // Expire ActiveMode timer
+        AdvanceClockAndRunEventLoop(ctx, Milliseconds32(ICD_ACTIVE_TIME_JITTER_MS));
+        NL_TEST_ASSERT(aSuite, !(ctx->mICDStateObserver.mOnTransitionToIdleCalled));
+
+        // Reset Old durations
+        ICDConfigurationData::GetInstance().SetModeDurations(MakeOptional(oldActiveModeDuration), NullOptional);
+    }
+
+    /**
+     * @brief Test verifies the OnTransitionToIdleMode event when the ActiveModeDuration is equal to the
+     *        ICD_ACTIVE_TIME_JITTER_MS.
+     */
+    static void TestICDStateObserverOnTransitionToIdleModeEqualActiveModeDuration(nlTestSuite * aSuite, void * aContext)
+    {
+        TestContext * ctx = static_cast<TestContext *>(aContext);
+
+        // Set New durations for test case - ActiveModeDuration must be equal to ICD_ACTIVE_TIME_JITTER_MS
+        Milliseconds32 oldActiveModeDuration = ICDConfigurationData::GetInstance().GetActiveModeDuration();
+        ICDConfigurationData::GetInstance().SetModeDurations(
+            MakeOptional<Milliseconds32>(Milliseconds32(ICD_ACTIVE_TIME_JITTER_MS)), NullOptional);
+
+        // Advance clock just before IdleMode timer expires
+        AdvanceClockAndRunEventLoop(ctx, ICDConfigurationData::GetInstance().GetIdleModeDuration() - 1_s);
+        NL_TEST_ASSERT(aSuite, !(ctx->mICDStateObserver.mOnTransitionToIdleCalled));
+
+        // Expire IdleMode timer
+        AdvanceClockAndRunEventLoop(ctx, 1_s);
+        NL_TEST_ASSERT(aSuite, !(ctx->mICDStateObserver.mOnTransitionToIdleCalled));
+
+        // Expire OnTransitionToIdleMode
+        AdvanceClockAndRunEventLoop(ctx, 1_ms32);
+        NL_TEST_ASSERT(aSuite, ctx->mICDStateObserver.mOnTransitionToIdleCalled);
+
+        // Reset Old durations
+        ICDConfigurationData::GetInstance().SetModeDurations(MakeOptional(oldActiveModeDuration), NullOptional);
+    }
+
+    /**
+     * @brief Test verifies the OnTransitionToIdleMode event when the ActiveModeDuration is 0 and without an ActiveMode req
+     */
+    static void TestICDStateObserverOnTransitionToIdleMode0ActiveModeDurationWithoutReq(nlTestSuite * aSuite, void * aContext)
+    {
+        TestContext * ctx = static_cast<TestContext *>(aContext);
+
+        // Set New durations for test case - ActiveModeDuration equal 0
+        Milliseconds32 oldActiveModeDuration = ICDConfigurationData::GetInstance().GetActiveModeDuration();
+        ICDConfigurationData::GetInstance().SetModeDurations(MakeOptional<Milliseconds32>(0), NullOptional);
+
+        // Advance clock just before IdleMode timer expires
+        AdvanceClockAndRunEventLoop(ctx, ICDConfigurationData::GetInstance().GetIdleModeDuration() - 1_s);
+        NL_TEST_ASSERT(aSuite, !(ctx->mICDStateObserver.mOnTransitionToIdleCalled));
+
+        // Expire IdleMode timer
+        AdvanceClockAndRunEventLoop(ctx, 1_s);
+        NL_TEST_ASSERT(aSuite, !(ctx->mICDStateObserver.mOnTransitionToIdleCalled));
+
+        // Increase time by 1 - Should not trigger OnTransitionToIdle.
+        // Timer length is one ActiveModeThreshold
+        AdvanceClockAndRunEventLoop(ctx, 1_ms32);
+        NL_TEST_ASSERT(aSuite, !(ctx->mICDStateObserver.mOnTransitionToIdleCalled));
+
+        // Expire ActiveModeThreshold
+        AdvanceClockAndRunEventLoop(ctx, ICDConfigurationData::GetInstance().GetActiveModeThreshold());
+        NL_TEST_ASSERT(aSuite, ctx->mICDStateObserver.mOnTransitionToIdleCalled);
+
+        // Reset Old durations
+        ICDConfigurationData::GetInstance().SetModeDurations(MakeOptional(oldActiveModeDuration), NullOptional);
+    }
+
+    /**
+     * @brief Test verifies the OnTransitionToIdleMode event when the ActiveModeDuration is 0 with an ActiveMode req
+     */
+    static void TestICDStateObserverOnTransitionToIdleMode0ActiveModeDurationWittReq(nlTestSuite * aSuite, void * aContext)
+    {
+        TestContext * ctx = static_cast<TestContext *>(aContext);
+
+        // Set New durations for test case - ActiveModeDuration equal 0
+        Milliseconds32 oldActiveModeDuration = ICDConfigurationData::GetInstance().GetActiveModeDuration();
+        ICDConfigurationData::GetInstance().SetModeDurations(MakeOptional<Milliseconds32>(0), NullOptional);
+
+        // Add ActiveMode req for the Test event trigger event
+        ctx->mICDManager.HandleEventTrigger(static_cast<uint64_t>(ICDTestEventTriggerEvent::kAddActiveModeReq));
+
+        // Expire IdleMode timer
+        AdvanceClockAndRunEventLoop(ctx, ICDConfigurationData::GetInstance().GetIdleModeDuration());
+        NL_TEST_ASSERT(aSuite, !(ctx->mICDStateObserver.mOnTransitionToIdleCalled));
+
+        // Reset Old durations
+        ICDConfigurationData::GetInstance().SetModeDurations(MakeOptional(oldActiveModeDuration), NullOptional);
+    }
 };
 
 } // namespace app
@@ -740,6 +1083,22 @@
     NL_TEST_DEF("TestICDStayActive", TestICDManager::TestICDMStayActive),
     NL_TEST_DEF("TestShouldCheckInMsgsBeSentAtActiveModeFunction", TestICDManager::TestShouldCheckInMsgsBeSentAtActiveModeFunction),
     NL_TEST_DEF("TestHandleTestEventTriggerActiveModeReq", TestICDManager::TestHandleTestEventTriggerActiveModeReq),
+    NL_TEST_DEF("TestICDStateObserverOnEnterIdleModeActiveModeDuration",
+                TestICDManager::TestICDStateObserverOnEnterIdleModeActiveModeDuration),
+    NL_TEST_DEF("TestICDStateObserverOnEnterIdleModeActiveModeThreshold",
+                TestICDManager::TestICDStateObserverOnEnterIdleModeActiveModeThreshold),
+    NL_TEST_DEF("TestICDStateObserverOnEnterActiveMode", TestICDManager::TestICDStateObserverOnEnterActiveMode),
+    NL_TEST_DEF("TestICDStateObserverOnICDModeChange", TestICDManager::TestICDStateObserverOnICDModeChange),
+    NL_TEST_DEF("TestICDStateObserverOnICDModeChangeOnInit", TestICDManager::TestICDStateObserverOnICDModeChangeOnInit),
+    NL_TEST_DEF("TestICDStateObserverOnTransitionToIdleModeGreaterActiveModeDuration",
+                TestICDManager::TestICDStateObserverOnTransitionToIdleModeGreaterActiveModeDuration),
+    NL_TEST_DEF("TestICDStateObserverOnTransitionToIdleModeEqualActiveModeDuration",
+                TestICDManager::TestICDStateObserverOnTransitionToIdleModeEqualActiveModeDuration),
+    NL_TEST_DEF("TestICDStateObserverOnTransitionToIdleMode0ActiveModeDurationWithoutReq",
+                TestICDManager::TestICDStateObserverOnTransitionToIdleMode0ActiveModeDurationWithoutReq),
+    // TODO(#33074): When the OnTransitionToIdle edge is fixed, we can enable this test
+    // NL_TEST_DEF("TestICDStateObserverOnTransitionToIdleMode0ActiveModeDurationWittReq",
+    //             TestICDManager::TestICDStateObserverOnTransitionToIdleMode0ActiveModeDurationWittReq),
     NL_TEST_SENTINEL(),
 };
 
diff --git a/src/app/tests/suites/TestIcdManagementCluster.yaml b/src/app/tests/suites/TestIcdManagementCluster.yaml
index 3d00815..f7f207a 100644
--- a/src/app/tests/suites/TestIcdManagementCluster.yaml
+++ b/src/app/tests/suites/TestIcdManagementCluster.yaml
@@ -87,7 +87,10 @@
       command: "readAttribute"
       attribute: "ICDCounter"
       response:
-          value: beforeRebootICDCounter + 101
+          constraints:
+              # It is possible ICD hasn't had time to send a Check-In message after reboot
+              minValue: beforeRebootICDCounter + 100
+              maxValue: beforeRebootICDCounter + 101
 
     - label: "Verify the ICD is operating as a LIT ICD"
       command: "readAttribute"
diff --git a/src/messaging/ExchangeContext.cpp b/src/messaging/ExchangeContext.cpp
index 74a861c..3869ab4 100644
--- a/src/messaging/ExchangeContext.cpp
+++ b/src/messaging/ExchangeContext.cpp
@@ -322,6 +322,7 @@
     SetAutoRequestAck(session->AllowsMRP());
 
 #if CHIP_CONFIG_ENABLE_ICD_SERVER
+    // TODO(#33075) : Add check for group context to not a req since it serves no purpose
     app::ICDNotifier::GetInstance().NotifyActiveRequestNotification(app::ICDListener::KeepActiveFlag::kExchangeContextOpen);
 #endif
 
@@ -341,6 +342,7 @@
     VerifyOrDie(mFlags.Has(Flags::kFlagClosed));
 
 #if CHIP_CONFIG_ENABLE_ICD_SERVER
+    // TODO(#33075) : Add check for group context to not a req since it serves no purpose
     app::ICDNotifier::GetInstance().NotifyActiveRequestWithdrawal(app::ICDListener::KeepActiveFlag::kExchangeContextOpen);
 #endif // CHIP_CONFIG_ENABLE_ICD_SERVER