Implement timeout mechanism when HotwordDetectionService initializes

Bug: 183684347
Test: atest CtsVoiceInteractionTestCases
Test: atest CtsVoiceInteractionTestCases --instant
Change-Id: I77d29491b055ee2a45aa088df4c1cd54e604c259
This commit is contained in:
lpeter
2021-03-31 09:01:35 +08:00
parent b9822aba4a
commit 9c9f5aa6a1
3 changed files with 84 additions and 17 deletions

View File

@@ -28,8 +28,10 @@ import android.annotation.SystemApi;
import android.app.Service;
import android.content.Intent;
import android.media.AudioFormat;
import android.os.Bundle;
import android.os.Handler;
import android.os.IBinder;
import android.os.IRemoteCallback;
import android.os.Looper;
import android.os.ParcelFileDescriptor;
import android.os.PersistableBundle;
@@ -37,8 +39,6 @@ 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;
@@ -58,6 +58,8 @@ public abstract class HotwordDetectionService extends Service {
private static final boolean DBG = true;
private static final long UPDATE_TIMEOUT_MILLIS = 5000;
/** @hide */
public static final String KEY_INITIALIZATION_STATUS = "initialization_status";
/** @hide */
@Retention(RetentionPolicy.SOURCE)
@@ -141,7 +143,7 @@ public abstract class HotwordDetectionService extends Service {
@Override
public void updateState(PersistableBundle options, SharedMemory sharedMemory,
IHotwordRecognitionStatusCallback callback) throws RemoteException {
IRemoteCallback callback) throws RemoteException {
if (DBG) {
Log.d(TAG, "#updateState");
}
@@ -314,14 +316,15 @@ public abstract class HotwordDetectionService extends Service {
}
private void onUpdateStateInternal(@Nullable PersistableBundle options,
@Nullable SharedMemory sharedMemory, IHotwordRecognitionStatusCallback callback) {
// TODO (b/183684347): Implement timeout case.
@Nullable SharedMemory sharedMemory, IRemoteCallback callback) {
IntConsumer intConsumer = null;
if (callback != null) {
intConsumer =
value -> {
try {
callback.onStatusReported(value);
Bundle status = new Bundle();
status.putInt(KEY_INITIALIZATION_STATUS, value);
callback.sendResult(status);
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
}

View File

@@ -17,13 +17,12 @@
package android.service.voice;
import android.media.AudioFormat;
import android.os.IRemoteCallback;
import android.os.ParcelFileDescriptor;
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.
*
@@ -44,5 +43,5 @@ oneway interface IHotwordDetectionService {
in IDspHotwordDetectionCallback callback);
void updateState(in PersistableBundle options, in SharedMemory sharedMemory,
in IHotwordRecognitionStatusCallback callback);
in IRemoteCallback callback);
}

View File

@@ -18,6 +18,8 @@ package com.android.server.voiceinteraction;
import static android.service.voice.HotwordDetectionService.AUDIO_SOURCE_EXTERNAL;
import static android.service.voice.HotwordDetectionService.AUDIO_SOURCE_MICROPHONE;
import static android.service.voice.HotwordDetectionService.INITIALIZATION_STATUS_UNKNOWN;
import static android.service.voice.HotwordDetectionService.KEY_INITIALIZATION_STATUS;
import android.annotation.NonNull;
import android.annotation.Nullable;
@@ -31,6 +33,8 @@ import android.media.AudioFormat;
import android.media.AudioManager;
import android.media.AudioRecord;
import android.media.MediaRecorder;
import android.os.Bundle;
import android.os.IRemoteCallback;
import android.os.ParcelFileDescriptor;
import android.os.PersistableBundle;
import android.os.RemoteException;
@@ -46,6 +50,7 @@ import android.util.Slog;
import com.android.internal.annotations.GuardedBy;
import com.android.internal.app.IHotwordRecognitionStatusCallback;
import com.android.internal.infra.AndroidFuture;
import com.android.internal.infra.ServiceConnector;
import java.io.Closeable;
@@ -58,6 +63,8 @@ import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.ScheduledFuture;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.TimeoutException;
import java.util.concurrent.atomic.AtomicBoolean;
/**
* A class that provides the communication with the HotwordDetectionService.
@@ -75,11 +82,13 @@ final class HotwordDetectionConnection {
private static final int MAX_STREAMING_SECONDS = 10;
private static final int MICROPHONE_BUFFER_LENGTH_SECONDS = 8;
private static final int HOTWORD_AUDIO_LENGTH_SECONDS = 3;
private static final long MAX_UPDATE_TIMEOUT_MILLIS = 6000;
private final Executor mAudioCopyExecutor = Executors.newCachedThreadPool();
// TODO: This may need to be a Handler(looper)
private final ScheduledExecutorService mScheduledExecutorService =
Executors.newSingleThreadScheduledExecutor();
private final AtomicBoolean mUpdateStateFinish = new AtomicBoolean(false);
final Object mLock;
final ComponentName mDetectionComponentName;
@@ -107,16 +116,11 @@ final class HotwordDetectionConnection {
@Override // from ServiceConnector.Impl
protected void onServiceConnectionStatusChanged(IHotwordDetectionService service,
boolean connected) {
if (DEBUG) {
Slog.d(TAG, "onServiceConnectionStatusChanged connected = " + connected);
}
synchronized (mLock) {
mBound = connected;
if (connected) {
try {
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);
}
}
}
}
@@ -126,6 +130,67 @@ final class HotwordDetectionConnection {
}
};
mRemoteHotwordDetectionService.connect();
if (callback == null) {
updateStateLocked(options, sharedMemory);
return;
}
updateStateWithCallbackLocked(options, sharedMemory, callback);
}
private void updateStateWithCallbackLocked(PersistableBundle options,
SharedMemory sharedMemory, IHotwordRecognitionStatusCallback callback) {
if (DEBUG) {
Slog.d(TAG, "updateStateWithCallbackLocked");
}
mRemoteHotwordDetectionService.postAsync(service -> {
AndroidFuture<Void> future = new AndroidFuture<>();
IRemoteCallback statusCallback = new IRemoteCallback.Stub() {
@Override
public void sendResult(Bundle bundle) throws RemoteException {
if (DEBUG) {
Slog.d(TAG, "updateState finish");
}
future.complete(null);
try {
if (mUpdateStateFinish.getAndSet(true)) {
Slog.w(TAG, "call callback after timeout");
return;
}
int status = bundle != null ? bundle.getInt(
KEY_INITIALIZATION_STATUS,
INITIALIZATION_STATUS_UNKNOWN)
: INITIALIZATION_STATUS_UNKNOWN;
callback.onStatusReported(status);
} catch (RemoteException e) {
Slog.w(TAG, "Failed to report initialization status: " + e);
}
}
};
try {
service.updateState(options, sharedMemory, statusCallback);
} catch (RemoteException e) {
// TODO: (b/181842909) Report an error to voice interactor
Slog.w(TAG, "Failed to updateState for HotwordDetectionService", e);
}
return future;
}).orTimeout(MAX_UPDATE_TIMEOUT_MILLIS, TimeUnit.MILLISECONDS)
.whenComplete((res, err) -> {
if (err instanceof TimeoutException) {
Slog.w(TAG, "updateState timed out");
try {
if (mUpdateStateFinish.getAndSet(true)) {
return;
}
callback.onStatusReported(INITIALIZATION_STATUS_UNKNOWN);
} catch (RemoteException e) {
Slog.w(TAG, "Failed to report initialization status: " + e);
}
} else if (err != null) {
Slog.w(TAG, "Failed to update state: " + err);
} else {
// NOTE: so far we don't need to take any action.
}
});
}
private boolean isBound() {