Merge "Add utility for to allow to overwrite on-device speech recognition temporarily." into sc-dev
This commit is contained in:
committed by
Android (Google) Code Review
commit
36a45cf022
@@ -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";
|
||||
|
||||
@@ -2125,6 +2125,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 {
|
||||
|
||||
@@ -31,4 +31,6 @@ oneway interface IRecognitionServiceManager {
|
||||
in IBinder clientToken,
|
||||
boolean onDevice,
|
||||
in IRecognitionServiceManagerCallback callback);
|
||||
|
||||
void setTemporaryComponent(in ComponentName componentName);
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -5259,15 +5259,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> -->
|
||||
|
||||
@@ -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.
|
||||
@@ -57,6 +62,16 @@ public final class SpeechRecognitionManagerService extends
|
||||
publishBinderService(Context.SPEECH_RECOGNITION_SERVICE, serviceStub);
|
||||
}
|
||||
|
||||
@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) {
|
||||
@@ -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");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user