Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit ed726ccd authored by Eric Laurent's avatar Eric Laurent
Browse files

audio policy: report app ops silencing audio recordings

Move audio recording app op monitoring from audio flinger to audio
policy service.
This groups all recording silencing logic in one place and allows for
proper reporting of the audio record silencing via RecordingActivityMonitor.

Bug: 191040756
Test: run app that records audio, then $adb shell appops set <UID> 27 2
Test: atest GtsSuspendAppsTestCases:SuspendPackagesTest
Change-Id: I12c30b1764b5e3d74612c6727a780026295cd367
parent c47a784d
Loading
Loading
Loading
Loading
+1 −39
Original line number Diff line number Diff line
@@ -21,42 +21,6 @@
    #error This header file should only be included from AudioFlinger.h
#endif

// Checks and monitors app ops for audio record
class OpRecordAudioMonitor : public RefBase {
public:
    ~OpRecordAudioMonitor() override;
    bool hasOp() const;
    int32_t getOp() const { return mAppOp; }

    static sp<OpRecordAudioMonitor> createIfNeeded(const AttributionSourceState& attributionSource,
            const audio_attributes_t& attr);

private:
    OpRecordAudioMonitor(const AttributionSourceState& attributionSource, int32_t appOp);

    void onFirstRef() override;

    AppOpsManager mAppOpsManager;

    class RecordAudioOpCallback : public BnAppOpsCallback {
    public:
        explicit RecordAudioOpCallback(const wp<OpRecordAudioMonitor>& monitor);
        void opChanged(int32_t op, const String16& packageName) override;

    private:
        const wp<OpRecordAudioMonitor> mMonitor;
    };

    sp<RecordAudioOpCallback> mOpCallback;
    // called by RecordAudioOpCallback when the app op for this OpRecordAudioMonitor is updated
    // in AppOp callback and in onFirstRef()
    void checkOp();

    std::atomic_bool mHasOp;
    const AttributionSourceState mAttributionSource;
    const int32_t mAppOp;
};

// record track
class RecordTrack : public TrackBase {
public:
@@ -107,7 +71,7 @@ public:
                                { return (mFlags & AUDIO_INPUT_FLAG_DIRECT) != 0; }

            void        setSilenced(bool silenced) { if (!isPatchTrack()) mSilenced = silenced; }
            bool        isSilenced() const;
            bool        isSilenced() const { return mSilenced; }

            status_t    getActiveMicrophones(std::vector<media::MicrophoneInfo>* activeMicrophones);

@@ -154,8 +118,6 @@ private:

            bool                               mSilenced;

            // used to enforce the audio record app op corresponding to this track's audio source
            sp<OpRecordAudioMonitor>           mOpRecordAudioMonitor;
            std::string                        mSharedAudioPackageName = {};
            int32_t                            mStartFrames = -1;
};
+0 −112
Original line number Diff line number Diff line
@@ -2247,109 +2247,6 @@ void AudioFlinger::PlaybackThread::PatchTrack::restartIfDisabled()
// ----------------------------------------------------------------------------


// ----------------------------------------------------------------------------
//      AppOp for audio recording
// -------------------------------

#undef LOG_TAG
#define LOG_TAG "AF::OpRecordAudioMonitor"

// static
sp<AudioFlinger::RecordThread::OpRecordAudioMonitor>
AudioFlinger::RecordThread::OpRecordAudioMonitor::createIfNeeded(
            const AttributionSourceState& attributionSource, const audio_attributes_t& attr)
{
    if (isServiceUid(attributionSource.uid)) {
        ALOGV("not silencing record for service %s",
                attributionSource.toString().c_str());
        return nullptr;
    }

    // Capturing from FM TUNER output is not controlled by an app op
    // because it does not affect users privacy as does capturing from an actual microphone.
    if (attr.source == AUDIO_SOURCE_FM_TUNER) {
        ALOGV("not muting FM TUNER capture for uid %d", attributionSource.uid);
        return nullptr;
    }

    AttributionSourceState checkedAttributionSource = AudioFlinger::checkAttributionSourcePackage(
            attributionSource);
    if (!checkedAttributionSource.packageName.has_value()
            || checkedAttributionSource.packageName.value().size() == 0) {
        return nullptr;
    }
    return new OpRecordAudioMonitor(checkedAttributionSource, getOpForSource(attr.source));
}

