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

Commit c13a0652 authored by Aleksandar Kiridžić's avatar Aleksandar Kiridžić Committed by Android (Google) Code Review
Browse files

Merge "Speech: Concurrent recognition service"

parents 2d20d1c9 ec5a9ecb
Loading
Loading
Loading
Loading
+1 −0
Original line number Original line Diff line number Diff line
@@ -40475,6 +40475,7 @@ package android.speech {
  public abstract class RecognitionService extends android.app.Service {
  public abstract class RecognitionService extends android.app.Service {
    ctor public RecognitionService();
    ctor public RecognitionService();
    method public int getMaxConcurrentSessionsCount();
    method public final android.os.IBinder onBind(android.content.Intent);
    method public final android.os.IBinder onBind(android.content.Intent);
    method protected abstract void onCancel(android.speech.RecognitionService.Callback);
    method protected abstract void onCancel(android.speech.RecognitionService.Callback);
    method public void onCheckRecognitionSupport(@NonNull android.content.Intent, @NonNull android.speech.RecognitionService.SupportCallback);
    method public void onCheckRecognitionSupport(@NonNull android.content.Intent, @NonNull android.speech.RecognitionService.SupportCallback);
+147 −85
Original line number Original line Diff line number Diff line
@@ -42,6 +42,8 @@ import android.util.Pair;
import com.android.internal.util.function.pooled.PooledLambda;
import com.android.internal.util.function.pooled.PooledLambda;


import java.lang.ref.WeakReference;
import java.lang.ref.WeakReference;
import java.util.HashMap;
import java.util.Map;
import java.util.Objects;
import java.util.Objects;


/**
/**
@@ -71,17 +73,12 @@ public abstract class RecognitionService extends Service {
    /** Debugging flag */
    /** Debugging flag */
    private static final boolean DBG = false;
    private static final boolean DBG = false;


    /** Binder of the recognition service */
    private static final int DEFAULT_MAX_CONCURRENT_SESSIONS_COUNT = 1;
    private RecognitionServiceBinder mBinder = new RecognitionServiceBinder(this);


    /**
    private final Map<IBinder, SessionState> mSessions = new HashMap<>();
     * The current callback of an application that invoked the
     *
     * {@link RecognitionService#onStartListening(Intent, Callback)} method
     */
    private Callback mCurrentCallback = null;


    private boolean mStartedDataDelivery;
    /** Binder of the recognition service */
    private final RecognitionServiceBinder mBinder = new RecognitionServiceBinder(this);


    private static final int MSG_START_LISTENING = 1;
    private static final int MSG_START_LISTENING = 1;


@@ -110,7 +107,7 @@ public abstract class RecognitionService extends Service {
                    dispatchCancel((IRecognitionListener) msg.obj);
                    dispatchCancel((IRecognitionListener) msg.obj);
                    break;
                    break;
                case MSG_RESET:
                case MSG_RESET:
                    dispatchClearCallback();
                    dispatchClearCallback((IRecognitionListener) msg.obj);
                    break;
                    break;
                case MSG_CHECK_RECOGNITION_SUPPORT:
                case MSG_CHECK_RECOGNITION_SUPPORT:
                    Pair<Intent, IRecognitionSupportCallback> intentAndListener =
                    Pair<Intent, IRecognitionSupportCallback> intentAndListener =
@@ -127,71 +124,90 @@ public abstract class RecognitionService extends Service {


    private void dispatchStartListening(Intent intent, final IRecognitionListener listener,
    private void dispatchStartListening(Intent intent, final IRecognitionListener listener,
            @NonNull AttributionSource attributionSource) {
            @NonNull AttributionSource attributionSource) {
        Callback currentCallback = null;
        SessionState sessionState = mSessions.get(listener.asBinder());

        try {
        try {
            if (mCurrentCallback == null) {
            if (sessionState == null) {
                if (mSessions.size() >= getMaxConcurrentSessionsCount()) {
                    listener.onError(SpeechRecognizer.ERROR_RECOGNIZER_BUSY);
                    Log.i(TAG, "#startListening received "
                            + "when the service's capacity is full - ignoring this call.");
                    return;
                }

                boolean preflightPermissionCheckPassed =
                boolean preflightPermissionCheckPassed =
                        intent.hasExtra(RecognizerIntent.EXTRA_AUDIO_SOURCE)
                        intent.hasExtra(RecognizerIntent.EXTRA_AUDIO_SOURCE)
                                || checkPermissionForPreflightNotHardDenied(attributionSource);
                                || checkPermissionForPreflightNotHardDenied(attributionSource);
                if (preflightPermissionCheckPassed) {
                if (preflightPermissionCheckPassed) {
                    if (DBG) {
                    currentCallback = new Callback(listener, attributionSource);
                        Log.d(TAG, "created new mCurrentCallback, listener = "
                    sessionState = new SessionState(currentCallback);
                                + listener.asBinder());
                    RecognitionService.this.onStartListening(intent, currentCallback);
                    }
                    mCurrentCallback = new Callback(listener, attributionSource);
                    RecognitionService.this.onStartListening(intent, mCurrentCallback);
                }
                }


                if (!preflightPermissionCheckPassed || !checkPermissionAndStartDataDelivery()) {
                if (!preflightPermissionCheckPassed
                        || !checkPermissionAndStartDataDelivery(sessionState)) {
                    listener.onError(SpeechRecognizer.ERROR_INSUFFICIENT_PERMISSIONS);
                    listener.onError(SpeechRecognizer.ERROR_INSUFFICIENT_PERMISSIONS);
                    if (preflightPermissionCheckPassed) {
                    if (preflightPermissionCheckPassed) {
                        // If we attempted to start listening, cancel the callback
                        // If start listening was attempted, cancel the callback.
                        RecognitionService.this.onCancel(mCurrentCallback);
                        RecognitionService.this.onCancel(currentCallback);
                        dispatchClearCallback();
                        finishDataDelivery(sessionState);
                        sessionState.reset();
                    }
                    }
                    Log.i(TAG, "caller doesn't have permission:"
                    Log.i(TAG, "#startListening received from a caller "
                            + Manifest.permission.RECORD_AUDIO);
                            + "without permission " + Manifest.permission.RECORD_AUDIO + ".");
                } else {
                    if (DBG) {
                        Log.d(TAG, "Added a new session to the map.");
                    }
                    mSessions.put(listener.asBinder(), sessionState);
                }
                }
            } else {
            } else {
                listener.onError(SpeechRecognizer.ERROR_RECOGNIZER_BUSY);
                listener.onError(SpeechRecognizer.ERROR_CLIENT);
                Log.i(TAG, "concurrent startListening received - ignoring this call");
                Log.i(TAG, "#startListening received "
                        + "for a listener which is already in session - ignoring this call.");
            }
            }
        } catch (RemoteException e) {
        } catch (RemoteException e) {
            Log.d(TAG, "onError call from startListening failed");
            Log.d(TAG, "#onError call from #startListening failed.");
        }
        }
    }
    }


    private void dispatchStopListening(IRecognitionListener listener) {
    private void dispatchStopListening(IRecognitionListener listener) {
        SessionState sessionState = mSessions.get(listener.asBinder());
        if (sessionState == null) {
            try {
            try {
            if (mCurrentCallback == null) {
                listener.onError(SpeechRecognizer.ERROR_CLIENT);
                listener.onError(SpeechRecognizer.ERROR_CLIENT);
                Log.w(TAG, "stopListening called with no preceding startListening - ignoring");
            } catch (RemoteException e) {
            } else if (mCurrentCallback.mListener.asBinder() != listener.asBinder()) {
                Log.d(TAG, "#onError call from #stopListening failed.");
                listener.onError(SpeechRecognizer.ERROR_RECOGNIZER_BUSY);
                Log.w(TAG, "stopListening called by other caller than startListening - ignoring");
            } else { // the correct state
                RecognitionService.this.onStopListening(mCurrentCallback);
            }
            }
        } catch (RemoteException e) { // occurs if onError fails
            Log.w(TAG, "#stopListening received for a listener "
            Log.d(TAG, "onError call from stopListening failed");
                    + "which has not started a session - ignoring this call.");
        } else {
            RecognitionService.this.onStopListening(sessionState.mCallback);
        }
        }
    }
    }


    private void dispatchCancel(IRecognitionListener listener) {
    private void dispatchCancel(IRecognitionListener listener) {
        if (mCurrentCallback == null) {
        SessionState sessionState = mSessions.get(listener.asBinder());
            if (DBG) Log.d(TAG, "cancel called with no preceding startListening - ignoring");
        if (sessionState == null) {
        } else if (mCurrentCallback.mListener.asBinder() != listener.asBinder()) {
            Log.w(TAG, "#cancel received for a listener which has not started a session "
            Log.w(TAG, "cancel called by client who did not call startListening - ignoring");
                    + "- ignoring this call.");
        } else { // the correct state
        } else {
            RecognitionService.this.onCancel(mCurrentCallback);
            RecognitionService.this.onCancel(sessionState.mCallback);
            dispatchClearCallback();
            dispatchClearCallback(listener);
            if (DBG) Log.d(TAG, "canceling - setting mCurrentCallback to null");
        }
        }
    }
    }


    private void dispatchClearCallback() {
    private void dispatchClearCallback(IRecognitionListener listener) {
        finishDataDelivery();
        SessionState sessionState = mSessions.remove(listener.asBinder());
        mCurrentCallback = null;
        if (sessionState != null) {
        mStartedDataDelivery = false;
            if (DBG) {
                Log.d(TAG, "Removed session from the map for listener = "
                        + listener.asBinder() + ".");
            }
            finishDataDelivery(sessionState);
            sessionState.reset();
        }
    }
    }


    private void dispatchCheckRecognitionSupport(
    private void dispatchCheckRecognitionSupport(
@@ -203,11 +219,11 @@ public abstract class RecognitionService extends Service {
        RecognitionService.this.onTriggerModelDownload(intent);
        RecognitionService.this.onTriggerModelDownload(intent);
    }
    }


    private class StartListeningArgs {
    private static class StartListeningArgs {
        public final Intent mIntent;
        public final Intent mIntent;


        public final IRecognitionListener mListener;
        public final IRecognitionListener mListener;
        public final @NonNull AttributionSource mAttributionSource;
        @NonNull public final AttributionSource mAttributionSource;


        public StartListeningArgs(Intent intent, IRecognitionListener listener,
        public StartListeningArgs(Intent intent, IRecognitionListener listener,
                @NonNull AttributionSource attributionSource) {
                @NonNull AttributionSource attributionSource) {
@@ -306,27 +322,42 @@ public abstract class RecognitionService extends Service {
    }
    }


    private void handleAttributionContextCreation(@NonNull AttributionSource attributionSource) {
    private void handleAttributionContextCreation(@NonNull AttributionSource attributionSource) {
        if (mCurrentCallback != null
        for (SessionState sessionState : mSessions.values()) {
                && mCurrentCallback.mCallingAttributionSource.equals(attributionSource)) {
            Callback currentCallback = sessionState.mCallback;
            mCurrentCallback.mAttributionContextCreated = true;
            if (currentCallback != null
                    && currentCallback.mCallingAttributionSource.equals(attributionSource)) {
                currentCallback.mAttributionContextCreated = true;
            }
        }
        }
    }
    }


    @Override
    @Override
    public final IBinder onBind(final Intent intent) {
    public final IBinder onBind(final Intent intent) {
        if (DBG) Log.d(TAG, "onBind, intent=" + intent);
        if (DBG) Log.d(TAG, "#onBind, intent=" + intent);
        return mBinder;
        return mBinder;
    }
    }


    @Override
    @Override
    public void onDestroy() {
    public void onDestroy() {
        if (DBG) Log.d(TAG, "onDestroy");
        if (DBG) Log.d(TAG, "#onDestroy");
        finishDataDelivery();
        for (SessionState sessionState : mSessions.values()) {
        mCurrentCallback = null;
            finishDataDelivery(sessionState);
            sessionState.reset();
        }
        mSessions.clear();
        mBinder.clearReference();
        mBinder.clearReference();
        super.onDestroy();
        super.onDestroy();
    }
    }


    /**
     * Returns the maximal number of recognition sessions ongoing at the same time.
     * <p>
     * The default value is 1, meaning concurrency should be enabled by overriding this method.
     */
    public int getMaxConcurrentSessionsCount() {
        return DEFAULT_MAX_CONCURRENT_SESSIONS_COUNT;
    }

    /**
    /**
     * This class receives callbacks from the speech recognition service and forwards them to the
     * This class receives callbacks from the speech recognition service and forwards them to the
     * user. An instance of this class is passed to the
     * user. An instance of this class is passed to the
@@ -335,8 +366,8 @@ public abstract class RecognitionService extends Service {
     */
     */
    public class Callback {
    public class Callback {
        private final IRecognitionListener mListener;
        private final IRecognitionListener mListener;
        private final @NonNull AttributionSource mCallingAttributionSource;
        @NonNull private final AttributionSource mCallingAttributionSource;
        private @Nullable Context mAttributionContext;
        @Nullable private Context mAttributionContext;
        private boolean mAttributionContextCreated;
        private boolean mAttributionContextCreated;


        private Callback(IRecognitionListener listener,
        private Callback(IRecognitionListener listener,
@@ -376,7 +407,7 @@ public abstract class RecognitionService extends Service {
         * @param error code is defined in {@link SpeechRecognizer}
         * @param error code is defined in {@link SpeechRecognizer}
         */
         */
        public void error(@SpeechRecognizer.RecognitionError int error) throws RemoteException {
        public void error(@SpeechRecognizer.RecognitionError int error) throws RemoteException {
            Message.obtain(mHandler, MSG_RESET).sendToTarget();
            Message.obtain(mHandler, MSG_RESET, mListener).sendToTarget();
            mListener.onError(error);
            mListener.onError(error);
        }
        }


@@ -413,7 +444,7 @@ public abstract class RecognitionService extends Service {
         *        {@link SpeechRecognizer#RESULTS_RECOGNITION} as a parameter
         *        {@link SpeechRecognizer#RESULTS_RECOGNITION} as a parameter
         */
         */
        public void results(Bundle results) throws RemoteException {
        public void results(Bundle results) throws RemoteException {
            Message.obtain(mHandler, MSG_RESET).sendToTarget();
            Message.obtain(mHandler, MSG_RESET, mListener).sendToTarget();
            mListener.onResults(results);
            mListener.onResults(results);
        }
        }


@@ -444,7 +475,7 @@ public abstract class RecognitionService extends Service {
         */
         */
        @SuppressLint({"CallbackMethodName", "RethrowRemoteException"})
        @SuppressLint({"CallbackMethodName", "RethrowRemoteException"})
        public void endOfSegmentedSession() throws RemoteException {
        public void endOfSegmentedSession() throws RemoteException {
            Message.obtain(mHandler, MSG_RESET).sendToTarget();
            Message.obtain(mHandler, MSG_RESET, mListener).sendToTarget();
            mListener.onEndOfSegmentedSession();
            mListener.onEndOfSegmentedSession();
        }
        }


@@ -469,7 +500,8 @@ public abstract class RecognitionService extends Service {
         * AttributionSource)
         * AttributionSource)
         */
         */
        @SuppressLint("CallbackMethodName")
        @SuppressLint("CallbackMethodName")
        public @NonNull AttributionSource getCallingAttributionSource() {
        @NonNull
        public AttributionSource getCallingAttributionSource() {
            return mCallingAttributionSource;
            return mCallingAttributionSource;
        }
        }


@@ -490,7 +522,6 @@ public abstract class RecognitionService extends Service {
     * these methods on any thread.
     * these methods on any thread.
     */
     */
    public static class SupportCallback {
    public static class SupportCallback {

        private final IRecognitionSupportCallback mCallback;
        private final IRecognitionSupportCallback mCallback;


        private SupportCallback(IRecognitionSupportCallback callback) {
        private SupportCallback(IRecognitionSupportCallback callback) {
@@ -521,7 +552,7 @@ public abstract class RecognitionService extends Service {
        }
        }
    }
    }


/** Binder of the recognition service */
    /** Binder of the recognition service. */
    private static final class RecognitionServiceBinder extends IRecognitionService.Stub {
    private static final class RecognitionServiceBinder extends IRecognitionService.Stub {
        private final WeakReference<RecognitionService> mServiceRef;
        private final WeakReference<RecognitionService> mServiceRef;


@@ -538,7 +569,7 @@ public abstract class RecognitionService extends Service {
            final RecognitionService service = mServiceRef.get();
            final RecognitionService service = mServiceRef.get();
            if (service != null) {
            if (service != null) {
                service.mHandler.sendMessage(Message.obtain(service.mHandler,
                service.mHandler.sendMessage(Message.obtain(service.mHandler,
                        MSG_START_LISTENING, service.new StartListeningArgs(
                        MSG_START_LISTENING, new StartListeningArgs(
                                recognizerIntent, listener, attributionSource)));
                                recognizerIntent, listener, attributionSource)));
            }
            }
        }
        }
@@ -589,17 +620,21 @@ public abstract class RecognitionService extends Service {
        }
        }
    }
    }


    private boolean checkPermissionAndStartDataDelivery() {
    private boolean checkPermissionAndStartDataDelivery(SessionState sessionState) {
        if (mCurrentCallback.mAttributionContextCreated) {
        if (sessionState.mCallback.mAttributionContextCreated) {
            return true;
            return true;
        }
        }

        if (PermissionChecker.checkPermissionAndStartDataDelivery(
        if (PermissionChecker.checkPermissionAndStartDataDelivery(
                RecognitionService.this, Manifest.permission.RECORD_AUDIO,
                RecognitionService.this,
                mCurrentCallback.getAttributionContextForCaller().getAttributionSource(),
                Manifest.permission.RECORD_AUDIO,
                /*message*/ null) == PermissionChecker.PERMISSION_GRANTED) {
                sessionState.mCallback.getAttributionContextForCaller().getAttributionSource(),
            mStartedDataDelivery = true;
                /* message */ null)
                == PermissionChecker.PERMISSION_GRANTED) {
            sessionState.mStartedDataDelivery = true;
        }
        }
        return mStartedDataDelivery;

        return sessionState.mStartedDataDelivery;
    }
    }


    private boolean checkPermissionForPreflightNotHardDenied(AttributionSource attributionSource) {
    private boolean checkPermissionForPreflightNotHardDenied(AttributionSource attributionSource) {
@@ -609,12 +644,39 @@ public abstract class RecognitionService extends Service {
                || result == PermissionChecker.PERMISSION_SOFT_DENIED;
                || result == PermissionChecker.PERMISSION_SOFT_DENIED;
    }
    }


    void finishDataDelivery() {
    void finishDataDelivery(SessionState sessionState) {
        if (mStartedDataDelivery) {
        if (sessionState.mStartedDataDelivery) {
            mStartedDataDelivery = false;
            sessionState.mStartedDataDelivery = false;
            final String op = AppOpsManager.permissionToOp(Manifest.permission.RECORD_AUDIO);
            final String op = AppOpsManager.permissionToOp(Manifest.permission.RECORD_AUDIO);
            PermissionChecker.finishDataDelivery(RecognitionService.this, op,
            PermissionChecker.finishDataDelivery(RecognitionService.this, op,
                    mCurrentCallback.getAttributionContextForCaller().getAttributionSource());
                    sessionState.mCallback.getAttributionContextForCaller().getAttributionSource());
        }
    }

    /**
     * Data class containing information about an ongoing session:
     * <ul>
     *   <li> {@link SessionState#mCallback} - callback of the client that invoked the
     *   {@link RecognitionService#onStartListening(Intent, Callback)} method;
     *   <li> {@link SessionState#mStartedDataDelivery} - flag denoting if data
     *   is being delivered to the client.
     */
    private static class SessionState {
        private Callback mCallback;
        private boolean mStartedDataDelivery;

        SessionState(Callback callback, boolean startedDataDelivery) {
            mCallback = callback;
            mStartedDataDelivery = startedDataDelivery;
        }

        SessionState(Callback currentCallback) {
            this(currentCallback, false);
        }

        void reset() {
            mCallback = null;
            mStartedDataDelivery = false;
        }
        }
    }
    }
}
}
+165 −93

File changed.

Preview size limit exceeded, changes collapsed.

+14 −9
Original line number Original line Diff line number Diff line
@@ -127,13 +127,14 @@ final class SpeechRecognitionManagerServiceImpl extends
        }
        }


        IBinder.DeathRecipient deathRecipient =
        IBinder.DeathRecipient deathRecipient =
                () -> handleClientDeath(creatorCallingUid, service, true /* invoke #cancel */);
                () -> handleClientDeath(
                        clientToken, creatorCallingUid, service, true /* invoke #cancel */);


        try {
        try {
            clientToken.linkToDeath(deathRecipient, 0);
            clientToken.linkToDeath(deathRecipient, 0);
        } catch (RemoteException e) {
        } catch (RemoteException e) {
            // RemoteException == binder already died, schedule disconnect anyway.
            // RemoteException == binder already died, schedule disconnect anyway.
            handleClientDeath(creatorCallingUid, service, true /* invoke #cancel */);
            handleClientDeath(clientToken, creatorCallingUid, service, true /* invoke #cancel */);
            return;
            return;
        }
        }


@@ -154,6 +155,7 @@ final class SpeechRecognitionManagerServiceImpl extends
                                        .registerAttributionSource(attributionSource);
                                        .registerAttributionSource(attributionSource);
                            }
                            }
                            service.startListening(recognizerIntent, listener, attributionSource);
                            service.startListening(recognizerIntent, listener, attributionSource);
                            service.associateClientWithActiveListener(clientToken, listener);
                        }
                        }


                        @Override
                        @Override
@@ -166,11 +168,11 @@ final class SpeechRecognitionManagerServiceImpl extends
                        public void cancel(
                        public void cancel(
                                IRecognitionListener listener,
                                IRecognitionListener listener,
                                boolean isShutdown) throws RemoteException {
                                boolean isShutdown) throws RemoteException {

                            service.cancel(listener, isShutdown);
                            service.cancel(listener, isShutdown);


                            if (isShutdown) {
                            if (isShutdown) {
                                handleClientDeath(
                                handleClientDeath(
                                        clientToken,
                                        creatorCallingUid,
                                        creatorCallingUid,
                                        service,
                                        service,
                                        false /* invoke #cancel */);
                                        false /* invoke #cancel */);
@@ -201,13 +203,17 @@ final class SpeechRecognitionManagerServiceImpl extends
    }
    }


    private void handleClientDeath(
    private void handleClientDeath(
            int callingUid,
            IBinder clientToken, int callingUid,
            RemoteSpeechRecognitionService service, boolean invokeCancel) {
            RemoteSpeechRecognitionService service, boolean invokeCancel) {
        if (invokeCancel) {
        if (invokeCancel) {
            service.shutdown();
            service.shutdown(clientToken);
        }
        }
        synchronized (mLock) {
            if (!service.hasActiveSessions()) {
                removeService(callingUid, service);
                removeService(callingUid, service);
            }
            }
        }
    }


    @GuardedBy("mLock")
    @GuardedBy("mLock")
    @Nullable
    @Nullable
@@ -245,7 +251,6 @@ final class SpeechRecognitionManagerServiceImpl extends
                                        service.getServiceComponentName().equals(serviceComponent))
                                        service.getServiceComponentName().equals(serviceComponent))
                                .findFirst();
                                .findFirst();
                if (existingService.isPresent()) {
                if (existingService.isPresent()) {

                    if (mMaster.debug) {
                    if (mMaster.debug) {
                        Slog.i(TAG, "Reused existing connection to " + serviceComponent);
                        Slog.i(TAG, "Reused existing connection to " + serviceComponent);
                    }
                    }