Loading core/java/android/service/voice/HotwordDetectionService.java +9 −6 Original line number Diff line number Diff line Loading @@ -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; Loading @@ -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; Loading @@ -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) Loading Loading @@ -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"); } Loading Loading @@ -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(); } Loading core/java/android/service/voice/IHotwordDetectionService.aidl +2 −3 Original line number Diff line number Diff line Loading @@ -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. * Loading @@ -44,5 +43,5 @@ oneway interface IHotwordDetectionService { in IDspHotwordDetectionCallback callback); void updateState(in PersistableBundle options, in SharedMemory sharedMemory, in IHotwordRecognitionStatusCallback callback); in IRemoteCallback callback); } services/voiceinteraction/java/com/android/server/voiceinteraction/HotwordDetectionConnection.java +73 −8 Original line number Diff line number Diff line Loading @@ -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; Loading @@ -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; Loading @@ -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; Loading @@ -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. Loading @@ -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; Loading Loading @@ -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); } } } } Loading @@ -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() { Loading Loading
core/java/android/service/voice/HotwordDetectionService.java +9 −6 Original line number Diff line number Diff line Loading @@ -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; Loading @@ -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; Loading @@ -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) Loading Loading @@ -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"); } Loading Loading @@ -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(); } Loading
core/java/android/service/voice/IHotwordDetectionService.aidl +2 −3 Original line number Diff line number Diff line Loading @@ -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. * Loading @@ -44,5 +43,5 @@ oneway interface IHotwordDetectionService { in IDspHotwordDetectionCallback callback); void updateState(in PersistableBundle options, in SharedMemory sharedMemory, in IHotwordRecognitionStatusCallback callback); in IRemoteCallback callback); }
services/voiceinteraction/java/com/android/server/voiceinteraction/HotwordDetectionConnection.java +73 −8 Original line number Diff line number Diff line Loading @@ -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; Loading @@ -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; Loading @@ -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; Loading @@ -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. Loading @@ -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; Loading Loading @@ -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); } } } } Loading @@ -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() { Loading