AudioFlinger::RecordThread::OpRecordAudioMonitor::OpRecordAudioMonitor(
        const AttributionSourceState& attributionSource, int32_t appOp)
        : mHasOp(true), mAttributionSource(attributionSource), mAppOp(appOp)
{
}

AudioFlinger::RecordThread::OpRecordAudioMonitor::~OpRecordAudioMonitor()
{
    if (mOpCallback != 0) {
        mAppOpsManager.stopWatchingMode(mOpCallback);
    }
    mOpCallback.clear();
}

void AudioFlinger::RecordThread::OpRecordAudioMonitor::onFirstRef()
{
    checkOp();
    mOpCallback = new RecordAudioOpCallback(this);
    ALOGV("start watching op %d for %s", mAppOp, mAttributionSource.toString().c_str());
    // TODO: We need to always watch AppOpsManager::OP_RECORD_AUDIO too
    // since it controls the mic permission for legacy apps.
    mAppOpsManager.startWatchingMode(mAppOp, VALUE_OR_FATAL(aidl2legacy_string_view_String16(
        mAttributionSource.packageName.value_or(""))),
        mOpCallback);
}

bool AudioFlinger::RecordThread::OpRecordAudioMonitor::hasOp() const {
    return mHasOp.load();
}

// Called by RecordAudioOpCallback when the app op corresponding to this OpRecordAudioMonitor
// is updated in AppOp callback and in onFirstRef()
// Note this method is never called (and never to be) for audio server / root track
// due to the UID in createIfNeeded(). As a result for those record track, it's:
// - not called from constructor,
// - not called from RecordAudioOpCallback because the callback is not installed in this case
void AudioFlinger::RecordThread::OpRecordAudioMonitor::checkOp()
{
    // TODO: We need to always check AppOpsManager::OP_RECORD_AUDIO too
    // since it controls the mic permission for legacy apps.
    const int32_t mode = mAppOpsManager.checkOp(mAppOp,
            mAttributionSource.uid, VALUE_OR_FATAL(aidl2legacy_string_view_String16(
                mAttributionSource.packageName.value_or(""))));
    const bool hasIt = (mode == AppOpsManager::MODE_ALLOWED);
    // verbose logging only log when appOp changed
    ALOGI_IF(hasIt != mHasOp.load(),
            "App op %d missing, %ssilencing record %s",
            mAppOp, hasIt ? "un" : "", mAttributionSource.toString().c_str());
    mHasOp.store(hasIt);
}

AudioFlinger::RecordThread::OpRecordAudioMonitor::RecordAudioOpCallback::RecordAudioOpCallback(
        const wp<OpRecordAudioMonitor>& monitor) : mMonitor(monitor)
{ }

void AudioFlinger::RecordThread::OpRecordAudioMonitor::RecordAudioOpCallback::opChanged(int32_t op,
            const String16& packageName) {
    UNUSED(packageName);
    sp<OpRecordAudioMonitor> monitor = mMonitor.promote();
    if (monitor != NULL) {
        if (op != monitor->getOp()) {
            return;
        }
        monitor->checkOp();
    }
}



#undef LOG_TAG
#define LOG_TAG "AF::RecordHandle"

