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:
committed by
Android (Google) Code Review
commit
0d52f4202c
@@ -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.
|
||||
*
|
||||
|
||||
329
core/java/android/hardware/soundtrigger/ConversionUtil.java
Normal file
329
core/java/android/hardware/soundtrigger/ConversionUtil.java
Normal 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);
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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
@@ -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 -->
|
||||
|
||||
@@ -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" />
|
||||
|
||||
@@ -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" ],
|
||||
}
|
||||
|
||||
217
media/java/android/media/audio/common/AudioChannelMask.aidl
Normal file
217
media/java/android/media/audio/common/AudioChannelMask.aidl
Normal 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),
|
||||
}
|
||||
36
media/java/android/media/audio/common/AudioConfig.aidl
Normal file
36
media/java/android/media/audio/common/AudioConfig.aidl
Normal 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;
|
||||
}
|
||||
170
media/java/android/media/audio/common/AudioFormat.aidl
Normal file
170
media/java/android/media/audio/common/AudioFormat.aidl
Normal 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),
|
||||
}
|
||||
44
media/java/android/media/audio/common/AudioOffloadInfo.aidl
Normal file
44
media/java/android/media/audio/common/AudioOffloadInfo.aidl
Normal 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;
|
||||
}
|
||||
|
||||
44
media/java/android/media/audio/common/AudioStreamType.aidl
Normal file
44
media/java/android/media/audio/common/AudioStreamType.aidl
Normal 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,
|
||||
}
|
||||
40
media/java/android/media/audio/common/AudioUsage.aidl
Normal file
40
media/java/android/media/audio/common/AudioUsage.aidl
Normal 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,
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
@@ -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,
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
34
media/java/android/media/soundtrigger_middleware/Phrase.aidl
Normal file
34
media/java/android/media/soundtrigger_middleware/Phrase.aidl
Normal 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;
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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,
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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,
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
29
media/java/android/media/soundtrigger_middleware/Status.aidl
Normal file
29
media/java/android/media/soundtrigger_middleware/Status.aidl
Normal 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,
|
||||
}
|
||||
@@ -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",
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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 + ")";
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -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 + ")";
|
||||
}
|
||||
}
|
||||
@@ -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.
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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.");
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
{
|
||||
"presubmit": [
|
||||
{
|
||||
"name": "FrameworksServicesTests",
|
||||
"options": [
|
||||
{
|
||||
"include-filter": "com.android.server.soundtrigger_middleware"
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -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";
|
||||
}
|
||||
@@ -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");
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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",
|
||||
|
||||
@@ -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
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
Reference in New Issue
Block a user