blob: f7a3415259fd5bca9cdcbf293160265a97abcb22 [file] [log] [blame]
/*
* 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.service.voice;
import static android.Manifest.permission.RECORD_AUDIO;
import static com.android.internal.util.function.pooled.PooledLambda.obtainMessage;
import android.annotation.Nullable;
import android.annotation.RequiresPermission;
import android.hardware.soundtrigger.SoundTrigger;
import android.media.AudioFormat;
import android.os.Handler;
import android.os.Looper;
import android.os.ParcelFileDescriptor;
import android.os.PersistableBundle;
import android.os.RemoteException;
import android.os.SharedMemory;
import android.util.Slog;
import com.android.internal.app.IHotwordRecognitionStatusCallback;
import com.android.internal.app.IVoiceInteractionManagerService;
import java.io.PrintWriter;
/**
* Manages hotword detection not relying on a specific hardware.
*
* <p>On devices where DSP is available it's strongly recommended to use
* {@link AlwaysOnHotwordDetector}.
*
* @hide
**/
class SoftwareHotwordDetector extends AbstractHotwordDetector {
private static final String TAG = SoftwareHotwordDetector.class.getSimpleName();
private static final boolean DEBUG = false;
private final IVoiceInteractionManagerService mManagerService;
private final HotwordDetector.Callback mCallback;
private final AudioFormat mAudioFormat;
private final Handler mHandler;
SoftwareHotwordDetector(
IVoiceInteractionManagerService managerService,
AudioFormat audioFormat,
PersistableBundle options,
SharedMemory sharedMemory,
HotwordDetector.Callback callback) {
super(managerService, callback);
mManagerService = managerService;
mAudioFormat = audioFormat;
mCallback = callback;
mHandler = new Handler(Looper.getMainLooper());
updateStateLocked(options, sharedMemory,
new InitializationStateListener(mHandler, mCallback));
}
@RequiresPermission(RECORD_AUDIO)
@Override
public boolean startRecognition() {
if (DEBUG) {
Slog.i(TAG, "#startRecognition");
}
maybeCloseExistingSession();
try {
mManagerService.startListeningFromMic(
mAudioFormat, new BinderCallback(mHandler, mCallback));
} catch (SecurityException e) {
Slog.e(TAG, "startRecognition failed: " + e);
return false;
} catch (RemoteException e) {
e.rethrowFromSystemServer();
}
return true;
}
/** TODO: stopRecognition */
@RequiresPermission(RECORD_AUDIO)
@Override
public boolean stopRecognition() {
if (DEBUG) {
Slog.i(TAG, "#stopRecognition");
}
try {
mManagerService.stopListeningFromMic();
} catch (RemoteException e) {
e.rethrowFromSystemServer();
}
return true;
}
private void maybeCloseExistingSession() {
// TODO: needs to be synchronized.
// TODO: implement this
}
private static class BinderCallback
extends IMicrophoneHotwordDetectionVoiceInteractionCallback.Stub {
private final Handler mHandler;
// TODO: this needs to be a weak reference.
private final HotwordDetector.Callback mCallback;
BinderCallback(Handler handler, HotwordDetector.Callback callback) {
this.mHandler = handler;
this.mCallback = callback;
}
/** Called when the detected result is valid. */
@Override
public void onDetected(
@Nullable HotwordDetectedResult hotwordDetectedResult,
@Nullable AudioFormat audioFormat,
@Nullable ParcelFileDescriptor audioStream) {
mHandler.sendMessage(obtainMessage(
HotwordDetector.Callback::onDetected,
mCallback,
new AlwaysOnHotwordDetector.EventPayload(
audioFormat, hotwordDetectedResult, audioStream)));
}
}
private static class InitializationStateListener
extends IHotwordRecognitionStatusCallback.Stub {
private final Handler mHandler;
private final HotwordDetector.Callback mCallback;
InitializationStateListener(Handler handler, HotwordDetector.Callback callback) {
this.mHandler = handler;
this.mCallback = callback;
}
@Override
public void onKeyphraseDetected(
SoundTrigger.KeyphraseRecognitionEvent recognitionEvent,
HotwordDetectedResult result) {
if (DEBUG) {
Slog.i(TAG, "Ignored #onKeyphraseDetected event");
}
}
@Override
public void onGenericSoundTriggerDetected(
SoundTrigger.GenericRecognitionEvent recognitionEvent) throws RemoteException {
if (DEBUG) {
Slog.i(TAG, "Ignored #onGenericSoundTriggerDetected event");
}
}
@Override
public void onRejected(HotwordRejectedResult result) throws RemoteException {
if (DEBUG) {
Slog.i(TAG, "Ignored #onRejected event");
}
}
@Override
public void onError(int status) throws RemoteException {
if (DEBUG) {
Slog.i(TAG, "Ignored #onError (" + status + ") event");
}
}
@Override
public void onRecognitionPaused() throws RemoteException {
if (DEBUG) {
Slog.i(TAG, "Ignored #onRecognitionPaused event");
}
}
@Override
public void onRecognitionResumed() throws RemoteException {
if (DEBUG) {
Slog.i(TAG, "Ignored #onRecognitionResumed event");
}
}
@Override
public void onStatusReported(int status) {
Slog.v(TAG, "onStatusReported" + (DEBUG ? "(" + status + ")" : ""));
mHandler.sendMessage(obtainMessage(
HotwordDetector.Callback::onHotwordDetectionServiceInitialized,
mCallback,
status));
}
@Override
public void onProcessRestarted() throws RemoteException {
Slog.v(TAG, "onProcessRestarted()");
mHandler.sendMessage(obtainMessage(
HotwordDetector.Callback::onHotwordDetectionServiceRestarted,
mCallback));
}
}
/** @hide */
public void dump(String prefix, PrintWriter pw) {
// TODO: implement this
}
}