summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorYtai Ben-tsvi <ytai@google.com>2019-12-17 22:33:55 +0000
committerAndroid (Google) Code Review <android-gerrit@google.com>2019-12-17 22:33:55 +0000
commit0d52f4202ca7ec1f06fcd5b0b3661130aae29332 (patch)
treee2802502e60a44e5cb2cfa488cc65183725fa908
parent1c66473972bdc9ee54219742ba10b962277f4aff (diff)
parent7d383d1a98957d46aa09ac64091e4eae73b4c662 (diff)
Merge changes from topic "soundtrigger_refactor"
* changes: Migrate SoundTrigger implementation to new service Implement the soundtrigger_middlewware service Add a permission for preempting sound trigger sessions Sound trigger middleware service definition Add audio.common types AIDL definition
-rw-r--r--core/java/android/content/Context.java9
-rw-r--r--core/java/android/hardware/soundtrigger/ConversionUtil.java329
-rw-r--r--core/java/android/hardware/soundtrigger/SoundTrigger.java176
-rw-r--r--core/java/android/hardware/soundtrigger/SoundTriggerModule.java304
-rw-r--r--core/jni/Android.bp2
-rw-r--r--core/jni/AndroidRuntime.cpp2
-rw-r--r--core/jni/android_hardware_SoundTrigger.cpp1071
-rw-r--r--core/res/AndroidManifest.xml6
-rw-r--r--data/etc/platform.xml1
-rw-r--r--media/Android.bp64
-rw-r--r--media/java/android/media/audio/common/AudioChannelMask.aidl217
-rw-r--r--media/java/android/media/audio/common/AudioConfig.aidl36
-rw-r--r--media/java/android/media/audio/common/AudioFormat.aidl170
-rw-r--r--media/java/android/media/audio/common/AudioOffloadInfo.aidl44
-rw-r--r--media/java/android/media/audio/common/AudioStreamType.aidl44
-rw-r--r--media/java/android/media/audio/common/AudioUsage.aidl40
-rw-r--r--media/java/android/media/soundtrigger_middleware/ConfidenceLevel.aidl35
-rw-r--r--media/java/android/media/soundtrigger_middleware/ISoundTriggerCallback.aidl47
-rw-r--r--media/java/android/media/soundtrigger_middleware/ISoundTriggerMiddlewareService.aidl48
-rw-r--r--media/java/android/media/soundtrigger_middleware/ISoundTriggerModule.aidl148
-rw-r--r--media/java/android/media/soundtrigger_middleware/ModelParameter.aidl38
-rw-r--r--media/java/android/media/soundtrigger_middleware/ModelParameterRange.aidl28
-rw-r--r--media/java/android/media/soundtrigger_middleware/Phrase.aidl34
-rw-r--r--media/java/android/media/soundtrigger_middleware/PhraseRecognitionEvent.aidl31
-rw-r--r--media/java/android/media/soundtrigger_middleware/PhraseRecognitionExtra.aidl35
-rw-r--r--media/java/android/media/soundtrigger_middleware/PhraseSoundModel.aidl32
-rw-r--r--media/java/android/media/soundtrigger_middleware/RecognitionConfig.aidl33
-rw-r--r--media/java/android/media/soundtrigger_middleware/RecognitionEvent.aidl51
-rw-r--r--media/java/android/media/soundtrigger_middleware/RecognitionMode.aidl32
-rw-r--r--media/java/android/media/soundtrigger_middleware/RecognitionStatus.aidl35
-rw-r--r--media/java/android/media/soundtrigger_middleware/SoundModel.aidl36
-rw-r--r--media/java/android/media/soundtrigger_middleware/SoundModelType.aidl30
-rw-r--r--media/java/android/media/soundtrigger_middleware/SoundTriggerModuleDescriptor.aidl31
-rw-r--r--media/java/android/media/soundtrigger_middleware/SoundTriggerModuleProperties.aidl53
-rw-r--r--media/java/android/media/soundtrigger_middleware/Status.aidl29
-rw-r--r--services/core/Android.bp1
-rw-r--r--services/core/java/com/android/server/soundtrigger_middleware/AudioSessionProviderImpl.java29
-rw-r--r--services/core/java/com/android/server/soundtrigger_middleware/ConversionUtil.java390
-rw-r--r--services/core/java/com/android/server/soundtrigger_middleware/HalException.java49
-rw-r--r--services/core/java/com/android/server/soundtrigger_middleware/Hw2CompatUtil.java77
-rw-r--r--services/core/java/com/android/server/soundtrigger_middleware/ISoundTriggerHw2.java158
-rw-r--r--services/core/java/com/android/server/soundtrigger_middleware/InternalServerError.java40
-rw-r--r--services/core/java/com/android/server/soundtrigger_middleware/RecoverableException.java53
-rw-r--r--services/core/java/com/android/server/soundtrigger_middleware/SoundTriggerHw2Compat.java470
-rw-r--r--services/core/java/com/android/server/soundtrigger_middleware/SoundTriggerMiddlewareImpl.java140
-rw-r--r--services/core/java/com/android/server/soundtrigger_middleware/SoundTriggerMiddlewareService.java709
-rw-r--r--services/core/java/com/android/server/soundtrigger_middleware/SoundTriggerModule.java545
-rw-r--r--services/core/java/com/android/server/soundtrigger_middleware/TEST_MAPPING12
-rw-r--r--services/core/java/com/android/server/soundtrigger_middleware/UuidUtil.java44
-rw-r--r--services/core/java/com/android/server/soundtrigger_middleware/ValidationUtil.java117
-rw-r--r--services/core/jni/Android.bp1
-rw-r--r--services/core/jni/com_android_server_soundtrigger_middleware_AudioSessionProviderImpl.cpp91
-rw-r--r--services/core/jni/onload.cpp4
-rw-r--r--services/java/com/android/server/SystemServer.java5
-rw-r--r--services/tests/servicestests/src/com/android/server/soundtrigger_middleware/ConversionUtilTest.java47
-rw-r--r--services/tests/servicestests/src/com/android/server/soundtrigger_middleware/SoundTriggerMiddlewareImplTest.java1306
56 files changed, 6374 insertions, 1235 deletions
diff --git a/core/java/android/content/Context.java b/core/java/android/content/Context.java
index 808f2160109d..d0844491da53 100644
--- a/core/java/android/content/Context.java
+++ b/core/java/android/content/Context.java
@@ -4336,6 +4336,15 @@ public abstract class Context {
public static final String SOUND_TRIGGER_SERVICE = "soundtrigger";
/**
+ * Use with {@link #getSystemService(String)} to access the
+ * {@link com.android.server.soundtrigger_middleware.SoundTriggerMiddlewareService}.
+ *
+ * @hide
+ * @see #getSystemService(String)
+ */
+ public static final String SOUND_TRIGGER_MIDDLEWARE_SERVICE = "soundtrigger_middleware";
+
+ /**
* Official published name of the (internal) permission service.
*
* @see #getSystemService(String)
diff --git a/core/java/android/hardware/soundtrigger/ConversionUtil.java b/core/java/android/hardware/soundtrigger/ConversionUtil.java
new file mode 100644
index 000000000000..8231c58a105e
--- /dev/null
+++ b/core/java/android/hardware/soundtrigger/ConversionUtil.java
@@ -0,0 +1,329 @@
+/*
+ * Copyright (C) 2019 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.hardware.soundtrigger;
+
+import android.hardware.soundtrigger.ModelParams;
+import android.media.AudioFormat;
+import android.media.audio.common.AudioConfig;
+import android.media.soundtrigger_middleware.ConfidenceLevel;
+import android.media.soundtrigger_middleware.ModelParameterRange;
+import android.media.soundtrigger_middleware.Phrase;
+import android.media.soundtrigger_middleware.PhraseRecognitionEvent;
+import android.media.soundtrigger_middleware.PhraseRecognitionExtra;
+import android.media.soundtrigger_middleware.PhraseSoundModel;
+import android.media.soundtrigger_middleware.RecognitionConfig;
+import android.media.soundtrigger_middleware.RecognitionEvent;
+import android.media.soundtrigger_middleware.RecognitionMode;
+import android.media.soundtrigger_middleware.SoundModel;
+import android.media.soundtrigger_middleware.SoundTriggerModuleDescriptor;
+import android.media.soundtrigger_middleware.SoundTriggerModuleProperties;
+
+import android.annotation.Nullable;
+
+import java.util.Arrays;
+import java.util.UUID;
+
+/** @hide */
+class ConversionUtil {
+ public static SoundTrigger.ModuleProperties aidl2apiModuleDescriptor(
+ SoundTriggerModuleDescriptor aidlDesc) {
+ SoundTriggerModuleProperties properties = aidlDesc.properties;
+ return new SoundTrigger.ModuleProperties(
+ aidlDesc.handle,
+ properties.implementor,
+ properties.description,
+ properties.uuid,
+ properties.version,
+ properties.maxSoundModels,
+ properties.maxKeyPhrases,
+ properties.maxUsers,
+ aidl2apiRecognitionModes(properties.recognitionModes),
+ properties.captureTransition,
+ properties.maxBufferMs,
+ properties.concurrentCapture,
+ properties.powerConsumptionMw,
+ properties.triggerInEvent
+ );
+ }
+
+ public static int aidl2apiRecognitionModes(int aidlModes) {
+ int result = 0;
+ if ((aidlModes & RecognitionMode.VOICE_TRIGGER) != 0) {
+ result |= SoundTrigger.RECOGNITION_MODE_VOICE_TRIGGER;
+ }
+ if ((aidlModes & RecognitionMode.USER_IDENTIFICATION) != 0) {
+ result |= SoundTrigger.RECOGNITION_MODE_USER_IDENTIFICATION;
+ }
+ if ((aidlModes & RecognitionMode.USER_AUTHENTICATION) != 0) {
+ result |= SoundTrigger.RECOGNITION_MODE_USER_AUTHENTICATION;
+ }
+ if ((aidlModes & RecognitionMode.GENERIC_TRIGGER) != 0) {
+ result |= SoundTrigger.RECOGNITION_MODE_GENERIC;
+ }
+ return result;
+ }
+
+ public static int api2aidlRecognitionModes(int apiModes) {
+ int result = 0;
+ if ((apiModes & SoundTrigger.RECOGNITION_MODE_VOICE_TRIGGER) != 0) {
+ result |= RecognitionMode.VOICE_TRIGGER;
+ }
+ if ((apiModes & SoundTrigger.RECOGNITION_MODE_USER_IDENTIFICATION) != 0) {
+ result |= RecognitionMode.USER_IDENTIFICATION;
+ }
+ if ((apiModes & SoundTrigger.RECOGNITION_MODE_USER_AUTHENTICATION) != 0) {
+ result |= RecognitionMode.USER_AUTHENTICATION;
+ }
+ if ((apiModes & SoundTrigger.RECOGNITION_MODE_GENERIC) != 0) {
+ result |= RecognitionMode.GENERIC_TRIGGER;
+ }
+ return result;
+ }
+
+
+ public static SoundModel api2aidlGenericSoundModel(SoundTrigger.GenericSoundModel apiModel) {
+ return api2aidlSoundModel(apiModel);
+ }
+
+ public static SoundModel api2aidlSoundModel(SoundTrigger.SoundModel apiModel) {
+ SoundModel aidlModel = new SoundModel();
+ aidlModel.type = apiModel.type;
+ aidlModel.uuid = api2aidlUuid(apiModel.uuid);
+ aidlModel.vendorUuid = api2aidlUuid(apiModel.vendorUuid);
+ aidlModel.data = Arrays.copyOf(apiModel.data, apiModel.data.length);
+ return aidlModel;
+ }
+
+ public static String api2aidlUuid(UUID apiUuid) {
+ return apiUuid.toString();
+ }
+
+ public static PhraseSoundModel api2aidlPhraseSoundModel(
+ SoundTrigger.KeyphraseSoundModel apiModel) {
+ PhraseSoundModel aidlModel = new PhraseSoundModel();
+ aidlModel.common = api2aidlSoundModel(apiModel);
+ aidlModel.phrases = new Phrase[apiModel.keyphrases.length];
+ for (int i = 0; i < apiModel.keyphrases.length; ++i) {
+ aidlModel.phrases[i] = api2aidlPhrase(apiModel.keyphrases[i]);
+ }
+ return aidlModel;
+ }
+
+ public static Phrase api2aidlPhrase(SoundTrigger.Keyphrase apiPhrase) {
+ Phrase aidlPhrase = new Phrase();
+ aidlPhrase.id = apiPhrase.id;
+ aidlPhrase.recognitionModes = api2aidlRecognitionModes(apiPhrase.recognitionModes);
+ aidlPhrase.users = Arrays.copyOf(apiPhrase.users, apiPhrase.users.length);
+ aidlPhrase.locale = apiPhrase.locale;
+ aidlPhrase.text = apiPhrase.text;
+ return aidlPhrase;
+ }
+
+ public static RecognitionConfig api2aidlRecognitionConfig(
+ SoundTrigger.RecognitionConfig apiConfig) {
+ RecognitionConfig aidlConfig = new RecognitionConfig();
+ aidlConfig.captureRequested = apiConfig.captureRequested;
+ // apiConfig.allowMultipleTriggers is ignored by the lower layers.
+ aidlConfig.phraseRecognitionExtras =
+ new PhraseRecognitionExtra[apiConfig.keyphrases.length];
+ for (int i = 0; i < apiConfig.keyphrases.length; ++i) {
+ aidlConfig.phraseRecognitionExtras[i] = api2aidlPhraseRecognitionExtra(
+ apiConfig.keyphrases[i]);
+ }
+ aidlConfig.data = Arrays.copyOf(apiConfig.data, apiConfig.data.length);
+ return aidlConfig;
+ }
+
+ public static PhraseRecognitionExtra api2aidlPhraseRecognitionExtra(
+ SoundTrigger.KeyphraseRecognitionExtra apiExtra) {
+ PhraseRecognitionExtra aidlExtra = new PhraseRecognitionExtra();
+ aidlExtra.id = apiExtra.id;
+ aidlExtra.recognitionModes = api2aidlRecognitionModes(apiExtra.recognitionModes);
+ aidlExtra.confidenceLevel = apiExtra.coarseConfidenceLevel;
+ aidlExtra.levels = new ConfidenceLevel[apiExtra.confidenceLevels.length];
+ for (int i = 0; i < apiExtra.confidenceLevels.length; ++i) {
+ aidlExtra.levels[i] = api2aidlConfidenceLevel(apiExtra.confidenceLevels[i]);
+ }
+ return aidlExtra;
+ }
+
+ public static SoundTrigger.KeyphraseRecognitionExtra aidl2apiPhraseRecognitionExtra(
+ PhraseRecognitionExtra aidlExtra) {
+ SoundTrigger.ConfidenceLevel[] apiLevels =
+ new SoundTrigger.ConfidenceLevel[aidlExtra.levels.length];
+ for (int i = 0; i < aidlExtra.levels.length; ++i) {
+ apiLevels[i] = aidl2apiConfidenceLevel(aidlExtra.levels[i]);
+ }
+ return new SoundTrigger.KeyphraseRecognitionExtra(aidlExtra.id,
+ aidl2apiRecognitionModes(aidlExtra.recognitionModes),
+ aidlExtra.confidenceLevel, apiLevels);
+ }
+
+ public static ConfidenceLevel api2aidlConfidenceLevel(
+ SoundTrigger.ConfidenceLevel apiLevel) {
+ ConfidenceLevel aidlLevel = new ConfidenceLevel();
+ aidlLevel.levelPercent = apiLevel.confidenceLevel;
+ aidlLevel.userId = apiLevel.userId;
+ return aidlLevel;
+ }
+
+ public static SoundTrigger.ConfidenceLevel aidl2apiConfidenceLevel(
+ ConfidenceLevel apiLevel) {
+ return new SoundTrigger.ConfidenceLevel(apiLevel.userId, apiLevel.levelPercent);
+ }
+
+ public static SoundTrigger.RecognitionEvent aidl2apiRecognitionEvent(
+ int modelHandle, RecognitionEvent aidlEvent) {
+ return new SoundTrigger.GenericRecognitionEvent(
+ aidlEvent.status,
+ modelHandle, aidlEvent.captureAvailable, aidlEvent.captureSession,
+ aidlEvent.captureDelayMs, aidlEvent.capturePreambleMs, aidlEvent.triggerInData,
+ aidl2apiAudioFormat(aidlEvent.audioConfig), aidlEvent.data);
+ }
+
+ public static SoundTrigger.RecognitionEvent aidl2apiPhraseRecognitionEvent(
+ int modelHandle,
+ PhraseRecognitionEvent aidlEvent) {
+ SoundTrigger.KeyphraseRecognitionExtra[] apiExtras =
+ new SoundTrigger.KeyphraseRecognitionExtra[aidlEvent.phraseExtras.length];
+ for (int i = 0; i < aidlEvent.phraseExtras.length; ++i) {
+ apiExtras[i] = aidl2apiPhraseRecognitionExtra(aidlEvent.phraseExtras[i]);
+ }
+ return new SoundTrigger.KeyphraseRecognitionEvent(aidlEvent.common.status, modelHandle,
+ aidlEvent.common.captureAvailable,
+ aidlEvent.common.captureSession, aidlEvent.common.captureDelayMs,
+ aidlEvent.common.capturePreambleMs, aidlEvent.common.triggerInData,
+ aidl2apiAudioFormat(aidlEvent.common.audioConfig), aidlEvent.common.data,
+ apiExtras);
+ }
+
+ public static AudioFormat aidl2apiAudioFormat(AudioConfig audioConfig) {
+ AudioFormat.Builder apiBuilder = new AudioFormat.Builder();
+ apiBuilder.setSampleRate(audioConfig.sampleRateHz);
+ apiBuilder.setChannelMask(aidl2apiChannelInMask(audioConfig.channelMask));
+ apiBuilder.setEncoding(aidl2apiEncoding(audioConfig.format));
+ return apiBuilder.build();
+ }
+
+ public static int aidl2apiEncoding(int aidlFormat) {
+ switch (aidlFormat) {
+ case android.media.audio.common.AudioFormat.PCM
+ | android.media.audio.common.AudioFormat.PCM_SUB_16_BIT:
+ return AudioFormat.ENCODING_PCM_16BIT;
+
+ case android.media.audio.common.AudioFormat.PCM
+ | android.media.audio.common.AudioFormat.PCM_SUB_8_BIT:
+ return AudioFormat.ENCODING_PCM_8BIT;
+
+ case android.media.audio.common.AudioFormat.PCM
+ | android.media.audio.common.AudioFormat.PCM_SUB_FLOAT:
+ case android.media.audio.common.AudioFormat.PCM
+ | android.media.audio.common.AudioFormat.PCM_SUB_8_24_BIT:
+ case android.media.audio.common.AudioFormat.PCM
+ | android.media.audio.common.AudioFormat.PCM_SUB_24_BIT_PACKED:
+ case android.media.audio.common.AudioFormat.PCM
+ | android.media.audio.common.AudioFormat.PCM_SUB_32_BIT:
+ return AudioFormat.ENCODING_PCM_FLOAT;
+
+ case android.media.audio.common.AudioFormat.AC3:
+ return AudioFormat.ENCODING_AC3;
+
+ case android.media.audio.common.AudioFormat.E_AC3:
+ return AudioFormat.ENCODING_E_AC3;
+
+ case android.media.audio.common.AudioFormat.DTS:
+ return AudioFormat.ENCODING_DTS;
+
+ case android.media.audio.common.AudioFormat.DTS_HD:
+ return AudioFormat.ENCODING_DTS_HD;
+
+ case android.media.audio.common.AudioFormat.MP3:
+ return AudioFormat.ENCODING_MP3;
+
+ case android.media.audio.common.AudioFormat.AAC
+ | android.media.audio.common.AudioFormat.AAC_SUB_LC:
+ return AudioFormat.ENCODING_AAC_LC;
+
+ case android.media.audio.common.AudioFormat.AAC
+ | android.media.audio.common.AudioFormat.AAC_SUB_HE_V1:
+ return AudioFormat.ENCODING_AAC_HE_V1;
+
+ case android.media.audio.common.AudioFormat.AAC
+ | android.media.audio.common.AudioFormat.AAC_SUB_HE_V2:
+ return AudioFormat.ENCODING_AAC_HE_V2;
+
+ case android.media.audio.common.AudioFormat.IEC61937:
+ return AudioFormat.ENCODING_IEC61937;
+
+ case android.media.audio.common.AudioFormat.DOLBY_TRUEHD:
+ return AudioFormat.ENCODING_DOLBY_TRUEHD;
+
+ case android.media.audio.common.AudioFormat.AAC
+ | android.media.audio.common.AudioFormat.AAC_SUB_ELD:
+ return AudioFormat.ENCODING_AAC_ELD;
+
+ case android.media.audio.common.AudioFormat.AAC
+ | android.media.audio.common.AudioFormat.AAC_SUB_XHE:
+ return AudioFormat.ENCODING_AAC_XHE;
+
+ case android.media.audio.common.AudioFormat.AC4:
+ return AudioFormat.ENCODING_AC4;
+
+ case android.media.audio.common.AudioFormat.E_AC3
+ | android.media.audio.common.AudioFormat.E_AC3_SUB_JOC:
+ return AudioFormat.ENCODING_E_AC3_JOC;
+
+ case android.media.audio.common.AudioFormat.MAT:
+ case android.media.audio.common.AudioFormat.MAT
+ | android.media.audio.common.AudioFormat.MAT_SUB_1_0:
+ case android.media.audio.common.AudioFormat.MAT
+ | android.media.audio.common.AudioFormat.MAT_SUB_2_0:
+ case android.media.audio.common.AudioFormat.MAT
+ | android.media.audio.common.AudioFormat.MAT_SUB_2_1:
+ return AudioFormat.ENCODING_DOLBY_MAT;
+
+ case android.media.audio.common.AudioFormat.DEFAULT:
+ return AudioFormat.ENCODING_DEFAULT;
+
+ default:
+ return AudioFormat.ENCODING_INVALID;
+ }
+ }
+
+ public static int api2aidlModelParameter(int apiParam) {
+ switch (apiParam) {
+ case ModelParams.THRESHOLD_FACTOR:
+ return android.media.soundtrigger_middleware.ModelParameter.THRESHOLD_FACTOR;
+ default:
+ return android.media.soundtrigger_middleware.ModelParameter.INVALID;
+ }
+ }
+
+ public static int aidl2apiChannelInMask(int aidlMask) {
+ // We're assuming AudioFormat.CHANNEL_IN_* constants are kept in sync with
+ // android.media.audio.common.AudioChannelMask.
+ return aidlMask;
+ }
+
+ public static SoundTrigger.ModelParamRange aidl2apiModelParameterRange(
+ @Nullable ModelParameterRange aidlRange) {
+ if (aidlRange == null) {
+ return null;
+ }
+ return new SoundTrigger.ModelParamRange(aidlRange.minInclusive, aidlRange.maxInclusive);
+ }
+}
diff --git a/core/java/android/hardware/soundtrigger/SoundTrigger.java b/core/java/android/hardware/soundtrigger/SoundTrigger.java
index 86f3eec4109e..5484df416e79 100644
--- a/core/java/android/hardware/soundtrigger/SoundTrigger.java
+++ b/core/java/android/hardware/soundtrigger/SoundTrigger.java
@@ -22,18 +22,29 @@ import static android.system.OsConstants.ENOSYS;
import static android.system.OsConstants.EPERM;
import static android.system.OsConstants.EPIPE;
+import static java.util.Objects.requireNonNull;
+
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.annotation.SystemApi;
import android.annotation.UnsupportedAppUsage;
import android.app.ActivityThread;
+import android.content.Context;
import android.media.AudioFormat;
+import android.media.soundtrigger_middleware.ISoundTriggerMiddlewareService;
+import android.media.soundtrigger_middleware.SoundTriggerModuleDescriptor;
import android.os.Handler;
+import android.os.IBinder;
+import android.os.Looper;
import android.os.Parcel;
import android.os.Parcelable;
+import android.os.RemoteException;
+import android.os.ServiceManager;
+import android.util.Log;
import java.util.ArrayList;
import java.util.Arrays;
+import java.util.List;
import java.util.UUID;
/**
@@ -44,6 +55,7 @@ import java.util.UUID;
*/
@SystemApi
public class SoundTrigger {
+ private static final String TAG = "SoundTrigger";
private SoundTrigger() {
}
@@ -119,15 +131,15 @@ public class SoundTrigger {
* recognition callback event */
public final boolean returnsTriggerInEvent;
- ModuleProperties(int id, String implementor, String description,
- String uuid, int version, int maxSoundModels, int maxKeyphrases,
+ ModuleProperties(int id, @NonNull String implementor, @NonNull String description,
+ @NonNull String uuid, int version, int maxSoundModels, int maxKeyphrases,
int maxUsers, int recognitionModes, boolean supportsCaptureTransition,
int maxBufferMs, boolean supportsConcurrentCapture,
int powerConsumptionMw, boolean returnsTriggerInEvent) {
this.id = id;
- this.implementor = implementor;
- this.description = description;
- this.uuid = UUID.fromString(uuid);
+ this.implementor = requireNonNull(implementor);
+ this.description = requireNonNull(description);
+ this.uuid = UUID.fromString(requireNonNull(uuid));
this.version = version;
this.maxSoundModels = maxSoundModels;
this.maxKeyphrases = maxKeyphrases;
@@ -231,6 +243,7 @@ public class SoundTrigger {
/** Unique sound model identifier */
@UnsupportedAppUsage
+ @NonNull
public final UUID uuid;
/** Sound model type (e.g. TYPE_KEYPHRASE); */
@@ -238,17 +251,20 @@ public class SoundTrigger {
/** Unique sound model vendor identifier */
@UnsupportedAppUsage
+ @NonNull
public final UUID vendorUuid;
/** Opaque data. For use by vendor implementation and enrollment application */
@UnsupportedAppUsage
+ @NonNull
public final byte[] data;
- public SoundModel(UUID uuid, UUID vendorUuid, int type, byte[] data) {
- this.uuid = uuid;
- this.vendorUuid = vendorUuid;
+ public SoundModel(@NonNull UUID uuid, @Nullable UUID vendorUuid, int type,
+ @Nullable byte[] data) {
+ this.uuid = requireNonNull(uuid);
+ this.vendorUuid = vendorUuid != null ? vendorUuid : new UUID(0, 0);
this.type = type;
- this.data = data;
+ this.data = data != null ? data : new byte[0];
}
@Override
@@ -271,8 +287,6 @@ public class SoundTrigger {
if (!(obj instanceof SoundModel))
return false;
SoundModel other = (SoundModel) obj;
- if (!Arrays.equals(data, other.data))
- return false;
if (type != other.type)
return false;
if (uuid == null) {
@@ -285,6 +299,8 @@ public class SoundTrigger {
return false;
} else if (!vendorUuid.equals(other.vendorUuid))
return false;
+ if (!Arrays.equals(data, other.data))
+ return false;
return true;
}
}
@@ -306,24 +322,28 @@ public class SoundTrigger {
/** Locale of the keyphrase. JAVA Locale string e.g en_US */
@UnsupportedAppUsage
+ @NonNull
public final String locale;
/** Key phrase text */
@UnsupportedAppUsage
+ @NonNull
public final String text;
/** Users this key phrase has been trained for. countains sound trigger specific user IDs
* derived from system user IDs {@link android.os.UserHandle#getIdentifier()}. */
@UnsupportedAppUsage
+ @NonNull
public final int[] users;
@UnsupportedAppUsage
- public Keyphrase(int id, int recognitionModes, String locale, String text, int[] users) {
+ public Keyphrase(int id, int recognitionModes, @NonNull String locale, @NonNull String text,
+ @Nullable int[] users) {
this.id = id;
this.recognitionModes = recognitionModes;
- this.locale = locale;
- this.text = text;
- this.users = users;
+ this.locale = requireNonNull(locale);
+ this.text = requireNonNull(text);
+ this.users = users != null ? users : new int[0];
}
public static final @android.annotation.NonNull Parcelable.Creator<Keyphrase> CREATOR
@@ -427,13 +447,15 @@ public class SoundTrigger {
public static class KeyphraseSoundModel extends SoundModel implements Parcelable {
/** Key phrases in this sound model */
@UnsupportedAppUsage
+ @NonNull
public final Keyphrase[] keyphrases; // keyword phrases in model
@UnsupportedAppUsage
public KeyphraseSoundModel(
- UUID uuid, UUID vendorUuid, byte[] data, Keyphrase[] keyphrases) {
+ @NonNull UUID uuid, @NonNull UUID vendorUuid, @Nullable byte[] data,
+ @Nullable Keyphrase[] keyphrases) {
super(uuid, vendorUuid, TYPE_KEYPHRASE, data);
- this.keyphrases = keyphrases;
+ this.keyphrases = keyphrases != null ? keyphrases : new Keyphrase[0];
}
public static final @android.annotation.NonNull Parcelable.Creator<KeyphraseSoundModel> CREATOR
@@ -528,7 +550,8 @@ public class SoundTrigger {
};
@UnsupportedAppUsage
- public GenericSoundModel(UUID uuid, UUID vendorUuid, byte[] data) {
+ public GenericSoundModel(@NonNull UUID uuid, @NonNull UUID vendorUuid,
+ @Nullable byte[] data) {
super(uuid, vendorUuid, TYPE_GENERIC_SOUND, data);
}
@@ -648,6 +671,12 @@ public class SoundTrigger {
* @hide
*/
public static final int RECOGNITION_MODE_USER_AUTHENTICATION = 0x4;
+ /**
+ * Generic (non-speech) recognition.
+ *
+ * @hide
+ */
+ public static final int RECOGNITION_MODE_GENERIC = 0x8;
/**
* Status codes for {@link RecognitionEvent}
@@ -739,6 +768,7 @@ public class SoundTrigger {
*
* @hide
*/
+ @NonNull
public final AudioFormat captureFormat;
/**
* Opaque data for use by system applications who know about voice engine internals,
@@ -747,13 +777,14 @@ public class SoundTrigger {
* @hide
*/
@UnsupportedAppUsage
+ @NonNull
public final byte[] data;
/** @hide */
@UnsupportedAppUsage
public RecognitionEvent(int status, int soundModelHandle, boolean captureAvailable,
int captureSession, int captureDelayMs, int capturePreambleMs,
- boolean triggerInData, AudioFormat captureFormat, byte[] data) {
+ boolean triggerInData, @NonNull AudioFormat captureFormat, @Nullable byte[] data) {
this.status = status;
this.soundModelHandle = soundModelHandle;
this.captureAvailable = captureAvailable;
@@ -761,8 +792,8 @@ public class SoundTrigger {
this.captureDelayMs = captureDelayMs;
this.capturePreambleMs = capturePreambleMs;
this.triggerInData = triggerInData;
- this.captureFormat = captureFormat;
- this.data = data;
+ this.captureFormat = requireNonNull(captureFormat);
+ this.data = data != null ? data : new byte[0];
}
/**
@@ -965,19 +996,21 @@ public class SoundTrigger {
/** List of all keyphrases in the sound model for which recognition should be performed with
* options for each keyphrase. */
@UnsupportedAppUsage
+ @NonNull
public final KeyphraseRecognitionExtra keyphrases[];
/** Opaque data for use by system applications who know about voice engine internals,
* typically during enrollment. */
@UnsupportedAppUsage
+ @NonNull
public final byte[] data;
@UnsupportedAppUsage
public RecognitionConfig(boolean captureRequested, boolean allowMultipleTriggers,
- KeyphraseRecognitionExtra[] keyphrases, byte[] data) {
+ @Nullable KeyphraseRecognitionExtra[] keyphrases, @Nullable byte[] data) {
this.captureRequested = captureRequested;
this.allowMultipleTriggers = allowMultipleTriggers;
- this.keyphrases = keyphrases;
- this.data = data;
+ this.keyphrases = keyphrases != null ? keyphrases : new KeyphraseRecognitionExtra[0];
+ this.data = data != null ? data : new byte[0];
}
public static final @android.annotation.NonNull Parcelable.Creator<RecognitionConfig> CREATOR
@@ -1126,15 +1159,17 @@ public class SoundTrigger {
/** Confidence levels for all users recognized (KeyphraseRecognitionEvent) or to
* be recognized (RecognitionConfig) */
@UnsupportedAppUsage
+ @NonNull
public final ConfidenceLevel[] confidenceLevels;
@UnsupportedAppUsage
public KeyphraseRecognitionExtra(int id, int recognitionModes, int coarseConfidenceLevel,
- ConfidenceLevel[] confidenceLevels) {
+ @Nullable ConfidenceLevel[] confidenceLevels) {
this.id = id;
this.recognitionModes = recognitionModes;
this.coarseConfidenceLevel = coarseConfidenceLevel;
- this.confidenceLevels = confidenceLevels;
+ this.confidenceLevels =
+ confidenceLevels != null ? confidenceLevels : new ConfidenceLevel[0];
}
public static final @android.annotation.NonNull Parcelable.Creator<KeyphraseRecognitionExtra> CREATOR
@@ -1217,16 +1252,18 @@ public class SoundTrigger {
public static class KeyphraseRecognitionEvent extends RecognitionEvent implements Parcelable {
/** Indicates if the key phrase is present in the buffered audio available for capture */
@UnsupportedAppUsage
+ @NonNull
public final KeyphraseRecognitionExtra[] keyphraseExtras;
@UnsupportedAppUsage
public KeyphraseRecognitionEvent(int status, int soundModelHandle, boolean captureAvailable,
int captureSession, int captureDelayMs, int capturePreambleMs,
- boolean triggerInData, AudioFormat captureFormat, byte[] data,
- KeyphraseRecognitionExtra[] keyphraseExtras) {
+ boolean triggerInData, @NonNull AudioFormat captureFormat, @Nullable byte[] data,
+ @Nullable KeyphraseRecognitionExtra[] keyphraseExtras) {
super(status, soundModelHandle, captureAvailable, captureSession, captureDelayMs,
capturePreambleMs, triggerInData, captureFormat, data);
- this.keyphraseExtras = keyphraseExtras;
+ this.keyphraseExtras =
+ keyphraseExtras != null ? keyphraseExtras : new KeyphraseRecognitionExtra[0];
}
public static final @android.annotation.NonNull Parcelable.Creator<KeyphraseRecognitionEvent> CREATOR
@@ -1343,8 +1380,8 @@ public class SoundTrigger {
@UnsupportedAppUsage
public GenericRecognitionEvent(int status, int soundModelHandle,
boolean captureAvailable, int captureSession, int captureDelayMs,
- int capturePreambleMs, boolean triggerInData, AudioFormat captureFormat,
- byte[] data) {
+ int capturePreambleMs, boolean triggerInData, @NonNull AudioFormat captureFormat,
+ @Nullable byte[] data) {
super(status, soundModelHandle, captureAvailable, captureSession,
captureDelayMs, capturePreambleMs, triggerInData, captureFormat,
data);
@@ -1408,13 +1445,14 @@ public class SoundTrigger {
/** The updated sound model handle */
public final int soundModelHandle;
/** New sound model data */
+ @NonNull
public final byte[] data;
@UnsupportedAppUsage
- SoundModelEvent(int status, int soundModelHandle, byte[] data) {
+ SoundModelEvent(int status, int soundModelHandle, @Nullable byte[] data) {
this.status = status;
this.soundModelHandle = soundModelHandle;
- this.data = data;
+ this.data = data != null ? data : new byte[0];
}
public static final @android.annotation.NonNull Parcelable.Creator<SoundModelEvent> CREATOR
@@ -1498,8 +1536,9 @@ public class SoundTrigger {
* @hide
*/
public static final int SERVICE_STATE_DISABLED = 1;
-
- /**
+ private static Object mServiceLock = new Object();
+ private static ISoundTriggerMiddlewareService mService;
+ /**
* @return returns current package name.
*/
static String getCurrentOpPackageName() {
@@ -1523,25 +1562,22 @@ public class SoundTrigger {
* @hide
*/
@UnsupportedAppUsage
- public static int listModules(ArrayList<ModuleProperties> modules) {
- return listModules(getCurrentOpPackageName(), modules);
+ public static int listModules(@NonNull ArrayList<ModuleProperties> modules) {
+ try {
+ SoundTriggerModuleDescriptor[] descs = getService().listModules();
+ modules.clear();
+ modules.ensureCapacity(descs.length);
+ for (SoundTriggerModuleDescriptor desc : descs) {
+ modules.add(ConversionUtil.aidl2apiModuleDescriptor(desc));
+ }
+ return STATUS_OK;
+ } catch (RemoteException e) {
+ Log.e(TAG, "Exception caught", e);
+ return STATUS_DEAD_OBJECT;
+ }
}
/**
- * Returns a list of descriptors for all hardware modules loaded.
- * @param opPackageName
- * @param modules A ModuleProperties array where the list will be returned.
- * @return - {@link #STATUS_OK} in case of success
- * - {@link #STATUS_ERROR} in case of unspecified error
- * - {@link #STATUS_PERMISSION_DENIED} if the caller does not have system permission
- * - {@link #STATUS_NO_INIT} if the native service cannot be reached
- * - {@link #STATUS_BAD_VALUE} if modules is null
- * - {@link #STATUS_DEAD_OBJECT} if the binder transaction to the native service fails
- */
- private static native int listModules(String opPackageName,
- ArrayList<ModuleProperties> modules);
-
- /**
* Get an interface on a hardware module to control sound models and recognition on
* this module.
* @param moduleId Sound module system identifier {@link ModuleProperties#id}. mandatory.
@@ -1553,14 +1589,40 @@ public class SoundTrigger {
* @hide
*/
@UnsupportedAppUsage
- public static SoundTriggerModule attachModule(int moduleId,
- StatusListener listener,
- Handler handler) {
- if (listener == null) {
+ public static @NonNull SoundTriggerModule attachModule(int moduleId,
+ @NonNull StatusListener listener,
+ @Nullable Handler handler) {
+ Looper looper = handler != null ? handler.getLooper() : Looper.getMainLooper();
+ try {
+ return new SoundTriggerModule(getService(), moduleId, listener, looper);
+ } catch (RemoteException e) {
+ Log.e(TAG, "", e);
return null;
}
- SoundTriggerModule module = new SoundTriggerModule(moduleId, listener, handler);
- return module;
+ }
+
+ private static ISoundTriggerMiddlewareService getService() {
+ synchronized (mServiceLock) {
+ while (true) {
+ IBinder binder = null;
+ try {
+ binder =
+ ServiceManager.getServiceOrThrow(
+ Context.SOUND_TRIGGER_MIDDLEWARE_SERVICE);
+ binder.linkToDeath(() -> {
+ synchronized (mServiceLock) {
+ mService = null;
+ }
+ }, 0);
+ mService = ISoundTriggerMiddlewareService.Stub.asInterface(binder);
+ break;
+ } catch (Exception e) {
+ Log.e(TAG, "Failed to bind to soundtrigger service", e);
+ }
+ }
+ return mService;
+ }
+
}
/**
diff --git a/core/java/android/hardware/soundtrigger/SoundTriggerModule.java b/core/java/android/hardware/soundtrigger/SoundTriggerModule.java
index b16ef5c43346..7cf560019239 100644
--- a/core/java/android/hardware/soundtrigger/SoundTriggerModule.java
+++ b/core/java/android/hardware/soundtrigger/SoundTriggerModule.java
@@ -16,14 +16,23 @@
package android.hardware.soundtrigger;
+import android.annotation.NonNull;
import android.annotation.Nullable;
import android.annotation.UnsupportedAppUsage;
-import android.hardware.soundtrigger.SoundTrigger.ModelParamRange;
+import android.media.soundtrigger_middleware.ISoundTriggerCallback;
+import android.media.soundtrigger_middleware.ISoundTriggerMiddlewareService;
+import android.media.soundtrigger_middleware.ISoundTriggerModule;
+import android.media.soundtrigger_middleware.ModelParameterRange;
+import android.media.soundtrigger_middleware.PhraseRecognitionEvent;
+import android.media.soundtrigger_middleware.PhraseSoundModel;
+import android.media.soundtrigger_middleware.RecognitionEvent;
+import android.media.soundtrigger_middleware.SoundModel;
import android.os.Handler;
+import android.os.IBinder;
import android.os.Looper;
import android.os.Message;
-
-import java.lang.ref.WeakReference;
+import android.os.RemoteException;
+import android.util.Log;
/**
* The SoundTriggerModule provides APIs to control sound models and sound detection
@@ -32,39 +41,47 @@ import java.lang.ref.WeakReference;
* @hide
*/
public class SoundTriggerModule {
- @UnsupportedAppUsage
- private long mNativeContext;
-
- @UnsupportedAppUsage
- private int mId;
- private NativeEventHandlerDelegate mEventHandlerDelegate;
+ private static final String TAG = "SoundTriggerModule";
- // to be kept in sync with core/jni/android_hardware_SoundTrigger.cpp
private static final int EVENT_RECOGNITION = 1;
private static final int EVENT_SERVICE_DIED = 2;
- private static final int EVENT_SOUNDMODEL = 3;
- private static final int EVENT_SERVICE_STATE_CHANGE = 4;
+ private static final int EVENT_SERVICE_STATE_CHANGE = 3;
+ @UnsupportedAppUsage
+ private int mId;
+ private EventHandlerDelegate mEventHandlerDelegate;
+ private ISoundTriggerModule mService;
- SoundTriggerModule(int moduleId, SoundTrigger.StatusListener listener, Handler handler) {
+ SoundTriggerModule(@NonNull ISoundTriggerMiddlewareService service,
+ int moduleId, @NonNull SoundTrigger.StatusListener listener, @NonNull Looper looper)
+ throws RemoteException {
mId = moduleId;
- mEventHandlerDelegate = new NativeEventHandlerDelegate(listener, handler);
- native_setup(SoundTrigger.getCurrentOpPackageName(),
- new WeakReference<SoundTriggerModule>(this));
+ mEventHandlerDelegate = new EventHandlerDelegate(listener, looper);
+ mService = service.attach(moduleId, mEventHandlerDelegate);
+ mService.asBinder().linkToDeath(mEventHandlerDelegate, 0);
}
- private native void native_setup(String opPackageName, Object moduleThis);
@Override
protected void finalize() {
- native_finalize();
+ detach();
}
- private native void native_finalize();
/**
* Detach from this module. The {@link SoundTrigger.StatusListener} callback will not be called
* anymore and associated resources will be released.
- * */
+ * All models must have been unloaded prior to detaching.
+ */
@UnsupportedAppUsage
- public native void detach();
+ public synchronized void detach() {
+ try {
+ if (mService != null) {
+ mService.asBinder().unlinkToDeath(mEventHandlerDelegate, 0);
+ mService.detach();
+ mService = null;
+ }
+ } catch (Exception e) {
+ handleException(e);
+ }
+ }
/**
* Load a {@link SoundTrigger.SoundModel} to the hardware. A sound model must be loaded in
@@ -82,7 +99,26 @@ public class SoundTriggerModule {
* - {@link SoundTrigger#STATUS_INVALID_OPERATION} if the call is out of sequence
*/
@UnsupportedAppUsage
- public native int loadSoundModel(SoundTrigger.SoundModel model, int[] soundModelHandle);
+ public synchronized int loadSoundModel(@NonNull SoundTrigger.SoundModel model,
+ @NonNull int[] soundModelHandle) {
+ try {
+ if (model instanceof SoundTrigger.GenericSoundModel) {
+ SoundModel aidlModel = ConversionUtil.api2aidlGenericSoundModel(
+ (SoundTrigger.GenericSoundModel) model);
+ soundModelHandle[0] = mService.loadModel(aidlModel);
+ return SoundTrigger.STATUS_OK;
+ }
+ if (model instanceof SoundTrigger.KeyphraseSoundModel) {
+ PhraseSoundModel aidlModel = ConversionUtil.api2aidlPhraseSoundModel(
+ (SoundTrigger.KeyphraseSoundModel) model);
+ soundModelHandle[0] = mService.loadPhraseModel(aidlModel);
+ return SoundTrigger.STATUS_OK;
+ }
+ return SoundTrigger.STATUS_BAD_VALUE;
+ } catch (Exception e) {
+ return handleException(e);
+ }
+ }
/**
* Unload a {@link SoundTrigger.SoundModel} and abort any pendiong recognition
@@ -97,7 +133,14 @@ public class SoundTriggerModule {
* service fails
*/
@UnsupportedAppUsage
- public native int unloadSoundModel(int soundModelHandle);
+ public synchronized int unloadSoundModel(int soundModelHandle) {
+ try {
+ mService.unloadModel(soundModelHandle);
+ return SoundTrigger.STATUS_OK;
+ } catch (Exception e) {
+ return handleException(e);
+ }
+ }
/**
* Start listening to all key phrases in a {@link SoundTrigger.SoundModel}.
@@ -117,7 +160,16 @@ public class SoundTriggerModule {
* - {@link SoundTrigger#STATUS_INVALID_OPERATION} if the call is out of sequence
*/
@UnsupportedAppUsage
- public native int startRecognition(int soundModelHandle, SoundTrigger.RecognitionConfig config);
+ public synchronized int startRecognition(int soundModelHandle,
+ SoundTrigger.RecognitionConfig config) {
+ try {
+ mService.startRecognition(soundModelHandle,
+ ConversionUtil.api2aidlRecognitionConfig(config));
+ return SoundTrigger.STATUS_OK;
+ } catch (Exception e) {
+ return handleException(e);
+ }
+ }
/**
* Stop listening to all key phrases in a {@link SoundTrigger.SoundModel}
@@ -133,12 +185,20 @@ public class SoundTriggerModule {
* - {@link SoundTrigger#STATUS_INVALID_OPERATION} if the call is out of sequence
*/
@UnsupportedAppUsage
- public native int stopRecognition(int soundModelHandle);
+ public synchronized int stopRecognition(int soundModelHandle) {
+ try {
+ mService.stopRecognition(soundModelHandle);
+ return SoundTrigger.STATUS_OK;
+ } catch (Exception e) {
+ return handleException(e);
+ }
+ }
/**
* Get the current state of a {@link SoundTrigger.SoundModel}.
- * The state will be returned asynchronously as a {@link SoundTrigger#RecognitionEvent}
- * in the callback registered in the {@link SoundTrigger.startRecognition} method.
+ * The state will be returned asynchronously as a {@link SoundTrigger.RecognitionEvent}
+ * in the callback registered in the
+ * {@link SoundTrigger#attachModule(int, SoundTrigger.StatusListener, Handler)} method.
* @param soundModelHandle The sound model handle indicating which model's state to return
* @return - {@link SoundTrigger#STATUS_OK} in case of success
* - {@link SoundTrigger#STATUS_ERROR} in case of unspecified error
@@ -150,46 +210,71 @@ public class SoundTriggerModule {
* service fails
* - {@link SoundTrigger#STATUS_INVALID_OPERATION} if the call is out of sequence
*/
- public native int getModelState(int soundModelHandle);
+ public synchronized int getModelState(int soundModelHandle) {
+ try {
+ mService.forceRecognitionEvent(soundModelHandle);
+ return SoundTrigger.STATUS_OK;
+ } catch (Exception e) {
+ return handleException(e);
+ }
+ }
/**
* Set a model specific {@link ModelParams} with the given value. This
- * parameter will keep its value for the duration the model is loaded regardless of starting and
+ * parameter will keep its value for the duration the model is loaded regardless of starting
+ * and
* stopping recognition. Once the model is unloaded, the value will be lost.
- * {@link SoundTriggerModule#isParameterSupported} should be checked first before calling this
- * method.
+ * {@link SoundTriggerModule#queryParameter(int, int)} should be checked first before calling
+ * this method.
*
* @param soundModelHandle handle of model to apply parameter
- * @param modelParam {@link ModelParams}
- * @param value Value to set
+ * @param modelParam {@link ModelParams}
+ * @param value Value to set
* @return - {@link SoundTrigger#STATUS_OK} in case of success
- * - {@link SoundTrigger#STATUS_NO_INIT} if the native service cannot be reached
- * - {@link SoundTrigger#STATUS_BAD_VALUE} invalid input parameter
- * - {@link SoundTrigger#STATUS_INVALID_OPERATION} if the call is out of sequence or
- * if API is not supported by HAL
+ * - {@link SoundTrigger#STATUS_NO_INIT} if the native service cannot be reached
+ * - {@link SoundTrigger#STATUS_BAD_VALUE} invalid input parameter
+ * - {@link SoundTrigger#STATUS_INVALID_OPERATION} if the call is out of sequence or
+ * if API is not supported by HAL
*/
- public native int setParameter(int soundModelHandle,
- @ModelParams int modelParam, int value);
+ public synchronized int setParameter(int soundModelHandle, @ModelParams int modelParam,
+ int value) {
+ try {
+ mService.setModelParameter(soundModelHandle,
+ ConversionUtil.api2aidlModelParameter(modelParam), value);
+ return SoundTrigger.STATUS_OK;
+ } catch (Exception e) {
+ return handleException(e);
+ }
+ }
/**
* Get a model specific {@link ModelParams}. This parameter will keep its value
* for the duration the model is loaded regardless of starting and stopping recognition.
* Once the model is unloaded, the value will be lost. If the value is not set, a default
* value is returned. See {@link ModelParams} for parameter default values.
- * {@link SoundTriggerModule#isParameterSupported} should be checked first before
+ * {@link SoundTriggerModule#queryParameter(int, int)} should be checked first before
* calling this method. Otherwise, an exception can be thrown.
*
* @param soundModelHandle handle of model to get parameter
- * @param modelParam {@link ModelParams}
+ * @param modelParam {@link ModelParams}
* @return value of parameter
* @throws UnsupportedOperationException if hal or model do not support this API.
- * {@link SoundTriggerModule#isParameterSupported} should be checked first.
- * @throws IllegalArgumentException if invalid model handle or parameter is passed.
- * {@link SoundTriggerModule#isParameterSupported} should be checked first.
+ * {@link SoundTriggerModule#queryParameter(int, int)}
+ * should
+ * be checked first.
+ * @throws IllegalArgumentException if invalid model handle or parameter is passed.
+ * {@link SoundTriggerModule#queryParameter(int, int)}
+ * should be checked first.
*/
- public native int getParameter(int soundModelHandle,
- @ModelParams int modelParam)
- throws UnsupportedOperationException, IllegalArgumentException;
+ public synchronized int getParameter(int soundModelHandle, @ModelParams int modelParam)
+ throws UnsupportedOperationException, IllegalArgumentException {
+ try {
+ return mService.getModelParameter(soundModelHandle,
+ ConversionUtil.api2aidlModelParameter(modelParam));
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
/**
* Determine if parameter control is supported for the given model handle.
@@ -197,85 +282,98 @@ public class SoundTriggerModule {
* {@link SoundTriggerModule#getParameter}.
*
* @param soundModelHandle handle of model to get parameter
- * @param modelParam {@link ModelParams}
+ * @param modelParam {@link ModelParams}
* @return supported range of parameter, null if not supported
*/
@Nullable
- public native ModelParamRange queryParameter(int soundModelHandle, @ModelParams int modelParam);
+ public synchronized SoundTrigger.ModelParamRange queryParameter(int soundModelHandle,
+ @ModelParams int modelParam) {
+ try {
+ return ConversionUtil.aidl2apiModelParameterRange(mService.queryModelParameterSupport(
+ soundModelHandle,
+ ConversionUtil.api2aidlModelParameter(modelParam)));
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
+ private int handleException(Exception e) {
+ Log.e(TAG, "", e);
+ if (e instanceof NullPointerException) {
+ return SoundTrigger.STATUS_NO_INIT;
+ }
+ if (e instanceof RemoteException) {
+ return SoundTrigger.STATUS_DEAD_OBJECT;
+ }
+ if (e instanceof IllegalArgumentException) {
+ return SoundTrigger.STATUS_BAD_VALUE;
+ }
+ if (e instanceof IllegalStateException) {
+ return SoundTrigger.STATUS_INVALID_OPERATION;
+ }
+ return SoundTrigger.STATUS_ERROR;
+ }
- private class NativeEventHandlerDelegate {
+ private class EventHandlerDelegate extends ISoundTriggerCallback.Stub implements
+ IBinder.DeathRecipient {
private final Handler mHandler;
- NativeEventHandlerDelegate(final SoundTrigger.StatusListener listener,
- Handler handler) {
- // find the looper for our new event handler
- Looper looper;
- if (handler != null) {
- looper = handler.getLooper();
- } else {
- looper = Looper.getMainLooper();
- }
+ EventHandlerDelegate(@NonNull final SoundTrigger.StatusListener listener,
+ @NonNull Looper looper) {
// construct the event handler with this looper
- if (looper != null) {
- // implement the event handler delegate
- mHandler = new Handler(looper) {
- @Override
- public void handleMessage(Message msg) {
- switch(msg.what) {
+ // implement the event handler delegate
+ mHandler = new Handler(looper) {
+ @Override
+ public void handleMessage(Message msg) {
+ switch (msg.what) {
case EVENT_RECOGNITION:
- if (listener != null) {
- listener.onRecognition(
- (SoundTrigger.RecognitionEvent)msg.obj);
- }
- break;
- case EVENT_SOUNDMODEL:
- if (listener != null) {
- listener.onSoundModelUpdate(
- (SoundTrigger.SoundModelEvent)msg.obj);
- }
+ listener.onRecognition(
+ (SoundTrigger.RecognitionEvent) msg.obj);
break;
case EVENT_SERVICE_STATE_CHANGE:
- if (listener != null) {
- listener.onServiceStateChange(msg.arg1);
- }
+ listener.onServiceStateChange(msg.arg1);
break;
case EVENT_SERVICE_DIED:
- if (listener != null) {
- listener.onServiceDied();
- }
+ listener.onServiceDied();
break;
default:
+ Log.e(TAG, "Unknown message: " + msg.toString());
break;
- }
}
- };
- } else {
- mHandler = null;
- }
+ }
+ };
}
- Handler handler() {
- return mHandler;
+ @Override
+ public synchronized void onRecognition(int handle, RecognitionEvent event)
+ throws RemoteException {
+ Message m = mHandler.obtainMessage(EVENT_RECOGNITION,
+ ConversionUtil.aidl2apiRecognitionEvent(handle, event));
+ mHandler.sendMessage(m);
}
- }
- @SuppressWarnings("unused")
- @UnsupportedAppUsage
- private static void postEventFromNative(Object module_ref,
- int what, int arg1, int arg2, Object obj) {
- SoundTriggerModule module = (SoundTriggerModule)((WeakReference)module_ref).get();
- if (module == null) {
- return;
+ @Override
+ public synchronized void onPhraseRecognition(int handle, PhraseRecognitionEvent event)
+ throws RemoteException {
+ Message m = mHandler.obtainMessage(EVENT_RECOGNITION,
+ ConversionUtil.aidl2apiPhraseRecognitionEvent(handle, event));
+ mHandler.sendMessage(m);
}
- NativeEventHandlerDelegate delegate = module.mEventHandlerDelegate;
- if (delegate != null) {
- Handler handler = delegate.handler();
- if (handler != null) {
- Message m = handler.obtainMessage(what, arg1, arg2, obj);
- handler.sendMessage(m);
- }
+ @Override
+ public synchronized void onRecognitionAvailabilityChange(boolean available)
+ throws RemoteException {
+ Message m = mHandler.obtainMessage(EVENT_SERVICE_STATE_CHANGE,
+ available ? SoundTrigger.SERVICE_STATE_ENABLED
+ : SoundTrigger.SERVICE_STATE_DISABLED);
+ mHandler.sendMessage(m);
+ }
+
+ @Override
+ public synchronized void binderDied() {
+ Message m = mHandler.obtainMessage(EVENT_SERVICE_DIED);
+ mHandler.sendMessage(m);
}
}
}
diff --git a/core/jni/Android.bp b/core/jni/Android.bp
index b91d3595fe6a..a2f6a62157ed 100644
--- a/core/jni/Android.bp
+++ b/core/jni/Android.bp
@@ -177,7 +177,6 @@ cc_library_shared {
"android_hardware_HardwareBuffer.cpp",
"android_hardware_SensorManager.cpp",
"android_hardware_SerialPort.cpp",
- "android_hardware_SoundTrigger.cpp",
"android_hardware_UsbDevice.cpp",
"android_hardware_UsbDeviceConnection.cpp",
"android_hardware_UsbRequest.cpp",
@@ -259,7 +258,6 @@ cc_library_shared {
"libpdfium",
"libimg_utils",
"libnetd_client",
- "libsoundtrigger",
"libprocessgroup",
"libnativebridge_lazy",
"libnativeloader_lazy",
diff --git a/core/jni/AndroidRuntime.cpp b/core/jni/AndroidRuntime.cpp
index b8fd3ad43ce5..f7a994f7071f 100644
--- a/core/jni/AndroidRuntime.cpp
+++ b/core/jni/AndroidRuntime.cpp
@@ -80,7 +80,6 @@ extern int register_android_hardware_camera2_DngCreator(JNIEnv *env);
extern int register_android_hardware_HardwareBuffer(JNIEnv *env);
extern int register_android_hardware_SensorManager(JNIEnv *env);
extern int register_android_hardware_SerialPort(JNIEnv *env);
-extern int register_android_hardware_SoundTrigger(JNIEnv *env);
extern int register_android_hardware_UsbDevice(JNIEnv *env);
extern int register_android_hardware_UsbDeviceConnection(JNIEnv *env);
extern int register_android_hardware_UsbRequest(JNIEnv *env);
@@ -1508,7 +1507,6 @@ static const RegJNIRec gRegJNI[] = {
REG_JNI(register_android_hardware_HardwareBuffer),
REG_JNI(register_android_hardware_SensorManager),
REG_JNI(register_android_hardware_SerialPort),
- REG_JNI(register_android_hardware_SoundTrigger),
REG_JNI(register_android_hardware_UsbDevice),
REG_JNI(register_android_hardware_UsbDeviceConnection),
REG_JNI(register_android_hardware_UsbRequest),
diff --git a/core/jni/android_hardware_SoundTrigger.cpp b/core/jni/android_hardware_SoundTrigger.cpp
deleted file mode 100644
index 4376b0b4ac42..000000000000
--- a/core/jni/android_hardware_SoundTrigger.cpp
+++ /dev/null
@@ -1,1071 +0,0 @@
-/*
-**
-** Copyright 2014, 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.
-*/
-
-//#define LOG_NDEBUG 0
-#define LOG_TAG "SoundTrigger-JNI"
-#include <utils/Log.h>
-
-#include "jni.h"
-#include <nativehelper/JNIHelp.h>
-#include <nativehelper/ScopedUtfChars.h>
-#include "core_jni_helpers.h"
-#include <system/sound_trigger.h>
-#include <soundtrigger/SoundTriggerCallback.h>
-#include <soundtrigger/SoundTrigger.h>
-#include <utils/RefBase.h>
-#include <utils/Vector.h>
-#include <binder/IMemory.h>
-#include <binder/MemoryDealer.h>
-#include "android_media_AudioFormat.h"
-
-using namespace android;
-
-static jclass gArrayListClass;
-static struct {
- jmethodID add;
-} gArrayListMethods;
-
-static jclass gUUIDClass;
-static struct {
- jmethodID toString;
-} gUUIDMethods;
-
-static const char* const kUnsupportedOperationExceptionClassPathName =
- "java/lang/UnsupportedOperationException";
-static jclass gUnsupportedOperationExceptionClass;
-static const char* const kIllegalArgumentExceptionClassPathName =
- "java/lang/IllegalArgumentException";
-static jclass gIllegalArgumentExceptionClass;
-
-static const char* const kSoundTriggerClassPathName = "android/hardware/soundtrigger/SoundTrigger";
-static jclass gSoundTriggerClass;
-
-static const char* const kModuleClassPathName = "android/hardware/soundtrigger/SoundTriggerModule";
-static jclass gModuleClass;
-static struct {
- jfieldID mNativeContext;
- jfieldID mId;
-} gModuleFields;
-static jmethodID gPostEventFromNative;
-
-static const char* const kModulePropertiesClassPathName =
- "android/hardware/soundtrigger/SoundTrigger$ModuleProperties";
-static jclass gModulePropertiesClass;
-static jmethodID gModulePropertiesCstor;
-
-static const char* const kSoundModelClassPathName =
- "android/hardware/soundtrigger/SoundTrigger$SoundModel";
-static jclass gSoundModelClass;
-static struct {
- jfieldID uuid;
- jfieldID vendorUuid;
- jfieldID data;
-} gSoundModelFields;
-
-static const char* const kGenericSoundModelClassPathName =
- "android/hardware/soundtrigger/SoundTrigger$GenericSoundModel";
-static jclass gGenericSoundModelClass;
-
-static const char* const kKeyphraseClassPathName =
- "android/hardware/soundtrigger/SoundTrigger$Keyphrase";
-static jclass gKeyphraseClass;
-static struct {
- jfieldID id;
- jfieldID recognitionModes;
- jfieldID locale;
- jfieldID text;
- jfieldID users;
-} gKeyphraseFields;
-
-static const char* const kKeyphraseSoundModelClassPathName =
- "android/hardware/soundtrigger/SoundTrigger$KeyphraseSoundModel";
-static jclass gKeyphraseSoundModelClass;
-static struct {
- jfieldID keyphrases;
-} gKeyphraseSoundModelFields;
-
-static const char* const kModelParamRangeClassPathName =
- "android/hardware/soundtrigger/SoundTrigger$ModelParamRange";
-static jclass gModelParamRangeClass;
-static jmethodID gModelParamRangeCstor;
-
-static const char* const kRecognitionConfigClassPathName =
- "android/hardware/soundtrigger/SoundTrigger$RecognitionConfig";
-static jclass gRecognitionConfigClass;
-static struct {
- jfieldID captureRequested;
- jfieldID keyphrases;
- jfieldID data;
-} gRecognitionConfigFields;
-
-static const char* const kRecognitionEventClassPathName =
- "android/hardware/soundtrigger/SoundTrigger$RecognitionEvent";
-static jclass gRecognitionEventClass;
-static jmethodID gRecognitionEventCstor;
-
-static const char* const kKeyphraseRecognitionEventClassPathName =
- "android/hardware/soundtrigger/SoundTrigger$KeyphraseRecognitionEvent";
-static jclass gKeyphraseRecognitionEventClass;
-static jmethodID gKeyphraseRecognitionEventCstor;
-
-static const char* const kGenericRecognitionEventClassPathName =
- "android/hardware/soundtrigger/SoundTrigger$GenericRecognitionEvent";
-static jclass gGenericRecognitionEventClass;
-static jmethodID gGenericRecognitionEventCstor;
-
-static const char* const kKeyphraseRecognitionExtraClassPathName =
- "android/hardware/soundtrigger/SoundTrigger$KeyphraseRecognitionExtra";
-static jclass gKeyphraseRecognitionExtraClass;
-static jmethodID gKeyphraseRecognitionExtraCstor;
-static struct {
- jfieldID id;
- jfieldID recognitionModes;
- jfieldID coarseConfidenceLevel;
- jfieldID confidenceLevels;
-} gKeyphraseRecognitionExtraFields;
-
-static const char* const kConfidenceLevelClassPathName =
- "android/hardware/soundtrigger/SoundTrigger$ConfidenceLevel";
-static jclass gConfidenceLevelClass;
-static jmethodID gConfidenceLevelCstor;
-static struct {
- jfieldID userId;
- jfieldID confidenceLevel;
-} gConfidenceLevelFields;
-
-static const char* const kAudioFormatClassPathName =
- "android/media/AudioFormat";
-static jclass gAudioFormatClass;
-static jmethodID gAudioFormatCstor;
-
-static const char* const kSoundModelEventClassPathName =
- "android/hardware/soundtrigger/SoundTrigger$SoundModelEvent";
-static jclass gSoundModelEventClass;
-static jmethodID gSoundModelEventCstor;
-
-static Mutex gLock;
-
-enum {
- SOUNDTRIGGER_STATUS_OK = 0,
- SOUNDTRIGGER_STATUS_ERROR = INT_MIN,
- SOUNDTRIGGER_PERMISSION_DENIED = -1,
- SOUNDTRIGGER_STATUS_NO_INIT = -19,
- SOUNDTRIGGER_STATUS_BAD_VALUE = -22,
- SOUNDTRIGGER_STATUS_DEAD_OBJECT = -32,
- SOUNDTRIGGER_INVALID_OPERATION = -38,
-};
-
-enum {
- SOUNDTRIGGER_EVENT_RECOGNITION = 1,
- SOUNDTRIGGER_EVENT_SERVICE_DIED = 2,
- SOUNDTRIGGER_EVENT_SOUNDMODEL = 3,
- SOUNDTRIGGER_EVENT_SERVICE_STATE_CHANGE = 4,
-};
-
-static jint throwUnsupportedOperationException(JNIEnv *env)
-{
- return env->ThrowNew(gUnsupportedOperationExceptionClass, nullptr);
-}
-
-static jint throwIllegalArgumentException(JNIEnv *env)
-{
- return env->ThrowNew(gIllegalArgumentExceptionClass, nullptr);
-}
-
-// ----------------------------------------------------------------------------
-// ref-counted object for callbacks
-class JNISoundTriggerCallback: public SoundTriggerCallback
-{
-public:
- JNISoundTriggerCallback(JNIEnv* env, jobject thiz, jobject weak_thiz);
- ~JNISoundTriggerCallback();
-
- virtual void onRecognitionEvent(struct sound_trigger_recognition_event *event);
- virtual void onSoundModelEvent(struct sound_trigger_model_event *event);
- virtual void onServiceStateChange(sound_trigger_service_state_t state);
- virtual void onServiceDied();
-
-private:
- jclass mClass; // Reference to SoundTrigger class
- jobject mObject; // Weak ref to SoundTrigger Java object to call on
-};
-
-JNISoundTriggerCallback::JNISoundTriggerCallback(JNIEnv* env, jobject thiz, jobject weak_thiz)
-{
-
- // Hold onto the SoundTriggerModule class for use in calling the static method
- // that posts events to the application thread.
- jclass clazz = env->GetObjectClass(thiz);
- if (clazz == NULL) {
- ALOGE("Can't find class %s", kModuleClassPathName);
- return;
- }
- mClass = (jclass)env->NewGlobalRef(clazz);
-
- // We use a weak reference so the SoundTriggerModule object can be garbage collected.
- // The reference is only used as a proxy for callbacks.
- mObject = env->NewGlobalRef(weak_thiz);
-}
-
-JNISoundTriggerCallback::~JNISoundTriggerCallback()
-{
- // remove global references
- JNIEnv *env = AndroidRuntime::getJNIEnv();
- env->DeleteGlobalRef(mObject);
- env->DeleteGlobalRef(mClass);
-}
-
-void JNISoundTriggerCallback::onRecognitionEvent(struct sound_trigger_recognition_event *event)
-{
- JNIEnv *env = AndroidRuntime::getJNIEnv();
- jobject jEvent = NULL;
- jbyteArray jData = NULL;
-
- if (event->data_size) {
- jData = env->NewByteArray(event->data_size);
- jbyte *nData = env->GetByteArrayElements(jData, NULL);
- memcpy(nData, (char *)event + event->data_offset, event->data_size);
- env->ReleaseByteArrayElements(jData, nData, 0);
- }
-
- jobject jAudioFormat = NULL;
- if (event->trigger_in_data || event->capture_available) {
- jint channelMask = (jint)audio_channel_mask_get_bits(event->audio_config.channel_mask);
- jint channelIndexMask = (jint)AUDIO_CHANNEL_NONE;
-
- switch (audio_channel_mask_get_representation(event->audio_config.channel_mask)) {
- case AUDIO_CHANNEL_REPRESENTATION_INDEX:
- channelIndexMask = channelMask;
- channelMask = (jint)AUDIO_CHANNEL_NONE;
- break;
- default:
- break;
- }
- jAudioFormat = env->NewObject(gAudioFormatClass,
- gAudioFormatCstor,
- audioFormatFromNative(event->audio_config.format),
- event->audio_config.sample_rate,
- channelMask,
- channelIndexMask);
-
- }
- if (event->type == SOUND_MODEL_TYPE_KEYPHRASE) {
- struct sound_trigger_phrase_recognition_event *phraseEvent =
- (struct sound_trigger_phrase_recognition_event *)event;
-
- jobjectArray jExtras = env->NewObjectArray(phraseEvent->num_phrases,
- gKeyphraseRecognitionExtraClass, NULL);
- if (jExtras == NULL) {
- return;
- }
-
- for (size_t i = 0; i < phraseEvent->num_phrases; i++) {
- jobjectArray jConfidenceLevels = env->NewObjectArray(
- phraseEvent->phrase_extras[i].num_levels,
- gConfidenceLevelClass, NULL);
-
- if (jConfidenceLevels == NULL) {
- return;
- }
- for (size_t j = 0; j < phraseEvent->phrase_extras[i].num_levels; j++) {
- jobject jConfidenceLevel = env->NewObject(gConfidenceLevelClass,
- gConfidenceLevelCstor,
- phraseEvent->phrase_extras[i].levels[j].user_id,
- phraseEvent->phrase_extras[i].levels[j].level);
- env->SetObjectArrayElement(jConfidenceLevels, j, jConfidenceLevel);
- env->DeleteLocalRef(jConfidenceLevel);
- }
-
- jobject jNewExtra = env->NewObject(gKeyphraseRecognitionExtraClass,
- gKeyphraseRecognitionExtraCstor,
- phraseEvent->phrase_extras[i].id,
- phraseEvent->phrase_extras[i].recognition_modes,
- phraseEvent->phrase_extras[i].confidence_level,
- jConfidenceLevels);
-
- if (jNewExtra == NULL) {
- return;
- }
- env->SetObjectArrayElement(jExtras, i, jNewExtra);
- env->DeleteLocalRef(jNewExtra);
- env->DeleteLocalRef(jConfidenceLevels);
- }
- jEvent = env->NewObject(gKeyphraseRecognitionEventClass, gKeyphraseRecognitionEventCstor,
- event->status, event->model, event->capture_available,
- event->capture_session, event->capture_delay_ms,
- event->capture_preamble_ms, event->trigger_in_data,
- jAudioFormat, jData, jExtras);
- env->DeleteLocalRef(jExtras);
- } else if (event->type == SOUND_MODEL_TYPE_GENERIC) {
- jEvent = env->NewObject(gGenericRecognitionEventClass, gGenericRecognitionEventCstor,
- event->status, event->model, event->capture_available,
- event->capture_session, event->capture_delay_ms,
- event->capture_preamble_ms, event->trigger_in_data,
- jAudioFormat, jData);
- } else {
- jEvent = env->NewObject(gRecognitionEventClass, gRecognitionEventCstor,
- event->status, event->model, event->capture_available,
- event->capture_session, event->capture_delay_ms,
- event->capture_preamble_ms, event->trigger_in_data,
- jAudioFormat, jData);
- }
-
- if (jAudioFormat != NULL) {
- env->DeleteLocalRef(jAudioFormat);
- }
- if (jData != NULL) {
- env->DeleteLocalRef(jData);
- }
-
- env->CallStaticVoidMethod(mClass, gPostEventFromNative, mObject,
- SOUNDTRIGGER_EVENT_RECOGNITION, 0, 0, jEvent);
-
- env->DeleteLocalRef(jEvent);
- if (env->ExceptionCheck()) {
- ALOGW("An exception occurred while notifying an event.");
- env->ExceptionClear();
- }
-}
-
-void JNISoundTriggerCallback::onSoundModelEvent(struct sound_trigger_model_event *event)
-{
- JNIEnv *env = AndroidRuntime::getJNIEnv();
- jobject jEvent = NULL;
- jbyteArray jData = NULL;
-
- if (event->data_size) {
- jData = env->NewByteArray(event->data_size);
- jbyte *nData = env->GetByteArrayElements(jData, NULL);
- memcpy(nData, (char *)event + event->data_offset, event->data_size);
- env->ReleaseByteArrayElements(jData, nData, 0);
- }
-
- jEvent = env->NewObject(gSoundModelEventClass, gSoundModelEventCstor,
- event->status, event->model, jData);
-
- env->DeleteLocalRef(jData);
- env->CallStaticVoidMethod(mClass, gPostEventFromNative, mObject,
- SOUNDTRIGGER_EVENT_SOUNDMODEL, 0, 0, jEvent);
- env->DeleteLocalRef(jEvent);
- if (env->ExceptionCheck()) {
- ALOGW("An exception occurred while notifying an event.");
- env->ExceptionClear();
- }
-}
-
-void JNISoundTriggerCallback::onServiceStateChange(sound_trigger_service_state_t state)
-{
- JNIEnv *env = AndroidRuntime::getJNIEnv();
- env->CallStaticVoidMethod(mClass, gPostEventFromNative, mObject,
- SOUNDTRIGGER_EVENT_SERVICE_STATE_CHANGE, state, 0, NULL);
- if (env->ExceptionCheck()) {
- ALOGW("An exception occurred while notifying an event.");
- env->ExceptionClear();
- }
-}
-
-void JNISoundTriggerCallback::onServiceDied()
-{
- JNIEnv *env = AndroidRuntime::getJNIEnv();
-
- env->CallStaticVoidMethod(mClass, gPostEventFromNative, mObject,
- SOUNDTRIGGER_EVENT_SERVICE_DIED, 0, 0, NULL);
- if (env->ExceptionCheck()) {
- ALOGW("An exception occurred while notifying an event.");
- env->ExceptionClear();
- }
-}
-
-// ----------------------------------------------------------------------------
-
-static sp<SoundTrigger> getSoundTrigger(JNIEnv* env, jobject thiz)
-{
- Mutex::Autolock l(gLock);
- SoundTrigger* const st = (SoundTrigger*)env->GetLongField(thiz,
- gModuleFields.mNativeContext);
- return sp<SoundTrigger>(st);
-}
-
-static sp<SoundTrigger> setSoundTrigger(JNIEnv* env, jobject thiz, const sp<SoundTrigger>& module)
-{
- Mutex::Autolock l(gLock);
- sp<SoundTrigger> old = (SoundTrigger*)env->GetLongField(thiz,
- gModuleFields.mNativeContext);
- if (module.get()) {
- module->incStrong((void*)setSoundTrigger);
- }
- if (old != 0) {
- old->decStrong((void*)setSoundTrigger);
- }
- env->SetLongField(thiz, gModuleFields.mNativeContext, (jlong)module.get());
- return old;
-}
-
-
-static jint
-android_hardware_SoundTrigger_listModules(JNIEnv *env, jobject clazz,
- jstring opPackageName, jobject jModules)
-{
- ALOGV("listModules");
-
- if (jModules == NULL) {
- ALOGE("listModules NULL AudioPatch ArrayList");
- return SOUNDTRIGGER_STATUS_BAD_VALUE;
- }
- if (!env->IsInstanceOf(jModules, gArrayListClass)) {
- ALOGE("listModules not an arraylist");
- return SOUNDTRIGGER_STATUS_BAD_VALUE;
- }
-
- unsigned int numModules = 0;
- struct sound_trigger_module_descriptor *nModules = NULL;
-
- ScopedUtfChars opPackageNameStr(env, opPackageName);
- const String16 opPackageNameString16 = String16(opPackageNameStr.c_str());
-
- status_t status = SoundTrigger::listModules(opPackageNameString16, nModules, &numModules);
- if (status != NO_ERROR || numModules == 0) {
- return (jint)status;
- }
-
- nModules = (struct sound_trigger_module_descriptor *)
- calloc(numModules, sizeof(struct sound_trigger_module_descriptor));
-
- status = SoundTrigger::listModules(opPackageNameString16, nModules, &numModules);
- ALOGV("listModules SoundTrigger::listModules status %d numModules %d", status, numModules);
-
- if (status != NO_ERROR) {
- numModules = 0;
- }
-
- for (size_t i = 0; i < numModules; i++) {
- char str[SOUND_TRIGGER_MAX_STRING_LEN];
-
- jstring implementor = env->NewStringUTF(nModules[i].properties.implementor);
- jstring description = env->NewStringUTF(nModules[i].properties.description);
- SoundTrigger::guidToString(&nModules[i].properties.uuid,
- str,
- SOUND_TRIGGER_MAX_STRING_LEN);
- jstring uuid = env->NewStringUTF(str);
-
- ALOGV("listModules module %zu id %d description %s maxSoundModels %d",
- i, nModules[i].handle, nModules[i].properties.description,
- nModules[i].properties.max_sound_models);
-
- jobject newModuleDesc = env->NewObject(gModulePropertiesClass, gModulePropertiesCstor,
- nModules[i].handle,
- implementor, description, uuid,
- nModules[i].properties.version,
- nModules[i].properties.max_sound_models,
- nModules[i].properties.max_key_phrases,
- nModules[i].properties.max_users,
- nModules[i].properties.recognition_modes,
- nModules[i].properties.capture_transition,
- nModules[i].properties.max_buffer_ms,
- nModules[i].properties.concurrent_capture,
- nModules[i].properties.power_consumption_mw,
- nModules[i].properties.trigger_in_event);
-
- env->DeleteLocalRef(implementor);
- env->DeleteLocalRef(description);
- env->DeleteLocalRef(uuid);
- if (newModuleDesc == NULL) {
- status = SOUNDTRIGGER_STATUS_ERROR;
- goto exit;
- }
- env->CallBooleanMethod(jModules, gArrayListMethods.add, newModuleDesc);
- }
-
-exit:
- free(nModules);
- return (jint) status;
-}
-
-static void
-android_hardware_SoundTrigger_setup(JNIEnv *env, jobject thiz,
- jstring opPackageName, jobject weak_this)
-{
- ALOGV("setup");
-
- ScopedUtfChars opPackageNameStr(env, opPackageName);
- const String16 opPackageNameString16 = String16(opPackageNameStr.c_str());
-
- sp<JNISoundTriggerCallback> callback = new JNISoundTriggerCallback(env, thiz, weak_this);
-
- sound_trigger_module_handle_t handle =
- (sound_trigger_module_handle_t)env->GetIntField(thiz, gModuleFields.mId);
-
- sp<SoundTrigger> module = SoundTrigger::attach(opPackageNameString16, handle, callback);
- if (module == 0) {
- return;
- }
-
- setSoundTrigger(env, thiz, module);
-}
-
-static void
-android_hardware_SoundTrigger_detach(JNIEnv *env, jobject thiz)
-{
- ALOGV("detach");
- sp<SoundTrigger> module = setSoundTrigger(env, thiz, 0);
- ALOGV("detach module %p", module.get());
- if (module != 0) {
- ALOGV("detach module->detach()");
- module->detach();
- }
-}
-
-static void
-android_hardware_SoundTrigger_finalize(JNIEnv *env, jobject thiz)
-{
- ALOGV("finalize");
- sp<SoundTrigger> module = getSoundTrigger(env, thiz);
- if (module != 0) {
- ALOGW("SoundTrigger finalized without being detached");
- }
- android_hardware_SoundTrigger_detach(env, thiz);
-}
-
-static jint
-android_hardware_SoundTrigger_loadSoundModel(JNIEnv *env, jobject thiz,
- jobject jSoundModel, jintArray jHandle)
-{
- jint status = SOUNDTRIGGER_STATUS_OK;
- jbyte *nData = NULL;
- struct sound_trigger_sound_model *nSoundModel;
- jbyteArray jData;
- sp<MemoryDealer> memoryDealer;
- sp<IMemory> memory;
- size_t size;
- sound_model_handle_t handle = 0;
- jobject jUuid;
- jstring jUuidString;
- const char *nUuidString;
-
- ALOGV("loadSoundModel");
- sp<SoundTrigger> module = getSoundTrigger(env, thiz);
- if (module == NULL) {
- return SOUNDTRIGGER_STATUS_ERROR;
- }
- if (jHandle == NULL) {
- return SOUNDTRIGGER_STATUS_BAD_VALUE;
- }
- jsize jHandleLen = env->GetArrayLength(jHandle);
- if (jHandleLen == 0) {
- return SOUNDTRIGGER_STATUS_BAD_VALUE;
- }
- jint *nHandle = env->GetIntArrayElements(jHandle, NULL);
- if (nHandle == NULL) {
- return SOUNDTRIGGER_STATUS_ERROR;
- }
- if (!env->IsInstanceOf(jSoundModel, gSoundModelClass)) {
- status = SOUNDTRIGGER_STATUS_BAD_VALUE;
- goto exit;
- }
- size_t offset;
- sound_trigger_sound_model_type_t type;
- if (env->IsInstanceOf(jSoundModel, gKeyphraseSoundModelClass)) {
- offset = sizeof(struct sound_trigger_phrase_sound_model);
- type = SOUND_MODEL_TYPE_KEYPHRASE;
- } else if (env->IsInstanceOf(jSoundModel, gGenericSoundModelClass)) {
- offset = sizeof(struct sound_trigger_generic_sound_model);
- type = SOUND_MODEL_TYPE_GENERIC;
- } else {
- offset = sizeof(struct sound_trigger_sound_model);
- type = SOUND_MODEL_TYPE_UNKNOWN;
- }
-
- jUuid = env->GetObjectField(jSoundModel, gSoundModelFields.uuid);
- jUuidString = (jstring)env->CallObjectMethod(jUuid, gUUIDMethods.toString);
- nUuidString = env->GetStringUTFChars(jUuidString, NULL);
- sound_trigger_uuid_t nUuid;
- SoundTrigger::stringToGuid(nUuidString, &nUuid);
- env->ReleaseStringUTFChars(jUuidString, nUuidString);
- env->DeleteLocalRef(jUuidString);
-
- sound_trigger_uuid_t nVendorUuid;
- jUuid = env->GetObjectField(jSoundModel, gSoundModelFields.vendorUuid);
- if (jUuid != NULL) {
- jUuidString = (jstring)env->CallObjectMethod(jUuid, gUUIDMethods.toString);
- nUuidString = env->GetStringUTFChars(jUuidString, NULL);
- SoundTrigger::stringToGuid(nUuidString, &nVendorUuid);
- env->ReleaseStringUTFChars(jUuidString, nUuidString);
- env->DeleteLocalRef(jUuidString);
- } else {
- SoundTrigger::stringToGuid("00000000-0000-0000-0000-000000000000", &nVendorUuid);
- }
-
- jData = (jbyteArray)env->GetObjectField(jSoundModel, gSoundModelFields.data);
- if (jData == NULL) {
- status = SOUNDTRIGGER_STATUS_BAD_VALUE;
- goto exit;
- }
- size = env->GetArrayLength(jData);
-
- nData = env->GetByteArrayElements(jData, NULL);
- if (jData == NULL) {
- status = SOUNDTRIGGER_STATUS_ERROR;
- goto exit;
- }
-
- memoryDealer = new MemoryDealer(offset + size, "SoundTrigge-JNI::LoadModel");
- if (memoryDealer == 0) {
- status = SOUNDTRIGGER_STATUS_ERROR;
- goto exit;
- }
- memory = memoryDealer->allocate(offset + size);
- if (memory == 0 || memory->unsecurePointer() == NULL) {
- status = SOUNDTRIGGER_STATUS_ERROR;
- goto exit;
- }
-
- nSoundModel = (struct sound_trigger_sound_model *)memory->unsecurePointer();
-
- nSoundModel->type = type;
- nSoundModel->uuid = nUuid;
- nSoundModel->vendor_uuid = nVendorUuid;
- nSoundModel->data_size = size;
- nSoundModel->data_offset = offset;
- memcpy((char *)nSoundModel + offset, nData, size);
- if (type == SOUND_MODEL_TYPE_KEYPHRASE) {
- struct sound_trigger_phrase_sound_model *phraseModel =
- (struct sound_trigger_phrase_sound_model *)nSoundModel;
-
- jobjectArray jPhrases =
- (jobjectArray)env->GetObjectField(jSoundModel, gKeyphraseSoundModelFields.keyphrases);
- if (jPhrases == NULL) {
- status = SOUNDTRIGGER_STATUS_BAD_VALUE;
- goto exit;
- }
-
- size_t numPhrases = env->GetArrayLength(jPhrases);
- phraseModel->num_phrases = numPhrases;
- ALOGV("loadSoundModel numPhrases %zu", numPhrases);
- for (size_t i = 0; i < numPhrases; i++) {
- jobject jPhrase = env->GetObjectArrayElement(jPhrases, i);
- phraseModel->phrases[i].id =
- env->GetIntField(jPhrase,gKeyphraseFields.id);
- phraseModel->phrases[i].recognition_mode =
- env->GetIntField(jPhrase,gKeyphraseFields.recognitionModes);
-
- jintArray jUsers = (jintArray)env->GetObjectField(jPhrase, gKeyphraseFields.users);
- phraseModel->phrases[i].num_users = env->GetArrayLength(jUsers);
- jint *nUsers = env->GetIntArrayElements(jUsers, NULL);
- memcpy(phraseModel->phrases[i].users,
- nUsers,
- phraseModel->phrases[i].num_users * sizeof(int));
- env->ReleaseIntArrayElements(jUsers, nUsers, 0);
- env->DeleteLocalRef(jUsers);
-
- jstring jLocale = (jstring)env->GetObjectField(jPhrase, gKeyphraseFields.locale);
- const char *nLocale = env->GetStringUTFChars(jLocale, NULL);
- strncpy(phraseModel->phrases[i].locale,
- nLocale,
- SOUND_TRIGGER_MAX_LOCALE_LEN);
- jstring jText = (jstring)env->GetObjectField(jPhrase, gKeyphraseFields.text);
- const char *nText = env->GetStringUTFChars(jText, NULL);
- strncpy(phraseModel->phrases[i].text,
- nText,
- SOUND_TRIGGER_MAX_STRING_LEN);
-
- env->ReleaseStringUTFChars(jLocale, nLocale);
- env->DeleteLocalRef(jLocale);
- env->ReleaseStringUTFChars(jText, nText);
- env->DeleteLocalRef(jText);
- ALOGV("loadSoundModel phrases %zu text %s locale %s",
- i, phraseModel->phrases[i].text, phraseModel->phrases[i].locale);
- env->DeleteLocalRef(jPhrase);
- }
- env->DeleteLocalRef(jPhrases);
- } else if (type == SOUND_MODEL_TYPE_GENERIC) {
- /* No initialization needed */
- }
- status = module->loadSoundModel(memory, &handle);
- ALOGV("loadSoundModel status %d handle %d", status, handle);
-
-exit:
- if (nHandle != NULL) {
- nHandle[0] = (jint)handle;
- env->ReleaseIntArrayElements(jHandle, nHandle, NULL);
- }
- if (nData != NULL) {
- env->ReleaseByteArrayElements(jData, nData, NULL);
- }
- return status;
-}
-
-static jint
-android_hardware_SoundTrigger_unloadSoundModel(JNIEnv *env, jobject thiz,
- jint jHandle)
-{
- jint status = SOUNDTRIGGER_STATUS_OK;
- ALOGV("unloadSoundModel");
- sp<SoundTrigger> module = getSoundTrigger(env, thiz);
- if (module == NULL) {
- return SOUNDTRIGGER_STATUS_ERROR;
- }
- status = module->unloadSoundModel((sound_model_handle_t)jHandle);
-
- return status;
-}
-
-static jint
-android_hardware_SoundTrigger_startRecognition(JNIEnv *env, jobject thiz,
- jint jHandle, jobject jConfig)
-{
- jint status = SOUNDTRIGGER_STATUS_OK;
- ALOGV("startRecognition");
- sp<SoundTrigger> module = getSoundTrigger(env, thiz);
- if (module == NULL) {
- return SOUNDTRIGGER_STATUS_ERROR;
- }
-
- if (!env->IsInstanceOf(jConfig, gRecognitionConfigClass)) {
- return SOUNDTRIGGER_STATUS_BAD_VALUE;
- }
-
- jbyteArray jData = (jbyteArray)env->GetObjectField(jConfig, gRecognitionConfigFields.data);
- jsize dataSize = 0;
- jbyte *nData = NULL;
- if (jData != NULL) {
- dataSize = env->GetArrayLength(jData);
- if (dataSize == 0) {
- return SOUNDTRIGGER_STATUS_BAD_VALUE;
- }
- nData = env->GetByteArrayElements(jData, NULL);
- if (nData == NULL) {
- return SOUNDTRIGGER_STATUS_ERROR;
- }
- }
-
- size_t totalSize = sizeof(struct sound_trigger_recognition_config) + dataSize;
- sp<MemoryDealer> memoryDealer =
- new MemoryDealer(totalSize, "SoundTrigge-JNI::StartRecognition");
- if (memoryDealer == 0) {
- return SOUNDTRIGGER_STATUS_ERROR;
- }
- sp<IMemory> memory = memoryDealer->allocate(totalSize);
- if (memory == 0 || memory->unsecurePointer() == NULL) {
- return SOUNDTRIGGER_STATUS_ERROR;
- }
- if (dataSize != 0) {
- memcpy((char *)memory->unsecurePointer() + sizeof(struct sound_trigger_recognition_config),
- nData,
- dataSize);
- env->ReleaseByteArrayElements(jData, nData, 0);
- }
- env->DeleteLocalRef(jData);
- struct sound_trigger_recognition_config *config =
- (struct sound_trigger_recognition_config *)memory->unsecurePointer();
- config->data_size = dataSize;
- config->data_offset = sizeof(struct sound_trigger_recognition_config);
- config->capture_requested = env->GetBooleanField(jConfig,
- gRecognitionConfigFields.captureRequested);
-
- config->num_phrases = 0;
- jobjectArray jPhrases =
- (jobjectArray)env->GetObjectField(jConfig, gRecognitionConfigFields.keyphrases);
- if (jPhrases != NULL) {
- config->num_phrases = env->GetArrayLength(jPhrases);
- }
- ALOGV("startRecognition num phrases %d", config->num_phrases);
- for (size_t i = 0; i < config->num_phrases; i++) {
- jobject jPhrase = env->GetObjectArrayElement(jPhrases, i);
- config->phrases[i].id = env->GetIntField(jPhrase,
- gKeyphraseRecognitionExtraFields.id);
- config->phrases[i].recognition_modes = env->GetIntField(jPhrase,
- gKeyphraseRecognitionExtraFields.recognitionModes);
- config->phrases[i].confidence_level = env->GetIntField(jPhrase,
- gKeyphraseRecognitionExtraFields.coarseConfidenceLevel);
- config->phrases[i].num_levels = 0;
- jobjectArray jConfidenceLevels = (jobjectArray)env->GetObjectField(jPhrase,
- gKeyphraseRecognitionExtraFields.confidenceLevels);
- if (jConfidenceLevels != NULL) {
- config->phrases[i].num_levels = env->GetArrayLength(jConfidenceLevels);
- }
- ALOGV("startRecognition phrase %zu num_levels %d", i, config->phrases[i].num_levels);
- for (size_t j = 0; j < config->phrases[i].num_levels; j++) {
- jobject jConfidenceLevel = env->GetObjectArrayElement(jConfidenceLevels, j);
- config->phrases[i].levels[j].user_id = env->GetIntField(jConfidenceLevel,
- gConfidenceLevelFields.userId);
- config->phrases[i].levels[j].level = env->GetIntField(jConfidenceLevel,
- gConfidenceLevelFields.confidenceLevel);
- env->DeleteLocalRef(jConfidenceLevel);
- }
- ALOGV("startRecognition phrases %zu", i);
- env->DeleteLocalRef(jConfidenceLevels);
- env->DeleteLocalRef(jPhrase);
- }
- env->DeleteLocalRef(jPhrases);
-
- status = module->startRecognition(jHandle, memory);
- return status;
-}
-
-static jint
-android_hardware_SoundTrigger_stopRecognition(JNIEnv *env, jobject thiz,
- jint jHandle)
-{
- jint status = SOUNDTRIGGER_STATUS_OK;
- ALOGV("stopRecognition");
- sp<SoundTrigger> module = getSoundTrigger(env, thiz);
- if (module == NULL) {
- return SOUNDTRIGGER_STATUS_ERROR;
- }
- status = module->stopRecognition(jHandle);
- return status;
-}
-
-static jint
-android_hardware_SoundTrigger_getModelState(JNIEnv *env, jobject thiz,
- jint jHandle)
-{
- jint status = SOUNDTRIGGER_STATUS_OK;
- ALOGV("getModelState");
- sp<SoundTrigger> module = getSoundTrigger(env, thiz);
- if (module == NULL) {
- return SOUNDTRIGGER_STATUS_ERROR;
- }
- status = module->getModelState(jHandle);
- return status;
-}
-
-static jint
-android_hardware_SoundTrigger_setParameter(JNIEnv *env, jobject thiz,
- jint jHandle, jint jModelParam, jint jValue)
-{
- ALOGV("setParameter");
- sp<SoundTrigger> module = getSoundTrigger(env, thiz);
- if (module == NULL) {
- return SOUNDTRIGGER_STATUS_NO_INIT;
- }
- return module->setParameter(jHandle, (sound_trigger_model_parameter_t) jModelParam, jValue);
-}
-
-static jint
-android_hardware_SoundTrigger_getParameter(JNIEnv *env, jobject thiz,
- jint jHandle, jint jModelParam)
-{
- ALOGV("getParameter");
- sp<SoundTrigger> module = getSoundTrigger(env, thiz);
- if (module == NULL) {
- throwUnsupportedOperationException(env);
- return -1;
- }
-
- jint nValue;
- jint status = module->getParameter(jHandle,
- (sound_trigger_model_parameter_t) jModelParam, &nValue);
-
- switch (status) {
- case 0:
- return nValue;
- case -EINVAL:
- throwIllegalArgumentException(env);
- break;
- default:
- throwUnsupportedOperationException(env);
- break;
- }
-
- return -1;
-}
-
-static jobject
-android_hardware_SoundTrigger_queryParameter(JNIEnv *env, jobject thiz,
- jint jHandle, jint jModelParam)
-{
- ALOGV("queryParameter");
- sp<SoundTrigger> module = getSoundTrigger(env, thiz);
- if (module == nullptr) {
- return nullptr;
- }
-
- sound_trigger_model_parameter_range_t nRange;
- jint nValue = module->queryParameter(jHandle,
- (sound_trigger_model_parameter_t) jModelParam, &nRange);
-
- if (nValue != 0) {
- ALOGE("failed to query parameter error code: %d", nValue);
- return nullptr;
- }
-
- return env->NewObject(gModelParamRangeClass, gModelParamRangeCstor, nRange.start, nRange.end);
-}
-
-static const JNINativeMethod gMethods[] = {
- {"listModules",
- "(Ljava/lang/String;Ljava/util/ArrayList;)I",
- (void *)android_hardware_SoundTrigger_listModules},
-};
-
-
-static const JNINativeMethod gModuleMethods[] = {
- {"native_setup",
- "(Ljava/lang/String;Ljava/lang/Object;)V",
- (void *)android_hardware_SoundTrigger_setup},
- {"native_finalize",
- "()V",
- (void *)android_hardware_SoundTrigger_finalize},
- {"detach",
- "()V",
- (void *)android_hardware_SoundTrigger_detach},
- {"loadSoundModel",
- "(Landroid/hardware/soundtrigger/SoundTrigger$SoundModel;[I)I",
- (void *)android_hardware_SoundTrigger_loadSoundModel},
- {"unloadSoundModel",
- "(I)I",
- (void *)android_hardware_SoundTrigger_unloadSoundModel},
- {"startRecognition",
- "(ILandroid/hardware/soundtrigger/SoundTrigger$RecognitionConfig;)I",
- (void *)android_hardware_SoundTrigger_startRecognition},
- {"stopRecognition",
- "(I)I",
- (void *)android_hardware_SoundTrigger_stopRecognition},
- {"getModelState",
- "(I)I",
- (void *)android_hardware_SoundTrigger_getModelState},
- {"setParameter",
- "(III)I",
- (void *)android_hardware_SoundTrigger_setParameter},
- {"getParameter",
- "(II)I",
- (void *)android_hardware_SoundTrigger_getParameter},
- {"queryParameter",
- "(II)Landroid/hardware/soundtrigger/SoundTrigger$ModelParamRange;",
- (void *)android_hardware_SoundTrigger_queryParameter}
-};
-
-int register_android_hardware_SoundTrigger(JNIEnv *env)
-{
- jclass arrayListClass = FindClassOrDie(env, "java/util/ArrayList");
- gArrayListClass = MakeGlobalRefOrDie(env, arrayListClass);
- gArrayListMethods.add = GetMethodIDOrDie(env, arrayListClass, "add", "(Ljava/lang/Object;)Z");
-
- jclass uuidClass = FindClassOrDie(env, "java/util/UUID");
- gUUIDClass = MakeGlobalRefOrDie(env, uuidClass);
- gUUIDMethods.toString = GetMethodIDOrDie(env, uuidClass, "toString", "()Ljava/lang/String;");
-
- jclass exUClass = FindClassOrDie(env, kUnsupportedOperationExceptionClassPathName);
- gUnsupportedOperationExceptionClass = MakeGlobalRefOrDie(env, exUClass);
-
- jclass exIClass = FindClassOrDie(env, kIllegalArgumentExceptionClassPathName);
- gIllegalArgumentExceptionClass = MakeGlobalRefOrDie(env, exIClass);
-
- jclass lClass = FindClassOrDie(env, kSoundTriggerClassPathName);
- gSoundTriggerClass = MakeGlobalRefOrDie(env, lClass);
-
- jclass moduleClass = FindClassOrDie(env, kModuleClassPathName);
- gModuleClass = MakeGlobalRefOrDie(env, moduleClass);
- gPostEventFromNative = GetStaticMethodIDOrDie(env, moduleClass, "postEventFromNative",
- "(Ljava/lang/Object;IIILjava/lang/Object;)V");
- gModuleFields.mNativeContext = GetFieldIDOrDie(env, moduleClass, "mNativeContext", "J");
- gModuleFields.mId = GetFieldIDOrDie(env, moduleClass, "mId", "I");
-
- jclass modulePropertiesClass = FindClassOrDie(env, kModulePropertiesClassPathName);
- gModulePropertiesClass = MakeGlobalRefOrDie(env, modulePropertiesClass);
- gModulePropertiesCstor = GetMethodIDOrDie(env, modulePropertiesClass, "<init>",
- "(ILjava/lang/String;Ljava/lang/String;Ljava/lang/String;IIIIIZIZIZ)V");
-
- jclass soundModelClass = FindClassOrDie(env, kSoundModelClassPathName);
- gSoundModelClass = MakeGlobalRefOrDie(env, soundModelClass);
- gSoundModelFields.uuid = GetFieldIDOrDie(env, soundModelClass, "uuid", "Ljava/util/UUID;");
- gSoundModelFields.vendorUuid = GetFieldIDOrDie(env, soundModelClass, "vendorUuid",
- "Ljava/util/UUID;");
- gSoundModelFields.data = GetFieldIDOrDie(env, soundModelClass, "data", "[B");
-
- jclass genericSoundModelClass = FindClassOrDie(env, kGenericSoundModelClassPathName);
- gGenericSoundModelClass = MakeGlobalRefOrDie(env, genericSoundModelClass);
-
- jclass keyphraseClass = FindClassOrDie(env, kKeyphraseClassPathName);
- gKeyphraseClass = MakeGlobalRefOrDie(env, keyphraseClass);
- gKeyphraseFields.id = GetFieldIDOrDie(env, keyphraseClass, "id", "I");
- gKeyphraseFields.recognitionModes = GetFieldIDOrDie(env, keyphraseClass, "recognitionModes",
- "I");
- gKeyphraseFields.locale = GetFieldIDOrDie(env, keyphraseClass, "locale", "Ljava/lang/String;");
- gKeyphraseFields.text = GetFieldIDOrDie(env, keyphraseClass, "text", "Ljava/lang/String;");
- gKeyphraseFields.users = GetFieldIDOrDie(env, keyphraseClass, "users", "[I");
-
- jclass keyphraseSoundModelClass = FindClassOrDie(env, kKeyphraseSoundModelClassPathName);
- gKeyphraseSoundModelClass = MakeGlobalRefOrDie(env, keyphraseSoundModelClass);
- gKeyphraseSoundModelFields.keyphrases = GetFieldIDOrDie(env, keyphraseSoundModelClass,
- "keyphrases",
- "[Landroid/hardware/soundtrigger/SoundTrigger$Keyphrase;");
-
- jclass modelParamRangeClass = FindClassOrDie(env, kModelParamRangeClassPathName);
- gModelParamRangeClass = MakeGlobalRefOrDie(env, modelParamRangeClass);
- gModelParamRangeCstor = GetMethodIDOrDie(env, modelParamRangeClass, "<init>", "(II)V");
-
- jclass recognitionEventClass = FindClassOrDie(env, kRecognitionEventClassPathName);
- gRecognitionEventClass = MakeGlobalRefOrDie(env, recognitionEventClass);
- gRecognitionEventCstor = GetMethodIDOrDie(env, recognitionEventClass, "<init>",
- "(IIZIIIZLandroid/media/AudioFormat;[B)V");
-
- jclass keyphraseRecognitionEventClass = FindClassOrDie(env,
- kKeyphraseRecognitionEventClassPathName);
- gKeyphraseRecognitionEventClass = MakeGlobalRefOrDie(env, keyphraseRecognitionEventClass);
- gKeyphraseRecognitionEventCstor = GetMethodIDOrDie(env, keyphraseRecognitionEventClass, "<init>",
- "(IIZIIIZLandroid/media/AudioFormat;[B[Landroid/hardware/soundtrigger/SoundTrigger$KeyphraseRecognitionExtra;)V");
-
- jclass genericRecognitionEventClass = FindClassOrDie(env,
- kGenericRecognitionEventClassPathName);
- gGenericRecognitionEventClass = MakeGlobalRefOrDie(env, genericRecognitionEventClass);
- gGenericRecognitionEventCstor = GetMethodIDOrDie(env, genericRecognitionEventClass, "<init>",
- "(IIZIIIZLandroid/media/AudioFormat;[B)V");
-
- jclass keyRecognitionConfigClass = FindClassOrDie(env, kRecognitionConfigClassPathName);
- gRecognitionConfigClass = MakeGlobalRefOrDie(env, keyRecognitionConfigClass);
- gRecognitionConfigFields.captureRequested = GetFieldIDOrDie(env, keyRecognitionConfigClass,
- "captureRequested", "Z");
- gRecognitionConfigFields.keyphrases = GetFieldIDOrDie(env, keyRecognitionConfigClass,
- "keyphrases", "[Landroid/hardware/soundtrigger/SoundTrigger$KeyphraseRecognitionExtra;");
- gRecognitionConfigFields.data = GetFieldIDOrDie(env, keyRecognitionConfigClass, "data", "[B");
-
- jclass keyphraseRecognitionExtraClass = FindClassOrDie(env,
- kKeyphraseRecognitionExtraClassPathName);
- gKeyphraseRecognitionExtraClass = MakeGlobalRefOrDie(env, keyphraseRecognitionExtraClass);
- gKeyphraseRecognitionExtraCstor = GetMethodIDOrDie(env, keyphraseRecognitionExtraClass,
- "<init>", "(III[Landroid/hardware/soundtrigger/SoundTrigger$ConfidenceLevel;)V");
- gKeyphraseRecognitionExtraFields.id = GetFieldIDOrDie(env, gKeyphraseRecognitionExtraClass,
- "id", "I");
- gKeyphraseRecognitionExtraFields.recognitionModes = GetFieldIDOrDie(env,
- gKeyphraseRecognitionExtraClass, "recognitionModes", "I");
- gKeyphraseRecognitionExtraFields.coarseConfidenceLevel = GetFieldIDOrDie(env,
- gKeyphraseRecognitionExtraClass, "coarseConfidenceLevel", "I");
- gKeyphraseRecognitionExtraFields.confidenceLevels = GetFieldIDOrDie(env,
- gKeyphraseRecognitionExtraClass, "confidenceLevels",
- "[Landroid/hardware/soundtrigger/SoundTrigger$ConfidenceLevel;");
-
- jclass confidenceLevelClass = FindClassOrDie(env, kConfidenceLevelClassPathName);
- gConfidenceLevelClass = MakeGlobalRefOrDie(env, confidenceLevelClass);
- gConfidenceLevelCstor = GetMethodIDOrDie(env, confidenceLevelClass, "<init>", "(II)V");
- gConfidenceLevelFields.userId = GetFieldIDOrDie(env, confidenceLevelClass, "userId", "I");
- gConfidenceLevelFields.confidenceLevel = GetFieldIDOrDie(env, confidenceLevelClass,
- "confidenceLevel", "I");
-
- jclass audioFormatClass = FindClassOrDie(env, kAudioFormatClassPathName);
- gAudioFormatClass = MakeGlobalRefOrDie(env, audioFormatClass);
- gAudioFormatCstor = GetMethodIDOrDie(env, audioFormatClass, "<init>", "(IIII)V");
-
- jclass soundModelEventClass = FindClassOrDie(env, kSoundModelEventClassPathName);
- gSoundModelEventClass = MakeGlobalRefOrDie(env, soundModelEventClass);
- gSoundModelEventCstor = GetMethodIDOrDie(env, soundModelEventClass, "<init>", "(II[B)V");
-
-
- RegisterMethodsOrDie(env, kSoundTriggerClassPathName, gMethods, NELEM(gMethods));
- return RegisterMethodsOrDie(env, kModuleClassPathName, gModuleMethods, NELEM(gModuleMethods));
-}
diff --git a/core/res/AndroidManifest.xml b/core/res/AndroidManifest.xml
index c925744fea3b..cb5b4a595ac3 100644
--- a/core/res/AndroidManifest.xml
+++ b/core/res/AndroidManifest.xml
@@ -4410,6 +4410,12 @@
<permission android:name="android.permission.MANAGE_SOUND_TRIGGER"
android:protectionLevel="signature|privileged" />
+ <!-- Allows preempting sound trigger recognitions for the sake of capturing audio on
+ implementations which do not support running both concurrently.
+ @hide -->
+ <permission android:name="android.permission.PREEMPT_SOUND_TRIGGER"
+ android:protectionLevel="signature|privileged" />
+
<!-- Must be required by system/priv apps implementing sound trigger detection services
@hide
@SystemApi -->
diff --git a/data/etc/platform.xml b/data/etc/platform.xml
index 80098c5a81f5..0574775712a6 100644
--- a/data/etc/platform.xml
+++ b/data/etc/platform.xml
@@ -157,6 +157,7 @@
<assign-permission name="android.permission.UPDATE_DEVICE_STATS" uid="audioserver" />
<assign-permission name="android.permission.UPDATE_APP_OPS_STATS" uid="audioserver" />
<assign-permission name="android.permission.PACKAGE_USAGE_STATS" uid="audioserver" />
+ <assign-permission name="android.permission.PREEMPT_SOUND_TRIGGER" uid="audioserver" />
<assign-permission name="android.permission.MODIFY_AUDIO_SETTINGS" uid="cameraserver" />
<assign-permission name="android.permission.ACCESS_SURFACE_FLINGER" uid="cameraserver" />
diff --git a/media/Android.bp b/media/Android.bp
index 4b50b7ad1aea..75ccb227e5c5 100644
--- a/media/Android.bp
+++ b/media/Android.bp
@@ -102,3 +102,67 @@ java_library {
srcs: [":framework-media-annotation-srcs"],
installable: false,
}
+
+aidl_interface {
+ name: "audio_common-aidl",
+ local_include_dir: "java",
+ srcs: [
+ "java/android/media/audio/common/AudioChannelMask.aidl",
+ "java/android/media/audio/common/AudioConfig.aidl",
+ "java/android/media/audio/common/AudioFormat.aidl",
+ "java/android/media/audio/common/AudioOffloadInfo.aidl",
+ "java/android/media/audio/common/AudioStreamType.aidl",
+ "java/android/media/audio/common/AudioUsage.aidl",
+ ],
+ backend:
+ {
+ cpp: {
+ enabled: true,
+ },
+ java: {
+ // Already generated as part of the entire media java library.
+ enabled: false,
+ },
+ },
+}
+
+aidl_interface {
+ name: "soundtrigger_middleware-aidl",
+ local_include_dir: "java",
+ srcs: [
+ "java/android/media/soundtrigger_middleware/ConfidenceLevel.aidl",
+ "java/android/media/soundtrigger_middleware/ISoundTriggerCallback.aidl",
+ "java/android/media/soundtrigger_middleware/ISoundTriggerMiddlewareService.aidl",
+ "java/android/media/soundtrigger_middleware/ISoundTriggerModule.aidl",
+ "java/android/media/soundtrigger_middleware/ModelParameter.aidl",
+ "java/android/media/soundtrigger_middleware/ModelParameterRange.aidl",
+ "java/android/media/soundtrigger_middleware/Phrase.aidl",
+ "java/android/media/soundtrigger_middleware/PhraseRecognitionEvent.aidl",
+ "java/android/media/soundtrigger_middleware/PhraseRecognitionExtra.aidl",
+ "java/android/media/soundtrigger_middleware/PhraseSoundModel.aidl",
+ "java/android/media/soundtrigger_middleware/RecognitionConfig.aidl",
+ "java/android/media/soundtrigger_middleware/RecognitionEvent.aidl",
+ "java/android/media/soundtrigger_middleware/RecognitionMode.aidl",
+ "java/android/media/soundtrigger_middleware/RecognitionStatus.aidl",
+ "java/android/media/soundtrigger_middleware/SoundModel.aidl",
+ "java/android/media/soundtrigger_middleware/SoundModelType.aidl",
+ "java/android/media/soundtrigger_middleware/SoundTriggerModuleDescriptor.aidl",
+ "java/android/media/soundtrigger_middleware/SoundTriggerModuleProperties.aidl",
+ "java/android/media/soundtrigger_middleware/Status.aidl",
+ ],
+ backend:
+ {
+ cpp: {
+ enabled: true,
+ },
+ java: {
+ // Already generated as part of the entire media java library.
+ enabled: false,
+ },
+ ndk: {
+ // Not currently needed, and disabled because of b/146172425
+ enabled: false,
+ },
+ },
+ imports: [ "audio_common-aidl" ],
+}
diff --git a/media/java/android/media/audio/common/AudioChannelMask.aidl b/media/java/android/media/audio/common/AudioChannelMask.aidl
new file mode 100644
index 000000000000..b9b08e6921bc
--- /dev/null
+++ b/media/java/android/media/audio/common/AudioChannelMask.aidl
@@ -0,0 +1,217 @@
+/*
+ * Copyright (C) 2019 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.
+ */
+
+ // This file has been semi-automatically generated using hidl2aidl from its counterpart in
+ // hardware/interfaces/audio/common/5.0/types.hal
+
+package android.media.audio.common;
+
+/**
+ * A channel mask per se only defines the presence or absence of a channel, not
+ * the order.
+ *
+ * The channel order convention is that channels are interleaved in order from
+ * least significant channel mask bit to most significant channel mask bit,
+ * with unused bits skipped. For example for stereo, LEFT would be first,
+ * followed by RIGHT.
+ * Any exceptions to this convention are noted at the appropriate API.
+ *
+ * AudioChannelMask is an opaque type and its internal layout should not be
+ * assumed as it may change in the future. Instead, always use functions
+ * to examine it.
+ *
+ * These are the current representations:
+ *
+ * REPRESENTATION_POSITION
+ * is a channel mask representation for position assignment. Each low-order
+ * bit corresponds to the spatial position of a transducer (output), or
+ * interpretation of channel (input). The user of a channel mask needs to
+ * know the context of whether it is for output or input. The constants
+ * OUT_* or IN_* apply to the bits portion. It is not permitted for no bits
+ * to be set.
+ *
+ * REPRESENTATION_INDEX
+ * is a channel mask representation for index assignment. Each low-order
+ * bit corresponds to a selected channel. There is no platform
+ * interpretation of the various bits. There is no concept of output or
+ * input. It is not permitted for no bits to be set.
+ *
+ * All other representations are reserved for future use.
+ *
+ * Warning: current representation distinguishes between input and output, but
+ * this will not the be case in future revisions of the platform. Wherever there
+ * is an ambiguity between input and output that is currently resolved by
+ * checking the channel mask, the implementer should look for ways to fix it
+ * with additional information outside of the mask.
+ *
+ * {@hide}
+ */
+@Backing(type="int")
+enum AudioChannelMask {
+ /**
+ * must be 0 for compatibility
+ */
+ REPRESENTATION_POSITION = 0,
+ /**
+ * 1 is reserved for future use
+ */
+ REPRESENTATION_INDEX = 2,
+ /**
+ * 3 is reserved for future use
+ *
+ *
+ * These can be a complete value of AudioChannelMask
+ */
+ NONE = 0x0,
+ INVALID = 0xC0000000,
+ /**
+ * These can be the bits portion of an AudioChannelMask
+ * with representation REPRESENTATION_POSITION.
+ *
+ *
+ * output channels
+ */
+ OUT_FRONT_LEFT = 0x1,
+ OUT_FRONT_RIGHT = 0x2,
+ OUT_FRONT_CENTER = 0x4,
+ OUT_LOW_FREQUENCY = 0x8,
+ OUT_BACK_LEFT = 0x10,
+ OUT_BACK_RIGHT = 0x20,
+ OUT_FRONT_LEFT_OF_CENTER = 0x40,
+ OUT_FRONT_RIGHT_OF_CENTER = 0x80,
+ OUT_BACK_CENTER = 0x100,
+ OUT_SIDE_LEFT = 0x200,
+ OUT_SIDE_RIGHT = 0x400,
+ OUT_TOP_CENTER = 0x800,
+ OUT_TOP_FRONT_LEFT = 0x1000,
+ OUT_TOP_FRONT_CENTER = 0x2000,
+ OUT_TOP_FRONT_RIGHT = 0x4000,
+ OUT_TOP_BACK_LEFT = 0x8000,
+ OUT_TOP_BACK_CENTER = 0x10000,
+ OUT_TOP_BACK_RIGHT = 0x20000,
+ OUT_TOP_SIDE_LEFT = 0x40000,
+ OUT_TOP_SIDE_RIGHT = 0x80000,
+ /**
+ * Haptic channel characteristics are specific to a device and
+ * only used to play device specific resources (eg: ringtones).
+ * The HAL can freely map A and B to haptic controllers, the
+ * framework shall not interpret those values and forward them
+ * from the device audio assets.
+ */
+ OUT_HAPTIC_A = 0x20000000,
+ OUT_HAPTIC_B = 0x10000000,
+// TODO(ytai): Aliases not currently supported in AIDL - can inline the values.
+// OUT_MONO = OUT_FRONT_LEFT,
+// OUT_STEREO = (OUT_FRONT_LEFT | OUT_FRONT_RIGHT),
+// OUT_2POINT1 = (OUT_FRONT_LEFT | OUT_FRONT_RIGHT | OUT_LOW_FREQUENCY),
+// OUT_2POINT0POINT2 = (OUT_FRONT_LEFT | OUT_FRONT_RIGHT | OUT_TOP_SIDE_LEFT | OUT_TOP_SIDE_RIGHT),
+// OUT_2POINT1POINT2 = (OUT_FRONT_LEFT | OUT_FRONT_RIGHT | OUT_TOP_SIDE_LEFT | OUT_TOP_SIDE_RIGHT | OUT_LOW_FREQUENCY),
+// OUT_3POINT0POINT2 = (OUT_FRONT_LEFT | OUT_FRONT_RIGHT | OUT_FRONT_CENTER | OUT_TOP_SIDE_LEFT | OUT_TOP_SIDE_RIGHT),
+// OUT_3POINT1POINT2 = (OUT_FRONT_LEFT | OUT_FRONT_RIGHT | OUT_FRONT_CENTER | OUT_TOP_SIDE_LEFT | OUT_TOP_SIDE_RIGHT | OUT_LOW_FREQUENCY),
+// OUT_QUAD = (OUT_FRONT_LEFT | OUT_FRONT_RIGHT | OUT_BACK_LEFT | OUT_BACK_RIGHT),
+// OUT_QUAD_BACK = OUT_QUAD,
+// /**
+// * like OUT_QUAD_BACK with *_SIDE_* instead of *_BACK_*
+// */
+// OUT_QUAD_SIDE = (OUT_FRONT_LEFT | OUT_FRONT_RIGHT | OUT_SIDE_LEFT | OUT_SIDE_RIGHT),
+// OUT_SURROUND = (OUT_FRONT_LEFT | OUT_FRONT_RIGHT | OUT_FRONT_CENTER | OUT_BACK_CENTER),
+// OUT_PENTA = (OUT_QUAD | OUT_FRONT_CENTER),
+// OUT_5POINT1 = (OUT_FRONT_LEFT | OUT_FRONT_RIGHT | OUT_FRONT_CENTER | OUT_LOW_FREQUENCY | OUT_BACK_LEFT | OUT_BACK_RIGHT),
+// OUT_5POINT1_BACK = OUT_5POINT1,
+// /**
+// * like OUT_5POINT1_BACK with *_SIDE_* instead of *_BACK_*
+// */
+// OUT_5POINT1_SIDE = (OUT_FRONT_LEFT | OUT_FRONT_RIGHT | OUT_FRONT_CENTER | OUT_LOW_FREQUENCY | OUT_SIDE_LEFT | OUT_SIDE_RIGHT),
+// OUT_5POINT1POINT2 = (OUT_5POINT1 | OUT_TOP_SIDE_LEFT | OUT_TOP_SIDE_RIGHT),
+// OUT_5POINT1POINT4 = (OUT_5POINT1 | OUT_TOP_FRONT_LEFT | OUT_TOP_FRONT_RIGHT | OUT_TOP_BACK_LEFT | OUT_TOP_BACK_RIGHT),
+// OUT_6POINT1 = (OUT_FRONT_LEFT | OUT_FRONT_RIGHT | OUT_FRONT_CENTER | OUT_LOW_FREQUENCY | OUT_BACK_LEFT | OUT_BACK_RIGHT | OUT_BACK_CENTER),
+// /**
+// * matches the correct AudioFormat.CHANNEL_OUT_7POINT1_SURROUND
+// */
+// OUT_7POINT1 = (OUT_FRONT_LEFT | OUT_FRONT_RIGHT | OUT_FRONT_CENTER | OUT_LOW_FREQUENCY | OUT_BACK_LEFT | OUT_BACK_RIGHT | OUT_SIDE_LEFT | OUT_SIDE_RIGHT),
+// OUT_7POINT1POINT2 = (OUT_7POINT1 | OUT_TOP_SIDE_LEFT | OUT_TOP_SIDE_RIGHT),
+// OUT_7POINT1POINT4 = (OUT_7POINT1 | OUT_TOP_FRONT_LEFT | OUT_TOP_FRONT_RIGHT | OUT_TOP_BACK_LEFT | OUT_TOP_BACK_RIGHT),
+// OUT_MONO_HAPTIC_A = (OUT_FRONT_LEFT | OUT_HAPTIC_A),
+// OUT_STEREO_HAPTIC_A = (OUT_FRONT_LEFT | OUT_FRONT_RIGHT | OUT_HAPTIC_A),
+// OUT_HAPTIC_AB = (OUT_HAPTIC_A | OUT_HAPTIC_B),
+// OUT_MONO_HAPTIC_AB = (OUT_FRONT_LEFT | OUT_HAPTIC_A | OUT_HAPTIC_B),
+// OUT_STEREO_HAPTIC_AB = (OUT_FRONT_LEFT | OUT_FRONT_RIGHT | OUT_HAPTIC_A | OUT_HAPTIC_B),
+ /**
+ * These are bits only, not complete values
+ *
+ *
+ * input channels
+ */
+ IN_LEFT = 0x4,
+ IN_RIGHT = 0x8,
+ IN_FRONT = 0x10,
+ IN_BACK = 0x20,
+ IN_LEFT_PROCESSED = 0x40,
+ IN_RIGHT_PROCESSED = 0x80,
+ IN_FRONT_PROCESSED = 0x100,
+ IN_BACK_PROCESSED = 0x200,
+ IN_PRESSURE = 0x400,
+ IN_X_AXIS = 0x800,
+ IN_Y_AXIS = 0x1000,
+ IN_Z_AXIS = 0x2000,
+ IN_BACK_LEFT = 0x10000,
+ IN_BACK_RIGHT = 0x20000,
+ IN_CENTER = 0x40000,
+ IN_LOW_FREQUENCY = 0x100000,
+ IN_TOP_LEFT = 0x200000,
+ IN_TOP_RIGHT = 0x400000,
+ IN_VOICE_UPLINK = 0x4000,
+ IN_VOICE_DNLINK = 0x8000,
+// TODO(ytai): Aliases not currently supported in AIDL - can inline the values.
+// IN_MONO = IN_FRONT,
+// IN_STEREO = (IN_LEFT | IN_RIGHT),
+// IN_FRONT_BACK = (IN_FRONT | IN_BACK),
+// IN_6 = (IN_LEFT | IN_RIGHT | IN_FRONT | IN_BACK | IN_LEFT_PROCESSED | IN_RIGHT_PROCESSED),
+// IN_2POINT0POINT2 = (IN_LEFT | IN_RIGHT | IN_TOP_LEFT | IN_TOP_RIGHT),
+// IN_2POINT1POINT2 = (IN_LEFT | IN_RIGHT | IN_TOP_LEFT | IN_TOP_RIGHT | IN_LOW_FREQUENCY),
+// IN_3POINT0POINT2 = (IN_LEFT | IN_CENTER | IN_RIGHT | IN_TOP_LEFT | IN_TOP_RIGHT),
+// IN_3POINT1POINT2 = (IN_LEFT | IN_CENTER | IN_RIGHT | IN_TOP_LEFT | IN_TOP_RIGHT | IN_LOW_FREQUENCY),
+// IN_5POINT1 = (IN_LEFT | IN_CENTER | IN_RIGHT | IN_BACK_LEFT | IN_BACK_RIGHT | IN_LOW_FREQUENCY),
+// IN_VOICE_UPLINK_MONO = (IN_VOICE_UPLINK | IN_MONO),
+// IN_VOICE_DNLINK_MONO = (IN_VOICE_DNLINK | IN_MONO),
+// IN_VOICE_CALL_MONO = (IN_VOICE_UPLINK_MONO | IN_VOICE_DNLINK_MONO),
+// COUNT_MAX = 30,
+// INDEX_HDR = REPRESENTATION_INDEX << COUNT_MAX,
+// INDEX_MASK_1 = INDEX_HDR | ((1 << 1) - 1),
+// INDEX_MASK_2 = INDEX_HDR | ((1 << 2) - 1),
+// INDEX_MASK_3 = INDEX_HDR | ((1 << 3) - 1),
+// INDEX_MASK_4 = INDEX_HDR | ((1 << 4) - 1),
+// INDEX_MASK_5 = INDEX_HDR | ((1 << 5) - 1),
+// INDEX_MASK_6 = INDEX_HDR | ((1 << 6) - 1),
+// INDEX_MASK_7 = INDEX_HDR | ((1 << 7) - 1),
+// INDEX_MASK_8 = INDEX_HDR | ((1 << 8) - 1),
+// INDEX_MASK_9 = INDEX_HDR | ((1 << 9) - 1),
+// INDEX_MASK_10 = INDEX_HDR | ((1 << 10) - 1),
+// INDEX_MASK_11 = INDEX_HDR | ((1 << 11) - 1),
+// INDEX_MASK_12 = INDEX_HDR | ((1 << 12) - 1),
+// INDEX_MASK_13 = INDEX_HDR | ((1 << 13) - 1),
+// INDEX_MASK_14 = INDEX_HDR | ((1 << 14) - 1),
+// INDEX_MASK_15 = INDEX_HDR | ((1 << 15) - 1),
+// INDEX_MASK_16 = INDEX_HDR | ((1 << 16) - 1),
+// INDEX_MASK_17 = INDEX_HDR | ((1 << 17) - 1),
+// INDEX_MASK_18 = INDEX_HDR | ((1 << 18) - 1),
+// INDEX_MASK_19 = INDEX_HDR | ((1 << 19) - 1),
+// INDEX_MASK_20 = INDEX_HDR | ((1 << 20) - 1),
+// INDEX_MASK_21 = INDEX_HDR | ((1 << 21) - 1),
+// INDEX_MASK_22 = INDEX_HDR | ((1 << 22) - 1),
+// INDEX_MASK_23 = INDEX_HDR | ((1 << 23) - 1),
+// INDEX_MASK_24 = INDEX_HDR | ((1 << 24) - 1),
+}
diff --git a/media/java/android/media/audio/common/AudioConfig.aidl b/media/java/android/media/audio/common/AudioConfig.aidl
new file mode 100644
index 000000000000..50dd796e1fa0
--- /dev/null
+++ b/media/java/android/media/audio/common/AudioConfig.aidl
@@ -0,0 +1,36 @@
+/*
+ * Copyright (C) 2019 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.
+ */
+
+ // This file has been semi-automatically generated using hidl2aidl from its counterpart in
+ // hardware/interfaces/audio/common/5.0/types.hal
+
+package android.media.audio.common;
+
+import android.media.audio.common.AudioFormat;
+import android.media.audio.common.AudioOffloadInfo;
+
+/**
+ * Commonly used audio stream configuration parameters.
+ *
+ * {@hide}
+ */
+parcelable AudioConfig {
+ int sampleRateHz;
+ int channelMask;
+ AudioFormat format;
+ AudioOffloadInfo offloadInfo;
+ long frameCount;
+}
diff --git a/media/java/android/media/audio/common/AudioFormat.aidl b/media/java/android/media/audio/common/AudioFormat.aidl
new file mode 100644
index 000000000000..aadc8e26cce3
--- /dev/null
+++ b/media/java/android/media/audio/common/AudioFormat.aidl
@@ -0,0 +1,170 @@
+/*
+ * Copyright (C) 2019 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.
+ */
+
+ // This file has been semi-automatically generated using hidl2aidl from its counterpart in
+ // hardware/interfaces/audio/common/5.0/types.hal
+
+package android.media.audio.common;
+
+/**
+ * Audio format is a 32-bit word that consists of:
+ * main format field (upper 8 bits)
+ * sub format field (lower 24 bits).
+ *
+ * The main format indicates the main codec type. The sub format field indicates
+ * options and parameters for each format. The sub format is mainly used for
+ * record to indicate for instance the requested bitrate or profile. It can
+ * also be used for certain formats to give informations not present in the
+ * encoded audio stream (e.g. octet alignement for AMR).
+ *
+ * {@hide}
+ */
+@Backing(type="int")
+enum AudioFormat {
+ INVALID = 0xFFFFFFFF,
+ DEFAULT = 0,
+ PCM = 0x00000000,
+ MP3 = 0x01000000,
+ AMR_NB = 0x02000000,
+ AMR_WB = 0x03000000,
+ AAC = 0x04000000,
+ /**
+ * Deprecated, Use AAC_HE_V1
+ */
+ HE_AAC_V1 = 0x05000000,
+ /**
+ * Deprecated, Use AAC_HE_V2
+ */
+ HE_AAC_V2 = 0x06000000,
+ VORBIS = 0x07000000,
+ OPUS = 0x08000000,
+ AC3 = 0x09000000,
+ E_AC3 = 0x0A000000,
+ DTS = 0x0B000000,
+ DTS_HD = 0x0C000000,
+ /**
+ * IEC61937 is encoded audio wrapped in 16-bit PCM.
+ */
+ IEC61937 = 0x0D000000,
+ DOLBY_TRUEHD = 0x0E000000,
+ EVRC = 0x10000000,
+ EVRCB = 0x11000000,
+ EVRCWB = 0x12000000,
+ EVRCNW = 0x13000000,
+ AAC_ADIF = 0x14000000,
+ WMA = 0x15000000,
+ WMA_PRO = 0x16000000,
+ AMR_WB_PLUS = 0x17000000,
+ MP2 = 0x18000000,
+ QCELP = 0x19000000,
+ DSD = 0x1A000000,
+ FLAC = 0x1B000000,
+ ALAC = 0x1C000000,
+ APE = 0x1D000000,
+ AAC_ADTS = 0x1E000000,
+ SBC = 0x1F000000,
+ APTX = 0x20000000,
+ APTX_HD = 0x21000000,
+ AC4 = 0x22000000,
+ LDAC = 0x23000000,
+ /**
+ * Dolby Metadata-enhanced Audio Transmission
+ */
+ MAT = 0x24000000,
+ AAC_LATM = 0x25000000,
+ CELT = 0x26000000,
+ APTX_ADAPTIVE = 0x27000000,
+ LHDC = 0x28000000,
+ LHDC_LL = 0x29000000,
+ APTX_TWSP = 0x2A000000,
+ /**
+ * Deprecated
+ */
+ MAIN_MASK = 0xFF000000,
+ SUB_MASK = 0x00FFFFFF,
+ /**
+ * Subformats
+ */
+ PCM_SUB_16_BIT = 0x1,
+ PCM_SUB_8_BIT = 0x2,
+ PCM_SUB_32_BIT = 0x3,
+ PCM_SUB_8_24_BIT = 0x4,
+ PCM_SUB_FLOAT = 0x5,
+ PCM_SUB_24_BIT_PACKED = 0x6,
+ MP3_SUB_NONE = 0x0,
+ AMR_SUB_NONE = 0x0,
+ AAC_SUB_MAIN = 0x1,
+ AAC_SUB_LC = 0x2,
+ AAC_SUB_SSR = 0x4,
+ AAC_SUB_LTP = 0x8,
+ AAC_SUB_HE_V1 = 0x10,
+ AAC_SUB_SCALABLE = 0x20,
+ AAC_SUB_ERLC = 0x40,
+ AAC_SUB_LD = 0x80,
+ AAC_SUB_HE_V2 = 0x100,
+ AAC_SUB_ELD = 0x200,
+ AAC_SUB_XHE = 0x300,
+ VORBIS_SUB_NONE = 0x0,
+ E_AC3_SUB_JOC = 0x1,
+ MAT_SUB_1_0 = 0x1,
+ MAT_SUB_2_0 = 0x2,
+ MAT_SUB_2_1 = 0x3,
+// TODO(ytai): Aliases not currently supported in AIDL - can inline the values.
+// /**
+// * Aliases
+// *
+// *
+// * note != AudioFormat.ENCODING_PCM_16BIT
+// */
+// PCM_16_BIT = (PCM | PCM_SUB_16_BIT),
+// /**
+// * note != AudioFormat.ENCODING_PCM_8BIT
+// */
+// PCM_8_BIT = (PCM | PCM_SUB_8_BIT),
+// PCM_32_BIT = (PCM | PCM_SUB_32_BIT),
+// PCM_8_24_BIT = (PCM | PCM_SUB_8_24_BIT),
+// PCM_FLOAT = (PCM | PCM_SUB_FLOAT),
+// PCM_24_BIT_PACKED = (PCM | PCM_SUB_24_BIT_PACKED),
+// AAC_MAIN = (AAC | AAC_SUB_MAIN),
+// AAC_LC = (AAC | AAC_SUB_LC),
+// AAC_SSR = (AAC | AAC_SUB_SSR),
+// AAC_LTP = (AAC | AAC_SUB_LTP),
+// AAC_HE_V1 = (AAC | AAC_SUB_HE_V1),
+// AAC_SCALABLE = (AAC | AAC_SUB_SCALABLE),
+// AAC_ERLC = (AAC | AAC_SUB_ERLC),
+// AAC_LD = (AAC | AAC_SUB_LD),
+// AAC_HE_V2 = (AAC | AAC_SUB_HE_V2),
+// AAC_ELD = (AAC | AAC_SUB_ELD),
+// AAC_XHE = (AAC | AAC_SUB_XHE),
+// AAC_ADTS_MAIN = (AAC_ADTS | AAC_SUB_MAIN),
+// AAC_ADTS_LC = (AAC_ADTS | AAC_SUB_LC),
+// AAC_ADTS_SSR = (AAC_ADTS | AAC_SUB_SSR),
+// AAC_ADTS_LTP = (AAC_ADTS | AAC_SUB_LTP),
+// AAC_ADTS_HE_V1 = (AAC_ADTS | AAC_SUB_HE_V1),
+// AAC_ADTS_SCALABLE = (AAC_ADTS | AAC_SUB_SCALABLE),
+// AAC_ADTS_ERLC = (AAC_ADTS | AAC_SUB_ERLC),
+// AAC_ADTS_LD = (AAC_ADTS | AAC_SUB_LD),
+// AAC_ADTS_HE_V2 = (AAC_ADTS | AAC_SUB_HE_V2),
+// AAC_ADTS_ELD = (AAC_ADTS | AAC_SUB_ELD),
+// AAC_ADTS_XHE = (AAC_ADTS | AAC_SUB_XHE),
+// E_AC3_JOC = (E_AC3 | E_AC3_SUB_JOC),
+// MAT_1_0 = (MAT | MAT_SUB_1_0),
+// MAT_2_0 = (MAT | MAT_SUB_2_0),
+// MAT_2_1 = (MAT | MAT_SUB_2_1),
+// AAC_LATM_LC = (AAC_LATM | AAC_SUB_LC),
+// AAC_LATM_HE_V1 = (AAC_LATM | AAC_SUB_HE_V1),
+// AAC_LATM_HE_V2 = (AAC_LATM | AAC_SUB_HE_V2),
+}
diff --git a/media/java/android/media/audio/common/AudioOffloadInfo.aidl b/media/java/android/media/audio/common/AudioOffloadInfo.aidl
new file mode 100644
index 000000000000..ec10d71135ae
--- /dev/null
+++ b/media/java/android/media/audio/common/AudioOffloadInfo.aidl
@@ -0,0 +1,44 @@
+/*
+ * Copyright (C) 2019 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.
+ */
+
+ // This file has been semi-automatically generated using hidl2aidl from its counterpart in
+ // hardware/interfaces/audio/common/5.0/types.hal
+
+package android.media.audio.common;
+
+import android.media.audio.common.AudioFormat;
+import android.media.audio.common.AudioStreamType;
+import android.media.audio.common.AudioUsage;
+
+/**
+ * Additional information about the stream passed to hardware decoders.
+ *
+ * {@hide}
+ */
+parcelable AudioOffloadInfo {
+ int sampleRateHz;
+ int channelMask;
+ AudioFormat format;
+ AudioStreamType streamType;
+ int bitRatePerSecond;
+ long durationMicroseconds;
+ boolean hasVideo;
+ boolean isStreaming;
+ int bitWidth;
+ int bufferSize;
+ AudioUsage usage;
+}
+
diff --git a/media/java/android/media/audio/common/AudioStreamType.aidl b/media/java/android/media/audio/common/AudioStreamType.aidl
new file mode 100644
index 000000000000..c54566726350
--- /dev/null
+++ b/media/java/android/media/audio/common/AudioStreamType.aidl
@@ -0,0 +1,44 @@
+/*
+ * Copyright (C) 2019 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.
+ */
+
+ // This file has been semi-automatically generated using hidl2aidl from its counterpart in
+ // hardware/interfaces/audio/common/5.0/types.hal
+
+package android.media.audio.common;
+
+/**
+ * Audio streams
+ *
+ * Audio stream type describing the intended use case of a stream.
+ *
+ * {@hide}
+ */
+@Backing(type="int")
+enum AudioStreamType {
+ DEFAULT = -1,
+ MIN = 0,
+ VOICE_CALL = 0,
+ SYSTEM = 1,
+ RING = 2,
+ MUSIC = 3,
+ ALARM = 4,
+ NOTIFICATION = 5,
+ BLUETOOTH_SCO = 6,
+ ENFORCED_AUDIBLE = 7,
+ DTMF = 8,
+ TTS = 9,
+ ACCESSIBILITY = 10,
+}
diff --git a/media/java/android/media/audio/common/AudioUsage.aidl b/media/java/android/media/audio/common/AudioUsage.aidl
new file mode 100644
index 000000000000..ef348165b22c
--- /dev/null
+++ b/media/java/android/media/audio/common/AudioUsage.aidl
@@ -0,0 +1,40 @@
+/*
+ * Copyright (C) 2019 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.
+ */
+
+ // This file has been semi-automatically generated using hidl2aidl from its counterpart in
+ // hardware/interfaces/audio/common/5.0/types.hal
+
+package android.media.audio.common;
+
+/**
+ * {@hide}
+ */
+@Backing(type="int")
+enum AudioUsage {
+ UNKNOWN = 0,
+ MEDIA = 1,
+ VOICE_COMMUNICATION = 2,
+ VOICE_COMMUNICATION_SIGNALLING = 3,
+ ALARM = 4,
+ NOTIFICATION = 5,
+ NOTIFICATION_TELEPHONY_RINGTONE = 6,
+ ASSISTANCE_ACCESSIBILITY = 11,
+ ASSISTANCE_NAVIGATION_GUIDANCE = 12,
+ ASSISTANCE_SONIFICATION = 13,
+ GAME = 14,
+ VIRTUAL_SOURCE = 15,
+ ASSISTANT = 16,
+}
diff --git a/media/java/android/media/soundtrigger_middleware/ConfidenceLevel.aidl b/media/java/android/media/soundtrigger_middleware/ConfidenceLevel.aidl
new file mode 100644
index 000000000000..3dbc70556bd3
--- /dev/null
+++ b/media/java/android/media/soundtrigger_middleware/ConfidenceLevel.aidl
@@ -0,0 +1,35 @@
+/*
+ * Copyright (C) 2019 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.media.soundtrigger_middleware;
+
+/**
+ * A recognition confidence level.
+ * This type is used to represent either a threshold or an actual detection confidence level.
+ *
+ * {@hide}
+ */
+parcelable ConfidenceLevel {
+ /** user ID. */
+ int userId;
+ /**
+ * Confidence level in percent (0 - 100).
+ * <ul>
+ * <li>Min level for recognition configuration
+ * <li>Detected level for recognition event.
+ * </ul>
+ */
+ int levelPercent;
+}
diff --git a/media/java/android/media/soundtrigger_middleware/ISoundTriggerCallback.aidl b/media/java/android/media/soundtrigger_middleware/ISoundTriggerCallback.aidl
new file mode 100644
index 000000000000..149c1cd0447b
--- /dev/null
+++ b/media/java/android/media/soundtrigger_middleware/ISoundTriggerCallback.aidl
@@ -0,0 +1,47 @@
+/*
+ * Copyright (C) 2019 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.media.soundtrigger_middleware;
+
+import android.media.soundtrigger_middleware.RecognitionEvent;
+import android.media.soundtrigger_middleware.PhraseRecognitionEvent;
+
+/**
+ * Main interface for a client to get notifications of events coming from this module.
+ *
+ * {@hide}
+ */
+oneway interface ISoundTriggerCallback {
+ /**
+ * Invoked whenever a recognition event is triggered (typically, on recognition, but also in
+ * case of external aborting of a recognition or a forced recognition event - see the status
+ * code in the event for determining).
+ */
+ void onRecognition(int modelHandle, in RecognitionEvent event);
+ /**
+ * Invoked whenever a phrase recognition event is triggered (typically, on recognition, but
+ * also in case of external aborting of a recognition or a forced recognition event - see the
+ * status code in the event for determining).
+ */
+ void onPhraseRecognition(int modelHandle, in PhraseRecognitionEvent event);
+ /**
+ * Notifies the client the recognition has become available after previously having been
+ * unavailable, or vice versa. This method will always be invoked once immediately after
+ * attachment, and then every time there is a change in availability.
+ * When availability changes from available to unavailable, all active recognitions are aborted,
+ * and this event will be sent in addition to the abort event.
+ */
+ void onRecognitionAvailabilityChange(boolean available);
+}
diff --git a/media/java/android/media/soundtrigger_middleware/ISoundTriggerMiddlewareService.aidl b/media/java/android/media/soundtrigger_middleware/ISoundTriggerMiddlewareService.aidl
new file mode 100644
index 000000000000..80333070b7ce
--- /dev/null
+++ b/media/java/android/media/soundtrigger_middleware/ISoundTriggerMiddlewareService.aidl
@@ -0,0 +1,48 @@
+/*
+ * Copyright (C) 2019 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.media.soundtrigger_middleware;
+
+import android.media.soundtrigger_middleware.ISoundTriggerModule;
+import android.media.soundtrigger_middleware.ISoundTriggerCallback;
+import android.media.soundtrigger_middleware.SoundTriggerModuleDescriptor;
+
+/**
+ * Main entry point into this module.
+ *
+ * Allows the client to enumerate the available soundtrigger devices and their capabilities, then
+ * attach to either one of them in order to use it.
+ *
+ * {@hide}
+ */
+interface ISoundTriggerMiddlewareService {
+ /**
+ * Query the available modules and their capabilities.
+ */
+ SoundTriggerModuleDescriptor[] listModules();
+
+ /**
+ * Attach to one of the available modules.
+ * listModules() must be called prior to calling this method and the provided handle must be
+ * one of the handles from the returned list.
+ */
+ ISoundTriggerModule attach(int handle, ISoundTriggerCallback callback);
+
+ /**
+ * Notify the service that external input capture is taking place. This may cause some of the
+ * active recognitions to be aborted.
+ */
+ void setExternalCaptureState(boolean active);
+} \ No newline at end of file
diff --git a/media/java/android/media/soundtrigger_middleware/ISoundTriggerModule.aidl b/media/java/android/media/soundtrigger_middleware/ISoundTriggerModule.aidl
new file mode 100644
index 000000000000..c4a57857dd3d
--- /dev/null
+++ b/media/java/android/media/soundtrigger_middleware/ISoundTriggerModule.aidl
@@ -0,0 +1,148 @@
+/*
+ * Copyright (C) 2019 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.media.soundtrigger_middleware;
+
+import android.media.soundtrigger_middleware.ModelParameter;
+import android.media.soundtrigger_middleware.ModelParameterRange;
+import android.media.soundtrigger_middleware.SoundModel;
+import android.media.soundtrigger_middleware.PhraseSoundModel;
+import android.media.soundtrigger_middleware.RecognitionConfig;
+
+/**
+ * A sound-trigger module.
+ *
+ * This interface allows a client to operate a sound-trigger device, intended for low-power
+ * detection of various sound patterns, represented by a "sound model".
+ *
+ * Basic operation is to load a sound model (either a generic one or a "phrase" model), then
+ * initiate recognition on this model. A trigger will be delivered asynchronously via a callback
+ * provided by the caller earlier, when attaching to this interface.
+ *
+ * In additon to recognition events, this module will also produce abort events in cases where
+ * recognition has been externally preempted.
+ *
+ * {@hide}
+ */
+interface ISoundTriggerModule {
+ /**
+ * Load a sound model. Will return a handle to the model on success or will throw a
+ * ServiceSpecificException with one of the {@link Status} error codes upon a recoverable error
+ * (for example, lack of resources of loading a model at the time of call.
+ * Model must eventually be unloaded using {@link #unloadModel(int)} prior to detaching.
+ *
+ * May throw a ServiceSpecificException with an RESOURCE_CONTENTION status to indicate that
+ * resources required for loading the model are currently consumed by other clients.
+ */
+ int loadModel(in SoundModel model);
+
+ /**
+ * Load a phrase sound model. Will return a handle to the model on success or will throw a
+ * ServiceSpecificException with one of the {@link Status} error codes upon a recoverable error
+ * (for example, lack of resources of loading a model at the time of call.
+ * Model must eventually be unloaded using unloadModel prior to detaching.
+ *
+ * May throw a ServiceSpecificException with an RESOURCE_CONTENTION status to indicate that
+ * resources required for loading the model are currently consumed by other clients.
+ */
+ int loadPhraseModel(in PhraseSoundModel model);
+
+ /**
+ * Unload a model, previously loaded with loadModel or loadPhraseModel. After unloading, model
+ * can no longer be used for recognition and the resources occupied by it are released.
+ * Model must not be active at the time of unloading. Cient may call stopRecognition to ensure
+ * that.
+ */
+ void unloadModel(int modelHandle);
+
+ /**
+ * Initiate recognition on a previously loaded model.
+ * Recognition event would eventually be delivered via the client-provided callback, typically
+ * supplied during attachment to this interface.
+ *
+ * Once a recognition event is passed to the client, the recognition automatically become
+ * inactive, unless the event is of the RecognitionStatus.FORCED kind. Client can also shut down
+ * the recognition explicitly, via stopRecognition.
+ */
+ void startRecognition(int modelHandle, in RecognitionConfig config);
+
+ /**
+ * Stop a recognition of a previously active recognition. Will NOT generate a recognition event.
+ * This call is idempotent - calling it on an inactive model has no effect. However, it must
+ * only be used with a loaded model handle.
+ */
+ void stopRecognition(int modelHandle);
+
+ /**
+ * Force generation of a recognition event. Handle must be that of a loaded model. If
+ * recognition is inactive, will do nothing. If recognition is active, will asynchronously
+ * deliever an event with RecognitionStatus.FORCED status and leave recognition in active state.
+ * To avoid any race conditions, once an event signalling the automatic stopping of recognition
+ * is sent, no more forced events will get sent (even if previously requested) until recognition
+ * is explicitly started again.
+ *
+ * Since not all module implementations support this feature, may throw a
+ * ServiceSpecificException with an OPERATION_NOT_SUPPORTED status.
+ */
+ void forceRecognitionEvent(int modelHandle);
+
+ /**
+ * Set a model specific parameter with the given value. This parameter
+ * will keep its value for the duration the model is loaded regardless of starting and stopping
+ * recognition. Once the model is unloaded, the value will be lost.
+ * It is expected to check if the handle supports the parameter via the
+ * queryModelParameterSupport API prior to calling this method.
+ *
+ * @param modelHandle The sound model handle indicating which model to modify parameters
+ * @param modelParam Parameter to set which will be validated against the
+ * ModelParameter type.
+ * @param value The value to set for the given model parameter
+ */
+ void setModelParameter(int modelHandle, ModelParameter modelParam, int value);
+
+ /**
+ * Get a model specific parameter. This parameter will keep its value
+ * for the duration the model is loaded regardless of starting and stopping recognition.
+ * Once the model is unloaded, the value will be lost. If the value is not set, a default
+ * value is returned. See ModelParameter for parameter default values.
+ * It is expected to check if the handle supports the parameter via the
+ * queryModelParameterSupport API prior to calling this method.
+ *
+ * @param modelHandle The sound model associated with given modelParam
+ * @param modelParam Parameter to set which will be validated against the
+ * ModelParameter type.
+ * @return Value set to the requested parameter.
+ */
+ int getModelParameter(int modelHandle, ModelParameter modelParam);
+
+ /**
+ * Determine if parameter control is supported for the given model handle, and its valid value
+ * range if it is.
+ *
+ * @param modelHandle The sound model handle indicating which model to query
+ * @param modelParam Parameter to set which will be validated against the
+ * ModelParameter type.
+ * @return If parameter is supported, the return value is its valid range, otherwise null.
+ */
+ @nullable ModelParameterRange queryModelParameterSupport(int modelHandle,
+ ModelParameter modelParam);
+
+ /**
+ * Detach from the module, releasing any active resources.
+ * This will ensure the client callback is no longer called after this call returns.
+ * All models must have been unloaded prior to calling this method.
+ */
+ void detach();
+} \ No newline at end of file
diff --git a/media/java/android/media/soundtrigger_middleware/ModelParameter.aidl b/media/java/android/media/soundtrigger_middleware/ModelParameter.aidl
new file mode 100644
index 000000000000..09936278e93a
--- /dev/null
+++ b/media/java/android/media/soundtrigger_middleware/ModelParameter.aidl
@@ -0,0 +1,38 @@
+/*
+ * Copyright (C) 2019 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.media.soundtrigger_middleware;
+
+/**
+ * Model specific parameters to be used with parameter set and get APIs.
+ *
+ * {@hide}
+ */
+@Backing(type="int")
+enum ModelParameter {
+ /**
+ * Placeholder for invalid model parameter used for returning error or
+ * passing an invalid value.
+ */
+ INVALID = -1,
+
+ /**
+ * Controls the sensitivity threshold adjustment factor for a given model.
+ * Negative value corresponds to less sensitive model (high threshold) and
+ * a positive value corresponds to a more sensitive model (low threshold).
+ * Default value is 0.
+ */
+ THRESHOLD_FACTOR = 0,
+}
diff --git a/media/java/android/media/soundtrigger_middleware/ModelParameterRange.aidl b/media/java/android/media/soundtrigger_middleware/ModelParameterRange.aidl
new file mode 100644
index 000000000000..d6948a87dc6d
--- /dev/null
+++ b/media/java/android/media/soundtrigger_middleware/ModelParameterRange.aidl
@@ -0,0 +1,28 @@
+/*
+ * Copyright (C) 2019 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.media.soundtrigger_middleware;
+
+/**
+ * Value range for a model parameter.
+ *
+ * {@hide}
+ */
+parcelable ModelParameterRange {
+ /** Minimum (inclusive) */
+ int minInclusive;
+ /** Maximum (inclusive) */
+ int maxInclusive;
+}
diff --git a/media/java/android/media/soundtrigger_middleware/Phrase.aidl b/media/java/android/media/soundtrigger_middleware/Phrase.aidl
new file mode 100644
index 000000000000..98a489f8a6a9
--- /dev/null
+++ b/media/java/android/media/soundtrigger_middleware/Phrase.aidl
@@ -0,0 +1,34 @@
+/*
+ * Copyright (C) 2019 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.media.soundtrigger_middleware;
+
+/**
+ * Key phrase descriptor.
+ *
+ * {@hide}
+ */
+parcelable Phrase {
+ /** Unique keyphrase ID assigned at enrollment time. */
+ int id;
+ /** Recognition modes supported by this key phrase (bitfield of RecognitionMode enum). */
+ int recognitionModes;
+ /** List of users IDs associated with this key phrase. */
+ int[] users;
+ /** Locale - Java Locale style (e.g. en_US). */
+ String locale;
+ /** Phrase text. */
+ String text;
+}
diff --git a/media/java/android/media/soundtrigger_middleware/PhraseRecognitionEvent.aidl b/media/java/android/media/soundtrigger_middleware/PhraseRecognitionEvent.aidl
new file mode 100644
index 000000000000..6a3ec61d1ebf
--- /dev/null
+++ b/media/java/android/media/soundtrigger_middleware/PhraseRecognitionEvent.aidl
@@ -0,0 +1,31 @@
+/*
+ * Copyright (C) 2019 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.media.soundtrigger_middleware;
+
+import android.media.soundtrigger_middleware.PhraseRecognitionExtra;
+import android.media.soundtrigger_middleware.RecognitionEvent;
+
+/**
+ * An event that gets sent to indicate a phrase recognition (or aborting of the recognition
+ process).
+ * {@hide}
+ */
+parcelable PhraseRecognitionEvent {
+ /** Common recognition event. */
+ RecognitionEvent common;
+ /** List of descriptors for each recognized key phrase */
+ PhraseRecognitionExtra[] phraseExtras;
+}
diff --git a/media/java/android/media/soundtrigger_middleware/PhraseRecognitionExtra.aidl b/media/java/android/media/soundtrigger_middleware/PhraseRecognitionExtra.aidl
new file mode 100644
index 000000000000..cb96bf37a95d
--- /dev/null
+++ b/media/java/android/media/soundtrigger_middleware/PhraseRecognitionExtra.aidl
@@ -0,0 +1,35 @@
+/*
+ * Copyright (C) 2019 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.media.soundtrigger_middleware;
+
+import android.media.soundtrigger_middleware.ConfidenceLevel;
+
+/**
+ * Specialized recognition event for key phrase detection.
+ * {@hide}
+ */
+parcelable PhraseRecognitionExtra {
+ // TODO(ytai): Constants / enums.
+
+ /** keyphrase ID */
+ int id;
+ /** recognition modes used for this keyphrase */
+ int recognitionModes;
+ /** confidence level for mode RECOGNITION_MODE_VOICE_TRIGGER */
+ int confidenceLevel;
+ /** number of user confidence levels */
+ ConfidenceLevel[] levels;
+}
diff --git a/media/java/android/media/soundtrigger_middleware/PhraseSoundModel.aidl b/media/java/android/media/soundtrigger_middleware/PhraseSoundModel.aidl
new file mode 100644
index 000000000000..81028c1608ea
--- /dev/null
+++ b/media/java/android/media/soundtrigger_middleware/PhraseSoundModel.aidl
@@ -0,0 +1,32 @@
+/*
+ * Copyright (C) 2019 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.media.soundtrigger_middleware;
+
+import android.media.soundtrigger_middleware.SoundModel;
+import android.media.soundtrigger_middleware.Phrase;
+
+/**
+ * Specialized sound model for key phrase detection.
+ * Proprietary representation of key phrases in binary data must match
+ * information indicated by phrases field.
+ * {@hide}
+ */
+parcelable PhraseSoundModel {
+ /** Common part of sound model descriptor */
+ SoundModel common;
+ /** List of descriptors for key phrases supported by this sound model */
+ Phrase[] phrases;
+}
diff --git a/media/java/android/media/soundtrigger_middleware/RecognitionConfig.aidl b/media/java/android/media/soundtrigger_middleware/RecognitionConfig.aidl
new file mode 100644
index 000000000000..c7642e8c1241
--- /dev/null
+++ b/media/java/android/media/soundtrigger_middleware/RecognitionConfig.aidl
@@ -0,0 +1,33 @@
+/*
+ * Copyright (C) 2019 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.media.soundtrigger_middleware;
+
+import android.media.soundtrigger_middleware.PhraseRecognitionExtra;
+
+/**
+ * Configuration for tuning behavior of an active recognition process.
+ * {@hide}
+ */
+parcelable RecognitionConfig {
+ /* Capture and buffer audio for this recognition instance. */
+ boolean captureRequested;
+
+ /* Configuration for each key phrase. */
+ PhraseRecognitionExtra[] phraseRecognitionExtras;
+
+ /** Opaque capture configuration data. */
+ byte[] data;
+}
diff --git a/media/java/android/media/soundtrigger_middleware/RecognitionEvent.aidl b/media/java/android/media/soundtrigger_middleware/RecognitionEvent.aidl
new file mode 100644
index 000000000000..de4d060ce484
--- /dev/null
+++ b/media/java/android/media/soundtrigger_middleware/RecognitionEvent.aidl
@@ -0,0 +1,51 @@
+/*
+ * Copyright (C) 2019 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.media.soundtrigger_middleware;
+
+import android.media.audio.common.AudioConfig;
+import android.media.soundtrigger_middleware.RecognitionStatus;
+import android.media.soundtrigger_middleware.SoundModelType;
+
+/**
+ * An event that gets sent to indicate a recognition (or aborting of the recognition process).
+ * {@hide}
+ */
+parcelable RecognitionEvent {
+ /** Recognition status. */
+ RecognitionStatus status;
+ /** Event type, same as sound model type. */
+ SoundModelType type;
+ /** Is it possible to capture audio from this utterance buffered by the implementation. */
+ boolean captureAvailable;
+ /* Audio session ID. framework use. */
+ int captureSession;
+ /**
+ * Delay in ms between end of model detection and start of audio available for capture.
+ * A negative value is possible (e.g. if key phrase is also available for Capture.
+ */
+ int captureDelayMs;
+ /** Duration in ms of audio captured before the start of the trigger. 0 if none. */
+ int capturePreambleMs;
+ /** If true, the 'data' field below contains the capture of the trigger sound. */
+ boolean triggerInData;
+ /**
+ * Audio format of either the trigger in event data or to use for capture of the rest of the
+ * utterance.
+ */
+ AudioConfig audioConfig;
+ /** Additional data. */
+ byte[] data;
+}
diff --git a/media/java/android/media/soundtrigger_middleware/RecognitionMode.aidl b/media/java/android/media/soundtrigger_middleware/RecognitionMode.aidl
new file mode 100644
index 000000000000..d8bfff4bec6f
--- /dev/null
+++ b/media/java/android/media/soundtrigger_middleware/RecognitionMode.aidl
@@ -0,0 +1,32 @@
+/*
+ * Copyright (C) 2019 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.media.soundtrigger_middleware;
+
+/**
+ * Recognition mode.
+ * {@hide}
+ */
+@Backing(type="int")
+enum RecognitionMode {
+ /** Simple voice trigger. */
+ VOICE_TRIGGER = 0x1,
+ /** Trigger only if one user in model identified. */
+ USER_IDENTIFICATION = 0x2,
+ /** Trigger only if one user in model authenticated. */
+ USER_AUTHENTICATION = 0x4,
+ /** Generic sound trigger. */
+ GENERIC_TRIGGER = 0x8,
+}
diff --git a/media/java/android/media/soundtrigger_middleware/RecognitionStatus.aidl b/media/java/android/media/soundtrigger_middleware/RecognitionStatus.aidl
new file mode 100644
index 000000000000..d563edca547d
--- /dev/null
+++ b/media/java/android/media/soundtrigger_middleware/RecognitionStatus.aidl
@@ -0,0 +1,35 @@
+/*
+ * Copyright (C) 2019 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.media.soundtrigger_middleware;
+
+/**
+ * A status for indicating the type of a recognition event.
+ * {@hide}
+ */
+@Backing(type="int")
+enum RecognitionStatus {
+ /** Recognition success. */
+ SUCCESS = 0,
+ /** Recognition aborted (e.g. capture preempted by another use-case. */
+ ABORTED = 1,
+ /** Recognition failure. */
+ FAILURE = 2,
+ /**
+ * Recognition event was triggered by a forceRecognitionEvent request, not by the DSP.
+ * Note that forced detections *do not* stop the active recognition, unlike the other types.
+ */
+ FORCED = 3
+}
diff --git a/media/java/android/media/soundtrigger_middleware/SoundModel.aidl b/media/java/android/media/soundtrigger_middleware/SoundModel.aidl
new file mode 100644
index 000000000000..fba1ee507836
--- /dev/null
+++ b/media/java/android/media/soundtrigger_middleware/SoundModel.aidl
@@ -0,0 +1,36 @@
+/*
+ * Copyright (C) 2019 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.media.soundtrigger_middleware;
+
+import android.media.soundtrigger_middleware.SoundModelType;
+
+/**
+ * Base sound model descriptor. This struct can be extended for various specific types by way of
+ * aggregation.
+ * {@hide}
+ */
+parcelable SoundModel {
+ /** Model type. */
+ SoundModelType type;
+ /** Unique sound model ID. */
+ String uuid;
+ /**
+ * Unique vendor ID. Identifies the engine the sound model
+ * was build for */
+ String vendorUuid;
+ /** Opaque data transparent to Android framework */
+ byte[] data;
+}
diff --git a/media/java/android/media/soundtrigger_middleware/SoundModelType.aidl b/media/java/android/media/soundtrigger_middleware/SoundModelType.aidl
new file mode 100644
index 000000000000..f2abc9af7780
--- /dev/null
+++ b/media/java/android/media/soundtrigger_middleware/SoundModelType.aidl
@@ -0,0 +1,30 @@
+/*
+ * Copyright (C) 2019 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.media.soundtrigger_middleware;
+
+/**
+ * Sound model type.
+ * {@hide}
+ */
+@Backing(type="int")
+enum SoundModelType {
+ /** Unspecified sound model type */
+ UNKNOWN = -1,
+ /** Key phrase sound models */
+ KEYPHRASE = 0,
+ /** All models other than keyphrase */
+ GENERIC = 1,
+}
diff --git a/media/java/android/media/soundtrigger_middleware/SoundTriggerModuleDescriptor.aidl b/media/java/android/media/soundtrigger_middleware/SoundTriggerModuleDescriptor.aidl
new file mode 100644
index 000000000000..667135ff61b9
--- /dev/null
+++ b/media/java/android/media/soundtrigger_middleware/SoundTriggerModuleDescriptor.aidl
@@ -0,0 +1,31 @@
+/*
+ * Copyright (C) 2019 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.media.soundtrigger_middleware;
+
+import android.media.soundtrigger_middleware.SoundTriggerModuleProperties;
+
+/**
+ * A descriptor of an available sound trigger module, containing the handle used to reference the
+ * module, as well its capabilities.
+ * {@hide}
+ */
+parcelable SoundTriggerModuleDescriptor {
+ /** Module handle to be used for attaching to it. */
+ int handle;
+ /** Module capabilities. */
+ SoundTriggerModuleProperties properties;
+}
+
diff --git a/media/java/android/media/soundtrigger_middleware/SoundTriggerModuleProperties.aidl b/media/java/android/media/soundtrigger_middleware/SoundTriggerModuleProperties.aidl
new file mode 100644
index 000000000000..1a3b40261a62
--- /dev/null
+++ b/media/java/android/media/soundtrigger_middleware/SoundTriggerModuleProperties.aidl
@@ -0,0 +1,53 @@
+/*
+ * Copyright (C) 2019 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.media.soundtrigger_middleware;
+
+/**
+ * Capabilities of a sound trigger module.
+ * {@hide}
+ */
+parcelable SoundTriggerModuleProperties {
+ /** Implementor name */
+ String implementor;
+ /** Implementation description */
+ String description;
+ /** Implementation version */
+ int version;
+ /**
+ * Unique implementation ID. The UUID must change with each version of
+ the engine implementation */
+ String uuid;
+ /** Maximum number of concurrent sound models loaded */
+ int maxSoundModels;
+ /** Maximum number of key phrases */
+ int maxKeyPhrases;
+ /** Maximum number of concurrent users detected */
+ int maxUsers;
+ /** All supported modes. e.g RecognitionMode.VOICE_TRIGGER */
+ int recognitionModes;
+ /** Supports seamless transition from detection to capture */
+ boolean captureTransition;
+ /** Maximum buffering capacity in ms if captureTransition is true */
+ int maxBufferMs;
+ /** Supports capture by other use cases while detection is active */
+ boolean concurrentCapture;
+ /** Returns the trigger capture in event */
+ boolean triggerInEvent;
+ /**
+ * Rated power consumption when detection is active with TDB
+ * silence/sound/speech ratio */
+ int powerConsumptionMw;
+}
diff --git a/media/java/android/media/soundtrigger_middleware/Status.aidl b/media/java/android/media/soundtrigger_middleware/Status.aidl
new file mode 100644
index 000000000000..d8f9d8f7e891
--- /dev/null
+++ b/media/java/android/media/soundtrigger_middleware/Status.aidl
@@ -0,0 +1,29 @@
+/*
+ * Copyright (C) 2019 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.media.soundtrigger_middleware;
+
+/**
+ * {@hide}
+ */
+@Backing(type="int")
+enum Status {
+ /** Success. */
+ SUCCESS = 0,
+ /** Failure due to resource contention. This is typically a temporary condition. */
+ RESOURCE_CONTENTION = 1,
+ /** Operation is not supported in this implementation. This is a permanent condition. */
+ OPERATION_NOT_SUPPORTED = 2,
+}
diff --git a/services/core/Android.bp b/services/core/Android.bp
index 203bc61c2022..b7adfa4a3ff1 100644
--- a/services/core/Android.bp
+++ b/services/core/Android.bp
@@ -116,6 +116,7 @@ java_library_static {
"android.hardware.oemlock-V1.0-java",
"android.hardware.configstore-V1.0-java",
"android.hardware.contexthub-V1.0-java",
+ "android.hardware.soundtrigger-V2.3-java",
"android.hidl.manager-V1.2-java",
"dnsresolver_aidl_interface-V2-java",
"netd_event_listener_interface-java",
diff --git a/services/core/java/com/android/server/soundtrigger_middleware/AudioSessionProviderImpl.java b/services/core/java/com/android/server/soundtrigger_middleware/AudioSessionProviderImpl.java
new file mode 100644
index 000000000000..3fa52301d9a0
--- /dev/null
+++ b/services/core/java/com/android/server/soundtrigger_middleware/AudioSessionProviderImpl.java
@@ -0,0 +1,29 @@
+/*
+ * Copyright (C) 2019 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.soundtrigger_middleware;
+
+/**
+ * An implementation of SoundTriggerMiddlewareImpl.AudioSessionProvider that ties to native
+ * AudioSystem module via JNI.
+ */
+class AudioSessionProviderImpl extends SoundTriggerMiddlewareImpl.AudioSessionProvider {
+ @Override
+ public native AudioSession acquireSession();
+
+ @Override
+ public native void releaseSession(int sessionHandle);
+}
diff --git a/services/core/java/com/android/server/soundtrigger_middleware/ConversionUtil.java b/services/core/java/com/android/server/soundtrigger_middleware/ConversionUtil.java
new file mode 100644
index 000000000000..9b22f33a20b0
--- /dev/null
+++ b/services/core/java/com/android/server/soundtrigger_middleware/ConversionUtil.java
@@ -0,0 +1,390 @@
+/*
+ * Copyright (C) 2019 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.soundtrigger_middleware;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.hardware.audio.common.V2_0.Uuid;
+import android.hardware.soundtrigger.V2_1.ISoundTriggerHwCallback;
+import android.hardware.soundtrigger.V2_3.ISoundTriggerHw;
+import android.media.audio.common.AudioConfig;
+import android.media.audio.common.AudioOffloadInfo;
+import android.media.soundtrigger_middleware.ConfidenceLevel;
+import android.media.soundtrigger_middleware.ModelParameter;
+import android.media.soundtrigger_middleware.ModelParameterRange;
+import android.media.soundtrigger_middleware.Phrase;
+import android.media.soundtrigger_middleware.PhraseRecognitionEvent;
+import android.media.soundtrigger_middleware.PhraseRecognitionExtra;
+import android.media.soundtrigger_middleware.PhraseSoundModel;
+import android.media.soundtrigger_middleware.RecognitionConfig;
+import android.media.soundtrigger_middleware.RecognitionEvent;
+import android.media.soundtrigger_middleware.RecognitionMode;
+import android.media.soundtrigger_middleware.RecognitionStatus;
+import android.media.soundtrigger_middleware.SoundModel;
+import android.media.soundtrigger_middleware.SoundModelType;
+import android.media.soundtrigger_middleware.SoundTriggerModuleProperties;
+import android.os.HidlMemoryUtil;
+
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+/**
+ * Utilities for type conversion between SoundTrigger HAL types and SoundTriggerMiddleware service
+ * types.
+ *
+ * @hide
+ */
+class ConversionUtil {
+ static @NonNull
+ SoundTriggerModuleProperties hidl2aidlProperties(
+ @NonNull ISoundTriggerHw.Properties hidlProperties) {
+ SoundTriggerModuleProperties aidlProperties = new SoundTriggerModuleProperties();
+ aidlProperties.implementor = hidlProperties.implementor;
+ aidlProperties.description = hidlProperties.description;
+ aidlProperties.version = hidlProperties.version;
+ aidlProperties.uuid = hidl2aidlUuid(hidlProperties.uuid);
+ aidlProperties.maxSoundModels = hidlProperties.maxSoundModels;
+ aidlProperties.maxKeyPhrases = hidlProperties.maxKeyPhrases;
+ aidlProperties.maxUsers = hidlProperties.maxUsers;
+ aidlProperties.recognitionModes = hidlProperties.recognitionModes;
+ aidlProperties.captureTransition = hidlProperties.captureTransition;
+ aidlProperties.maxBufferMs = hidlProperties.maxBufferMs;
+ aidlProperties.concurrentCapture = hidlProperties.concurrentCapture;
+ aidlProperties.triggerInEvent = hidlProperties.triggerInEvent;
+ aidlProperties.powerConsumptionMw = hidlProperties.powerConsumptionMw;
+ return aidlProperties;
+ }
+
+ static @NonNull
+ String hidl2aidlUuid(@NonNull Uuid hidlUuid) {
+ if (hidlUuid.node == null || hidlUuid.node.length != 6) {
+ throw new IllegalArgumentException("UUID.node must be of length 6.");
+ }
+ return String.format(UuidUtil.FORMAT,
+ hidlUuid.timeLow,
+ hidlUuid.timeMid,
+ hidlUuid.versionAndTimeHigh,
+ hidlUuid.variantAndClockSeqHigh,
+ hidlUuid.node[0],
+ hidlUuid.node[1],
+ hidlUuid.node[2],
+ hidlUuid.node[3],
+ hidlUuid.node[4],
+ hidlUuid.node[5]);
+ }
+
+ static @NonNull
+ Uuid aidl2hidlUuid(@NonNull String aidlUuid) {
+ Matcher matcher = UuidUtil.PATTERN.matcher(aidlUuid);
+ if (!matcher.matches()) {
+ throw new IllegalArgumentException("Illegal format for UUID: " + aidlUuid);
+ }
+ Uuid hidlUuid = new Uuid();
+ hidlUuid.timeLow = Integer.parseUnsignedInt(matcher.group(1), 16);
+ hidlUuid.timeMid = (short) Integer.parseUnsignedInt(matcher.group(2), 16);
+ hidlUuid.versionAndTimeHigh = (short) Integer.parseUnsignedInt(matcher.group(3), 16);
+ hidlUuid.variantAndClockSeqHigh = (short) Integer.parseUnsignedInt(matcher.group(4), 16);
+ hidlUuid.node = new byte[]{(byte) Integer.parseUnsignedInt(matcher.group(5), 16),
+ (byte) Integer.parseUnsignedInt(matcher.group(6), 16),
+ (byte) Integer.parseUnsignedInt(matcher.group(7), 16),
+ (byte) Integer.parseUnsignedInt(matcher.group(8), 16),
+ (byte) Integer.parseUnsignedInt(matcher.group(9), 16),
+ (byte) Integer.parseUnsignedInt(matcher.group(10), 16)};
+ return hidlUuid;
+ }
+
+ static int aidl2hidlSoundModelType(int aidlType) {
+ switch (aidlType) {
+ case SoundModelType.GENERIC:
+ return android.hardware.soundtrigger.V2_0.SoundModelType.GENERIC;
+ case SoundModelType.KEYPHRASE:
+ return android.hardware.soundtrigger.V2_0.SoundModelType.KEYPHRASE;
+ default:
+ throw new IllegalArgumentException("Unknown sound model type: " + aidlType);
+ }
+ }
+
+ static int hidl2aidlSoundModelType(int hidlType) {
+ switch (hidlType) {
+ case android.hardware.soundtrigger.V2_0.SoundModelType.GENERIC:
+ return SoundModelType.GENERIC;
+ case android.hardware.soundtrigger.V2_0.SoundModelType.KEYPHRASE:
+ return SoundModelType.KEYPHRASE;
+ default:
+ throw new IllegalArgumentException("Unknown sound model type: " + hidlType);
+ }
+ }
+
+ static @NonNull
+ ISoundTriggerHw.Phrase aidl2hidlPhrase(@NonNull Phrase aidlPhrase) {
+ ISoundTriggerHw.Phrase hidlPhrase = new ISoundTriggerHw.Phrase();
+ hidlPhrase.id = aidlPhrase.id;
+ hidlPhrase.recognitionModes = aidl2hidlRecognitionModes(aidlPhrase.recognitionModes);
+ for (int aidlUser : aidlPhrase.users) {
+ hidlPhrase.users.add(aidlUser);
+ }
+ hidlPhrase.locale = aidlPhrase.locale;
+ hidlPhrase.text = aidlPhrase.text;
+ return hidlPhrase;
+ }
+
+ static int aidl2hidlRecognitionModes(int aidlModes) {
+ int hidlModes = 0;
+
+ if ((aidlModes & RecognitionMode.VOICE_TRIGGER) != 0) {
+ hidlModes |= android.hardware.soundtrigger.V2_0.RecognitionMode.VOICE_TRIGGER;
+ }
+ if ((aidlModes & RecognitionMode.USER_IDENTIFICATION) != 0) {
+ hidlModes |= android.hardware.soundtrigger.V2_0.RecognitionMode.USER_IDENTIFICATION;
+ }
+ if ((aidlModes & RecognitionMode.USER_AUTHENTICATION) != 0) {
+ hidlModes |= android.hardware.soundtrigger.V2_0.RecognitionMode.USER_AUTHENTICATION;
+ }
+ if ((aidlModes & RecognitionMode.GENERIC_TRIGGER) != 0) {
+ hidlModes |= android.hardware.soundtrigger.V2_0.RecognitionMode.GENERIC_TRIGGER;
+ }
+ return hidlModes;
+ }
+
+ static int hidl2aidlRecognitionModes(int hidlModes) {
+ int aidlModes = 0;
+ if ((hidlModes & android.hardware.soundtrigger.V2_0.RecognitionMode.VOICE_TRIGGER) != 0) {
+ aidlModes |= RecognitionMode.VOICE_TRIGGER;
+ }
+ if ((hidlModes & android.hardware.soundtrigger.V2_0.RecognitionMode.USER_IDENTIFICATION)
+ != 0) {
+ aidlModes |= RecognitionMode.USER_IDENTIFICATION;
+ }
+ if ((hidlModes & android.hardware.soundtrigger.V2_0.RecognitionMode.USER_AUTHENTICATION)
+ != 0) {
+ aidlModes |= RecognitionMode.USER_AUTHENTICATION;
+ }
+ if ((hidlModes & android.hardware.soundtrigger.V2_0.RecognitionMode.GENERIC_TRIGGER) != 0) {
+ aidlModes |= RecognitionMode.GENERIC_TRIGGER;
+ }
+ return aidlModes;
+ }
+
+ static @NonNull
+ ISoundTriggerHw.SoundModel aidl2hidlSoundModel(@NonNull SoundModel aidlModel) {
+ ISoundTriggerHw.SoundModel hidlModel = new ISoundTriggerHw.SoundModel();
+ hidlModel.header.type = aidl2hidlSoundModelType(aidlModel.type);
+ hidlModel.header.uuid = aidl2hidlUuid(aidlModel.uuid);
+ hidlModel.header.vendorUuid = aidl2hidlUuid(aidlModel.vendorUuid);
+ hidlModel.data = HidlMemoryUtil.byteArrayToHidlMemory(aidlModel.data,
+ "SoundTrigger SoundModel");
+ return hidlModel;
+ }
+
+ static @NonNull
+ ISoundTriggerHw.PhraseSoundModel aidl2hidlPhraseSoundModel(
+ @NonNull PhraseSoundModel aidlModel) {
+ ISoundTriggerHw.PhraseSoundModel hidlModel = new ISoundTriggerHw.PhraseSoundModel();
+ hidlModel.common = aidl2hidlSoundModel(aidlModel.common);
+ for (Phrase aidlPhrase : aidlModel.phrases) {
+ hidlModel.phrases.add(aidl2hidlPhrase(aidlPhrase));
+ }
+ return hidlModel;
+ }
+
+ static @NonNull
+ ISoundTriggerHw.RecognitionConfig aidl2hidlRecognitionConfig(
+ @NonNull RecognitionConfig aidlConfig) {
+ ISoundTriggerHw.RecognitionConfig hidlConfig = new ISoundTriggerHw.RecognitionConfig();
+ hidlConfig.header.captureRequested = aidlConfig.captureRequested;
+ for (PhraseRecognitionExtra aidlPhraseExtra : aidlConfig.phraseRecognitionExtras) {
+ hidlConfig.header.phrases.add(aidl2hidlPhraseRecognitionExtra(aidlPhraseExtra));
+ }
+ hidlConfig.data = HidlMemoryUtil.byteArrayToHidlMemory(aidlConfig.data,
+ "SoundTrigger RecognitionConfig");
+ return hidlConfig;
+ }
+
+ static @NonNull
+ android.hardware.soundtrigger.V2_0.PhraseRecognitionExtra aidl2hidlPhraseRecognitionExtra(
+ @NonNull PhraseRecognitionExtra aidlExtra) {
+ android.hardware.soundtrigger.V2_0.PhraseRecognitionExtra hidlExtra =
+ new android.hardware.soundtrigger.V2_0.PhraseRecognitionExtra();
+ hidlExtra.id = aidlExtra.id;
+ hidlExtra.recognitionModes = aidl2hidlRecognitionModes(aidlExtra.recognitionModes);
+ hidlExtra.confidenceLevel = aidlExtra.confidenceLevel;
+ hidlExtra.levels.ensureCapacity(aidlExtra.levels.length);
+ for (ConfidenceLevel aidlLevel : aidlExtra.levels) {
+ hidlExtra.levels.add(aidl2hidlConfidenceLevel(aidlLevel));
+ }
+ return hidlExtra;
+ }
+
+ static @NonNull
+ PhraseRecognitionExtra hidl2aidlPhraseRecognitionExtra(
+ @NonNull android.hardware.soundtrigger.V2_0.PhraseRecognitionExtra hidlExtra) {
+ PhraseRecognitionExtra aidlExtra = new PhraseRecognitionExtra();
+ aidlExtra.id = hidlExtra.id;
+ aidlExtra.recognitionModes = hidl2aidlRecognitionModes(hidlExtra.recognitionModes);
+ aidlExtra.confidenceLevel = hidlExtra.confidenceLevel;
+ aidlExtra.levels = new ConfidenceLevel[hidlExtra.levels.size()];
+ for (int i = 0; i < hidlExtra.levels.size(); ++i) {
+ aidlExtra.levels[i] = hidl2aidlConfidenceLevel(hidlExtra.levels.get(i));
+ }
+ return aidlExtra;
+ }
+
+ static @NonNull
+ android.hardware.soundtrigger.V2_0.ConfidenceLevel aidl2hidlConfidenceLevel(
+ @NonNull ConfidenceLevel aidlLevel) {
+ android.hardware.soundtrigger.V2_0.ConfidenceLevel hidlLevel =
+ new android.hardware.soundtrigger.V2_0.ConfidenceLevel();
+ hidlLevel.userId = aidlLevel.userId;
+ hidlLevel.levelPercent = aidlLevel.levelPercent;
+ return hidlLevel;
+ }
+
+ static @NonNull
+ ConfidenceLevel hidl2aidlConfidenceLevel(
+ @NonNull android.hardware.soundtrigger.V2_0.ConfidenceLevel hidlLevel) {
+ ConfidenceLevel aidlLevel = new ConfidenceLevel();
+ aidlLevel.userId = hidlLevel.userId;
+ aidlLevel.levelPercent = hidlLevel.levelPercent;
+ return aidlLevel;
+ }
+
+ static int hidl2aidlRecognitionStatus(int hidlStatus) {
+ switch (hidlStatus) {
+ case ISoundTriggerHwCallback.RecognitionStatus.SUCCESS:
+ return RecognitionStatus.SUCCESS;
+ case ISoundTriggerHwCallback.RecognitionStatus.ABORT:
+ return RecognitionStatus.ABORTED;
+ case ISoundTriggerHwCallback.RecognitionStatus.FAILURE:
+ return RecognitionStatus.FAILURE;
+ case 3: // This doesn't have a constant in HIDL.
+ return RecognitionStatus.FORCED;
+ default:
+ throw new IllegalArgumentException("Unknown recognition status: " + hidlStatus);
+ }
+ }
+
+ static @NonNull
+ RecognitionEvent hidl2aidlRecognitionEvent(@NonNull
+ android.hardware.soundtrigger.V2_0.ISoundTriggerHwCallback.RecognitionEvent hidlEvent) {
+ RecognitionEvent aidlEvent = new RecognitionEvent();
+ aidlEvent.status = hidl2aidlRecognitionStatus(hidlEvent.status);
+ aidlEvent.type = hidl2aidlSoundModelType(hidlEvent.type);
+ aidlEvent.captureAvailable = hidlEvent.captureAvailable;
+ // hidlEvent.captureSession is never a valid field.
+ aidlEvent.captureSession = -1;
+ aidlEvent.captureDelayMs = hidlEvent.captureDelayMs;
+ aidlEvent.capturePreambleMs = hidlEvent.capturePreambleMs;
+ aidlEvent.triggerInData = hidlEvent.triggerInData;
+ aidlEvent.audioConfig = hidl2aidlAudioConfig(hidlEvent.audioConfig);
+ aidlEvent.data = new byte[hidlEvent.data.size()];
+ for (int i = 0; i < aidlEvent.data.length; ++i) {
+ aidlEvent.data[i] = hidlEvent.data.get(i);
+ }
+ return aidlEvent;
+ }
+
+ static @NonNull
+ RecognitionEvent hidl2aidlRecognitionEvent(
+ @NonNull ISoundTriggerHwCallback.RecognitionEvent hidlEvent) {
+ RecognitionEvent aidlEvent = hidl2aidlRecognitionEvent(hidlEvent.header);
+ // Data needs to get overridden with 2.1 data.
+ aidlEvent.data = HidlMemoryUtil.hidlMemoryToByteArray(hidlEvent.data);
+ return aidlEvent;
+ }
+
+ static @NonNull
+ PhraseRecognitionEvent hidl2aidlPhraseRecognitionEvent(@NonNull
+ android.hardware.soundtrigger.V2_0.ISoundTriggerHwCallback.PhraseRecognitionEvent hidlEvent) {
+ PhraseRecognitionEvent aidlEvent = new PhraseRecognitionEvent();
+ aidlEvent.common = hidl2aidlRecognitionEvent(hidlEvent.common);
+ aidlEvent.phraseExtras = new PhraseRecognitionExtra[hidlEvent.phraseExtras.size()];
+ for (int i = 0; i < hidlEvent.phraseExtras.size(); ++i) {
+ aidlEvent.phraseExtras[i] = hidl2aidlPhraseRecognitionExtra(
+ hidlEvent.phraseExtras.get(i));
+ }
+ return aidlEvent;
+ }
+
+ static @NonNull
+ PhraseRecognitionEvent hidl2aidlPhraseRecognitionEvent(
+ @NonNull ISoundTriggerHwCallback.PhraseRecognitionEvent hidlEvent) {
+ PhraseRecognitionEvent aidlEvent = new PhraseRecognitionEvent();
+ aidlEvent.common = hidl2aidlRecognitionEvent(hidlEvent.common);
+ aidlEvent.phraseExtras = new PhraseRecognitionExtra[hidlEvent.phraseExtras.size()];
+ for (int i = 0; i < hidlEvent.phraseExtras.size(); ++i) {
+ aidlEvent.phraseExtras[i] = hidl2aidlPhraseRecognitionExtra(
+ hidlEvent.phraseExtras.get(i));
+ }
+ return aidlEvent;
+ }
+
+ static @NonNull
+ AudioConfig hidl2aidlAudioConfig(
+ @NonNull android.hardware.audio.common.V2_0.AudioConfig hidlConfig) {
+ AudioConfig aidlConfig = new AudioConfig();
+ // TODO(ytai): channelMask and format might need a more careful conversion to make sure the
+ // constants match.
+ aidlConfig.sampleRateHz = hidlConfig.sampleRateHz;
+ aidlConfig.channelMask = hidlConfig.channelMask;
+ aidlConfig.format = hidlConfig.format;
+ aidlConfig.offloadInfo = hidl2aidlOffloadInfo(hidlConfig.offloadInfo);
+ aidlConfig.frameCount = hidlConfig.frameCount;
+ return aidlConfig;
+ }
+
+ static @NonNull
+ AudioOffloadInfo hidl2aidlOffloadInfo(
+ @NonNull android.hardware.audio.common.V2_0.AudioOffloadInfo hidlInfo) {
+ AudioOffloadInfo aidlInfo = new AudioOffloadInfo();
+ // TODO(ytai): channelMask, format, streamType and usage might need a more careful
+ // conversion to make sure the constants match.
+ aidlInfo.sampleRateHz = hidlInfo.sampleRateHz;
+ aidlInfo.channelMask = hidlInfo.channelMask;
+ aidlInfo.format = hidlInfo.format;
+ aidlInfo.streamType = hidlInfo.streamType;
+ aidlInfo.bitRatePerSecond = hidlInfo.bitRatePerSecond;
+ aidlInfo.durationMicroseconds = hidlInfo.durationMicroseconds;
+ aidlInfo.hasVideo = hidlInfo.hasVideo;
+ aidlInfo.isStreaming = hidlInfo.isStreaming;
+ aidlInfo.bitWidth = hidlInfo.bitWidth;
+ aidlInfo.bufferSize = hidlInfo.bufferSize;
+ aidlInfo.usage = hidlInfo.usage;
+ return aidlInfo;
+ }
+
+ @Nullable
+ static ModelParameterRange hidl2aidlModelParameterRange(
+ android.hardware.soundtrigger.V2_3.ModelParameterRange hidlRange) {
+ if (hidlRange == null) {
+ return null;
+ }
+ ModelParameterRange aidlRange = new ModelParameterRange();
+ aidlRange.minInclusive = hidlRange.start;
+ aidlRange.maxInclusive = hidlRange.end;
+ return aidlRange;
+ }
+
+ static int aidl2hidlModelParameter(int aidlParam) {
+ switch (aidlParam) {
+ case ModelParameter.THRESHOLD_FACTOR:
+ return android.hardware.soundtrigger.V2_3.ModelParameter.THRESHOLD_FACTOR;
+
+ default:
+ return android.hardware.soundtrigger.V2_3.ModelParameter.INVALID;
+ }
+ }
+}
diff --git a/services/core/java/com/android/server/soundtrigger_middleware/HalException.java b/services/core/java/com/android/server/soundtrigger_middleware/HalException.java
new file mode 100644
index 000000000000..8b3e70875183
--- /dev/null
+++ b/services/core/java/com/android/server/soundtrigger_middleware/HalException.java
@@ -0,0 +1,49 @@
+/*
+ * Copyright (C) 2019 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.soundtrigger_middleware;
+
+import android.annotation.NonNull;
+
+/**
+ * This exception represents a non-zero status code returned by a HAL invocation.
+ * Depending on the operation that threw the error, the integrity of the HAL implementation and the
+ * client's tolerance to error, this error may or may not be recoverable. The HAL itself is expected
+ * to retain the state it had prior to the invocation (so, unless the error is a result of a HAL
+ * bug, normal operation may resume).
+ * <p>
+ * The reason why this is a RuntimeException, even though the HAL interface allows returning them
+ * is because we expect none of them to actually occur as part of correct usage of the HAL.
+ *
+ * @hide
+ */
+public class HalException extends RuntimeException {
+ public final int errorCode;
+
+ public HalException(int errorCode, @NonNull String message) {
+ super(message);
+ this.errorCode = errorCode;
+ }
+
+ public HalException(int errorCode) {
+ this.errorCode = errorCode;
+ }
+
+ @Override
+ public @NonNull String toString() {
+ return super.toString() + " (code " + errorCode + ")";
+ }
+}
diff --git a/services/core/java/com/android/server/soundtrigger_middleware/Hw2CompatUtil.java b/services/core/java/com/android/server/soundtrigger_middleware/Hw2CompatUtil.java
new file mode 100644
index 000000000000..f0a0d8305bc6
--- /dev/null
+++ b/services/core/java/com/android/server/soundtrigger_middleware/Hw2CompatUtil.java
@@ -0,0 +1,77 @@
+/*
+ * Copyright (C) 2019 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.soundtrigger_middleware;
+
+import android.os.HidlMemoryUtil;
+
+import java.util.ArrayList;
+
+/**
+ * Utilities for maintaining data compatibility between different minor versions of soundtrigger@2.x
+ * HAL.
+ * Note that some of these conversion utilities are destructive, i.e. mutate their input (for the
+ * sake of simplifying code and reducing copies).
+ */
+class Hw2CompatUtil {
+ static android.hardware.soundtrigger.V2_0.ISoundTriggerHw.SoundModel convertSoundModel_2_1_to_2_0(
+ android.hardware.soundtrigger.V2_1.ISoundTriggerHw.SoundModel soundModel) {
+ android.hardware.soundtrigger.V2_0.ISoundTriggerHw.SoundModel model_2_0 = soundModel.header;
+ // Note: this mutates the input!
+ model_2_0.data = HidlMemoryUtil.hidlMemoryToByteList(soundModel.data);
+ return model_2_0;
+ }
+
+ static android.hardware.soundtrigger.V2_1.ISoundTriggerHwCallback.RecognitionEvent convertRecognitionEvent_2_0_to_2_1(
+ android.hardware.soundtrigger.V2_0.ISoundTriggerHwCallback.RecognitionEvent event) {
+ android.hardware.soundtrigger.V2_1.ISoundTriggerHwCallback.RecognitionEvent event_2_1 =
+ new android.hardware.soundtrigger.V2_1.ISoundTriggerHwCallback.RecognitionEvent();
+ event_2_1.header = event;
+ event_2_1.data = HidlMemoryUtil.byteListToHidlMemory(event_2_1.header.data,
+ "SoundTrigger RecognitionEvent");
+ // Note: this mutates the input!
+ event_2_1.header.data = new ArrayList<>();
+ return event_2_1;
+ }
+
+ static android.hardware.soundtrigger.V2_1.ISoundTriggerHwCallback.PhraseRecognitionEvent convertPhraseRecognitionEvent_2_0_to_2_1(
+ android.hardware.soundtrigger.V2_0.ISoundTriggerHwCallback.PhraseRecognitionEvent event) {
+ android.hardware.soundtrigger.V2_1.ISoundTriggerHwCallback.PhraseRecognitionEvent
+ event_2_1 =
+ new android.hardware.soundtrigger.V2_1.ISoundTriggerHwCallback.PhraseRecognitionEvent();
+ event_2_1.common = convertRecognitionEvent_2_0_to_2_1(event.common);
+ event_2_1.phraseExtras = event.phraseExtras;
+ return event_2_1;
+ }
+
+ static android.hardware.soundtrigger.V2_0.ISoundTriggerHw.PhraseSoundModel convertPhraseSoundModel_2_1_to_2_0(
+ android.hardware.soundtrigger.V2_1.ISoundTriggerHw.PhraseSoundModel soundModel) {
+ android.hardware.soundtrigger.V2_0.ISoundTriggerHw.PhraseSoundModel model_2_0 =
+ new android.hardware.soundtrigger.V2_0.ISoundTriggerHw.PhraseSoundModel();
+ model_2_0.common = convertSoundModel_2_1_to_2_0(soundModel.common);
+ model_2_0.phrases = soundModel.phrases;
+ return model_2_0;
+ }
+
+ static android.hardware.soundtrigger.V2_0.ISoundTriggerHw.RecognitionConfig convertRecognitionConfig_2_1_to_2_0(
+ android.hardware.soundtrigger.V2_1.ISoundTriggerHw.RecognitionConfig config) {
+ android.hardware.soundtrigger.V2_0.ISoundTriggerHw.RecognitionConfig config_2_0 =
+ config.header;
+ // Note: this mutates the input!
+ config_2_0.data = HidlMemoryUtil.hidlMemoryToByteList(config.data);
+ return config_2_0;
+ }
+}
diff --git a/services/core/java/com/android/server/soundtrigger_middleware/ISoundTriggerHw2.java b/services/core/java/com/android/server/soundtrigger_middleware/ISoundTriggerHw2.java
new file mode 100644
index 000000000000..81252c9a8c14
--- /dev/null
+++ b/services/core/java/com/android/server/soundtrigger_middleware/ISoundTriggerHw2.java
@@ -0,0 +1,158 @@
+/*
+ * Copyright (C) 2019 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.soundtrigger_middleware;
+
+import android.hardware.soundtrigger.V2_3.ISoundTriggerHw;
+import android.hardware.soundtrigger.V2_3.ModelParameterRange;
+import android.hidl.base.V1_0.IBase;
+import android.os.IHwBinder;
+
+/**
+ * This interface mimics android.hardware.soundtrigger.V2_x.ISoundTriggerHw and
+ * android.hardware.soundtrigger.V2_x.ISoundTriggerHwCallback, with a few key differences:
+ * <ul>
+ * <li>Methods in the original interface generally have a status return value and potentially a
+ * second return value which is the actual return value. This is reflected via a synchronous
+ * callback, which is not very pleasant to work with. This interface replaces that pattern with
+ * the convention that a HalException is thrown for non-OK status, and then we can use the
+ * return value for the actual return value.
+ * <li>This interface will always include all the methods from the latest 2.x version (and thus
+ * from every 2.x version) interface, with the convention that unsupported methods throw a
+ * {@link RecoverableException} with a
+ * {@link android.media.soundtrigger_middleware.Status#OPERATION_NOT_SUPPORTED}
+ * code.
+ * <li>Cases where the original interface had multiple versions of a method representing the exact
+ * thing, or there exists a trivial conversion between the new and old version, this interface
+ * represents only the latest version, without any _version suffixes.
+ * <li>Removes some of the obscure IBinder methods.
+ * <li>No RemoteExceptions are specified. Some implementations of this interface may rethrow
+ * RemoteExceptions as RuntimeExceptions, some can guarantee handling them somehow and never throw
+ * them.
+ * <li>soundModelCallback has been removed, since nobody cares about it. Implementations are free
+ * to silently discard it.
+ * </ul>
+ * For cases where the client wants to explicitly handle specific versions of the underlying driver
+ * interface, they may call {@link #interfaceDescriptor()}.
+ * <p>
+ * <b>Note to maintainers</b>: This class must always be kept in sync with the latest 2.x version,
+ * so that clients have access to the entire functionality without having to burden themselves with
+ * compatibility, as much as possible.
+ */
+public interface ISoundTriggerHw2 {
+ /**
+ * @see android.hardware.soundtrigger.V2_2.ISoundTriggerHw#getProperties(android.hardware.soundtrigger.V2_0.ISoundTriggerHw.getPropertiesCallback
+ */
+ android.hardware.soundtrigger.V2_1.ISoundTriggerHw.Properties getProperties();
+
+ /**
+ * @see android.hardware.soundtrigger.V2_2.ISoundTriggerHw#loadSoundModel_2_1(android.hardware.soundtrigger.V2_1.ISoundTriggerHw.SoundModel,
+ * android.hardware.soundtrigger.V2_1.ISoundTriggerHwCallback, int,
+ * android.hardware.soundtrigger.V2_1.ISoundTriggerHw.loadSoundModel_2_1Callback)
+ */
+ int loadSoundModel(
+ android.hardware.soundtrigger.V2_1.ISoundTriggerHw.SoundModel soundModel,
+ SoundTriggerHw2Compat.Callback callback, int cookie);
+
+ /**
+ * @see android.hardware.soundtrigger.V2_2.ISoundTriggerHw#loadPhraseSoundModel_2_1(android.hardware.soundtrigger.V2_1.ISoundTriggerHw.PhraseSoundModel,
+ * android.hardware.soundtrigger.V2_1.ISoundTriggerHwCallback, int,
+ * android.hardware.soundtrigger.V2_1.ISoundTriggerHw.loadPhraseSoundModel_2_1Callback)
+ */
+ int loadPhraseSoundModel(
+ android.hardware.soundtrigger.V2_1.ISoundTriggerHw.PhraseSoundModel soundModel,
+ SoundTriggerHw2Compat.Callback callback, int cookie);
+
+ /**
+ * @see android.hardware.soundtrigger.V2_2.ISoundTriggerHw#unloadSoundModel(int)
+ */
+ void unloadSoundModel(int modelHandle);
+
+ /**
+ * @see android.hardware.soundtrigger.V2_2.ISoundTriggerHw#stopRecognition(int)
+ */
+ void stopRecognition(int modelHandle);
+
+ /**
+ * @see android.hardware.soundtrigger.V2_2.ISoundTriggerHw#stopAllRecognitions()
+ */
+ void stopAllRecognitions();
+
+ /**
+ * @see android.hardware.soundtrigger.V2_2.ISoundTriggerHw#startRecognition_2_1(int,
+ * android.hardware.soundtrigger.V2_1.ISoundTriggerHw.RecognitionConfig,
+ * android.hardware.soundtrigger.V2_1.ISoundTriggerHwCallback, int)
+ */
+ void startRecognition(int modelHandle,
+ android.hardware.soundtrigger.V2_1.ISoundTriggerHw.RecognitionConfig config,
+ SoundTriggerHw2Compat.Callback callback, int cookie);
+
+ /**
+ * @see android.hardware.soundtrigger.V2_2.ISoundTriggerHw#getModelState(int)
+ */
+ void getModelState(int modelHandle);
+
+ /**
+ * @see android.hardware.soundtrigger.V2_3.ISoundTriggerHw#getParameter(int, int,
+ * ISoundTriggerHw.getParameterCallback)
+ */
+ int getModelParameter(int modelHandle, int param);
+
+ /**
+ * @see android.hardware.soundtrigger.V2_3.ISoundTriggerHw#setParameter(int, int, int)
+ */
+ void setModelParameter(int modelHandle, int param, int value);
+
+ /**
+ * @return null if not supported.
+ * @see android.hardware.soundtrigger.V2_3.ISoundTriggerHw#queryParameter(int, int,
+ * ISoundTriggerHw.queryParameterCallback)
+ */
+ ModelParameterRange queryParameter(int modelHandle, int param);
+
+ /**
+ * @see IHwBinder#linkToDeath(IHwBinder.DeathRecipient, long)
+ */
+ boolean linkToDeath(IHwBinder.DeathRecipient recipient, long cookie);
+
+ /**
+ * @see IHwBinder#unlinkToDeath(IHwBinder.DeathRecipient)
+ */
+ boolean unlinkToDeath(IHwBinder.DeathRecipient recipient);
+
+ /**
+ * @see IBase#interfaceDescriptor()
+ */
+ String interfaceDescriptor() throws android.os.RemoteException;
+
+ interface Callback {
+ /**
+ * @see android.hardware.soundtrigger.V2_1.ISoundTriggerHwCallback#recognitionCallback_2_1(android.hardware.soundtrigger.V2_1.ISoundTriggerHwCallback.RecognitionEvent,
+ * int)
+ */
+ void recognitionCallback(
+ android.hardware.soundtrigger.V2_1.ISoundTriggerHwCallback.RecognitionEvent event,
+ int cookie);
+
+ /**
+ * @see android.hardware.soundtrigger.V2_1.ISoundTriggerHwCallback#phraseRecognitionCallback_2_1(android.hardware.soundtrigger.V2_1.ISoundTriggerHwCallback.PhraseRecognitionEvent,
+ * int)
+ */
+ void phraseRecognitionCallback(
+ android.hardware.soundtrigger.V2_1.ISoundTriggerHwCallback.PhraseRecognitionEvent event,
+ int cookie);
+ }
+}
diff --git a/services/core/java/com/android/server/soundtrigger_middleware/InternalServerError.java b/services/core/java/com/android/server/soundtrigger_middleware/InternalServerError.java
new file mode 100644
index 000000000000..e1fb2266b7c6
--- /dev/null
+++ b/services/core/java/com/android/server/soundtrigger_middleware/InternalServerError.java
@@ -0,0 +1,40 @@
+/*
+ * Copyright (C) 2019 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.soundtrigger_middleware;
+
+import android.annotation.NonNull;
+
+/**
+ * An internal server error.
+ * <p>
+ * This exception wraps any exception thrown from a service implementation, which is a result of a
+ * bug in the server implementation (or any of its dependencies).
+ * <p>
+ * Specifically, this type is excluded from the set of whitelisted exceptions that binder would
+ * tunnel to the client process, since these exceptions are ambiguous regarding whether the client
+ * had done something wrong or the server is buggy. For example, a client getting an
+ * IllegalArgumentException cannot easily determine whether they had provided illegal arguments to
+ * the method they were calling, or whether the method implementation provided illegal arguments to
+ * some method it was calling due to a bug.
+ *
+ * @hide
+ */
+public class InternalServerError extends RuntimeException {
+ public InternalServerError(@NonNull Throwable cause) {
+ super(cause);
+ }
+}
diff --git a/services/core/java/com/android/server/soundtrigger_middleware/RecoverableException.java b/services/core/java/com/android/server/soundtrigger_middleware/RecoverableException.java
new file mode 100644
index 000000000000..83618505814e
--- /dev/null
+++ b/services/core/java/com/android/server/soundtrigger_middleware/RecoverableException.java
@@ -0,0 +1,53 @@
+/*
+ * Copyright (C) 2019 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.soundtrigger_middleware;
+
+import android.annotation.NonNull;
+
+/**
+ * This exception represents a fault which:
+ * <ul>
+ * <li>Could not have been anticipated by a caller (i.e. is not a violation of any preconditions).
+ * <li>Is guaranteed to not have been caused any meaningful state change in the callee. The caller
+ * may continue operation as if the call has never been made.
+ * </ul>
+ * <p>
+ * Some recoverable faults are permanent and some are transient / circumstantial, the specific error
+ * code can provide more information about the possible recovery options.
+ * <p>
+ * The reason why this is a RuntimeException is to allow it to go through interfaces defined by
+ * AIDL, which we have no control over.
+ *
+ * @hide
+ */
+public class RecoverableException extends RuntimeException {
+ public final int errorCode;
+
+ public RecoverableException(int errorCode, @NonNull String message) {
+ super(message);
+ this.errorCode = errorCode;
+ }
+
+ public RecoverableException(int errorCode) {
+ this.errorCode = errorCode;
+ }
+
+ @Override
+ public @NonNull String toString() {
+ return super.toString() + " (code " + errorCode + ")";
+ }
+}
diff --git a/services/core/java/com/android/server/soundtrigger_middleware/SoundTriggerHw2Compat.java b/services/core/java/com/android/server/soundtrigger_middleware/SoundTriggerHw2Compat.java
new file mode 100644
index 000000000000..4a852c4b68e8
--- /dev/null
+++ b/services/core/java/com/android/server/soundtrigger_middleware/SoundTriggerHw2Compat.java
@@ -0,0 +1,470 @@
+/*
+ * Copyright (C) 2019 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.soundtrigger_middleware;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.media.soundtrigger_middleware.Status;
+import android.os.IHwBinder;
+import android.os.RemoteException;
+
+import java.util.Objects;
+import java.util.concurrent.atomic.AtomicInteger;
+import java.util.concurrent.atomic.AtomicReference;
+
+/**
+ * An implementation of {@link ISoundTriggerHw2}, on top of any
+ * android.hardware.soundtrigger.V2_x.ISoundTriggerHw implementation. This class hides away some of
+ * the details involved with retaining backward compatibility and adapts to the more pleasant syntax
+ * exposed by {@link ISoundTriggerHw2}, compared to the bare driver interface.
+ * <p>
+ * Exception handling:
+ * <ul>
+ * <li>All {@link RemoteException}s get rethrown as {@link RuntimeException}.
+ * <li>All HAL malfunctions get thrown as {@link HalException}.
+ * <li>All unsupported operations get thrown as {@link RecoverableException} with a
+ * {@link android.media.soundtrigger_middleware.Status#OPERATION_NOT_SUPPORTED}
+ * code.
+ * </ul>
+ */
+final class SoundTriggerHw2Compat implements ISoundTriggerHw2 {
+ private final @NonNull
+ IHwBinder mBinder;
+ private final @NonNull
+ android.hardware.soundtrigger.V2_0.ISoundTriggerHw mUnderlying_2_0;
+ private final @Nullable
+ android.hardware.soundtrigger.V2_1.ISoundTriggerHw mUnderlying_2_1;
+ private final @Nullable
+ android.hardware.soundtrigger.V2_2.ISoundTriggerHw mUnderlying_2_2;
+ private final @Nullable
+ android.hardware.soundtrigger.V2_3.ISoundTriggerHw mUnderlying_2_3;
+
+ public SoundTriggerHw2Compat(
+ @NonNull android.hardware.soundtrigger.V2_0.ISoundTriggerHw underlying) {
+ this(underlying.asBinder());
+ }
+
+ public SoundTriggerHw2Compat(IHwBinder binder) {
+ Objects.requireNonNull(binder);
+
+ mBinder = binder;
+
+ // We want to share the proxy instances rather than create a separate proxy for every
+ // version, so we go down the versions in descending order to find the latest one supported,
+ // and then simply up-cast it to obtain all the versions that are earlier.
+
+ // Attempt 2.3
+ android.hardware.soundtrigger.V2_3.ISoundTriggerHw as2_3 =
+ android.hardware.soundtrigger.V2_3.ISoundTriggerHw.asInterface(binder);
+ if (as2_3 != null) {
+ mUnderlying_2_0 = mUnderlying_2_1 = mUnderlying_2_2 = mUnderlying_2_3 = as2_3;
+ return;
+ }
+
+ // Attempt 2.2
+ android.hardware.soundtrigger.V2_2.ISoundTriggerHw as2_2 =
+ android.hardware.soundtrigger.V2_2.ISoundTriggerHw.asInterface(binder);
+ if (as2_2 != null) {
+ mUnderlying_2_0 = mUnderlying_2_1 = mUnderlying_2_2 = as2_2;
+ mUnderlying_2_3 = null;
+ return;
+ }
+
+ // Attempt 2.1
+ android.hardware.soundtrigger.V2_1.ISoundTriggerHw as2_1 =
+ android.hardware.soundtrigger.V2_1.ISoundTriggerHw.asInterface(binder);
+ if (as2_1 != null) {
+ mUnderlying_2_0 = mUnderlying_2_1 = as2_1;
+ mUnderlying_2_2 = mUnderlying_2_3 = null;
+ return;
+ }
+
+ // Attempt 2.0
+ android.hardware.soundtrigger.V2_0.ISoundTriggerHw as2_0 =
+ android.hardware.soundtrigger.V2_0.ISoundTriggerHw.asInterface(binder);
+ if (as2_0 != null) {
+ mUnderlying_2_0 = as2_0;
+ mUnderlying_2_1 = mUnderlying_2_2 = mUnderlying_2_3 = null;
+ return;
+ }
+
+ throw new RuntimeException("Binder doesn't support ISoundTriggerHw@2.0");
+ }
+
+ private static void handleHalStatus(int status, String methodName) {
+ if (status != 0) {
+ throw new HalException(status, methodName);
+ }
+ }
+
+ @Override
+ public android.hardware.soundtrigger.V2_1.ISoundTriggerHw.Properties getProperties() {
+ try {
+ AtomicInteger retval = new AtomicInteger(-1);
+ AtomicReference<android.hardware.soundtrigger.V2_1.ISoundTriggerHw.Properties>
+ properties =
+ new AtomicReference<>();
+ as2_0().getProperties(
+ (r, p) -> {
+ retval.set(r);
+ properties.set(p);
+ });
+ handleHalStatus(retval.get(), "getProperties");
+ return properties.get();
+ } catch (RemoteException e) {
+ throw e.rethrowAsRuntimeException();
+ }
+ }
+
+ @Override
+ public int loadSoundModel(
+ android.hardware.soundtrigger.V2_1.ISoundTriggerHw.SoundModel soundModel,
+ Callback callback, int cookie) {
+ try {
+ AtomicInteger retval = new AtomicInteger(-1);
+ AtomicInteger handle = new AtomicInteger(0);
+ try {
+ as2_1().loadSoundModel_2_1(soundModel, new SoundTriggerCallback(callback), cookie,
+ (r, h) -> {
+ retval.set(r);
+ handle.set(h);
+ });
+ } catch (NotSupported e) {
+ // Fall-back to the 2.0 version:
+ return loadSoundModel_2_0(soundModel, callback, cookie);
+ }
+ handleHalStatus(retval.get(), "loadSoundModel_2_1");
+ return handle.get();
+ } catch (RemoteException e) {
+ throw e.rethrowAsRuntimeException();
+ }
+ }
+
+ @Override
+ public int loadPhraseSoundModel(
+ android.hardware.soundtrigger.V2_1.ISoundTriggerHw.PhraseSoundModel soundModel,
+ Callback callback, int cookie) {
+ try {
+ AtomicInteger retval = new AtomicInteger(-1);
+ AtomicInteger handle = new AtomicInteger(0);
+ try {
+ as2_1().loadPhraseSoundModel_2_1(soundModel, new SoundTriggerCallback(callback),
+ cookie,
+ (r, h) -> {
+ retval.set(r);
+ handle.set(h);
+ });
+ } catch (NotSupported e) {
+ // Fall-back to the 2.0 version:
+ return loadPhraseSoundModel_2_0(soundModel, callback, cookie);
+ }
+ handleHalStatus(retval.get(), "loadSoundModel_2_1");
+ return handle.get();
+ } catch (RemoteException e) {
+ throw e.rethrowAsRuntimeException();
+ }
+ }
+
+ @Override
+ public void unloadSoundModel(int modelHandle) {
+ try {
+ int retval = as2_0().unloadSoundModel(modelHandle);
+ handleHalStatus(retval, "unloadSoundModel");
+ } catch (RemoteException e) {
+ throw e.rethrowAsRuntimeException();
+ }
+ }
+
+ @Override
+ public void stopRecognition(int modelHandle) {
+ try {
+ int retval = as2_0().stopRecognition(modelHandle);
+ handleHalStatus(retval, "stopRecognition");
+ } catch (RemoteException e) {
+ throw e.rethrowAsRuntimeException();
+ }
+
+ }
+
+ @Override
+ public void stopAllRecognitions() {
+ try {
+ int retval = as2_0().stopAllRecognitions();
+ handleHalStatus(retval, "stopAllRecognitions");
+ } catch (RemoteException e) {
+ throw e.rethrowAsRuntimeException();
+ }
+ }
+
+ @Override
+ public void startRecognition(int modelHandle,
+ android.hardware.soundtrigger.V2_1.ISoundTriggerHw.RecognitionConfig config,
+ Callback callback, int cookie) {
+ try {
+ try {
+ int retval = as2_1().startRecognition_2_1(modelHandle, config,
+ new SoundTriggerCallback(callback), cookie);
+ handleHalStatus(retval, "startRecognition_2_1");
+ } catch (NotSupported e) {
+ // Fall-back to the 2.0 version:
+ startRecognition_2_0(modelHandle, config, callback, cookie);
+ }
+ } catch (RemoteException e) {
+ throw e.rethrowAsRuntimeException();
+ }
+ }
+
+ @Override
+ public void getModelState(int modelHandle) {
+ try {
+ int retval = as2_2().getModelState(modelHandle);
+ handleHalStatus(retval, "getModelState");
+ } catch (RemoteException e) {
+ throw e.rethrowAsRuntimeException();
+ } catch (NotSupported e) {
+ throw e.throwAsRecoverableException();
+ }
+ }
+
+ @Override
+ public int getModelParameter(int modelHandle, int param) {
+ AtomicInteger status = new AtomicInteger(-1);
+ AtomicInteger value = new AtomicInteger(0);
+ try {
+ as2_3().getParameter(modelHandle, param,
+ (s, v) -> {
+ status.set(s);
+ value.set(v);
+ });
+ } catch (RemoteException e) {
+ throw e.rethrowAsRuntimeException();
+ } catch (NotSupported e) {
+ throw e.throwAsRecoverableException();
+ }
+ handleHalStatus(status.get(), "getParameter");
+ return value.get();
+ }
+
+ @Override
+ public void setModelParameter(int modelHandle, int param, int value) {
+ try {
+ int retval = as2_3().setParameter(modelHandle, param, value);
+ handleHalStatus(retval, "setParameter");
+ } catch (RemoteException e) {
+ throw e.rethrowAsRuntimeException();
+ } catch (NotSupported e) {
+ throw e.throwAsRecoverableException();
+ }
+ }
+
+ @Override
+ public android.hardware.soundtrigger.V2_3.ModelParameterRange queryParameter(int modelHandle,
+ int param) {
+ AtomicInteger status = new AtomicInteger(-1);
+ AtomicReference<android.hardware.soundtrigger.V2_3.OptionalModelParameterRange>
+ optionalRange =
+ new AtomicReference<>();
+ try {
+ as2_3().queryParameter(modelHandle, param,
+ (s, r) -> {
+ status.set(s);
+ optionalRange.set(r);
+ });
+ } catch (NotSupported e) {
+ // For older drivers, we consider no model parameter to be supported.
+ return null;
+ } catch (RemoteException e) {
+ throw e.rethrowAsRuntimeException();
+ }
+ handleHalStatus(status.get(), "queryParameter");
+ return (optionalRange.get().getDiscriminator()
+ == android.hardware.soundtrigger.V2_3.OptionalModelParameterRange.hidl_discriminator.range)
+ ?
+ optionalRange.get().range() : null;
+ }
+
+ @Override
+ public boolean linkToDeath(IHwBinder.DeathRecipient recipient, long cookie) {
+ return mBinder.linkToDeath(recipient, cookie);
+ }
+
+ @Override
+ public boolean unlinkToDeath(IHwBinder.DeathRecipient recipient) {
+ return mBinder.unlinkToDeath(recipient);
+ }
+
+ @Override
+ public String interfaceDescriptor() throws RemoteException {
+ return as2_0().interfaceDescriptor();
+ }
+
+ private int loadSoundModel_2_0(
+ android.hardware.soundtrigger.V2_1.ISoundTriggerHw.SoundModel soundModel,
+ Callback callback, int cookie)
+ throws RemoteException {
+ // Convert the soundModel to V2.0.
+ android.hardware.soundtrigger.V2_0.ISoundTriggerHw.SoundModel model_2_0 =
+ Hw2CompatUtil.convertSoundModel_2_1_to_2_0(soundModel);
+
+ AtomicInteger retval = new AtomicInteger(-1);
+ AtomicInteger handle = new AtomicInteger(0);
+ as2_0().loadSoundModel(model_2_0, new SoundTriggerCallback(callback), cookie, (r, h) -> {
+ retval.set(r);
+ handle.set(h);
+ });
+ handleHalStatus(retval.get(), "loadSoundModel");
+ return handle.get();
+ }
+
+ private int loadPhraseSoundModel_2_0(
+ android.hardware.soundtrigger.V2_1.ISoundTriggerHw.PhraseSoundModel soundModel,
+ Callback callback, int cookie)
+ throws RemoteException {
+ // Convert the soundModel to V2.0.
+ android.hardware.soundtrigger.V2_0.ISoundTriggerHw.PhraseSoundModel model_2_0 =
+ Hw2CompatUtil.convertPhraseSoundModel_2_1_to_2_0(soundModel);
+
+ AtomicInteger retval = new AtomicInteger(-1);
+ AtomicInteger handle = new AtomicInteger(0);
+ as2_0().loadPhraseSoundModel(model_2_0, new SoundTriggerCallback(callback), cookie,
+ (r, h) -> {
+ retval.set(r);
+ handle.set(h);
+ });
+ handleHalStatus(retval.get(), "loadSoundModel");
+ return handle.get();
+ }
+
+ private void startRecognition_2_0(int modelHandle,
+ android.hardware.soundtrigger.V2_1.ISoundTriggerHw.RecognitionConfig config,
+ Callback callback, int cookie)
+ throws RemoteException {
+
+ android.hardware.soundtrigger.V2_0.ISoundTriggerHw.RecognitionConfig config_2_0 =
+ Hw2CompatUtil.convertRecognitionConfig_2_1_to_2_0(config);
+ int retval = as2_0().startRecognition(modelHandle, config_2_0,
+ new SoundTriggerCallback(callback), cookie);
+ handleHalStatus(retval, "startRecognition");
+ }
+
+ private @NonNull
+ android.hardware.soundtrigger.V2_0.ISoundTriggerHw as2_0() {
+ return mUnderlying_2_0;
+ }
+
+ private @NonNull
+ android.hardware.soundtrigger.V2_1.ISoundTriggerHw as2_1() throws NotSupported {
+ if (mUnderlying_2_1 == null) {
+ throw new NotSupported("Underlying driver version < 2.1");
+ }
+ return mUnderlying_2_1;
+ }
+
+ private @NonNull
+ android.hardware.soundtrigger.V2_2.ISoundTriggerHw as2_2() throws NotSupported {
+ if (mUnderlying_2_2 == null) {
+ throw new NotSupported("Underlying driver version < 2.2");
+ }
+ return mUnderlying_2_2;
+ }
+
+ private @NonNull
+ android.hardware.soundtrigger.V2_3.ISoundTriggerHw as2_3() throws NotSupported {
+ if (mUnderlying_2_3 == null) {
+ throw new NotSupported("Underlying driver version < 2.3");
+ }
+ return mUnderlying_2_3;
+ }
+
+ /**
+ * A checked exception representing the requested interface version not being supported.
+ * At the public interface layer, use {@link #throwAsRecoverableException()} to propagate it to
+ * the caller if the request cannot be fulfilled.
+ */
+ private static class NotSupported extends Exception {
+ NotSupported(String message) {
+ super(message);
+ }
+
+ /**
+ * Throw this as a recoverable exception.
+ *
+ * @return Never actually returns anything. Always throws. Used so that caller can write
+ * throw e.throwAsRecoverableException().
+ */
+ RecoverableException throwAsRecoverableException() {
+ throw new RecoverableException(Status.OPERATION_NOT_SUPPORTED, getMessage());
+ }
+ }
+
+ private static class SoundTriggerCallback extends
+ android.hardware.soundtrigger.V2_1.ISoundTriggerHwCallback.Stub {
+ private final @NonNull
+ Callback mDelegate;
+
+ private SoundTriggerCallback(
+ @NonNull Callback delegate) {
+ mDelegate = Objects.requireNonNull(delegate);
+ }
+
+ @Override
+ public void recognitionCallback_2_1(
+ android.hardware.soundtrigger.V2_1.ISoundTriggerHwCallback.RecognitionEvent event,
+ int cookie) {
+ mDelegate.recognitionCallback(event, cookie);
+ }
+
+ @Override
+ public void phraseRecognitionCallback_2_1(
+ android.hardware.soundtrigger.V2_1.ISoundTriggerHwCallback.PhraseRecognitionEvent event,
+ int cookie) {
+ mDelegate.phraseRecognitionCallback(event, cookie);
+ }
+
+ @Override
+ public void soundModelCallback_2_1(
+ android.hardware.soundtrigger.V2_1.ISoundTriggerHwCallback.ModelEvent event,
+ int cookie) {
+ // Nobody cares.
+ }
+
+ @Override
+ public void recognitionCallback(
+ android.hardware.soundtrigger.V2_0.ISoundTriggerHwCallback.RecognitionEvent event,
+ int cookie) {
+ android.hardware.soundtrigger.V2_1.ISoundTriggerHwCallback.RecognitionEvent event_2_1 =
+ Hw2CompatUtil.convertRecognitionEvent_2_0_to_2_1(event);
+ mDelegate.recognitionCallback(event_2_1, cookie);
+ }
+
+ @Override
+ public void phraseRecognitionCallback(
+ android.hardware.soundtrigger.V2_0.ISoundTriggerHwCallback.PhraseRecognitionEvent event,
+ int cookie) {
+ android.hardware.soundtrigger.V2_1.ISoundTriggerHwCallback.PhraseRecognitionEvent
+ event_2_1 = Hw2CompatUtil.convertPhraseRecognitionEvent_2_0_to_2_1(event);
+ mDelegate.phraseRecognitionCallback(event_2_1, cookie);
+ }
+
+ @Override
+ public void soundModelCallback(
+ android.hardware.soundtrigger.V2_0.ISoundTriggerHwCallback.ModelEvent event,
+ int cookie) {
+ // Nobody cares.
+ }
+ }
+}
diff --git a/services/core/java/com/android/server/soundtrigger_middleware/SoundTriggerMiddlewareImpl.java b/services/core/java/com/android/server/soundtrigger_middleware/SoundTriggerMiddlewareImpl.java
new file mode 100644
index 000000000000..9d51b65ea152
--- /dev/null
+++ b/services/core/java/com/android/server/soundtrigger_middleware/SoundTriggerMiddlewareImpl.java
@@ -0,0 +1,140 @@
+/*
+ * Copyright (C) 2019 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.soundtrigger_middleware;
+
+import android.annotation.NonNull;
+import android.hardware.soundtrigger.V2_0.ISoundTriggerHw;
+import android.media.soundtrigger_middleware.ISoundTriggerCallback;
+import android.media.soundtrigger_middleware.ISoundTriggerMiddlewareService;
+import android.media.soundtrigger_middleware.ISoundTriggerModule;
+import android.media.soundtrigger_middleware.SoundTriggerModuleDescriptor;
+import android.os.IBinder;
+import android.util.Log;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * This is an implementation of the ISoundTriggerMiddlewareService interface.
+ * <p>
+ * <b>Important conventions:</b>
+ * <ul>
+ * <li>Correct usage is assumed. This implementation does not attempt to gracefully handle invalid
+ * usage, and such usage will result in undefined behavior. If this service is to be offered to an
+ * untrusted client, it must be wrapped with input and state validation.
+ * <li>There is no binder instance associated with this implementation. Do not call asBinder().
+ * <li>The implementation may throw a {@link RecoverableException} to indicate non-fatal,
+ * recoverable faults. The error code would one of the
+ * {@link android.media.soundtrigger_middleware.Status}
+ * constants. Any other exception thrown should be regarded as a bug in the implementation or one
+ * of its dependencies (assuming correct usage).
+ * <li>The implementation is designed for testibility by featuring dependency injection (the
+ * underlying HAL driver instances are passed to the ctor) and by minimizing dependencies on
+ * Android runtime.
+ * <li>The implementation is thread-safe.
+ * </ul>
+ *
+ * @hide
+ */
+public class SoundTriggerMiddlewareImpl implements ISoundTriggerMiddlewareService {
+ static private final String TAG = "SoundTriggerMiddlewareImpl";
+ private final SoundTriggerModule[] mModules;
+
+ /**
+ * Interface to the audio system, which can allocate capture session handles.
+ * SoundTrigger uses those sessions in order to associate a recognition session with an optional
+ * capture from the same device that triggered the recognition.
+ */
+ public static abstract class AudioSessionProvider {
+ public static final class AudioSession {
+ final int mSessionHandle;
+ final int mIoHandle;
+ final int mDeviceHandle;
+
+ AudioSession(int sessionHandle, int ioHandle, int deviceHandle) {
+ mSessionHandle = sessionHandle;
+ mIoHandle = ioHandle;
+ mDeviceHandle = deviceHandle;
+ }
+ }
+
+ public abstract AudioSession acquireSession();
+
+ public abstract void releaseSession(int sessionHandle);
+ }
+
+ /**
+ * Most generic constructor - gets an array of HAL driver instances.
+ */
+ public SoundTriggerMiddlewareImpl(@NonNull ISoundTriggerHw[] halServices,
+ @NonNull AudioSessionProvider audioSessionProvider) {
+ List<SoundTriggerModule> modules = new ArrayList<>(halServices.length);
+
+ for (int i = 0; i < halServices.length; ++i) {
+ ISoundTriggerHw service = halServices[i];
+ try {
+ modules.add(new SoundTriggerModule(service, audioSessionProvider));
+ } catch (Exception e) {
+ Log.e(TAG, "Failed to a SoundTriggerModule instance", e);
+ }
+ }
+
+ mModules = modules.toArray(new SoundTriggerModule[modules.size()]);
+ }
+
+ /**
+ * Convenience constructor - gets a single HAL driver instance.
+ */
+ public SoundTriggerMiddlewareImpl(@NonNull ISoundTriggerHw halService,
+ @NonNull AudioSessionProvider audioSessionProvider) {
+ this(new ISoundTriggerHw[]{halService}, audioSessionProvider);
+ }
+
+ @Override
+ public @NonNull
+ SoundTriggerModuleDescriptor[] listModules() {
+ SoundTriggerModuleDescriptor[] result = new SoundTriggerModuleDescriptor[mModules.length];
+
+ for (int i = 0; i < mModules.length; ++i) {
+ SoundTriggerModuleDescriptor desc = new SoundTriggerModuleDescriptor();
+ desc.handle = i;
+ desc.properties = mModules[i].getProperties();
+ result[i] = desc;
+ }
+ return result;
+ }
+
+ @Override
+ public @NonNull
+ ISoundTriggerModule attach(int handle, @NonNull ISoundTriggerCallback callback) {
+ return mModules[handle].attach(callback);
+ }
+
+ @Override
+ public void setExternalCaptureState(boolean active) {
+ for (SoundTriggerModule module : mModules) {
+ module.setExternalCaptureState(active);
+ }
+ }
+
+ @Override
+ public @NonNull
+ IBinder asBinder() {
+ throw new UnsupportedOperationException(
+ "This implementation is not inteded to be used directly with Binder.");
+ }
+} \ No newline at end of file
diff --git a/services/core/java/com/android/server/soundtrigger_middleware/SoundTriggerMiddlewareService.java b/services/core/java/com/android/server/soundtrigger_middleware/SoundTriggerMiddlewareService.java
new file mode 100644
index 000000000000..a7cfe1037f11
--- /dev/null
+++ b/services/core/java/com/android/server/soundtrigger_middleware/SoundTriggerMiddlewareService.java
@@ -0,0 +1,709 @@
+/*
+ * Copyright (C) 2019 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.soundtrigger_middleware;
+
+import android.Manifest;
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.content.Context;
+import android.hardware.soundtrigger.V2_0.ISoundTriggerHw;
+import android.media.soundtrigger_middleware.ISoundTriggerCallback;
+import android.media.soundtrigger_middleware.ISoundTriggerMiddlewareService;
+import android.media.soundtrigger_middleware.ISoundTriggerModule;
+import android.media.soundtrigger_middleware.ModelParameterRange;
+import android.media.soundtrigger_middleware.PhraseRecognitionEvent;
+import android.media.soundtrigger_middleware.PhraseSoundModel;
+import android.media.soundtrigger_middleware.RecognitionConfig;
+import android.media.soundtrigger_middleware.RecognitionEvent;
+import android.media.soundtrigger_middleware.RecognitionStatus;
+import android.media.soundtrigger_middleware.SoundModel;
+import android.media.soundtrigger_middleware.SoundTriggerModuleDescriptor;
+import android.os.RemoteException;
+import android.os.ServiceSpecificException;
+import android.util.Log;
+
+import com.android.internal.util.Preconditions;
+import com.android.server.SystemService;
+
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.Map;
+import java.util.Set;
+
+/**
+ * This is a wrapper around an {@link ISoundTriggerMiddlewareService} implementation, which exposes
+ * it as a Binder service and enforces permissions and correct usage by the client, as well as makes
+ * sure that exceptions representing a server malfunction do not get sent to the client.
+ * <p>
+ * This is intended to extract the non-business logic out of the underlying implementation and thus
+ * make it easier to maintain each one of those separate aspects. A design trade-off is being made
+ * here, in that this class would need to essentially eavesdrop on all the client-server
+ * communication and retain all state known to the client, while the client doesn't necessarily care
+ * about all of it, and while the server has its own representation of this information. However,
+ * in this case, this is a small amount of data, and the benefits in code elegance seem worth it.
+ * There is also some additional cost in employing a simplistic locking mechanism here, but
+ * following the same line of reasoning, the benefits in code simplicity outweigh it.
+ * <p>
+ * Every public method in this class, overriding an interface method, must follow the following
+ * pattern:
+ * <code><pre>
+ * @Override public T method(S arg) {
+ * // Permission check.
+ * checkPermissions();
+ * // Input validation.
+ * ValidationUtil.validateS(arg);
+ * synchronized (this) {
+ * // State validation.
+ * if (...state is not valid for this call...) {
+ * throw new IllegalStateException("State is invalid because...");
+ * }
+ * // From here on, every exception isn't client's fault.
+ * try {
+ * T result = mDelegate.method(arg);
+ * // Update state.;
+ * ...
+ * return result;
+ * } catch (Exception e) {
+ * throw handleException(e);
+ * }
+ * }
+ * }
+ * </pre></code>
+ * Following this patterns ensures a consistent and rigorous handling of all aspects associated
+ * with client-server separation.
+ * <p>
+ * <b>Exception handling approach:</b><br>
+ * We make sure all client faults (permissions, argument and state validation) happen first, and
+ * would throw {@link SecurityException}, {@link IllegalArgumentException}/
+ * {@link NullPointerException} or {@link
+ * IllegalStateException}, respectively. All those exceptions are treated specially by Binder and
+ * will get sent back to the client.<br>
+ * Once this is done, any subsequent fault is considered a server fault. Only {@link
+ * RecoverableException}s thrown by the implementation are special-cased: they would get sent back
+ * to the caller as a {@link ServiceSpecificException}, which is the behavior of Binder. Any other
+ * exception gets wrapped with a {@link InternalServerError}, which is specifically chosen as a type
+ * that <b>does NOT</b> get forwarded by binder. Those exceptions would be handled by a high-level
+ * exception handler on the server side, typically resulting in rebooting the server.
+ * <p>
+ * <b>Exposing this service as a System Service:</b><br>
+ * Insert this line into {@link com.android.server.SystemServer}:
+ * <code><pre>
+ * mSystemServiceManager.startService(SoundTriggerMiddlewareService.Lifecycle.class);
+ * </pre></code>
+ *
+ * {@hide}
+ */
+public class SoundTriggerMiddlewareService extends ISoundTriggerMiddlewareService.Stub {
+ static private final String TAG = "SoundTriggerMiddlewareService";
+
+ final ISoundTriggerMiddlewareService mDelegate;
+ final Context mContext;
+ Set<Integer> mModuleHandles;
+
+ /**
+ * Constructor for internal use only. Could be exposed for testing purposes in the future.
+ * Users should access this class via {@link Lifecycle}.
+ */
+ private SoundTriggerMiddlewareService(
+ @NonNull ISoundTriggerMiddlewareService delegate, @NonNull Context context) {
+ mDelegate = delegate;
+ mContext = context;
+ }
+
+ /**
+ * Generic exception handling for exceptions thrown by the underlying implementation.
+ *
+ * Would throw any {@link RecoverableException} as a {@link ServiceSpecificException} (passed
+ * by Binder to the caller) and <i>any other</i> exception as {@link InternalServerError}
+ * (<b>not</b> passed by Binder to the caller).
+ * <p>
+ * Typical usage:
+ * <code><pre>
+ * try {
+ * ... Do server operations ...
+ * } catch (Exception e) {
+ * throw handleException(e);
+ * }
+ * </pre></code>
+ */
+ private static @NonNull
+ RuntimeException handleException(@NonNull Exception e) {
+ if (e instanceof RecoverableException) {
+ throw new ServiceSpecificException(((RecoverableException) e).errorCode,
+ e.getMessage());
+ }
+ throw new InternalServerError(e);
+ }
+
+ @Override
+ public @NonNull
+ SoundTriggerModuleDescriptor[] listModules() {
+ // Permission check.
+ checkPermissions();
+ // Input validation (always valid).
+
+ synchronized (this) {
+ // State validation (always valid).
+
+ // From here on, every exception isn't client's fault.
+ try {
+ SoundTriggerModuleDescriptor[] result = mDelegate.listModules();
+ mModuleHandles = new HashSet<>(result.length);
+ for (SoundTriggerModuleDescriptor desc : result) {
+ mModuleHandles.add(desc.handle);
+ }
+ return result;
+ } catch (Exception e) {
+ throw handleException(e);
+ }
+ }
+ }
+
+ @Override
+ public @NonNull
+ ISoundTriggerModule attach(int handle, @NonNull ISoundTriggerCallback callback) {
+ // Permission check.
+ checkPermissions();
+ // Input validation.
+ Preconditions.checkNotNull(callback);
+ Preconditions.checkNotNull(callback.asBinder());
+
+ synchronized (this) {
+ // State validation.
+ if (mModuleHandles == null) {
+ throw new IllegalStateException(
+ "Client must call listModules() prior to attaching.");
+ }
+ if (!mModuleHandles.contains(handle)) {
+ throw new IllegalArgumentException("Invalid handle: " + handle);
+ }
+
+ // From here on, every exception isn't client's fault.
+ try {
+ ModuleService moduleService = new ModuleService(callback);
+ moduleService.attach(mDelegate.attach(handle, moduleService));
+ return moduleService;
+ } catch (Exception e) {
+ throw handleException(e);
+ }
+ }
+ }
+
+ @Override
+ public void setExternalCaptureState(boolean active) {
+ // Permission check.
+ checkPreemptPermissions();
+ // Input validation (always valid).
+
+ synchronized (this) {
+ // State validation (always valid).
+
+ // From here on, every exception isn't client's fault.
+ try {
+ mDelegate.setExternalCaptureState(active);
+ } catch (Exception e) {
+ throw handleException(e);
+ }
+ }
+ }
+
+ /**
+ * Throws a {@link SecurityException} if caller doesn't have the right permissions to use this
+ * service.
+ */
+ private void checkPermissions() {
+ mContext.enforceCallingOrSelfPermission(Manifest.permission.RECORD_AUDIO,
+ "Caller must have the android.permission.RECORD_AUDIO permission.");
+ mContext.enforceCallingOrSelfPermission(Manifest.permission.CAPTURE_AUDIO_HOTWORD,
+ "Caller must have the android.permission.CAPTURE_AUDIO_HOTWORD permission.");
+ }
+
+ /**
+ * Throws a {@link SecurityException} if caller doesn't have the right permissions to preempt
+ * active sound trigger sessions.
+ */
+ private void checkPreemptPermissions() {
+ mContext.enforceCallingOrSelfPermission(Manifest.permission.PREEMPT_SOUND_TRIGGER,
+ "Caller must have the android.permission.PREEMPT_SOUND_TRIGGER permission.");
+ }
+
+ /** State of a sound model. */
+ static class ModelState {
+ /** Activity state of a sound model. */
+ enum Activity {
+ /** Model is loaded, recognition is inactive. */
+ LOADED,
+ /** Model is loaded, recognition is active. */
+ ACTIVE
+ }
+
+ /** Activity state. */
+ public Activity activityState = Activity.LOADED;
+
+ /**
+ * A map of known parameter support. A missing key means we don't know yet whether the
+ * parameter is supported. A null value means it is known to not be supported. A non-null
+ * value indicates the valid value range.
+ */
+ private Map<Integer, ModelParameterRange> parameterSupport = new HashMap<>();
+
+ /**
+ * Check that the given parameter is known to be supported for this model.
+ *
+ * @param modelParam The parameter key.
+ */
+ public void checkSupported(int modelParam) {
+ if (!parameterSupport.containsKey(modelParam)) {
+ throw new IllegalStateException("Parameter has not been checked for support.");
+ }
+ ModelParameterRange range = parameterSupport.get(modelParam);
+ if (range == null) {
+ throw new IllegalArgumentException("Paramater is not supported.");
+ }
+ }
+
+ /**
+ * Check that the given parameter is known to be supported for this model and that the given
+ * value is a valid value for it.
+ *
+ * @param modelParam The parameter key.
+ * @param value The value.
+ */
+ public void checkSupported(int modelParam, int value) {
+ if (!parameterSupport.containsKey(modelParam)) {
+ throw new IllegalStateException("Parameter has not been checked for support.");
+ }
+ ModelParameterRange range = parameterSupport.get(modelParam);
+ if (range == null) {
+ throw new IllegalArgumentException("Paramater is not supported.");
+ }
+ Preconditions.checkArgumentInRange(value, range.minInclusive, range.maxInclusive,
+ "value");
+ }
+
+ /**
+ * Update support state for the given parameter for this model.
+ *
+ * @param modelParam The parameter key.
+ * @param range The parameter value range, or null if not supported.
+ */
+ public void updateParameterSupport(int modelParam, @Nullable ModelParameterRange range) {
+ parameterSupport.put(modelParam, range);
+ }
+ }
+
+ /**
+ * Entry-point to this module: exposes the module as a {@link SystemService}.
+ */
+ public static final class Lifecycle extends SystemService {
+ private SoundTriggerMiddlewareService mService;
+
+ public Lifecycle(Context context) {
+ super(context);
+ }
+
+ @Override
+ public void onStart() {
+ ISoundTriggerHw[] services;
+ try {
+ services = new ISoundTriggerHw[]{ISoundTriggerHw.getService(true)};
+ Log.d(TAG, "Connected to default ISoundTriggerHw");
+ } catch (Exception e) {
+ Log.e(TAG, "Failed to connect to default ISoundTriggerHw", e);
+ services = new ISoundTriggerHw[0];
+ }
+
+ mService = new SoundTriggerMiddlewareService(
+ new SoundTriggerMiddlewareImpl(services, new AudioSessionProviderImpl()),
+ getContext());
+ publishBinderService(Context.SOUND_TRIGGER_MIDDLEWARE_SERVICE, mService);
+ }
+ }
+
+ /**
+ * A wrapper around an {@link ISoundTriggerModule} implementation, to address the same aspects
+ * mentioned in {@link SoundTriggerModule} above. This class follows the same conventions.
+ */
+ private class ModuleService extends ISoundTriggerModule.Stub implements ISoundTriggerCallback,
+ DeathRecipient {
+ private final ISoundTriggerCallback mCallback;
+ private ISoundTriggerModule mDelegate;
+ private Map<Integer, ModelState> mLoadedModels = new HashMap<>();
+
+ ModuleService(@NonNull ISoundTriggerCallback callback) {
+ mCallback = callback;
+ try {
+ mCallback.asBinder().linkToDeath(this, 0);
+ } catch (RemoteException e) {
+ throw e.rethrowAsRuntimeException();
+ }
+ }
+
+ void attach(@NonNull ISoundTriggerModule delegate) {
+ mDelegate = delegate;
+ }
+
+ @Override
+ public int loadModel(@NonNull SoundModel model) {
+ // Permission check.
+ checkPermissions();
+ // Input validation.
+ ValidationUtil.validateGenericModel(model);
+
+ synchronized (this) {
+ // State validation.
+ if (mDelegate == null) {
+ throw new IllegalStateException("Module has been detached.");
+ }
+
+ // From here on, every exception isn't client's fault.
+ try {
+ int handle = mDelegate.loadModel(model);
+ mLoadedModels.put(handle, new ModelState());
+ return handle;
+ } catch (Exception e) {
+ throw handleException(e);
+ }
+ }
+ }
+
+ @Override
+ public int loadPhraseModel(@NonNull PhraseSoundModel model) {
+ // Permission check.
+ checkPermissions();
+ // Input validation.
+ ValidationUtil.validatePhraseModel(model);
+
+ synchronized (this) {
+ // State validation.
+ if (mDelegate == null) {
+ throw new IllegalStateException("Module has been detached.");
+ }
+
+ // From here on, every exception isn't client's fault.
+ try {
+ int handle = mDelegate.loadPhraseModel(model);
+ mLoadedModels.put(handle, new ModelState());
+ return handle;
+ } catch (Exception e) {
+ throw handleException(e);
+ }
+ }
+ }
+
+ @Override
+ public void unloadModel(int modelHandle) {
+ // Permission check.
+ checkPermissions();
+ // Input validation (always valid).
+
+ synchronized (this) {
+ // State validation.
+ if (mDelegate == null) {
+ throw new IllegalStateException("Module has been detached.");
+ }
+ ModelState modelState = mLoadedModels.get(modelHandle);
+ if (modelState == null) {
+ throw new IllegalStateException("Invalid handle: " + modelHandle);
+ }
+ if (modelState.activityState != ModelState.Activity.LOADED) {
+ throw new IllegalStateException("Model with handle: " + modelHandle
+ + " has invalid state for unloading: " + modelState.activityState);
+ }
+
+ // From here on, every exception isn't client's fault.
+ try {
+ mDelegate.unloadModel(modelHandle);
+ mLoadedModels.remove(modelHandle);
+ } catch (Exception e) {
+ throw handleException(e);
+ }
+ }
+ }
+
+ @Override
+ public void startRecognition(int modelHandle, @NonNull RecognitionConfig config) {
+ // Permission check.
+ checkPermissions();
+ // Input validation.
+ ValidationUtil.validateRecognitionConfig(config);
+
+ synchronized (this) {
+ // State validation.
+ if (mDelegate == null) {
+ throw new IllegalStateException("Module has been detached.");
+ }
+ ModelState modelState = mLoadedModels.get(modelHandle);
+ if (modelState == null) {
+ throw new IllegalStateException("Invalid handle: " + modelHandle);
+ }
+ if (modelState.activityState != ModelState.Activity.LOADED) {
+ throw new IllegalStateException("Model with handle: " + modelHandle
+ + " has invalid state for starting recognition: "
+ + modelState.activityState);
+ }
+
+ // From here on, every exception isn't client's fault.
+ try {
+ mDelegate.startRecognition(modelHandle, config);
+ modelState.activityState = ModelState.Activity.ACTIVE;
+ } catch (Exception e) {
+ throw handleException(e);
+ }
+ }
+ }
+
+ @Override
+ public void stopRecognition(int modelHandle) {
+ // Permission check.
+ checkPermissions();
+ // Input validation (always valid).
+
+ synchronized (this) {
+ // State validation.
+ if (mDelegate == null) {
+ throw new IllegalStateException("Module has been detached.");
+ }
+ ModelState modelState = mLoadedModels.get(modelHandle);
+ if (modelState == null) {
+ throw new IllegalStateException("Invalid handle: " + modelHandle);
+ }
+ // stopRecognition is idempotent - no need to check model state.
+
+ // From here on, every exception isn't client's fault.
+ try {
+ mDelegate.stopRecognition(modelHandle);
+ modelState.activityState = ModelState.Activity.LOADED;
+ } catch (Exception e) {
+ throw handleException(e);
+ }
+ }
+ }
+
+ @Override
+ public void forceRecognitionEvent(int modelHandle) {
+ // Permission check.
+ checkPermissions();
+ // Input validation (always valid).
+
+ synchronized (this) {
+ // State validation.
+ if (mDelegate == null) {
+ throw new IllegalStateException("Module has been detached.");
+ }
+ ModelState modelState = mLoadedModels.get(modelHandle);
+ if (modelState == null) {
+ throw new IllegalStateException("Invalid handle: " + modelHandle);
+ }
+ // forceRecognitionEvent is idempotent - no need to check model state.
+
+ // From here on, every exception isn't client's fault.
+ try {
+ mDelegate.forceRecognitionEvent(modelHandle);
+ } catch (Exception e) {
+ throw handleException(e);
+ }
+ }
+ }
+
+ @Override
+ public void setModelParameter(int modelHandle, int modelParam, int value) {
+ // Permission check.
+ checkPermissions();
+ // Input validation.
+ ValidationUtil.validateModelParameter(modelParam);
+
+ synchronized (this) {
+ // State validation.
+ if (mDelegate == null) {
+ throw new IllegalStateException("Module has been detached.");
+ }
+ ModelState modelState = mLoadedModels.get(modelHandle);
+ if (modelState == null) {
+ throw new IllegalStateException("Invalid handle: " + modelHandle);
+ }
+ modelState.checkSupported(modelParam, value);
+
+ // From here on, every exception isn't client's fault.
+ try {
+ mDelegate.setModelParameter(modelHandle, modelParam, value);
+ } catch (Exception e) {
+ throw handleException(e);
+ }
+ }
+ }
+
+ @Override
+ public int getModelParameter(int modelHandle, int modelParam) {
+ // Permission check.
+ checkPermissions();
+ // Input validation.
+ ValidationUtil.validateModelParameter(modelParam);
+
+ synchronized (this) {
+ // State validation.
+ if (mDelegate == null) {
+ throw new IllegalStateException("Module has been detached.");
+ }
+ ModelState modelState = mLoadedModels.get(modelHandle);
+ if (modelState == null) {
+ throw new IllegalStateException("Invalid handle: " + modelHandle);
+ }
+ modelState.checkSupported(modelParam);
+
+ // From here on, every exception isn't client's fault.
+ try {
+ return mDelegate.getModelParameter(modelHandle, modelParam);
+ } catch (Exception e) {
+ throw handleException(e);
+ }
+ }
+ }
+
+ @Override
+ @Nullable
+ public ModelParameterRange queryModelParameterSupport(int modelHandle, int modelParam) {
+ // Permission check.
+ checkPermissions();
+ // Input validation.
+ ValidationUtil.validateModelParameter(modelParam);
+
+ synchronized (this) {
+ // State validation.
+ if (mDelegate == null) {
+ throw new IllegalStateException("Module has been detached.");
+ }
+ ModelState modelState = mLoadedModels.get(modelHandle);
+ if (modelState == null) {
+ throw new IllegalStateException("Invalid handle: " + modelHandle);
+ }
+
+ // From here on, every exception isn't client's fault.
+ try {
+ ModelParameterRange result = mDelegate.queryModelParameterSupport(modelHandle,
+ modelParam);
+ modelState.updateParameterSupport(modelParam, result);
+ return result;
+ } catch (Exception e) {
+ throw handleException(e);
+ }
+ }
+ }
+
+ @Override
+ public void detach() {
+ // Permission check.
+ checkPermissions();
+ // Input validation (always valid).
+
+ synchronized (this) {
+ // State validation.
+ if (mDelegate == null) {
+ throw new IllegalStateException("Module has already been detached.");
+ }
+ if (!mLoadedModels.isEmpty()) {
+ throw new IllegalStateException("Cannot detach while models are loaded.");
+ }
+
+ // From here on, every exception isn't client's fault.
+ try {
+ detachInternal();
+ } catch (Exception e) {
+ throw handleException(e);
+ }
+ }
+ }
+
+ private void detachInternal() {
+ try {
+ mDelegate.detach();
+ mDelegate = null;
+ mCallback.asBinder().unlinkToDeath(this, 0);
+ } catch (RemoteException e) {
+ throw e.rethrowAsRuntimeException();
+ }
+ }
+
+ ////////////////////////////////////////////////////////////////////////////////////////////
+ // Callbacks
+
+ @Override
+ public void onRecognition(int modelHandle, @NonNull RecognitionEvent event) {
+ synchronized (this) {
+ if (event.status != RecognitionStatus.FORCED) {
+ mLoadedModels.get(modelHandle).activityState = ModelState.Activity.LOADED;
+ }
+ try {
+ mCallback.onRecognition(modelHandle, event);
+ } catch (RemoteException e) {
+ // Dead client will be handled by binderDied() - no need to handle here.
+ // In any case, client callbacks are considered best effort.
+ Log.e(TAG, "Client callback execption.", e);
+ }
+ }
+ }
+
+ @Override
+ public void onPhraseRecognition(int modelHandle, @NonNull PhraseRecognitionEvent event) {
+ synchronized (this) {
+ if (event.common.status != RecognitionStatus.FORCED) {
+ mLoadedModels.get(modelHandle).activityState = ModelState.Activity.LOADED;
+ }
+ try {
+ mCallback.onPhraseRecognition(modelHandle, event);
+ } catch (RemoteException e) {
+ // Dead client will be handled by binderDied() - no need to handle here.
+ // In any case, client callbacks are considered best effort.
+ Log.e(TAG, "Client callback execption.", e);
+ }
+ }
+ }
+
+ @Override
+ public void onRecognitionAvailabilityChange(boolean available) throws RemoteException {
+ synchronized (this) {
+ try {
+ mCallback.onRecognitionAvailabilityChange(available);
+ } catch (RemoteException e) {
+ // Dead client will be handled by binderDied() - no need to handle here.
+ // In any case, client callbacks are considered best effort.
+ Log.e(TAG, "Client callback execption.", e);
+ }
+ }
+ }
+
+ @Override
+ public void binderDied() {
+ // This is called whenever our client process dies.
+ synchronized (this) {
+ try {
+ // Gracefully stop all active recognitions and unload the models.
+ for (Map.Entry<Integer, ModelState> entry : mLoadedModels.entrySet()) {
+ if (entry.getValue().activityState == ModelState.Activity.ACTIVE) {
+ mDelegate.stopRecognition(entry.getKey());
+ }
+ mDelegate.unloadModel(entry.getKey());
+ }
+ // Detach.
+ detachInternal();
+ } catch (Exception e) {
+ throw handleException(e);
+ }
+ }
+ }
+ }
+}
diff --git a/services/core/java/com/android/server/soundtrigger_middleware/SoundTriggerModule.java b/services/core/java/com/android/server/soundtrigger_middleware/SoundTriggerModule.java
new file mode 100644
index 000000000000..81789e1362c0
--- /dev/null
+++ b/services/core/java/com/android/server/soundtrigger_middleware/SoundTriggerModule.java
@@ -0,0 +1,545 @@
+/*
+ * Copyright (C) 2019 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.soundtrigger_middleware;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.hardware.soundtrigger.V2_1.ISoundTriggerHwCallback;
+import android.hardware.soundtrigger.V2_2.ISoundTriggerHw;
+import android.media.soundtrigger_middleware.ISoundTriggerCallback;
+import android.media.soundtrigger_middleware.ISoundTriggerModule;
+import android.media.soundtrigger_middleware.ModelParameterRange;
+import android.media.soundtrigger_middleware.PhraseSoundModel;
+import android.media.soundtrigger_middleware.RecognitionConfig;
+import android.media.soundtrigger_middleware.SoundModel;
+import android.media.soundtrigger_middleware.SoundModelType;
+import android.media.soundtrigger_middleware.SoundTriggerModuleProperties;
+import android.media.soundtrigger_middleware.Status;
+import android.os.IBinder;
+import android.os.RemoteException;
+import android.util.Log;
+
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.Map;
+import java.util.Set;
+
+/**
+ * This is an implementation of a single module of the ISoundTriggerMiddlewareService interface,
+ * exposing itself through the {@link ISoundTriggerModule} interface, possibly to multiple separate
+ * clients.
+ * <p>
+ * Typical usage is to query the module capabilities using {@link #getProperties()} and then to use
+ * the module through an {@link ISoundTriggerModule} instance, obtained via {@link
+ * #attach(ISoundTriggerCallback)}. Every such interface is its own session and state is not shared
+ * between sessions (i.e. cannot use a handle obtained from one session through another).
+ * <p>
+ * <b>Important conventions:</b>
+ * <ul>
+ * <li>Correct usage is assumed. This implementation does not attempt to gracefully handle
+ * invalid usage, and such usage will result in undefined behavior. If this service is to be
+ * offered to an untrusted client, it must be wrapped with input and state validation.
+ * <li>The underlying driver is assumed to be correct. This implementation does not attempt to
+ * gracefully handle driver malfunction and such behavior will result in undefined behavior. If this
+ * service is to used with an untrusted driver, the driver must be wrapped with validation / error
+ * recovery code.
+ * <li>RemoteExceptions thrown by the driver are treated as RuntimeExceptions - they are not
+ * considered recoverable faults and should not occur in a properly functioning system.
+ * <li>There is no binder instance associated with this implementation. Do not call asBinder().
+ * <li>The implementation may throw a {@link RecoverableException} to indicate non-fatal,
+ * recoverable faults. The error code would one of the
+ * {@link android.media.soundtrigger_middleware.Status} constants. Any other exception
+ * thrown should be regarded as a bug in the implementation or one of its dependencies
+ * (assuming correct usage).
+ * <li>The implementation is designed for testability by featuring dependency injection (the
+ * underlying HAL driver instances are passed to the ctor) and by minimizing dependencies
+ * on Android runtime.
+ * <li>The implementation is thread-safe. This is achieved by a simplistic model, where all entry-
+ * points (both client API and driver callbacks) obtain a lock on the SoundTriggerModule instance
+ * for their entire scope. Any other method can be assumed to be running with the lock already
+ * obtained, so no further locking should be done. While this is not necessarily the most efficient
+ * synchronization strategy, it is very easy to reason about and this code is likely not on any
+ * performance-critical
+ * path.
+ * </ul>
+ *
+ * @hide
+ */
+class SoundTriggerModule {
+ static private final String TAG = "SoundTriggerModule";
+ @NonNull private final ISoundTriggerHw2 mHalService;
+ @NonNull private final SoundTriggerMiddlewareImpl.AudioSessionProvider mAudioSessionProvider;
+ private final Set<Session> mActiveSessions = new HashSet<>();
+ private int mNumLoadedModels = 0;
+ private SoundTriggerModuleProperties mProperties = null;
+ private boolean mRecognitionAvailable;
+
+ /**
+ * Ctor.
+ *
+ * @param halService The underlying HAL driver.
+ */
+ SoundTriggerModule(@NonNull android.hardware.soundtrigger.V2_0.ISoundTriggerHw halService,
+ @NonNull SoundTriggerMiddlewareImpl.AudioSessionProvider audioSessionProvider) {
+ assert halService != null;
+ mHalService = new SoundTriggerHw2Compat(halService);
+ mAudioSessionProvider = audioSessionProvider;
+ mProperties = ConversionUtil.hidl2aidlProperties(mHalService.getProperties());
+
+ // We conservatively assume that external capture is active until explicitly told otherwise.
+ mRecognitionAvailable = mProperties.concurrentCapture;
+ }
+
+ /**
+ * Establish a client session with this module.
+ *
+ * This module may be shared by multiple clients, each will get its own session. While resources
+ * are shared between the clients, each session has its own state and data should not be shared
+ * across sessions.
+ *
+ * @param callback The client callback, which will be used for all messages. This is a oneway
+ * callback, so will never block, throw an unchecked exception or return a
+ * value.
+ * @return The interface through which this module can be controlled.
+ */
+ synchronized @NonNull
+ Session attach(@NonNull ISoundTriggerCallback callback) {
+ Log.d(TAG, "attach()");
+ Session session = new Session(callback);
+ mActiveSessions.add(session);
+ return session;
+ }
+
+ /**
+ * Query the module's properties.
+ *
+ * @return The properties structure.
+ */
+ synchronized @NonNull
+ SoundTriggerModuleProperties getProperties() {
+ return mProperties;
+ }
+
+ /**
+ * Notify the module that external capture has started / finished, using the same input device
+ * used for recognition.
+ * If the underlying driver does not support recognition while capturing, capture will be
+ * aborted, and the recognition callback will receive and abort event. In addition, all active
+ * clients will be notified of the change in state.
+ *
+ * @param active true iff external capture is active.
+ */
+ synchronized void setExternalCaptureState(boolean active) {
+ Log.d(TAG, String.format("setExternalCaptureState(active=%b)", active));
+ if (mProperties.concurrentCapture) {
+ // If we support concurrent capture, we don't care about any of this.
+ return;
+ }
+ mRecognitionAvailable = !active;
+ if (!mRecognitionAvailable) {
+ // Our module does not support recognition while a capture is active -
+ // need to abort all active recognitions.
+ for (Session session : mActiveSessions) {
+ session.abortActiveRecognitions();
+ }
+ }
+ for (Session session : mActiveSessions) {
+ session.notifyRecognitionAvailability();
+ }
+ }
+
+ /**
+ * Remove session from the list of active sessions.
+ *
+ * @param session The session to remove.
+ */
+ private void removeSession(@NonNull Session session) {
+ mActiveSessions.remove(session);
+ }
+
+ /** State of a single sound model. */
+ private enum ModelState {
+ /** Initial state, until load() is called. */
+ INIT,
+ /** Model is loaded, but recognition is not active. */
+ LOADED,
+ /** Model is loaded and recognition is active. */
+ ACTIVE
+ }
+
+ /**
+ * A single client session with this module.
+ *
+ * This is the main interface used to interact with this module.
+ */
+ private class Session implements ISoundTriggerModule {
+ private ISoundTriggerCallback mCallback;
+ private Map<Integer, Model> mLoadedModels = new HashMap<>();
+
+ /**
+ * Ctor.
+ *
+ * @param callback The client callback interface.
+ */
+ private Session(@NonNull ISoundTriggerCallback callback) {
+ mCallback = callback;
+ notifyRecognitionAvailability();
+ }
+
+ @Override
+ public void detach() {
+ Log.d(TAG, "detach()");
+ synchronized (SoundTriggerModule.this) {
+ removeSession(this);
+ }
+ }
+
+ @Override
+ public int loadModel(@NonNull SoundModel model) {
+ Log.d(TAG, String.format("loadModel(model=%s)", model));
+ synchronized (SoundTriggerModule.this) {
+ if (mNumLoadedModels == mProperties.maxSoundModels) {
+ throw new RecoverableException(Status.RESOURCE_CONTENTION,
+ "Maximum number of models loaded.");
+ }
+ Model loadedModel = new Model();
+ int result = loadedModel.load(model);
+ ++mNumLoadedModels;
+ return result;
+ }
+ }
+
+ @Override
+ public int loadPhraseModel(@NonNull PhraseSoundModel model) {
+ Log.d(TAG, String.format("loadPhraseModel(model=%s)", model));
+ synchronized (SoundTriggerModule.this) {
+ if (mNumLoadedModels == mProperties.maxSoundModels) {
+ throw new RecoverableException(Status.RESOURCE_CONTENTION,
+ "Maximum number of models loaded.");
+ }
+ Model loadedModel = new Model();
+ int result = loadedModel.load(model);
+ ++mNumLoadedModels;
+ Log.d(TAG, String.format("loadPhraseModel()->%d", result));
+ return result;
+ }
+ }
+
+ @Override
+ public void unloadModel(int modelHandle) {
+ Log.d(TAG, String.format("unloadModel(handle=%d)", modelHandle));
+ synchronized (SoundTriggerModule.this) {
+ mLoadedModels.get(modelHandle).unload();
+ --mNumLoadedModels;
+ }
+ }
+
+ @Override
+ public void startRecognition(int modelHandle, @NonNull RecognitionConfig config) {
+ Log.d(TAG,
+ String.format("startRecognition(handle=%d, config=%s)", modelHandle, config));
+ synchronized (SoundTriggerModule.this) {
+ mLoadedModels.get(modelHandle).startRecognition(config);
+ }
+ }
+
+ @Override
+ public void stopRecognition(int modelHandle) {
+ Log.d(TAG, String.format("stopRecognition(handle=%d)", modelHandle));
+ synchronized (SoundTriggerModule.this) {
+ mLoadedModels.get(modelHandle).stopRecognition();
+ }
+ }
+
+ @Override
+ public void forceRecognitionEvent(int modelHandle) {
+ Log.d(TAG, String.format("forceRecognitionEvent(handle=%d)", modelHandle));
+ synchronized (SoundTriggerModule.this) {
+ mLoadedModels.get(modelHandle).forceRecognitionEvent();
+ }
+ }
+
+ @Override
+ public void setModelParameter(int modelHandle, int modelParam, int value)
+ throws RemoteException {
+ Log.d(TAG,
+ String.format("setModelParameter(handle=%d, param=%d, value=%d)", modelHandle,
+ modelParam, value));
+ synchronized (SoundTriggerModule.this) {
+ mLoadedModels.get(modelHandle).setParameter(modelParam, value);
+ }
+ }
+
+ @Override
+ public int getModelParameter(int modelHandle, int modelParam) throws RemoteException {
+ Log.d(TAG, String.format("getModelParameter(handle=%d, param=%d)", modelHandle,
+ modelParam));
+ synchronized (SoundTriggerModule.this) {
+ return mLoadedModels.get(modelHandle).getParameter(modelParam);
+ }
+ }
+
+ @Override
+ @Nullable
+ public ModelParameterRange queryModelParameterSupport(int modelHandle, int modelParam) {
+ Log.d(TAG, String.format("queryModelParameterSupport(handle=%d, param=%d)", modelHandle,
+ modelParam));
+ synchronized (SoundTriggerModule.this) {
+ return mLoadedModels.get(modelHandle).queryModelParameterSupport(modelParam);
+ }
+ }
+
+ /**
+ * Abort all currently active recognitions.
+ */
+ private void abortActiveRecognitions() {
+ for (Model model : mLoadedModels.values()) {
+ model.abortActiveRecognition();
+ }
+ }
+
+ private void notifyRecognitionAvailability() {
+ try {
+ mCallback.onRecognitionAvailabilityChange(mRecognitionAvailable);
+ } catch (RemoteException e) {
+ // Dead client will be handled by binderDied() - no need to handle here.
+ // In any case, client callbacks are considered best effort.
+ Log.e(TAG, "Client callback execption.", e);
+ }
+ }
+
+ @Override
+ public @NonNull
+ IBinder asBinder() {
+ throw new UnsupportedOperationException(
+ "This implementation is not intended to be used directly with Binder.");
+ }
+
+ /**
+ * A single sound model in the system.
+ *
+ * All model-based operations are delegated to this class and implemented here.
+ */
+ private class Model implements ISoundTriggerHw2.Callback {
+ public int mHandle;
+ private ModelState mState = ModelState.INIT;
+ private int mModelType = SoundModelType.UNKNOWN;
+ private SoundTriggerMiddlewareImpl.AudioSessionProvider.AudioSession mSession;
+
+ private @NonNull
+ ModelState getState() {
+ return mState;
+ }
+
+ private void setState(@NonNull ModelState state) {
+ mState = state;
+ SoundTriggerModule.this.notifyAll();
+ }
+
+ private void waitStateChange() throws InterruptedException {
+ SoundTriggerModule.this.wait();
+ }
+
+ private int load(@NonNull SoundModel model) {
+ mModelType = model.type;
+ ISoundTriggerHw.SoundModel hidlModel = ConversionUtil.aidl2hidlSoundModel(model);
+
+ mSession = mAudioSessionProvider.acquireSession();
+ try {
+ mHandle = mHalService.loadSoundModel(hidlModel, this, 0);
+ } catch (Exception e) {
+ mAudioSessionProvider.releaseSession(mSession.mSessionHandle);
+ throw e;
+ }
+
+ setState(ModelState.LOADED);
+ mLoadedModels.put(mHandle, this);
+ return mHandle;
+ }
+
+ private int load(@NonNull PhraseSoundModel model) {
+ mModelType = model.common.type;
+ ISoundTriggerHw.PhraseSoundModel hidlModel =
+ ConversionUtil.aidl2hidlPhraseSoundModel(model);
+
+ mSession = mAudioSessionProvider.acquireSession();
+ try {
+ mHandle = mHalService.loadPhraseSoundModel(hidlModel, this, 0);
+ } catch (Exception e) {
+ mAudioSessionProvider.releaseSession(mSession.mSessionHandle);
+ throw e;
+ }
+
+ setState(ModelState.LOADED);
+ mLoadedModels.put(mHandle, this);
+ return mHandle;
+ }
+
+ private void unload() {
+ mAudioSessionProvider.releaseSession(mSession.mSessionHandle);
+ mHalService.unloadSoundModel(mHandle);
+ mLoadedModels.remove(mHandle);
+ }
+
+ private void startRecognition(@NonNull RecognitionConfig config) {
+ if (!mRecognitionAvailable) {
+ // Recognition is unavailable - send an abort event immediately.
+ notifyAbort();
+ return;
+ }
+ android.hardware.soundtrigger.V2_1.ISoundTriggerHw.RecognitionConfig hidlConfig =
+ ConversionUtil.aidl2hidlRecognitionConfig(config);
+ hidlConfig.header.captureDevice = mSession.mDeviceHandle;
+ hidlConfig.header.captureHandle = mSession.mIoHandle;
+ mHalService.startRecognition(mHandle, hidlConfig, this, 0);
+ setState(ModelState.ACTIVE);
+ }
+
+ private void stopRecognition() {
+ if (getState() == ModelState.LOADED) {
+ // This call is idempotent in order to avoid races.
+ return;
+ }
+ mHalService.stopRecognition(mHandle);
+ setState(ModelState.LOADED);
+ }
+
+ /** Request a forced recognition event. Will do nothing if recognition is inactive. */
+ private void forceRecognitionEvent() {
+ if (getState() != ModelState.ACTIVE) {
+ // This call is idempotent in order to avoid races.
+ return;
+ }
+ mHalService.getModelState(mHandle);
+ }
+
+
+ private void setParameter(int modelParam, int value) {
+ mHalService.setModelParameter(mHandle,
+ ConversionUtil.aidl2hidlModelParameter(modelParam), value);
+ }
+
+ private int getParameter(int modelParam) {
+ return mHalService.getModelParameter(mHandle,
+ ConversionUtil.aidl2hidlModelParameter(modelParam));
+ }
+
+ @Nullable
+ private ModelParameterRange queryModelParameterSupport(int modelParam) {
+ return ConversionUtil.hidl2aidlModelParameterRange(
+ mHalService.queryParameter(mHandle,
+ ConversionUtil.aidl2hidlModelParameter(modelParam)));
+ }
+
+ /** Abort the recognition, if active. */
+ private void abortActiveRecognition() {
+ // If we're inactive, do nothing.
+ if (getState() != ModelState.ACTIVE) {
+ return;
+ }
+ // Stop recognition.
+ stopRecognition();
+
+ // Notify the client that recognition has been aborted.
+ notifyAbort();
+ }
+
+ /** Notify the client that recognition has been aborted. */
+ private void notifyAbort() {
+ try {
+ switch (mModelType) {
+ case SoundModelType.GENERIC: {
+ android.media.soundtrigger_middleware.RecognitionEvent event =
+ new android.media.soundtrigger_middleware.RecognitionEvent();
+ event.status =
+ android.media.soundtrigger_middleware.RecognitionStatus.ABORTED;
+ mCallback.onRecognition(mHandle, event);
+ }
+ break;
+
+ case SoundModelType.KEYPHRASE: {
+ android.media.soundtrigger_middleware.PhraseRecognitionEvent event =
+ new android.media.soundtrigger_middleware.PhraseRecognitionEvent();
+ event.common =
+ new android.media.soundtrigger_middleware.RecognitionEvent();
+ event.common.status =
+ android.media.soundtrigger_middleware.RecognitionStatus.ABORTED;
+ mCallback.onPhraseRecognition(mHandle, event);
+ }
+ break;
+
+ default:
+ Log.e(TAG, "Unknown model type: " + mModelType);
+
+ }
+ } catch (RemoteException e) {
+ // Dead client will be handled by binderDied() - no need to handle here.
+ // In any case, client callbacks are considered best effort.
+ Log.e(TAG, "Client callback execption.", e);
+ }
+ }
+
+ @Override
+ public void recognitionCallback(
+ @NonNull ISoundTriggerHwCallback.RecognitionEvent recognitionEvent,
+ int cookie) {
+ Log.d(TAG, String.format("recognitionCallback_2_1(event=%s, cookie=%d)",
+ recognitionEvent, cookie));
+ synchronized (SoundTriggerModule.this) {
+ android.media.soundtrigger_middleware.RecognitionEvent aidlEvent =
+ ConversionUtil.hidl2aidlRecognitionEvent(recognitionEvent);
+ aidlEvent.captureSession = mSession.mSessionHandle;
+ try {
+ mCallback.onRecognition(mHandle, aidlEvent);
+ } catch (RemoteException e) {
+ // Dead client will be handled by binderDied() - no need to handle here.
+ // In any case, client callbacks are considered best effort.
+ Log.e(TAG, "Client callback execption.", e);
+ }
+ if (aidlEvent.status
+ != android.media.soundtrigger_middleware.RecognitionStatus.FORCED) {
+ setState(ModelState.LOADED);
+ }
+ }
+ }
+
+ @Override
+ public void phraseRecognitionCallback(
+ @NonNull ISoundTriggerHwCallback.PhraseRecognitionEvent phraseRecognitionEvent,
+ int cookie) {
+ Log.d(TAG, String.format("phraseRecognitionCallback_2_1(event=%s, cookie=%d)",
+ phraseRecognitionEvent, cookie));
+ synchronized (SoundTriggerModule.this) {
+ android.media.soundtrigger_middleware.PhraseRecognitionEvent aidlEvent =
+ ConversionUtil.hidl2aidlPhraseRecognitionEvent(phraseRecognitionEvent);
+ aidlEvent.common.captureSession = mSession.mSessionHandle;
+ try {
+ mCallback.onPhraseRecognition(mHandle, aidlEvent);
+ } catch (RemoteException e) {
+ // Dead client will be handled by binderDied() - no need to handle here.
+ // In any case, client callbacks are considered best effort.
+ Log.e(TAG, "Client callback execption.", e);
+ }
+ if (aidlEvent.common.status
+ != android.media.soundtrigger_middleware.RecognitionStatus.FORCED) {
+ setState(ModelState.LOADED);
+ }
+ }
+ }
+ }
+ }
+}
diff --git a/services/core/java/com/android/server/soundtrigger_middleware/TEST_MAPPING b/services/core/java/com/android/server/soundtrigger_middleware/TEST_MAPPING
new file mode 100644
index 000000000000..9ed894bc1ca9
--- /dev/null
+++ b/services/core/java/com/android/server/soundtrigger_middleware/TEST_MAPPING
@@ -0,0 +1,12 @@
+{
+ "presubmit": [
+ {
+ "name": "FrameworksServicesTests",
+ "options": [
+ {
+ "include-filter": "com.android.server.soundtrigger_middleware"
+ }
+ ]
+ }
+ ]
+}
diff --git a/services/core/java/com/android/server/soundtrigger_middleware/UuidUtil.java b/services/core/java/com/android/server/soundtrigger_middleware/UuidUtil.java
new file mode 100644
index 000000000000..80f69d08c089
--- /dev/null
+++ b/services/core/java/com/android/server/soundtrigger_middleware/UuidUtil.java
@@ -0,0 +1,44 @@
+/*
+ * Copyright (C) 2019 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.soundtrigger_middleware;
+
+import java.util.regex.Pattern;
+
+/**
+ * Utilities for representing UUIDs as strings.
+ *
+ * @hide
+ */
+public class UuidUtil {
+ /**
+ * Regex pattern that can be used to validate / extract the various fields of a string-formatted
+ * UUID.
+ */
+ static final Pattern PATTERN = Pattern.compile("^([a-fA-F0-9]{8})-" +
+ "([a-fA-F0-9]{4})-" +
+ "([a-fA-F0-9]{4})-" +
+ "([a-fA-F0-9]{4})-" +
+ "([a-fA-F0-9]{2})" +
+ "([a-fA-F0-9]{2})" +
+ "([a-fA-F0-9]{2})" +
+ "([a-fA-F0-9]{2})" +
+ "([a-fA-F0-9]{2})" +
+ "([a-fA-F0-9]{2})$");
+
+ /** Printf-style pattern for formatting the various fields of a UUID as a string. */
+ static final String FORMAT = "%08x-%04x-%04x-%04x-%02x%02x%02x%02x%02x%02x";
+}
diff --git a/services/core/java/com/android/server/soundtrigger_middleware/ValidationUtil.java b/services/core/java/com/android/server/soundtrigger_middleware/ValidationUtil.java
new file mode 100644
index 000000000000..4898e6b59ab2
--- /dev/null
+++ b/services/core/java/com/android/server/soundtrigger_middleware/ValidationUtil.java
@@ -0,0 +1,117 @@
+/*
+ * Copyright (C) 2019 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.soundtrigger_middleware;
+
+import android.annotation.Nullable;
+import android.media.soundtrigger_middleware.ConfidenceLevel;
+import android.media.soundtrigger_middleware.ModelParameter;
+import android.media.soundtrigger_middleware.Phrase;
+import android.media.soundtrigger_middleware.PhraseRecognitionExtra;
+import android.media.soundtrigger_middleware.PhraseSoundModel;
+import android.media.soundtrigger_middleware.RecognitionConfig;
+import android.media.soundtrigger_middleware.RecognitionMode;
+import android.media.soundtrigger_middleware.SoundModel;
+import android.media.soundtrigger_middleware.SoundModelType;
+
+import com.android.internal.util.Preconditions;
+
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+/**
+ * Utilities for asserting the validity of various data types used by this module.
+ * Each of the methods below would throw an {@link IllegalArgumentException} if its input is
+ * invalid. The input's validity is determined irrespective of any context. In cases where the valid
+ * value space is further limited by state, it is the caller's responsibility to assert.
+ *
+ * @hide
+ */
+public class ValidationUtil {
+ static void validateUuid(@Nullable String uuid) {
+ Preconditions.checkNotNull(uuid);
+ Matcher matcher = UuidUtil.PATTERN.matcher(uuid);
+ if (!matcher.matches()) {
+ throw new IllegalArgumentException(
+ "Illegal format for UUID: " + uuid);
+ }
+ }
+
+ static void validateGenericModel(@Nullable SoundModel model) {
+ validateModel(model, SoundModelType.GENERIC);
+ }
+
+ static void validateModel(@Nullable SoundModel model, int expectedType) {
+ Preconditions.checkNotNull(model);
+ if (model.type != expectedType) {
+ throw new IllegalArgumentException("Invalid type");
+ }
+ validateUuid(model.uuid);
+ validateUuid(model.vendorUuid);
+ Preconditions.checkNotNull(model.data);
+ }
+
+ static void validatePhraseModel(@Nullable PhraseSoundModel model) {
+ Preconditions.checkNotNull(model);
+ validateModel(model.common, SoundModelType.KEYPHRASE);
+ Preconditions.checkNotNull(model.phrases);
+ for (Phrase phrase : model.phrases) {
+ Preconditions.checkNotNull(phrase);
+ if ((phrase.recognitionModes & ~(RecognitionMode.VOICE_TRIGGER
+ | RecognitionMode.USER_IDENTIFICATION | RecognitionMode.USER_AUTHENTICATION
+ | RecognitionMode.GENERIC_TRIGGER)) != 0) {
+ throw new IllegalArgumentException("Invalid recognitionModes");
+ }
+ Preconditions.checkNotNull(phrase.users);
+ Preconditions.checkNotNull(phrase.locale);
+ Preconditions.checkNotNull(phrase.text);
+ }
+ }
+
+ static void validateRecognitionConfig(@Nullable RecognitionConfig config) {
+ Preconditions.checkNotNull(config);
+ Preconditions.checkNotNull(config.phraseRecognitionExtras);
+ for (PhraseRecognitionExtra extra : config.phraseRecognitionExtras) {
+ Preconditions.checkNotNull(extra);
+ if ((extra.recognitionModes & ~(RecognitionMode.VOICE_TRIGGER
+ | RecognitionMode.USER_IDENTIFICATION | RecognitionMode.USER_AUTHENTICATION
+ | RecognitionMode.GENERIC_TRIGGER)) != 0) {
+ throw new IllegalArgumentException("Invalid recognitionModes");
+ }
+ if (extra.confidenceLevel < 0 || extra.confidenceLevel > 100) {
+ throw new IllegalArgumentException("Invalid confidenceLevel");
+ }
+ Preconditions.checkNotNull(extra.levels);
+ for (ConfidenceLevel level : extra.levels) {
+ Preconditions.checkNotNull(level);
+ if (level.levelPercent < 0 || level.levelPercent > 100) {
+ throw new IllegalArgumentException("Invalid confidenceLevel");
+ }
+ }
+ }
+ Preconditions.checkNotNull(config.data);
+ }
+
+ static void validateModelParameter(int modelParam) {
+ switch (modelParam) {
+ case ModelParameter.THRESHOLD_FACTOR:
+ return;
+
+ default:
+ throw new IllegalArgumentException("Invalid model parameter");
+ }
+ }
+}
diff --git a/services/core/jni/Android.bp b/services/core/jni/Android.bp
index fd8094cc43dd..a34b7fdb911c 100644
--- a/services/core/jni/Android.bp
+++ b/services/core/jni/Android.bp
@@ -35,6 +35,7 @@ cc_library_static {
"com_android_server_power_PowerManagerService.cpp",
"com_android_server_security_VerityUtils.cpp",
"com_android_server_SerialService.cpp",
+ "com_android_server_soundtrigger_middleware_AudioSessionProviderImpl.cpp",
"com_android_server_storage_AppFuseBridge.cpp",
"com_android_server_SystemServer.cpp",
"com_android_server_TestNetworkService.cpp",
diff --git a/services/core/jni/com_android_server_soundtrigger_middleware_AudioSessionProviderImpl.cpp b/services/core/jni/com_android_server_soundtrigger_middleware_AudioSessionProviderImpl.cpp
new file mode 100644
index 000000000000..774534f23b8c
--- /dev/null
+++ b/services/core/jni/com_android_server_soundtrigger_middleware_AudioSessionProviderImpl.cpp
@@ -0,0 +1,91 @@
+/*
+ * Copyright (C) 2019 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.
+ */
+
+#include <sstream>
+
+#include "core_jni_helpers.h"
+#include <media/AudioSystem.h>
+
+namespace android {
+
+namespace {
+
+#define PACKAGE "com/android/server/soundtrigger_middleware"
+#define CLASSNAME PACKAGE "/AudioSessionProviderImpl"
+#define SESSION_CLASSNAME PACKAGE "/SoundTriggerMiddlewareImpl$AudioSessionProvider$AudioSession"
+
+jobject acquireAudioSession(
+ JNIEnv* env,
+ jobject clazz) {
+
+ audio_session_t session;
+ audio_io_handle_t ioHandle;
+ audio_devices_t device;
+
+ status_t status = AudioSystem::acquireSoundTriggerSession(&session,
+ &ioHandle,
+ &device);
+ if (status != 0) {
+ std::ostringstream message;
+ message
+ << "AudioSystem::acquireSoundTriggerSession returned an error code: "
+ << status;
+ env->ThrowNew(FindClassOrDie(env, "java/lang/RuntimeException"),
+ message.str().c_str());
+ return nullptr;
+ }
+
+ jclass cls = FindClassOrDie(env, SESSION_CLASSNAME);
+ jmethodID ctor = GetMethodIDOrDie(env, cls, "<init>", "(III)V");
+ return env->NewObject(cls,
+ ctor,
+ static_cast<int>(session),
+ static_cast<int>(ioHandle),
+ static_cast<int>(device));
+}
+
+void releaseAudioSession(JNIEnv* env, jobject clazz, jint handle) {
+ status_t status =
+ AudioSystem::releaseSoundTriggerSession(static_cast<audio_session_t>(handle));
+
+ if (status != 0) {
+ std::ostringstream message;
+ message
+ << "AudioSystem::releaseAudioSystemSession returned an error code: "
+ << status;
+ env->ThrowNew(FindClassOrDie(env, "java/lang/RuntimeException"),
+ message.str().c_str());
+ }
+}
+
+const JNINativeMethod g_methods[] = {
+ {"acquireSession", "()L" SESSION_CLASSNAME ";",
+ reinterpret_cast<void*>(acquireAudioSession)},
+ {"releaseSession", "(I)V",
+ reinterpret_cast<void*>(releaseAudioSession)},
+};
+
+} // namespace
+
+int register_com_android_server_soundtrigger_middleware_AudioSessionProviderImpl(
+ JNIEnv* env) {
+ return RegisterMethodsOrDie(env,
+ CLASSNAME,
+ g_methods,
+ NELEM(g_methods));
+}
+
+} // namespace android
diff --git a/services/core/jni/onload.cpp b/services/core/jni/onload.cpp
index 692c9d25baa9..165edf15ca23 100644
--- a/services/core/jni/onload.cpp
+++ b/services/core/jni/onload.cpp
@@ -56,6 +56,8 @@ int register_android_server_net_NetworkStatsService(JNIEnv* env);
int register_android_server_security_VerityUtils(JNIEnv* env);
int register_android_server_am_AppCompactor(JNIEnv* env);
int register_android_server_am_LowMemDetector(JNIEnv* env);
+int register_com_android_server_soundtrigger_middleware_AudioSessionProviderImpl(
+ JNIEnv* env);
};
using namespace android;
@@ -105,5 +107,7 @@ extern "C" jint JNI_OnLoad(JavaVM* vm, void* /* reserved */)
register_android_server_security_VerityUtils(env);
register_android_server_am_AppCompactor(env);
register_android_server_am_LowMemDetector(env);
+ register_com_android_server_soundtrigger_middleware_AudioSessionProviderImpl(
+ env);
return JNI_VERSION_1_4;
}
diff --git a/services/java/com/android/server/SystemServer.java b/services/java/com/android/server/SystemServer.java
index f3ac7d6d3cc9..7d6b0c99c5b5 100644
--- a/services/java/com/android/server/SystemServer.java
+++ b/services/java/com/android/server/SystemServer.java
@@ -148,6 +148,7 @@ import com.android.server.security.KeyAttestationApplicationIdProviderService;
import com.android.server.security.KeyChainSystemService;
import com.android.server.signedconfig.SignedConfigService;
import com.android.server.soundtrigger.SoundTriggerService;
+import com.android.server.soundtrigger_middleware.SoundTriggerMiddlewareService;
import com.android.server.statusbar.StatusBarManagerService;
import com.android.server.storage.DeviceStorageMonitorService;
import com.android.server.telecom.TelecomLoaderService;
@@ -1555,6 +1556,10 @@ public final class SystemServer {
}
t.traceEnd();
+ t.traceBegin("StartSoundTriggerMiddlewareService");
+ mSystemServiceManager.startService(SoundTriggerMiddlewareService.Lifecycle.class);
+ t.traceEnd();
+
if (mPackageManager.hasSystemFeature(PackageManager.FEATURE_BROADCAST_RADIO)) {
t.traceBegin("StartBroadcastRadioService");
mSystemServiceManager.startService(BroadcastRadioService.class);
diff --git a/services/tests/servicestests/src/com/android/server/soundtrigger_middleware/ConversionUtilTest.java b/services/tests/servicestests/src/com/android/server/soundtrigger_middleware/ConversionUtilTest.java
new file mode 100644
index 000000000000..5a2ce4540b82
--- /dev/null
+++ b/services/tests/servicestests/src/com/android/server/soundtrigger_middleware/ConversionUtilTest.java
@@ -0,0 +1,47 @@
+/*
+ * Copyright (C) 2019 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.soundtrigger_middleware;
+
+import static org.junit.Assert.assertEquals;
+
+import android.hardware.audio.common.V2_0.Uuid;
+
+import androidx.test.runner.AndroidJUnit4;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+@RunWith(AndroidJUnit4.class)
+public class ConversionUtilTest {
+ private static final String TAG = "ConversionUtilTest";
+
+ @Test
+ public void testUuidRoundTrip() {
+ Uuid hidl = new Uuid();
+ hidl.timeLow = 0xFEDCBA98;
+ hidl.timeMid = (short) 0xEDCB;
+ hidl.versionAndTimeHigh = (short) 0xDCBA;
+ hidl.variantAndClockSeqHigh = (short) 0xCBA9;
+ hidl.node = new byte[] { 0x11, 0x12, 0x13, 0x14, 0x15, 0x16 };
+
+ String aidl = ConversionUtil.hidl2aidlUuid(hidl);
+ assertEquals("fedcba98-edcb-dcba-cba9-111213141516", aidl);
+
+ Uuid reconstructed = ConversionUtil.aidl2hidlUuid(aidl);
+ assertEquals(hidl, reconstructed);
+ }
+}
diff --git a/services/tests/servicestests/src/com/android/server/soundtrigger_middleware/SoundTriggerMiddlewareImplTest.java b/services/tests/servicestests/src/com/android/server/soundtrigger_middleware/SoundTriggerMiddlewareImplTest.java
new file mode 100644
index 000000000000..82f32f88d3a2
--- /dev/null
+++ b/services/tests/servicestests/src/com/android/server/soundtrigger_middleware/SoundTriggerMiddlewareImplTest.java
@@ -0,0 +1,1306 @@
+/*
+ * Copyright (C) 2019 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.soundtrigger_middleware;
+
+import static org.junit.Assert.assertArrayEquals;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertNull;
+import static org.junit.Assert.assertTrue;
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.anyBoolean;
+import static org.mockito.ArgumentMatchers.anyInt;
+import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.clearInvocations;
+import static org.mockito.Mockito.doAnswer;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+import android.hardware.audio.common.V2_0.AudioConfig;
+import android.hardware.audio.common.V2_0.Uuid;
+import android.hardware.soundtrigger.V2_3.OptionalModelParameterRange;
+import android.media.audio.common.AudioChannelMask;
+import android.media.audio.common.AudioFormat;
+import android.media.soundtrigger_middleware.ConfidenceLevel;
+import android.media.soundtrigger_middleware.ISoundTriggerCallback;
+import android.media.soundtrigger_middleware.ISoundTriggerModule;
+import android.media.soundtrigger_middleware.ModelParameter;
+import android.media.soundtrigger_middleware.ModelParameterRange;
+import android.media.soundtrigger_middleware.Phrase;
+import android.media.soundtrigger_middleware.PhraseRecognitionEvent;
+import android.media.soundtrigger_middleware.PhraseRecognitionExtra;
+import android.media.soundtrigger_middleware.PhraseSoundModel;
+import android.media.soundtrigger_middleware.RecognitionConfig;
+import android.media.soundtrigger_middleware.RecognitionEvent;
+import android.media.soundtrigger_middleware.RecognitionMode;
+import android.media.soundtrigger_middleware.RecognitionStatus;
+import android.media.soundtrigger_middleware.SoundModel;
+import android.media.soundtrigger_middleware.SoundModelType;
+import android.media.soundtrigger_middleware.SoundTriggerModuleDescriptor;
+import android.media.soundtrigger_middleware.SoundTriggerModuleProperties;
+import android.os.HidlMemoryUtil;
+import android.os.HwParcel;
+import android.os.IHwBinder;
+import android.os.IHwInterface;
+import android.os.RemoteException;
+
+import androidx.test.runner.AndroidJUnit4;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+import org.mockito.ArgumentCaptor;
+import org.mockito.Mock;
+import org.mockito.Mockito;
+import org.mockito.stubbing.Answer;
+
+@RunWith(Parameterized.class)
+public class SoundTriggerMiddlewareImplTest {
+ private static final String TAG = "SoundTriggerMiddlewareImplTest";
+
+ // We run the test once for every version of the underlying driver.
+ @Parameterized.Parameters
+ public static Object[] data() {
+ return new Object[]{
+ mock(android.hardware.soundtrigger.V2_0.ISoundTriggerHw.class),
+ mock(android.hardware.soundtrigger.V2_1.ISoundTriggerHw.class),
+ mock(android.hardware.soundtrigger.V2_2.ISoundTriggerHw.class),
+ mock(android.hardware.soundtrigger.V2_3.ISoundTriggerHw.class),
+ };
+ }
+
+ @Mock
+ @Parameterized.Parameter
+ public android.hardware.soundtrigger.V2_0.ISoundTriggerHw mHalDriver;
+
+ @Mock
+ private SoundTriggerMiddlewareImpl.AudioSessionProvider mAudioSessionProvider = mock(
+ SoundTriggerMiddlewareImpl.AudioSessionProvider.class);
+
+ private SoundTriggerMiddlewareImpl mService;
+
+ private static ISoundTriggerCallback createCallbackMock() {
+ return mock(ISoundTriggerCallback.Stub.class, Mockito.CALLS_REAL_METHODS);
+ }
+
+ private static SoundModel createGenericSoundModel() {
+ return createSoundModel(SoundModelType.GENERIC);
+ }
+
+ private static SoundModel createSoundModel(int type) {
+ SoundModel model = new SoundModel();
+ model.type = type;
+ model.uuid = "12345678-2345-3456-4567-abcdef987654";
+ model.vendorUuid = "87654321-5432-6543-7654-456789fedcba";
+ model.data = new byte[]{91, 92, 93, 94, 95};
+ return model;
+ }
+
+ private static PhraseSoundModel createPhraseSoundModel() {
+ PhraseSoundModel model = new PhraseSoundModel();
+ model.common = createSoundModel(SoundModelType.KEYPHRASE);
+ model.phrases = new Phrase[1];
+ model.phrases[0] = new Phrase();
+ model.phrases[0].id = 123;
+ model.phrases[0].users = new int[]{5, 6, 7};
+ model.phrases[0].locale = "locale";
+ model.phrases[0].text = "text";
+ model.phrases[0].recognitionModes =
+ RecognitionMode.USER_AUTHENTICATION | RecognitionMode.USER_IDENTIFICATION;
+ return model;
+ }
+
+ private static android.hardware.soundtrigger.V2_0.ISoundTriggerHw.Properties createDefaultProperties(
+ boolean supportConcurrentCapture) {
+ android.hardware.soundtrigger.V2_0.ISoundTriggerHw.Properties properties =
+ new android.hardware.soundtrigger.V2_0.ISoundTriggerHw.Properties();
+ properties.implementor = "implementor";
+ properties.description = "description";
+ properties.version = 123;
+ properties.uuid = new Uuid();
+ properties.uuid.timeLow = 1;
+ properties.uuid.timeMid = 2;
+ properties.uuid.versionAndTimeHigh = 3;
+ properties.uuid.variantAndClockSeqHigh = 4;
+ properties.uuid.node = new byte[]{5, 6, 7, 8, 9, 10};
+
+ properties.maxSoundModels = 456;
+ properties.maxKeyPhrases = 567;
+ properties.maxUsers = 678;
+ properties.recognitionModes = 789;
+ properties.captureTransition = true;
+ properties.maxBufferMs = 321;
+ properties.concurrentCapture = supportConcurrentCapture;
+ properties.triggerInEvent = true;
+ properties.powerConsumptionMw = 432;
+ return properties;
+ }
+
+ private static void validateDefaultProperties(SoundTriggerModuleProperties properties,
+ boolean supportConcurrentCapture) {
+ assertEquals("implementor", properties.implementor);
+ assertEquals("description", properties.description);
+ assertEquals(123, properties.version);
+ assertEquals("00000001-0002-0003-0004-05060708090a", properties.uuid);
+ assertEquals(456, properties.maxSoundModels);
+ assertEquals(567, properties.maxKeyPhrases);
+ assertEquals(678, properties.maxUsers);
+ assertEquals(789, properties.recognitionModes);
+ assertTrue(properties.captureTransition);
+ assertEquals(321, properties.maxBufferMs);
+ assertEquals(supportConcurrentCapture, properties.concurrentCapture);
+ assertTrue(properties.triggerInEvent);
+ assertEquals(432, properties.powerConsumptionMw);
+ }
+
+
+ private static android.hardware.soundtrigger.V2_0.ISoundTriggerHwCallback.RecognitionEvent createRecognitionEvent_2_0(
+ int hwHandle,
+ int status) {
+ android.hardware.soundtrigger.V2_0.ISoundTriggerHwCallback.RecognitionEvent halEvent =
+ new android.hardware.soundtrigger.V2_0.ISoundTriggerHwCallback.RecognitionEvent();
+ halEvent.status = status;
+ halEvent.type = SoundModelType.GENERIC;
+ halEvent.model = hwHandle;
+ halEvent.captureAvailable = true;
+ // This field is ignored.
+ halEvent.captureSession = 123;
+ halEvent.captureDelayMs = 234;
+ halEvent.capturePreambleMs = 345;
+ halEvent.triggerInData = true;
+ halEvent.audioConfig = new AudioConfig();
+ halEvent.audioConfig.sampleRateHz = 456;
+ halEvent.audioConfig.channelMask = AudioChannelMask.IN_LEFT;
+ halEvent.audioConfig.format = AudioFormat.MP3;
+ // hwEvent.audioConfig.offloadInfo is irrelevant.
+ halEvent.data.add((byte) 31);
+ halEvent.data.add((byte) 32);
+ halEvent.data.add((byte) 33);
+ return halEvent;
+ }
+
+ private static android.hardware.soundtrigger.V2_1.ISoundTriggerHwCallback.RecognitionEvent createRecognitionEvent_2_1(
+ int hwHandle,
+ int status) {
+ android.hardware.soundtrigger.V2_1.ISoundTriggerHwCallback.RecognitionEvent halEvent =
+ new android.hardware.soundtrigger.V2_1.ISoundTriggerHwCallback.RecognitionEvent();
+ halEvent.header = createRecognitionEvent_2_0(hwHandle, status);
+ halEvent.header.data.clear();
+ halEvent.data = HidlMemoryUtil.byteArrayToHidlMemory(new byte[]{31, 32, 33});
+ return halEvent;
+ }
+
+ private static void validateRecognitionEvent(RecognitionEvent event, int status) {
+ assertEquals(status, event.status);
+ assertEquals(SoundModelType.GENERIC, event.type);
+ assertTrue(event.captureAvailable);
+ assertEquals(101, event.captureSession);
+ assertEquals(234, event.captureDelayMs);
+ assertEquals(345, event.capturePreambleMs);
+ assertTrue(event.triggerInData);
+ assertEquals(456, event.audioConfig.sampleRateHz);
+ assertEquals(AudioChannelMask.IN_LEFT, event.audioConfig.channelMask);
+ assertEquals(AudioFormat.MP3, event.audioConfig.format);
+ }
+
+ private static android.hardware.soundtrigger.V2_0.ISoundTriggerHwCallback.PhraseRecognitionEvent createPhraseRecognitionEvent_2_0(
+ int hwHandle, int status) {
+ android.hardware.soundtrigger.V2_0.ISoundTriggerHwCallback.PhraseRecognitionEvent halEvent =
+ new android.hardware.soundtrigger.V2_0.ISoundTriggerHwCallback.PhraseRecognitionEvent();
+ halEvent.common = createRecognitionEvent_2_0(hwHandle, status);
+
+ android.hardware.soundtrigger.V2_0.PhraseRecognitionExtra halExtra =
+ new android.hardware.soundtrigger.V2_0.PhraseRecognitionExtra();
+ halExtra.id = 123;
+ halExtra.confidenceLevel = 52;
+ halExtra.recognitionModes = android.hardware.soundtrigger.V2_0.RecognitionMode.VOICE_TRIGGER
+ | android.hardware.soundtrigger.V2_0.RecognitionMode.GENERIC_TRIGGER;
+ android.hardware.soundtrigger.V2_0.ConfidenceLevel halLevel =
+ new android.hardware.soundtrigger.V2_0.ConfidenceLevel();
+ halLevel.userId = 31;
+ halLevel.levelPercent = 43;
+ halExtra.levels.add(halLevel);
+ halEvent.phraseExtras.add(halExtra);
+ return halEvent;
+ }
+
+ private static android.hardware.soundtrigger.V2_1.ISoundTriggerHwCallback.PhraseRecognitionEvent createPhraseRecognitionEvent_2_1(
+ int hwHandle, int status) {
+ android.hardware.soundtrigger.V2_1.ISoundTriggerHwCallback.PhraseRecognitionEvent halEvent =
+ new android.hardware.soundtrigger.V2_1.ISoundTriggerHwCallback.PhraseRecognitionEvent();
+ halEvent.common = createRecognitionEvent_2_1(hwHandle, status);
+
+ android.hardware.soundtrigger.V2_0.PhraseRecognitionExtra halExtra =
+ new android.hardware.soundtrigger.V2_0.PhraseRecognitionExtra();
+ halExtra.id = 123;
+ halExtra.confidenceLevel = 52;
+ halExtra.recognitionModes = android.hardware.soundtrigger.V2_0.RecognitionMode.VOICE_TRIGGER
+ | android.hardware.soundtrigger.V2_0.RecognitionMode.GENERIC_TRIGGER;
+ android.hardware.soundtrigger.V2_0.ConfidenceLevel halLevel =
+ new android.hardware.soundtrigger.V2_0.ConfidenceLevel();
+ halLevel.userId = 31;
+ halLevel.levelPercent = 43;
+ halExtra.levels.add(halLevel);
+ halEvent.phraseExtras.add(halExtra);
+ return halEvent;
+ }
+
+ private static void validatePhraseRecognitionEvent(PhraseRecognitionEvent event, int status) {
+ validateRecognitionEvent(event.common, status);
+
+ assertEquals(1, event.phraseExtras.length);
+ assertEquals(123, event.phraseExtras[0].id);
+ assertEquals(52, event.phraseExtras[0].confidenceLevel);
+ assertEquals(RecognitionMode.VOICE_TRIGGER | RecognitionMode.GENERIC_TRIGGER,
+ event.phraseExtras[0].recognitionModes);
+ assertEquals(1, event.phraseExtras[0].levels.length);
+ assertEquals(31, event.phraseExtras[0].levels[0].userId);
+ assertEquals(43, event.phraseExtras[0].levels[0].levelPercent);
+ }
+
+ private void initService(boolean supportConcurrentCapture) throws RemoteException {
+ doAnswer(invocation -> {
+ android.hardware.soundtrigger.V2_0.ISoundTriggerHw.Properties properties =
+ createDefaultProperties(
+ supportConcurrentCapture);
+ ((android.hardware.soundtrigger.V2_0.ISoundTriggerHw.getPropertiesCallback) invocation.getArgument(
+ 0)).onValues(0,
+ properties);
+ return null;
+ }).when(mHalDriver).getProperties(any());
+ mService = new SoundTriggerMiddlewareImpl(mHalDriver, mAudioSessionProvider);
+ }
+
+ private int loadGenericModel_2_0(ISoundTriggerModule module, int hwHandle)
+ throws RemoteException {
+ SoundModel model = createGenericSoundModel();
+ ArgumentCaptor<android.hardware.soundtrigger.V2_0.ISoundTriggerHw.SoundModel> modelCaptor =
+ ArgumentCaptor.forClass(
+ android.hardware.soundtrigger.V2_0.ISoundTriggerHw.SoundModel.class);
+ doAnswer(invocation -> {
+ android.hardware.soundtrigger.V2_0.ISoundTriggerHwCallback callback =
+ invocation.getArgument(1);
+ int callbackCookie = invocation.getArgument(2);
+ android.hardware.soundtrigger.V2_0.ISoundTriggerHw.loadSoundModelCallback
+ resultCallback = invocation.getArgument(3);
+
+ // This is the return of this method.
+ resultCallback.onValues(0, hwHandle);
+
+ // This is the async mCallback that comes after.
+ android.hardware.soundtrigger.V2_0.ISoundTriggerHwCallback.ModelEvent modelEvent =
+ new android.hardware.soundtrigger.V2_0.ISoundTriggerHwCallback.ModelEvent();
+ modelEvent.status =
+ android.hardware.soundtrigger.V2_0.ISoundTriggerHwCallback.SoundModelStatus.UPDATED;
+ modelEvent.model = hwHandle;
+ callback.soundModelCallback(modelEvent, callbackCookie);
+ return null;
+ }).when(mHalDriver).loadSoundModel(modelCaptor.capture(), any(), anyInt(), any());
+
+ when(mAudioSessionProvider.acquireSession()).thenReturn(
+ new SoundTriggerMiddlewareImpl.AudioSessionProvider.AudioSession(101, 102, 103));
+
+ int handle = module.loadModel(model);
+ verify(mHalDriver).loadSoundModel(any(), any(), anyInt(), any());
+ verify(mAudioSessionProvider).acquireSession();
+
+ android.hardware.soundtrigger.V2_0.ISoundTriggerHw.SoundModel hidlModel =
+ modelCaptor.getValue();
+ assertEquals(android.hardware.soundtrigger.V2_0.SoundModelType.GENERIC,
+ hidlModel.type);
+ assertEquals(model.uuid, ConversionUtil.hidl2aidlUuid(hidlModel.uuid));
+ assertEquals(model.vendorUuid, ConversionUtil.hidl2aidlUuid(hidlModel.vendorUuid));
+ assertArrayEquals(new Byte[]{91, 92, 93, 94, 95}, hidlModel.data.toArray());
+
+ return handle;
+ }
+
+ private int loadGenericModel_2_1(ISoundTriggerModule module, int hwHandle)
+ throws RemoteException {
+ android.hardware.soundtrigger.V2_1.ISoundTriggerHw driver =
+ (android.hardware.soundtrigger.V2_1.ISoundTriggerHw) mHalDriver;
+ SoundModel model = createGenericSoundModel();
+ ArgumentCaptor<android.hardware.soundtrigger.V2_1.ISoundTriggerHw.SoundModel> modelCaptor =
+ ArgumentCaptor.forClass(
+ android.hardware.soundtrigger.V2_1.ISoundTriggerHw.SoundModel.class);
+ doAnswer(invocation -> {
+ android.hardware.soundtrigger.V2_1.ISoundTriggerHwCallback callback =
+ invocation.getArgument(1);
+ int callbackCookie = invocation.getArgument(2);
+ android.hardware.soundtrigger.V2_1.ISoundTriggerHw.loadSoundModel_2_1Callback
+ resultCallback = invocation.getArgument(3);
+
+ // This is the return of this method.
+ resultCallback.onValues(0, hwHandle);
+
+ // This is the async mCallback that comes after.
+ android.hardware.soundtrigger.V2_1.ISoundTriggerHwCallback.ModelEvent modelEvent =
+ new android.hardware.soundtrigger.V2_1.ISoundTriggerHwCallback.ModelEvent();
+ modelEvent.header.status =
+ android.hardware.soundtrigger.V2_1.ISoundTriggerHwCallback.SoundModelStatus.UPDATED;
+ modelEvent.header.model = hwHandle;
+ callback.soundModelCallback_2_1(modelEvent, callbackCookie);
+ return null;
+ }).when(driver).loadSoundModel_2_1(modelCaptor.capture(), any(), anyInt(), any());
+
+ when(mAudioSessionProvider.acquireSession()).thenReturn(
+ new SoundTriggerMiddlewareImpl.AudioSessionProvider.AudioSession(101, 102, 103));
+
+ int handle = module.loadModel(model);
+ verify(driver).loadSoundModel_2_1(any(), any(), anyInt(), any());
+ verify(mAudioSessionProvider).acquireSession();
+
+ android.hardware.soundtrigger.V2_1.ISoundTriggerHw.SoundModel hidlModel =
+ modelCaptor.getValue();
+ assertEquals(android.hardware.soundtrigger.V2_0.SoundModelType.GENERIC,
+ hidlModel.header.type);
+ assertEquals(model.uuid, ConversionUtil.hidl2aidlUuid(hidlModel.header.uuid));
+ assertEquals(model.vendorUuid, ConversionUtil.hidl2aidlUuid(hidlModel.header.vendorUuid));
+ assertArrayEquals(new byte[]{91, 92, 93, 94, 95},
+ HidlMemoryUtil.hidlMemoryToByteArray(hidlModel.data));
+
+ return handle;
+ }
+
+ private int loadGenericModel(ISoundTriggerModule module, int hwHandle) throws RemoteException {
+ if (mHalDriver instanceof android.hardware.soundtrigger.V2_1.ISoundTriggerHw) {
+ return loadGenericModel_2_1(module, hwHandle);
+ } else {
+ return loadGenericModel_2_0(module, hwHandle);
+ }
+ }
+
+ private int loadPhraseModel_2_0(ISoundTriggerModule module, int hwHandle)
+ throws RemoteException {
+ PhraseSoundModel model = createPhraseSoundModel();
+ ArgumentCaptor<android.hardware.soundtrigger.V2_0.ISoundTriggerHw.PhraseSoundModel>
+ modelCaptor = ArgumentCaptor.forClass(
+ android.hardware.soundtrigger.V2_0.ISoundTriggerHw.PhraseSoundModel.class);
+ doAnswer(invocation -> {
+ android.hardware.soundtrigger.V2_0.ISoundTriggerHwCallback callback =
+ invocation.getArgument(
+ 1);
+ int callbackCookie = invocation.getArgument(2);
+ android.hardware.soundtrigger.V2_0.ISoundTriggerHw.loadPhraseSoundModelCallback
+ resultCallback =
+ invocation.getArgument(
+ 3);
+
+ // This is the return of this method.
+ resultCallback.onValues(0, hwHandle);
+
+ // This is the async mCallback that comes after.
+ android.hardware.soundtrigger.V2_0.ISoundTriggerHwCallback.ModelEvent modelEvent =
+ new android.hardware.soundtrigger.V2_0.ISoundTriggerHwCallback.ModelEvent();
+ modelEvent.status =
+ android.hardware.soundtrigger.V2_0.ISoundTriggerHwCallback.SoundModelStatus.UPDATED;
+ modelEvent.model = hwHandle;
+ callback.soundModelCallback(modelEvent, callbackCookie);
+ return null;
+ }).when(mHalDriver).loadPhraseSoundModel(modelCaptor.capture(), any(), anyInt(), any());
+
+ when(mAudioSessionProvider.acquireSession()).thenReturn(
+ new SoundTriggerMiddlewareImpl.AudioSessionProvider.AudioSession(101, 102, 103));
+
+ int handle = module.loadPhraseModel(model);
+ verify(mHalDriver).loadPhraseSoundModel(any(), any(), anyInt(), any());
+ verify(mAudioSessionProvider).acquireSession();
+
+ android.hardware.soundtrigger.V2_0.ISoundTriggerHw.PhraseSoundModel hidlModel =
+ modelCaptor.getValue();
+
+ // Validate common part.
+ assertEquals(android.hardware.soundtrigger.V2_0.SoundModelType.KEYPHRASE,
+ hidlModel.common.type);
+ assertEquals(model.common.uuid, ConversionUtil.hidl2aidlUuid(hidlModel.common.uuid));
+ assertEquals(model.common.vendorUuid,
+ ConversionUtil.hidl2aidlUuid(hidlModel.common.vendorUuid));
+ assertArrayEquals(new Byte[]{91, 92, 93, 94, 95}, hidlModel.common.data.toArray());
+
+ // Validate phrase part.
+ assertEquals(1, hidlModel.phrases.size());
+ android.hardware.soundtrigger.V2_0.ISoundTriggerHw.Phrase hidlPhrase =
+ hidlModel.phrases.get(0);
+ assertEquals(123, hidlPhrase.id);
+ assertArrayEquals(new Integer[]{5, 6, 7}, hidlPhrase.users.toArray());
+ assertEquals("locale", hidlPhrase.locale);
+ assertEquals("text", hidlPhrase.text);
+ assertEquals(android.hardware.soundtrigger.V2_0.RecognitionMode.USER_AUTHENTICATION
+ | android.hardware.soundtrigger.V2_0.RecognitionMode.USER_IDENTIFICATION,
+ hidlPhrase.recognitionModes);
+
+ return handle;
+ }
+
+ private int loadPhraseModel_2_1(ISoundTriggerModule module, int hwHandle)
+ throws RemoteException {
+ android.hardware.soundtrigger.V2_1.ISoundTriggerHw driver =
+ (android.hardware.soundtrigger.V2_1.ISoundTriggerHw) mHalDriver;
+
+ PhraseSoundModel model = createPhraseSoundModel();
+ ArgumentCaptor<android.hardware.soundtrigger.V2_1.ISoundTriggerHw.PhraseSoundModel>
+ modelCaptor = ArgumentCaptor.forClass(
+ android.hardware.soundtrigger.V2_1.ISoundTriggerHw.PhraseSoundModel.class);
+ doAnswer(invocation -> {
+ android.hardware.soundtrigger.V2_1.ISoundTriggerHwCallback callback =
+ invocation.getArgument(
+ 1);
+ int callbackCookie = invocation.getArgument(2);
+ android.hardware.soundtrigger.V2_1.ISoundTriggerHw.loadPhraseSoundModel_2_1Callback
+ resultCallback =
+ invocation.getArgument(
+ 3);
+
+ // This is the return of this method.
+ resultCallback.onValues(0, hwHandle);
+
+ // This is the async mCallback that comes after.
+ android.hardware.soundtrigger.V2_1.ISoundTriggerHwCallback.ModelEvent modelEvent =
+ new android.hardware.soundtrigger.V2_1.ISoundTriggerHwCallback.ModelEvent();
+ modelEvent.header.status =
+ android.hardware.soundtrigger.V2_1.ISoundTriggerHwCallback.SoundModelStatus.UPDATED;
+ modelEvent.header.model = hwHandle;
+ callback.soundModelCallback_2_1(modelEvent, callbackCookie);
+ return null;
+ }).when(driver).loadPhraseSoundModel_2_1(modelCaptor.capture(), any(), anyInt(), any());
+
+ when(mAudioSessionProvider.acquireSession()).thenReturn(
+ new SoundTriggerMiddlewareImpl.AudioSessionProvider.AudioSession(101, 102, 103));
+
+ int handle = module.loadPhraseModel(model);
+ verify(driver).loadPhraseSoundModel_2_1(any(), any(), anyInt(), any());
+ verify(mAudioSessionProvider).acquireSession();
+
+ android.hardware.soundtrigger.V2_1.ISoundTriggerHw.PhraseSoundModel hidlModel =
+ modelCaptor.getValue();
+
+ // Validate common part.
+ assertEquals(android.hardware.soundtrigger.V2_0.SoundModelType.KEYPHRASE,
+ hidlModel.common.header.type);
+ assertEquals(model.common.uuid, ConversionUtil.hidl2aidlUuid(hidlModel.common.header.uuid));
+ assertEquals(model.common.vendorUuid,
+ ConversionUtil.hidl2aidlUuid(hidlModel.common.header.vendorUuid));
+ assertArrayEquals(new byte[]{91, 92, 93, 94, 95},
+ HidlMemoryUtil.hidlMemoryToByteArray(hidlModel.common.data));
+
+ // Validate phrase part.
+ assertEquals(1, hidlModel.phrases.size());
+ android.hardware.soundtrigger.V2_1.ISoundTriggerHw.Phrase hidlPhrase =
+ hidlModel.phrases.get(0);
+ assertEquals(123, hidlPhrase.id);
+ assertArrayEquals(new Integer[]{5, 6, 7}, hidlPhrase.users.toArray());
+ assertEquals("locale", hidlPhrase.locale);
+ assertEquals("text", hidlPhrase.text);
+ assertEquals(android.hardware.soundtrigger.V2_0.RecognitionMode.USER_AUTHENTICATION
+ | android.hardware.soundtrigger.V2_0.RecognitionMode.USER_IDENTIFICATION,
+ hidlPhrase.recognitionModes);
+
+ return handle;
+ }
+
+ private int loadPhraseModel(ISoundTriggerModule module, int hwHandle) throws RemoteException {
+ if (mHalDriver instanceof android.hardware.soundtrigger.V2_1.ISoundTriggerHw) {
+ return loadPhraseModel_2_1(module, hwHandle);
+ } else {
+ return loadPhraseModel_2_0(module, hwHandle);
+ }
+ }
+
+ private void unloadModel(ISoundTriggerModule module, int handle, int hwHandle)
+ throws RemoteException {
+ module.unloadModel(handle);
+ verify(mHalDriver).unloadSoundModel(hwHandle);
+ verify(mAudioSessionProvider).releaseSession(101);
+ }
+
+ private SoundTriggerHwCallback startRecognition_2_0(ISoundTriggerModule module, int handle,
+ int hwHandle) throws RemoteException {
+ ArgumentCaptor<android.hardware.soundtrigger.V2_0.ISoundTriggerHw.RecognitionConfig>
+ configCaptor = ArgumentCaptor.forClass(
+ android.hardware.soundtrigger.V2_0.ISoundTriggerHw.RecognitionConfig.class);
+ ArgumentCaptor<android.hardware.soundtrigger.V2_0.ISoundTriggerHwCallback> callbackCaptor =
+ ArgumentCaptor.forClass(
+ android.hardware.soundtrigger.V2_0.ISoundTriggerHwCallback.class);
+ ArgumentCaptor<Integer> cookieCaptor = ArgumentCaptor.forClass(Integer.class);
+
+ when(mHalDriver.startRecognition(eq(hwHandle), configCaptor.capture(),
+ callbackCaptor.capture(), cookieCaptor.capture())).thenReturn(0);
+
+ RecognitionConfig config = createRecognitionConfig();
+
+ module.startRecognition(handle, config);
+ verify(mHalDriver).startRecognition(eq(hwHandle), any(), any(), anyInt());
+
+ android.hardware.soundtrigger.V2_0.ISoundTriggerHw.RecognitionConfig halConfig =
+ configCaptor.getValue();
+ assertTrue(halConfig.captureRequested);
+ assertEquals(102, halConfig.captureHandle);
+ assertEquals(103, halConfig.captureDevice);
+ assertEquals(1, halConfig.phrases.size());
+ android.hardware.soundtrigger.V2_0.PhraseRecognitionExtra halPhraseExtra =
+ halConfig.phrases.get(0);
+ assertEquals(123, halPhraseExtra.id);
+ assertEquals(4, halPhraseExtra.confidenceLevel);
+ assertEquals(5, halPhraseExtra.recognitionModes);
+ assertEquals(1, halPhraseExtra.levels.size());
+ android.hardware.soundtrigger.V2_0.ConfidenceLevel halLevel = halPhraseExtra.levels.get(0);
+ assertEquals(234, halLevel.userId);
+ assertEquals(34, halLevel.levelPercent);
+ assertArrayEquals(new Byte[]{5, 4, 3, 2, 1}, halConfig.data.toArray());
+
+ return new SoundTriggerHwCallback(callbackCaptor.getValue(), cookieCaptor.getValue());
+ }
+
+ private SoundTriggerHwCallback startRecognition_2_1(ISoundTriggerModule module, int handle,
+ int hwHandle) throws RemoteException {
+ android.hardware.soundtrigger.V2_1.ISoundTriggerHw driver =
+ (android.hardware.soundtrigger.V2_1.ISoundTriggerHw) mHalDriver;
+
+ ArgumentCaptor<android.hardware.soundtrigger.V2_1.ISoundTriggerHw.RecognitionConfig>
+ configCaptor = ArgumentCaptor.forClass(
+ android.hardware.soundtrigger.V2_1.ISoundTriggerHw.RecognitionConfig.class);
+ ArgumentCaptor<android.hardware.soundtrigger.V2_1.ISoundTriggerHwCallback> callbackCaptor =
+ ArgumentCaptor.forClass(
+ android.hardware.soundtrigger.V2_1.ISoundTriggerHwCallback.class);
+ ArgumentCaptor<Integer> cookieCaptor = ArgumentCaptor.forClass(Integer.class);
+
+ when(driver.startRecognition_2_1(eq(hwHandle), configCaptor.capture(),
+ callbackCaptor.capture(), cookieCaptor.capture())).thenReturn(0);
+
+ RecognitionConfig config = createRecognitionConfig();
+
+ module.startRecognition(handle, config);
+ verify(driver).startRecognition_2_1(eq(hwHandle), any(), any(), anyInt());
+
+ android.hardware.soundtrigger.V2_1.ISoundTriggerHw.RecognitionConfig halConfig =
+ configCaptor.getValue();
+ assertTrue(halConfig.header.captureRequested);
+ assertEquals(102, halConfig.header.captureHandle);
+ assertEquals(103, halConfig.header.captureDevice);
+ assertEquals(1, halConfig.header.phrases.size());
+ android.hardware.soundtrigger.V2_0.PhraseRecognitionExtra halPhraseExtra =
+ halConfig.header.phrases.get(0);
+ assertEquals(123, halPhraseExtra.id);
+ assertEquals(4, halPhraseExtra.confidenceLevel);
+ assertEquals(5, halPhraseExtra.recognitionModes);
+ assertEquals(1, halPhraseExtra.levels.size());
+ android.hardware.soundtrigger.V2_0.ConfidenceLevel halLevel = halPhraseExtra.levels.get(0);
+ assertEquals(234, halLevel.userId);
+ assertEquals(34, halLevel.levelPercent);
+ assertArrayEquals(new byte[]{5, 4, 3, 2, 1},
+ HidlMemoryUtil.hidlMemoryToByteArray(halConfig.data));
+
+ return new SoundTriggerHwCallback(callbackCaptor.getValue(), cookieCaptor.getValue());
+ }
+
+ private SoundTriggerHwCallback startRecognition(ISoundTriggerModule module, int handle,
+ int hwHandle) throws RemoteException {
+ if (mHalDriver instanceof android.hardware.soundtrigger.V2_1.ISoundTriggerHw) {
+ return startRecognition_2_1(module, handle, hwHandle);
+ } else {
+ return startRecognition_2_0(module, handle, hwHandle);
+ }
+ }
+
+ private RecognitionConfig createRecognitionConfig() {
+ RecognitionConfig config = new RecognitionConfig();
+ config.captureRequested = true;
+ config.phraseRecognitionExtras = new PhraseRecognitionExtra[]{new PhraseRecognitionExtra()};
+ config.phraseRecognitionExtras[0].id = 123;
+ config.phraseRecognitionExtras[0].confidenceLevel = 4;
+ config.phraseRecognitionExtras[0].recognitionModes = 5;
+ config.phraseRecognitionExtras[0].levels = new ConfidenceLevel[]{new ConfidenceLevel()};
+ config.phraseRecognitionExtras[0].levels[0].userId = 234;
+ config.phraseRecognitionExtras[0].levels[0].levelPercent = 34;
+ config.data = new byte[]{5, 4, 3, 2, 1};
+ return config;
+ }
+
+ private void stopRecognition(ISoundTriggerModule module, int handle, int hwHandle)
+ throws RemoteException {
+ when(mHalDriver.stopRecognition(hwHandle)).thenReturn(0);
+ module.stopRecognition(handle);
+ verify(mHalDriver).stopRecognition(hwHandle);
+ }
+
+ private void verifyNotStartRecognition() throws RemoteException {
+ verify(mHalDriver, never()).startRecognition(anyInt(), any(), any(), anyInt());
+ if (mHalDriver instanceof android.hardware.soundtrigger.V2_1.ISoundTriggerHw) {
+ verify((android.hardware.soundtrigger.V2_1.ISoundTriggerHw) mHalDriver,
+ never()).startRecognition_2_1(anyInt(), any(), any(), anyInt());
+ }
+ }
+
+
+ @Before
+ public void setUp() throws Exception {
+ clearInvocations(mHalDriver);
+ clearInvocations(mAudioSessionProvider);
+
+ // This binder is associated with the mock, so it can be cast to either version of the
+ // HAL interface.
+ final IHwBinder binder = new IHwBinder() {
+ @Override
+ public void transact(int code, HwParcel request, HwParcel reply, int flags)
+ throws RemoteException {
+ // This is a little hacky, but a very easy way to gracefully reject a request for
+ // an unsupported interface (after queryLocalInterface() returns null, the client
+ // will attempt a remote transaction to obtain the interface. RemoteException will
+ // cause it to give up).
+ throw new RemoteException();
+ }
+
+ @Override
+ public IHwInterface queryLocalInterface(String descriptor) {
+ if (descriptor.equals("android.hardware.soundtrigger@2.0::ISoundTriggerHw")
+ || descriptor.equals("android.hardware.soundtrigger@2.1::ISoundTriggerHw")
+ && mHalDriver instanceof android.hardware.soundtrigger.V2_1.ISoundTriggerHw
+ || descriptor.equals("android.hardware.soundtrigger@2.2::ISoundTriggerHw")
+ && mHalDriver instanceof android.hardware.soundtrigger.V2_2.ISoundTriggerHw
+ || descriptor.equals("android.hardware.soundtrigger@2.3::ISoundTriggerHw")
+ && mHalDriver instanceof android.hardware.soundtrigger.V2_3.ISoundTriggerHw) {
+ return mHalDriver;
+ }
+ return null;
+ }
+
+ @Override
+ public boolean linkToDeath(DeathRecipient recipient, long cookie) {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public boolean unlinkToDeath(DeathRecipient recipient) {
+ throw new UnsupportedOperationException();
+ }
+ };
+
+ when(mHalDriver.asBinder()).thenReturn(binder);
+ }
+
+ @Test
+ public void testSetUpAndTearDown() {
+ }
+
+ @Test
+ public void testListModules() throws Exception {
+ initService(true);
+ // Note: input and output properties are NOT the same type, even though they are in any way
+ // equivalent. One is a type that's exposed by the HAL and one is a type that's exposed by
+ // the service. The service actually performs a (trivial) conversion between the two.
+ SoundTriggerModuleDescriptor[] allDescriptors = mService.listModules();
+ assertEquals(1, allDescriptors.length);
+
+ SoundTriggerModuleProperties properties = allDescriptors[0].properties;
+
+ validateDefaultProperties(properties, true);
+ }
+
+ @Test
+ public void testAttachDetach() throws Exception {
+ // Normal attachment / detachment.
+ initService(true);
+ ISoundTriggerCallback callback = createCallbackMock();
+ ISoundTriggerModule module = mService.attach(0, callback);
+ verify(callback).onRecognitionAvailabilityChange(true);
+ assertNotNull(module);
+ module.detach();
+ }
+
+ @Test
+ public void testAttachDetachNotAvailable() throws Exception {
+ // Attachment / detachment during external capture, with a module not supporting concurrent
+ // capture.
+ initService(false);
+ ISoundTriggerCallback callback = createCallbackMock();
+ ISoundTriggerModule module = mService.attach(0, callback);
+ verify(callback).onRecognitionAvailabilityChange(false);
+ assertNotNull(module);
+ module.detach();
+ }
+
+ @Test
+ public void testAttachDetachAvailable() throws Exception {
+ // Attachment / detachment during external capture, with a module supporting concurrent
+ // capture.
+ initService(true);
+ ISoundTriggerCallback callback = createCallbackMock();
+ ISoundTriggerModule module = mService.attach(0, callback);
+ verify(callback).onRecognitionAvailabilityChange(true);
+ assertNotNull(module);
+ module.detach();
+ }
+
+ @Test
+ public void testLoadUnloadModel() throws Exception {
+ initService(true);
+ ISoundTriggerCallback callback = createCallbackMock();
+ ISoundTriggerModule module = mService.attach(0, callback);
+
+ final int hwHandle = 7;
+ int handle = loadGenericModel(module, hwHandle);
+ unloadModel(module, handle, hwHandle);
+ module.detach();
+ }
+
+ @Test
+ public void testLoadUnloadPhraseModel() throws Exception {
+ initService(true);
+ ISoundTriggerCallback callback = createCallbackMock();
+ ISoundTriggerModule module = mService.attach(0, callback);
+
+ final int hwHandle = 73;
+ int handle = loadPhraseModel(module, hwHandle);
+ unloadModel(module, handle, hwHandle);
+ module.detach();
+ }
+
+ @Test
+ public void testStartStopRecognition() throws Exception {
+ initService(true);
+ ISoundTriggerCallback callback = createCallbackMock();
+ ISoundTriggerModule module = mService.attach(0, callback);
+
+ // Load the model.
+ final int hwHandle = 7;
+ int handle = loadGenericModel(module, hwHandle);
+
+ // Initiate a recognition.
+ startRecognition(module, handle, hwHandle);
+
+ // Stop the recognition.
+ stopRecognition(module, handle, hwHandle);
+
+ // Unload the model.
+ unloadModel(module, handle, hwHandle);
+ module.detach();
+ }
+
+ @Test
+ public void testStartStopPhraseRecognition() throws Exception {
+ initService(true);
+ ISoundTriggerCallback callback = createCallbackMock();
+ ISoundTriggerModule module = mService.attach(0, callback);
+
+ // Load the model.
+ final int hwHandle = 67;
+ int handle = loadPhraseModel(module, hwHandle);
+
+ // Initiate a recognition.
+ startRecognition(module, handle, hwHandle);
+
+ // Stop the recognition.
+ stopRecognition(module, handle, hwHandle);
+
+ // Unload the model.
+ unloadModel(module, handle, hwHandle);
+ module.detach();
+ }
+
+ @Test
+ public void testRecognition() throws Exception {
+ initService(true);
+ ISoundTriggerCallback callback = createCallbackMock();
+ ISoundTriggerModule module = mService.attach(0, callback);
+
+ // Load the model.
+ final int hwHandle = 7;
+ int handle = loadGenericModel(module, hwHandle);
+
+ // Initiate a recognition.
+ SoundTriggerHwCallback hwCallback = startRecognition(module, handle, hwHandle);
+
+ // Signal a capture from the driver.
+ hwCallback.sendRecognitionEvent(hwHandle,
+ android.hardware.soundtrigger.V2_0.ISoundTriggerHwCallback.RecognitionStatus.SUCCESS);
+
+ ArgumentCaptor<RecognitionEvent> eventCaptor = ArgumentCaptor.forClass(
+ RecognitionEvent.class);
+ verify(callback).onRecognition(eq(handle), eventCaptor.capture());
+
+ // Validate the event.
+ validateRecognitionEvent(eventCaptor.getValue(), RecognitionStatus.SUCCESS);
+
+ // Unload the model.
+ unloadModel(module, handle, hwHandle);
+ module.detach();
+ }
+
+ @Test
+ public void testPhraseRecognition() throws Exception {
+ initService(true);
+ ISoundTriggerCallback callback = createCallbackMock();
+ ISoundTriggerModule module = mService.attach(0, callback);
+
+ // Load the model.
+ final int hwHandle = 7;
+ int handle = loadPhraseModel(module, hwHandle);
+
+ // Initiate a recognition.
+ SoundTriggerHwCallback hwCallback = startRecognition(module, handle, hwHandle);
+
+ // Signal a capture from the driver.
+ hwCallback.sendPhraseRecognitionEvent(hwHandle,
+ android.hardware.soundtrigger.V2_0.ISoundTriggerHwCallback.RecognitionStatus.SUCCESS);
+
+ ArgumentCaptor<PhraseRecognitionEvent> eventCaptor = ArgumentCaptor.forClass(
+ PhraseRecognitionEvent.class);
+ verify(callback).onPhraseRecognition(eq(handle), eventCaptor.capture());
+
+ // Validate the event.
+ validatePhraseRecognitionEvent(eventCaptor.getValue(), RecognitionStatus.SUCCESS);
+
+ // Unload the model.
+ unloadModel(module, handle, hwHandle);
+ module.detach();
+ }
+
+ @Test
+ public void testForceRecognition() throws Exception {
+ if (!(mHalDriver instanceof android.hardware.soundtrigger.V2_2.ISoundTriggerHw)) {
+ return;
+ }
+
+ android.hardware.soundtrigger.V2_2.ISoundTriggerHw driver =
+ (android.hardware.soundtrigger.V2_2.ISoundTriggerHw) mHalDriver;
+
+ initService(true);
+ ISoundTriggerCallback callback = createCallbackMock();
+ ISoundTriggerModule module = mService.attach(0, callback);
+
+ // Load the model.
+ final int hwHandle = 17;
+ int handle = loadGenericModel(module, hwHandle);
+
+ // Initiate a recognition.
+ SoundTriggerHwCallback hwCallback = startRecognition(module, handle, hwHandle);
+
+ // Force a trigger.
+ module.forceRecognitionEvent(handle);
+ verify(driver).getModelState(hwHandle);
+
+ // Signal a capture from the driver.
+ // '3' means 'forced', there's no constant for that in the HAL.
+ hwCallback.sendRecognitionEvent(hwHandle, 3);
+
+ ArgumentCaptor<RecognitionEvent> eventCaptor = ArgumentCaptor.forClass(
+ RecognitionEvent.class);
+ verify(callback).onRecognition(eq(handle), eventCaptor.capture());
+
+ // Validate the event.
+ validateRecognitionEvent(eventCaptor.getValue(), RecognitionStatus.FORCED);
+
+ // Stop the recognition.
+ stopRecognition(module, handle, hwHandle);
+
+ // Unload the model.
+ unloadModel(module, handle, hwHandle);
+ module.detach();
+ }
+
+ @Test
+ public void testForcePhraseRecognition() throws Exception {
+ if (!(mHalDriver instanceof android.hardware.soundtrigger.V2_2.ISoundTriggerHw)) {
+ return;
+ }
+
+ android.hardware.soundtrigger.V2_2.ISoundTriggerHw driver =
+ (android.hardware.soundtrigger.V2_2.ISoundTriggerHw) mHalDriver;
+
+ initService(true);
+ ISoundTriggerCallback callback = createCallbackMock();
+ ISoundTriggerModule module = mService.attach(0, callback);
+
+ // Load the model.
+ final int hwHandle = 17;
+ int handle = loadPhraseModel(module, hwHandle);
+
+ // Initiate a recognition.
+ SoundTriggerHwCallback hwCallback = startRecognition(module, handle, hwHandle);
+
+ // Force a trigger.
+ module.forceRecognitionEvent(handle);
+ verify(driver).getModelState(hwHandle);
+
+ // Signal a capture from the driver.
+ // '3' means 'forced', there's no constant for that in the HAL.
+ hwCallback.sendPhraseRecognitionEvent(hwHandle, 3);
+
+ ArgumentCaptor<PhraseRecognitionEvent> eventCaptor = ArgumentCaptor.forClass(
+ PhraseRecognitionEvent.class);
+ verify(callback).onPhraseRecognition(eq(handle), eventCaptor.capture());
+
+ // Validate the event.
+ validatePhraseRecognitionEvent(eventCaptor.getValue(), RecognitionStatus.FORCED);
+
+ // Stop the recognition.
+ stopRecognition(module, handle, hwHandle);
+
+ // Unload the model.
+ unloadModel(module, handle, hwHandle);
+ module.detach();
+ }
+
+ @Test
+ public void testAbortRecognition() throws Exception {
+ // Make sure the HAL doesn't support concurrent capture.
+ initService(false);
+ mService.setExternalCaptureState(false);
+
+ ISoundTriggerCallback callback = createCallbackMock();
+ ISoundTriggerModule module = mService.attach(0, callback);
+ verify(callback).onRecognitionAvailabilityChange(true);
+
+ // Load the model.
+ final int hwHandle = 11;
+ int handle = loadGenericModel(module, hwHandle);
+
+ // Initiate a recognition.
+ startRecognition(module, handle, hwHandle);
+
+ // Abort.
+ mService.setExternalCaptureState(true);
+
+ ArgumentCaptor<RecognitionEvent> eventCaptor = ArgumentCaptor.forClass(
+ RecognitionEvent.class);
+ verify(callback).onRecognition(eq(handle), eventCaptor.capture());
+
+ // Validate the event.
+ assertEquals(RecognitionStatus.ABORTED, eventCaptor.getValue().status);
+
+ // Make sure we are notified of the lost availability.
+ verify(callback).onRecognitionAvailabilityChange(false);
+
+ // Attempt to start a new recognition - should get an abort event immediately, without
+ // involving the HAL.
+ clearInvocations(callback);
+ clearInvocations(mHalDriver);
+ module.startRecognition(handle, createRecognitionConfig());
+ verify(callback).onRecognition(eq(handle), eventCaptor.capture());
+ assertEquals(RecognitionStatus.ABORTED, eventCaptor.getValue().status);
+ verifyNotStartRecognition();
+
+ // Now enable it and make sure we are notified.
+ mService.setExternalCaptureState(false);
+ verify(callback).onRecognitionAvailabilityChange(true);
+
+ // Unload the model.
+ unloadModel(module, handle, hwHandle);
+ module.detach();
+ }
+
+ @Test
+ public void testAbortPhraseRecognition() throws Exception {
+ // Make sure the HAL doesn't support concurrent capture.
+ initService(false);
+ mService.setExternalCaptureState(false);
+
+ ISoundTriggerCallback callback = createCallbackMock();
+ ISoundTriggerModule module = mService.attach(0, callback);
+ verify(callback).onRecognitionAvailabilityChange(true);
+
+ // Load the model.
+ final int hwHandle = 11;
+ int handle = loadPhraseModel(module, hwHandle);
+
+ // Initiate a recognition.
+ startRecognition(module, handle, hwHandle);
+
+ // Abort.
+ mService.setExternalCaptureState(true);
+
+ ArgumentCaptor<PhraseRecognitionEvent> eventCaptor = ArgumentCaptor.forClass(
+ PhraseRecognitionEvent.class);
+ verify(callback).onPhraseRecognition(eq(handle), eventCaptor.capture());
+
+ // Validate the event.
+ assertEquals(RecognitionStatus.ABORTED, eventCaptor.getValue().common.status);
+
+ // Make sure we are notified of the lost availability.
+ verify(callback).onRecognitionAvailabilityChange(false);
+
+ // Attempt to start a new recognition - should get an abort event immediately, without
+ // involving the HAL.
+ clearInvocations(callback);
+ clearInvocations(mHalDriver);
+ module.startRecognition(handle, createRecognitionConfig());
+ verify(callback).onPhraseRecognition(eq(handle), eventCaptor.capture());
+ assertEquals(RecognitionStatus.ABORTED, eventCaptor.getValue().common.status);
+ verifyNotStartRecognition();
+
+ // Now enable it and make sure we are notified.
+ mService.setExternalCaptureState(false);
+ verify(callback).onRecognitionAvailabilityChange(true);
+
+ // Unload the model.
+ unloadModel(module, handle, hwHandle);
+ module.detach();
+ }
+
+ @Test
+ public void testNotAbortRecognitionConcurrent() throws Exception {
+ // Make sure the HAL supports concurrent capture.
+ initService(true);
+
+ ISoundTriggerCallback callback = createCallbackMock();
+ ISoundTriggerModule module = mService.attach(0, callback);
+ verify(callback).onRecognitionAvailabilityChange(true);
+ clearInvocations(callback);
+
+ // Load the model.
+ final int hwHandle = 13;
+ int handle = loadGenericModel(module, hwHandle);
+
+ // Initiate a recognition.
+ startRecognition(module, handle, hwHandle);
+
+ // Signal concurrent capture. Shouldn't abort.
+ mService.setExternalCaptureState(true);
+ verify(callback, never()).onRecognition(anyInt(), any());
+ verify(callback, never()).onRecognitionAvailabilityChange(anyBoolean());
+
+ // Stop the recognition.
+ stopRecognition(module, handle, hwHandle);
+
+ // Initiating a new one should work fine.
+ clearInvocations(mHalDriver);
+ startRecognition(module, handle, hwHandle);
+ verify(callback, never()).onRecognition(anyInt(), any());
+ stopRecognition(module, handle, hwHandle);
+
+ // Unload the model.
+ module.unloadModel(handle);
+ module.detach();
+ }
+
+ @Test
+ public void testNotAbortPhraseRecognitionConcurrent() throws Exception {
+ // Make sure the HAL supports concurrent capture.
+ initService(true);
+
+ ISoundTriggerCallback callback = createCallbackMock();
+ ISoundTriggerModule module = mService.attach(0, callback);
+ verify(callback).onRecognitionAvailabilityChange(true);
+ clearInvocations(callback);
+
+ // Load the model.
+ final int hwHandle = 13;
+ int handle = loadPhraseModel(module, hwHandle);
+
+ // Initiate a recognition.
+ startRecognition(module, handle, hwHandle);
+
+ // Signal concurrent capture. Shouldn't abort.
+ mService.setExternalCaptureState(true);
+ verify(callback, never()).onPhraseRecognition(anyInt(), any());
+ verify(callback, never()).onRecognitionAvailabilityChange(anyBoolean());
+
+ // Stop the recognition.
+ stopRecognition(module, handle, hwHandle);
+
+ // Initiating a new one should work fine.
+ clearInvocations(mHalDriver);
+ startRecognition(module, handle, hwHandle);
+ verify(callback, never()).onRecognition(anyInt(), any());
+ stopRecognition(module, handle, hwHandle);
+
+ // Unload the model.
+ module.unloadModel(handle);
+ module.detach();
+ }
+
+ @Test
+ public void testParameterSupported() throws Exception {
+ if (!(mHalDriver instanceof android.hardware.soundtrigger.V2_3.ISoundTriggerHw)) {
+ return;
+ }
+
+ android.hardware.soundtrigger.V2_3.ISoundTriggerHw driver =
+ (android.hardware.soundtrigger.V2_3.ISoundTriggerHw) mHalDriver;
+
+ initService(false);
+ ISoundTriggerCallback callback = createCallbackMock();
+ ISoundTriggerModule module = mService.attach(0, callback);
+ final int hwHandle = 12;
+ int modelHandle = loadGenericModel(module, hwHandle);
+
+ doAnswer((Answer<Void>) invocation -> {
+ android.hardware.soundtrigger.V2_3.ISoundTriggerHw.queryParameterCallback
+ resultCallback = invocation.getArgument(2);
+ android.hardware.soundtrigger.V2_3.ModelParameterRange range =
+ new android.hardware.soundtrigger.V2_3.ModelParameterRange();
+ range.start = 23;
+ range.end = 45;
+ OptionalModelParameterRange optionalRange = new OptionalModelParameterRange();
+ optionalRange.range(range);
+ resultCallback.onValues(0, optionalRange);
+ return null;
+ }).when(driver).queryParameter(eq(hwHandle),
+ eq(android.hardware.soundtrigger.V2_3.ModelParameter.THRESHOLD_FACTOR), any());
+
+ ModelParameterRange range = module.queryModelParameterSupport(modelHandle,
+ ModelParameter.THRESHOLD_FACTOR);
+
+ verify(driver).queryParameter(eq(hwHandle),
+ eq(android.hardware.soundtrigger.V2_3.ModelParameter.THRESHOLD_FACTOR), any());
+
+ assertEquals(23, range.minInclusive);
+ assertEquals(45, range.maxInclusive);
+ }
+
+ @Test
+ public void testParameterNotSupportedOld() throws Exception {
+ if (mHalDriver instanceof android.hardware.soundtrigger.V2_3.ISoundTriggerHw) {
+ return;
+ }
+
+ initService(false);
+ ISoundTriggerCallback callback = createCallbackMock();
+ ISoundTriggerModule module = mService.attach(0, callback);
+ final int hwHandle = 13;
+ int modelHandle = loadGenericModel(module, hwHandle);
+
+ ModelParameterRange range = module.queryModelParameterSupport(modelHandle,
+ ModelParameter.THRESHOLD_FACTOR);
+
+ assertNull(range);
+ }
+
+ @Test
+ public void testParameterNotSupported() throws Exception {
+ if (!(mHalDriver instanceof android.hardware.soundtrigger.V2_3.ISoundTriggerHw)) {
+ return;
+ }
+
+ android.hardware.soundtrigger.V2_3.ISoundTriggerHw driver =
+ (android.hardware.soundtrigger.V2_3.ISoundTriggerHw) mHalDriver;
+
+ initService(false);
+ ISoundTriggerCallback callback = createCallbackMock();
+ ISoundTriggerModule module = mService.attach(0, callback);
+ final int hwHandle = 13;
+ int modelHandle = loadGenericModel(module, hwHandle);
+
+ doAnswer(invocation -> {
+ android.hardware.soundtrigger.V2_3.ISoundTriggerHw.queryParameterCallback
+ resultCallback = invocation.getArgument(2);
+ // This is the return of this method.
+ resultCallback.onValues(0, new OptionalModelParameterRange());
+ return null;
+ }).when(driver).queryParameter(eq(hwHandle),
+ eq(android.hardware.soundtrigger.V2_3.ModelParameter.THRESHOLD_FACTOR), any());
+
+ ModelParameterRange range = module.queryModelParameterSupport(modelHandle,
+ ModelParameter.THRESHOLD_FACTOR);
+
+ verify(driver).queryParameter(eq(hwHandle),
+ eq(android.hardware.soundtrigger.V2_3.ModelParameter.THRESHOLD_FACTOR), any());
+
+ assertNull(range);
+ }
+
+ @Test
+ public void testGetParameter() throws Exception {
+ if (!(mHalDriver instanceof android.hardware.soundtrigger.V2_3.ISoundTriggerHw)) {
+ return;
+ }
+
+ android.hardware.soundtrigger.V2_3.ISoundTriggerHw driver =
+ (android.hardware.soundtrigger.V2_3.ISoundTriggerHw) mHalDriver;
+
+ initService(false);
+ ISoundTriggerCallback callback = createCallbackMock();
+ ISoundTriggerModule module = mService.attach(0, callback);
+ final int hwHandle = 14;
+ int modelHandle = loadGenericModel(module, hwHandle);
+
+ doAnswer(invocation -> {
+ android.hardware.soundtrigger.V2_3.ISoundTriggerHw.getParameterCallback
+ resultCallback = invocation.getArgument(2);
+ // This is the return of this method.
+ resultCallback.onValues(0, 234);
+ return null;
+ }).when(driver).getParameter(eq(hwHandle),
+ eq(android.hardware.soundtrigger.V2_3.ModelParameter.THRESHOLD_FACTOR), any());
+
+ int value = module.getModelParameter(modelHandle, ModelParameter.THRESHOLD_FACTOR);
+
+ verify(driver).getParameter(eq(hwHandle),
+ eq(android.hardware.soundtrigger.V2_3.ModelParameter.THRESHOLD_FACTOR), any());
+
+ assertEquals(234, value);
+ }
+
+ @Test
+ public void testSetParameter() throws Exception {
+ if (!(mHalDriver instanceof android.hardware.soundtrigger.V2_3.ISoundTriggerHw)) {
+ return;
+ }
+
+ android.hardware.soundtrigger.V2_3.ISoundTriggerHw driver =
+ (android.hardware.soundtrigger.V2_3.ISoundTriggerHw) mHalDriver;
+
+ initService(false);
+ ISoundTriggerCallback callback = createCallbackMock();
+ ISoundTriggerModule module = mService.attach(0, callback);
+ final int hwHandle = 17;
+ int modelHandle = loadGenericModel(module, hwHandle);
+
+ when(driver.setParameter(hwHandle,
+ android.hardware.soundtrigger.V2_3.ModelParameter.THRESHOLD_FACTOR,
+ 456)).thenReturn(0);
+
+ module.setModelParameter(modelHandle, ModelParameter.THRESHOLD_FACTOR, 456);
+
+ verify(driver).setParameter(hwHandle,
+ android.hardware.soundtrigger.V2_3.ModelParameter.THRESHOLD_FACTOR, 456);
+ }
+
+ private static class SoundTriggerHwCallback {
+ private final android.hardware.soundtrigger.V2_0.ISoundTriggerHwCallback mCallback;
+ private final int mCookie;
+
+ SoundTriggerHwCallback(android.hardware.soundtrigger.V2_0.ISoundTriggerHwCallback callback,
+ int cookie) {
+ mCallback = callback;
+ mCookie = cookie;
+ }
+
+ private void sendRecognitionEvent(int hwHandle, int status) throws RemoteException {
+ if (mCallback instanceof android.hardware.soundtrigger.V2_1.ISoundTriggerHwCallback) {
+ ((android.hardware.soundtrigger.V2_1.ISoundTriggerHwCallback) mCallback).recognitionCallback_2_1(
+ createRecognitionEvent_2_1(hwHandle, status), mCookie);
+ } else {
+ mCallback.recognitionCallback(createRecognitionEvent_2_0(hwHandle, status),
+ mCookie);
+ }
+ }
+
+ private void sendPhraseRecognitionEvent(int hwHandle, int status) throws RemoteException {
+ if (mCallback instanceof android.hardware.soundtrigger.V2_1.ISoundTriggerHwCallback) {
+ ((android.hardware.soundtrigger.V2_1.ISoundTriggerHwCallback) mCallback).phraseRecognitionCallback_2_1(
+ createPhraseRecognitionEvent_2_1(hwHandle, status), mCookie);
+ } else {
+ mCallback.phraseRecognitionCallback(
+ createPhraseRecognitionEvent_2_0(hwHandle, status), mCookie);
+ }
+ }
+ }
+}