diff options
| -rw-r--r-- | core/api/system-current.txt | 1 | ||||
| -rw-r--r-- | core/api/test-current.txt | 8 | ||||
| -rw-r--r-- | core/java/android/speech/IRecognitionServiceManager.aidl | 2 | ||||
| -rw-r--r-- | core/java/android/speech/SpeechRecognizer.java | 121 | ||||
| -rw-r--r-- | core/res/AndroidManifest.xml | 9 | ||||
| -rw-r--r-- | services/core/java/com/android/server/speech/SpeechRecognitionManagerService.java | 31 | ||||
| -rw-r--r-- | services/core/java/com/android/server/speech/SpeechRecognitionManagerServiceImpl.java | 16 |
7 files changed, 159 insertions, 29 deletions
diff --git a/core/api/system-current.txt b/core/api/system-current.txt index 5ca4d35d4c22..0b439434d586 100644 --- a/core/api/system-current.txt +++ b/core/api/system-current.txt @@ -147,6 +147,7 @@ package android { field public static final String MANAGE_SENSOR_PRIVACY = "android.permission.MANAGE_SENSOR_PRIVACY"; field public static final String MANAGE_SMARTSPACE = "android.permission.MANAGE_SMARTSPACE"; field public static final String MANAGE_SOUND_TRIGGER = "android.permission.MANAGE_SOUND_TRIGGER"; + field public static final String MANAGE_SPEECH_RECOGNITION = "android.permission.MANAGE_SPEECH_RECOGNITION"; field public static final String MANAGE_SUBSCRIPTION_PLANS = "android.permission.MANAGE_SUBSCRIPTION_PLANS"; field public static final String MANAGE_TEST_NETWORKS = "android.permission.MANAGE_TEST_NETWORKS"; field public static final String MANAGE_TIME_AND_ZONE_DETECTION = "android.permission.MANAGE_TIME_AND_ZONE_DETECTION"; diff --git a/core/api/test-current.txt b/core/api/test-current.txt index 7b5b1989c1e5..0e30246ded37 100644 --- a/core/api/test-current.txt +++ b/core/api/test-current.txt @@ -2123,6 +2123,14 @@ package android.service.watchdog { } +package android.speech { + + public class SpeechRecognizer { + method public void setTemporaryOnDeviceRecognizer(@Nullable android.content.ComponentName); + } + +} + package android.telecom { public static class Call.Details { diff --git a/core/java/android/speech/IRecognitionServiceManager.aidl b/core/java/android/speech/IRecognitionServiceManager.aidl index 8e5292d1ddf1..ad402262878d 100644 --- a/core/java/android/speech/IRecognitionServiceManager.aidl +++ b/core/java/android/speech/IRecognitionServiceManager.aidl @@ -31,4 +31,6 @@ oneway interface IRecognitionServiceManager { in IBinder clientToken, boolean onDevice, in IRecognitionServiceManagerCallback callback); + + void setTemporaryComponent(in ComponentName componentName); } diff --git a/core/java/android/speech/SpeechRecognizer.java b/core/java/android/speech/SpeechRecognizer.java index 850f997a2d2f..9b93a64e48a3 100644 --- a/core/java/android/speech/SpeechRecognizer.java +++ b/core/java/android/speech/SpeechRecognizer.java @@ -17,6 +17,8 @@ package android.speech; import android.annotation.NonNull; +import android.annotation.Nullable; +import android.annotation.TestApi; import android.content.ComponentName; import android.content.Context; import android.content.Intent; @@ -35,6 +37,8 @@ import android.util.Log; import android.util.Slog; import java.util.List; +import java.util.Queue; +import java.util.concurrent.LinkedBlockingQueue; /** * This class provides access to the speech recognition service. This service allows access to the @@ -52,7 +56,7 @@ import java.util.List; */ public class SpeechRecognizer { /** DEBUG value to enable verbose debug prints */ - private final static boolean DBG = false; + private static final boolean DBG = false; /** Log messages identifier */ private static final String TAG = "SpeechRecognizer"; @@ -113,10 +117,11 @@ public class SpeechRecognizer { public static final int ERROR_SERVER_DISCONNECTED = 11; /** action codes */ - private final static int MSG_START = 1; - private final static int MSG_STOP = 2; - private final static int MSG_CANCEL = 3; - private final static int MSG_CHANGE_LISTENER = 4; + 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; /** The actual RecognitionService endpoint */ private IRecognitionService mService; @@ -134,6 +139,7 @@ public class SpeechRecognizer { /** Handler that will execute the main tasks */ private Handler mHandler = new Handler(Looper.getMainLooper()) { + @Override public void handleMessage(Message msg) { switch (msg.what) { @@ -149,10 +155,19 @@ public class SpeechRecognizer { case MSG_CHANGE_LISTENER: handleChangeListener((RecognitionListener) msg.obj); break; + case MSG_SET_TEMPORARY_ON_DEVICE_COMPONENT: + handleSetTemporaryComponent((ComponentName) msg.obj); + 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 InternalListener mListener = new InternalListener(); @@ -287,11 +302,9 @@ public class SpeechRecognizer { if (mService == null) { // First time connection: first establish a connection, then dispatch #startListening. - connectToSystemService( - () -> putMessage(Message.obtain(mHandler, MSG_START, recognizerIntent))); - } else { - putMessage(Message.obtain(mHandler, MSG_START, recognizerIntent)); + connectToSystemService(); } + putMessage(Message.obtain(mHandler, MSG_START, recognizerIntent)); } /** @@ -336,6 +349,22 @@ public class SpeechRecognizer { putMessage(Message.obtain(mHandler, MSG_CANCEL)); } + /** + * Sets a temporary component to power on-device speech recognizer. + * + * <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. + * + * @hide + */ + @TestApi + 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( @@ -344,7 +373,11 @@ public class SpeechRecognizer { } private void putMessage(Message msg) { - mHandler.sendMessage(msg); + if (mService == null) { + mPendingTasks.offer(msg); + } else { + mHandler.sendMessage(msg); + } } /** sends the actual message to the service */ @@ -395,6 +428,22 @@ public class SpeechRecognizer { } } + 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 boolean checkOpenConnection() { if (mService != null) { return true; @@ -422,16 +471,13 @@ public class SpeechRecognizer { } mService = null; + mPendingTasks.clear(); mListener.mInternalListener = null; } /** Establishes a connection to system server proxy and initializes the session. */ - private void connectToSystemService(Runnable onSuccess) { - mManagerService = IRecognitionServiceManager.Stub.asInterface( - ServiceManager.getService(Context.SPEECH_RECOGNITION_SERVICE)); - - if (mManagerService == null) { - mListener.onError(ERROR_CLIENT); + private void connectToSystemService() { + if (!maybeInitializeManagerService()) { return; } @@ -450,13 +496,19 @@ public class SpeechRecognizer { new IRecognitionServiceManagerCallback.Stub(){ @Override public void onSuccess(IRecognitionService service) throws RemoteException { + if (DBG) { + Log.i(TAG, "Connected to speech recognition service"); + } mService = service; - onSuccess.run(); + while (!mPendingTasks.isEmpty()) { + mHandler.sendMessage(mPendingTasks.poll()); + } } @Override public void onError(int errorCode) throws RemoteException { - Log.e(TAG, "Bind to system recognition service failed"); + Log.e(TAG, "Bind to system recognition service failed with error " + + errorCode); mListener.onError(errorCode); } }); @@ -465,6 +517,21 @@ public class SpeechRecognizer { } } + private boolean maybeInitializeManagerService() { + if (mManagerService != null) { + return true; + } + + mManagerService = IRecognitionServiceManager.Stub.asInterface( + ServiceManager.getService(Context.SPEECH_RECOGNITION_SERVICE)); + + if (mManagerService == null && 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. @@ -505,15 +572,15 @@ public class SpeechRecognizer { private static class InternalListener extends IRecognitionListener.Stub { private RecognitionListener mInternalListener; - private final static int MSG_BEGINNING_OF_SPEECH = 1; - private final static int MSG_BUFFER_RECEIVED = 2; - private final static int MSG_END_OF_SPEECH = 3; - private final static int MSG_ERROR = 4; - private final static int MSG_READY_FOR_SPEECH = 5; - private final static int MSG_RESULTS = 6; - private final static int MSG_PARTIAL_RESULTS = 7; - private final static int MSG_RMS_CHANGED = 8; - private final static int MSG_ON_EVENT = 9; + 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 final Handler mInternalHandler = new Handler(Looper.getMainLooper()) { @Override diff --git a/core/res/AndroidManifest.xml b/core/res/AndroidManifest.xml index f7eb3648dfc0..915142852f8f 100644 --- a/core/res/AndroidManifest.xml +++ b/core/res/AndroidManifest.xml @@ -5257,15 +5257,20 @@ <permission android:name="android.permission.MANAGE_MUSIC_RECOGNITION" android:protectionLevel="signature|privileged" /> + <!-- @SystemApi Allows an application to manage speech recognition service. + @hide <p>Not for use by third-party applications.</p> --> + <permission android:name="android.permission.MANAGE_SPEECH_RECOGNITION" + android:protectionLevel="signature" /> + <!-- @SystemApi Allows an application to manage the content suggestions service. @hide <p>Not for use by third-party applications.</p> --> <permission android:name="android.permission.MANAGE_CONTENT_SUGGESTIONS" - android:protectionLevel="signature" /> + android:protectionLevel="signature" /> <!-- @SystemApi Allows an application to manage the app predictions service. @hide <p>Not for use by third-party applications.</p> --> <permission android:name="android.permission.MANAGE_APP_PREDICTIONS" - android:protectionLevel="signature|appPredictor" /> + android:protectionLevel="signature|appPredictor" /> <!-- @SystemApi Allows an application to manage the search ui service. @hide <p>Not for use by third-party applications.</p> --> diff --git a/services/core/java/com/android/server/speech/SpeechRecognitionManagerService.java b/services/core/java/com/android/server/speech/SpeechRecognitionManagerService.java index dbe73546d748..52c1467bd5d0 100644 --- a/services/core/java/com/android/server/speech/SpeechRecognitionManagerService.java +++ b/services/core/java/com/android/server/speech/SpeechRecognitionManagerService.java @@ -16,6 +16,8 @@ package com.android.server.speech; +import static android.Manifest.permission.MANAGE_SPEECH_RECOGNITION; + import android.annotation.NonNull; import android.annotation.UserIdInt; import android.content.ComponentName; @@ -24,6 +26,7 @@ import android.os.IBinder; import android.os.UserHandle; import android.speech.IRecognitionServiceManager; import android.speech.IRecognitionServiceManagerCallback; +import android.util.Slog; import com.android.internal.R; import com.android.server.infra.AbstractMasterSystemService; @@ -42,6 +45,8 @@ public final class SpeechRecognitionManagerService extends SpeechRecognitionManagerServiceImpl> { private static final String TAG = SpeechRecognitionManagerService.class.getSimpleName(); + private static final int MAX_TEMP_SERVICE_SUBSTITUTION_DURATION_MS = 60_000; + public SpeechRecognitionManagerService(@NonNull Context context) { super(context, // TODO(b/176578753): think if we want to favor the particular service here. @@ -58,6 +63,16 @@ public final class SpeechRecognitionManagerService extends } @Override + protected void enforceCallingPermissionForManagement() { + getContext().enforceCallingPermission(MANAGE_SPEECH_RECOGNITION, TAG); + } + + @Override + protected int getMaximumTemporaryServiceDurationMs() { + return MAX_TEMP_SERVICE_SUBSTITUTION_DURATION_MS; + } + + @Override protected SpeechRecognitionManagerServiceImpl newServiceLocked( @UserIdInt int resolvedUserId, boolean disabled) { return new SpeechRecognitionManagerServiceImpl(this, mLock, resolvedUserId, disabled); @@ -77,5 +92,21 @@ public final class SpeechRecognitionManagerService extends service.createSessionLocked(componentName, clientToken, onDevice, callback); } } + + @Override + public void setTemporaryComponent(ComponentName componentName) { + int userId = UserHandle.getCallingUserId(); + if (componentName == null) { + resetTemporaryService(userId); + Slog.i(TAG, "Reset temporary service for user " + userId); + return; + } + setTemporaryService( + userId, + componentName.flattenToString(), + MAX_TEMP_SERVICE_SUBSTITUTION_DURATION_MS); + Slog.i(TAG, "SpeechRecognition temporarily set to " + componentName + " for " + + MAX_TEMP_SERVICE_SUBSTITUTION_DURATION_MS + "ms"); + } } } diff --git a/services/core/java/com/android/server/speech/SpeechRecognitionManagerServiceImpl.java b/services/core/java/com/android/server/speech/SpeechRecognitionManagerServiceImpl.java index 2656a3d32555..769e049c8d0e 100644 --- a/services/core/java/com/android/server/speech/SpeechRecognitionManagerServiceImpl.java +++ b/services/core/java/com/android/server/speech/SpeechRecognitionManagerServiceImpl.java @@ -100,6 +100,9 @@ final class SpeechRecognitionManagerServiceImpl extends } if (serviceComponent == null) { + if (mMaster.debug) { + Slog.i(TAG, "Service component is undefined, responding with error."); + } tryRespondWithError(callback, SpeechRecognizer.ERROR_CLIENT); return; } @@ -213,6 +216,10 @@ final class SpeechRecognitionManagerServiceImpl extends @Nullable private ComponentName getOnDeviceComponentNameLocked() { final String serviceName = getComponentNameLocked(); + if (mMaster.debug) { + Slog.i(TAG, "Resolved component name: " + serviceName); + } + if (serviceName == null) { if (mMaster.verbose) { Slog.v(TAG, "ensureRemoteServiceLocked(): no service component name."); @@ -241,6 +248,11 @@ final class SpeechRecognitionManagerServiceImpl extends service.getServiceComponentName().equals(serviceComponent)) .findFirst(); if (existingService.isPresent()) { + + if (mMaster.debug) { + Slog.i(TAG, "Reused existing connection to " + serviceComponent); + } + return existingService.get(); } } @@ -253,6 +265,10 @@ final class SpeechRecognitionManagerServiceImpl extends mRemoteServicesByUid.computeIfAbsent(callingUid, key -> new HashSet<>()); valuesByCaller.add(service); + if (mMaster.debug) { + Slog.i(TAG, "Creating a new connection to " + serviceComponent); + } + return service; } } |