Add callback to report HotwordDetectionService status

Bug: 182788844
Test: Test: atest CtsVoiceInteractionTestCases
Test: atest CtsVoiceInteractionTestCases --instant
Change-Id: I12f0a265fc4fce0bbfe0983f39b4dcd947e3cf64
This commit is contained in:
lpeter
2021-03-24 03:17:19 +08:00
parent 948bd86481
commit 6f2a263810
11 changed files with 156 additions and 23 deletions

View File

@@ -10372,6 +10372,7 @@ package android.service.voice {
public abstract static class AlwaysOnHotwordDetector.Callback implements android.service.voice.HotwordDetector.Callback {
ctor public AlwaysOnHotwordDetector.Callback();
method public abstract void onAvailabilityChanged(int);
method public void onHotwordDetectionServiceInitialized(int);
method public void onRejected(@Nullable android.service.voice.HotwordRejectedResult);
}
@@ -10419,7 +10420,11 @@ package android.service.voice {
method public void onDetect(@NonNull android.os.ParcelFileDescriptor, @NonNull android.media.AudioFormat, long, @NonNull android.service.voice.HotwordDetectionService.Callback);
method public void onDetect(@NonNull android.os.ParcelFileDescriptor, @NonNull android.media.AudioFormat, @NonNull android.service.voice.HotwordDetectionService.Callback);
method public void onDetect(@NonNull android.os.ParcelFileDescriptor, @NonNull android.media.AudioFormat, @Nullable android.os.PersistableBundle, @NonNull android.service.voice.HotwordDetectionService.Callback);
method public void onUpdateState(@Nullable android.os.PersistableBundle, @Nullable android.os.SharedMemory);
method public void onUpdateState(@Nullable android.os.PersistableBundle, @Nullable android.os.SharedMemory, long, @Nullable java.util.function.IntConsumer);
field public static final int INITIALIZATION_STATUS_CUSTOM_ERROR_1 = 1; // 0x1
field public static final int INITIALIZATION_STATUS_CUSTOM_ERROR_2 = 2; // 0x2
field public static final int INITIALIZATION_STATUS_SUCCESS = 0; // 0x0
field public static final int INITIALIZATION_STATUS_UNKNOWN = 100; // 0x64
field public static final String SERVICE_INTERFACE = "android.service.voice.HotwordDetectionService";
}
@@ -10441,6 +10446,7 @@ package android.service.voice {
public static interface HotwordDetector.Callback {
method public void onDetected(@NonNull android.service.voice.AlwaysOnHotwordDetector.EventPayload);
method public void onError();
method public void onHotwordDetectionServiceInitialized(int);
method public void onRecognitionPaused();
method public void onRecognitionResumed();
method public void onRejected(@Nullable android.service.voice.HotwordRejectedResult);

View File

@@ -48,6 +48,7 @@ import android.os.ParcelFileDescriptor;
import android.os.PersistableBundle;
import android.os.RemoteException;
import android.os.SharedMemory;
import android.service.voice.HotwordDetectionService.InitializationStatus;
import android.util.Slog;
import com.android.internal.app.IHotwordRecognitionStatusCallback;
@@ -260,6 +261,7 @@ public class AlwaysOnHotwordDetector extends AbstractHotwordDetector {
private static final int MSG_DETECTION_PAUSE = 4;
private static final int MSG_DETECTION_RESUME = 5;
private static final int MSG_HOTWORD_REJECTED = 6;
private static final int MSG_HOTWORD_STATUS_REPORTED = 7;
private final String mText;
private final Locale mLocale;
@@ -523,6 +525,15 @@ public class AlwaysOnHotwordDetector extends AbstractHotwordDetector {
*/
public void onRejected(@Nullable HotwordRejectedResult result) {
}
/**
* Called when the {@link HotwordDetectionService} is created by the system and given a
* short amount of time to report it's initialization state.
*
* @param status Info about initialization state of {@link HotwordDetectionService}.
*/
public void onHotwordDetectionServiceInitialized(@InitializationStatus int status) {
}
}
/**
@@ -559,7 +570,7 @@ public class AlwaysOnHotwordDetector extends AbstractHotwordDetector {
mTargetSdkVersion = targetSdkVersion;
mSupportHotwordDetectionService = supportHotwordDetectionService;
if (mSupportHotwordDetectionService) {
updateState(options, sharedMemory);
updateStateLocked(options, sharedMemory, mInternalCallback);
}
try {
Identity identity = new Identity();
@@ -583,20 +594,34 @@ public class AlwaysOnHotwordDetector extends AbstractHotwordDetector {
* such data to the trusted process.
*
* @throws IllegalStateException if this AlwaysOnHotwordDetector wasn't specified to use a
* {@link HotwordDetectionService} when it was created.
* {@link HotwordDetectionService} when it was created. In addition, if this
* AlwaysOnHotwordDetector is in an invalid or error state.
*/
public final void updateState(@Nullable PersistableBundle options,
@Nullable SharedMemory sharedMemory) {
if (DBG) {
Slog.d(TAG, "updateState()");
}
if (!mSupportHotwordDetectionService) {
throw new IllegalStateException(
"updateState called, but it doesn't support hotword detection service");
synchronized (mLock) {
if (!mSupportHotwordDetectionService) {
throw new IllegalStateException(
"updateState called, but it doesn't support hotword detection service");
}
if (mAvailability == STATE_INVALID || mAvailability == STATE_ERROR) {
throw new IllegalStateException(
"updateState called on an invalid detector or error state");
}
updateStateLocked(options, sharedMemory, null /* callback */);
}
}
private void updateStateLocked(@Nullable PersistableBundle options,
@Nullable SharedMemory sharedMemory, IHotwordRecognitionStatusCallback callback) {
if (DBG) {
Slog.d(TAG, "updateStateLocked()");
}
try {
mModelManagementService.updateState(options, sharedMemory);
mModelManagementService.updateState(options, sharedMemory, callback);
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
}
@@ -1147,6 +1172,18 @@ public class AlwaysOnHotwordDetector extends AbstractHotwordDetector {
Slog.i(TAG, "onRecognitionResumed");
mHandler.sendEmptyMessage(MSG_DETECTION_RESUME);
}
@Override
public void onStatusReported(int status) {
if (DBG) {
Slog.d(TAG, "onStatusReported(" + status + ")");
} else {
Slog.i(TAG, "onStatusReported");
}
Message message = Message.obtain(mHandler, MSG_HOTWORD_STATUS_REPORTED);
message.arg1 = status;
message.sendToTarget();
}
}
class MyHandler extends Handler {
@@ -1178,6 +1215,9 @@ public class AlwaysOnHotwordDetector extends AbstractHotwordDetector {
case MSG_HOTWORD_REJECTED:
mExternalCallback.onRejected((HotwordRejectedResult) msg.obj);
break;
case MSG_HOTWORD_STATUS_REPORTED:
mExternalCallback.onHotwordDetectionServiceInitialized(msg.arg1);
break;
default:
super.handleMessage(msg);
}

View File

@@ -37,10 +37,13 @@ import android.os.RemoteException;
import android.os.SharedMemory;
import android.util.Log;
import com.android.internal.app.IHotwordRecognitionStatusCallback;
import java.lang.annotation.Documented;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.util.Locale;
import java.util.function.IntConsumer;
/**
* Implemented by an application that wants to offer detection for hotword. The system will
@@ -54,6 +57,39 @@ public abstract class HotwordDetectionService extends Service {
// TODO (b/177502877): Set the Debug flag to false before shipping.
private static final boolean DBG = true;
private static final long UPDATE_TIMEOUT_MILLIS = 5000;
/** @hide */
@Retention(RetentionPolicy.SOURCE)
@IntDef(flag = true, prefix = { "INITIALIZATION_STATUS_" }, value = {
INITIALIZATION_STATUS_SUCCESS,
INITIALIZATION_STATUS_CUSTOM_ERROR_1,
INITIALIZATION_STATUS_CUSTOM_ERROR_2,
INITIALIZATION_STATUS_UNKNOWN,
})
public @interface InitializationStatus {}
/**
* Indicates that the updated status is successful.
*/
public static final int INITIALIZATION_STATUS_SUCCESS = 0;
/**
* Indicates that the updated status is failure for some application specific reasons.
*/
public static final int INITIALIZATION_STATUS_CUSTOM_ERROR_1 = 1;
/**
* Indicates that the updated status is failure for some application specific reasons.
*/
public static final int INITIALIZATION_STATUS_CUSTOM_ERROR_2 = 2;
/**
* Indicates that the callback wasn’t invoked within the timeout.
* This is used by system.
*/
public static final int INITIALIZATION_STATUS_UNKNOWN = 100;
/**
* Source for the given audio stream.
*
@@ -104,15 +140,16 @@ public abstract class HotwordDetectionService extends Service {
}
@Override
public void updateState(PersistableBundle options, SharedMemory sharedMemory)
throws RemoteException {
public void updateState(PersistableBundle options, SharedMemory sharedMemory,
IHotwordRecognitionStatusCallback callback) throws RemoteException {
if (DBG) {
Log.d(TAG, "#updateState");
}
mHandler.sendMessage(obtainMessage(HotwordDetectionService::onUpdateState,
mHandler.sendMessage(obtainMessage(HotwordDetectionService::onUpdateStateInternal,
HotwordDetectionService.this,
options,
sharedMemory));
sharedMemory,
callback));
}
@Override
@@ -207,12 +244,20 @@ public abstract class HotwordDetectionService extends Service {
* @param sharedMemory The unrestricted data blob to provide to the
* {@link HotwordDetectionService}. Use this to provide the hotword models data or other
* such data to the trusted process.
* @param callbackTimeoutMillis Timeout in milliseconds for the operation to invoke the
* statusCallback.
* @param statusCallback Use this to return the updated result. This is non-null only when the
* {@link HotwordDetectionService} is being initialized; and it is null if the state is updated
* after that.
*
* @hide
*/
@SystemApi
public void onUpdateState(@Nullable PersistableBundle options,
@Nullable SharedMemory sharedMemory) {
public void onUpdateState(
@Nullable PersistableBundle options,
@Nullable SharedMemory sharedMemory,
@DurationMillisLong long callbackTimeoutMillis,
@Nullable @InitializationStatus IntConsumer statusCallback) {
// TODO: Handle the unimplemented case by throwing?
}
@@ -268,6 +313,23 @@ public abstract class HotwordDetectionService extends Service {
throw new UnsupportedOperationException();
}
private void onUpdateStateInternal(@Nullable PersistableBundle options,
@Nullable SharedMemory sharedMemory, IHotwordRecognitionStatusCallback callback) {
// TODO (b/183684347): Implement timeout case.
IntConsumer intConsumer = null;
if (callback != null) {
intConsumer =
value -> {
try {
callback.onStatusReported(value);
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
}
};
}
onUpdateState(options, sharedMemory, UPDATE_TIMEOUT_MILLIS, intConsumer);
}
/**
* Callback for returning the detection result.
*

View File

@@ -27,6 +27,7 @@ import android.annotation.SystemApi;
import android.media.AudioFormat;
import android.os.ParcelFileDescriptor;
import android.os.PersistableBundle;
import android.service.voice.HotwordDetectionService.InitializationStatus;
/**
* Basic functionality for hotword detectors.
@@ -144,5 +145,13 @@ public interface HotwordDetector {
* {@link HotwordDetectionService}.
*/
void onRejected(@Nullable HotwordRejectedResult result);
/**
* Called when the {@link HotwordDetectionService} is created by the system and given a
* short amount of time to report it's initialization state.
*
* @param status Info about initialization state of {@link HotwordDetectionService}.
*/
void onHotwordDetectionServiceInitialized(@InitializationStatus int status);
}
}

View File

@@ -22,6 +22,8 @@ import android.os.PersistableBundle;
import android.os.SharedMemory;
import android.service.voice.IDspHotwordDetectionCallback;
import com.android.internal.app.IHotwordRecognitionStatusCallback;
/**
* Provide the interface to communicate with hotword detection service.
*
@@ -41,5 +43,6 @@ oneway interface IHotwordDetectionService {
in PersistableBundle options,
in IDspHotwordDetectionCallback callback);
void updateState(in PersistableBundle options, in SharedMemory sharedMemory);
void updateState(in PersistableBundle options, in SharedMemory sharedMemory,
in IHotwordRecognitionStatusCallback callback);
}

View File

@@ -67,7 +67,7 @@ class SoftwareHotwordDetector extends AbstractHotwordDetector {
mHandler = new Handler(Looper.getMainLooper());
try {
mManagerService.updateState(options, sharedMemory);
mManagerService.updateState(options, sharedMemory, null /* callback */);
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
}

View File

@@ -66,4 +66,12 @@ oneway interface IHotwordRecognitionStatusCallback {
* Called when the recognition is resumed after it was temporarily paused.
*/
void onRecognitionResumed();
/**
* Called when the {@link HotwordDetectionService} reported the result for requesting update
* state action.
*
* @param status The status about the result of requesting update state action.
*/
void onStatusReported(int status);
}

View File

@@ -26,6 +26,7 @@ import android.os.PersistableBundle;
import android.os.RemoteCallback;
import android.os.SharedMemory;
import com.android.internal.app.IHotwordRecognitionStatusCallback;
import com.android.internal.app.IVoiceActionCheckCallback;
import com.android.internal.app.IVoiceInteractionSessionShowCallback;
import com.android.internal.app.IVoiceInteractor;
@@ -238,8 +239,12 @@ interface IVoiceInteractionManagerService {
* @param sharedMemory The unrestricted data blob to provide to the
* {@link HotwordDetectionService}. Use this to provide the hotword models data or other
* such data to the trusted process.
* @param callback Use this to report {@link HotwordDetectionService} status.
*/
void updateState(in PersistableBundle options, in SharedMemory sharedMemory);
void updateState(
in PersistableBundle options,
in SharedMemory sharedMemory,
in IHotwordRecognitionStatusCallback callback);
/**
* Requests to shutdown hotword detection service.

View File

@@ -93,7 +93,7 @@ final class HotwordDetectionConnection {
HotwordDetectionConnection(Object lock, Context context, ComponentName serviceName,
int userId, boolean bindInstantServiceAllowed, @Nullable PersistableBundle options,
@Nullable SharedMemory sharedMemory) {
@Nullable SharedMemory sharedMemory, IHotwordRecognitionStatusCallback callback) {
mLock = lock;
mContext = context;
mDetectionComponentName = serviceName;
@@ -111,7 +111,7 @@ final class HotwordDetectionConnection {
mBound = connected;
if (connected) {
try {
service.updateState(options, sharedMemory);
service.updateState(options, sharedMemory, callback);
} catch (RemoteException e) {
// TODO: (b/181842909) Report an error to voice interactor
Slog.w(TAG, "Failed to updateState for HotwordDetectionService", e);
@@ -146,7 +146,7 @@ final class HotwordDetectionConnection {
void updateStateLocked(PersistableBundle options, SharedMemory sharedMemory) {
mRemoteHotwordDetectionService.run(
service -> service.updateState(options, sharedMemory));
service -> service.updateState(options, sharedMemory, null /* callback */));
}
void startListeningFromMic(

View File

@@ -1026,7 +1026,7 @@ public class VoiceInteractionManagerService extends SystemService {
@Override
public void updateState(@Nullable PersistableBundle options,
@Nullable SharedMemory sharedMemory) {
@Nullable SharedMemory sharedMemory, IHotwordRecognitionStatusCallback callback) {
enforceCallingPermission(Manifest.permission.MANAGE_HOTWORD_DETECTION);
synchronized (this) {
enforceIsCurrentVoiceInteractionService();
@@ -1037,7 +1037,7 @@ public class VoiceInteractionManagerService extends SystemService {
}
final long caller = Binder.clearCallingIdentity();
try {
mImpl.updateStateLocked(options, sharedMemory);
mImpl.updateStateLocked(options, sharedMemory, callback);
} finally {
Binder.restoreCallingIdentity(caller);
}

View File

@@ -405,7 +405,7 @@ class VoiceInteractionManagerServiceImpl implements VoiceInteractionSessionConne
}
public void updateStateLocked(@Nullable PersistableBundle options,
@Nullable SharedMemory sharedMemory) {
@Nullable SharedMemory sharedMemory, IHotwordRecognitionStatusCallback callback) {
if (DEBUG) {
Slog.d(TAG, "updateStateLocked");
}
@@ -427,7 +427,7 @@ class VoiceInteractionManagerServiceImpl implements VoiceInteractionSessionConne
if (mHotwordDetectionConnection == null) {
mHotwordDetectionConnection = new HotwordDetectionConnection(mServiceStub, mContext,
mHotwordDetectionComponentName, mUser, /* bindInstantServiceAllowed= */ false,
options, sharedMemory);
options, sharedMemory, callback);
} else {
mHotwordDetectionConnection.updateStateLocked(options, sharedMemory);
}