AIDL: Release patches on external device disconnection

When an external device is being disconnected, check
which patches are holding the port and release them.
This replaces the behavior of "postponed disconnection".

Bug: 327449747
Test: repro steps from the bug
Change-Id: I98399fd6e820046391ad9ee1e2cb3ae0afc7eed2
diff --git a/media/libaudiohal/impl/DeviceHalAidl.cpp b/media/libaudiohal/impl/DeviceHalAidl.cpp
index 14765f6b..f865603 100644
--- a/media/libaudiohal/impl/DeviceHalAidl.cpp
+++ b/media/libaudiohal/impl/DeviceHalAidl.cpp
@@ -589,7 +589,6 @@
     // that the HAL module uses `int32_t` for patch IDs. The following assert ensures
     // that both the framework and the HAL use the same value for "no ID":
     static_assert(AUDIO_PATCH_HANDLE_NONE == 0);
-    int32_t aidlPatchId = static_cast<int32_t>(*patch);
 
     // Upon conversion, mix port configs contain audio configuration, while
     // device port configs contain device address. This data is used to find
@@ -611,11 +610,27 @@
                         ::aidl::android::legacy2aidl_audio_port_config_AudioPortConfig(
                                 sinks[i], isInput, 0)));
     }
+    int32_t aidlPatchId = static_cast<int32_t>(*patch);
     Hal2AidlMapper::Cleanups cleanups(mMapperAccessor);
     {
         std::lock_guard l(mLock);
-        RETURN_STATUS_IF_ERROR(mMapper.createOrUpdatePatch(
-                        aidlSources, aidlSinks, &aidlPatchId, &cleanups));
+        // Check for patches that only exist for the framework, or have different HAL patch ID.
+        if (int32_t aidlHalPatchId = mMapper.findFwkPatch(aidlPatchId); aidlHalPatchId != 0) {
+            if (aidlHalPatchId == aidlPatchId) {
+                // This patch was previously released by the HAL. Thus we need to pass '0'
+                // to the HAL to obtain a new patch.
+                int32_t newAidlPatchId = 0;
+                RETURN_STATUS_IF_ERROR(mMapper.createOrUpdatePatch(
+                                aidlSources, aidlSinks, &newAidlPatchId, &cleanups));
+                mMapper.updateFwkPatch(aidlPatchId, newAidlPatchId);
+            } else {
+                RETURN_STATUS_IF_ERROR(mMapper.createOrUpdatePatch(
+                                aidlSources, aidlSinks, &aidlHalPatchId, &cleanups));
+            }
+        } else {
+            RETURN_STATUS_IF_ERROR(mMapper.createOrUpdatePatch(
+                            aidlSources, aidlSinks, &aidlPatchId, &cleanups));
+        }
     }
     *patch = static_cast<audio_patch_handle_t>(aidlPatchId);
     cleanups.disarmAll();
@@ -631,7 +646,19 @@
         return BAD_VALUE;
     }
     std::lock_guard l(mLock);
-    RETURN_STATUS_IF_ERROR(mMapper.releaseAudioPatch(static_cast<int32_t>(patch)));
+    // Check for patches that only exist for the framework, or have different HAL patch ID.
+    int32_t aidlPatchId = static_cast<int32_t>(patch);
+    if (int32_t aidlHalPatchId = mMapper.findFwkPatch(aidlPatchId); aidlHalPatchId != 0) {
+        if (aidlHalPatchId == aidlPatchId) {
+            // This patch was previously released by the HAL, just need to finish its removal.
+            mMapper.eraseFwkPatch(aidlPatchId);
+            return OK;
+        } else {
+            // This patch has a HAL patch ID which is different
+            aidlPatchId = aidlHalPatchId;
+        }
+    }
+    RETURN_STATUS_IF_ERROR(mMapper.releaseAudioPatch(aidlPatchId));
     return OK;
 }
 
@@ -988,7 +1015,7 @@
     if (mModule == nullptr) return NO_INIT;
     {
         std::lock_guard l(mLock);
-        mMapper.resetUnusedPatchesPortConfigsAndPorts();
+        mMapper.resetUnusedPatchesAndPortConfigs();
     }
     ModuleDebug debug{ .simulateDeviceConnections = enabled };
     status_t status = statusTFromBinderStatus(mModule->setModuleDebug(debug));
