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

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

Change external capture state notification mechanism

Previously, external capture state notifications were delivered from
the audio policy manager to the sound trigger service as normal
"forward calls". However this creates a cyclic dependency between
sound trigger and APM, which is especially problematic during startup,
when the audio server is assumed to start prior to the system server
and thus cannot block on the sound trigger service becoming available.

With this change, the sound trigger service would register a callback
with APM for these notifications.

A small refactoring was needed in order to keep the internal interface
of sound trigger classes having the setExternalCapture() methods,
while removing them from the external interface.

Test: Manual verification, checking the logs, validating behavior in
      response to killing the audioserver process.
Bug: 146157104
Change-Id: I4b4467fbac3607ee170394dc1b3309e7e3d422d8
parent 762bfe6d
Loading
Loading
Loading
Loading
+0 −6
Original line number Diff line number Diff line
@@ -39,10 +39,4 @@ interface ISoundTriggerMiddlewareService {
     * one of the handles from the returned list.
     */
    ISoundTriggerModule attach(int handle, ISoundTriggerCallback callback);

    /**
     * Notify the service that external input capture is taking place. This may cause some of the
     * active recognitions to be aborted.
     */
    void setExternalCaptureState(boolean active);
}
 No newline at end of file
+1 −0
Original line number Diff line number Diff line
@@ -126,6 +126,7 @@ java_library_static {
        "android.hardware.rebootescrow-java",
        "android.hardware.soundtrigger-V2.3-java",
        "android.hidl.manager-V1.2-java",
        "capture_state_listener-aidl-java",
        "dnsresolver_aidl_interface-V2-java",
        "netd_event_listener_interface-java",
        "overlayable_policy_aidl-java",
+157 −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.
 */

package com.android.server.soundtrigger_middleware;

import android.media.ICaptureStateListener;
import android.os.IBinder;
import android.os.Parcel;
import android.os.RemoteException;
import android.os.ServiceManager;
import android.util.Log;

import java.util.concurrent.Semaphore;
import java.util.function.Consumer;

/**
 * This is a never-give-up listener for sound trigger external capture state notifications, as
 * published by the audio policy service.
 *
 * This class will constantly try to connect to the service over a background thread and tolerate
 * its death. The client will be notified by a single provided function that is called in a
 * synchronized manner.
 * For simplicity, there is currently no way to stop the tracker. This is possible to add if the
 * need ever arises.
 */
public 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;
        mListener = listener;
        new Thread(this::run).start();
    }

    /**
     * Routine for the background thread. Keeps trying to reconnect.
     */
    private void run() {
        while (true) {
            mNeedToConnect.acquireUninterruptibly();
            connectWithRetry();
        }
    }

    /**
     * Try to connect. Retry in case of RemoteException.
     */
    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;
            }
        }
    }

    /**
     * Connect to the service, install listener and death notifier.
     *
     * @throws RemoteException In case of a binder issue.
     */
    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);
            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.
     */
    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);
            }
        }
    }
}
+27 −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.
 */

package com.android.server.soundtrigger_middleware;

import android.media.ICaptureStateListener;
import android.media.soundtrigger_middleware.ISoundTriggerMiddlewareService;

/**
 * This interface unifies ISoundTriggerMiddlewareService with ICaptureStateListener.
 */
public interface ISoundTriggerMiddlewareInternal extends ISoundTriggerMiddlewareService,
        ICaptureStateListener {
}
+2 −2
Original line number Diff line number Diff line
@@ -50,7 +50,7 @@ import java.util.List;
 *
 * @hide
 */
public class SoundTriggerMiddlewareImpl implements ISoundTriggerMiddlewareService {
public class SoundTriggerMiddlewareImpl implements ISoundTriggerMiddlewareInternal {
    static private final String TAG = "SoundTriggerMiddlewareImpl";
    private final SoundTriggerModule[] mModules;

@@ -124,7 +124,7 @@ public class SoundTriggerMiddlewareImpl implements ISoundTriggerMiddlewareServic
    }

    @Override
    public void setExternalCaptureState(boolean active) {
    public void setCaptureState(boolean active) {
        for (SoundTriggerModule module : mModules) {
            module.setExternalCaptureState(active);
        }
Loading