@@ -2450,7 +2347,6 @@ AudioFlinger::RecordThread::RecordTrack::RecordTrack(
        mRecordBufferConverter(NULL),
        mFlags(flags),
        mSilenced(false),
        mOpRecordAudioMonitor(OpRecordAudioMonitor::createIfNeeded(attributionSource, attr)),
        mStartFrames(startFrames)
{
    if (mCblk == NULL) {
@@ -2709,14 +2605,6 @@ void AudioFlinger::RecordThread::RecordTrack::updateTrackFrameInfo(
    mServerLatencyMs.store(latencyMs);
}

bool AudioFlinger::RecordThread::RecordTrack::isSilenced() const {
    if (mSilenced) {
        return true;
    }
    // The monitor is only created for record tracks that can be silenced.
    return mOpRecordAudioMonitor ? !mOpRecordAudioMonitor->hasOp() : false;
}

status_t AudioFlinger::RecordThread::RecordTrack::getActiveMicrophones(
        std::vector<media::MicrophoneInfo>* activeMicrophones)
{
+1 −0
Original line number Diff line number Diff line
@@ -38,6 +38,7 @@ cc_library_shared {
        "libmedia_helper",
        "libmediametrics",
        "libmediautils",
        "libpermission",
        "libsensorprivacy",
        "libutils",
        "audioclient-types-aidl-cpp",
+2 −1
Original line number Diff line number Diff line
@@ -674,7 +674,8 @@ Status AudioPolicyService::getInputForAttr(const media::AudioAttributesInternal&

        sp<AudioRecordClient> client = new AudioRecordClient(attr, input, session, portId,
                                                             selectedDeviceId, adjAttributionSource,
                                                             canCaptureOutput, canCaptureHotword);
                                                             canCaptureOutput, canCaptureHotword,
                                                             mAudioCommandThread);
        mAudioRecordClients.add(portId, client);
    }

+139 −1
Original line number Diff line number Diff line
@@ -730,7 +730,10 @@ void AudioPolicyService::updateUidStates_l()
                    && !(isTopOrLatestSensitive || current->canCaptureOutput))
                && canCaptureIfInCallOrCommunication(current);

        if (isVirtualSource(source)) {
        if (!current->hasOp()) {
            // Never allow capture if app op is denied
            allowCapture = false;
        } else if (isVirtualSource(source)) {
            // Allow capture for virtual (remote submix, call audio TX or RX...) sources
            allowCapture = true;
        } else if (mUidPolicy->isAssistantUid(currentUid)) {
@@ -830,6 +833,19 @@ bool AudioPolicyService::isVirtualSource(audio_source_t source)
    return false;
}

/* static */
bool AudioPolicyService::isAppOpSource(audio_source_t source)
{
    switch (source) {
        case AUDIO_SOURCE_FM_TUNER:
        case AUDIO_SOURCE_ECHO_REFERENCE:
            return false;
        default:
            break;
    }
    return true;
}

void AudioPolicyService::setAppState_l(sp<AudioRecordClient> client, app_state_t state)
{
    AutoCallerClear acc;
@@ -1418,6 +1434,109 @@ binder::Status AudioPolicyService::SensorPrivacyPolicy::onSensorPrivacyChanged(b
    return binder::Status::ok();
}

// -----------  AudioPolicyService::OpRecordAudioMonitor implementation ----------

// static
sp<AudioPolicyService::OpRecordAudioMonitor>
AudioPolicyService::OpRecordAudioMonitor::createIfNeeded(
            const AttributionSourceState& attributionSource, const audio_attributes_t& attr,
            wp<AudioCommandThread> commandThread)
{
    if (isServiceUid(attributionSource.uid)) {
        ALOGV("not silencing record for service %s",
                attributionSource.toString().c_str());
        return nullptr;
    }

    if (!AudioPolicyService::isAppOpSource(attr.source)) {
        ALOGD("not monitoring app op for uid %d and source %d",
                attributionSource.uid, attr.source);
        return nullptr;
    }

    if (!attributionSource.packageName.has_value()
            || attributionSource.packageName.value().size() == 0) {
        return nullptr;
    }
    return new OpRecordAudioMonitor(attributionSource, getOpForSource(attr.source), commandThread);
}

AudioPolicyService::OpRecordAudioMonitor::OpRecordAudioMonitor(
        const AttributionSourceState& attributionSource, int32_t appOp,
        wp<AudioCommandThread> commandThread) :
            mHasOp(true), mAttributionSource(attributionSource), mAppOp(appOp),
            mCommandThread(commandThread)
{
}

AudioPolicyService::OpRecordAudioMonitor::~OpRecordAudioMonitor()
{
    if (mOpCallback != 0) {
        mAppOpsManager.stopWatchingMode(mOpCallback);
    }
    mOpCallback.clear();
}

void AudioPolicyService::OpRecordAudioMonitor::onFirstRef()
{
    checkOp();
    mOpCallback = new RecordAudioOpCallback(this);
    ALOGV("start watching op %d for %s", mAppOp, mAttributionSource.toString().c_str());
    // TODO: We need to always watch AppOpsManager::OP_RECORD_AUDIO too
    // since it controls the mic permission for legacy apps.
    mAppOpsManager.startWatchingMode(mAppOp, VALUE_OR_FATAL(aidl2legacy_string_view_String16(
        mAttributionSource.packageName.value_or(""))),
        mOpCallback);
}

bool AudioPolicyService::OpRecordAudioMonitor::hasOp() const {
    return mHasOp.load();
}

// Called by RecordAudioOpCallback when the app op corresponding to this OpRecordAudioMonitor
// is updated in AppOp callback and in onFirstRef()
// Note this method is never called (and never to be) for audio server / root track
// due to the UID in createIfNeeded(). As a result for those record track, it's:
// - not called from constructor,
// - not called from RecordAudioOpCallback because the callback is not installed in this case
void AudioPolicyService::OpRecordAudioMonitor::checkOp(bool updateUidStates)
{
    // TODO: We need to always check AppOpsManager::OP_RECORD_AUDIO too
    // since it controls the mic permission for legacy apps.
    const int32_t mode = mAppOpsManager.checkOp(mAppOp,
            mAttributionSource.uid, VALUE_OR_FATAL(aidl2legacy_string_view_String16(
                mAttributionSource.packageName.value_or(""))));
    const bool hasIt = (mode == AppOpsManager::MODE_ALLOWED);
    // verbose logging only log when appOp changed
    ALOGI_IF(hasIt != mHasOp.load(),
            "App op %d missing, %ssilencing record %s",
            mAppOp, hasIt ? "un" : "", mAttributionSource.toString().c_str());
    mHasOp.store(hasIt);

    if (updateUidStates) {
          sp<AudioCommandThread> commandThread = mCommandThread.promote();
          if (commandThread != nullptr) {
              commandThread->updateUidStatesCommand();
          }
    }
}

AudioPolicyService::OpRecordAudioMonitor::RecordAudioOpCallback::RecordAudioOpCallback(
        const wp<OpRecordAudioMonitor>& monitor) : mMonitor(monitor)
{ }

void AudioPolicyService::OpRecordAudioMonitor::RecordAudioOpCallback::opChanged(int32_t op,
            const String16& packageName __unused) {
    sp<OpRecordAudioMonitor> monitor = mMonitor.promote();
    if (monitor != NULL) {
        if (op != monitor->getOp()) {
            return;
        }
        monitor->checkOp(true);
    }
}


// -----------  AudioPolicyService::AudioCommandThread implementation ----------

AudioPolicyService::AudioCommandThread::AudioCommandThread(String8 name,
@@ -1634,6 +1753,17 @@ bool AudioPolicyService::AudioCommandThread::threadLoop()
                    mLock.lock();
                    } break;

                case UPDATE_UID_STATES: {
                    ALOGV("AudioCommandThread() processing updateUID states");
                    svc = mService.promote();
                    if (svc == 0) {
                        break;
                    }
                    mLock.unlock();
                    svc->updateUidStates();
                    mLock.lock();
                    } break;

                default:
                    ALOGW("AudioCommandThread() unknown command %d", command->mCommand);
                }
@@ -1847,6 +1977,14 @@ void AudioPolicyService::AudioCommandThread::updateAudioPortListCommand()
    sendCommand(command);
}

void AudioPolicyService::AudioCommandThread::updateUidStatesCommand()
{
    sp<AudioCommand> command = new AudioCommand();
    command->mCommand = UPDATE_UID_STATES;
    ALOGV("AudioCommandThread() adding update UID states");
    sendCommand(command);
}

void AudioPolicyService::AudioCommandThread::updateAudioPatchListCommand()
{
    sp<AudioCommand>command = new AudioCommand();
Loading