Merge "Add System speech recognition service." into sc-dev
This commit is contained in:
committed by
Android (Google) Code Review
commit
bf51d7482c
@@ -38576,6 +38576,7 @@ package android.speech {
|
||||
|
||||
public class SpeechRecognizer {
|
||||
method public void cancel();
|
||||
method @NonNull public static android.speech.SpeechRecognizer createOnDeviceSpeechRecognizer(@NonNull android.content.Context);
|
||||
method public static android.speech.SpeechRecognizer createSpeechRecognizer(android.content.Context);
|
||||
method public static android.speech.SpeechRecognizer createSpeechRecognizer(android.content.Context, android.content.ComponentName);
|
||||
method public void destroy();
|
||||
|
||||
@@ -64,7 +64,6 @@ import android.os.HandlerExecutor;
|
||||
import android.os.IBinder;
|
||||
import android.os.Looper;
|
||||
import android.os.StatFs;
|
||||
import android.os.StrictMode;
|
||||
import android.os.UserHandle;
|
||||
import android.os.UserManager;
|
||||
import android.os.storage.StorageManager;
|
||||
@@ -3575,6 +3574,7 @@ public abstract class Context {
|
||||
LIGHTS_SERVICE,
|
||||
//@hide: PEOPLE_SERVICE,
|
||||
//@hide: DEVICE_STATE_SERVICE,
|
||||
//@hide: SPEECH_RECOGNITION_SERVICE,
|
||||
UWB_SERVICE,
|
||||
MEDIA_METRICS_SERVICE,
|
||||
})
|
||||
@@ -5409,6 +5409,14 @@ public abstract class Context {
|
||||
*/
|
||||
public static final String MEDIA_METRICS_SERVICE = "media_metrics";
|
||||
|
||||
/**
|
||||
* Use with {@link #getSystemService(String)} to access system speech recognition service.
|
||||
*
|
||||
* @see #getSystemService(String)
|
||||
* @hide
|
||||
*/
|
||||
public static final String SPEECH_RECOGNITION_SERVICE = "speech_recognition";
|
||||
|
||||
/**
|
||||
* Determine whether the given permission is allowed for a particular
|
||||
* process and user ID running in the system.
|
||||
@@ -6469,7 +6477,7 @@ public abstract class Context {
|
||||
* {@link WindowManager}, {@link android.view.LayoutInflater LayoutInflater} or
|
||||
* {@link android.app.WallpaperManager WallpaperManager}. Accessing UI components from non-UI
|
||||
* contexts throws {@link android.os.strictmode.Violation} if
|
||||
* {@link StrictMode.VmPolicy.Builder#detectIncorrectContextUse()} is enabled.
|
||||
* {@link android.os.StrictMode.VmPolicy.Builder#detectIncorrectContextUse()} is enabled.
|
||||
* <p>
|
||||
* Examples of UI contexts are
|
||||
* an {@link android.app.Activity Activity}, a context created from
|
||||
@@ -6479,7 +6487,7 @@ public abstract class Context {
|
||||
*
|
||||
* @see #getDisplay()
|
||||
* @see #getSystemService(String)
|
||||
* @see StrictMode.VmPolicy.Builder#detectIncorrectContextUse()
|
||||
* @see android.os.StrictMode.VmPolicy.Builder#detectIncorrectContextUse()
|
||||
*/
|
||||
public static boolean isUiContext(@NonNull Context context) {
|
||||
return context.isUiContext();
|
||||
|
||||
28
core/java/android/speech/IRecognitionServiceManager.aidl
Normal file
28
core/java/android/speech/IRecognitionServiceManager.aidl
Normal file
@@ -0,0 +1,28 @@
|
||||
/*
|
||||
* Copyright (C) 2021 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.speech.IRecognitionServiceManagerCallback;
|
||||
|
||||
/**
|
||||
* Binder service allowing speech recognition proxied by the system.
|
||||
*
|
||||
* {@hide}
|
||||
*/
|
||||
interface IRecognitionServiceManager {
|
||||
void createSession(in IRecognitionServiceManagerCallback callback);
|
||||
}
|
||||
@@ -0,0 +1,29 @@
|
||||
/*
|
||||
* Copyright (C) 2021 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.speech.IRecognitionService;
|
||||
|
||||
/**
|
||||
* Callback for the service allowing speech recognition proxied by the system.
|
||||
*
|
||||
* {@hide}
|
||||
*/
|
||||
oneway interface IRecognitionServiceManagerCallback {
|
||||
void onSuccess(in IRecognitionService service);
|
||||
void onError();
|
||||
}
|
||||
@@ -28,6 +28,7 @@ import android.os.Bundle;
|
||||
import android.os.Handler;
|
||||
import android.os.IBinder;
|
||||
import android.os.Message;
|
||||
import android.os.Process;
|
||||
import android.os.RemoteException;
|
||||
import android.util.Log;
|
||||
|
||||
@@ -50,7 +51,9 @@ public abstract class RecognitionService extends Service {
|
||||
/**
|
||||
* Name under which a RecognitionService component publishes information about itself.
|
||||
* This meta-data should reference an XML resource containing a
|
||||
* <code><{@link android.R.styleable#RecognitionService recognition-service}></code> tag.
|
||||
* <code><{@link android.R.styleable#RecognitionService recognition-service}></code> or
|
||||
* <code><{@link android.R.styleable#RecognitionService on-device-recognition-service}
|
||||
* ></code> tag.
|
||||
*/
|
||||
public static final String SERVICE_META_DATA = "android.speech";
|
||||
|
||||
@@ -182,6 +185,13 @@ public abstract class RecognitionService extends Service {
|
||||
private boolean checkPermissions(IRecognitionListener listener, boolean forDataDelivery,
|
||||
@NonNull String packageName, @Nullable String featureId) {
|
||||
if (DBG) Log.d(TAG, "checkPermissions");
|
||||
|
||||
final int callingUid = Binder.getCallingUid();
|
||||
if (callingUid == Process.SYSTEM_UID) {
|
||||
// Assuming system has verified permissions of the caller.
|
||||
return true;
|
||||
}
|
||||
|
||||
if (forDataDelivery) {
|
||||
if (PermissionChecker.checkCallingOrSelfPermissionForDataDelivery(this,
|
||||
android.Manifest.permission.RECORD_AUDIO, packageName, featureId,
|
||||
@@ -342,6 +352,7 @@ public abstract class RecognitionService extends Service {
|
||||
* Return the Linux uid assigned to the process that sent you the current transaction that
|
||||
* is being processed. This is obtained from {@link Binder#getCallingUid()}.
|
||||
*/
|
||||
// TODO(b/176578753): need to make sure this is fixed when proxied through system.
|
||||
public int getCallingUid() {
|
||||
return mCallingUid;
|
||||
}
|
||||
|
||||
@@ -16,6 +16,7 @@
|
||||
|
||||
package android.speech;
|
||||
|
||||
import android.annotation.NonNull;
|
||||
import android.content.ComponentName;
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
@@ -27,6 +28,7 @@ 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;
|
||||
@@ -38,8 +40,9 @@ import java.util.Queue;
|
||||
/**
|
||||
* This class provides access to the speech recognition service. This service allows access to the
|
||||
* speech recognizer. Do not instantiate this class directly, instead, call
|
||||
* {@link SpeechRecognizer#createSpeechRecognizer(Context)}. This class's methods must be
|
||||
* invoked only from the main application thread.
|
||||
* {@link SpeechRecognizer#createSpeechRecognizer(Context)}, or
|
||||
* {@link SpeechRecognizer#createOnDeviceSpeechRecognizer(Context)}. This class's methods must be
|
||||
* invoked only from the main application thread.
|
||||
*
|
||||
* <p>The implementation of this API is likely to stream audio to remote servers to perform speech
|
||||
* recognition. As such this API is not intended to be used for continuous recognition, which would
|
||||
@@ -122,8 +125,13 @@ public class SpeechRecognizer {
|
||||
/** 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() {
|
||||
private Handler mHandler = new Handler(Looper.getMainLooper()) {
|
||||
@Override
|
||||
public void handleMessage(Message msg) {
|
||||
switch (msg.what) {
|
||||
@@ -159,6 +167,17 @@ public class SpeechRecognizer {
|
||||
private SpeechRecognizer(final Context context, final ComponentName serviceComponent) {
|
||||
mContext = context;
|
||||
mServiceComponent = serviceComponent;
|
||||
mOnDevice = false;
|
||||
}
|
||||
|
||||
/**
|
||||
* The right way to create a {@code SpeechRecognizer} is by using
|
||||
* {@link #createOnDeviceSpeechRecognizer} static factory method
|
||||
*/
|
||||
private SpeechRecognizer(final Context context, boolean onDevice) {
|
||||
mContext = context;
|
||||
mServiceComponent = null;
|
||||
mOnDevice = onDevice;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -194,6 +213,7 @@ public class SpeechRecognizer {
|
||||
* @return {@code true} if recognition is available, {@code false} otherwise
|
||||
*/
|
||||
public static boolean isRecognitionAvailable(final 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);
|
||||
return list != null && list.size() != 0;
|
||||
@@ -231,12 +251,31 @@ public class SpeechRecognizer {
|
||||
public static SpeechRecognizer createSpeechRecognizer(final Context context,
|
||||
final ComponentName serviceComponent) {
|
||||
if (context == null) {
|
||||
throw new IllegalArgumentException("Context cannot be null)");
|
||||
throw new IllegalArgumentException("Context cannot be null");
|
||||
}
|
||||
checkIsCalledFromMainThread();
|
||||
return new SpeechRecognizer(context, serviceComponent);
|
||||
}
|
||||
|
||||
/**
|
||||
* Factory method to create a new {@code SpeechRecognizer}.
|
||||
*
|
||||
* <p>Please note that {@link #setRecognitionListener(RecognitionListener)} should be called
|
||||
* before dispatching any command to the created {@code SpeechRecognizer}, otherwise no
|
||||
* notifications will be received.
|
||||
*
|
||||
* @param context in which to create {@code SpeechRecognizer}
|
||||
* @return a new on-device {@code SpeechRecognizer}.
|
||||
*/
|
||||
@NonNull
|
||||
public static SpeechRecognizer createOnDeviceSpeechRecognizer(@NonNull final Context context) {
|
||||
if (context == null) {
|
||||
throw new IllegalArgumentException("Context cannot be null");
|
||||
}
|
||||
checkIsCalledFromMainThread();
|
||||
return new SpeechRecognizer(context, /* onDevice */ true);
|
||||
}
|
||||
|
||||
/**
|
||||
* 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
|
||||
@@ -265,36 +304,74 @@ public class SpeechRecognizer {
|
||||
}
|
||||
checkIsCalledFromMainThread();
|
||||
if (mConnection == null) { // first time connection
|
||||
mConnection = new Connection();
|
||||
|
||||
Intent serviceIntent = new Intent(RecognitionService.SERVICE_INTERFACE);
|
||||
|
||||
if (mServiceComponent == null) {
|
||||
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;
|
||||
}
|
||||
|
||||
serviceIntent.setComponent(ComponentName.unflattenFromString(serviceComponent));
|
||||
// TODO(b/176578753): both flows should go through system service.
|
||||
if (mOnDevice) {
|
||||
connectToSystemService();
|
||||
} else {
|
||||
serviceIntent.setComponent(mServiceComponent);
|
||||
}
|
||||
if (!mContext.bindService(serviceIntent, mConnection,
|
||||
Context.BIND_AUTO_CREATE | Context.BIND_INCLUDE_CAPABILITIES)) {
|
||||
Log.e(TAG, "bind to recognition service failed");
|
||||
mConnection = null;
|
||||
mService = null;
|
||||
mListener.onError(ERROR_CLIENT);
|
||||
return;
|
||||
connectToService();
|
||||
}
|
||||
}
|
||||
putMessage(Message.obtain(mHandler, MSG_START, recognizerIntent));
|
||||
}
|
||||
|
||||
private void connectToSystemService() {
|
||||
mManagerService = IRecognitionServiceManager.Stub.asInterface(
|
||||
ServiceManager.getService(Context.SPEECH_RECOGNITION_SERVICE));
|
||||
|
||||
if (mManagerService == null) {
|
||||
mListener.onError(ERROR_CLIENT);
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
// TODO(b/176578753): this has to supply information on whether to use on-device impl.
|
||||
mManagerService.createSession(new IRecognitionServiceManagerCallback.Stub(){
|
||||
@Override
|
||||
public void onSuccess(IRecognitionService service) throws RemoteException {
|
||||
mService = service;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onError() throws RemoteException {
|
||||
Log.e(TAG, "Bind to system recognition service failed");
|
||||
mListener.onError(ERROR_CLIENT);
|
||||
}
|
||||
});
|
||||
} catch (RemoteException e) {
|
||||
e.rethrowFromSystemServer();
|
||||
}
|
||||
}
|
||||
|
||||
private void connectToService() {
|
||||
mConnection = new Connection();
|
||||
|
||||
Intent serviceIntent = new Intent(RecognitionService.SERVICE_INTERFACE);
|
||||
|
||||
if (mServiceComponent == null) {
|
||||
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;
|
||||
}
|
||||
|
||||
serviceIntent.setComponent(
|
||||
ComponentName.unflattenFromString(serviceComponent));
|
||||
} else {
|
||||
serviceIntent.setComponent(mServiceComponent);
|
||||
}
|
||||
if (!mContext.bindService(serviceIntent, mConnection,
|
||||
Context.BIND_AUTO_CREATE | Context.BIND_INCLUDE_CAPABILITIES)) {
|
||||
Log.e(TAG, "bind to recognition service failed");
|
||||
mConnection = null;
|
||||
mService = null;
|
||||
mListener.onError(ERROR_CLIENT);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Stops listening for speech. Speech captured so far will be recognized as if the user had
|
||||
* stopped speaking at this point. Note that in the default case, this does not need to be
|
||||
@@ -378,7 +455,7 @@ public class SpeechRecognizer {
|
||||
mListener.onError(ERROR_CLIENT);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private boolean checkOpenConnection() {
|
||||
if (mService != null) {
|
||||
return true;
|
||||
@@ -433,7 +510,7 @@ public class SpeechRecognizer {
|
||||
private final static int MSG_RMS_CHANGED = 8;
|
||||
private final static int MSG_ON_EVENT = 9;
|
||||
|
||||
private final Handler mInternalHandler = new Handler() {
|
||||
private final Handler mInternalHandler = new Handler(Looper.getMainLooper()) {
|
||||
@Override
|
||||
public void handleMessage(Message msg) {
|
||||
if (mInternalListener == null) {
|
||||
|
||||
@@ -8287,6 +8287,23 @@
|
||||
<attr name="maxLongVersionCode" format="string" />
|
||||
</declare-styleable>
|
||||
|
||||
<!-- =============================== -->
|
||||
<!-- System Speech Recognition attributes -->
|
||||
<!-- =============================== -->
|
||||
<eat-comment />
|
||||
|
||||
<!-- Use <code>on-device-recognition-service</code> as the root tag of the XML resource that
|
||||
describes a {@link android.service.speech.RecognitionService}, which is referenced
|
||||
from its {@link android.service.speech.RecognitionService#SERVICE_META_DATA} meta-data
|
||||
entry.
|
||||
@hide @SystemApi
|
||||
-->
|
||||
<declare-styleable name="OnDeviceRecognitionService">
|
||||
<!-- Fully qualified class name of an activity that allows the user to modify
|
||||
the settings for this service. -->
|
||||
<attr name="settingsActivity" />
|
||||
</declare-styleable>
|
||||
|
||||
<!-- =============================== -->
|
||||
<!-- Content Capture attributes -->
|
||||
<!-- =============================== -->
|
||||
|
||||
@@ -3800,6 +3800,12 @@
|
||||
-->
|
||||
<string name="config_defaultSearchUiService" translatable="false"></string>
|
||||
|
||||
<!-- The package name for the system's speech recognition service.
|
||||
This service must be trusted, as it can be activated without explicit consent of the user.
|
||||
Example: "com.android.speech/.RecognitionService"
|
||||
-->
|
||||
<string name="config_defaultOnDeviceSpeechRecognitionService" translatable="false"></string>
|
||||
|
||||
<string name="config_defaultMusicRecognitionService" translatable="false"></string>
|
||||
|
||||
<!-- The package name for the default retail demo app.
|
||||
|
||||
@@ -3489,6 +3489,7 @@
|
||||
<java-symbol type="string" name="notification_channel_do_not_disturb" />
|
||||
<java-symbol type="string" name="notification_channel_accessibility_magnification" />
|
||||
<java-symbol type="string" name="config_defaultAutofillService" />
|
||||
<java-symbol type="string" name="config_defaultOnDeviceSpeechRecognitionService" />
|
||||
<java-symbol type="string" name="config_defaultTextClassifierPackage" />
|
||||
<java-symbol type="string" name="config_defaultWellbeingPackage" />
|
||||
<java-symbol type="string" name="config_defaultContentCaptureService" />
|
||||
|
||||
@@ -28,6 +28,7 @@ filegroup {
|
||||
":services.profcollect-sources",
|
||||
":services.restrictions-sources",
|
||||
":services.searchui-sources",
|
||||
":services.speech-sources",
|
||||
":services.startop.iorap-sources",
|
||||
":services.systemcaptions-sources",
|
||||
":services.translation-sources",
|
||||
@@ -75,6 +76,7 @@ java_library {
|
||||
"services.profcollect",
|
||||
"services.restrictions",
|
||||
"services.searchui",
|
||||
"services.speech",
|
||||
"services.startop",
|
||||
"services.systemcaptions",
|
||||
"services.translation",
|
||||
|
||||
13
services/core/java/com/android/server/speech/Android.bp
Normal file
13
services/core/java/com/android/server/speech/Android.bp
Normal file
@@ -0,0 +1,13 @@
|
||||
filegroup {
|
||||
name: "services.speech-sources",
|
||||
srcs: ["java/**/*.java"],
|
||||
path: "java",
|
||||
visibility: ["//frameworks/base/services"],
|
||||
}
|
||||
|
||||
java_library_static {
|
||||
name: "services.speech",
|
||||
defaults: ["platform_service_defaults"],
|
||||
srcs: [":services.speech-sources"],
|
||||
libs: ["services.core"],
|
||||
}
|
||||
@@ -0,0 +1,93 @@
|
||||
/*
|
||||
* Copyright (C) 2021 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 com.android.server.speech;
|
||||
|
||||
import static com.android.internal.infra.AbstractRemoteService.PERMANENT_BOUND_TIMEOUT_MS;
|
||||
|
||||
import android.content.ComponentName;
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.os.RemoteException;
|
||||
import android.speech.IRecognitionListener;
|
||||
import android.speech.IRecognitionService;
|
||||
import android.speech.RecognitionService;
|
||||
import android.util.Slog;
|
||||
|
||||
import com.android.internal.infra.ServiceConnector;
|
||||
|
||||
final class RemoteSpeechRecognitionService extends ServiceConnector.Impl<IRecognitionService> {
|
||||
private static final String TAG = RemoteSpeechRecognitionService.class.getSimpleName();
|
||||
private static final boolean DEBUG = true;
|
||||
|
||||
RemoteSpeechRecognitionService(Context context, ComponentName serviceName, int userId) {
|
||||
super(context,
|
||||
new Intent(RecognitionService.SERVICE_INTERFACE).setComponent(serviceName),
|
||||
Context.BIND_AUTO_CREATE
|
||||
| Context.BIND_FOREGROUND_SERVICE
|
||||
| Context.BIND_INCLUDE_CAPABILITIES
|
||||
| Context.BIND_ALLOW_BACKGROUND_ACTIVITY_STARTS,
|
||||
userId,
|
||||
IRecognitionService.Stub::asInterface);
|
||||
|
||||
if (DEBUG) {
|
||||
Slog.i(TAG, "Bound to recognition service at: " + serviceName.flattenToString());
|
||||
}
|
||||
}
|
||||
|
||||
void startListening(Intent recognizerIntent, IRecognitionListener listener, String packageName,
|
||||
String featureId) throws RemoteException {
|
||||
if (DEBUG) {
|
||||
Slog.i(TAG, "#startListening for package: " + packageName + ", feature=" + featureId);
|
||||
}
|
||||
run(service -> service.startListening(recognizerIntent, listener, packageName, featureId));
|
||||
}
|
||||
|
||||
void stopListening(IRecognitionListener listener, String packageName, String featureId)
|
||||
throws RemoteException {
|
||||
if (DEBUG) {
|
||||
Slog.i(TAG, "#stopListening for package: " + packageName + ", feature=" + featureId);
|
||||
}
|
||||
run(service -> service.stopListening(listener, packageName, featureId));
|
||||
}
|
||||
|
||||
void cancel(IRecognitionListener listener, String packageName, String featureId)
|
||||
throws RemoteException {
|
||||
if (DEBUG) {
|
||||
Slog.i(TAG, "#cancel for package: " + packageName + ", feature=" + featureId);
|
||||
}
|
||||
run(service -> service.cancel(listener, packageName, featureId));
|
||||
}
|
||||
|
||||
@Override // from ServiceConnector.Impl
|
||||
protected void onServiceConnectionStatusChanged(
|
||||
IRecognitionService service, boolean connected) {
|
||||
if (!DEBUG) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (connected) {
|
||||
Slog.i(TAG, "Connected to ASR service");
|
||||
} else {
|
||||
Slog.w(TAG, "Disconnected from ASR service");
|
||||
}
|
||||
}
|
||||
|
||||
@Override // from AbstractRemoteService
|
||||
protected long getAutoDisconnectTimeoutMs() {
|
||||
return PERMANENT_BOUND_TIMEOUT_MS;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,74 @@
|
||||
/*
|
||||
* Copyright (C) 2021 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 com.android.server.speech;
|
||||
|
||||
import android.annotation.NonNull;
|
||||
import android.annotation.UserIdInt;
|
||||
import android.content.Context;
|
||||
import android.os.UserHandle;
|
||||
import android.speech.IRecognitionServiceManager;
|
||||
import android.speech.IRecognitionServiceManagerCallback;
|
||||
|
||||
import com.android.internal.R;
|
||||
import com.android.server.infra.AbstractMasterSystemService;
|
||||
import com.android.server.infra.FrameworkResourcesServiceNameResolver;
|
||||
|
||||
/**
|
||||
* System service implementation for Speech Recognizer.
|
||||
*
|
||||
* <p>This service uses RemoteSpeechRecognitionService to bind to a default implementation of
|
||||
* ISpeechRecognition. It relays all the requests from the client to the default system impl of
|
||||
* ISpeechRecognition service (denoted by {@code
|
||||
* R.string.config_defaultOnDeviceSpeechRecognitionService}).
|
||||
*/
|
||||
public final class SpeechRecognitionManagerService extends
|
||||
AbstractMasterSystemService<SpeechRecognitionManagerService,
|
||||
SpeechRecognitionManagerServiceImpl> {
|
||||
private static final String TAG = SpeechRecognitionManagerService.class.getSimpleName();
|
||||
|
||||
public SpeechRecognitionManagerService(@NonNull Context context) {
|
||||
super(context,
|
||||
new FrameworkResourcesServiceNameResolver(
|
||||
context,
|
||||
R.string.config_defaultOnDeviceSpeechRecognitionService),
|
||||
/*disallowProperty=*/ null);
|
||||
}
|
||||
|
||||
@Override // from SystemService
|
||||
public void onStart() {
|
||||
SpeechRecognitionManagerServiceStub serviceStub = new SpeechRecognitionManagerServiceStub();
|
||||
publishBinderService(Context.SPEECH_RECOGNITION_SERVICE, serviceStub);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected SpeechRecognitionManagerServiceImpl newServiceLocked(
|
||||
@UserIdInt int resolvedUserId, boolean disabled) {
|
||||
return new SpeechRecognitionManagerServiceImpl(this, mLock, resolvedUserId, disabled);
|
||||
}
|
||||
|
||||
final class SpeechRecognitionManagerServiceStub extends IRecognitionServiceManager.Stub {
|
||||
|
||||
@Override
|
||||
public void createSession(IRecognitionServiceManagerCallback callback) {
|
||||
int userId = UserHandle.getCallingUserId();
|
||||
synchronized (mLock) {
|
||||
SpeechRecognitionManagerServiceImpl service = getServiceForUserLocked(userId);
|
||||
service.createSessionLocked(callback);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,160 @@
|
||||
/*
|
||||
* Copyright (C) 2021 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 com.android.server.speech;
|
||||
|
||||
import android.annotation.NonNull;
|
||||
import android.annotation.Nullable;
|
||||
import android.annotation.UserIdInt;
|
||||
import android.app.AppGlobals;
|
||||
import android.content.ComponentName;
|
||||
import android.content.Intent;
|
||||
import android.content.pm.PackageManager;
|
||||
import android.content.pm.ServiceInfo;
|
||||
import android.os.RemoteException;
|
||||
import android.speech.IRecognitionListener;
|
||||
import android.speech.IRecognitionService;
|
||||
import android.speech.IRecognitionServiceManagerCallback;
|
||||
import android.util.Slog;
|
||||
|
||||
import com.android.internal.annotations.GuardedBy;
|
||||
import com.android.server.infra.AbstractPerUserSystemService;
|
||||
|
||||
final class SpeechRecognitionManagerServiceImpl extends
|
||||
AbstractPerUserSystemService<SpeechRecognitionManagerServiceImpl,
|
||||
SpeechRecognitionManagerService> {
|
||||
|
||||
private static final String TAG = SpeechRecognitionManagerServiceImpl.class.getSimpleName();
|
||||
|
||||
@GuardedBy("mLock")
|
||||
@Nullable
|
||||
private RemoteSpeechRecognitionService mRemoteService;
|
||||
|
||||
SpeechRecognitionManagerServiceImpl(
|
||||
@NonNull SpeechRecognitionManagerService master,
|
||||
@NonNull Object lock, @UserIdInt int userId, boolean disabled) {
|
||||
super(master, lock, userId);
|
||||
updateRemoteServiceLocked();
|
||||
}
|
||||
|
||||
@GuardedBy("mLock")
|
||||
@Override // from PerUserSystemService
|
||||
protected ServiceInfo newServiceInfoLocked(@NonNull ComponentName serviceComponent)
|
||||
throws PackageManager.NameNotFoundException {
|
||||
try {
|
||||
return AppGlobals.getPackageManager().getServiceInfo(serviceComponent,
|
||||
PackageManager.GET_META_DATA, mUserId);
|
||||
} catch (RemoteException e) {
|
||||
throw new PackageManager.NameNotFoundException(
|
||||
"Could not get service for " + serviceComponent);
|
||||
}
|
||||
}
|
||||
|
||||
@GuardedBy("mLock")
|
||||
@Override // from PerUserSystemService
|
||||
protected boolean updateLocked(boolean disabled) {
|
||||
final boolean enabledChanged = super.updateLocked(disabled);
|
||||
updateRemoteServiceLocked();
|
||||
return enabledChanged;
|
||||
}
|
||||
|
||||
/**
|
||||
* Updates the reference to the remote service.
|
||||
*/
|
||||
@GuardedBy("mLock")
|
||||
private void updateRemoteServiceLocked() {
|
||||
if (mRemoteService != null) {
|
||||
if (mMaster.debug) {
|
||||
Slog.d(TAG, "updateRemoteService(): destroying old remote service");
|
||||
}
|
||||
mRemoteService.unbind();
|
||||
mRemoteService = null;
|
||||
}
|
||||
}
|
||||
|
||||
void createSessionLocked(IRecognitionServiceManagerCallback callback) {
|
||||
// TODO(b/176578753): check clients have record audio permission.
|
||||
// TODO(b/176578753): verify caller package is the one supplied
|
||||
|
||||
RemoteSpeechRecognitionService service = ensureRemoteServiceLocked();
|
||||
|
||||
if (service == null) {
|
||||
tryRespondWithError(callback);
|
||||
return;
|
||||
}
|
||||
|
||||
service.connect().thenAccept(binderService -> {
|
||||
if (binderService != null) {
|
||||
try {
|
||||
callback.onSuccess(new IRecognitionService.Stub() {
|
||||
@Override
|
||||
public void startListening(Intent recognizerIntent,
|
||||
IRecognitionListener listener,
|
||||
String packageName, String featureId) throws RemoteException {
|
||||
service.startListening(
|
||||
recognizerIntent, listener, packageName, featureId);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void stopListening(IRecognitionListener listener,
|
||||
String packageName,
|
||||
String featureId) throws RemoteException {
|
||||
service.stopListening(listener, packageName, featureId);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void cancel(IRecognitionListener listener,
|
||||
String packageName,
|
||||
String featureId) throws RemoteException {
|
||||
service.cancel(listener, packageName, featureId);
|
||||
}
|
||||
});
|
||||
} catch (RemoteException e) {
|
||||
Slog.e(TAG, "Error creating a speech recognition session", e);
|
||||
tryRespondWithError(callback);
|
||||
}
|
||||
} else {
|
||||
tryRespondWithError(callback);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@GuardedBy("mLock")
|
||||
@Nullable
|
||||
private RemoteSpeechRecognitionService ensureRemoteServiceLocked() {
|
||||
if (mRemoteService == null) {
|
||||
final String serviceName = getComponentNameLocked();
|
||||
if (serviceName == null) {
|
||||
if (mMaster.verbose) {
|
||||
Slog.v(TAG, "ensureRemoteServiceLocked(): no service component name.");
|
||||
}
|
||||
return null;
|
||||
}
|
||||
final ComponentName serviceComponent = ComponentName.unflattenFromString(serviceName);
|
||||
mRemoteService =
|
||||
new RemoteSpeechRecognitionService(getContext(), serviceComponent, mUserId);
|
||||
}
|
||||
return mRemoteService;
|
||||
}
|
||||
|
||||
private static void tryRespondWithError(IRecognitionServiceManagerCallback callback) {
|
||||
try {
|
||||
callback.onError();
|
||||
} catch (RemoteException e) {
|
||||
Slog.w(TAG, "Failed to respond with error");
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -333,6 +333,8 @@ public final class SystemServer implements Dumpable {
|
||||
"com.android.server.accessibility.AccessibilityManagerService$Lifecycle";
|
||||
private static final String ADB_SERVICE_CLASS =
|
||||
"com.android.server.adb.AdbService$Lifecycle";
|
||||
private static final String SPEECH_RECOGNITION_MANAGER_SERVICE_CLASS =
|
||||
"com.android.server.speech.SpeechRecognitionManagerService";
|
||||
private static final String APP_PREDICTION_MANAGER_SERVICE_CLASS =
|
||||
"com.android.server.appprediction.AppPredictionManagerService";
|
||||
private static final String CONTENT_SUGGESTIONS_SERVICE_CLASS =
|
||||
@@ -1629,12 +1631,21 @@ public final class SystemServer implements Dumpable {
|
||||
"MusicRecognitionManagerService not defined by OEM or disabled by flag");
|
||||
}
|
||||
|
||||
|
||||
startContentCaptureService(context, t);
|
||||
startAttentionService(context, t);
|
||||
startRotationResolverService(context, t);
|
||||
startSystemCaptionsManagerService(context, t);
|
||||
|
||||
// System Speech Recognition Service
|
||||
if (deviceHasConfigString(context,
|
||||
R.string.config_defaultOnDeviceSpeechRecognitionService)) {
|
||||
t.traceBegin("StartSpeechRecognitionManagerService");
|
||||
mSystemServiceManager.startService(SPEECH_RECOGNITION_MANAGER_SERVICE_CLASS);
|
||||
t.traceEnd();
|
||||
} else {
|
||||
Slog.d(TAG, "System speech recognition is not defined by OEM");
|
||||
}
|
||||
|
||||
// App prediction manager service
|
||||
if (deviceHasConfigString(context, R.string.config_defaultAppPredictionService)) {
|
||||
t.traceBegin("StartAppPredictionService");
|
||||
|
||||
Reference in New Issue
Block a user