summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--core/java/android/speech/SpeechRecognizer.java741
-rw-r--r--core/java/android/speech/SpeechRecognizerImpl.java745
-rw-r--r--core/java/android/speech/SpeechRecognizerProxy.java108
-rw-r--r--services/core/java/com/android/server/speech/SpeechRecognitionManagerServiceImpl.java2
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")