aaudio: steal exclusive streams

An app (B) that asks for an exclusive stream can steal
an exclusive stream from an earlier app (A).
App B will be given the MMAP resource as a SHARED stream.
The stream for app A will be disconnected and released
by the service.
If app A reopens a stream then it will get a SHARED
stream.

The order of the opening of the streams is controlled by using a
recursive_mutex in AAudioService::openStream().

Bug: 129846760
Test: media/libaaudio/tests/test_steal_exclusive.cpp
Test: also
Test: Launch AudioTroubleMaker. It should say "EXCLUSIVE".
Test: Press Home button.
Test: Siren sound from AudioTroubleMaker should continue.
Test: Launch OboeTester
Test: TEST OUTPUT, then Open, Start
Test: You should get an MMAP SHARED stream on Pixel.
Test: Go back to AudioTroubleMaker. It should say "SHARED".
Change-Id: I7f8339d8ed62546520a9b46ed398418b41ca2832
diff --git a/media/libaaudio/src/client/AudioStreamInternal.cpp b/media/libaaudio/src/client/AudioStreamInternal.cpp
index 3db0099..79fa5ed 100644
--- a/media/libaaudio/src/client/AudioStreamInternal.cpp
+++ b/media/libaaudio/src/client/AudioStreamInternal.cpp
@@ -354,6 +354,12 @@
     drainTimestampsFromService();
 
     aaudio_result_t result = mServiceInterface.startStream(mServiceStreamHandle);
+    if (result == AAUDIO_ERROR_INVALID_HANDLE) {
+        ALOGD("%s() INVALID_HANDLE, stream was probably stolen", __func__);
+        // Stealing was added in R. Coerce result to improve backward compatibility.
+        result = AAUDIO_ERROR_DISCONNECTED;
+        setState(AAUDIO_STREAM_STATE_DISCONNECTED);
+    }
 
     startTime = AudioClock::getNanoseconds();
     mClockModel.start(startTime);
@@ -397,7 +403,12 @@
     if (isDataCallbackSet()
             && (isActive() || getState() == AAUDIO_STREAM_STATE_DISCONNECTED)) {
         mCallbackEnabled.store(false);
-        return joinThread(NULL); // may temporarily unlock mStreamLock
+        aaudio_result_t result = joinThread(NULL); // may temporarily unlock mStreamLock
+        if (result == AAUDIO_ERROR_INVALID_HANDLE) {
+            ALOGD("%s() INVALID_HANDLE, stream was probably stolen", __func__);
+            result = AAUDIO_OK;
+        }
+        return result;
     } else {
         return AAUDIO_OK;
     }
@@ -427,7 +438,12 @@
     setState(AAUDIO_STREAM_STATE_STOPPING);
     mAtomicInternalTimestamp.clear();
 
