Merge "SoundTrigger API update." into lmp-dev

This commit is contained in:
Eric Laurent
2014-08-05 19:54:20 +00:00
committed by Android (Google) Code Review
8 changed files with 462 additions and 72 deletions

View File

@ -16,6 +16,7 @@
package android.hardware.soundtrigger;
import android.media.AudioFormat;
import android.os.Handler;
import android.os.Parcel;
import android.os.Parcelable;
@ -86,11 +87,15 @@ public class SoundTrigger {
/** Rated power consumption when detection is active with TDB silence/sound/speech ratio */
public final int powerConsumptionMw;
/** Returns the trigger (key phrase) capture in the binary data of the
* recognition callback event */
public final boolean returnsTriggerInEvent;
ModuleProperties(int id, String implementor, String description,
String uuid, int version, int maxSoundModels, int maxKeyphrases,
int maxUsers, int recognitionModes, boolean supportsCaptureTransition,
int maxBufferMs, boolean supportsConcurrentCapture,
int powerConsumptionMw) {
int powerConsumptionMw, boolean returnsTriggerInEvent) {
this.id = id;
this.implementor = implementor;
this.description = description;
@ -104,6 +109,7 @@ public class SoundTrigger {
this.maxBufferMs = maxBufferMs;
this.supportsConcurrentCapture = supportsConcurrentCapture;
this.powerConsumptionMw = powerConsumptionMw;
this.returnsTriggerInEvent = returnsTriggerInEvent;
}
public static final Parcelable.Creator<ModuleProperties> CREATOR
@ -131,10 +137,11 @@ public class SoundTrigger {
int maxBufferMs = in.readInt();
boolean supportsConcurrentCapture = in.readByte() == 1;
int powerConsumptionMw = in.readInt();
boolean returnsTriggerInEvent = in.readByte() == 1;
return new ModuleProperties(id, implementor, description, uuid, version,
maxSoundModels, maxKeyphrases, maxUsers, recognitionModes,
supportsCaptureTransition, maxBufferMs, supportsConcurrentCapture,
powerConsumptionMw);
powerConsumptionMw, returnsTriggerInEvent);
}
@Override
@ -152,6 +159,7 @@ public class SoundTrigger {
dest.writeInt(maxBufferMs);
dest.writeByte((byte) (supportsConcurrentCapture ? 1 : 0));
dest.writeInt(powerConsumptionMw);
dest.writeByte((byte) (returnsTriggerInEvent ? 1 : 0));
}
@Override
@ -167,7 +175,8 @@ public class SoundTrigger {
+ maxUsers + ", recognitionModes=" + recognitionModes
+ ", supportsCaptureTransition=" + supportsCaptureTransition + ", maxBufferMs="
+ maxBufferMs + ", supportsConcurrentCapture=" + supportsConcurrentCapture
+ ", powerConsumptionMw=" + powerConsumptionMw + "]";
+ ", powerConsumptionMw=" + powerConsumptionMw
+ ", returnsTriggerInEvent=" + returnsTriggerInEvent + "]";
}
}
@ -190,11 +199,15 @@ public class SoundTrigger {
/** Sound model type (e.g. TYPE_KEYPHRASE); */
public final int type;
/** Unique sound model vendor identifier */
public final UUID vendorUuid;
/** Opaque data. For use by vendor implementation and enrollment application */
public final byte[] data;
public SoundModel(UUID uuid, int type, byte[] data) {
public SoundModel(UUID uuid, UUID vendorUuid, int type, byte[] data) {
this.uuid = uuid;
this.vendorUuid = vendorUuid;
this.type = type;
this.data = data;
}
@ -329,8 +342,9 @@ public class SoundTrigger {
/** Key phrases in this sound model */
public final Keyphrase[] keyphrases; // keyword phrases in model
public KeyphraseSoundModel(UUID id, byte[] data, Keyphrase[] keyphrases) {
super(id, TYPE_KEYPHRASE, data);
public KeyphraseSoundModel(
UUID uuid, UUID vendorUuid, byte[] data, Keyphrase[] keyphrases) {
super(uuid, vendorUuid, TYPE_KEYPHRASE, data);
this.keyphrases = keyphrases;
}
@ -347,9 +361,14 @@ public class SoundTrigger {
private static KeyphraseSoundModel fromParcel(Parcel in) {
UUID uuid = UUID.fromString(in.readString());
UUID vendorUuid = null;
int length = in.readInt();
if (length >= 0) {
vendorUuid = UUID.fromString(in.readString());
}
byte[] data = in.readBlob();
Keyphrase[] keyphrases = in.createTypedArray(Keyphrase.CREATOR);
return new KeyphraseSoundModel(uuid, data, keyphrases);
return new KeyphraseSoundModel(uuid, vendorUuid, data, keyphrases);
}
@Override
@ -360,14 +379,21 @@ public class SoundTrigger {
@Override
public void writeToParcel(Parcel dest, int flags) {
dest.writeString(uuid.toString());
if (vendorUuid == null) {
dest.writeInt(-1);
} else {
dest.writeInt(vendorUuid.toString().length());
dest.writeString(vendorUuid.toString());
}
dest.writeBlob(data);
dest.writeTypedArray(keyphrases, flags);
}
@Override
public String toString() {
return "KeyphraseSoundModel [keyphrases=" + Arrays.toString(keyphrases) + ", uuid="
+ uuid + ", type=" + type + ", data=" + (data == null ? 0 : data.length) + "]";
return "KeyphraseSoundModel [keyphrases=" + Arrays.toString(keyphrases)
+ ", uuid=" + uuid + ", vendorUuid=" + vendorUuid
+ ", type=" + type + ", data=" + (data == null ? 0 : data.length) + "]";
}
}
@ -411,18 +437,26 @@ public class SoundTrigger {
public final int captureDelayMs;
/** Duration in ms of audio captured before the start of the trigger. 0 if none. */
public final int capturePreambleMs;
/** True if the trigger (key phrase capture is present in binary data */
public final boolean triggerInData;
/** Audio format of either the trigger in event data or to use for capture of the
* rest of the utterance */
public AudioFormat captureFormat;
/** Opaque data for use by system applications who know about voice engine internals,
* typically during enrollment. */
public final byte[] data;
public RecognitionEvent(int status, int soundModelHandle, boolean captureAvailable,
int captureSession, int captureDelayMs, int capturePreambleMs, byte[] data) {
int captureSession, int captureDelayMs, int capturePreambleMs,
boolean triggerInData, AudioFormat captureFormat, byte[] data) {
this.status = status;
this.soundModelHandle = soundModelHandle;
this.captureAvailable = captureAvailable;
this.captureSession = captureSession;
this.captureDelayMs = captureDelayMs;
this.capturePreambleMs = capturePreambleMs;
this.triggerInData = triggerInData;
this.captureFormat = captureFormat;
this.data = data;
}
@ -444,9 +478,21 @@ public class SoundTrigger {
int captureSession = in.readInt();
int captureDelayMs = in.readInt();
int capturePreambleMs = in.readInt();
boolean triggerInData = in.readByte() == 1;
AudioFormat captureFormat = null;
if (triggerInData) {
int sampleRate = in.readInt();
int encoding = in.readInt();
int channelMask = in.readInt();
captureFormat = (new AudioFormat.Builder())
.setChannelMask(channelMask)
.setEncoding(encoding)
.setSampleRate(sampleRate)
.build();
}
byte[] data = in.readBlob();
return new RecognitionEvent(status, soundModelHandle, captureAvailable, captureSession,
captureDelayMs, capturePreambleMs, data);
captureDelayMs, capturePreambleMs, triggerInData, captureFormat, data);
}
@Override
@ -462,6 +508,14 @@ public class SoundTrigger {
dest.writeInt(captureSession);
dest.writeInt(captureDelayMs);
dest.writeInt(capturePreambleMs);
if (triggerInData && (captureFormat != null)) {
dest.writeByte((byte)1);
dest.writeInt(captureFormat.getSampleRate());
dest.writeInt(captureFormat.getEncoding());
dest.writeInt(captureFormat.getChannelMask());
} else {
dest.writeByte((byte)0);
}
dest.writeBlob(data);
}
@ -473,6 +527,12 @@ public class SoundTrigger {
result = prime * result + captureDelayMs;
result = prime * result + capturePreambleMs;
result = prime * result + captureSession;
result = prime * result + (triggerInData ? 1231 : 1237);
if (captureFormat != null) {
result = prime * result + captureFormat.getSampleRate();
result = prime * result + captureFormat.getEncoding();
result = prime * result + captureFormat.getChannelMask();
}
result = prime * result + Arrays.hashCode(data);
result = prime * result + soundModelHandle;
result = prime * result + status;
@ -502,6 +562,14 @@ public class SoundTrigger {
return false;
if (status != other.status)
return false;
if (triggerInData != other.triggerInData)
return false;
if (captureFormat.getSampleRate() != other.captureFormat.getSampleRate())
return false;
if (captureFormat.getEncoding() != other.captureFormat.getEncoding())
return false;
if (captureFormat.getChannelMask() != other.captureFormat.getChannelMask())
return false;
return true;
}
@ -511,6 +579,13 @@ public class SoundTrigger {
+ ", captureAvailable=" + captureAvailable + ", captureSession="
+ captureSession + ", captureDelayMs=" + captureDelayMs
+ ", capturePreambleMs=" + capturePreambleMs
+ ", triggerInData=" + triggerInData
+ ((captureFormat == null) ? "" :
(", sampleRate=" + captureFormat.getSampleRate()))
+ ((captureFormat == null) ? "" :
(", encoding=" + captureFormat.getEncoding()))
+ ((captureFormat == null) ? "" :
(", channelMask=" + captureFormat.getChannelMask()))
+ ", data=" + (data == null ? 0 : data.length) + "]";
}
}
@ -673,14 +748,19 @@ public class SoundTrigger {
/** Recognition modes matched for this event */
public final int recognitionModes;
/** Confidence level for mode RECOGNITION_MODE_VOICE_TRIGGER when user identification
* is not performed */
public final int coarseConfidenceLevel;
/** Confidence levels for all users recognized (KeyphraseRecognitionEvent) or to
* be recognized (RecognitionConfig) */
public final ConfidenceLevel[] confidenceLevels;
public KeyphraseRecognitionExtra(int id, int recognitionModes,
ConfidenceLevel[] confidenceLevels) {
public KeyphraseRecognitionExtra(int id, int recognitionModes, int coarseConfidenceLevel,
ConfidenceLevel[] confidenceLevels) {
this.id = id;
this.recognitionModes = recognitionModes;
this.coarseConfidenceLevel = coarseConfidenceLevel;
this.confidenceLevels = confidenceLevels;
}
@ -698,14 +778,17 @@ public class SoundTrigger {
private static KeyphraseRecognitionExtra fromParcel(Parcel in) {
int id = in.readInt();
int recognitionModes = in.readInt();
int coarseConfidenceLevel = in.readInt();
ConfidenceLevel[] confidenceLevels = in.createTypedArray(ConfidenceLevel.CREATOR);
return new KeyphraseRecognitionExtra(id, recognitionModes, confidenceLevels);
return new KeyphraseRecognitionExtra(id, recognitionModes, coarseConfidenceLevel,
confidenceLevels);
}
@Override
public void writeToParcel(Parcel dest, int flags) {
dest.writeInt(id);
dest.writeInt(recognitionModes);
dest.writeInt(coarseConfidenceLevel);
dest.writeTypedArray(confidenceLevels, flags);
}
@ -721,6 +804,7 @@ public class SoundTrigger {
result = prime * result + Arrays.hashCode(confidenceLevels);
result = prime * result + id;
result = prime * result + recognitionModes;
result = prime * result + coarseConfidenceLevel;
return result;
}
@ -739,12 +823,15 @@ public class SoundTrigger {
return false;
if (recognitionModes != other.recognitionModes)
return false;
if (coarseConfidenceLevel != other.coarseConfidenceLevel)
return false;
return true;
}
@Override
public String toString() {
return "KeyphraseRecognitionExtra [id=" + id + ", recognitionModes=" + recognitionModes
+ ", coarseConfidenceLevel=" + coarseConfidenceLevel
+ ", confidenceLevels=" + Arrays.toString(confidenceLevels) + "]";
}
}
@ -756,15 +843,12 @@ public class SoundTrigger {
/** Indicates if the key phrase is present in the buffered audio available for capture */
public final KeyphraseRecognitionExtra[] keyphraseExtras;
/** Additional data available for each recognized key phrases in the model */
public final boolean keyphraseInCapture;
public KeyphraseRecognitionEvent(int status, int soundModelHandle, boolean captureAvailable,
int captureSession, int captureDelayMs, int capturePreambleMs, byte[] data,
boolean keyphraseInCapture, KeyphraseRecognitionExtra[] keyphraseExtras) {
int captureSession, int captureDelayMs, int capturePreambleMs,
boolean triggerInData, AudioFormat captureFormat, byte[] data,
KeyphraseRecognitionExtra[] keyphraseExtras) {
super(status, soundModelHandle, captureAvailable, captureSession, captureDelayMs,
capturePreambleMs, data);
this.keyphraseInCapture = keyphraseInCapture;
capturePreambleMs, triggerInData, captureFormat, data);
this.keyphraseExtras = keyphraseExtras;
}
@ -786,13 +870,24 @@ public class SoundTrigger {
int captureSession = in.readInt();
int captureDelayMs = in.readInt();
int capturePreambleMs = in.readInt();
boolean triggerInData = in.readByte() == 1;
AudioFormat captureFormat = null;
if (triggerInData) {
int sampleRate = in.readInt();
int encoding = in.readInt();
int channelMask = in.readInt();
captureFormat = (new AudioFormat.Builder())
.setChannelMask(channelMask)
.setEncoding(encoding)
.setSampleRate(sampleRate)
.build();
}
byte[] data = in.readBlob();
boolean keyphraseInCapture = in.readByte() == 1;
KeyphraseRecognitionExtra[] keyphraseExtras =
in.createTypedArray(KeyphraseRecognitionExtra.CREATOR);
return new KeyphraseRecognitionEvent(status, soundModelHandle, captureAvailable,
captureSession, captureDelayMs, capturePreambleMs, data, keyphraseInCapture,
keyphraseExtras);
captureSession, captureDelayMs, capturePreambleMs, triggerInData,
captureFormat, data, keyphraseExtras);
}
@Override
@ -803,8 +898,15 @@ public class SoundTrigger {
dest.writeInt(captureSession);
dest.writeInt(captureDelayMs);
dest.writeInt(capturePreambleMs);
if (triggerInData && (captureFormat != null)) {
dest.writeByte((byte)1);
dest.writeInt(captureFormat.getSampleRate());
dest.writeInt(captureFormat.getEncoding());
dest.writeInt(captureFormat.getChannelMask());
} else {
dest.writeByte((byte)0);
}
dest.writeBlob(data);
dest.writeByte((byte) (keyphraseInCapture ? 1 : 0));
dest.writeTypedArray(keyphraseExtras, flags);
}
@ -818,7 +920,6 @@ public class SoundTrigger {
final int prime = 31;
int result = super.hashCode();
result = prime * result + Arrays.hashCode(keyphraseExtras);
result = prime * result + (keyphraseInCapture ? 1231 : 1237);
return result;
}
@ -833,22 +934,126 @@ public class SoundTrigger {
KeyphraseRecognitionEvent other = (KeyphraseRecognitionEvent) obj;
if (!Arrays.equals(keyphraseExtras, other.keyphraseExtras))
return false;
if (keyphraseInCapture != other.keyphraseInCapture)
return false;
return true;
}
@Override
public String toString() {
return "KeyphraseRecognitionEvent [keyphraseExtras=" + Arrays.toString(keyphraseExtras)
+ ", keyphraseInCapture=" + keyphraseInCapture + ", status=" + status
+ ", status=" + status
+ ", soundModelHandle=" + soundModelHandle + ", captureAvailable="
+ captureAvailable + ", captureSession=" + captureSession + ", captureDelayMs="
+ captureDelayMs + ", capturePreambleMs=" + capturePreambleMs
+ ", triggerInData=" + triggerInData
+ ((captureFormat == null) ? "" :
(", sampleRate=" + captureFormat.getSampleRate()))
+ ((captureFormat == null) ? "" :
(", encoding=" + captureFormat.getEncoding()))
+ ((captureFormat == null) ? "" :
(", channelMask=" + captureFormat.getChannelMask()))
+ ", data=" + (data == null ? 0 : data.length) + "]";
}
}
/**
* Status codes for {@link SoundModelEvent}
*/
/** Sound Model was updated */
public static final int SOUNDMODEL_STATUS_UPDATED = 0;
/**
* A SoundModelEvent is provided by the
* {@link StatusListener#onSoundModelUpdate(SoundModelEvent)}
* callback when a sound model has been updated by the implementation
*/
public static class SoundModelEvent implements Parcelable {
/** Status e.g {@link #SOUNDMODEL_STATUS_UPDATED} */
public final int status;
/** The updated sound model handle */
public final int soundModelHandle;
/** New sound model data */
public final byte[] data;
SoundModelEvent(int status, int soundModelHandle, byte[] data) {
this.status = status;
this.soundModelHandle = soundModelHandle;
this.data = data;
}
public static final Parcelable.Creator<SoundModelEvent> CREATOR
= new Parcelable.Creator<SoundModelEvent>() {
public SoundModelEvent createFromParcel(Parcel in) {
return SoundModelEvent.fromParcel(in);
}
public SoundModelEvent[] newArray(int size) {
return new SoundModelEvent[size];
}
};
private static SoundModelEvent fromParcel(Parcel in) {
int status = in.readInt();
int soundModelHandle = in.readInt();
byte[] data = in.readBlob();
return new SoundModelEvent(status, soundModelHandle, data);
}
@Override
public int describeContents() {
return 0;
}
@Override
public void writeToParcel(Parcel dest, int flags) {
dest.writeInt(status);
dest.writeInt(soundModelHandle);
dest.writeBlob(data);
}
@Override
public int hashCode() {
final int prime = 31;
int result = 1;
result = prime * result + Arrays.hashCode(data);
result = prime * result + soundModelHandle;
result = prime * result + status;
return result;
}
@Override
public boolean equals(Object obj) {
if (this == obj)
return true;
if (obj == null)
return false;
if (getClass() != obj.getClass())
return false;
SoundModelEvent other = (SoundModelEvent) obj;
if (!Arrays.equals(data, other.data))
return false;
if (soundModelHandle != other.soundModelHandle)
return false;
if (status != other.status)
return false;
return true;
}
@Override
public String toString() {
return "SoundModelEvent [status=" + status + ", soundModelHandle=" + soundModelHandle
+ ", data=" + (data == null ? 0 : data.length) + "]";
}
}
/**
* Native service state. {@link StatusListener#onServiceStateChange(int)}
*/
// Keep in sync with system/core/include/system/sound_trigger.h
/** Sound trigger service is enabled */
public static final int SERVICE_STATE_ENABLED = 0;
/** Sound trigger service is disabled */
public static final int SERVICE_STATE_DISABLED = 1;
/**
* Returns a list of descriptors for all harware modules loaded.
* @param modules A ModuleProperties array where the list will be returned.
@ -890,6 +1095,18 @@ public class SoundTrigger {
*/
public abstract void onRecognition(RecognitionEvent event);
/**
* Called when a sound model has been updated
*/
public abstract void onSoundModelUpdate(SoundModelEvent event);
/**
* Called when the sound trigger native service state changes.
* @param state Native service state. One of {@link SoundTrigger#SERVICE_STATE_ENABLED},
* {@link SoundTrigger#SERVICE_STATE_DISABLED}
*/
public abstract void onServiceStateChange(int state);
/**
* Called when the sound trigger native service dies
*/

View File

@ -36,8 +36,11 @@ public class SoundTriggerModule {
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;
SoundTriggerModule(int moduleId, SoundTrigger.StatusListener listener, Handler handler) {
mId = moduleId;
@ -133,10 +136,7 @@ public class SoundTriggerModule {
if (handler != null) {
looper = handler.getLooper();
} else {
looper = Looper.myLooper();
if (looper == null) {
looper = Looper.getMainLooper();
}
looper = Looper.getMainLooper();
}
// construct the event handler with this looper
@ -152,6 +152,17 @@ public class SoundTriggerModule {
(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();

View File

@ -426,7 +426,7 @@ public class AlwaysOnHotwordDetector {
KeyphraseRecognitionExtra[] recognitionExtra = new KeyphraseRecognitionExtra[1];
// TODO: Do we need to do something about the confidence level here?
recognitionExtra[0] = new KeyphraseRecognitionExtra(mKeyphraseMetadata.id,
mKeyphraseMetadata.recognitionModeFlags, new ConfidenceLevel[0]);
mKeyphraseMetadata.recognitionModeFlags, 0, new ConfidenceLevel[0]);
boolean captureTriggerAudio =
(recognitionFlags&RECOGNITION_FLAG_CAPTURE_TRIGGER_AUDIO) != 0;
boolean allowMultipleTriggers =

View File

@ -29,6 +29,7 @@
#include <utils/Vector.h>
#include <binder/IMemory.h>
#include <binder/MemoryDealer.h>
#include "android_media_AudioFormat.h"
using namespace android;
@ -63,6 +64,7 @@ static const char* const kSoundModelClassPathName =
static jclass gSoundModelClass;
static struct {
jfieldID uuid;
jfieldID vendorUuid;
jfieldID data;
} gSoundModelFields;
@ -110,6 +112,7 @@ static jmethodID gKeyphraseRecognitionExtraCstor;
static struct {
jfieldID id;
jfieldID recognitionModes;
jfieldID coarseConfidenceLevel;
jfieldID confidenceLevels;
} gKeyphraseRecognitionExtraFields;
@ -122,6 +125,16 @@ static struct {
jfieldID confidenceLevel;
} gConfidenceLevelFields;
static const char* const kAudioFormatClassPathName =
"android/media/AudioFormat";
static jclass gAudioFormatClass;
static jmethodID gAudioFormatCstor;
static const char* const kSoundModelEventClassPathName =
"android/hardware/soundtrigger/SoundTrigger$SoundModelEvent";
static jclass gSoundModelEventClass;
static jmethodID gSoundModelEventCstor;
static Mutex gLock;
enum {
@ -137,6 +150,8 @@ enum {
enum {
SOUNDTRIGGER_EVENT_RECOGNITION = 1,
SOUNDTRIGGER_EVENT_SERVICE_DIED = 2,
SOUNDTRIGGER_EVENT_SOUNDMODEL = 3,
SOUNDTRIGGER_EVENT_SERVICE_STATE_CHANGE = 4,
};
// ----------------------------------------------------------------------------
@ -148,6 +163,8 @@ public:
~JNISoundTriggerCallback();
virtual void onRecognitionEvent(struct sound_trigger_recognition_event *event);
virtual void onSoundModelEvent(struct sound_trigger_model_event *event);
virtual void onServiceStateChange(sound_trigger_service_state_t state);
virtual void onServiceDied();
private:
@ -183,10 +200,9 @@ JNISoundTriggerCallback::~JNISoundTriggerCallback()
void JNISoundTriggerCallback::onRecognitionEvent(struct sound_trigger_recognition_event *event)
{
JNIEnv *env = AndroidRuntime::getJNIEnv();
jobject jEvent;
jobject jEvent = NULL;
jbyteArray jData = NULL;
if (event->data_size) {
jData = env->NewByteArray(event->data_size);
jbyte *nData = env->GetByteArrayElements(jData, NULL);
@ -194,6 +210,15 @@ void JNISoundTriggerCallback::onRecognitionEvent(struct sound_trigger_recognitio
env->ReleaseByteArrayElements(jData, nData, 0);
}
jobject jAudioFormat = NULL;
if (event->trigger_in_data) {
jAudioFormat = env->NewObject(gAudioFormatClass,
gAudioFormatCstor,
audioFormatFromNative(event->audio_config.format),
event->audio_config.sample_rate,
inChannelMaskFromNative(event->audio_config.channel_mask));
}
if (event->type == SOUND_MODEL_TYPE_KEYPHRASE) {
struct sound_trigger_phrase_recognition_event *phraseEvent =
(struct sound_trigger_phrase_recognition_event *)event;
@ -225,6 +250,7 @@ void JNISoundTriggerCallback::onRecognitionEvent(struct sound_trigger_recognitio
gKeyphraseRecognitionExtraCstor,
phraseEvent->phrase_extras[i].id,
phraseEvent->phrase_extras[i].recognition_modes,
phraseEvent->phrase_extras[i].confidence_level,
jConfidenceLevels);
if (jNewExtra == NULL) {
@ -236,19 +262,63 @@ void JNISoundTriggerCallback::onRecognitionEvent(struct sound_trigger_recognitio
}
jEvent = env->NewObject(gKeyphraseRecognitionEventClass, gKeyphraseRecognitionEventCstor,
event->status, event->model, event->capture_available,
event->capture_session, event->capture_delay_ms,
event->capture_preamble_ms, jData,
phraseEvent->key_phrase_in_capture, jExtras);
event->capture_session, event->capture_delay_ms,
event->capture_preamble_ms, event->trigger_in_data,
jAudioFormat, jData, jExtras);
env->DeleteLocalRef(jAudioFormat);
env->DeleteLocalRef(jData);
} else {
jEvent = env->NewObject(gRecognitionEventClass, gRecognitionEventCstor,
event->status, event->model, event->capture_available,
event->capture_session, event->capture_delay_ms,
event->capture_preamble_ms, jData);
event->capture_preamble_ms, event->trigger_in_data,
jAudioFormat, jData);
env->DeleteLocalRef(jAudioFormat);
env->DeleteLocalRef(jData);
}
env->CallStaticVoidMethod(mClass, gPostEventFromNative, mObject,
SOUNDTRIGGER_EVENT_RECOGNITION, 0, 0, jEvent);
env->DeleteLocalRef(jEvent);
if (env->ExceptionCheck()) {
ALOGW("An exception occurred while notifying an event.");
env->ExceptionClear();
}
}
void JNISoundTriggerCallback::onSoundModelEvent(struct sound_trigger_model_event *event)
{
JNIEnv *env = AndroidRuntime::getJNIEnv();
jobject jEvent = NULL;
jbyteArray jData = NULL;
if (event->data_size) {
jData = env->NewByteArray(event->data_size);
jbyte *nData = env->GetByteArrayElements(jData, NULL);
memcpy(nData, (char *)event + event->data_offset, event->data_size);
env->ReleaseByteArrayElements(jData, nData, 0);
}
jEvent = env->NewObject(gSoundModelEventClass, gSoundModelEventCstor,
event->status, event->model, jData);
env->DeleteLocalRef(jData);
env->CallStaticVoidMethod(mClass, gPostEventFromNative, mObject,
SOUNDTRIGGER_EVENT_SOUNDMODEL, 0, 0, jEvent);
env->DeleteLocalRef(jEvent);
if (env->ExceptionCheck()) {
ALOGW("An exception occurred while notifying an event.");
env->ExceptionClear();
}
}
void JNISoundTriggerCallback::onServiceStateChange(sound_trigger_service_state_t state)
{
JNIEnv *env = AndroidRuntime::getJNIEnv();
env->CallStaticVoidMethod(mClass, gPostEventFromNative, mObject,
SOUNDTRIGGER_EVENT_SERVICE_STATE_CHANGE, state, 0, NULL);
if (env->ExceptionCheck()) {
ALOGW("An exception occurred while notifying an event.");
env->ExceptionClear();
@ -336,7 +406,7 @@ android_hardware_SoundTrigger_listModules(JNIEnv *env, jobject clazz,
SOUND_TRIGGER_MAX_STRING_LEN);
jstring uuid = env->NewStringUTF(str);
ALOGV("listModules module %d id %d description %s maxSoundModels %d",
ALOGV("listModules module %zu id %d description %s maxSoundModels %d",
i, nModules[i].handle, nModules[i].properties.description,
nModules[i].properties.max_sound_models);
@ -351,7 +421,8 @@ android_hardware_SoundTrigger_listModules(JNIEnv *env, jobject clazz,
nModules[i].properties.capture_transition,
nModules[i].properties.max_buffer_ms,
nModules[i].properties.concurrent_capture,
nModules[i].properties.power_consumption_mw);
nModules[i].properties.power_consumption_mw,
nModules[i].properties.trigger_in_event);
env->DeleteLocalRef(implementor);
env->DeleteLocalRef(description);
@ -463,6 +534,18 @@ android_hardware_SoundTrigger_loadSoundModel(JNIEnv *env, jobject thiz,
env->ReleaseStringUTFChars(jUuidString, nUuidString);
env->DeleteLocalRef(jUuidString);
sound_trigger_uuid_t nVendorUuid;
jUuid = env->GetObjectField(jSoundModel, gSoundModelFields.vendorUuid);
if (jUuid != NULL) {
jUuidString = (jstring)env->CallObjectMethod(jUuid, gUUIDMethods.toString);
nUuidString = env->GetStringUTFChars(jUuidString, NULL);
SoundTrigger::stringToGuid(nUuidString, &nVendorUuid);
env->ReleaseStringUTFChars(jUuidString, nUuidString);
env->DeleteLocalRef(jUuidString);
} else {
SoundTrigger::stringToGuid("00000000-0000-0000-0000-000000000000", &nVendorUuid);
}
jData = (jbyteArray)env->GetObjectField(jSoundModel, gSoundModelFields.data);
if (jData == NULL) {
status = SOUNDTRIGGER_STATUS_BAD_VALUE;
@ -491,6 +574,7 @@ android_hardware_SoundTrigger_loadSoundModel(JNIEnv *env, jobject thiz,
nSoundModel->type = type;
nSoundModel->uuid = nUuid;
nSoundModel->vendor_uuid = nVendorUuid;
nSoundModel->data_size = size;
nSoundModel->data_offset = offset;
memcpy((char *)nSoundModel + offset, nData, size);
@ -507,7 +591,7 @@ android_hardware_SoundTrigger_loadSoundModel(JNIEnv *env, jobject thiz,
size_t numPhrases = env->GetArrayLength(jPhrases);
phraseModel->num_phrases = numPhrases;
ALOGV("loadSoundModel numPhrases %d", numPhrases);
ALOGV("loadSoundModel numPhrases %zu", numPhrases);
for (size_t i = 0; i < numPhrases; i++) {
jobject jPhrase = env->GetObjectArrayElement(jPhrases, i);
phraseModel->phrases[i].id =
@ -539,7 +623,7 @@ android_hardware_SoundTrigger_loadSoundModel(JNIEnv *env, jobject thiz,
env->DeleteLocalRef(jLocale);
env->ReleaseStringUTFChars(jText, nText);
env->DeleteLocalRef(jText);
ALOGV("loadSoundModel phrases %d text %s locale %s",
ALOGV("loadSoundModel phrases %zu text %s locale %s",
i, phraseModel->phrases[i].text, phraseModel->phrases[i].locale);
env->DeleteLocalRef(jPhrase);
}
@ -640,13 +724,15 @@ android_hardware_SoundTrigger_startRecognition(JNIEnv *env, jobject thiz,
gKeyphraseRecognitionExtraFields.id);
config->phrases[i].recognition_modes = env->GetIntField(jPhrase,
gKeyphraseRecognitionExtraFields.recognitionModes);
config->phrases[i].confidence_level = env->GetIntField(jPhrase,
gKeyphraseRecognitionExtraFields.coarseConfidenceLevel);
config->phrases[i].num_levels = 0;
jobjectArray jConfidenceLevels = (jobjectArray)env->GetObjectField(jPhrase,
gKeyphraseRecognitionExtraFields.confidenceLevels);
if (jConfidenceLevels != NULL) {
config->phrases[i].num_levels = env->GetArrayLength(jConfidenceLevels);
}
ALOGV("startRecognition phrase %d num_levels %d", i, config->phrases[i].num_levels);
ALOGV("startRecognition phrase %zu num_levels %d", i, config->phrases[i].num_levels);
for (size_t j = 0; j < config->phrases[i].num_levels; j++) {
jobject jConfidenceLevel = env->GetObjectArrayElement(jConfidenceLevels, j);
config->phrases[i].levels[j].user_id = env->GetIntField(jConfidenceLevel,
@ -655,7 +741,7 @@ android_hardware_SoundTrigger_startRecognition(JNIEnv *env, jobject thiz,
gConfidenceLevelFields.confidenceLevel);
env->DeleteLocalRef(jConfidenceLevel);
}
ALOGV("startRecognition phrases %d", i);
ALOGV("startRecognition phrases %zu", i);
env->DeleteLocalRef(jConfidenceLevels);
env->DeleteLocalRef(jPhrase);
}
@ -734,11 +820,12 @@ int register_android_hardware_SoundTrigger(JNIEnv *env)
jclass modulePropertiesClass = env->FindClass(kModulePropertiesClassPathName);
gModulePropertiesClass = (jclass) env->NewGlobalRef(modulePropertiesClass);
gModulePropertiesCstor = env->GetMethodID(modulePropertiesClass, "<init>",
"(ILjava/lang/String;Ljava/lang/String;Ljava/lang/String;IIIIIZIZI)V");
"(ILjava/lang/String;Ljava/lang/String;Ljava/lang/String;IIIIIZIZIZ)V");
jclass soundModelClass = env->FindClass(kSoundModelClassPathName);
gSoundModelClass = (jclass) env->NewGlobalRef(soundModelClass);
gSoundModelFields.uuid = env->GetFieldID(soundModelClass, "uuid", "Ljava/util/UUID;");
gSoundModelFields.vendorUuid = env->GetFieldID(soundModelClass, "vendorUuid", "Ljava/util/UUID;");
gSoundModelFields.data = env->GetFieldID(soundModelClass, "data", "[B");
jclass keyphraseClass = env->FindClass(kKeyphraseClassPathName);
@ -759,12 +846,12 @@ int register_android_hardware_SoundTrigger(JNIEnv *env)
jclass recognitionEventClass = env->FindClass(kRecognitionEventClassPathName);
gRecognitionEventClass = (jclass) env->NewGlobalRef(recognitionEventClass);
gRecognitionEventCstor = env->GetMethodID(recognitionEventClass, "<init>",
"(IIZIII[B)V");
"(IIZIIIZLandroid/media/AudioFormat;[B)V");
jclass keyphraseRecognitionEventClass = env->FindClass(kKeyphraseRecognitionEventClassPathName);
gKeyphraseRecognitionEventClass = (jclass) env->NewGlobalRef(keyphraseRecognitionEventClass);
gKeyphraseRecognitionEventCstor = env->GetMethodID(keyphraseRecognitionEventClass, "<init>",
"(IIZIII[BZ[Landroid/hardware/soundtrigger/SoundTrigger$KeyphraseRecognitionExtra;)V");
"(IIZIIIZLandroid/media/AudioFormat;[B[Landroid/hardware/soundtrigger/SoundTrigger$KeyphraseRecognitionExtra;)V");
jclass keyRecognitionConfigClass = env->FindClass(kRecognitionConfigClassPathName);
@ -782,9 +869,12 @@ int register_android_hardware_SoundTrigger(JNIEnv *env)
jclass keyphraseRecognitionExtraClass = env->FindClass(kKeyphraseRecognitionExtraClassPathName);
gKeyphraseRecognitionExtraClass = (jclass) env->NewGlobalRef(keyphraseRecognitionExtraClass);
gKeyphraseRecognitionExtraCstor = env->GetMethodID(keyphraseRecognitionExtraClass, "<init>",
"(II[Landroid/hardware/soundtrigger/SoundTrigger$ConfidenceLevel;)V");
"(III[Landroid/hardware/soundtrigger/SoundTrigger$ConfidenceLevel;)V");
gKeyphraseRecognitionExtraFields.id = env->GetFieldID(gKeyphraseRecognitionExtraClass, "id", "I");
gKeyphraseRecognitionExtraFields.recognitionModes = env->GetFieldID(gKeyphraseRecognitionExtraClass, "recognitionModes", "I");
gKeyphraseRecognitionExtraFields.recognitionModes = env->GetFieldID(gKeyphraseRecognitionExtraClass,
"recognitionModes", "I");
gKeyphraseRecognitionExtraFields.coarseConfidenceLevel = env->GetFieldID(gKeyphraseRecognitionExtraClass,
"coarseConfidenceLevel", "I");
gKeyphraseRecognitionExtraFields.confidenceLevels = env->GetFieldID(gKeyphraseRecognitionExtraClass,
"confidenceLevels",
"[Landroid/hardware/soundtrigger/SoundTrigger$ConfidenceLevel;");
@ -796,6 +886,16 @@ int register_android_hardware_SoundTrigger(JNIEnv *env)
gConfidenceLevelFields.confidenceLevel = env->GetFieldID(confidenceLevelClass,
"confidenceLevel", "I");
jclass audioFormatClass = env->FindClass(kAudioFormatClassPathName);
gAudioFormatClass = (jclass) env->NewGlobalRef(audioFormatClass);
gAudioFormatCstor = env->GetMethodID(audioFormatClass, "<init>", "(III)V");
jclass soundModelEventClass = env->FindClass(kSoundModelEventClassPathName);
gSoundModelEventClass = (jclass) env->NewGlobalRef(soundModelEventClass);
gSoundModelEventCstor = env->GetMethodID(soundModelEventClass, "<init>",
"(II[B)V");
int status = AndroidRuntime::registerNativeMethods(env,
kSoundTriggerClassPathName, gMethods, NELEM(gMethods));

View File

@ -249,6 +249,20 @@ public class AudioFormat {
private AudioFormat(int ignoredArgument) {
}
/**
* Constructor used by the JNI
*/
// Update sound trigger JNI in core/jni/android_hardware_SoundTrigger.cpp when modifying this
// constructor
private AudioFormat(int encoding, int sampleRate, int channelMask) {
mEncoding = encoding;
mSampleRate = sampleRate;
mChannelMask = channelMask;
mPropertySetMask = AUDIO_FORMAT_HAS_PROPERTY_ENCODING |
AUDIO_FORMAT_HAS_PROPERTY_SAMPLE_RATE |
AUDIO_FORMAT_HAS_PROPERTY_CHANNEL_MASK;
}
/** @hide */
public final static int AUDIO_FORMAT_HAS_PROPERTY_NONE = 0x0;
/** @hide */

View File

@ -195,7 +195,8 @@ public class DatabaseHelper extends SQLiteOpenHelper {
Keyphrase[] keyphrases = new Keyphrase[1];
keyphrases[0] = new Keyphrase(
keyphraseId, recognitionModes, locale, text, users);
return new KeyphraseSoundModel(UUID.fromString(modelUuid), data, keyphrases);
return new KeyphraseSoundModel(UUID.fromString(modelUuid),
null /* FIXME use vendor UUID */, data, keyphrases);
}
Slog.w(TAG, "No SoundModel available for the given keyphrase");
} finally {

View File

@ -25,6 +25,7 @@ import android.hardware.soundtrigger.SoundTrigger.KeyphraseSoundModel;
import android.hardware.soundtrigger.SoundTrigger.ModuleProperties;
import android.hardware.soundtrigger.SoundTrigger.RecognitionConfig;
import android.hardware.soundtrigger.SoundTrigger.RecognitionEvent;
import android.hardware.soundtrigger.SoundTrigger.SoundModelEvent;
import android.hardware.soundtrigger.SoundTriggerModule;
import android.os.RemoteException;
import android.util.Slog;
@ -330,6 +331,23 @@ public class SoundTriggerHelper implements SoundTrigger.StatusListener {
}
}
public void onSoundModelUpdate(SoundModelEvent event) {
if (event == null) {
Slog.w(TAG, "Invalid sound model event!");
return;
}
if (DBG) Slog.d(TAG, "onSoundModelUpdate: " + event);
//TODO: implement sound model update
}
public void onServiceStateChange(int state) {
if (DBG) Slog.d(TAG, "onServiceStateChange, state: " + state);
//TODO: implement service state update
}
@Override
public void onServiceDied() {
synchronized (this) {

View File

@ -23,6 +23,7 @@ import android.hardware.soundtrigger.SoundTrigger.KeyphraseRecognitionEvent;
import android.hardware.soundtrigger.SoundTrigger.KeyphraseRecognitionExtra;
import android.hardware.soundtrigger.SoundTrigger.KeyphraseSoundModel;
import android.hardware.soundtrigger.SoundTrigger.RecognitionEvent;
import android.media.AudioFormat;
import android.os.Parcel;
import android.test.InstrumentationTestCase;
import android.test.suitebuilder.annotation.LargeTest;
@ -97,7 +98,8 @@ public class SoundTriggerTest extends InstrumentationTestCase {
Keyphrase[] keyphrases = new Keyphrase[2];
keyphrases[0] = new Keyphrase(1, 0, "en-US", "hello", new int[] {0});
keyphrases[1] = new Keyphrase(2, 0, "fr-FR", "there", new int[] {1, 2});
KeyphraseSoundModel ksm = new KeyphraseSoundModel(UUID.randomUUID(), null, keyphrases);
KeyphraseSoundModel ksm = new KeyphraseSoundModel(UUID.randomUUID(), UUID.randomUUID(),
null, keyphrases);
// Write to a parcel
Parcel parcel = Parcel.obtain();
@ -119,8 +121,8 @@ public class SoundTriggerTest extends InstrumentationTestCase {
Keyphrase[] keyphrases = new Keyphrase[2];
keyphrases[0] = new Keyphrase(1, 0, "en-US", "hello", new int[] {0});
keyphrases[1] = new Keyphrase(2, 0, "fr-FR", "there", new int[] {1, 2});
KeyphraseSoundModel ksm = new KeyphraseSoundModel(UUID.randomUUID(), new byte[0],
keyphrases);
KeyphraseSoundModel ksm = new KeyphraseSoundModel(UUID.randomUUID(), UUID.randomUUID(),
new byte[0], keyphrases);
// Write to a parcel
Parcel parcel = Parcel.obtain();
@ -141,7 +143,8 @@ public class SoundTriggerTest extends InstrumentationTestCase {
public void testKeyphraseSoundModelParcelUnparcel_noKeyphrases() throws Exception {
byte[] data = new byte[10];
mRandom.nextBytes(data);
KeyphraseSoundModel ksm = new KeyphraseSoundModel(UUID.randomUUID(), data, null);
KeyphraseSoundModel ksm = new KeyphraseSoundModel(UUID.randomUUID(), UUID.randomUUID(),
data, null);
// Write to a parcel
Parcel parcel = Parcel.obtain();
@ -162,8 +165,8 @@ public class SoundTriggerTest extends InstrumentationTestCase {
public void testKeyphraseSoundModelParcelUnparcel_zeroKeyphrases() throws Exception {
byte[] data = new byte[10];
mRandom.nextBytes(data);
KeyphraseSoundModel ksm = new KeyphraseSoundModel(UUID.randomUUID(), data,
new Keyphrase[0]);
KeyphraseSoundModel ksm = new KeyphraseSoundModel(UUID.randomUUID(), UUID.randomUUID(),
data, new Keyphrase[0]);
// Write to a parcel
Parcel parcel = Parcel.obtain();
@ -187,7 +190,8 @@ public class SoundTriggerTest extends InstrumentationTestCase {
keyphrases[1] = new Keyphrase(2, 0, "fr-FR", "there", new int[] {1, 2});
byte[] data = new byte[200 * 1024];
mRandom.nextBytes(data);
KeyphraseSoundModel ksm = new KeyphraseSoundModel(UUID.randomUUID(), data, keyphrases);
KeyphraseSoundModel ksm = new KeyphraseSoundModel(UUID.randomUUID(), UUID.randomUUID(),
data, keyphrases);
// Write to a parcel
Parcel parcel = Parcel.obtain();
@ -207,7 +211,7 @@ public class SoundTriggerTest extends InstrumentationTestCase {
@SmallTest
public void testRecognitionEventParcelUnparcel_noData() throws Exception {
RecognitionEvent re = new RecognitionEvent(SoundTrigger.RECOGNITION_STATUS_SUCCESS, 1,
true, 2, 3, 4, null);
true, 2, 3, 4, false, null, null);
// Write to a parcel
Parcel parcel = Parcel.obtain();
@ -224,7 +228,7 @@ public class SoundTriggerTest extends InstrumentationTestCase {
@SmallTest
public void testRecognitionEventParcelUnparcel_zeroData() throws Exception {
RecognitionEvent re = new RecognitionEvent(SoundTrigger.RECOGNITION_STATUS_FAILURE, 1,
true, 2, 3, 4, new byte[1]);
true, 2, 3, 4, false, null, new byte[1]);
// Write to a parcel
Parcel parcel = Parcel.obtain();
@ -243,7 +247,32 @@ public class SoundTriggerTest extends InstrumentationTestCase {
byte[] data = new byte[200 * 1024];
mRandom.nextBytes(data);
RecognitionEvent re = new RecognitionEvent(SoundTrigger.RECOGNITION_STATUS_ABORT, 1,
false, 2, 3, 4, data);
false, 2, 3, 4, false, null, data);
// Write to a parcel
Parcel parcel = Parcel.obtain();
re.writeToParcel(parcel, 0);
// Read from it
parcel.setDataPosition(0);
RecognitionEvent unparceled = RecognitionEvent.CREATOR.createFromParcel(parcel);
// Verify that they are the same
assertEquals(re, unparceled);
}
@SmallTest
public void testRecognitionEventParcelUnparcel_largeAudioData() throws Exception {
byte[] data = new byte[200 * 1024];
mRandom.nextBytes(data);
RecognitionEvent re = new RecognitionEvent(SoundTrigger.RECOGNITION_STATUS_ABORT, 1,
false, 2, 3, 4, true,
(new AudioFormat.Builder())
.setChannelMask(AudioFormat.CHANNEL_IN_MONO)
.setEncoding(AudioFormat.ENCODING_PCM_16BIT)
.setSampleRate(16000)
.build(),
data);
// Write to a parcel
Parcel parcel = Parcel.obtain();
@ -260,7 +289,7 @@ public class SoundTriggerTest extends InstrumentationTestCase {
@SmallTest
public void testKeyphraseRecognitionEventParcelUnparcel_noKeyphrases() throws Exception {
KeyphraseRecognitionEvent re = new KeyphraseRecognitionEvent(
SoundTrigger.RECOGNITION_STATUS_SUCCESS, 1, true, 2, 3, 4, null, false, null);
SoundTrigger.RECOGNITION_STATUS_SUCCESS, 1, true, 2, 3, 4, false, null, null, null);
// Write to a parcel
Parcel parcel = Parcel.obtain();
@ -279,8 +308,8 @@ public class SoundTriggerTest extends InstrumentationTestCase {
public void testKeyphraseRecognitionEventParcelUnparcel_zeroData() throws Exception {
KeyphraseRecognitionExtra[] kpExtra = new KeyphraseRecognitionExtra[0];
KeyphraseRecognitionEvent re = new KeyphraseRecognitionEvent(
SoundTrigger.RECOGNITION_STATUS_FAILURE, 2, true, 2, 3, 4, new byte[1],
true, kpExtra);
SoundTrigger.RECOGNITION_STATUS_FAILURE, 2, true, 2, 3, 4, false, null, new byte[1],
kpExtra);
// Write to a parcel
Parcel parcel = Parcel.obtain();
@ -303,20 +332,20 @@ public class SoundTriggerTest extends InstrumentationTestCase {
ConfidenceLevel cl1 = new ConfidenceLevel(1, 90);
ConfidenceLevel cl2 = new ConfidenceLevel(2, 30);
kpExtra[0] = new KeyphraseRecognitionExtra(1,
SoundTrigger.RECOGNITION_MODE_USER_IDENTIFICATION,
SoundTrigger.RECOGNITION_MODE_USER_IDENTIFICATION, 0,
new ConfidenceLevel[] {cl1, cl2});
kpExtra[1] = new KeyphraseRecognitionExtra(1,
SoundTrigger.RECOGNITION_MODE_VOICE_TRIGGER,
SoundTrigger.RECOGNITION_MODE_VOICE_TRIGGER, 0,
new ConfidenceLevel[] {cl2});
kpExtra[2] = new KeyphraseRecognitionExtra(1,
SoundTrigger.RECOGNITION_MODE_VOICE_TRIGGER, null);
SoundTrigger.RECOGNITION_MODE_VOICE_TRIGGER, 0, null);
kpExtra[3] = new KeyphraseRecognitionExtra(1,
SoundTrigger.RECOGNITION_MODE_VOICE_TRIGGER,
SoundTrigger.RECOGNITION_MODE_VOICE_TRIGGER, 0,
new ConfidenceLevel[0]);
KeyphraseRecognitionEvent re = new KeyphraseRecognitionEvent(
SoundTrigger.RECOGNITION_STATUS_FAILURE, 1, true, 2, 3, 4, data,
false, kpExtra);
SoundTrigger.RECOGNITION_STATUS_FAILURE, 1, true, 2, 3, 4, false, null, data,
kpExtra);
// Write to a parcel
Parcel parcel = Parcel.obtain();