Merge changes from topics "aohd-invalidate", "eventpayload-data"
* changes: API to destroy active detectors expose HAL keyphrase trigger extras to EventPayload expose HAL keyphrase trigger data to EventPayload
This commit is contained in:
@@ -5176,6 +5176,15 @@ package android.hardware.soundtrigger {
|
||||
field @NonNull public static final android.os.Parcelable.Creator<android.hardware.soundtrigger.SoundTrigger.Keyphrase> CREATOR;
|
||||
}
|
||||
|
||||
public static final class SoundTrigger.KeyphraseRecognitionExtra implements android.os.Parcelable {
|
||||
method public int describeContents();
|
||||
method public int getCoarseConfidenceLevel();
|
||||
method public int getKeyphraseId();
|
||||
method public int getRecognitionModes();
|
||||
method public void writeToParcel(@NonNull android.os.Parcel, int);
|
||||
field @NonNull public static final android.os.Parcelable.Creator<android.hardware.soundtrigger.SoundTrigger.KeyphraseRecognitionExtra> CREATOR;
|
||||
}
|
||||
|
||||
public static final class SoundTrigger.KeyphraseSoundModel extends android.hardware.soundtrigger.SoundTrigger.SoundModel implements android.os.Parcelable {
|
||||
ctor public SoundTrigger.KeyphraseSoundModel(@NonNull java.util.UUID, @NonNull java.util.UUID, @Nullable byte[], @Nullable android.hardware.soundtrigger.SoundTrigger.Keyphrase[], int);
|
||||
ctor public SoundTrigger.KeyphraseSoundModel(@NonNull java.util.UUID, @NonNull java.util.UUID, @Nullable byte[], @Nullable android.hardware.soundtrigger.SoundTrigger.Keyphrase[]);
|
||||
@@ -11760,8 +11769,13 @@ package android.service.voice {
|
||||
public static class AlwaysOnHotwordDetector.EventPayload {
|
||||
method @Nullable public android.os.ParcelFileDescriptor getAudioStream();
|
||||
method @Nullable public android.media.AudioFormat getCaptureAudioFormat();
|
||||
method @Nullable public byte[] getData();
|
||||
method public int getDataFormat();
|
||||
method @Nullable public android.service.voice.HotwordDetectedResult getHotwordDetectedResult();
|
||||
method @Nullable public byte[] getTriggerAudio();
|
||||
method @NonNull public java.util.List<android.hardware.soundtrigger.SoundTrigger.KeyphraseRecognitionExtra> getKeyphraseRecognitionExtras();
|
||||
method @Deprecated @Nullable public byte[] getTriggerAudio();
|
||||
field public static final int DATA_FORMAT_RAW = 0; // 0x0
|
||||
field public static final int DATA_FORMAT_TRIGGER_AUDIO = 1; // 0x1
|
||||
}
|
||||
|
||||
public static final class AlwaysOnHotwordDetector.ModelParamRange {
|
||||
@@ -11832,6 +11846,7 @@ package android.service.voice {
|
||||
}
|
||||
|
||||
public interface HotwordDetector {
|
||||
method public default void destroy();
|
||||
method @RequiresPermission(allOf={android.Manifest.permission.RECORD_AUDIO, android.Manifest.permission.CAPTURE_AUDIO_HOTWORD}) public boolean startRecognition();
|
||||
method public boolean startRecognition(@NonNull android.os.ParcelFileDescriptor, @NonNull android.media.AudioFormat, @Nullable android.os.PersistableBundle);
|
||||
method public boolean stopRecognition();
|
||||
|
||||
@@ -1279,6 +1279,10 @@ package android.hardware.soundtrigger {
|
||||
field @NonNull public static final android.os.Parcelable.Creator<android.hardware.soundtrigger.KeyphraseMetadata> CREATOR;
|
||||
}
|
||||
|
||||
public static final class SoundTrigger.KeyphraseRecognitionExtra implements android.os.Parcelable {
|
||||
ctor public SoundTrigger.KeyphraseRecognitionExtra(int, int, int);
|
||||
}
|
||||
|
||||
public static final class SoundTrigger.ModelParamRange implements android.os.Parcelable {
|
||||
ctor public SoundTrigger.ModelParamRange(int, int);
|
||||
}
|
||||
@@ -2401,6 +2405,19 @@ package android.service.voice {
|
||||
method @RequiresPermission(allOf={android.Manifest.permission.RECORD_AUDIO, android.Manifest.permission.CAPTURE_AUDIO_HOTWORD}) public void triggerHardwareRecognitionEventForTest(int, int, boolean, int, int, int, boolean, @NonNull android.media.AudioFormat, @Nullable byte[]);
|
||||
}
|
||||
|
||||
public static final class AlwaysOnHotwordDetector.EventPayload.Builder {
|
||||
ctor public AlwaysOnHotwordDetector.EventPayload.Builder();
|
||||
method @NonNull public android.service.voice.AlwaysOnHotwordDetector.EventPayload build();
|
||||
method @NonNull public android.service.voice.AlwaysOnHotwordDetector.EventPayload.Builder setAudioStream(@NonNull android.os.ParcelFileDescriptor);
|
||||
method @NonNull public android.service.voice.AlwaysOnHotwordDetector.EventPayload.Builder setCaptureAudioFormat(@NonNull android.media.AudioFormat);
|
||||
method @NonNull public android.service.voice.AlwaysOnHotwordDetector.EventPayload.Builder setCaptureAvailable(boolean);
|
||||
method @NonNull public android.service.voice.AlwaysOnHotwordDetector.EventPayload.Builder setCaptureSession(int);
|
||||
method @NonNull public android.service.voice.AlwaysOnHotwordDetector.EventPayload.Builder setData(@NonNull byte[]);
|
||||
method @NonNull public android.service.voice.AlwaysOnHotwordDetector.EventPayload.Builder setDataFormat(int);
|
||||
method @NonNull public android.service.voice.AlwaysOnHotwordDetector.EventPayload.Builder setHotwordDetectedResult(@NonNull android.service.voice.HotwordDetectedResult);
|
||||
method @NonNull public android.service.voice.AlwaysOnHotwordDetector.EventPayload.Builder setKeyphraseRecognitionExtras(@NonNull java.util.List<android.hardware.soundtrigger.SoundTrigger.KeyphraseRecognitionExtra>);
|
||||
}
|
||||
|
||||
public final class VisibleActivityInfo implements android.os.Parcelable {
|
||||
ctor public VisibleActivityInfo(int, @NonNull android.os.IBinder);
|
||||
}
|
||||
|
||||
@@ -61,6 +61,7 @@ import java.lang.annotation.Retention;
|
||||
import java.lang.annotation.RetentionPolicy;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collection;
|
||||
import java.util.Locale;
|
||||
import java.util.UUID;
|
||||
|
||||
@@ -1576,31 +1577,58 @@ public class SoundTrigger {
|
||||
}
|
||||
|
||||
/**
|
||||
* Additional data conveyed by a {@link KeyphraseRecognitionEvent}
|
||||
* for a key phrase detection.
|
||||
*
|
||||
* @hide
|
||||
* Additional data conveyed by a {@link KeyphraseRecognitionEvent}
|
||||
* for a key phrase detection.
|
||||
*/
|
||||
public static class KeyphraseRecognitionExtra implements Parcelable {
|
||||
/** The keyphrase ID */
|
||||
public static final class KeyphraseRecognitionExtra implements Parcelable {
|
||||
/**
|
||||
* The keyphrase ID
|
||||
*
|
||||
* @hide
|
||||
*/
|
||||
@UnsupportedAppUsage
|
||||
public final int id;
|
||||
|
||||
/** Recognition modes matched for this event */
|
||||
/**
|
||||
* Recognition modes matched for this event
|
||||
*
|
||||
* @hide
|
||||
*/
|
||||
@UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
|
||||
public final int recognitionModes;
|
||||
|
||||
/** Confidence level for mode RECOGNITION_MODE_VOICE_TRIGGER when user identification
|
||||
* is not performed */
|
||||
/**
|
||||
* Confidence level for mode RECOGNITION_MODE_VOICE_TRIGGER when user identification
|
||||
* is not performed
|
||||
*
|
||||
* @hide
|
||||
*/
|
||||
@UnsupportedAppUsage
|
||||
public final int coarseConfidenceLevel;
|
||||
|
||||
/** Confidence levels for all users recognized (KeyphraseRecognitionEvent) or to
|
||||
* be recognized (RecognitionConfig) */
|
||||
/**
|
||||
* Confidence levels for all users recognized (KeyphraseRecognitionEvent) or to
|
||||
* be recognized (RecognitionConfig)
|
||||
*
|
||||
* @hide
|
||||
*/
|
||||
@UnsupportedAppUsage
|
||||
@NonNull
|
||||
public final ConfidenceLevel[] confidenceLevels;
|
||||
|
||||
|
||||
/**
|
||||
* @hide
|
||||
*/
|
||||
@TestApi
|
||||
public KeyphraseRecognitionExtra(int id, @RecognitionModes int recognitionModes,
|
||||
int coarseConfidenceLevel) {
|
||||
this(id, recognitionModes, coarseConfidenceLevel, new ConfidenceLevel[0]);
|
||||
}
|
||||
|
||||
/**
|
||||
* @hide
|
||||
*/
|
||||
@UnsupportedAppUsage
|
||||
public KeyphraseRecognitionExtra(int id, int recognitionModes, int coarseConfidenceLevel,
|
||||
@Nullable ConfidenceLevel[] confidenceLevels) {
|
||||
@@ -1611,7 +1639,47 @@ public class SoundTrigger {
|
||||
confidenceLevels != null ? confidenceLevels : new ConfidenceLevel[0];
|
||||
}
|
||||
|
||||
public static final @android.annotation.NonNull Parcelable.Creator<KeyphraseRecognitionExtra> CREATOR
|
||||
/**
|
||||
* The keyphrase ID associated with this class' additional data
|
||||
*/
|
||||
public int getKeyphraseId() {
|
||||
return id;
|
||||
}
|
||||
|
||||
/**
|
||||
* Recognition modes matched for this event
|
||||
*/
|
||||
@RecognitionModes
|
||||
public int getRecognitionModes() {
|
||||
return recognitionModes;
|
||||
}
|
||||
|
||||
/**
|
||||
* Confidence level for mode RECOGNITION_MODE_VOICE_TRIGGER when user identification
|
||||
* is not performed
|
||||
*
|
||||
* <p>The confidence level is expressed in percent (0% -100%).
|
||||
*/
|
||||
public int getCoarseConfidenceLevel() {
|
||||
return coarseConfidenceLevel;
|
||||
}
|
||||
|
||||
/**
|
||||
* Detected confidence level for users defined in a keyphrase.
|
||||
*
|
||||
* <p>The confidence level is expressed in percent (0% -100%).
|
||||
*
|
||||
* <p>The user ID is derived from the system ID
|
||||
* {@link android.os.UserHandle#getIdentifier()}.
|
||||
*
|
||||
* @hide
|
||||
*/
|
||||
@NonNull
|
||||
public Collection<ConfidenceLevel> getConfidenceLevels() {
|
||||
return Arrays.asList(confidenceLevels);
|
||||
}
|
||||
|
||||
public static final @NonNull Parcelable.Creator<KeyphraseRecognitionExtra> CREATOR
|
||||
= new Parcelable.Creator<KeyphraseRecognitionExtra>() {
|
||||
public KeyphraseRecognitionExtra createFromParcel(Parcel in) {
|
||||
return KeyphraseRecognitionExtra.fromParcel(in);
|
||||
@@ -1632,7 +1700,7 @@ public class SoundTrigger {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void writeToParcel(Parcel dest, int flags) {
|
||||
public void writeToParcel(@NonNull Parcel dest, int flags) {
|
||||
dest.writeInt(id);
|
||||
dest.writeInt(recognitionModes);
|
||||
dest.writeInt(coarseConfidenceLevel);
|
||||
@@ -1657,21 +1725,28 @@ public class SoundTrigger {
|
||||
|
||||
@Override
|
||||
public boolean equals(@Nullable Object obj) {
|
||||
if (this == obj)
|
||||
if (this == obj) {
|
||||
return true;
|
||||
if (obj == null)
|
||||
}
|
||||
if (obj == null) {
|
||||
return false;
|
||||
if (getClass() != obj.getClass())
|
||||
}
|
||||
if (getClass() != obj.getClass()) {
|
||||
return false;
|
||||
}
|
||||
KeyphraseRecognitionExtra other = (KeyphraseRecognitionExtra) obj;
|
||||
if (!Arrays.equals(confidenceLevels, other.confidenceLevels))
|
||||
if (!Arrays.equals(confidenceLevels, other.confidenceLevels)) {
|
||||
return false;
|
||||
if (id != other.id)
|
||||
}
|
||||
if (id != other.id) {
|
||||
return false;
|
||||
if (recognitionModes != other.recognitionModes)
|
||||
}
|
||||
if (recognitionModes != other.recognitionModes) {
|
||||
return false;
|
||||
if (coarseConfidenceLevel != other.coarseConfidenceLevel)
|
||||
}
|
||||
if (coarseConfidenceLevel != other.coarseConfidenceLevel) {
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -1715,7 +1790,7 @@ public class SoundTrigger {
|
||||
keyphraseExtras != null ? keyphraseExtras : new KeyphraseRecognitionExtra[0];
|
||||
}
|
||||
|
||||
public static final @android.annotation.NonNull Parcelable.Creator<KeyphraseRecognitionEvent> CREATOR
|
||||
public static final @NonNull Parcelable.Creator<KeyphraseRecognitionEvent> CREATOR
|
||||
= new Parcelable.Creator<KeyphraseRecognitionEvent>() {
|
||||
public KeyphraseRecognitionEvent createFromParcel(Parcel in) {
|
||||
return KeyphraseRecognitionEvent.fromParcelForKeyphrase(in);
|
||||
|
||||
@@ -18,6 +18,7 @@ package android.service.voice;
|
||||
|
||||
import static com.android.internal.util.function.pooled.PooledLambda.obtainMessage;
|
||||
|
||||
import android.annotation.CallSuper;
|
||||
import android.annotation.NonNull;
|
||||
import android.annotation.Nullable;
|
||||
import android.app.ActivityThread;
|
||||
@@ -34,6 +35,9 @@ import android.util.Slog;
|
||||
import com.android.internal.app.IHotwordRecognitionStatusCallback;
|
||||
import com.android.internal.app.IVoiceInteractionManagerService;
|
||||
|
||||
import java.util.concurrent.atomic.AtomicBoolean;
|
||||
import java.util.function.Consumer;
|
||||
|
||||
/** Base implementation of {@link HotwordDetector}. */
|
||||
abstract class AbstractHotwordDetector implements HotwordDetector {
|
||||
private static final String TAG = AbstractHotwordDetector.class.getSimpleName();
|
||||
@@ -45,6 +49,8 @@ abstract class AbstractHotwordDetector implements HotwordDetector {
|
||||
private final Handler mHandler;
|
||||
private final HotwordDetector.Callback mCallback;
|
||||
private final int mDetectorType;
|
||||
private Consumer<AbstractHotwordDetector> mOnDestroyListener;
|
||||
private final AtomicBoolean mIsDetectorActive;
|
||||
|
||||
AbstractHotwordDetector(
|
||||
IVoiceInteractionManagerService managerService,
|
||||
@@ -55,6 +61,7 @@ abstract class AbstractHotwordDetector implements HotwordDetector {
|
||||
mHandler = new Handler(Looper.getMainLooper());
|
||||
mCallback = callback;
|
||||
mDetectorType = detectorType;
|
||||
mIsDetectorActive = new AtomicBoolean(true);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -70,6 +77,7 @@ abstract class AbstractHotwordDetector implements HotwordDetector {
|
||||
if (DEBUG) {
|
||||
Slog.i(TAG, "#recognizeHotword");
|
||||
}
|
||||
throwIfDetectorIsNoLongerActive();
|
||||
|
||||
// TODO: consider closing existing session.
|
||||
|
||||
@@ -106,6 +114,7 @@ abstract class AbstractHotwordDetector implements HotwordDetector {
|
||||
if (DEBUG) {
|
||||
Slog.d(TAG, "updateState()");
|
||||
}
|
||||
throwIfDetectorIsNoLongerActive();
|
||||
synchronized (mLock) {
|
||||
updateStateLocked(options, sharedMemory, null /* callback */, mDetectorType);
|
||||
}
|
||||
@@ -126,6 +135,35 @@ abstract class AbstractHotwordDetector implements HotwordDetector {
|
||||
}
|
||||
}
|
||||
|
||||
void registerOnDestroyListener(Consumer<AbstractHotwordDetector> onDestroyListener) {
|
||||
synchronized (mLock) {
|
||||
if (mOnDestroyListener != null) {
|
||||
throw new IllegalStateException("only one destroy listener can be registered");
|
||||
}
|
||||
mOnDestroyListener = onDestroyListener;
|
||||
}
|
||||
}
|
||||
|
||||
@CallSuper
|
||||
@Override
|
||||
public void destroy() {
|
||||
if (!mIsDetectorActive.get()) {
|
||||
return;
|
||||
}
|
||||
mIsDetectorActive.set(false);
|
||||
synchronized (mLock) {
|
||||
mOnDestroyListener.accept(this);
|
||||
}
|
||||
}
|
||||
|
||||
protected void throwIfDetectorIsNoLongerActive() {
|
||||
if (!mIsDetectorActive.get()) {
|
||||
Slog.e(TAG, "attempting to use a destroyed detector which is no longer active");
|
||||
throw new IllegalStateException(
|
||||
"attempting to use a destroyed detector which is no longer active");
|
||||
}
|
||||
}
|
||||
|
||||
private static class BinderCallback
|
||||
extends IMicrophoneHotwordDetectionVoiceInteractionCallback.Stub {
|
||||
private final Handler mHandler;
|
||||
@@ -146,7 +184,10 @@ abstract class AbstractHotwordDetector implements HotwordDetector {
|
||||
mHandler.sendMessage(obtainMessage(
|
||||
HotwordDetector.Callback::onDetected,
|
||||
mCallback,
|
||||
new AlwaysOnHotwordDetector.EventPayload(audioFormat, hotwordDetectedResult)));
|
||||
new AlwaysOnHotwordDetector.EventPayload.Builder()
|
||||
.setCaptureAudioFormat(audioFormat)
|
||||
.setHotwordDetectedResult(hotwordDetectedResult)
|
||||
.build()));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -23,6 +23,7 @@ import android.annotation.IntDef;
|
||||
import android.annotation.NonNull;
|
||||
import android.annotation.Nullable;
|
||||
import android.annotation.RequiresPermission;
|
||||
import android.annotation.SuppressLint;
|
||||
import android.annotation.SystemApi;
|
||||
import android.annotation.TestApi;
|
||||
import android.app.ActivityThread;
|
||||
@@ -59,6 +60,9 @@ import com.android.internal.app.IVoiceInteractionSoundTriggerSession;
|
||||
import java.io.PrintWriter;
|
||||
import java.lang.annotation.Retention;
|
||||
import java.lang.annotation.RetentionPolicy;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.Locale;
|
||||
|
||||
/**
|
||||
@@ -336,7 +340,39 @@ public class AlwaysOnHotwordDetector extends AbstractHotwordDetector {
|
||||
* Additional payload for {@link Callback#onDetected}.
|
||||
*/
|
||||
public static class EventPayload {
|
||||
private final boolean mTriggerAvailable;
|
||||
|
||||
/**
|
||||
* Flags for describing the data format provided in the event payload.
|
||||
*
|
||||
* @hide
|
||||
*/
|
||||
@Retention(RetentionPolicy.SOURCE)
|
||||
@IntDef(prefix = {"DATA_FORMAT_"}, value = {
|
||||
DATA_FORMAT_RAW,
|
||||
DATA_FORMAT_TRIGGER_AUDIO,
|
||||
})
|
||||
public @interface DataFormat {
|
||||
}
|
||||
|
||||
/**
|
||||
* Data format is not strictly defined by the framework, and the
|
||||
* {@link android.hardware.soundtrigger.SoundTriggerModule} voice engine may populate this
|
||||
* field in any format.
|
||||
*/
|
||||
public static final int DATA_FORMAT_RAW = 0;
|
||||
|
||||
/**
|
||||
* Data format is defined as trigger audio.
|
||||
*
|
||||
* <p>When this format is used, {@link #getCaptureAudioFormat()} can be used to understand
|
||||
* further the audio format for reading the data.
|
||||
*
|
||||
* @see AlwaysOnHotwordDetector#RECOGNITION_FLAG_CAPTURE_TRIGGER_AUDIO
|
||||
*/
|
||||
public static final int DATA_FORMAT_TRIGGER_AUDIO = 1;
|
||||
|
||||
@DataFormat
|
||||
private final int mDataFormat;
|
||||
// Indicates if {@code captureSession} can be used to continue capturing more audio
|
||||
// from the DSP hardware.
|
||||
private final boolean mCaptureAvailable;
|
||||
@@ -348,40 +384,24 @@ public class AlwaysOnHotwordDetector extends AbstractHotwordDetector {
|
||||
private final byte[] mData;
|
||||
private final HotwordDetectedResult mHotwordDetectedResult;
|
||||
private final ParcelFileDescriptor mAudioStream;
|
||||
private final List<KeyphraseRecognitionExtra> mKephraseExtras;
|
||||
|
||||
EventPayload(boolean triggerAvailable, boolean captureAvailable,
|
||||
AudioFormat audioFormat, int captureSession, byte[] data) {
|
||||
this(triggerAvailable, captureAvailable, audioFormat, captureSession, data, null,
|
||||
null);
|
||||
}
|
||||
|
||||
EventPayload(boolean triggerAvailable, boolean captureAvailable,
|
||||
AudioFormat audioFormat, int captureSession, byte[] data,
|
||||
HotwordDetectedResult hotwordDetectedResult) {
|
||||
this(triggerAvailable, captureAvailable, audioFormat, captureSession, data,
|
||||
hotwordDetectedResult, null);
|
||||
}
|
||||
|
||||
EventPayload(AudioFormat audioFormat, HotwordDetectedResult hotwordDetectedResult) {
|
||||
this(false, false, audioFormat, -1, null, hotwordDetectedResult, null);
|
||||
}
|
||||
|
||||
EventPayload(AudioFormat audioFormat,
|
||||
HotwordDetectedResult hotwordDetectedResult,
|
||||
ParcelFileDescriptor audioStream) {
|
||||
this(false, false, audioFormat, -1, null, hotwordDetectedResult, audioStream);
|
||||
}
|
||||
|
||||
private EventPayload(boolean triggerAvailable, boolean captureAvailable,
|
||||
AudioFormat audioFormat, int captureSession, byte[] data,
|
||||
HotwordDetectedResult hotwordDetectedResult, ParcelFileDescriptor audioStream) {
|
||||
mTriggerAvailable = triggerAvailable;
|
||||
private EventPayload(boolean captureAvailable,
|
||||
@Nullable AudioFormat audioFormat,
|
||||
int captureSession,
|
||||
@DataFormat int dataFormat,
|
||||
@Nullable byte[] data,
|
||||
@Nullable HotwordDetectedResult hotwordDetectedResult,
|
||||
@Nullable ParcelFileDescriptor audioStream,
|
||||
@NonNull List<KeyphraseRecognitionExtra> keyphraseExtras) {
|
||||
mCaptureAvailable = captureAvailable;
|
||||
mCaptureSession = captureSession;
|
||||
mAudioFormat = audioFormat;
|
||||
mDataFormat = dataFormat;
|
||||
mData = data;
|
||||
mHotwordDetectedResult = hotwordDetectedResult;
|
||||
mAudioStream = audioStream;
|
||||
mKephraseExtras = keyphraseExtras;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -400,16 +420,48 @@ public class AlwaysOnHotwordDetector extends AbstractHotwordDetector {
|
||||
* {@link #getCaptureAudioFormat()}.
|
||||
*
|
||||
* @see AlwaysOnHotwordDetector#RECOGNITION_FLAG_CAPTURE_TRIGGER_AUDIO
|
||||
* @deprecated Use {@link #getData()} instead.
|
||||
*/
|
||||
@Deprecated
|
||||
@Nullable
|
||||
public byte[] getTriggerAudio() {
|
||||
if (mTriggerAvailable) {
|
||||
if (mDataFormat == DATA_FORMAT_TRIGGER_AUDIO) {
|
||||
return mData;
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Conveys the format of the additional data that is triggered with the keyphrase event.
|
||||
*
|
||||
* @see AlwaysOnHotwordDetector#RECOGNITION_FLAG_CAPTURE_TRIGGER_AUDIO
|
||||
* @see DataFormat
|
||||
*/
|
||||
@DataFormat
|
||||
public int getDataFormat() {
|
||||
return mDataFormat;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets additional raw data that is triggered with the keyphrase event.
|
||||
*
|
||||
* <p>A {@link android.hardware.soundtrigger.SoundTriggerModule} may populate this
|
||||
* field with opaque data for use by system applications who know about voice
|
||||
* engine internals. Data may be null if the field is not populated by the
|
||||
* {@link android.hardware.soundtrigger.SoundTriggerModule}.
|
||||
*
|
||||
* <p>If {@link #getDataFormat()} is {@link #DATA_FORMAT_TRIGGER_AUDIO}, then the
|
||||
* entirety of this buffer is expected to be of the format from
|
||||
* {@link #getCaptureAudioFormat()}.
|
||||
*
|
||||
* @see AlwaysOnHotwordDetector#RECOGNITION_FLAG_CAPTURE_TRIGGER_AUDIO
|
||||
*/
|
||||
@Nullable
|
||||
public byte[] getData() {
|
||||
return mData;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the session ID to start a capture from the DSP.
|
||||
* This may be null if streaming capture isn't possible.
|
||||
@@ -464,6 +516,166 @@ public class AlwaysOnHotwordDetector extends AbstractHotwordDetector {
|
||||
public ParcelFileDescriptor getAudioStream() {
|
||||
return mAudioStream;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the keyphrases recognized by the voice engine with additional confidence
|
||||
* information
|
||||
*
|
||||
* @return List of keyphrase extras describing additional data for each keyphrase the voice
|
||||
* engine triggered on for this event. The ordering of the list is preserved based on what
|
||||
* the ordering provided by {@link android.hardware.soundtrigger.SoundTriggerModule}.
|
||||
*/
|
||||
@NonNull
|
||||
public List<KeyphraseRecognitionExtra> getKeyphraseRecognitionExtras() {
|
||||
return mKephraseExtras;
|
||||
}
|
||||
|
||||
/**
|
||||
* Builder class for {@link EventPayload} objects
|
||||
*
|
||||
* @hide
|
||||
*/
|
||||
@TestApi
|
||||
public static final class Builder {
|
||||
private boolean mCaptureAvailable = false;
|
||||
private int mCaptureSession = -1;
|
||||
private AudioFormat mAudioFormat = null;
|
||||
@DataFormat
|
||||
private int mDataFormat = DATA_FORMAT_RAW;
|
||||
private byte[] mData = null;
|
||||
private HotwordDetectedResult mHotwordDetectedResult = null;
|
||||
private ParcelFileDescriptor mAudioStream = null;
|
||||
private List<KeyphraseRecognitionExtra> mKeyphraseExtras = Collections.emptyList();
|
||||
|
||||
public Builder() {}
|
||||
|
||||
Builder(SoundTrigger.KeyphraseRecognitionEvent keyphraseRecognitionEvent) {
|
||||
setCaptureAvailable(keyphraseRecognitionEvent.isCaptureAvailable());
|
||||
setCaptureSession(keyphraseRecognitionEvent.getCaptureSession());
|
||||
if (keyphraseRecognitionEvent.getCaptureFormat() != null) {
|
||||
setCaptureAudioFormat(keyphraseRecognitionEvent.getCaptureFormat());
|
||||
}
|
||||
setDataFormat((keyphraseRecognitionEvent.triggerInData) ? DATA_FORMAT_TRIGGER_AUDIO
|
||||
: DATA_FORMAT_RAW);
|
||||
if (keyphraseRecognitionEvent.getData() != null) {
|
||||
setData(keyphraseRecognitionEvent.getData());
|
||||
}
|
||||
if (keyphraseRecognitionEvent.keyphraseExtras != null) {
|
||||
setKeyphraseRecognitionExtras(
|
||||
Arrays.asList(keyphraseRecognitionEvent.keyphraseExtras));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Indicates if {@code captureSession} can be used to continue capturing more audio from
|
||||
* the DSP hardware.
|
||||
*/
|
||||
@SuppressLint("MissingGetterMatchingBuilder")
|
||||
@NonNull
|
||||
public Builder setCaptureAvailable(boolean captureAvailable) {
|
||||
mCaptureAvailable = captureAvailable;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the session ID to start a capture from the DSP.
|
||||
*/
|
||||
@SuppressLint("MissingGetterMatchingBuilder")
|
||||
@NonNull
|
||||
public Builder setCaptureSession(int captureSession) {
|
||||
mCaptureSession = captureSession;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the format of the audio obtained using {@link #getTriggerAudio()}.
|
||||
*/
|
||||
@NonNull
|
||||
public Builder setCaptureAudioFormat(@NonNull AudioFormat audioFormat) {
|
||||
mAudioFormat = audioFormat;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Conveys the format of the additional data that is triggered with the keyphrase event.
|
||||
*
|
||||
* @see AlwaysOnHotwordDetector#RECOGNITION_FLAG_CAPTURE_TRIGGER_AUDIO
|
||||
* @see DataFormat
|
||||
*/
|
||||
@NonNull
|
||||
public Builder setDataFormat(@DataFormat int dataFormat) {
|
||||
mDataFormat = dataFormat;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets additional raw data that is triggered with the keyphrase event.
|
||||
*
|
||||
* <p>A {@link android.hardware.soundtrigger.SoundTriggerModule} may populate this
|
||||
* field with opaque data for use by system applications who know about voice
|
||||
* engine internals. Data may be null if the field is not populated by the
|
||||
* {@link android.hardware.soundtrigger.SoundTriggerModule}.
|
||||
*
|
||||
* <p>If {@link #getDataFormat()} is {@link #DATA_FORMAT_TRIGGER_AUDIO}, then the
|
||||
* entirety of this
|
||||
* buffer is expected to be of the format from {@link #getCaptureAudioFormat()}.
|
||||
*
|
||||
* @see AlwaysOnHotwordDetector#RECOGNITION_FLAG_CAPTURE_TRIGGER_AUDIO
|
||||
*/
|
||||
@NonNull
|
||||
public Builder setData(@NonNull byte[] data) {
|
||||
mData = data;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets {@link HotwordDetectedResult} associated with the hotword event, passed from
|
||||
* {@link HotwordDetectionService}.
|
||||
*/
|
||||
@NonNull
|
||||
public Builder setHotwordDetectedResult(
|
||||
@NonNull HotwordDetectedResult hotwordDetectedResult) {
|
||||
mHotwordDetectedResult = hotwordDetectedResult;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets a stream with bytes corresponding to the open audio stream with hotword data.
|
||||
*
|
||||
* <p>This data represents an audio stream in the format returned by
|
||||
* {@link #getCaptureAudioFormat}.
|
||||
*
|
||||
* <p>Clients are expected to start consuming the stream within 1 second of receiving
|
||||
* the
|
||||
* event.
|
||||
*/
|
||||
@NonNull
|
||||
public Builder setAudioStream(@NonNull ParcelFileDescriptor audioStream) {
|
||||
mAudioStream = audioStream;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the keyphrases recognized by the voice engine with additional confidence
|
||||
* information
|
||||
*/
|
||||
@NonNull
|
||||
public Builder setKeyphraseRecognitionExtras(
|
||||
@NonNull List<KeyphraseRecognitionExtra> keyphraseRecognitionExtras) {
|
||||
mKeyphraseExtras = keyphraseRecognitionExtras;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Builds an {@link EventPayload} instance
|
||||
*/
|
||||
@NonNull
|
||||
public EventPayload build() {
|
||||
return new EventPayload(mCaptureAvailable, mAudioFormat, mCaptureSession,
|
||||
mDataFormat, mData, mHotwordDetectedResult, mAudioStream,
|
||||
mKeyphraseExtras);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -993,11 +1205,14 @@ public class AlwaysOnHotwordDetector extends AbstractHotwordDetector {
|
||||
/**
|
||||
* Invalidates this hotword detector so that any future calls to this result
|
||||
* in an IllegalStateException.
|
||||
*
|
||||
* @hide
|
||||
*/
|
||||
void invalidate() {
|
||||
@Override
|
||||
public void destroy() {
|
||||
synchronized (mLock) {
|
||||
if (mAvailability == STATE_KEYPHRASE_ENROLLED) {
|
||||
stopRecognition();
|
||||
}
|
||||
|
||||
mAvailability = STATE_INVALID;
|
||||
notifyStateChangedLocked();
|
||||
|
||||
@@ -1009,6 +1224,7 @@ public class AlwaysOnHotwordDetector extends AbstractHotwordDetector {
|
||||
}
|
||||
}
|
||||
}
|
||||
super.destroy();
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -1171,8 +1387,9 @@ public class AlwaysOnHotwordDetector extends AbstractHotwordDetector {
|
||||
Slog.i(TAG, "onDetected");
|
||||
}
|
||||
Message.obtain(mHandler, MSG_HOTWORD_DETECTED,
|
||||
new EventPayload(event.triggerInData, event.captureAvailable,
|
||||
event.captureFormat, event.captureSession, event.data, result))
|
||||
new EventPayload.Builder(event)
|
||||
.setHotwordDetectedResult(result)
|
||||
.build())
|
||||
.sendToTarget();
|
||||
}
|
||||
@Override
|
||||
|
||||
@@ -140,9 +140,7 @@ public abstract class HotwordDetectionService extends Service {
|
||||
Log.d(TAG, "#detectFromDspSource");
|
||||
}
|
||||
HotwordDetectionService.this.onDetect(
|
||||
new AlwaysOnHotwordDetector.EventPayload(
|
||||
event.triggerInData, event.captureAvailable,
|
||||
event.captureFormat, event.captureSession, event.data),
|
||||
new AlwaysOnHotwordDetector.EventPayload.Builder(event).build(),
|
||||
timeoutMillis,
|
||||
new Callback(callback));
|
||||
}
|
||||
|
||||
@@ -118,6 +118,17 @@ public interface HotwordDetector {
|
||||
*/
|
||||
void updateState(@Nullable PersistableBundle options, @Nullable SharedMemory sharedMemory);
|
||||
|
||||
/**
|
||||
* Invalidates this hotword detector so that any future calls to this result
|
||||
* in an {@link IllegalStateException}.
|
||||
*
|
||||
* <p>If there are no other {@link HotwordDetector} instances linked to the
|
||||
* {@link HotwordDetectionService}, the service will be shutdown.
|
||||
*/
|
||||
default void destroy() {
|
||||
throw new UnsupportedOperationException("Not implemented. Must override in a subclass.");
|
||||
}
|
||||
|
||||
/**
|
||||
* @hide
|
||||
*/
|
||||
|
||||
@@ -77,7 +77,7 @@ class SoftwareHotwordDetector extends AbstractHotwordDetector {
|
||||
if (DEBUG) {
|
||||
Slog.i(TAG, "#startRecognition");
|
||||
}
|
||||
|
||||
throwIfDetectorIsNoLongerActive();
|
||||
maybeCloseExistingSession();
|
||||
|
||||
try {
|
||||
@@ -100,6 +100,7 @@ class SoftwareHotwordDetector extends AbstractHotwordDetector {
|
||||
if (DEBUG) {
|
||||
Slog.i(TAG, "#stopRecognition");
|
||||
}
|
||||
throwIfDetectorIsNoLongerActive();
|
||||
|
||||
try {
|
||||
mManagerService.stopListeningFromMic();
|
||||
@@ -110,6 +111,19 @@ class SoftwareHotwordDetector extends AbstractHotwordDetector {
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void destroy() {
|
||||
stopRecognition();
|
||||
maybeCloseExistingSession();
|
||||
|
||||
try {
|
||||
mManagerService.shutdownHotwordDetectionService();
|
||||
} catch (RemoteException ex) {
|
||||
ex.rethrowFromSystemServer();
|
||||
}
|
||||
super.destroy();
|
||||
}
|
||||
|
||||
private void maybeCloseExistingSession() {
|
||||
// TODO: needs to be synchronized.
|
||||
// TODO: implement this
|
||||
@@ -135,8 +149,11 @@ class SoftwareHotwordDetector extends AbstractHotwordDetector {
|
||||
mHandler.sendMessage(obtainMessage(
|
||||
HotwordDetector.Callback::onDetected,
|
||||
mCallback,
|
||||
new AlwaysOnHotwordDetector.EventPayload(
|
||||
audioFormat, hotwordDetectedResult, audioStream)));
|
||||
new AlwaysOnHotwordDetector.EventPayload.Builder()
|
||||
.setCaptureAudioFormat(audioFormat)
|
||||
.setAudioStream(audioStream)
|
||||
.setHotwordDetectedResult(hotwordDetectedResult)
|
||||
.build()));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -271,7 +271,7 @@ public class VoiceInteractionService extends Service {
|
||||
// It's still guaranteed to have been stopped.
|
||||
// This helps with cases where the voice interaction implementation is changed
|
||||
// by the user.
|
||||
safelyShutdownHotwordDetector();
|
||||
safelyShutdownAllHotwordDetectors();
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -380,11 +380,13 @@ public class VoiceInteractionService extends Service {
|
||||
}
|
||||
synchronized (mLock) {
|
||||
// Allow only one concurrent recognition via the APIs.
|
||||
safelyShutdownHotwordDetector();
|
||||
safelyShutdownAllHotwordDetectors();
|
||||
mHotwordDetector = new AlwaysOnHotwordDetector(keyphrase, locale, callback,
|
||||
mKeyphraseEnrollmentInfo, mSystemService,
|
||||
getApplicationContext().getApplicationInfo().targetSdkVersion,
|
||||
supportHotwordDetectionService, options, sharedMemory);
|
||||
mHotwordDetector.registerOnDestroyListener((detector) -> onDspHotwordDetectorDestroyed(
|
||||
(AlwaysOnHotwordDetector) detector));
|
||||
}
|
||||
return mHotwordDetector;
|
||||
}
|
||||
@@ -433,10 +435,13 @@ public class VoiceInteractionService extends Service {
|
||||
}
|
||||
synchronized (mLock) {
|
||||
// Allow only one concurrent recognition via the APIs.
|
||||
safelyShutdownHotwordDetector();
|
||||
safelyShutdownAllHotwordDetectors();
|
||||
mSoftwareHotwordDetector =
|
||||
new SoftwareHotwordDetector(
|
||||
mSystemService, null, options, sharedMemory, callback);
|
||||
mSoftwareHotwordDetector.registerOnDestroyListener(
|
||||
(detector) -> onMicrophoneHotwordDetectorDestroyed(
|
||||
(SoftwareHotwordDetector) detector));
|
||||
}
|
||||
return mSoftwareHotwordDetector;
|
||||
}
|
||||
@@ -482,51 +487,36 @@ public class VoiceInteractionService extends Service {
|
||||
return mKeyphraseEnrollmentInfo.getKeyphraseMetadata(keyphrase, locale) != null;
|
||||
}
|
||||
|
||||
private void safelyShutdownHotwordDetector() {
|
||||
private void safelyShutdownAllHotwordDetectors() {
|
||||
synchronized (mLock) {
|
||||
shutdownDspHotwordDetectorLocked();
|
||||
shutdownMicrophoneHotwordDetectorLocked();
|
||||
if (mHotwordDetector != null) {
|
||||
try {
|
||||
mHotwordDetector.destroy();
|
||||
} catch (Exception ex) {
|
||||
Log.i(TAG, "exception destroying AlwaysOnHotwordDetector", ex);
|
||||
}
|
||||
}
|
||||
|
||||
if (mSoftwareHotwordDetector != null) {
|
||||
try {
|
||||
mSoftwareHotwordDetector.destroy();
|
||||
} catch (Exception ex) {
|
||||
Log.i(TAG, "exception destroying SoftwareHotwordDetector", ex);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void shutdownDspHotwordDetectorLocked() {
|
||||
if (mHotwordDetector == null) {
|
||||
return;
|
||||
private void onDspHotwordDetectorDestroyed(@NonNull AlwaysOnHotwordDetector detector) {
|
||||
synchronized (mLock) {
|
||||
mHotwordDetector = null;
|
||||
}
|
||||
|
||||
try {
|
||||
mHotwordDetector.stopRecognition();
|
||||
} catch (Exception ex) {
|
||||
// Ignore.
|
||||
}
|
||||
|
||||
try {
|
||||
mHotwordDetector.invalidate();
|
||||
} catch (Exception ex) {
|
||||
// Ignore.
|
||||
}
|
||||
|
||||
mHotwordDetector = null;
|
||||
}
|
||||
|
||||
private void shutdownMicrophoneHotwordDetectorLocked() {
|
||||
if (mSoftwareHotwordDetector == null) {
|
||||
return;
|
||||
private void onMicrophoneHotwordDetectorDestroyed(@NonNull SoftwareHotwordDetector detector) {
|
||||
synchronized (mLock) {
|
||||
mSoftwareHotwordDetector = null;
|
||||
}
|
||||
|
||||
try {
|
||||
mSoftwareHotwordDetector.stopRecognition();
|
||||
} catch (Exception ex) {
|
||||
// Ignore.
|
||||
}
|
||||
|
||||
try {
|
||||
mSystemService.shutdownHotwordDetectionService();
|
||||
} catch (Exception ex) {
|
||||
// Ignore.
|
||||
}
|
||||
|
||||
mSoftwareHotwordDetector = null;
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
Reference in New Issue
Block a user