diff --git a/media/libaudiohal/impl/Hal2AidlMapper.cpp b/media/libaudiohal/impl/Hal2AidlMapper.cpp
index 2b7f298..acc69ec 100644
--- a/media/libaudiohal/impl/Hal2AidlMapper.cpp
+++ b/media/libaudiohal/impl/Hal2AidlMapper.cpp
@@ -102,8 +102,8 @@
 }
 
 void Hal2AidlMapper::addStream(
-        const sp<StreamHalInterface>& stream, int32_t portConfigId, int32_t patchId) {
-    mStreams.insert(std::pair(stream, std::pair(portConfigId, patchId)));
+        const sp<StreamHalInterface>& stream, int32_t mixPortConfigId, int32_t patchId) {
+    mStreams.insert(std::pair(stream, std::pair(mixPortConfigId, patchId)));
 }
 
 bool Hal2AidlMapper::audioDeviceMatches(const AudioDevice& device, const AudioPort& p) {
@@ -698,20 +698,23 @@
     return OK;
 }
 
-bool Hal2AidlMapper::isPortBeingHeld(int32_t portId) {
-    // It is assumed that mStreams has already been cleaned up.
-    for (const auto& s : mStreams) {
-        if (portConfigBelongsToPort(s.second.first, portId)) return true;
-    }
-    for (const auto& [_, patch] : mPatches) {
+std::set<int32_t> Hal2AidlMapper::getPatchIdsByPortId(int32_t portId) {
+    std::set<int32_t> result;
+    for (const auto& [patchId, patch] : mPatches) {
         for (int32_t id : patch.sourcePortConfigIds) {
-            if (portConfigBelongsToPort(id, portId)) return true;
+            if (portConfigBelongsToPort(id, portId)) {
+                result.insert(patchId);
+                break;
+            }
         }
         for (int32_t id : patch.sinkPortConfigIds) {
-            if (portConfigBelongsToPort(id, portId)) return true;
+            if (portConfigBelongsToPort(id, portId)) {
+                result.insert(patchId);
+                break;
+            }
         }
     }
-    return false;
+    return result;
 }
 
 status_t Hal2AidlMapper::prepareToDisconnectExternalDevice(const AudioPort& devicePort) {
@@ -730,7 +733,7 @@
             this, __func__, ioHandle, device.toString().c_str(),
             flags.toString().c_str(), toString(source).c_str(),
             config->toString().c_str(), mixPortConfig->toString().c_str());
-    resetUnusedPatchesPortConfigsAndPorts();
+    resetUnusedPatchesAndPortConfigs();
     const AudioConfig initialConfig = *config;
     // Find / create AudioPortConfigs for the device port and the mix port,
     // then find / create a patch between them, and open a stream on the mix port.
@@ -843,39 +846,52 @@
     return releaseAudioPatches({patchId});
 }
 
+// Note: does not reset port configs.
+status_t Hal2AidlMapper::releaseAudioPatch(Patches::iterator it) {
+    const int32_t patchId = it->first;
+    if (ndk::ScopedAStatus status = mModule->resetAudioPatch(patchId); !status.isOk()) {
+        ALOGE("%s: error while resetting patch %d: %s",
+                __func__, patchId, status.getDescription().c_str());
+        return statusTFromBinderStatus(status);
+    }
+    mPatches.erase(it);
+    for (auto it = mFwkPatches.begin(); it != mFwkPatches.end(); ++it) {
+        if (it->second == patchId) {
+            mFwkPatches.erase(it);
+            break;
+        }
+    }
+    return OK;
+}
+
 status_t Hal2AidlMapper::releaseAudioPatches(const std::set<int32_t>& patchIds) {
     status_t result = OK;
     for (const auto patchId : patchIds) {
         if (auto it = mPatches.find(patchId); it != mPatches.end()) {
-            mPatches.erase(it);
-            if (ndk::ScopedAStatus status = mModule->resetAudioPatch(patchId); !status.isOk()) {
-                ALOGE("%s: error while resetting patch %d: %s",
-                        __func__, patchId, status.getDescription().c_str());
-                result = statusTFromBinderStatus(status);
-            }
+            releaseAudioPatch(it);
         } else {
             ALOGE("%s: patch id %d not found", __func__, patchId);
             result = BAD_VALUE;
         }
     }
-    resetUnusedPortConfigsAndPorts();
+    resetUnusedPortConfigs();
     return result;
 }
 
 void Hal2AidlMapper::resetPortConfig(int32_t portConfigId) {
     if (auto it = mPortConfigs.find(portConfigId); it != mPortConfigs.end()) {
-        mPortConfigs.erase(it);
         if (ndk::ScopedAStatus status = mModule->resetAudioPortConfig(portConfigId);
                 !status.isOk()) {
             ALOGE("%s: error while resetting port config %d: %s",
                     __func__, portConfigId, status.getDescription().c_str());
         }
+        mPortConfigs.erase(it);
         return;
     }
     ALOGE("%s: port config id %d not found", __func__, portConfigId);
 }
 
-void Hal2AidlMapper::resetUnusedPatchesPortConfigsAndPorts() {
+void Hal2AidlMapper::resetUnusedPatchesAndPortConfigs() {
     // Since patches can be created independently of streams via 'createOrUpdatePatch',
     // here we only clean up patches for released streams.
     std::set<int32_t> patchesToRelease;
@@ -889,52 +905,35 @@
             it = mStreams.erase(it);
         }
     }
-    // 'releaseAudioPatches' also resets unused port configs and ports.
+    // 'releaseAudioPatches' also resets unused port configs.
     releaseAudioPatches(patchesToRelease);
 }
 
