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

Commit 935e33f4 authored by Nicholas Ambur's avatar Nicholas Ambur
Browse files

API to destroy active detectors

The active AlwaysOnHotwordDetector and SoftwareHotwordDetector is always
maintained  in memory regardless if the client is using it or not.
This added API allows an active VoiceInteractionService to indicate that
they will no longer use the detector, and it can be cleaned up.

Test: atest HotwordDetectionServiceBasicTest
Bug: 193232191
Change-Id: I47c6c64c5c85c01e75ddc6bc504664883a57730b
parent 2f7b2d5f
Loading
Loading
Loading
Loading
+1 −0
Original line number Diff line number Diff line
@@ -11841,6 +11841,7 @@ package android.service.voice {
  }
  public interface HotwordDetector {
    method public default void destroy();
    method @RequiresPermission(allOf={android.Manifest.permission.RECORD_AUDIO, android.Manifest.permission.CAPTURE_AUDIO_HOTWORD}) public boolean startRecognition();
    method public boolean startRecognition(@NonNull android.os.ParcelFileDescriptor, @NonNull android.media.AudioFormat, @Nullable android.os.PersistableBundle);
    method public boolean stopRecognition();
+38 −0
Original line number Diff line number Diff line
@@ -18,6 +18,7 @@ package android.service.voice;

import static com.android.internal.util.function.pooled.PooledLambda.obtainMessage;

import android.annotation.CallSuper;
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.app.ActivityThread;
@@ -34,6 +35,9 @@ import android.util.Slog;
import com.android.internal.app.IHotwordRecognitionStatusCallback;
import com.android.internal.app.IVoiceInteractionManagerService;

import java.util.concurrent.atomic.AtomicBoolean;
import java.util.function.Consumer;

/** Base implementation of {@link HotwordDetector}. */
abstract class AbstractHotwordDetector implements HotwordDetector {
    private static final String TAG = AbstractHotwordDetector.class.getSimpleName();
@@ -45,6 +49,8 @@ abstract class AbstractHotwordDetector implements HotwordDetector {
    private final Handler mHandler;
    private final HotwordDetector.Callback mCallback;
    private final int mDetectorType;
    private Consumer<AbstractHotwordDetector> mOnDestroyListener;
    private final AtomicBoolean mIsDetectorActive;

    AbstractHotwordDetector(
            IVoiceInteractionManagerService managerService,
@@ -55,6 +61,7 @@ abstract class AbstractHotwordDetector implements HotwordDetector {
        mHandler = new Handler(Looper.getMainLooper());
        mCallback = callback;
        mDetectorType = detectorType;
        mIsDetectorActive = new AtomicBoolean(true);
    }

    /**
@@ -70,6 +77,7 @@ abstract class AbstractHotwordDetector implements HotwordDetector {
        if (DEBUG) {
            Slog.i(TAG, "#recognizeHotword");
        }
        throwIfDetectorIsNoLongerActive();

        // TODO: consider closing existing session.

@@ -106,6 +114,7 @@ abstract class AbstractHotwordDetector implements HotwordDetector {
        if (DEBUG) {
            Slog.d(TAG, "updateState()");
        }
        throwIfDetectorIsNoLongerActive();
        synchronized (mLock) {
            updateStateLocked(options, sharedMemory, null /* callback */, mDetectorType);
        }
@@ -126,6 +135,35 @@ abstract class AbstractHotwordDetector implements HotwordDetector {
        }
    }

    void registerOnDestroyListener(Consumer<AbstractHotwordDetector> onDestroyListener) {
        synchronized (mLock) {
            if (mOnDestroyListener != null) {
                throw new IllegalStateException("only one destroy listener can be registered");
            }
            mOnDestroyListener = onDestroyListener;
        }
    }

    @CallSuper
    @Override
    public void destroy() {
        if (!mIsDetectorActive.get()) {
            return;
        }
        mIsDetectorActive.set(false);
        synchronized (mLock) {
            mOnDestroyListener.accept(this);
        }
    }

    protected void throwIfDetectorIsNoLongerActive() {
        if (!mIsDetectorActive.get()) {
            Slog.e(TAG, "attempting to use a destroyed detector which is no longer active");
            throw new IllegalStateException(
                    "attempting to use a destroyed detector which is no longer active");
        }
    }

    private static class BinderCallback
            extends IMicrophoneHotwordDetectionVoiceInteractionCallback.Stub {
        private final Handler mHandler;
+7 −3
Original line number Diff line number Diff line
@@ -1205,11 +1205,14 @@ public class AlwaysOnHotwordDetector extends AbstractHotwordDetector {
    /**
     * Invalidates this hotword detector so that any future calls to this result
     * in an IllegalStateException.
     *
     * @hide
     */
    void invalidate() {
    @Override
    public void destroy() {
        synchronized (mLock) {
            if (mAvailability == STATE_KEYPHRASE_ENROLLED) {
                stopRecognition();
            }

            mAvailability = STATE_INVALID;
            notifyStateChangedLocked();

@@ -1221,6 +1224,7 @@ public class AlwaysOnHotwordDetector extends AbstractHotwordDetector {
                }
            }
        }
        super.destroy();
    }

    /**
+11 −0
Original line number Diff line number Diff line
@@ -118,6 +118,17 @@ public interface HotwordDetector {
     */
    void updateState(@Nullable PersistableBundle options, @Nullable SharedMemory sharedMemory);

    /**
     * Invalidates this hotword detector so that any future calls to this result
     * in an {@link IllegalStateException}.
     *
     * <p>If there are no other {@link HotwordDetector} instances linked to the
     * {@link HotwordDetectionService}, the service will be shutdown.
     */
    default void destroy() {
        throw new UnsupportedOperationException("Not implemented. Must override in a subclass.");
    }

    /**
     * @hide
     */
+15 −1
Original line number Diff line number Diff line
@@ -77,7 +77,7 @@ class SoftwareHotwordDetector extends AbstractHotwordDetector {
        if (DEBUG) {
            Slog.i(TAG, "#startRecognition");
        }

        throwIfDetectorIsNoLongerActive();
        maybeCloseExistingSession();

        try {
@@ -100,6 +100,7 @@ class SoftwareHotwordDetector extends AbstractHotwordDetector {
        if (DEBUG) {
            Slog.i(TAG, "#stopRecognition");
        }
        throwIfDetectorIsNoLongerActive();

        try {
            mManagerService.stopListeningFromMic();
@@ -110,6 +111,19 @@ class SoftwareHotwordDetector extends AbstractHotwordDetector {
        return true;
    }

    @Override
    public void destroy() {
        stopRecognition();
        maybeCloseExistingSession();

        try {
            mManagerService.shutdownHotwordDetectionService();
        } catch (RemoteException ex) {
            ex.rethrowFromSystemServer();
        }
        super.destroy();
    }

    private void maybeCloseExistingSession() {
        // TODO: needs to be synchronized.
        // TODO: implement this
Loading