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

Commit d1afa087 authored by Ytai Ben-Tsvi's avatar Ytai Ben-Tsvi
Browse files

Register for capture state notifications via JNI

This change replaces the direct binder connection of the sound trigger
middleware service to the audio policy service with going through
AudioSystem.h through JNI.

Bug: 146157104
Test: Manual verification of proper behavior when starting/stopping
      recording, killing of audio server, by examination of the logs.
Merged-In: I9d9a61653e968b63e3de36c0cc7f5925e06204c6
Change-Id: Idde6179580dc5b8eb638a74e7f9b44707d6ca179
parent 152e25b8
Loading
Loading
Loading
Loading
+18 −84
Original line number Diff line number Diff line
@@ -36,40 +36,23 @@ import java.util.function.Consumer;
 * For simplicity, there is currently no way to stop the tracker. This is possible to add if the
 * need ever arises.
 */
public class ExternalCaptureStateTracker {
class ExternalCaptureStateTracker {
    private static final String TAG = "CaptureStateTracker";
    /** Our client's listener. */
    private final Consumer<Boolean> mListener;
    /** A lock used to ensure synchronized access to mListener. */
    private final Object mListenerLock = new Object();
    /**
     * The binder listener that we're providing to the audio policy service. Ensures synchronized
     * access to mListener.
     */
    private final Listener mSyncListener = new Listener();
    /** The name of the audio policy service. */
    private final String mAudioPolicyServiceName;
    /** This semaphore will get a permit every time we need to reconnect. */
    private final Semaphore mNeedToConnect = new Semaphore(1);
    /**
     * We must hold a reference to the APM service, even though we're not actually using it after
     * installing the callback. Otherwise, binder silently un-links our death listener.
     */
    private IBinder mService;