-    return mServiceInterface.stopStream(mServiceStreamHandle);
+    result = mServiceInterface.stopStream(mServiceStreamHandle);
+    if (result == AAUDIO_ERROR_INVALID_HANDLE) {
+        ALOGD("%s() INVALID_HANDLE, stream was probably stolen", __func__);
+        result = AAUDIO_OK;
+    }
+    return result;
 }
 
 aaudio_result_t AudioStreamInternal::registerThread() {
diff --git a/services/oboeservice/AAudioEndpointManager.cpp b/services/oboeservice/AAudioEndpointManager.cpp
index 82cc90e..c9bf72f 100644
--- a/services/oboeservice/AAudioEndpointManager.cpp
+++ b/services/oboeservice/AAudioEndpointManager.cpp
@@ -76,6 +76,7 @@
         result << "  ExclusiveFoundCount:   " << mExclusiveFoundCount << "\n";
         result << "  ExclusiveOpenCount:    " << mExclusiveOpenCount << "\n";
         result << "  ExclusiveCloseCount:   " << mExclusiveCloseCount << "\n";
+        result << "  ExclusiveStolenCount:  " << mExclusiveStolenCount << "\n";
         result << "\n";
 
         if (isExclusiveLocked) {
@@ -142,7 +143,13 @@
 sp<AAudioServiceEndpoint> AAudioEndpointManager::openEndpoint(AAudioService &audioService,
                                         const aaudio::AAudioStreamRequest &request) {
     if (request.getConstantConfiguration().getSharingMode() == AAUDIO_SHARING_MODE_EXCLUSIVE) {
-        return openExclusiveEndpoint(audioService, request);
+        sp<AAudioServiceEndpoint> endpointToSteal;
+        sp<AAudioServiceEndpoint> foundEndpoint =
+                openExclusiveEndpoint(audioService, request, endpointToSteal);
+        if (endpointToSteal.get()) {
+            endpointToSteal->releaseRegisteredStreams(); // free the MMAP resource
+        }
+        return foundEndpoint;
     } else {
         return openSharedEndpoint(audioService, request);
     }
@@ -150,7 +157,8 @@
 
 sp<AAudioServiceEndpoint> AAudioEndpointManager::openExclusiveEndpoint(
         AAudioService &aaudioService,
-        const aaudio::AAudioStreamRequest &request) {
+        const aaudio::AAudioStreamRequest &request,
+        sp<AAudioServiceEndpoint> &endpointToSteal) {
 
     std::lock_guard<std::mutex> lock(mExclusiveLock);
 
@@ -161,18 +169,22 @@
 
     // If we find an existing one then this one cannot be exclusive.
     if (endpoint.get() != nullptr) {
-        ALOGW("openExclusiveEndpoint() already in use");
-        // Already open so do not allow a second stream.
+        if (kStealingEnabled
+                && !endpoint->isForSharing() // not currently SHARED
+                && !request.isSharingModeMatchRequired()) { // app did not request a shared stream
+            ALOGD("%s() endpoint in EXCLUSIVE use. Steal it!", __func__);
+            mExclusiveStolenCount++;
+            endpointToSteal = endpoint;
+        }
         return nullptr;
     } else {
         sp<AAudioServiceEndpointMMAP> endpointMMap = new AAudioServiceEndpointMMAP(aaudioService);
-        ALOGV("openExclusiveEndpoint(), no match so try to open MMAP %p for dev %d",
-              endpointMMap.get(), configuration.getDeviceId());
+        ALOGV("%s(), no match so try to open MMAP %p for dev %d",
+              __func__, endpointMMap.get(), configuration.getDeviceId());
         endpoint = endpointMMap;
 
         aaudio_result_t result = endpoint->open(request);
         if (result != AAUDIO_OK) {
-            ALOGV("openExclusiveEndpoint(), open failed");
             endpoint.clear();
         } else {
             mExclusiveStreams.push_back(endpointMMap);
@@ -183,7 +195,9 @@
     if (endpoint.get() != nullptr) {
         // Increment the reference count under this lock.
         endpoint->setOpenCount(endpoint->getOpenCount() + 1);
+        endpoint->setForSharing(request.isSharingModeMatchRequired());
     }
+
     return endpoint;
 }
 
diff --git a/services/oboeservice/AAudioEndpointManager.h b/services/oboeservice/AAudioEndpointManager.h
index ba17853..ae776b1 100644
--- a/services/oboeservice/AAudioEndpointManager.h
+++ b/services/oboeservice/AAudioEndpointManager.h
@@ -19,6 +19,7 @@
 
 #include <map>
 #include <mutex>
+#include <sys/types.h>
 #include <utils/Singleton.h>
 
 #include "binding/AAudioServiceMessage.h"
@@ -62,7 +63,8 @@
 
 private:
     android::sp<AAudioServiceEndpoint> openExclusiveEndpoint(android::AAudioService &aaudioService,
-                                                 const aaudio::AAudioStreamRequest &request);
+                                                 const aaudio::AAudioStreamRequest &request,
+                                                 sp<AAudioServiceEndpoint> &endpointToSteal);
 
     android::sp<AAudioServiceEndpoint> openSharedEndpoint(android::AAudioService &aaudioService,
                                               const aaudio::AAudioStreamRequest &request);
@@ -91,11 +93,16 @@
     int32_t mExclusiveFoundCount  = 0; // number of times we FOUND an exclusive endpoint
     int32_t mExclusiveOpenCount   = 0; // number of times we OPENED an exclusive endpoint
     int32_t mExclusiveCloseCount  = 0; // number of times we CLOSED an exclusive endpoint
+    int32_t mExclusiveStolenCount = 0; // number of times we STOLE an exclusive endpoint
+
     // Same as above but for SHARED endpoints.
     int32_t mSharedSearchCount    = 0;
     int32_t mSharedFoundCount     = 0;
     int32_t mSharedOpenCount      = 0;
     int32_t mSharedCloseCount     = 0;
+
+    // For easily disabling the stealing of exclusive streams.
+    static constexpr bool kStealingEnabled = true;
 };
 } /* namespace aaudio */
 
diff --git a/services/oboeservice/AAudioService.cpp b/services/oboeservice/AAudioService.cpp
index cac7453..ecbcb7e 100644
--- a/services/oboeservice/AAudioService.cpp
+++ b/services/oboeservice/AAudioService.cpp
@@ -85,6 +85,17 @@
 
 aaudio_handle_t AAudioService::openStream(const aaudio::AAudioStreamRequest &request,
                                           aaudio::AAudioStreamConfiguration &configurationOutput) {
+    // A lock in is used to order the opening of endpoints when an
+    // EXCLUSIVE endpoint is stolen. We want the order to be:
+    // 1) Thread A opens exclusive MMAP endpoint
+    // 2) Thread B wants to open an exclusive MMAP endpoint so it steals the one from A
+    //    under this lock.
+    // 3) Thread B opens a shared MMAP endpoint.
+    // 4) Thread A can then get the lock and also open a shared stream.
+    // Without the lock. Thread A might sneak in and reallocate an exclusive stream
+    // before B can open the shared stream.
+    std::unique_lock<std::recursive_mutex> lock(mOpenLock);
+
     aaudio_result_t result = AAUDIO_OK;
     sp<AAudioServiceStreamBase> serviceStream;
     const AAudioStreamConfiguration &configurationInput = request.getConstantConfiguration();
@@ -139,7 +150,6 @@
         return result;
     } else {
         aaudio_handle_t handle = mStreamTracker.addStreamForHandle(serviceStream.get());
-        ALOGV("openStream(): handle = 0x%08X", handle);
         serviceStream->setHandle(handle);
         pid_t pid = request.getProcessId();
         AAudioClientTracker::getInstance().registerClientStream(pid, serviceStream);
@@ -147,6 +157,7 @@
         // Log open in MediaMetrics after we have the handle because we need the handle to
         // create the metrics ID.
         serviceStream->logOpen(handle);
+        ALOGV("%s(): return handle = 0x%08X", __func__, handle);
         return handle;
     }
 }
@@ -180,7 +191,10 @@
         ALOGE("closeStream(0x%0x), illegal stream handle", streamHandle);
         return AAUDIO_ERROR_INVALID_HANDLE;
     }
