Don't record audio if UID is idle - media framework
To protect user's privacy if a UID is in an idle state we allow
recording but report silence (all zeros in the byte array) and once
the process goes in an active state we report the real mic data.
This avoids the race between the app being notified aboout its
lifecycle and the audio system being notified about the state
of a UID.
Test: Added - AudioRecordTest#testRecordNoDataForIdleUids
Passing - cts-tradefed run cts-dev -m CtsMediaTestCases
-t android.media.cts.AudioRecordTest
bug:63938985
Change-Id: I1442a9dda1553e9ea7a4a654c50555ac1ec06aa0
diff --git a/services/audiopolicy/AudioPolicyInterface.h b/services/audiopolicy/AudioPolicyInterface.h
index f2cb25f..2a8f54d 100644
--- a/services/audiopolicy/AudioPolicyInterface.h
+++ b/services/audiopolicy/AudioPolicyInterface.h
@@ -65,15 +65,15 @@
API_INPUT_TELEPHONY_RX, // used for capture from telephony RX path
} input_type_t;
- enum {
+ enum {
API_INPUT_CONCURRENCY_NONE = 0,
API_INPUT_CONCURRENCY_CALL = (1 << 0), // Concurrency with a call
API_INPUT_CONCURRENCY_CAPTURE = (1 << 1), // Concurrency with another capture
API_INPUT_CONCURRENCY_ALL = (API_INPUT_CONCURRENCY_CALL | API_INPUT_CONCURRENCY_CAPTURE),
- };
+ };
- typedef uint32_t concurrency_type__mask_t;
+ typedef uint32_t concurrency_type__mask_t;
public:
virtual ~AudioPolicyInterface() {}
@@ -145,6 +145,7 @@
// indicates to the audio policy manager that the input starts being used.
virtual status_t startInput(audio_io_handle_t input,
audio_session_t session,
+ bool silenced,
concurrency_type__mask_t *concurrency) = 0;
// indicates to the audio policy manager that the input stops being used.
virtual status_t stopInput(audio_io_handle_t input,
@@ -239,6 +240,8 @@
virtual float getStreamVolumeDB(
audio_stream_type_t stream, int index, audio_devices_t device) = 0;
+
+ virtual void setRecordSilenced(uid_t uid, bool silenced);
};
diff --git a/services/audiopolicy/common/managerdefinitions/include/AudioSession.h b/services/audiopolicy/common/managerdefinitions/include/AudioSession.h
index 0d19373..dd5247d 100644
--- a/services/audiopolicy/common/managerdefinitions/include/AudioSession.h
+++ b/services/audiopolicy/common/managerdefinitions/include/AudioSession.h
@@ -55,6 +55,8 @@
void setUid(uid_t uid) { mRecordClientInfo.uid = uid; }
bool matches(const sp<AudioSession> &other) const;
bool isSoundTrigger() const { return mIsSoundTrigger; }
+ void setSilenced(bool silenced) { mSilenced = silenced; }
+ bool isSilenced() const { return mSilenced; }
uint32_t openCount() const { return mOpenCount; } ;
uint32_t activeCount() const { return mActiveCount; } ;
@@ -70,6 +72,7 @@
const struct audio_config_base mConfig;
const audio_input_flags_t mFlags;
bool mIsSoundTrigger;
+ bool mSilenced;
uint32_t mOpenCount;
uint32_t mActiveCount;
AudioMix* mPolicyMix; // non NULL when used by a dynamic policy
diff --git a/services/audiopolicy/managerdefault/AudioPolicyManager.cpp b/services/audiopolicy/managerdefault/AudioPolicyManager.cpp
index 68730a5..f072adc 100644
--- a/services/audiopolicy/managerdefault/AudioPolicyManager.cpp
+++ b/services/audiopolicy/managerdefault/AudioPolicyManager.cpp
@@ -1801,10 +1801,15 @@
status_t AudioPolicyManager::startInput(audio_io_handle_t input,
audio_session_t session,
+ bool silenced,
concurrency_type__mask_t *concurrency)
{
- ALOGV("startInput() input %d", input);
+
+ ALOGV("AudioPolicyManager::startInput(input:%d, session:%d, silenced:%d, concurrency:%d)",
+ input, session, silenced, *concurrency);
+
*concurrency = API_INPUT_CONCURRENCY_NONE;
+
ssize_t index = mInputs.indexOfKey(input);
if (index < 0) {
ALOGW("startInput() unknown input %d", input);
@@ -1839,12 +1844,33 @@
return INVALID_OPERATION;
}
- Vector< sp<AudioInputDescriptor> > activeInputs = mInputs.getActiveInputs();
- for (const auto& activeDesc : activeInputs) {
- if (is_virtual_input_device(activeDesc->mDevice)) {
- continue;
- }
+ Vector<sp<AudioInputDescriptor>> activeInputs = mInputs.getActiveInputs();
+ // If a UID is idle and records silence and another not silenced recording starts
+ // from another UID (idle or active) we stop the current idle UID recording in
+ // favor of the new one - "There can be only one" TM
+ if (!silenced) {
+ for (const auto& activeDesc : activeInputs) {
+ if ((audioSession->flags() & AUDIO_INPUT_FLAG_MMAP_NOIRQ) != 0 &&
+ activeDesc->getId() == inputDesc->getId()) {
+ continue;
+ }
+
+ AudioSessionCollection activeSessions = activeDesc->getAudioSessions(
+ true /*activeOnly*/);
+ sp<AudioSession> activeSession = activeSessions.valueAt(0);
+ if (activeSession->isSilenced()) {
+ audio_io_handle_t activeInput = activeDesc->mIoHandle;
+ audio_session_t activeSessionId = activeSession->session();
+ stopInput(activeInput, activeSessionId);
+ releaseInput(activeInput, activeSessionId);
+ ALOGV("startInput(%d) stopping silenced input %d", input, activeInput);
+ activeInputs = mInputs.getActiveInputs();
+ }
+ }
+ }
+
+ for (const auto& activeDesc : activeInputs) {
if ((audioSession->flags() & AUDIO_INPUT_FLAG_MMAP_NOIRQ) != 0 &&
activeDesc->getId() == inputDesc->getId()) {
continue;
@@ -1881,10 +1907,6 @@
// if capture is allowed, preempt currently active HOTWORD captures
for (const auto& activeDesc : activeInputs) {
- if (is_virtual_input_device(activeDesc->mDevice)) {
- continue;
- }
-
if (allowConcurrentWithSoundTrigger && activeDesc->isSoundTrigger()) {
continue;
}
@@ -1907,6 +1929,9 @@
}
#endif
+ // Make sure we start with the correct silence state
+ audioSession->setSilenced(silenced);
+
// increment activity count before calling getNewInputDevice() below as only active sessions
// are considered for device selection
audioSession->changeActiveCount(1);
@@ -2039,7 +2064,6 @@
void AudioPolicyManager::releaseInput(audio_io_handle_t input,
audio_session_t session)
{
-
ALOGV("releaseInput() %d", input);
ssize_t index = mInputs.indexOfKey(input);
if (index < 0) {
@@ -3391,6 +3415,23 @@
return computeVolume(stream, index, device);
}
+void AudioPolicyManager::setRecordSilenced(uid_t uid, bool silenced)
+{
+ ALOGV("AudioPolicyManager:setRecordSilenced(uid:%d, silenced:%d)", uid, silenced);
+
+ Vector<sp<AudioInputDescriptor> > activeInputs = mInputs.getActiveInputs();
+ for (size_t i = 0; i < activeInputs.size(); i++) {
+ sp<AudioInputDescriptor> activeDesc = activeInputs[i];
+ AudioSessionCollection activeSessions = activeDesc->getAudioSessions(true);
+ for (size_t j = 0; j < activeSessions.size(); j++) {
+ sp<AudioSession> activeSession = activeSessions.valueAt(j);
+ if (activeSession->uid() == uid) {
+ activeSession->setSilenced(silenced);
+ }
+ }
+ }
+}
+
status_t AudioPolicyManager::disconnectAudioSource(const sp<AudioSourceDescriptor>& sourceDesc)
{
ALOGV("%s handle %d", __FUNCTION__, sourceDesc->getHandle());
diff --git a/services/audiopolicy/managerdefault/AudioPolicyManager.h b/services/audiopolicy/managerdefault/AudioPolicyManager.h
index 611edec..4fd73e6 100644
--- a/services/audiopolicy/managerdefault/AudioPolicyManager.h
+++ b/services/audiopolicy/managerdefault/AudioPolicyManager.h
@@ -135,6 +135,7 @@
// indicates to the audio policy manager that the input starts being used.
virtual status_t startInput(audio_io_handle_t input,
audio_session_t session,
+ bool silenced,
concurrency_type__mask_t *concurrency);
// indicates to the audio policy manager that the input stops being used.
@@ -235,6 +236,8 @@
// return the strategy corresponding to a given stream type
routing_strategy getStrategy(audio_stream_type_t stream) const;
+ virtual void setRecordSilenced(uid_t uid, bool silenced);
+
protected:
// A constructor that allows more fine-grained control over initialization process,
// used in automatic tests.
diff --git a/services/audiopolicy/service/AudioPolicyInterfaceImpl.cpp b/services/audiopolicy/service/AudioPolicyInterfaceImpl.cpp
index 7dd6d70..dc676d0 100644
--- a/services/audiopolicy/service/AudioPolicyInterfaceImpl.cpp
+++ b/services/audiopolicy/service/AudioPolicyInterfaceImpl.cpp
@@ -362,14 +362,22 @@
}
status_t AudioPolicyService::startInput(audio_io_handle_t input,
- audio_session_t session)
+ audio_session_t session,
+ audio_devices_t device,
+ uid_t uid,
+ bool *silenced)
{
+ // If UID inactive it records silence until becoming active
+ *silenced = !mUidPolicy->isUidActive(uid) && !is_virtual_input_device(device);
+
if (mAudioPolicyManager == NULL) {
return NO_INIT;
}
+
Mutex::Autolock _l(mLock);
- AudioPolicyInterface::concurrency_type__mask_t concurrency;
- status_t status = mAudioPolicyManager->startInput(input, session, &concurrency);
+ AudioPolicyInterface::concurrency_type__mask_t concurrency =
+ AudioPolicyInterface::API_INPUT_CONCURRENCY_NONE;
+ status_t status = mAudioPolicyManager->startInput(input, session, silenced, &concurrency);
if (status == NO_ERROR) {
LOG_ALWAYS_FATAL_IF(concurrency & ~AudioPolicyInterface::API_INPUT_CONCURRENCY_ALL,
diff --git a/services/audiopolicy/service/AudioPolicyService.cpp b/services/audiopolicy/service/AudioPolicyService.cpp
index af0c823..e5aed9a 100644
--- a/services/audiopolicy/service/AudioPolicyService.cpp
+++ b/services/audiopolicy/service/AudioPolicyService.cpp
@@ -28,6 +28,9 @@
#include <utils/Log.h>
#include <cutils/properties.h>
#include <binder/IPCThreadState.h>
+#include <binder/ActivityManager.h>
+#include <binder/PermissionController.h>
+#include <binder/IResultReceiver.h>
#include <utils/String16.h>
#include <utils/threads.h>
#include "AudioPolicyService.h"
@@ -39,6 +42,8 @@
#include <system/audio.h>
#include <system/audio_policy.h>
+#include <private/android_filesystem_config.h>
+
namespace android {
static const char kDeadlockedString[] = "AudioPolicyService may be deadlocked\n";
@@ -49,6 +54,7 @@
static const nsecs_t kAudioCommandTimeoutNs = seconds(3); // 3 seconds
+static const String16 sManageAudioPolicyPermission("android.permission.MANAGE_AUDIO_POLICY");
// ----------------------------------------------------------------------------
@@ -79,6 +85,9 @@
Mutex::Autolock _l(mLock);
mAudioPolicyEffects = audioPolicyEffects;
}
+
+ mUidPolicy = new UidPolicy(this);
+ mUidPolicy->registerSelf();
}
AudioPolicyService::~AudioPolicyService()
@@ -92,6 +101,9 @@
mNotificationClients.clear();
mAudioPolicyEffects.clear();
+
+ mUidPolicy->unregisterSelf();
+ mUidPolicy.clear();
}
// A notification client is always registered by AudioSystem when the client process
@@ -318,6 +330,20 @@
return NO_ERROR;
}
+void AudioPolicyService::setRecordSilenced(uid_t uid, bool silenced)
+{
+ {
+ Mutex::Autolock _l(mLock);
+ if (mAudioPolicyManager) {
+ mAudioPolicyManager->setRecordSilenced(uid, silenced);
+ }
+ }
+ sp<IAudioFlinger> af = AudioSystem::get_audio_flinger();
+ if (af) {
+ af->setRecordSilenced(uid, silenced);
+ }
+}
+
status_t AudioPolicyService::dump(int fd, const Vector<String16>& args __unused)
{
if (!dumpAllowed()) {
@@ -361,11 +387,210 @@
}
status_t AudioPolicyService::onTransact(
- uint32_t code, const Parcel& data, Parcel* reply, uint32_t flags)
-{
+ uint32_t code, const Parcel& data, Parcel* reply, uint32_t flags) {
+ switch (code) {
+ case SHELL_COMMAND_TRANSACTION: {
+ int in = data.readFileDescriptor();
+ int out = data.readFileDescriptor();
+ int err = data.readFileDescriptor();
+ int argc = data.readInt32();
+ Vector<String16> args;
+ for (int i = 0; i < argc && data.dataAvail() > 0; i++) {
+ args.add(data.readString16());
+ }
+ sp<IBinder> unusedCallback;
+ sp<IResultReceiver> resultReceiver;
+ status_t status;
+ if ((status = data.readNullableStrongBinder(&unusedCallback)) != NO_ERROR) {
+ return status;
+ }
+ if ((status = data.readNullableStrongBinder(&resultReceiver)) != NO_ERROR) {
+ return status;
+ }
+ status = shellCommand(in, out, err, args);
+ if (resultReceiver != nullptr) {
+ resultReceiver->send(status);
+ }
+ return NO_ERROR;
+ }
+ }
+
return BnAudioPolicyService::onTransact(code, data, reply, flags);
}
+// ------------------- Shell command implementation -------------------
+
+// NOTE: This is a remote API - make sure all args are validated
+status_t AudioPolicyService::shellCommand(int in, int out, int err, Vector<String16>& args) {
+ if (!checkCallingPermission(sManageAudioPolicyPermission, nullptr, nullptr)) {
+ return PERMISSION_DENIED;
+ }
+ if (in == BAD_TYPE || out == BAD_TYPE || err == BAD_TYPE) {
+ return BAD_VALUE;
+ }
+ if (args.size() == 3 && args[0] == String16("set-uid-state")) {
+ return handleSetUidState(args, err);
+ } else if (args.size() == 2 && args[0] == String16("reset-uid-state")) {
+ return handleResetUidState(args, err);
+ } else if (args.size() == 2 && args[0] == String16("get-uid-state")) {
+ return handleGetUidState(args, out, err);
+ } else if (args.size() == 1 && args[0] == String16("help")) {
+ printHelp(out);
+ return NO_ERROR;
+ }
+ printHelp(err);
+ return BAD_VALUE;
+}
+
+status_t AudioPolicyService::handleSetUidState(Vector<String16>& args, int err) {
+ PermissionController pc;
+ int uid = pc.getPackageUid(args[1], 0);
+ if (uid <= 0) {
+ ALOGE("Unknown package: '%s'", String8(args[1]).string());
+ dprintf(err, "Unknown package: '%s'\n", String8(args[1]).string());
+ return BAD_VALUE;
+ }
+ bool active = false;
+ if (args[2] == String16("active")) {
+ active = true;
+ } else if ((args[2] != String16("idle"))) {
+ ALOGE("Expected active or idle but got: '%s'", String8(args[2]).string());
+ return BAD_VALUE;
+ }
+ mUidPolicy->addOverrideUid(uid, active);
+ return NO_ERROR;
+}
+
+status_t AudioPolicyService::handleResetUidState(Vector<String16>& args, int err) {
+ PermissionController pc;
+ int uid = pc.getPackageUid(args[1], 0);
+ if (uid < 0) {
+ ALOGE("Unknown package: '%s'", String8(args[1]).string());
+ dprintf(err, "Unknown package: '%s'\n", String8(args[1]).string());
+ return BAD_VALUE;
+ }
+ mUidPolicy->removeOverrideUid(uid);
+ return NO_ERROR;
+}
+
+status_t AudioPolicyService::handleGetUidState(Vector<String16>& args, int out, int err) {
+ PermissionController pc;
+ int uid = pc.getPackageUid(args[1], 0);
+ if (uid < 0) {
+ ALOGE("Unknown package: '%s'", String8(args[1]).string());
+ dprintf(err, "Unknown package: '%s'\n", String8(args[1]).string());
+ return BAD_VALUE;
+ }
+ if (mUidPolicy->isUidActive(uid)) {
+ return dprintf(out, "active\n");
+ } else {
+ return dprintf(out, "idle\n");
+ }
+}
+
+status_t AudioPolicyService::printHelp(int out) {
+ return dprintf(out, "Audio policy service commands:\n"
+ " get-uid-state <PACKAGE> gets the uid state\n"
+ " set-uid-state <PACKAGE> <active|idle> overrides the uid state\n"
+ " reset-uid-state <PACKAGE> clears the uid state override\n"
+ " help print this message\n");
+}
+
+// ----------- AudioPolicyService::UidPolicy implementation ----------
+
+void AudioPolicyService::UidPolicy::registerSelf() {
+ ActivityManager am;
+ am.registerUidObserver(this, ActivityManager::UID_OBSERVER_GONE
+ | ActivityManager::UID_OBSERVER_IDLE
+ | ActivityManager::UID_OBSERVER_ACTIVE,
+ ActivityManager::PROCESS_STATE_UNKNOWN,
+ String16("audioserver"));
+}
+
+void AudioPolicyService::UidPolicy::unregisterSelf() {
+ ActivityManager am;
+ am.unregisterUidObserver(this);
+}
+
+void AudioPolicyService::UidPolicy::onUidGone(uid_t uid, __unused bool disabled) {
+ onUidIdle(uid, disabled);
+}
+
+void AudioPolicyService::UidPolicy::onUidActive(uid_t uid) {
+ {
+ Mutex::Autolock _l(mUidLock);
+ mActiveUids.insert(uid);
+ }
+ sp<AudioPolicyService> service = mService.promote();
+ if (service != nullptr) {
+ service->setRecordSilenced(uid, false);
+ }
+}
+
+void AudioPolicyService::UidPolicy::onUidIdle(uid_t uid, __unused bool disabled) {
+ bool deleted = false;
+ {
+ Mutex::Autolock _l(mUidLock);
+ if (mActiveUids.erase(uid) > 0) {
+ deleted = true;
+ }
+ }
+ if (deleted) {
+ sp<AudioPolicyService> service = mService.promote();
+ if (service != nullptr) {
+ service->setRecordSilenced(uid, true);
+ }
+ }
+}
+
+void AudioPolicyService::UidPolicy::addOverrideUid(uid_t uid, bool active) {
+ updateOverrideUid(uid, active, true);
+}
+
+void AudioPolicyService::UidPolicy::removeOverrideUid(uid_t uid) {
+ updateOverrideUid(uid, false, false);
+}
+
+void AudioPolicyService::UidPolicy::updateOverrideUid(uid_t uid, bool active, bool insert) {
+ bool wasActive = false;
+ bool isActive = false;
+ {
+ Mutex::Autolock _l(mUidLock);
+ wasActive = isUidActiveLocked(uid);
+ mOverrideUids.erase(uid);
+ if (insert) {
+ mOverrideUids.insert(std::pair<uid_t, bool>(uid, active));
+ }
+ isActive = isUidActiveLocked(uid);
+ }
+ if (wasActive != isActive) {
+ sp<AudioPolicyService> service = mService.promote();
+ if (service != nullptr) {
+ service->setRecordSilenced(uid, !isActive);
+ }
+ }
+}
+
+bool AudioPolicyService::UidPolicy::isUidActive(uid_t uid) {
+ // Non-app UIDs are considered always active
+ if (uid < FIRST_APPLICATION_UID) {
+ return true;
+ }
+ Mutex::Autolock _l(mUidLock);
+ return isUidActiveLocked(uid);
+}
+
+bool AudioPolicyService::UidPolicy::isUidActiveLocked(uid_t uid) {
+ // Non-app UIDs are considered always active
+ if (uid < FIRST_APPLICATION_UID) {
+ return true;
+ }
+ auto it = mOverrideUids.find(uid);
+ if (it != mOverrideUids.end()) {
+ return it->second;
+ }
+ return mActiveUids.find(uid) != mActiveUids.end();
+}
// ----------- AudioPolicyService::AudioCommandThread implementation ----------
diff --git a/services/audiopolicy/service/AudioPolicyService.h b/services/audiopolicy/service/AudioPolicyService.h
index 833a230..9bc13c2 100644
--- a/services/audiopolicy/service/AudioPolicyService.h
+++ b/services/audiopolicy/service/AudioPolicyService.h
@@ -24,6 +24,7 @@
#include <utils/Vector.h>
#include <utils/SortedVector.h>
#include <binder/BinderService.h>
+#include <binder/IUidObserver.h>
#include <system/audio.h>
#include <system/audio_policy.h>
#include <media/IAudioPolicyService.h>
@@ -33,9 +34,13 @@
#include "AudioPolicyEffects.h"
#include "managerdefault/AudioPolicyManager.h"
+#include <unordered_map>
+#include <unordered_set>
namespace android {
+using namespace std;
+
// ----------------------------------------------------------------------------
class AudioPolicyService :
@@ -97,7 +102,10 @@
audio_port_handle_t *selectedDeviceId = NULL,
audio_port_handle_t *portId = NULL);
virtual status_t startInput(audio_io_handle_t input,
- audio_session_t session);
+ audio_session_t session,
+ audio_devices_t device,
+ uid_t uid,
+ bool *silenced);
virtual status_t stopInput(audio_io_handle_t input,
audio_session_t session);
virtual void releaseInput(audio_io_handle_t input,
@@ -235,6 +243,57 @@
status_t dumpInternals(int fd);
+ // Handles binder shell commands
+ virtual status_t shellCommand(int in, int out, int err, Vector<String16>& args);
+
+ // Sets whether the given UID records only silence
+ virtual void setRecordSilenced(uid_t uid, bool silenced);
+
+ // Overrides the UID state as if it is idle
+ status_t handleSetUidState(Vector<String16>& args, int err);
+
+ // Clears the override for the UID state
+ status_t handleResetUidState(Vector<String16>& args, int err);
+
+ // Gets the UID state
+ status_t handleGetUidState(Vector<String16>& args, int out, int err);
+
+ // Prints the shell command help
+ status_t printHelp(int out);
+
+ // If recording we need to make sure the UID is allowed to do that. If the UID is idle
+ // then it cannot record and gets buffers with zeros - silence. As soon as the UID
+ // transitions to an active state we will start reporting buffers with data. This approach
+ // transparently handles recording while the UID transitions between idle/active state
+ // avoiding to get stuck in a state receiving non-empty buffers while idle or in a state
+ // receiving empty buffers while active.
+ class UidPolicy : public BnUidObserver {
+ public:
+ explicit UidPolicy(wp<AudioPolicyService> service)
+ : mService(service) {}
+
+ void registerSelf();
+ void unregisterSelf();
+
+ bool isUidActive(uid_t uid);
+
+ void onUidGone(uid_t uid, bool disabled);
+ void onUidActive(uid_t uid);
+ void onUidIdle(uid_t uid, bool disabled);
+
+ void addOverrideUid(uid_t uid, bool active);
+ void removeOverrideUid(uid_t uid);
+
+ private:
+ bool isUidActiveLocked(uid_t uid);
+ void updateOverrideUid(uid_t uid, bool active, bool insert);
+
+ Mutex mUidLock;
+ wp<AudioPolicyService> mService;
+ std::unordered_set<uid_t> mActiveUids;
+ std::unordered_map<uid_t, bool> mOverrideUids;
+ };
+
// Thread used for tone playback and to send audio config commands to audio flinger
// For tone playback, using a separate thread is necessary to avoid deadlock with mLock because
// startTone() and stopTone() are normally called with mLock locked and requesting a tone start
@@ -306,7 +365,6 @@
const audio_config_base_t *deviceConfig,
audio_patch_handle_t patchHandle);
void insertCommand_l(AudioCommand *command, int delayMs = 0);
-
private:
class AudioCommandData;
@@ -575,6 +633,8 @@
// Manage all effects configured in audio_effects.conf
sp<AudioPolicyEffects> mAudioPolicyEffects;
audio_mode_t mPhoneState;
+
+ sp<UidPolicy> mUidPolicy;
};
} // namespace android