    /**
     * Constructor. Will start a background thread to do the work.
     *
     * @param audioPolicyServiceName The name of the audio policy service to connect to.
     * @param listener A client provided listener that will be called on state
     *                 changes. May be
     *                 called multiple consecutive times with the same value. Never
     *                 called
     *                 concurrently.
     */
    public ExternalCaptureStateTracker(String audioPolicyServiceName,
            Consumer<Boolean> listener) {
        mAudioPolicyServiceName = audioPolicyServiceName;
    ExternalCaptureStateTracker(Consumer<Boolean> listener) {
        mListener = listener;
        new Thread(this::run).start();
    }
@@ -80,78 +63,29 @@ public class ExternalCaptureStateTracker {
    private void run() {
        while (true) {
            mNeedToConnect.acquireUninterruptibly();
            connectWithRetry();
            connect();
        }
    }

    /**
     * Try to connect. Retry in case of RemoteException.
     * Connect to the service, install listener and death notifier.
     */
    private void connectWithRetry() {
        while (true) {
            try {
                connect();
                return;
            } catch (RemoteException e) {
                Log.w(TAG, "Exception caught trying to connect", e);
            } catch (ServiceManager.ServiceNotFoundException e) {
                Log.w(TAG, "Service not yet available, waiting", e);
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException ex) {
                }
            } catch (Exception e) {
                Log.e(TAG, "Unexpected exception caught trying to connect", e);
                throw e;
            }
        }
    }
    private native void connect();

    /**
     * Connect to the service, install listener and death notifier.
     * Called by native code to invoke the client listener.
     *
     * @throws RemoteException In case of a binder issue.
     * @param active true when external capture is active.
     */
    private void connect() throws RemoteException, ServiceManager.ServiceNotFoundException {
        Log.d(TAG, "Connecting to audio policy service: " + mAudioPolicyServiceName);
        mService = ServiceManager.getServiceOrThrow(mAudioPolicyServiceName);

        synchronized (mListenerLock) {
            boolean active = registerSoundTriggerCaptureStateListener(mService, mSyncListener);
    private void setCaptureState(boolean active) {
        mListener.accept(active);
    }

        mService.linkToDeath(() -> {
            Log.w(TAG, "Audio policy service died");
            mNeedToConnect.release();
        }, 0);
    }

    /**
     * Since the audio policy service does not have an AIDL interface, this method does the
     * necessary manual marshalling.
     *
     * @param service  The service binder object.
     * @param listener The listener binder object to register.
     * @return The active state at the time of registration.
     * Called by native code when the remote service died.
     */
    private boolean registerSoundTriggerCaptureStateListener(IBinder service,
            ICaptureStateListener listener) throws RemoteException {
        Parcel request = Parcel.obtain();
        Parcel response = Parcel.obtain();
        request.writeInterfaceToken("android.media.IAudioPolicyService");
        request.writeStrongBinder(listener.asBinder());
        service.transact(82 /* REGISTER_SOUNDTRIGGER_CAPTURE_STATE_LISTENER */, request, response,
                0);
        return response.readBoolean();
    }

    private class Listener extends ICaptureStateListener.Stub {
        @Override
        public void setCaptureState(boolean active) {
            synchronized (mListenerLock) {
                mListener.accept(active);
            }
        }
    private void binderDied() {
        Log.w(TAG, "Audio policy service died");
        mNeedToConnect.release();
    }
}
+1 −1
Original line number Diff line number Diff line
@@ -71,7 +71,7 @@ public class SoundTriggerMiddlewareService extends ISoundTriggerMiddlewareServic
     */
    private SoundTriggerMiddlewareService(@NonNull ISoundTriggerMiddlewareInternal delegate) {
        mDelegate = Objects.requireNonNull(delegate);
        new ExternalCaptureStateTracker("media.audio_policy", active -> {
        new ExternalCaptureStateTracker(active -> {
            try {
                mDelegate.setCaptureState(active);
            } catch (RemoteException e) {
+1 −0
Original line number Diff line number Diff line
@@ -40,6 +40,7 @@ cc_library_static {
        "com_android_server_security_VerityUtils.cpp",
        "com_android_server_SerialService.cpp",
        "com_android_server_soundtrigger_middleware_AudioSessionProviderImpl.cpp",
        "com_android_server_soundtrigger_middleware_ExternalCaptureStateTracker.cpp",
        "com_android_server_stats_pull_StatsPullAtomService.cpp",
        "com_android_server_storage_AppFuseBridge.cpp",
        "com_android_server_SystemServer.cpp",
+93 −0
Original line number Diff line number Diff line
/*
 * Copyright (C) 2020 The Android Open Source Project
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

#include <sstream>

#define LOG_TAG "ExternalCaptureStateTracker"

#include "core_jni_helpers.h"
#include <log/log.h>
#include <media/AudioSystem.h>

namespace android {
namespace {

#define PACKAGE "com/android/server/soundtrigger_middleware"
#define CLASSNAME PACKAGE "/ExternalCaptureStateTracker"

jclass gExternalCaptureStateTrackerClassId;
jmethodID gSetCaptureStateMethodId;
jmethodID gBinderDiedMethodId;

void PopulateIds(JNIEnv* env) {
    gExternalCaptureStateTrackerClassId =
        (jclass) env->NewGlobalRef(FindClassOrDie(env, CLASSNAME));
    gSetCaptureStateMethodId = GetMethodIDOrDie(env,
                                                gExternalCaptureStateTrackerClassId,
                                                "setCaptureState",
                                                "(Z)V");
    gBinderDiedMethodId = GetMethodIDOrDie(env,
                                           gExternalCaptureStateTrackerClassId,
                                           "binderDied",
                                           "()V");
}

class Listener : public AudioSystem::CaptureStateListener {
public:
    Listener(JNIEnv* env, jobject obj) : mObj(env->NewGlobalRef(obj)) {}

    ~Listener() {
        JNIEnv* env = AndroidRuntime::getJNIEnv();
        env->DeleteGlobalRef(mObj);
    }

    void onStateChanged(bool active) override {
        JNIEnv* env = AndroidRuntime::getJNIEnv();
        env->CallVoidMethod(mObj, gSetCaptureStateMethodId, active);
    }

    void onServiceDied() override {
        JNIEnv* env = AndroidRuntime::getJNIEnv();
        env->CallVoidMethod(mObj, gBinderDiedMethodId);
    }

private:
    jobject mObj;
};

void connect(JNIEnv* env, jobject obj) {
    sp<AudioSystem::CaptureStateListener> listener(new Listener(env, obj));
    status_t status =
        AudioSystem::registerSoundTriggerCaptureStateListener(listener);
    LOG_ALWAYS_FATAL_IF(status != NO_ERROR);
}

const JNINativeMethod gMethods[] = {
    {"connect", "()V", reinterpret_cast<void*>(connect)},
};

}  // namespace

int register_com_android_server_soundtrigger_middleware_ExternalCaptureStateTracker(
    JNIEnv* env) {
    PopulateIds(env);
    return RegisterMethodsOrDie(env,
                                CLASSNAME,
                                gMethods,
                                NELEM(gMethods));
}

} // namespace android
+4 −0
Original line number Diff line number Diff line
@@ -58,6 +58,8 @@ int register_android_server_am_CachedAppOptimizer(JNIEnv* env);
int register_android_server_am_LowMemDetector(JNIEnv* env);
int register_com_android_server_soundtrigger_middleware_AudioSessionProviderImpl(
        JNIEnv* env);
int register_com_android_server_soundtrigger_middleware_ExternalCaptureStateTracker(
    JNIEnv* env);
int register_android_server_com_android_server_pm_PackageManagerShellCommandDataLoader(JNIEnv* env);
int register_android_server_stats_pull_StatsPullAtomService(JNIEnv* env);
int register_android_server_AdbDebuggingManager(JNIEnv* env);
@@ -112,6 +114,8 @@ extern "C" jint JNI_OnLoad(JavaVM* vm, void* /* reserved */)
    register_android_server_am_LowMemDetector(env);
    register_com_android_server_soundtrigger_middleware_AudioSessionProviderImpl(
            env);
    register_com_android_server_soundtrigger_middleware_ExternalCaptureStateTracker(
        env);
    register_android_server_com_android_server_pm_PackageManagerShellCommandDataLoader(env);
    register_android_server_stats_pull_StatsPullAtomService(env);
    register_android_server_AdbDebuggingManager(env);