diff options
4 files changed, 899 insertions, 697 deletions
diff --git a/core/java/android/speech/SpeechRecognizer.java b/core/java/android/speech/SpeechRecognizer.java index e6fcc0c391b2..388f08a92c95 100644 --- a/core/java/android/speech/SpeechRecognizer.java +++ b/core/java/android/speech/SpeechRecognizer.java @@ -28,31 +28,15 @@ import android.content.ComponentName; import android.content.Context; import android.content.Intent; import android.content.pm.ResolveInfo; -import android.os.Binder; import android.os.Bundle; -import android.os.Handler; -import android.os.IBinder; -import android.os.Looper; -import android.os.Message; -import android.os.RemoteException; -import android.os.ServiceManager; -import android.provider.Settings; -import android.text.TextUtils; -import android.util.CloseGuard; -import android.util.Log; -import android.util.Slog; import com.android.internal.R; import java.lang.annotation.Documented; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; -import java.lang.ref.Reference; import java.util.List; -import java.util.Objects; -import java.util.Queue; import java.util.concurrent.Executor; -import java.util.concurrent.LinkedBlockingQueue; /** * This class provides access to the speech recognition service. This service allows access to the @@ -72,11 +56,6 @@ import java.util.concurrent.LinkedBlockingQueue; * permission to use this class. */ public class SpeechRecognizer { - /** DEBUG value to enable verbose debug prints */ - private static final boolean DBG = false; - - /** Log messages identifier */ - private static final String TAG = "SpeechRecognizer"; /** * Key used to retrieve an {@code ArrayList<String>} from the {@link Bundle} passed to the @@ -303,106 +282,17 @@ public class SpeechRecognizer { private static final int MSG_CHECK_RECOGNITION_SUPPORT = 6; private static final int MSG_TRIGGER_MODEL_DOWNLOAD = 7; - /** The actual RecognitionService endpoint */ - private IRecognitionService mService; - - private final CloseGuard mCloseGuard = new CloseGuard(); - - /** Context with which the manager was created */ - private final Context mContext; - - /** Component to direct service intent to */ - private final ComponentName mServiceComponent; - - /** Whether to use on-device speech recognizer. */ - private final boolean mOnDevice; - - private IRecognitionServiceManager mManagerService; - - /** Handler that will execute the main tasks */ - private 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: - handleSetTemporaryComponent((ComponentName) msg.obj); - break; - case MSG_CHECK_RECOGNITION_SUPPORT: - CheckRecognitionSupportArgs args = (CheckRecognitionSupportArgs) msg.obj; - handleCheckRecognitionSupport( - args.mIntent, args.mCallbackExecutor, args.mCallback); - break; - case MSG_TRIGGER_MODEL_DOWNLOAD: - ModelDownloadListenerArgs modelDownloadListenerArgs = - (ModelDownloadListenerArgs) msg.obj; - handleTriggerModelDownload( - modelDownloadListenerArgs.mIntent, - modelDownloadListenerArgs.mExecutor, - modelDownloadListenerArgs.mModelDownloadListener); - break; - } - } - }; - - /** - * Temporary queue, saving the messages until the connection will be established, afterwards, - * only mHandler will receive the messages - */ - private final Queue<Message> mPendingTasks = new LinkedBlockingQueue<>(); - - /** The Listener that will receive all the callbacks */ - private final InternalRecognitionListener mListener = new InternalRecognitionListener(); - - private final IBinder mClientToken = new Binder(); - - /** - * The right way to create a {@code SpeechRecognizer} is by using - * {@link #createSpeechRecognizer} static factory method - */ - private SpeechRecognizer(final Context context, final ComponentName serviceComponent) { - this(context, serviceComponent, false); - } - - /** - * The right way to create a {@code SpeechRecognizer} is by using - * {@link #createOnDeviceSpeechRecognizer} static factory method - */ - private SpeechRecognizer(final Context context, boolean onDevice) { - this(context, null, onDevice); - } - - private SpeechRecognizer( - final Context context, - final ComponentName serviceComponent, - final boolean onDevice) { - mContext = context; - mServiceComponent = serviceComponent; - mOnDevice = onDevice; - mCloseGuard.open("SpeechRecognizer#destroy()"); - } + SpeechRecognizer() { } /** * Checks whether a speech recognition service is available on the system. If this method * returns {@code false}, {@link SpeechRecognizer#createSpeechRecognizer(Context)} will * fail. - * + * * @param context with which {@code SpeechRecognizer} will be created * @return {@code true} if recognition is available, {@code false} otherwise */ - public static boolean isRecognitionAvailable(@NonNull final Context context) { + public static boolean isRecognitionAvailable(@NonNull Context context) { // TODO(b/176578753): make sure this works well with system speech recognizers. final List<ResolveInfo> list = context.getPackageManager().queryIntentServices( new Intent(RecognitionService.SERVICE_INTERFACE), 0); @@ -418,7 +308,7 @@ public class SpeechRecognizer { * @param context with which on-device {@code SpeechRecognizer} will be created * @return {@code true} if on-device recognition is available, {@code false} otherwise */ - public static boolean isOnDeviceRecognitionAvailable(@NonNull final Context context) { + public static boolean isOnDeviceRecognitionAvailable(@NonNull Context context) { ComponentName componentName = ComponentName.unflattenFromString( context.getString(R.string.config_defaultOnDeviceSpeechRecognitionService)); @@ -449,7 +339,7 @@ public class SpeechRecognizer { * @return a new {@code SpeechRecognizer} */ @MainThread - public static SpeechRecognizer createSpeechRecognizer(final Context context) { + public static SpeechRecognizer createSpeechRecognizer(Context context) { return createSpeechRecognizer(context, null); } @@ -483,19 +373,19 @@ public class SpeechRecognizer { * </queries> * }</pre> * - * @param context in which to create {@code SpeechRecognizer} + * @param context in which to create {@code SpeechRecognizer} * @param serviceComponent the {@link ComponentName} of a specific service to direct this - * {@code SpeechRecognizer} to + * {@code SpeechRecognizer} to * @return a new {@code SpeechRecognizer} */ @MainThread - public static SpeechRecognizer createSpeechRecognizer(final Context context, - final ComponentName serviceComponent) { + public static SpeechRecognizer createSpeechRecognizer(Context context, + ComponentName serviceComponent) { if (context == null) { throw new IllegalArgumentException("Context cannot be null"); } - checkIsCalledFromMainThread(); - return new SpeechRecognizer(context, serviceComponent); + SpeechRecognizerImpl.checkIsCalledFromMainThread(); + return wrapWithProxy(new SpeechRecognizerImpl(context, serviceComponent)); } /** @@ -515,11 +405,15 @@ public class SpeechRecognizer { */ @NonNull @MainThread - public static SpeechRecognizer createOnDeviceSpeechRecognizer(@NonNull final Context context) { + public static SpeechRecognizer createOnDeviceSpeechRecognizer(@NonNull Context context) { if (!isOnDeviceRecognitionAvailable(context)) { throw new UnsupportedOperationException("On-device recognition is not available"); } - return lenientlyCreateOnDeviceSpeechRecognizer(context); + return wrapWithProxy(SpeechRecognizerImpl.lenientlyCreateOnDeviceSpeechRecognizer(context)); + } + + private static SpeechRecognizer wrapWithProxy(SpeechRecognizer delegate) { + return new SpeechRecognizerProxy(delegate); } /** @@ -532,42 +426,21 @@ public class SpeechRecognizer { @NonNull @MainThread public static SpeechRecognizer createOnDeviceTestingSpeechRecognizer( - @NonNull final Context context) { - return lenientlyCreateOnDeviceSpeechRecognizer(context); - } - - @NonNull - @MainThread - private static SpeechRecognizer lenientlyCreateOnDeviceSpeechRecognizer( - @NonNull final Context context) { - if (context == null) { - throw new IllegalArgumentException("Context cannot be null"); - } - checkIsCalledFromMainThread(); - return new SpeechRecognizer(context, /* onDevice */ true); + @NonNull Context context) { + return wrapWithProxy(SpeechRecognizerImpl.lenientlyCreateOnDeviceSpeechRecognizer(context)); } /** * Sets the listener that will receive all the callbacks. The previous unfinished commands will * be executed with the old listener, while any following command will be executed with the new * listener. - * + * * @param listener listener that will receive all the callbacks from the created - * {@link SpeechRecognizer}, this must not be null. + * {@link SpeechRecognizer}, this must not be null. */ @MainThread public void setRecognitionListener(RecognitionListener listener) { - checkIsCalledFromMainThread(); - if (mListener.mInternalListener == null) { - // This shortcut is needed because otherwise, if there's an error connecting, it never - // gets delivered. I.e., the onSuccess callback set up in connectToSystemService does - // not get called, MSG_CHANGE_LISTENER does not get executed, so the onError in the same - // place does not get forwarded anywhere. - // Thread-wise, this is safe as both this method and the handler are on the UI thread. - handleChangeListener(listener); - } else { - putMessage(Message.obtain(mHandler, MSG_CHANGE_LISTENER, listener)); - } + throw new UnsupportedOperationException(); } /** @@ -576,28 +449,13 @@ public class SpeechRecognizer { * no notifications will be received. * * @param recognizerIntent contains parameters for the recognition to be performed. The intent - * may also contain optional extras, see {@link RecognizerIntent}. If these values are - * not set explicitly, default values will be used by the recognizer. + * may also contain optional extras, see {@link RecognizerIntent}. If + * these values are not set explicitly, default values will be used by + * the recognizer. */ @MainThread - public void startListening(final Intent recognizerIntent) { - if (recognizerIntent == null) { - throw new IllegalArgumentException("intent must not be null"); - } - checkIsCalledFromMainThread(); - - if (DBG) { - Slog.i(TAG, "#startListening called"); - if (mService == null) { - Slog.i(TAG, "Connection is not established yet"); - } - } - - if (mService == null) { - // First time connection: first establish a connection, then dispatch #startListening. - connectToSystemService(); - } - putMessage(Message.obtain(mHandler, MSG_START, recognizerIntent)); + public void startListening(Intent recognizerIntent) { + throw new UnsupportedOperationException(); } /** @@ -621,16 +479,7 @@ public class SpeechRecognizer { */ @MainThread public void stopListening() { - checkIsCalledFromMainThread(); - - if (DBG) { - Slog.i(TAG, "#stopListening called"); - if (mService == null) { - Slog.i(TAG, "Connection is not established yet"); - } - } - - putMessage(Message.obtain(mHandler, MSG_STOP)); + throw new UnsupportedOperationException(); } /** @@ -640,8 +489,7 @@ public class SpeechRecognizer { */ @MainThread public void cancel() { - checkIsCalledFromMainThread(); - putMessage(Message.obtain(mHandler, MSG_CANCEL)); + throw new UnsupportedOperationException(); } /** @@ -649,30 +497,15 @@ public class SpeechRecognizer { * {@link SpeechRecognizer#startListening(Intent)}. * * @param recognizerIntent contains parameters for the recognition to be performed. The intent - * may also contain optional extras. See {@link RecognizerIntent} for the list of - * supported extras, any unlisted extra might be ignored. - * @param supportListener the listener on which to receive the support query results. + * may also contain optional extras. See {@link RecognizerIntent} for + * the list of supported extras, any unlisted extra might be ignored. + * @param supportListener the listener on which to receive the support query results. */ public void checkRecognitionSupport( @NonNull Intent recognizerIntent, @NonNull @CallbackExecutor Executor executor, @NonNull RecognitionSupportCallback supportListener) { - Objects.requireNonNull(recognizerIntent, "intent must not be null"); - Objects.requireNonNull(supportListener, "listener must not be null"); - - if (DBG) { - Slog.i(TAG, "#checkRecognitionSupport called"); - if (mService == null) { - Slog.i(TAG, "Connection is not established yet"); - } - } - - if (mService == null) { - // First time connection: first establish a connection, then dispatch. - connectToSystemService(); - } - putMessage(Message.obtain(mHandler, MSG_CHECK_RECOGNITION_SUPPORT, - new CheckRecognitionSupportArgs(recognizerIntent, executor, supportListener))); + throw new UnsupportedOperationException(); } /** @@ -681,23 +514,10 @@ public class SpeechRecognizer { * {@link #checkRecognitionSupport(Intent, Executor, RecognitionSupportCallback)}. * * @param recognizerIntent contains parameters for the recognition to be performed. The intent - * may also contain optional extras, see {@link RecognizerIntent}. + * may also contain optional extras, see {@link RecognizerIntent}. */ public void triggerModelDownload(@NonNull Intent recognizerIntent) { - Objects.requireNonNull(recognizerIntent, "intent must not be null"); - if (DBG) { - Slog.i(TAG, "#triggerModelDownload without a listener called"); - if (mService == null) { - Slog.i(TAG, "Connection is not established yet"); - } - } - if (mService == null) { - // First time connection: first establish a connection, then dispatch. - connectToSystemService(); - } - putMessage(Message.obtain( - mHandler, MSG_TRIGGER_MODEL_DOWNLOAD, - new ModelDownloadListenerArgs(recognizerIntent, null, null))); + throw new UnsupportedOperationException(); } /** @@ -725,28 +545,15 @@ public class SpeechRecognizer { * {@link ModelDownloadListener#onError(int)} will be called. * * @param recognizerIntent contains parameters for the recognition to be performed. The intent - * may also contain optional extras, see {@link RecognizerIntent}. - * @param executor for dispatching listener callbacks - * @param listener on which to receive updates about the model download request. + * may also contain optional extras, see {@link RecognizerIntent}. + * @param executor for dispatching listener callbacks + * @param listener on which to receive updates about the model download request. */ public void triggerModelDownload( @NonNull Intent recognizerIntent, @NonNull @CallbackExecutor Executor executor, @NonNull ModelDownloadListener listener) { - Objects.requireNonNull(recognizerIntent, "intent must not be null"); - if (DBG) { - Slog.i(TAG, "#triggerModelDownload with a listener called"); - if (mService == null) { - Slog.i(TAG, "Connection is not established yet"); - } - } - if (mService == null) { - // First time connection: first establish a connection, then dispatch. - connectToSystemService(); - } - putMessage(Message.obtain( - mHandler, MSG_TRIGGER_MODEL_DOWNLOAD, - new ModelDownloadListenerArgs(recognizerIntent, executor, listener))); + throw new UnsupportedOperationException(); } /** @@ -755,479 +562,19 @@ public class SpeechRecognizer { * <p>This is only expected to be called in tests, system would reject calls from client apps. * * @param componentName name of the component to set temporary replace speech recognizer. {@code - * null} value resets the recognizer to default. - * + * null} value resets the recognizer to default. * @hide */ @TestApi @RequiresPermission(Manifest.permission.MANAGE_SPEECH_RECOGNITION) public void setTemporaryOnDeviceRecognizer(@Nullable ComponentName componentName) { - mHandler.sendMessage( - Message.obtain(mHandler, MSG_SET_TEMPORARY_ON_DEVICE_COMPONENT, componentName)); - } - - private static void checkIsCalledFromMainThread() { - if (Looper.myLooper() != Looper.getMainLooper()) { - throw new RuntimeException( - "SpeechRecognizer should be used only from the application's main thread"); - } - } - - private void putMessage(Message msg) { - if (mService == null) { - mPendingTasks.offer(msg); - } else { - mHandler.sendMessage(msg); - } - } - - /** sends the actual message to the service */ - private void handleStartListening(Intent recognizerIntent) { - if (!checkOpenConnection()) { - return; - } - try { - mService.startListening(recognizerIntent, mListener, mContext.getAttributionSource()); - if (DBG) Log.d(TAG, "service start listening command succeeded"); - } catch (final Exception e) { - Log.e(TAG, "startListening() failed", e); - mListener.onError(ERROR_CLIENT); - } - } - - /** sends the actual message to the service */ - private void handleStopMessage() { - if (!checkOpenConnection()) { - return; - } - try { - mService.stopListening(mListener); - if (DBG) Log.d(TAG, "service stop listening command succeeded"); - } catch (final Exception e) { - Log.e(TAG, "stopListening() failed", e); - mListener.onError(ERROR_CLIENT); - } - } - - /** sends the actual message to the service */ - private void handleCancelMessage() { - if (!checkOpenConnection()) { - return; - } - try { - mService.cancel(mListener, /*isShutdown*/ false); - if (DBG) Log.d(TAG, "service cancel command succeeded"); - } catch (final Exception e) { - Log.e(TAG, "cancel() failed", e); - mListener.onError(ERROR_CLIENT); - } - } - - private void handleSetTemporaryComponent(ComponentName componentName) { - if (DBG) { - Log.d(TAG, "handleSetTemporaryComponent, componentName=" + componentName); - } - - if (!maybeInitializeManagerService()) { - return; - } - - try { - mManagerService.setTemporaryComponent(componentName); - } catch (final RemoteException e) { - e.rethrowFromSystemServer(); - } - } - - private void handleCheckRecognitionSupport( - Intent recognizerIntent, - Executor callbackExecutor, - RecognitionSupportCallback recognitionSupportCallback) { - if (!maybeInitializeManagerService() || !checkOpenConnection()) { - return; - } - try { - mService.checkRecognitionSupport( - recognizerIntent, - mContext.getAttributionSource(), - new InternalSupportCallback(callbackExecutor, recognitionSupportCallback)); - if (DBG) Log.d(TAG, "service support command succeeded"); - } catch (final Exception e) { - Log.e(TAG, "checkRecognitionSupport() failed", e); - callbackExecutor.execute(() -> recognitionSupportCallback.onError(ERROR_CLIENT)); - } - } - - private void handleTriggerModelDownload( - Intent recognizerIntent, - @Nullable Executor callbackExecutor, - @Nullable ModelDownloadListener modelDownloadListener) { - if (!maybeInitializeManagerService() || !checkOpenConnection()) { - return; - } - - // Trigger model download without a listener. - if (modelDownloadListener == null) { - try { - mService.triggerModelDownload( - recognizerIntent, mContext.getAttributionSource(), null); - if (DBG) Log.d(TAG, "triggerModelDownload() without a listener"); - } catch (final Exception e) { - Log.e(TAG, "triggerModelDownload() without a listener failed", e); - mListener.onError(ERROR_CLIENT); - } - } - // Trigger model download with a listener. - else { - try { - mService.triggerModelDownload( - recognizerIntent, mContext.getAttributionSource(), - new InternalModelDownloadListener(callbackExecutor, modelDownloadListener)); - if (DBG) Log.d(TAG, "triggerModelDownload() with a listener"); - } catch (final Exception e) { - Log.e(TAG, "triggerModelDownload() with a listener failed", e); - callbackExecutor.execute(() -> modelDownloadListener.onError(ERROR_CLIENT)); - } - } - } - - private boolean checkOpenConnection() { - if (mService != null && mService.asBinder().isBinderAlive()) { - return true; - } - mListener.onError(ERROR_CLIENT); - Log.e(TAG, "not connected to the recognition service"); - return false; - } - - /** changes the listener */ - private void handleChangeListener(RecognitionListener listener) { - if (DBG) Log.d(TAG, "handleChangeListener, listener=" + listener); - mListener.mInternalListener = listener; - } - - /** Destroys the {@code SpeechRecognizer} object. */ - public void destroy() { - try { - if (mService != null) { - try { - mService.cancel(mListener, /*isShutdown*/ true); - } catch (final Exception e) { - // Not important - } - } - - mService = null; - mPendingTasks.clear(); - mListener.mInternalListener = null; - mCloseGuard.close(); - } finally { - Reference.reachabilityFence(this); - } - } - - @Override - protected void finalize() throws Throwable { - try { - mCloseGuard.warnIfOpen(); - destroy(); - } finally { - super.finalize(); - } - } - - /** Establishes a connection to system server proxy and initializes the session. */ - private void connectToSystemService() { - if (!maybeInitializeManagerService()) { - return; - } - - ComponentName componentName = getSpeechRecognizerComponentName(); - - if (!mOnDevice && componentName == null) { - mListener.onError(ERROR_CLIENT); - return; - } - - try { - mManagerService.createSession( - componentName, - mClientToken, - mOnDevice, - new IRecognitionServiceManagerCallback.Stub(){ - @Override - public void onSuccess(IRecognitionService service) throws RemoteException { - if (DBG) { - Log.i(TAG, "Connected to speech recognition service"); - } - mService = service; - while (!mPendingTasks.isEmpty()) { - mHandler.sendMessage(mPendingTasks.poll()); - } - } - - @Override - public void onError(int errorCode) throws RemoteException { - Log.e(TAG, "Bind to system recognition service failed with error " - + errorCode); - mListener.onError(errorCode); - } - }); - } catch (RemoteException e) { - e.rethrowFromSystemServer(); - } - } - - private synchronized boolean maybeInitializeManagerService() { - if (DBG) { - Log.i(TAG, "#maybeInitializeManagerService found = " + mManagerService); - } - if (mManagerService != null) { - return true; - } - - IBinder service = ServiceManager.getService(Context.SPEECH_RECOGNITION_SERVICE); - if (service == null && mOnDevice) { - service = (IBinder) mContext.getSystemService(Context.SPEECH_RECOGNITION_SERVICE); - } - mManagerService = IRecognitionServiceManager.Stub.asInterface(service); - - if (mManagerService == null) { - if (mListener != null) { - mListener.onError(ERROR_CLIENT); - } - return false; - } - return true; + throw new UnsupportedOperationException(); } /** - * Returns the component name to be used for establishing a connection, based on the parameters - * used during initialization. - * - * <p>Note the 3 different scenarios: - * <ol> - * <li>On-device speech recognizer which is determined by the manufacturer and not - * changeable by the user - * <li>Default user-selected speech recognizer as specified by - * {@code Settings.Secure.VOICE_RECOGNITION_SERVICE} - * <li>Custom speech recognizer supplied by the client. + * Destroys the {@code SpeechRecognizer} object. */ - private ComponentName getSpeechRecognizerComponentName() { - if (mOnDevice) { - return null; - } - - if (mServiceComponent != null) { - return mServiceComponent; - } - - String serviceComponent = Settings.Secure.getString(mContext.getContentResolver(), - Settings.Secure.VOICE_RECOGNITION_SERVICE); - - if (TextUtils.isEmpty(serviceComponent)) { - Log.e(TAG, "no selected voice recognition service"); - mListener.onError(ERROR_CLIENT); - return null; - } - - return ComponentName.unflattenFromString(serviceComponent); - } - - private static class CheckRecognitionSupportArgs { - final Intent mIntent; - final Executor mCallbackExecutor; - final RecognitionSupportCallback mCallback; - - private CheckRecognitionSupportArgs( - Intent intent, - Executor callbackExecutor, - RecognitionSupportCallback callback) { - mIntent = intent; - mCallbackExecutor = callbackExecutor; - mCallback = callback; - } - } - - private static class ModelDownloadListenerArgs { - final Intent mIntent; - final Executor mExecutor; - final ModelDownloadListener mModelDownloadListener; - - private ModelDownloadListenerArgs(Intent intent, Executor executor, - ModelDownloadListener modelDownloadListener) { - mIntent = intent; - mExecutor = executor; - mModelDownloadListener = modelDownloadListener; - } - } - - /** - * Internal wrapper of IRecognitionListener which will propagate the results to - * RecognitionListener - */ - private static class InternalRecognitionListener extends IRecognitionListener.Stub { - private RecognitionListener mInternalListener; - - private static final int MSG_BEGINNING_OF_SPEECH = 1; - private static final int MSG_BUFFER_RECEIVED = 2; - private static final int MSG_END_OF_SPEECH = 3; - private static final int MSG_ERROR = 4; - private static final int MSG_READY_FOR_SPEECH = 5; - private static final int MSG_RESULTS = 6; - private static final int MSG_PARTIAL_RESULTS = 7; - private static final int MSG_RMS_CHANGED = 8; - private static final int MSG_ON_EVENT = 9; - private static final int MSG_SEGMENT_RESULTS = 10; - private static final int MSG_SEGMENT_END_SESSION = 11; - private static final int MSG_LANGUAGE_DETECTION = 12; - - private final Handler mInternalHandler = new Handler(Looper.getMainLooper()) { - @Override - public void handleMessage(Message msg) { - if (mInternalListener == null) { - return; - } - switch (msg.what) { - case MSG_BEGINNING_OF_SPEECH: - mInternalListener.onBeginningOfSpeech(); - break; - case MSG_BUFFER_RECEIVED: - mInternalListener.onBufferReceived((byte[]) msg.obj); - break; - case MSG_END_OF_SPEECH: - mInternalListener.onEndOfSpeech(); - break; - case MSG_ERROR: - mInternalListener.onError((Integer) msg.obj); - break; - case MSG_READY_FOR_SPEECH: - mInternalListener.onReadyForSpeech((Bundle) msg.obj); - break; - case MSG_RESULTS: - mInternalListener.onResults((Bundle) msg.obj); - break; - case MSG_PARTIAL_RESULTS: - mInternalListener.onPartialResults((Bundle) msg.obj); - break; - case MSG_RMS_CHANGED: - mInternalListener.onRmsChanged((Float) msg.obj); - break; - case MSG_ON_EVENT: - mInternalListener.onEvent(msg.arg1, (Bundle) msg.obj); - break; - case MSG_SEGMENT_RESULTS: - mInternalListener.onSegmentResults((Bundle) msg.obj); - break; - case MSG_SEGMENT_END_SESSION: - mInternalListener.onEndOfSegmentedSession(); - break; - case MSG_LANGUAGE_DETECTION: - mInternalListener.onLanguageDetection((Bundle) msg.obj); - break; - } - } - }; - - public void onBeginningOfSpeech() { - Message.obtain(mInternalHandler, MSG_BEGINNING_OF_SPEECH).sendToTarget(); - } - - public void onBufferReceived(final byte[] buffer) { - Message.obtain(mInternalHandler, MSG_BUFFER_RECEIVED, buffer).sendToTarget(); - } - - public void onEndOfSpeech() { - Message.obtain(mInternalHandler, MSG_END_OF_SPEECH).sendToTarget(); - } - - public void onError(final int error) { - Message.obtain(mInternalHandler, MSG_ERROR, error).sendToTarget(); - } - - public void onReadyForSpeech(final Bundle noiseParams) { - Message.obtain(mInternalHandler, MSG_READY_FOR_SPEECH, noiseParams).sendToTarget(); - } - - public void onResults(final Bundle results) { - Message.obtain(mInternalHandler, MSG_RESULTS, results).sendToTarget(); - } - - public void onPartialResults(final Bundle results) { - Message.obtain(mInternalHandler, MSG_PARTIAL_RESULTS, results).sendToTarget(); - } - - public void onRmsChanged(final float rmsdB) { - Message.obtain(mInternalHandler, MSG_RMS_CHANGED, rmsdB).sendToTarget(); - } - - public void onSegmentResults(final Bundle bundle) { - Message.obtain(mInternalHandler, MSG_SEGMENT_RESULTS, bundle).sendToTarget(); - } - - public void onEndOfSegmentedSession() { - Message.obtain(mInternalHandler, MSG_SEGMENT_END_SESSION).sendToTarget(); - } - - public void onLanguageDetection(final Bundle results) { - Message.obtain(mInternalHandler, MSG_LANGUAGE_DETECTION, results).sendToTarget(); - } - - public void onEvent(final int eventType, final Bundle params) { - Message.obtain(mInternalHandler, MSG_ON_EVENT, eventType, eventType, params) - .sendToTarget(); - } - } - - private static class InternalSupportCallback extends IRecognitionSupportCallback.Stub { - private final Executor mExecutor; - private final RecognitionSupportCallback mCallback; - - private InternalSupportCallback(Executor executor, RecognitionSupportCallback callback) { - this.mExecutor = executor; - this.mCallback = callback; - } - - @Override - public void onSupportResult(RecognitionSupport recognitionSupport) throws RemoteException { - mExecutor.execute(() -> mCallback.onSupportResult(recognitionSupport)); - } - - @Override - public void onError(int errorCode) throws RemoteException { - mExecutor.execute(() -> mCallback.onError(errorCode)); - } - } - - private static class InternalModelDownloadListener extends IModelDownloadListener.Stub { - private final Executor mExecutor; - private final ModelDownloadListener mModelDownloadListener; - - private InternalModelDownloadListener( - Executor executor, - @NonNull ModelDownloadListener modelDownloadListener) { - mExecutor = executor; - mModelDownloadListener = modelDownloadListener; - } - - @Override - public void onProgress(int completedPercent) throws RemoteException { - mExecutor.execute(() -> mModelDownloadListener.onProgress(completedPercent)); - } - - @Override - public void onSuccess() throws RemoteException { - mExecutor.execute(() -> mModelDownloadListener.onSuccess()); - } - - @Override - public void onScheduled() throws RemoteException { - mExecutor.execute(() -> mModelDownloadListener.onScheduled()); - } - - @Override - public void onError(int error) throws RemoteException { - mExecutor.execute(() -> mModelDownloadListener.onError(error)); - } + public void destroy() { + throw new UnsupportedOperationException(); } } diff --git a/core/java/android/speech/SpeechRecognizerImpl.java b/core/java/android/speech/SpeechRecognizerImpl.java new file mode 100644 index 000000000000..6c008743f07a --- /dev/null +++ b/core/java/android/speech/SpeechRecognizerImpl.java @@ -0,0 +1,745 @@ +/* + * Copyright (C) 2010 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 android.speech; + +import android.Manifest; +import android.annotation.CallbackExecutor; +import android.annotation.MainThread; +import android.annotation.NonNull; +import android.annotation.Nullable; +import android.annotation.RequiresPermission; +import android.content.ComponentName; +import android.content.Context; +import android.content.Intent; +import android.os.Binder; +import android.os.Bundle; +import android.os.Handler; +import android.os.IBinder; +import android.os.Looper; +import android.os.Message; +import android.os.RemoteException; +import android.os.ServiceManager; +import android.provider.Settings; +import android.text.TextUtils; +import android.util.Log; +import android.util.Slog; + +import java.util.Objects; +import java.util.Queue; +import java.util.concurrent.Executor; +import java.util.concurrent.LinkedBlockingQueue; + +/** + * @hide + */ +class SpeechRecognizerImpl extends SpeechRecognizer { + /** DEBUG value to enable verbose debug prints */ + private static final boolean DBG = false; + + /** Log messages identifier */ + private static final String TAG = "SpeechRecognizer"; + + /** action codes */ + private static final int MSG_START = 1; + private static final int MSG_STOP = 2; + private static final int MSG_CANCEL = 3; + private static final int MSG_CHANGE_LISTENER = 4; + 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; + + /** The actual RecognitionService endpoint */ + private IRecognitionService mService; + + /** Context with which the manager was created */ + private final Context mContext; + + /** Component to direct service intent to */ + private final ComponentName mServiceComponent; + + /** Whether to use on-device speech recognizer. */ + private final boolean mOnDevice; + + private IRecognitionServiceManager mManagerService; + + /** Handler that will execute the main tasks */ + private 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: + handleSetTemporaryComponent((ComponentName) msg.obj); + break; + case MSG_CHECK_RECOGNITION_SUPPORT: + CheckRecognitionSupportArgs args = (CheckRecognitionSupportArgs) msg.obj; + handleCheckRecognitionSupport( + args.mIntent, args.mCallbackExecutor, args.mCallback); + break; + case MSG_TRIGGER_MODEL_DOWNLOAD: + ModelDownloadListenerArgs modelDownloadListenerArgs = + (ModelDownloadListenerArgs) msg.obj; + handleTriggerModelDownload( + modelDownloadListenerArgs.mIntent, + modelDownloadListenerArgs.mExecutor, + modelDownloadListenerArgs.mModelDownloadListener); + break; + } + } + }; + + /** + * Temporary queue, saving the messages until the connection will be established, afterwards, + * only mHandler will receive the messages + */ + private final Queue<Message> mPendingTasks = new LinkedBlockingQueue<>(); + + /** The Listener that will receive all the callbacks */ + private final InternalRecognitionListener mListener = new InternalRecognitionListener(); + + private final IBinder mClientToken = new Binder(); + + /** + * The right way to create a {@code SpeechRecognizer} is by using + * {@link #createSpeechRecognizer} static factory method + */ + /* package */ SpeechRecognizerImpl( + final Context context, + final ComponentName serviceComponent) { + this(context, serviceComponent, false); + } + + /** + * The right way to create a {@code SpeechRecognizer} is by using + * {@link #createOnDeviceSpeechRecognizer} static factory method + */ + /* package */ SpeechRecognizerImpl(final Context context, boolean onDevice) { + this(context, null, onDevice); + } + + private SpeechRecognizerImpl( + final Context context, + final ComponentName serviceComponent, + final boolean onDevice) { + mContext = context; + mServiceComponent = serviceComponent; + mOnDevice = onDevice; + } + + @NonNull + @MainThread + /* package */ static SpeechRecognizerImpl lenientlyCreateOnDeviceSpeechRecognizer( + @NonNull final Context context) { + if (context == null) { + throw new IllegalArgumentException("Context cannot be null"); + } + checkIsCalledFromMainThread(); + return new SpeechRecognizerImpl(context, /* onDevice */ true); + } + + @Override + @MainThread + public void setRecognitionListener(RecognitionListener listener) { + checkIsCalledFromMainThread(); + if (mListener.mInternalListener == null) { + // This shortcut is needed because otherwise, if there's an error connecting, it never + // gets delivered. I.e., the onSuccess callback set up in connectToSystemService does + // not get called, MSG_CHANGE_LISTENER does not get executed, so the onError in the same + // place does not get forwarded anywhere. + // Thread-wise, this is safe as both this method and the handler are on the UI thread. + handleChangeListener(listener); + } else { + putMessage(Message.obtain(mHandler, MSG_CHANGE_LISTENER, listener)); + } + } + + @Override + @MainThread + public void startListening(final Intent recognizerIntent) { + if (recognizerIntent == null) { + throw new IllegalArgumentException("intent must not be null"); + } + checkIsCalledFromMainThread(); + + if (DBG) { + Slog.i(TAG, "#startListening called"); + if (mService == null) { + Slog.i(TAG, "Connection is not established yet"); + } + } + + if (mService == null) { + // First time connection: first establish a connection, then dispatch #startListening. + connectToSystemService(); + } + putMessage(Message.obtain(mHandler, MSG_START, recognizerIntent)); + } + + @Override + @MainThread + public void stopListening() { + checkIsCalledFromMainThread(); + + if (DBG) { + Slog.i(TAG, "#stopListening called"); + if (mService == null) { + Slog.i(TAG, "Connection is not established yet"); + } + } + + putMessage(Message.obtain(mHandler, MSG_STOP)); + } + + @Override + @MainThread + public void cancel() { + checkIsCalledFromMainThread(); + putMessage(Message.obtain(mHandler, MSG_CANCEL)); + } + + @Override + public void checkRecognitionSupport( + @NonNull Intent recognizerIntent, + @NonNull @CallbackExecutor Executor executor, + @NonNull RecognitionSupportCallback supportListener) { + Objects.requireNonNull(recognizerIntent, "intent must not be null"); + Objects.requireNonNull(supportListener, "listener must not be null"); + + if (DBG) { + Slog.i(TAG, "#checkRecognitionSupport called"); + if (mService == null) { + Slog.i(TAG, "Connection is not established yet"); + } + } + + if (mService == null) { + // First time connection: first establish a connection, then dispatch. + connectToSystemService(); + } + putMessage(Message.obtain(mHandler, MSG_CHECK_RECOGNITION_SUPPORT, + new CheckRecognitionSupportArgs(recognizerIntent, executor, supportListener))); + } + + @Override + public void triggerModelDownload(@NonNull Intent recognizerIntent) { + Objects.requireNonNull(recognizerIntent, "intent must not be null"); + if (DBG) { + Slog.i(TAG, "#triggerModelDownload without a listener called"); + if (mService == null) { + Slog.i(TAG, "Connection is not established yet"); + } + } + if (mService == null) { + // First time connection: first establish a connection, then dispatch. + connectToSystemService(); + } + putMessage(Message.obtain( + mHandler, MSG_TRIGGER_MODEL_DOWNLOAD, + new ModelDownloadListenerArgs(recognizerIntent, null, null))); + } + + @Override + public void triggerModelDownload( + @NonNull Intent recognizerIntent, + @NonNull @CallbackExecutor Executor executor, + @NonNull ModelDownloadListener listener) { + Objects.requireNonNull(recognizerIntent, "intent must not be null"); + if (DBG) { + Slog.i(TAG, "#triggerModelDownload with a listener called"); + if (mService == null) { + Slog.i(TAG, "Connection is not established yet"); + } + } + if (mService == null) { + // First time connection: first establish a connection, then dispatch. + connectToSystemService(); + } + putMessage(Message.obtain( + mHandler, MSG_TRIGGER_MODEL_DOWNLOAD, + new ModelDownloadListenerArgs(recognizerIntent, executor, listener))); + } + + @Override + @RequiresPermission(Manifest.permission.MANAGE_SPEECH_RECOGNITION) + public void setTemporaryOnDeviceRecognizer(@Nullable ComponentName componentName) { + mHandler.sendMessage( + Message.obtain(mHandler, MSG_SET_TEMPORARY_ON_DEVICE_COMPONENT, componentName)); + } + + /* package */ static void checkIsCalledFromMainThread() { + if (Looper.myLooper() != Looper.getMainLooper()) { + throw new RuntimeException( + "SpeechRecognizer should be used only from the application's main thread"); + } + } + + private void putMessage(Message msg) { + if (mService == null) { + mPendingTasks.offer(msg); + } else { + mHandler.sendMessage(msg); + } + } + + /** sends the actual message to the service */ + private void handleStartListening(Intent recognizerIntent) { + if (!checkOpenConnection()) { + return; + } + try { + mService.startListening(recognizerIntent, mListener, mContext.getAttributionSource()); + if (DBG) Log.d(TAG, "service start listening command succeeded"); + } catch (final Exception e) { + Log.e(TAG, "startListening() failed", e); + mListener.onError(ERROR_CLIENT); + } + } + + /** sends the actual message to the service */ + private void handleStopMessage() { + if (!checkOpenConnection()) { + return; + } + try { + mService.stopListening(mListener); + if (DBG) Log.d(TAG, "service stop listening command succeeded"); + } catch (final Exception e) { + Log.e(TAG, "stopListening() failed", e); + mListener.onError(ERROR_CLIENT); + } + } + + /** sends the actual message to the service */ + private void handleCancelMessage() { + if (!checkOpenConnection()) { + return; + } + try { + mService.cancel(mListener, /*isShutdown*/ false); + if (DBG) Log.d(TAG, "service cancel command succeeded"); + } catch (final Exception e) { + Log.e(TAG, "cancel() failed", e); + mListener.onError(ERROR_CLIENT); + } + } + + private void handleSetTemporaryComponent(ComponentName componentName) { + if (DBG) { + Log.d(TAG, "handleSetTemporaryComponent, componentName=" + componentName); + } + + if (!maybeInitializeManagerService()) { + return; + } + + try { + mManagerService.setTemporaryComponent(componentName); + } catch (final RemoteException e) { + e.rethrowFromSystemServer(); + } + } + + private void handleCheckRecognitionSupport( + Intent recognizerIntent, + Executor callbackExecutor, + RecognitionSupportCallback recognitionSupportCallback) { + if (!maybeInitializeManagerService() || !checkOpenConnection()) { + return; + } + try { + mService.checkRecognitionSupport( + recognizerIntent, + mContext.getAttributionSource(), + new InternalSupportCallback(callbackExecutor, recognitionSupportCallback)); + if (DBG) Log.d(TAG, "service support command succeeded"); + } catch (final Exception e) { + Log.e(TAG, "checkRecognitionSupport() failed", e); + callbackExecutor.execute(() -> recognitionSupportCallback.onError(ERROR_CLIENT)); + } + } + + private void handleTriggerModelDownload( + Intent recognizerIntent, + @Nullable Executor callbackExecutor, + @Nullable ModelDownloadListener modelDownloadListener) { + if (!maybeInitializeManagerService() || !checkOpenConnection()) { + return; + } + + if (modelDownloadListener == null) { + // Trigger model download without a listener. + try { + mService.triggerModelDownload( + recognizerIntent, mContext.getAttributionSource(), null); + if (DBG) Log.d(TAG, "triggerModelDownload() without a listener"); + } catch (final Exception e) { + Log.e(TAG, "triggerModelDownload() without a listener failed", e); + mListener.onError(ERROR_CLIENT); + } + } else { + // Trigger model download with a listener. + try { + mService.triggerModelDownload( + recognizerIntent, mContext.getAttributionSource(), + new InternalModelDownloadListener(callbackExecutor, modelDownloadListener)); + if (DBG) Log.d(TAG, "triggerModelDownload() with a listener"); + } catch (final Exception e) { + Log.e(TAG, "triggerModelDownload() with a listener failed", e); + callbackExecutor.execute(() -> modelDownloadListener.onError(ERROR_CLIENT)); + } + } + } + + private boolean checkOpenConnection() { + if (mService != null && mService.asBinder().isBinderAlive()) { + return true; + } + mListener.onError(ERROR_CLIENT); + Log.e(TAG, "not connected to the recognition service"); + return false; + } + + /** changes the listener */ + private void handleChangeListener(RecognitionListener listener) { + if (DBG) Log.d(TAG, "handleChangeListener, listener=" + listener); + mListener.mInternalListener = listener; + } + + @Override + public void destroy() { + if (mService != null) { + try { + mService.cancel(mListener, /*isShutdown*/ true); + } catch (final Exception e) { + // Not important + } + } + + mService = null; + mPendingTasks.clear(); + mListener.mInternalListener = null; + } + + /** Establishes a connection to system server proxy and initializes the session. */ + private void connectToSystemService() { + if (!maybeInitializeManagerService()) { + return; + } + + ComponentName componentName = getSpeechRecognizerComponentName(); + + if (!mOnDevice && componentName == null) { + mListener.onError(ERROR_CLIENT); + return; + } + + try { + mManagerService.createSession( + componentName, + mClientToken, + mOnDevice, + new IRecognitionServiceManagerCallback.Stub(){ + @Override + public void onSuccess(IRecognitionService service) throws RemoteException { + if (DBG) { + Log.i(TAG, "Connected to speech recognition service"); + } + mService = service; + while (!mPendingTasks.isEmpty()) { + mHandler.sendMessage(mPendingTasks.poll()); + } + } + + @Override + public void onError(int errorCode) throws RemoteException { + Log.e(TAG, "Bind to system recognition service failed with error " + + errorCode); + mListener.onError(errorCode); + } + }); + } catch (RemoteException e) { + e.rethrowFromSystemServer(); + } + } + + private synchronized boolean maybeInitializeManagerService() { + if (DBG) { + Log.i(TAG, "#maybeInitializeManagerService found = " + mManagerService); + } + if (mManagerService != null) { + return true; + } + + IBinder service = ServiceManager.getService(Context.SPEECH_RECOGNITION_SERVICE); + if (service == null && mOnDevice) { + service = (IBinder) mContext.getSystemService(Context.SPEECH_RECOGNITION_SERVICE); + } + mManagerService = IRecognitionServiceManager.Stub.asInterface(service); + + if (mManagerService == null) { + if (mListener != null) { + mListener.onError(ERROR_CLIENT); + } + return false; + } + return true; + } + + /** + * Returns the component name to be used for establishing a connection, based on the parameters + * used during initialization. + * + * <p>Note the 3 different scenarios: + * <ol> + * <li>On-device speech recognizer which is determined by the manufacturer and not + * changeable by the user + * <li>Default user-selected speech recognizer as specified by + * {@code Settings.Secure.VOICE_RECOGNITION_SERVICE} + * <li>Custom speech recognizer supplied by the client. + * </ol> + */ + @SuppressWarnings("NonUserGetterCalled") + private ComponentName getSpeechRecognizerComponentName() { + if (mOnDevice) { + return null; + } + + if (mServiceComponent != null) { + return mServiceComponent; + } + + String serviceComponent = Settings.Secure.getString(mContext.getContentResolver(), + Settings.Secure.VOICE_RECOGNITION_SERVICE); + + if (TextUtils.isEmpty(serviceComponent)) { + Log.e(TAG, "no selected voice recognition service"); + mListener.onError(ERROR_CLIENT); + return null; + } + + return ComponentName.unflattenFromString(serviceComponent); + } + + private static class CheckRecognitionSupportArgs { + final Intent mIntent; + final Executor mCallbackExecutor; + final RecognitionSupportCallback mCallback; + + private CheckRecognitionSupportArgs( + Intent intent, + Executor callbackExecutor, + RecognitionSupportCallback callback) { + mIntent = intent; + mCallbackExecutor = callbackExecutor; + mCallback = callback; + } + } + + private static class ModelDownloadListenerArgs { + final Intent mIntent; + final Executor mExecutor; + final ModelDownloadListener mModelDownloadListener; + + private ModelDownloadListenerArgs(Intent intent, Executor executor, + ModelDownloadListener modelDownloadListener) { + mIntent = intent; + mExecutor = executor; + mModelDownloadListener = modelDownloadListener; + } + } + + /** + * Internal wrapper of IRecognitionListener which will propagate the results to + * RecognitionListener + */ + private static class InternalRecognitionListener extends IRecognitionListener.Stub { + private RecognitionListener mInternalListener; + + private static final int MSG_BEGINNING_OF_SPEECH = 1; + private static final int MSG_BUFFER_RECEIVED = 2; + private static final int MSG_END_OF_SPEECH = 3; + private static final int MSG_ERROR = 4; + private static final int MSG_READY_FOR_SPEECH = 5; + private static final int MSG_RESULTS = 6; + private static final int MSG_PARTIAL_RESULTS = 7; + private static final int MSG_RMS_CHANGED = 8; + private static final int MSG_ON_EVENT = 9; + private static final int MSG_SEGMENT_RESULTS = 10; + private static final int MSG_SEGMENT_END_SESSION = 11; + private static final int MSG_LANGUAGE_DETECTION = 12; + + private final Handler mInternalHandler = new Handler(Looper.getMainLooper()) { + @Override + public void handleMessage(Message msg) { + if (mInternalListener == null) { + return; + } + switch (msg.what) { + case MSG_BEGINNING_OF_SPEECH: + mInternalListener.onBeginningOfSpeech(); + break; + case MSG_BUFFER_RECEIVED: + mInternalListener.onBufferReceived((byte[]) msg.obj); + break; + case MSG_END_OF_SPEECH: + mInternalListener.onEndOfSpeech(); + break; + case MSG_ERROR: + mInternalListener.onError((Integer) msg.obj); + break; + case MSG_READY_FOR_SPEECH: + mInternalListener.onReadyForSpeech((Bundle) msg.obj); + break; + case MSG_RESULTS: + mInternalListener.onResults((Bundle) msg.obj); + break; + case MSG_PARTIAL_RESULTS: + mInternalListener.onPartialResults((Bundle) msg.obj); + break; + case MSG_RMS_CHANGED: + mInternalListener.onRmsChanged((Float) msg.obj); + break; + case MSG_ON_EVENT: + mInternalListener.onEvent(msg.arg1, (Bundle) msg.obj); + break; + case MSG_SEGMENT_RESULTS: + mInternalListener.onSegmentResults((Bundle) msg.obj); + break; + case MSG_SEGMENT_END_SESSION: + mInternalListener.onEndOfSegmentedSession(); + break; + case MSG_LANGUAGE_DETECTION: + mInternalListener.onLanguageDetection((Bundle) msg.obj); + break; + } + } + }; + + public void onBeginningOfSpeech() { + Message.obtain(mInternalHandler, MSG_BEGINNING_OF_SPEECH).sendToTarget(); + } + + public void onBufferReceived(final byte[] buffer) { + Message.obtain(mInternalHandler, MSG_BUFFER_RECEIVED, buffer).sendToTarget(); + } + + public void onEndOfSpeech() { + Message.obtain(mInternalHandler, MSG_END_OF_SPEECH).sendToTarget(); + } + + public void onError(final int error) { + Message.obtain(mInternalHandler, MSG_ERROR, error).sendToTarget(); + } + + public void onReadyForSpeech(final Bundle noiseParams) { + Message.obtain(mInternalHandler, MSG_READY_FOR_SPEECH, noiseParams).sendToTarget(); + } + + public void onResults(final Bundle results) { + Message.obtain(mInternalHandler, MSG_RESULTS, results).sendToTarget(); + } + + public void onPartialResults(final Bundle results) { + Message.obtain(mInternalHandler, MSG_PARTIAL_RESULTS, results).sendToTarget(); + } + + public void onRmsChanged(final float rmsdB) { + Message.obtain(mInternalHandler, MSG_RMS_CHANGED, rmsdB).sendToTarget(); + } + + public void onSegmentResults(final Bundle bundle) { + Message.obtain(mInternalHandler, MSG_SEGMENT_RESULTS, bundle).sendToTarget(); + } + + public void onEndOfSegmentedSession() { + Message.obtain(mInternalHandler, MSG_SEGMENT_END_SESSION).sendToTarget(); + } + + public void onLanguageDetection(final Bundle results) { + Message.obtain(mInternalHandler, MSG_LANGUAGE_DETECTION, results).sendToTarget(); + } + + public void onEvent(final int eventType, final Bundle params) { + Message.obtain(mInternalHandler, MSG_ON_EVENT, eventType, eventType, params) + .sendToTarget(); + } + } + + private static class InternalSupportCallback extends IRecognitionSupportCallback.Stub { + private final Executor mExecutor; + private final RecognitionSupportCallback mCallback; + + private InternalSupportCallback(Executor executor, RecognitionSupportCallback callback) { + this.mExecutor = executor; + this.mCallback = callback; + } + + @Override + public void onSupportResult(RecognitionSupport recognitionSupport) throws RemoteException { + mExecutor.execute(() -> mCallback.onSupportResult(recognitionSupport)); + } + + @Override + public void onError(int errorCode) throws RemoteException { + mExecutor.execute(() -> mCallback.onError(errorCode)); + } + } + + private static class InternalModelDownloadListener extends IModelDownloadListener.Stub { + private final Executor mExecutor; + private final ModelDownloadListener mModelDownloadListener; + + private InternalModelDownloadListener( + Executor executor, + @NonNull ModelDownloadListener modelDownloadListener) { + mExecutor = executor; + mModelDownloadListener = modelDownloadListener; + } + + @Override + public void onProgress(int completedPercent) throws RemoteException { + mExecutor.execute(() -> mModelDownloadListener.onProgress(completedPercent)); + } + + @Override + public void onSuccess() throws RemoteException { + mExecutor.execute(() -> mModelDownloadListener.onSuccess()); + } + + @Override + public void onScheduled() throws RemoteException { + mExecutor.execute(() -> mModelDownloadListener.onScheduled()); + } + + @Override + public void onError(int error) throws RemoteException { + mExecutor.execute(() -> mModelDownloadListener.onError(error)); + } + } +} diff --git a/core/java/android/speech/SpeechRecognizerProxy.java b/core/java/android/speech/SpeechRecognizerProxy.java new file mode 100644 index 000000000000..be9be9a94538 --- /dev/null +++ b/core/java/android/speech/SpeechRecognizerProxy.java @@ -0,0 +1,108 @@ +/* + * Copyright (C) 2010 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 android.speech; + +import android.content.ComponentName; +import android.content.Intent; +import android.util.CloseGuard; + +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; + +import java.lang.ref.Reference; +import java.util.concurrent.Executor; + +/** + * @hide + */ +class SpeechRecognizerProxy extends SpeechRecognizer { + + private final CloseGuard mCloseGuard = new CloseGuard(); + + private final SpeechRecognizer mDelegate; + + SpeechRecognizerProxy(final SpeechRecognizer delegate) { + mDelegate = delegate; + mCloseGuard.open("SpeechRecognizer#destroy()"); + } + + @Override + public void setRecognitionListener(RecognitionListener listener) { + mDelegate.setRecognitionListener(listener); + } + + @Override + public void startListening(Intent recognizerIntent) { + mDelegate.startListening(recognizerIntent); + } + + @Override + public void stopListening() { + mDelegate.stopListening(); + } + + @Override + public void cancel() { + mDelegate.cancel(); + } + + @Override + public void destroy() { + try { + mCloseGuard.close(); + mDelegate.destroy(); + } finally { + Reference.reachabilityFence(this); + } + } + + @Override + public void checkRecognitionSupport( + @NonNull Intent recognizerIntent, + @NonNull Executor executor, + @NonNull RecognitionSupportCallback supportListener) { + mDelegate.checkRecognitionSupport(recognizerIntent, executor, supportListener); + } + + @Override + public void triggerModelDownload(@NonNull Intent recognizerIntent) { + mDelegate.triggerModelDownload(recognizerIntent); + } + + @Override + public void triggerModelDownload( + @NonNull Intent recognizerIntent, + @NonNull Executor executor, + @NonNull ModelDownloadListener listener) { + mDelegate.triggerModelDownload(recognizerIntent, executor, listener); + } + + @Override + public void setTemporaryOnDeviceRecognizer(@Nullable ComponentName componentName) { + mDelegate.setTemporaryOnDeviceRecognizer(componentName); + } + + @Override + protected void finalize() throws Throwable { + try { + mCloseGuard.warnIfOpen(); + destroy(); + } finally { + super.finalize(); + } + } +} diff --git a/services/core/java/com/android/server/speech/SpeechRecognitionManagerServiceImpl.java b/services/core/java/com/android/server/speech/SpeechRecognitionManagerServiceImpl.java index 8b57f87f5df3..8e9c889c8ef3 100644 --- a/services/core/java/com/android/server/speech/SpeechRecognitionManagerServiceImpl.java +++ b/services/core/java/com/android/server/speech/SpeechRecognitionManagerServiceImpl.java @@ -38,6 +38,7 @@ import android.speech.IRecognitionServiceManagerCallback; import android.speech.IRecognitionSupportCallback; import android.speech.RecognitionService; import android.speech.SpeechRecognizer; +import android.util.Log; import android.util.Slog; import android.util.SparseIntArray; @@ -253,6 +254,7 @@ final class SpeechRecognitionManagerServiceImpl extends @GuardedBy("mLock") private void incrementSessionCountForUidLocked(int uid) { mSessionCountByUid.put(uid, mSessionCountByUid.get(uid, 0) + 1); + Log.i(TAG, "Client " + uid + " has opened " + mSessionCountByUid.get(uid, 0) + " sessions"); } @GuardedBy("mLock") |