-void Hal2AidlMapper::resetUnusedPortConfigsAndPorts() {
+void Hal2AidlMapper::resetUnusedPortConfigs() {
     // The assumption is that port configs are used to create patches
     // (or to open streams, but that involves creation of patches, too). Thus,
     // orphaned port configs can and should be reset.
-    std::map<int32_t, int32_t /*portID*/> portConfigIds;
+    std::set<int32_t> portConfigIdsToReset;
     std::transform(mPortConfigs.begin(), mPortConfigs.end(),
-            std::inserter(portConfigIds, portConfigIds.end()),
-            [](const auto& pcPair) { return std::make_pair(pcPair.first, pcPair.second.portId); });
+            std::inserter(portConfigIdsToReset, portConfigIdsToReset.end()),
+            [](const auto& pcPair) { return pcPair.first; });
     for (const auto& p : mPatches) {
-        for (int32_t id : p.second.sourcePortConfigIds) portConfigIds.erase(id);
-        for (int32_t id : p.second.sinkPortConfigIds) portConfigIds.erase(id);
+        for (int32_t id : p.second.sourcePortConfigIds) portConfigIdsToReset.erase(id);
+        for (int32_t id : p.second.sinkPortConfigIds) portConfigIdsToReset.erase(id);
     }
     for (int32_t id : mInitialPortConfigIds) {
-        portConfigIds.erase(id);
+        portConfigIdsToReset.erase(id);
     }
     for (const auto& s : mStreams) {
-        portConfigIds.erase(s.second.first);
+        portConfigIdsToReset.erase(s.second.first);
     }
-    std::set<int32_t> retryDeviceDisconnection;
-    for (const auto& portConfigAndIdPair : portConfigIds) {
-        resetPortConfig(portConfigAndIdPair.first);
-        if (const auto it = mConnectedPorts.find(portConfigAndIdPair.second);
-                it != mConnectedPorts.end() && it->second) {
-            retryDeviceDisconnection.insert(portConfigAndIdPair.second);
-        }
-    }
-    for (int32_t portId : retryDeviceDisconnection) {
-        if (!isPortBeingHeld(portId)) {
-            if (auto status = mModule->disconnectExternalDevice(portId); status.isOk()) {
-                eraseConnectedPort(portId);
-                ALOGD("%s: executed postponed external device disconnection for port ID %d",
-                        __func__, portId);
-            }
-        }
-    }
-    if (!retryDeviceDisconnection.empty()) {
-        updateRoutes();
+    for (const auto& portConfigId : portConfigIdsToReset) {
+        resetPortConfig(portConfigId);
     }
 }
 
 status_t Hal2AidlMapper::setDevicePortConnectedState(const AudioPort& devicePort, bool connected) {
-    resetUnusedPatchesPortConfigsAndPorts();
+    resetUnusedPatchesAndPortConfigs();
     if (connected) {
         AudioDevice matchDevice = devicePort.ext.get<AudioPortExt::device>().device;
         std::optional<AudioPort> templatePort;
@@ -980,7 +979,7 @@
                 "%s: module %s, duplicate port ID received from HAL: %s, existing port: %s",
                 __func__, mInstance.c_str(), connectedPort.toString().c_str(),
                 it->second.toString().c_str());
-        mConnectedPorts[connectedPort.id] = false;
+        mConnectedPorts.insert(connectedPort.id);
         if (erasePortAfterConnectionIt != mPorts.end()) {
             mPorts.erase(erasePortAfterConnectionIt);
         }
@@ -1007,17 +1006,27 @@
             port.ext.get<AudioPortExt::Tag::device>().device = matchDevice;
             port.profiles = portsIt->second.profiles;
         }
-        // Streams are closed by AudioFlinger independently from device disconnections.
-        // It is possible that the stream has not been closed yet.
-        if (!isPortBeingHeld(portId)) {
-            RETURN_STATUS_IF_ERROR(statusTFromBinderStatus(
-                            mModule->disconnectExternalDevice(portId)));
-            eraseConnectedPort(portId);
-        } else {
-            ALOGD("%s: since device port ID %d is used by a stream, "
-                    "external device disconnection postponed", __func__, portId);
-            mConnectedPorts[portId] = true;
+
+        // Patches may still exist, the framework may reset or update them later.
+        // For disconnection to succeed, need to release these patches first.
+        if (std::set<int32_t> patchIdsToRelease = getPatchIdsByPortId(portId);
+                !patchIdsToRelease.empty()) {
+            FwkPatches releasedPatches;
+            status_t status = OK;
+            for (int32_t patchId : patchIdsToRelease) {
+                if (auto it = mPatches.find(patchId); it != mPatches.end()) {
+                    if (status = releaseAudioPatch(it); status != OK) break;
+                    releasedPatches.insert(std::make_pair(patchId, patchId));
+                }
+            }
+            resetUnusedPortConfigs();
+            mFwkPatches.merge(releasedPatches);
+            LOG_ALWAYS_FATAL_IF(!releasedPatches.empty(),
+                    "mFwkPatches already contains some of released patches");
+            if (status != OK) return status;
         }
+        RETURN_STATUS_IF_ERROR(statusTFromBinderStatus(mModule->disconnectExternalDevice(portId)));
+        eraseConnectedPort(portId);
     }
     return updateRoutes();
 }