+    return closeStream(serviceStream);
+}
 
+aaudio_result_t AAudioService::closeStream(sp<AAudioServiceStreamBase> serviceStream) {
     pid_t pid = serviceStream->getOwnerProcessId();
     AAudioClientTracker::getInstance().unregisterClientStream(pid, serviceStream);
 
diff --git a/services/oboeservice/AAudioService.h b/services/oboeservice/AAudioService.h
index 7167868..6a2ac1f 100644
--- a/services/oboeservice/AAudioService.h
+++ b/services/oboeservice/AAudioService.h
@@ -55,6 +55,10 @@
                                        aaudio::AAudioStreamConfiguration &configurationOutput)
                                        override;
 
+    /*
+     * This is called from Binder. It checks for permissions
+     * and converts the handle passed through Binder to a stream pointer.
+     */
     aaudio_result_t closeStream(aaudio::aaudio_handle_t streamHandle) override;
 
     aaudio_result_t getStreamDescription(
@@ -84,8 +88,18 @@
     aaudio_result_t stopClient(aaudio::aaudio_handle_t streamHandle,
                                        audio_port_handle_t clientHandle) override;
 
+ // ===============================================================================
+ // The following public methods are only called from the service and NOT by Binder.
+ // ===============================================================================
+
     aaudio_result_t disconnectStreamByPortHandle(audio_port_handle_t portHandle);
 
+    /*
+     * This is only called from within the Service.
+     * It bypasses the permission checks in closeStream(handle).
+     */
+    aaudio_result_t closeStream(sp<aaudio::AAudioServiceStreamBase> serviceStream);
+
 private:
 
     /** @return true if the client is the audioserver
@@ -100,8 +114,6 @@
     sp<aaudio::AAudioServiceStreamBase> convertHandleToServiceStream(
             aaudio::aaudio_handle_t streamHandle);
 
-
-
     bool releaseStream(const sp<aaudio::AAudioServiceStreamBase> &serviceStream);
 
     aaudio_result_t checkForPendingClose(const sp<aaudio::AAudioServiceStreamBase> &serviceStream,
@@ -111,6 +123,10 @@
 
     aaudio::AAudioStreamTracker     mStreamTracker;
 
+    // We use a lock to prevent thread A from reopening an exclusive stream
+    // after thread B steals thread A's exclusive MMAP resource stream.
+    std::recursive_mutex            mOpenLock;
+
     // TODO  Extract the priority constants from services/audioflinger/Threads.cpp
     // and share them with this code. Look for "kPriorityFastMixer".
     static constexpr int32_t        kRealTimeAudioPriorityClient = 2;
diff --git a/services/oboeservice/AAudioServiceEndpoint.cpp b/services/oboeservice/AAudioServiceEndpoint.cpp
index 647dcf7..b09cbf4 100644
--- a/services/oboeservice/AAudioServiceEndpoint.cpp
+++ b/services/oboeservice/AAudioServiceEndpoint.cpp
@@ -27,13 +27,13 @@
 
 #include <utils/Singleton.h>
 
-#include "AAudioEndpointManager.h"
-#include "AAudioServiceEndpoint.h"
 
 #include "core/AudioStreamBuilder.h"
+
+#include "AAudioEndpointManager.h"
+#include "AAudioClientTracker.h"
 #include "AAudioServiceEndpoint.h"
 #include "AAudioServiceStreamShared.h"
-#include "AAudioServiceEndpointShared.h"
 
 using namespace android;  // TODO just import names needed
 using namespace aaudio;   // TODO just import names needed
@@ -87,16 +87,31 @@
     return false;
 }
 
-void AAudioServiceEndpoint::disconnectRegisteredStreams() {
+std::vector<android::sp<AAudioServiceStreamBase>>
+        AAudioServiceEndpoint::disconnectRegisteredStreams() {
+    std::vector<android::sp<AAudioServiceStreamBase>> streamsDisconnected;
     std::lock_guard<std::mutex> lock(mLockStreams);
     mConnected.store(false);
-    for (const auto& stream : mRegisteredStreams) {
-        ALOGD("disconnectRegisteredStreams() stop and disconnect port %d",
-              stream->getPortHandle());
+    for (const auto &stream : mRegisteredStreams) {
+        ALOGD("%s() - stop and disconnect port %d", __func__, stream->getPortHandle());
         stream->stop();
         stream->disconnect();
     }
-    mRegisteredStreams.clear();
+    mRegisteredStreams.swap(streamsDisconnected);
+    return streamsDisconnected;
+}
+
+void AAudioServiceEndpoint::releaseRegisteredStreams() {
+    // List of streams to be closed after we disconnect everything.
+    std::vector<android::sp<AAudioServiceStreamBase>> streamsToClose
+            = disconnectRegisteredStreams();
+
+    // Close outside the lock to avoid recursive locks.
+    AAudioService *aaudioService = AAudioClientTracker::getInstance().getAAudioService();
+    for (const auto& serviceStream : streamsToClose) {
+        ALOGD("%s() - close stream 0x%08X", __func__, serviceStream->getHandle());
+        aaudioService->closeStream(serviceStream);
+    }
 }
 
 aaudio_result_t AAudioServiceEndpoint::registerStream(sp<AAudioServiceStreamBase>stream) {
diff --git a/services/oboeservice/AAudioServiceEndpoint.h b/services/oboeservice/AAudioServiceEndpoint.h
index ad30087..a171cb0 100644
--- a/services/oboeservice/AAudioServiceEndpoint.h
+++ b/services/oboeservice/AAudioServiceEndpoint.h
@@ -111,6 +111,21 @@
 
     static audio_attributes_t getAudioAttributesFrom(const AAudioStreamParameters *params);
 
+    // Stop, disconnect and release any streams registered on this endpoint.
+    void releaseRegisteredStreams();
+
+    bool isForSharing() const {
+        return mForSharing;
+    }
+
+    /**
+     *
+     * @param flag true if this endpoint is to be shared between multiple streams
+     */
+    void setForSharing(bool flag) {
+        mForSharing = flag;
+    }
+
 protected:
 
     /**
@@ -119,7 +134,7 @@
      */
     bool                     isStreamRegistered(audio_port_handle_t portHandle);
 
-    void                     disconnectRegisteredStreams();
+    std::vector<android::sp<AAudioServiceStreamBase>> disconnectRegisteredStreams();
 
     mutable std::mutex       mLockStreams;
     std::vector<android::sp<AAudioServiceStreamBase>> mRegisteredStreams;
@@ -132,7 +147,11 @@
     int32_t                  mOpenCount = 0;
     int32_t                  mRequestedDeviceId = 0;
 
+    // True if this will be shared by one or more other streams.
+    bool                     mForSharing = false;
+
     std::atomic<bool>        mConnected{true};
+
 };
 
 } /* namespace aaudio */
diff --git a/services/oboeservice/AAudioServiceEndpointMMAP.cpp b/services/oboeservice/AAudioServiceEndpointMMAP.cpp
index af2710d..9dab770 100644
--- a/services/oboeservice/AAudioServiceEndpointMMAP.cpp
+++ b/services/oboeservice/AAudioServiceEndpointMMAP.cpp
@@ -229,7 +229,7 @@
 }
 
 aaudio_result_t AAudioServiceEndpointMMAP::close() {
-    if (mMmapStream != 0) {
+    if (mMmapStream != nullptr) {
         // Needs to be explicitly cleared or CTS will fail but it is not clear why.
         mMmapStream.clear();
         // Apparently the above close is asynchronous. An attempt to open a new device