Merge "Add utility for to allow to overwrite on-device speech recognition temporarily." into sc-dev

This commit is contained in:
Sergey Volnov
2021-03-11 12:13:58 +00:00
committed by Android (Google) Code Review
7 changed files with 159 additions and 29 deletions

View File

@@ -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";

View File

@@ -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 {

View File

@@ -31,4 +31,6 @@ oneway interface IRecognitionServiceManager {
in IBinder clientToken,
boolean onDevice,
in IRecognitionServiceManagerCallback callback);
void setTemporaryComponent(in ComponentName componentName);
}

View File

@@ -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

View File

@@ -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> -->

View File

@@ -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");
}
}
}

View File

@@ -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;
}
}