blob: 2e05e2073e80e7156d52b96cd7c765fd30f66a32 [file] [log] [blame]
/*
* Copyright (C) 2023 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.voiceinteraction;
import static android.service.voice.VisualQueryDetectionServiceFailure.ERROR_CODE_ILLEGAL_ATTENTION_STATE;
import static android.service.voice.VisualQueryDetectionServiceFailure.ERROR_CODE_ILLEGAL_STREAMING_STATE;
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.content.Context;
import android.media.AudioFormat;
import android.media.permission.Identity;
import android.os.IBinder;
import android.os.ParcelFileDescriptor;
import android.os.PersistableBundle;
import android.os.RemoteException;
import android.os.SharedMemory;
import android.service.voice.IDetectorSessionVisualQueryDetectionCallback;
import android.service.voice.IMicrophoneHotwordDetectionVoiceInteractionCallback;
import android.service.voice.ISandboxedDetectionService;
import android.service.voice.IVisualQueryDetectionVoiceInteractionCallback;
import android.service.voice.VisualQueryDetectionServiceFailure;
import android.util.Slog;
import com.android.internal.app.IHotwordRecognitionStatusCallback;
import com.android.internal.app.IVisualQueryDetectionAttentionListener;
import com.android.server.voiceinteraction.VoiceInteractionManagerServiceImpl.DetectorRemoteExceptionListener;
import java.io.PrintWriter;
import java.util.Objects;
import java.util.concurrent.ScheduledExecutorService;
/**
* A class that provides visual query detector to communicate with the {@link
* android.service.voice.VisualQueryDetectionService}.
*
* This class can handle the visual query detection whose detector is created by using
* {@link android.service.voice.VoiceInteractionService#createVisualQueryDetector(PersistableBundle
* ,SharedMemory, HotwordDetector.Callback)}.
*/
final class VisualQueryDetectorSession extends DetectorSession {
private static final String TAG = "VisualQueryDetectorSession";
private IVisualQueryDetectionAttentionListener mAttentionListener;
private boolean mEgressingData;
private boolean mQueryStreaming;
//TODO(b/261783819): Determines actual functionalities, e.g., startRecognition etc.
VisualQueryDetectorSession(
@NonNull HotwordDetectionConnection.ServiceConnection remoteService,
@NonNull Object lock, @NonNull Context context, @NonNull IBinder token,
@NonNull IHotwordRecognitionStatusCallback callback, int voiceInteractionServiceUid,
Identity voiceInteractorIdentity,
@NonNull ScheduledExecutorService scheduledExecutorService, boolean logging,
@NonNull DetectorRemoteExceptionListener listener) {
super(remoteService, lock, context, token, callback,
voiceInteractionServiceUid, voiceInteractorIdentity, scheduledExecutorService,
logging, listener);
mEgressingData = false;
mQueryStreaming = false;
mAttentionListener = null;
// TODO: handle notify RemoteException to client
}
@Override
@SuppressWarnings("GuardedBy")
void informRestartProcessLocked() {
Slog.v(TAG, "informRestartProcessLocked");
mUpdateStateAfterStartFinished.set(false);
try {
mCallback.onProcessRestarted();
} catch (RemoteException e) {
Slog.w(TAG, "Failed to communicate #onProcessRestarted", e);
notifyOnDetectorRemoteException();
}
}
void setVisualQueryDetectionAttentionListenerLocked(
@Nullable IVisualQueryDetectionAttentionListener listener) {
mAttentionListener = listener;
}
@SuppressWarnings("GuardedBy")
void startPerceivingLocked(IVisualQueryDetectionVoiceInteractionCallback callback) {
if (DEBUG) {
Slog.d(TAG, "startPerceivingLocked");
}
IDetectorSessionVisualQueryDetectionCallback internalCallback =
new IDetectorSessionVisualQueryDetectionCallback.Stub(){
@Override
public void onAttentionGained() {
Slog.v(TAG, "BinderCallback#onAttentionGained");
mEgressingData = true;
if (mAttentionListener == null) {
return;
}
try {
mAttentionListener.onAttentionGained();
} catch (RemoteException e) {
Slog.e(TAG, "Error delivering attention gained event.", e);
try {
callback.onVisualQueryDetectionServiceFailure(
new VisualQueryDetectionServiceFailure(
ERROR_CODE_ILLEGAL_ATTENTION_STATE,
"Attention listener failed to switch to GAINED state."));
} catch (RemoteException ex) {
Slog.v(TAG, "Fail to call onVisualQueryDetectionServiceFailure");
}
return;
}
}
@Override
public void onAttentionLost() {
Slog.v(TAG, "BinderCallback#onAttentionLost");
mEgressingData = false;
if (mAttentionListener == null) {
return;
}
try {
mAttentionListener.onAttentionLost();
} catch (RemoteException e) {
Slog.e(TAG, "Error delivering attention lost event.", e);
try {
callback.onVisualQueryDetectionServiceFailure(
new VisualQueryDetectionServiceFailure(
ERROR_CODE_ILLEGAL_ATTENTION_STATE,
"Attention listener failed to switch to LOST state."));
} catch (RemoteException ex) {
Slog.v(TAG, "Fail to call onVisualQueryDetectionServiceFailure");
}
return;
}
}
@Override
public void onQueryDetected(@NonNull String partialQuery) throws RemoteException {
Objects.requireNonNull(partialQuery);
Slog.v(TAG, "BinderCallback#onQueryDetected");
if (!mEgressingData) {
Slog.v(TAG, "Query should not be egressed within the unattention state.");
callback.onVisualQueryDetectionServiceFailure(
new VisualQueryDetectionServiceFailure(
ERROR_CODE_ILLEGAL_STREAMING_STATE,
"Cannot stream queries without attention signals."));
return;
}
mQueryStreaming = true;
callback.onQueryDetected(partialQuery);
Slog.i(TAG, "Egressed from visual query detection process.");
}
@Override
public void onQueryFinished() throws RemoteException {
Slog.v(TAG, "BinderCallback#onQueryFinished");
if (!mQueryStreaming) {
Slog.v(TAG, "Query streaming state signal FINISHED is block since there is"
+ " no active query being streamed.");
callback.onVisualQueryDetectionServiceFailure(
new VisualQueryDetectionServiceFailure(
ERROR_CODE_ILLEGAL_STREAMING_STATE,
"Cannot send FINISHED signal with no query streamed."));
return;
}
callback.onQueryFinished();
mQueryStreaming = false;
}
@Override
public void onQueryRejected() throws RemoteException {
Slog.v(TAG, "BinderCallback#onQueryRejected");
if (!mQueryStreaming) {
Slog.v(TAG, "Query streaming state signal REJECTED is block since there is"
+ " no active query being streamed.");
callback.onVisualQueryDetectionServiceFailure(
new VisualQueryDetectionServiceFailure(
ERROR_CODE_ILLEGAL_STREAMING_STATE,
"Cannot send REJECTED signal with no query streamed."));
return;
}
callback.onQueryRejected();
mQueryStreaming = false;
}
};
mRemoteDetectionService.run(service -> service.detectWithVisualSignals(internalCallback));
}
@SuppressWarnings("GuardedBy")
void stopPerceivingLocked() {
if (DEBUG) {
Slog.d(TAG, "stopPerceivingLocked");
}
mRemoteDetectionService.run(ISandboxedDetectionService::stopDetection);
}
@Override
void startListeningFromExternalSourceLocked(
ParcelFileDescriptor audioStream,
AudioFormat audioFormat,
@Nullable PersistableBundle options,
IMicrophoneHotwordDetectionVoiceInteractionCallback callback)
throws UnsupportedOperationException {
throw new UnsupportedOperationException("HotwordDetectionService method"
+ " should not be called from VisualQueryDetectorSession.");
}
@SuppressWarnings("GuardedBy")
public void dumpLocked(String prefix, PrintWriter pw) {
super.dumpLocked(prefix, pw);
pw.print(prefix);
}
}