Loading core/api/current.txt +1 −0 Original line number Original line Diff line number Diff line Loading @@ -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); core/java/android/speech/RecognitionService.java +147 −85 Original line number Original line Diff line number Diff line Loading @@ -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; /** /** Loading Loading @@ -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; Loading Loading @@ -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 = Loading @@ -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( Loading @@ -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) { Loading Loading @@ -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 Loading @@ -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, Loading Loading @@ -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); } } Loading Loading @@ -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); } } Loading Loading @@ -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(); } } Loading @@ -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; } } Loading @@ -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) { Loading Loading @@ -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; Loading @@ -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))); } } } } Loading Loading @@ -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) { Loading @@ -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; } } } } } } services/core/java/com/android/server/speech/RemoteSpeechRecognitionService.java +165 −93 File changed.Preview size limit exceeded, changes collapsed. Show changes services/core/java/com/android/server/speech/SpeechRecognitionManagerServiceImpl.java +14 −9 Original line number Original line Diff line number Diff line Loading @@ -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; } } Loading @@ -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 Loading @@ -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 */); Loading Loading @@ -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 Loading Loading @@ -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); } } Loading Loading
core/api/current.txt +1 −0 Original line number Original line Diff line number Diff line Loading @@ -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);
core/java/android/speech/RecognitionService.java +147 −85 Original line number Original line Diff line number Diff line Loading @@ -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; /** /** Loading Loading @@ -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; Loading Loading @@ -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 = Loading @@ -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( Loading @@ -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) { Loading Loading @@ -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 Loading @@ -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, Loading Loading @@ -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); } } Loading Loading @@ -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); } } Loading Loading @@ -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(); } } Loading @@ -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; } } Loading @@ -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) { Loading Loading @@ -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; Loading @@ -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))); } } } } Loading Loading @@ -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) { Loading @@ -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; } } } } } }
services/core/java/com/android/server/speech/RemoteSpeechRecognitionService.java +165 −93 File changed.Preview size limit exceeded, changes collapsed. Show changes
services/core/java/com/android/server/speech/SpeechRecognitionManagerServiceImpl.java +14 −9 Original line number Original line Diff line number Diff line Loading @@ -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; } } Loading @@ -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 Loading @@ -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 */); Loading Loading @@ -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 Loading Loading @@ -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); } } Loading