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
This commit is contained in:
Ytai Ben-tsvi
2019-12-17 22:33:55 +00:00
committed by Android (Google) Code Review
56 changed files with 6389 additions and 1250 deletions

View File

@@ -4335,6 +4335,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.
*

View File

@@ -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);
}
}

View File

@@ -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,24 +1562,21 @@ 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.
@@ -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;
}
}
/**

View File

@@ -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;
private static final String TAG = "SoundTriggerModule";
@UnsupportedAppUsage
private int mId;
private NativeEventHandlerDelegate mEventHandlerDelegate;
// 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);
private class NativeEventHandlerDelegate {
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();
}
// 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) {
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);
}
break;
case EVENT_SERVICE_STATE_CHANGE:
if (listener != null) {
listener.onServiceStateChange(msg.arg1);
}
break;
case EVENT_SERVICE_DIED:
if (listener != null) {
listener.onServiceDied();
}
break;
default:
break;
}
}
};
} else {
mHandler = null;
}
}
Handler handler() {
return mHandler;
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();
}
}
@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;
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 EventHandlerDelegate extends ISoundTriggerCallback.Stub implements
IBinder.DeathRecipient {
private final Handler mHandler;
EventHandlerDelegate(@NonNull final SoundTrigger.StatusListener listener,
@NonNull Looper looper) {
// construct the event handler with this looper
// implement the event handler delegate
mHandler = new Handler(looper) {
@Override
public void handleMessage(Message msg) {
switch (msg.what) {
case EVENT_RECOGNITION:
listener.onRecognition(
(SoundTrigger.RecognitionEvent) msg.obj);
break;
case EVENT_SERVICE_STATE_CHANGE:
listener.onServiceStateChange(msg.arg1);
break;
case EVENT_SERVICE_DIED:
listener.onServiceDied();
break;
default:
Log.e(TAG, "Unknown message: " + msg.toString());
break;
}
}
};
}
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 onRecognition(int handle, RecognitionEvent event)
throws RemoteException {
Message m = mHandler.obtainMessage(EVENT_RECOGNITION,
ConversionUtil.aidl2apiRecognitionEvent(handle, event));
mHandler.sendMessage(m);
}
@Override
public synchronized void onPhraseRecognition(int handle, PhraseRecognitionEvent event)
throws RemoteException {
Message m = mHandler.obtainMessage(EVENT_RECOGNITION,
ConversionUtil.aidl2apiPhraseRecognitionEvent(handle, event));
mHandler.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);
}
}
}

View File

@@ -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",

View File

@@ -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),

File diff suppressed because it is too large Load Diff

View File

@@ -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 -->

View File

@@ -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" />

View File

@@ -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" ],
}

View File

@@ -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),
}

View File

@@ -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;
}

View File

@@ -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),
}

View File

@@ -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;
}

View File

@@ -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,
}

View File

@@ -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,
}

View File

@@ -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;
}

View File

@@ -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);
}

View File

@@ -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);
}

View File

@@ -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();
}

View File

@@ -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,
}

View File

@@ -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;
}

View File

@@ -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;
}

View File

@@ -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;
}

View File

@@ -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;
}

View File

@@ -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;
}

View File

@@ -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;
}

View File

@@ -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;
}

View File

@@ -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,
}

View File

@@ -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
}

View File

@@ -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;
}

View File

@@ -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,
}

View File

@@ -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;
}

View File

@@ -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;
}

View File

@@ -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,
}

View File

@@ -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",

View File

@@ -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);
}

View File

@@ -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;
}
}
}

View File

@@ -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 + ")";
}
}

View File

@@ -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;
}
}

View File

@@ -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);
}
}

View File

@@ -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);
}
}

View File

@@ -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 + ")";
}
}

View File

@@ -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.
}
}
}

View File

@@ -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.");
}
}

View File

@@ -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);
}
}
}
}
}

View File

@@ -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);
}
}
}
}
}
}

View File

@@ -0,0 +1,12 @@
{
"presubmit": [
{
"name": "FrameworksServicesTests",
"options": [
{
"include-filter": "com.android.server.soundtrigger_middleware"
}
]
}
]
}

View File

@@ -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";
}

View File

@@ -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");
}
}
}

View File

@@ -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",

View File

@@ -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

View File

@@ -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;
}

View File

@@ -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);

View File

@@ -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);
}
}