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

Commit 06e0fa42 authored by Felix Oghina's avatar Felix Oghina
Browse files

[speech] unbind from recognition service

Currently, we only remove the service reference, but rely on the timeout
built into `ServiceConnector` to actually unbind. This is causing a
service leak.

We also move `SpeechRecognizer#destroy()` to be handled on the Handler
(just like all other calls) in order to fix racing.

Bug: 313604307
Bug: 311124261
Test: cts

Change-Id: I5384916da98b286f869a32857079087105ca32f9
parent 08e5de96
Loading
Loading
Loading
Loading
+4 −0
Original line number Diff line number Diff line
@@ -3101,6 +3101,10 @@ package android.service.watchdog {

package android.speech {

  public abstract class RecognitionService extends android.app.Service {
    method public void onBindInternal();
  }

  public class SpeechRecognizer {
    method @MainThread @NonNull public static android.speech.SpeechRecognizer createOnDeviceTestingSpeechRecognizer(@NonNull android.content.Context);
    method @RequiresPermission(android.Manifest.permission.MANAGE_SPEECH_RECOGNITION) public void setTemporaryOnDeviceRecognizer(@Nullable android.content.ComponentName);
+7 −0
Original line number Diff line number Diff line
@@ -22,6 +22,7 @@ import android.annotation.Nullable;
import android.annotation.SdkConstant;
import android.annotation.SdkConstant.SdkConstantType;
import android.annotation.SuppressLint;
import android.annotation.TestApi;
import android.app.AppOpsManager;
import android.app.Service;
import android.content.AttributionSource;
@@ -514,9 +515,15 @@ public abstract class RecognitionService extends Service {
    @Override
    public final IBinder onBind(final Intent intent) {
        if (DBG) Log.d(TAG, "#onBind, intent=" + intent);
        onBindInternal();
        return mBinder;
    }

    /** @hide */
    @SuppressLint("UnflaggedApi") // @TestApi without associated feature.
    @TestApi
    public void onBindInternal() { }

    @Override
    public void onDestroy() {
        if (DBG) Log.d(TAG, "#onDestroy");
+17 −20
Original line number Diff line number Diff line
@@ -61,6 +61,7 @@ class SpeechRecognizerImpl extends SpeechRecognizer {
    private static final int MSG_SET_TEMPORARY_ON_DEVICE_COMPONENT = 5;
    private static final int MSG_CHECK_RECOGNITION_SUPPORT = 6;
    private static final int MSG_TRIGGER_MODEL_DOWNLOAD = 7;
    private static final int MSG_DESTROY = 8;

    /** The actual RecognitionService endpoint */
    private IRecognitionService mService;
@@ -77,39 +78,31 @@ class SpeechRecognizerImpl extends SpeechRecognizer {
    private IRecognitionServiceManager mManagerService;

    /** Handler that will execute the main tasks */
    private Handler mHandler = new Handler(Looper.getMainLooper()) {
    private final Handler mHandler = new Handler(Looper.getMainLooper()) {

        @Override
        public void handleMessage(Message msg) {
            switch (msg.what) {
                case MSG_START:
                    handleStartListening((Intent) msg.obj);
                    break;
                case MSG_STOP:
                    handleStopMessage();
                    break;
                case MSG_CANCEL:
                    handleCancelMessage();
                    break;
                case MSG_CHANGE_LISTENER:
                    handleChangeListener((RecognitionListener) msg.obj);
                    break;
                case MSG_SET_TEMPORARY_ON_DEVICE_COMPONENT:
                case MSG_START -> handleStartListening((Intent) msg.obj);
                case MSG_STOP -> handleStopMessage();
                case MSG_CANCEL -> handleCancelMessage();
                case MSG_CHANGE_LISTENER -> handleChangeListener((RecognitionListener) msg.obj);
                case MSG_SET_TEMPORARY_ON_DEVICE_COMPONENT ->
                        handleSetTemporaryComponent((ComponentName) msg.obj);
                    break;
                case MSG_CHECK_RECOGNITION_SUPPORT:
                case MSG_CHECK_RECOGNITION_SUPPORT -> {
                    CheckRecognitionSupportArgs args = (CheckRecognitionSupportArgs) msg.obj;
                    handleCheckRecognitionSupport(
                            args.mIntent, args.mCallbackExecutor, args.mCallback);
                    break;
                case MSG_TRIGGER_MODEL_DOWNLOAD:
                }
                case MSG_TRIGGER_MODEL_DOWNLOAD -> {
                    ModelDownloadListenerArgs modelDownloadListenerArgs =
                            (ModelDownloadListenerArgs) msg.obj;
                    handleTriggerModelDownload(
                            modelDownloadListenerArgs.mIntent,
                            modelDownloadListenerArgs.mExecutor,
                            modelDownloadListenerArgs.mModelDownloadListener);
                    break;
                }
                case MSG_DESTROY -> handleDestroy();
            }
        }
    };
@@ -433,6 +426,10 @@ class SpeechRecognizerImpl extends SpeechRecognizer {

    @Override
    public void destroy() {
        putMessage(mHandler.obtainMessage(MSG_DESTROY));
    }

    private void handleDestroy() {
        if (mService != null) {
            try {
                mService.cancel(mListener, /*isShutdown*/ true);
+5 −11
Original line number Diff line number Diff line
@@ -206,22 +206,16 @@ final class RemoteSpeechRecognitionService extends ServiceConnector.Impl<IRecogn
        synchronized (mLock) {
            ClientState clientState = mClients.get(listener.asBinder());

            if (clientState == null) {
                if (DEBUG) {
                    Slog.w(TAG, "#cancel called with no preceding #startListening - ignoring.");
                }
                return;
            }
            if (clientState != null) {
                clientState.mRecordingInProgress = false;

            // Temporary reference to allow for resetting the hard link mDelegatingListener to null.
                // Temporary reference to allow for resetting mDelegatingListener to null.
                final IRecognitionListener delegatingListener = clientState.mDelegatingListener;
                run(service -> service.cancel(delegatingListener, isShutdown));
            }

            // If shutdown, remove the client info from the map. Unbind if that was the last client.
            if (isShutdown) {
                removeClient(listener);

                if (mClients.isEmpty()) {
                    if (DEBUG) {
                        Slog.d(TAG, "Unbinding from the recognition service.");