diff --git a/media/libaudiohal/impl/Hal2AidlMapper.h b/media/libaudiohal/impl/Hal2AidlMapper.h
index f937173..f302c23 100644
--- a/media/libaudiohal/impl/Hal2AidlMapper.h
+++ b/media/libaudiohal/impl/Hal2AidlMapper.h
@@ -49,7 +49,7 @@
             const std::string& instance,
             const std::shared_ptr<::aidl::android::hardware::audio::core::IModule>& module);
 
-    void addStream(const sp<StreamHalInterface>& stream, int32_t portConfigId, int32_t patchId);
+    void addStream(const sp<StreamHalInterface>& stream, int32_t mixPortConfigId, int32_t patchId);
     status_t createOrUpdatePatch(
             const std::vector<::aidl::android::media::audio::common::AudioPortConfig>& sources,
             const std::vector<::aidl::android::media::audio::common::AudioPortConfig>& sinks,
@@ -91,13 +91,32 @@
         ::aidl::android::media::audio::common::AudioPortConfig* portConfig,
         Cleanups* cleanups = nullptr);
     status_t releaseAudioPatch(int32_t patchId);
-    void resetUnusedPatchesPortConfigsAndPorts();
+    void resetUnusedPatchesAndPortConfigs();
     status_t setDevicePortConnectedState(
             const ::aidl::android::media::audio::common::AudioPort& devicePort, bool connected);
 
+    // Methods to work with FwkPatches.
+    void eraseFwkPatch(int32_t fwkPatchId) { mFwkPatches.erase(fwkPatchId); }
+    int32_t findFwkPatch(int32_t fwkPatchId) {
+        const auto it = mFwkPatches.find(fwkPatchId);
+        return it != mFwkPatches.end() ? it->second : 0;
+    }
+    void updateFwkPatch(int32_t fwkPatchId, int32_t halPatchId) {
+        mFwkPatches[fwkPatchId] = halPatchId;
+    }
+
   private:
-    // IDs of ports for connected external devices, and whether they are held by streams.
-    using ConnectedPorts = std::map<int32_t /*port ID*/, bool>;
+    // 'FwkPatches' is used to store patches that diverge from the framework's state.
+    // Uses framework patch ID (aka audio_patch_handle_t) values for indexing.
+    // When the 'key == value', that means Hal2AidlMapper has removed this patch, and it is absent
+    // from 'mPatches', but it still "exists" for the framework. It will either remove it or
+    // re-patch. If the framework re-patches, it will continue to use the same patch handle,
+    // but the HAL will use the new one (since the old patch was reset), thus 'key != value'
+    // for such patches. Since they "exist" both for the framework and the HAL, 'mPatches'
+    // contains their data under HAL patch ID ('value' of 'FwkPatches').
+    // To avoid confusion, all patchIDs used by Hal2AidlMapper are HAL IDs. Mapping between
+    // framework patch IDs and HAL patch IDs is done by DeviceHalAidl.
+    using FwkPatches = std::map<int32_t /*audio_patch_handle_t*/, int32_t /*patch ID*/>;
     using Patches = std::map<int32_t /*patch ID*/,
             ::aidl::android::hardware::audio::core::AudioPatch>;
     using PortConfigs = std::map<int32_t /*port config ID*/,
@@ -107,12 +126,12 @@
     // Answers the question "whether portID 'first' is reachable from portID 'second'?"
     // It's not a map because both portIDs are known. The matrix is symmetric.
     using RoutingMatrix = std::set<std::pair<int32_t, int32_t>>;
-    // There is always a port config ID set. The patch ID is set after stream
+    // There is always a mix port config ID set. The patch ID is set after stream
     // creation, and can be set to '-1' later if the framework happens to create
     // a patch between the same endpoints. In that case, the ownership of the patch
     // is on the framework.
     using Streams = std::map<wp<StreamHalInterface>,
-            std::pair<int32_t /*port config ID*/, int32_t /*patch ID*/>>;
+            std::pair<int32_t /*mix port config ID*/, int32_t /*patch ID*/>>;
 
     const std::string mInstance;
     const std::shared_ptr<::aidl::android::hardware::audio::core::IModule> mModule;
@@ -168,7 +187,7 @@
             const std::optional<::aidl::android::media::audio::common::AudioConfig>& config,
             const std::optional<::aidl::android::media::audio::common::AudioIoFlags>& flags,
             int32_t ioHandle);
-    bool isPortBeingHeld(int32_t portId);
+    std::set<int32_t> getPatchIdsByPortId(int32_t portId);
     status_t prepareToOpenStreamHelper(
         int32_t ioHandle, int32_t devicePortId, int32_t devicePortConfigId,
         const ::aidl::android::media::audio::common::AudioIoFlags& flags,
@@ -181,10 +200,11 @@
         auto it = mPortConfigs.find(portConfigId);
         return it != mPortConfigs.end() && it->second.portId == portId;
     }
+    status_t releaseAudioPatch(Patches::iterator it);
     status_t releaseAudioPatches(const std::set<int32_t>& patchIds);
     void resetPatch(int32_t patchId) { (void)releaseAudioPatch(patchId); }
     void resetPortConfig(int32_t portConfigId);
-    void resetUnusedPortConfigsAndPorts();
+    void resetUnusedPortConfigs();
     status_t updateAudioPort(
             int32_t portId, ::aidl::android::media::audio::common::AudioPort* port);
     status_t updateRoutes();
@@ -197,13 +217,14 @@
     std::optional<::aidl::android::media::audio::common::AudioPort> mRemoteSubmixOut;
     int32_t mDefaultInputPortId = -1;
     int32_t mDefaultOutputPortId = -1;
+    FwkPatches mFwkPatches;
     PortConfigs mPortConfigs;
     std::set<int32_t> mInitialPortConfigIds;
     Patches mPatches;
     Routes mRoutes;
     RoutingMatrix mRoutingMatrix;
     Streams mStreams;
-    ConnectedPorts mConnectedPorts;
+    std::set<int32_t> mConnectedPorts;
     std::pair<int32_t, ::aidl::android::media::audio::common::AudioPort>
             mDisconnectedPortReplacement;
     std::set<int32_t> mDynamicMixPortIds;