SoundTrigger API improvements.
This CL implements the SoundTrigger API improvements as given in b/22860713. Only the java-level parts are implemented in this CL. Key changes include: * Addition of a SoundTriggerManager/SoundTriggerDetector system API to manage the sound-trigger based sound models. * Addition of a SoundTriggerService service that manages all sound models including voice (keyphrase) and sound-trigger based models. * Includes logic to write sound-trigger based models to the database. * VoiceInteractionManager service now uses SoundTriggerService instead of SoundTriggerHelper. Bug: 22860713 Change-Id: I7b5c0ed80702527c4460372efeb5e542d3693a69
This commit is contained in:
@@ -288,6 +288,7 @@ LOCAL_SRC_FILES += \
|
||||
core/java/com/android/internal/app/IBatteryStats.aidl \
|
||||
core/java/com/android/internal/app/IEphemeralResolver.aidl \
|
||||
core/java/com/android/internal/app/IProcessStats.aidl \
|
||||
core/java/com/android/internal/app/ISoundTriggerService.aidl \
|
||||
core/java/com/android/internal/app/IVoiceInteractionManagerService.aidl \
|
||||
core/java/com/android/internal/app/IVoiceInteractionSessionShowCallback.aidl \
|
||||
core/java/com/android/internal/app/IVoiceInteractor.aidl \
|
||||
|
||||
@@ -17,6 +17,7 @@
|
||||
package android.app;
|
||||
|
||||
import com.android.internal.app.IAppOpsService;
|
||||
import com.android.internal.app.ISoundTriggerService;
|
||||
import com.android.internal.appwidget.IAppWidgetService;
|
||||
import com.android.internal.os.IDropBoxManagerService;
|
||||
|
||||
@@ -61,6 +62,7 @@ import android.media.midi.IMidiManager;
|
||||
import android.media.midi.MidiManager;
|
||||
import android.media.projection.MediaProjectionManager;
|
||||
import android.media.session.MediaSessionManager;
|
||||
import android.media.soundtrigger.SoundTriggerManager;
|
||||
import android.media.tv.ITvInputManager;
|
||||
import android.media.tv.TvInputManager;
|
||||
import android.net.ConnectivityManager;
|
||||
@@ -708,12 +710,22 @@ final class SystemServiceRegistry {
|
||||
public RadioManager createService(ContextImpl ctx) {
|
||||
return new RadioManager(ctx);
|
||||
}});
|
||||
|
||||
registerService(Context.HARDWARE_PROPERTIES_SERVICE, HardwarePropertiesManager.class,
|
||||
new CachedServiceFetcher<HardwarePropertiesManager>() {
|
||||
@Override
|
||||
public HardwarePropertiesManager createService(ContextImpl ctx) {
|
||||
return new HardwarePropertiesManager();
|
||||
}});
|
||||
|
||||
registerService(Context.SOUND_TRIGGER_SERVICE, SoundTriggerManager.class,
|
||||
new CachedServiceFetcher<SoundTriggerManager>() {
|
||||
@Override
|
||||
public SoundTriggerManager createService(ContextImpl ctx) {
|
||||
IBinder b = ServiceManager.getService(Context.SOUND_TRIGGER_SERVICE);
|
||||
Log.i(TAG, "Creating new instance of SoundTriggerManager object.");
|
||||
return new SoundTriggerManager(ctx, ISoundTriggerService.Stub.asInterface(b));
|
||||
}});
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -2613,6 +2613,7 @@ public abstract class Context {
|
||||
MIDI_SERVICE,
|
||||
RADIO_SERVICE,
|
||||
HARDWARE_PROPERTIES_SERVICE,
|
||||
//@hide: SOUND_TRIGGER_SERVICE,
|
||||
})
|
||||
@Retention(RetentionPolicy.SOURCE)
|
||||
public @interface ServiceName {}
|
||||
@@ -3194,6 +3195,16 @@ public abstract class Context {
|
||||
*/
|
||||
public static final String VOICE_INTERACTION_MANAGER_SERVICE = "voiceinteraction";
|
||||
|
||||
/**
|
||||
* Use with {@link #getSystemService} to access the
|
||||
* {@link com.android.server.voiceinteraction.SoundTriggerService}.
|
||||
*
|
||||
* @hide
|
||||
* @see #getSystemService
|
||||
*/
|
||||
public static final String SOUND_TRIGGER_SERVICE = "soundtrigger";
|
||||
|
||||
|
||||
/**
|
||||
* Use with {@link #getSystemService} to retrieve an
|
||||
* {@link android.app.backup.IBackupManager IBackupManager} for communicating
|
||||
|
||||
@@ -25,9 +25,12 @@ oneway interface IRecognitionStatusCallback {
|
||||
/**
|
||||
* Called when the keyphrase is spoken.
|
||||
*
|
||||
* @param data Optional trigger audio data, if it was requested and is available.
|
||||
* @param recognitionEvent Object containing data relating to the
|
||||
* recognition event such as trigger audio data, if it was requested
|
||||
* and is available.
|
||||
*/
|
||||
void onDetected(in SoundTrigger.KeyphraseRecognitionEvent recognitionEvent);
|
||||
void onDetected(in SoundTrigger.RecognitionEvent recognitionEvent);
|
||||
|
||||
/**
|
||||
* Called when the detection fails due to an error.
|
||||
*
|
||||
@@ -42,4 +45,4 @@ oneway interface IRecognitionStatusCallback {
|
||||
* Called when the recognition is resumed after it was temporarily paused.
|
||||
*/
|
||||
void onRecognitionResumed();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -18,8 +18,11 @@ package android.hardware.soundtrigger;
|
||||
|
||||
parcelable SoundTrigger.ConfidenceLevel;
|
||||
parcelable SoundTrigger.Keyphrase;
|
||||
parcelable SoundTrigger.RecognitionEvent;
|
||||
parcelable SoundTrigger.KeyphraseRecognitionEvent;
|
||||
parcelable SoundTrigger.GenericSoundRecognitionEvent;
|
||||
parcelable SoundTrigger.KeyphraseRecognitionExtra;
|
||||
parcelable SoundTrigger.KeyphraseSoundModel;
|
||||
parcelable SoundTrigger.GenericSoundModel;
|
||||
parcelable SoundTrigger.ModuleProperties;
|
||||
parcelable SoundTrigger.RecognitionConfig;
|
||||
|
||||
@@ -195,6 +195,12 @@ public class SoundTrigger {
|
||||
/** Keyphrase sound model */
|
||||
public static final int TYPE_KEYPHRASE = 0;
|
||||
|
||||
/**
|
||||
* A generic sound model. Use this type only for non-keyphrase sound models such as
|
||||
* ones that match a particular sound pattern.
|
||||
*/
|
||||
public static final int TYPE_GENERIC_SOUND = 1;
|
||||
|
||||
/** Unique sound model identifier */
|
||||
public final UUID uuid;
|
||||
|
||||
@@ -458,6 +464,63 @@ public class SoundTrigger {
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/*****************************************************************************
|
||||
* A GenericSoundModel is a specialized {@link SoundModel} for non-voice sound
|
||||
* patterns.
|
||||
****************************************************************************/
|
||||
public static class GenericSoundModel extends SoundModel implements Parcelable {
|
||||
|
||||
public static final Parcelable.Creator<GenericSoundModel> CREATOR
|
||||
= new Parcelable.Creator<GenericSoundModel>() {
|
||||
public GenericSoundModel createFromParcel(Parcel in) {
|
||||
return GenericSoundModel.fromParcel(in);
|
||||
}
|
||||
|
||||
public GenericSoundModel[] newArray(int size) {
|
||||
return new GenericSoundModel[size];
|
||||
}
|
||||
};
|
||||
|
||||
public GenericSoundModel(UUID uuid, UUID vendorUuid, byte[] data) {
|
||||
super(uuid, vendorUuid, TYPE_GENERIC_SOUND, data);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int describeContents() {
|
||||
return 0;
|
||||
}
|
||||
|
||||
private static GenericSoundModel 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();
|
||||
return new GenericSoundModel(uuid, vendorUuid, data);
|
||||
}
|
||||
|
||||
@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);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "GenericSoundModel [uuid=" + uuid + ", vendorUuid=" + vendorUuid
|
||||
+ ", type=" + type + ", data=" + (data == null ? 0 : data.length) + "]";
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Modes for key phrase recognition
|
||||
*/
|
||||
@@ -1018,6 +1081,21 @@ public class SoundTrigger {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Sub-class of RecognitionEvent specifically for sound-trigger based sound
|
||||
* models(non-keyphrase). Currently does not contain any additional fields.
|
||||
*/
|
||||
public static class GenericRecognitionEvent extends RecognitionEvent {
|
||||
public GenericRecognitionEvent(int status, int soundModelHandle,
|
||||
boolean captureAvailable, int captureSession, int captureDelayMs,
|
||||
int capturePreambleMs, boolean triggerInData, AudioFormat captureFormat,
|
||||
byte[] data) {
|
||||
super(status, soundModelHandle, captureAvailable, captureSession,
|
||||
captureDelayMs, capturePreambleMs, triggerInData, captureFormat,
|
||||
data);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Status codes for {@link SoundModelEvent}
|
||||
*/
|
||||
@@ -1118,7 +1196,7 @@ public class SoundTrigger {
|
||||
public static final int SERVICE_STATE_DISABLED = 1;
|
||||
|
||||
/**
|
||||
* Returns a list of descriptors for all harware modules loaded.
|
||||
* Returns a list of descriptors for all hardware modules loaded.
|
||||
* @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
|
||||
|
||||
@@ -31,6 +31,7 @@ import android.hardware.soundtrigger.SoundTrigger.KeyphraseRecognitionExtra;
|
||||
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.media.AudioFormat;
|
||||
import android.os.AsyncTask;
|
||||
import android.os.Handler;
|
||||
@@ -616,7 +617,11 @@ public class AlwaysOnHotwordDetector {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onDetected(KeyphraseRecognitionEvent event) {
|
||||
public void onDetected(RecognitionEvent event) {
|
||||
if (! (event instanceof KeyphraseRecognitionEvent)) {
|
||||
Slog.e(TAG, "onDetected() called for a soundtrigger event.");
|
||||
return;
|
||||
}
|
||||
if (DBG) {
|
||||
Slog.d(TAG, "onDetected(" + event + ")");
|
||||
} else {
|
||||
|
||||
42
core/java/com/android/internal/app/ISoundTriggerService.aidl
Normal file
42
core/java/com/android/internal/app/ISoundTriggerService.aidl
Normal file
@@ -0,0 +1,42 @@
|
||||
/*
|
||||
* Copyright (C) 2014 The Android Open Source Project
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package com.android.internal.app;
|
||||
|
||||
import android.hardware.soundtrigger.IRecognitionStatusCallback;
|
||||
import android.hardware.soundtrigger.SoundTrigger;
|
||||
import android.os.ParcelUuid;
|
||||
|
||||
/**
|
||||
* Service interface for a generic sound recognition model.
|
||||
* @hide
|
||||
*/
|
||||
interface ISoundTriggerService {
|
||||
|
||||
|
||||
SoundTrigger.GenericSoundModel getSoundModel(in ParcelUuid soundModelId);
|
||||
|
||||
void updateSoundModel(in SoundTrigger.GenericSoundModel soundModel);
|
||||
|
||||
void deleteSoundModel(in ParcelUuid soundModelId);
|
||||
|
||||
void startRecognition(in ParcelUuid soundModelId, in IRecognitionStatusCallback callback);
|
||||
|
||||
/**
|
||||
* Stops recognition.
|
||||
*/
|
||||
void stopRecognition(in ParcelUuid soundModelId, in IRecognitionStatusCallback callback);
|
||||
}
|
||||
@@ -2860,6 +2860,12 @@
|
||||
<permission android:name="android.permission.RECEIVE_MEDIA_RESOURCE_USAGE"
|
||||
android:protectionLevel="signature|privileged" />
|
||||
|
||||
<!-- Must be required by system/priv apps when accessing the sound trigger
|
||||
APIs given by {@link SoundTriggerManager}.
|
||||
@hide <p>Not for use by third-party applications.</p> -->
|
||||
<permission android:name="android.permission.MANAGE_SOUND_TRIGGER"
|
||||
android:protectionLevel="signature|privileged" />
|
||||
|
||||
<application android:process="system"
|
||||
android:persistent="true"
|
||||
android:hasCode="false"
|
||||
|
||||
180
media/java/android/media/soundtrigger/SoundTriggerDetector.java
Normal file
180
media/java/android/media/soundtrigger/SoundTriggerDetector.java
Normal file
@@ -0,0 +1,180 @@
|
||||
/**
|
||||
* Copyright (C) 2014 The Android Open Source Project
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package android.media.soundtrigger;
|
||||
|
||||
import android.annotation.NonNull;
|
||||
import android.annotation.Nullable;
|
||||
import android.annotation.SystemApi;
|
||||
import android.hardware.soundtrigger.IRecognitionStatusCallback;
|
||||
import android.hardware.soundtrigger.SoundTrigger;
|
||||
import android.os.Handler;
|
||||
import android.os.ParcelUuid;
|
||||
import android.os.RemoteException;
|
||||
import android.util.Slog;
|
||||
|
||||
import com.android.internal.app.ISoundTriggerService;
|
||||
|
||||
import java.io.PrintWriter;
|
||||
import java.util.UUID;
|
||||
|
||||
/**
|
||||
* A class that allows interaction with the actual sound trigger detection on the system.
|
||||
* Sound trigger detection refers to a detectors that match generic sound patterns that are
|
||||
* not voice-based. The voice-based recognition models should utilize the {@link
|
||||
* VoiceInteractionService} instead. Access to this class is protected by a permission
|
||||
* granted only to system or privileged apps.
|
||||
*
|
||||
* @hide
|
||||
*/
|
||||
public final class SoundTriggerDetector {
|
||||
private static final boolean DBG = false;
|
||||
private static final String TAG = "SoundTriggerDetector";
|
||||
|
||||
private final Object mLock = new Object();
|
||||
|
||||
private final ISoundTriggerService mSoundTriggerService;
|
||||
private final UUID mSoundModelId;
|
||||
private final Callback mCallback;
|
||||
private final Handler mHandler;
|
||||
private final RecognitionCallback mRecognitionCallback;
|
||||
|
||||
public abstract class Callback {
|
||||
/**
|
||||
* Called when the availability of the sound model changes.
|
||||
*/
|
||||
public abstract void onAvailabilityChanged(int status);
|
||||
|
||||
/**
|
||||
* Called when the sound model has triggered (such as when it matched a
|
||||
* given sound pattern).
|
||||
*/
|
||||
public abstract void onDetected();
|
||||
|
||||
/**
|
||||
* Called when the detection fails due to an error.
|
||||
*/
|
||||
public abstract void onError();
|
||||
|
||||
/**
|
||||
* Called when the recognition is paused temporarily for some reason.
|
||||
* This is an informational callback, and the clients shouldn't be doing anything here
|
||||
* except showing an indication on their UI if they have to.
|
||||
*/
|
||||
public abstract void onRecognitionPaused();
|
||||
|
||||
/**
|
||||
* Called when the recognition is resumed after it was temporarily paused.
|
||||
* This is an informational callback, and the clients shouldn't be doing anything here
|
||||
* except showing an indication on their UI if they have to.
|
||||
*/
|
||||
public abstract void onRecognitionResumed();
|
||||
}
|
||||
|
||||
/**
|
||||
* This class should be constructed by the {@link SoundTriggerManager}.
|
||||
* @hide
|
||||
*/
|
||||
SoundTriggerDetector(ISoundTriggerService soundTriggerService, UUID soundModelId,
|
||||
@NonNull Callback callback, @Nullable Handler handler) {
|
||||
mSoundTriggerService = soundTriggerService;
|
||||
mSoundModelId = soundModelId;
|
||||
mCallback = callback;
|
||||
if (handler == null) {
|
||||
mHandler = new Handler();
|
||||
} else {
|
||||
mHandler = handler;
|
||||
}
|
||||
mRecognitionCallback = new RecognitionCallback();
|
||||
}
|
||||
|
||||
/**
|
||||
* Starts recognition on the associated sound model. Result is indicated via the
|
||||
* {@link Callback}.
|
||||
* @return Indicates whether the call succeeded or not.
|
||||
*/
|
||||
public boolean startRecognition() {
|
||||
if (DBG) {
|
||||
Slog.d(TAG, "startRecognition()");
|
||||
}
|
||||
try {
|
||||
mSoundTriggerService.startRecognition(new ParcelUuid(mSoundModelId),
|
||||
mRecognitionCallback);
|
||||
} catch (RemoteException e) {
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Stops recognition for the associated model.
|
||||
*/
|
||||
public boolean stopRecognition() {
|
||||
try {
|
||||
mSoundTriggerService.stopRecognition(new ParcelUuid(mSoundModelId),
|
||||
mRecognitionCallback);
|
||||
} catch (RemoteException e) {
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
public void dump(String prefix, PrintWriter pw) {
|
||||
synchronized (mLock) {
|
||||
// TODO: Dump useful debug information.
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Callback that handles events from the lower sound trigger layer.
|
||||
* @hide
|
||||
*/
|
||||
private static class RecognitionCallback extends
|
||||
IRecognitionStatusCallback.Stub {
|
||||
|
||||
/**
|
||||
* @hide
|
||||
*/
|
||||
@Override
|
||||
public void onDetected(SoundTrigger.RecognitionEvent event) {
|
||||
Slog.e(TAG, "onDetected()" + event);
|
||||
}
|
||||
|
||||
/**
|
||||
* @hide
|
||||
*/
|
||||
@Override
|
||||
public void onError(int status) {
|
||||
Slog.e(TAG, "onError()" + status);
|
||||
}
|
||||
|
||||
/**
|
||||
* @hide
|
||||
*/
|
||||
@Override
|
||||
public void onRecognitionPaused() {
|
||||
Slog.e(TAG, "onRecognitionPaused()");
|
||||
}
|
||||
|
||||
/**
|
||||
* @hide
|
||||
*/
|
||||
@Override
|
||||
public void onRecognitionResumed() {
|
||||
Slog.e(TAG, "onRecognitionResumed()");
|
||||
}
|
||||
}
|
||||
}
|
||||
172
media/java/android/media/soundtrigger/SoundTriggerManager.java
Normal file
172
media/java/android/media/soundtrigger/SoundTriggerManager.java
Normal file
@@ -0,0 +1,172 @@
|
||||
/**
|
||||
* Copyright (C) 2014 The Android Open Source Project
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package android.media.soundtrigger;
|
||||
|
||||
import android.annotation.NonNull;
|
||||
import android.annotation.Nullable;
|
||||
import android.annotation.SystemApi;
|
||||
import android.content.Context;
|
||||
import android.hardware.soundtrigger.SoundTrigger;
|
||||
import android.os.Handler;
|
||||
import android.os.ParcelUuid;
|
||||
import android.os.RemoteException;
|
||||
import android.util.Slog;
|
||||
|
||||
import com.android.internal.app.ISoundTriggerService;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.UUID;
|
||||
|
||||
/**
|
||||
* This class provides management of non-voice (general sound trigger) based sound recognition
|
||||
* models. Usage of this class is restricted to system or signature applications only. This allows
|
||||
* OEMs to write apps that can manage non-voice based sound trigger models.
|
||||
*
|
||||
* @hide
|
||||
* TODO: Mark this as a SystemApi and get approval.
|
||||
*/
|
||||
public final class SoundTriggerManager {
|
||||
private static final boolean DBG = false;
|
||||
private static final String TAG = "SoundTriggerManager";
|
||||
|
||||
private final Context mContext;
|
||||
private final ISoundTriggerService mSoundTriggerService;
|
||||
|
||||
// Stores a mapping from the sound model UUID to the SoundTriggerInstance created by
|
||||
// the createSoundTriggerDetector() call.
|
||||
private final HashMap<UUID, SoundTriggerDetector> mReceiverInstanceMap;
|
||||
|
||||
/**
|
||||
* @hide
|
||||
*/
|
||||
public SoundTriggerManager(Context context, ISoundTriggerService soundTriggerService ) {
|
||||
if (DBG) {
|
||||
Slog.i(TAG, "SoundTriggerManager created.");
|
||||
}
|
||||
mSoundTriggerService = soundTriggerService;
|
||||
mContext = context;
|
||||
mReceiverInstanceMap = new HashMap<UUID, SoundTriggerDetector>();
|
||||
}
|
||||
|
||||
/**
|
||||
* Updates the given sound trigger model.
|
||||
*/
|
||||
public void updateModel(Model model) {
|
||||
try {
|
||||
mSoundTriggerService.updateSoundModel(model.getGenericSoundModel());
|
||||
} catch (RemoteException e) {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the sound trigger model represented by the given UUID. An instance of {@link Model}
|
||||
* is returned.
|
||||
*/
|
||||
public Model getModel(UUID soundModelId) {
|
||||
try {
|
||||
return new Model(mSoundTriggerService.getSoundModel(
|
||||
new ParcelUuid(soundModelId)));
|
||||
} catch (RemoteException e) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Deletes the sound model represented by the provided UUID.
|
||||
*/
|
||||
public void deleteModel(UUID soundModelId) {
|
||||
try {
|
||||
mSoundTriggerService.deleteSoundModel(new ParcelUuid(soundModelId));
|
||||
} catch (RemoteException e) {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates an instance of {@link SoundTriggerDetector} which can be used to start/stop
|
||||
* recognition on the model and register for triggers from the model. Note that this call
|
||||
* invalidates any previously returned instances for the same sound model Uuid.
|
||||
*
|
||||
* @param soundModelId UUID of the sound model to create the receiver object for.
|
||||
* @param callback Instance of the {@link SoundTriggerDetector#Callback} object for the
|
||||
* callbacks for the given sound model.
|
||||
* @param handler The Handler to use for the callback operations. A null value will use the
|
||||
* current thread's Looper.
|
||||
* @return Instance of {@link SoundTriggerDetector} or null on error.
|
||||
*/
|
||||
@Nullable
|
||||
public SoundTriggerDetector createSoundTriggerDetector(UUID soundModelId,
|
||||
@NonNull SoundTriggerDetector.Callback callback, @Nullable Handler handler) {
|
||||
if (soundModelId == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
SoundTriggerDetector oldInstance = mReceiverInstanceMap.get(soundModelId);
|
||||
if (oldInstance != null) {
|
||||
// Shutdown old instance.
|
||||
}
|
||||
SoundTriggerDetector newInstance = new SoundTriggerDetector(mSoundTriggerService,
|
||||
soundModelId, callback, handler);
|
||||
mReceiverInstanceMap.put(soundModelId, newInstance);
|
||||
return newInstance;
|
||||
}
|
||||
|
||||
/**
|
||||
* Class captures the data and fields that represent a non-keyphrase sound model. Use the
|
||||
* factory constructor {@link Model#create()} to create an instance.
|
||||
*/
|
||||
// We use encapsulation to expose the SoundTrigger.GenericSoundModel as a SystemApi. This
|
||||
// prevents us from exposing SoundTrigger.GenericSoundModel as an Api.
|
||||
public static class Model {
|
||||
|
||||
private SoundTrigger.GenericSoundModel mGenericSoundModel;
|
||||
|
||||
/**
|
||||
* @hide
|
||||
*/
|
||||
Model(SoundTrigger.GenericSoundModel soundTriggerModel) {
|
||||
mGenericSoundModel = soundTriggerModel;
|
||||
}
|
||||
|
||||
/**
|
||||
* Factory constructor to create a SoundModel instance for use with methods in this
|
||||
* class.
|
||||
*/
|
||||
public static Model create(UUID modelUuid, UUID vendorUuid, byte[] data) {
|
||||
return new Model(new SoundTrigger.GenericSoundModel(modelUuid,
|
||||
vendorUuid, data));
|
||||
}
|
||||
|
||||
public UUID getModelUuid() {
|
||||
return mGenericSoundModel.uuid;
|
||||
}
|
||||
|
||||
public UUID getVendorUuid() {
|
||||
return mGenericSoundModel.vendorUuid;
|
||||
}
|
||||
|
||||
public byte[] getModelData() {
|
||||
return mGenericSoundModel.data;
|
||||
}
|
||||
|
||||
/**
|
||||
* @hide
|
||||
*/
|
||||
SoundTrigger.GenericSoundModel getGenericSoundModel() {
|
||||
return mGenericSoundModel;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -84,6 +84,7 @@ import com.android.server.pm.UserManagerService;
|
||||
import com.android.server.power.PowerManagerService;
|
||||
import com.android.server.power.ShutdownThread;
|
||||
import com.android.server.restrictions.RestrictionsManagerService;
|
||||
import com.android.server.soundtrigger.SoundTriggerService;
|
||||
import com.android.server.statusbar.StatusBarManagerService;
|
||||
import com.android.server.storage.DeviceStorageMonitorService;
|
||||
import com.android.server.telecom.TelecomLoaderService;
|
||||
@@ -959,6 +960,8 @@ public final class SystemServer {
|
||||
|
||||
mSystemServiceManager.startService(JobSchedulerService.class);
|
||||
|
||||
mSystemServiceManager.startService(SoundTriggerService.class);
|
||||
|
||||
if (!disableNonCoreServices) {
|
||||
if (mPackageManager.hasSystemFeature(PackageManager.FEATURE_BACKUP)) {
|
||||
mSystemServiceManager.startService(BACKUP_MANAGER_SERVICE_CLASS);
|
||||
|
||||
@@ -0,0 +1,144 @@
|
||||
/**
|
||||
* Copyright (C) 2014 The Android Open Source Project
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package com.android.server.soundtrigger;
|
||||
|
||||
import android.content.ContentValues;
|
||||
import android.content.Context;
|
||||
import android.database.Cursor;
|
||||
import android.database.sqlite.SQLiteDatabase;
|
||||
import android.database.sqlite.SQLiteOpenHelper;
|
||||
import android.hardware.soundtrigger.SoundTrigger;
|
||||
import android.hardware.soundtrigger.SoundTrigger.GenericSoundModel;
|
||||
import android.text.TextUtils;
|
||||
import android.util.Slog;
|
||||
|
||||
import java.util.Locale;
|
||||
import java.util.UUID;
|
||||
|
||||
/**
|
||||
* Helper to manage the database of the sound models that have been registered on the device.
|
||||
*
|
||||
* @hide
|
||||
*/
|
||||
public class SoundTriggerDbHelper extends SQLiteOpenHelper {
|
||||
static final String TAG = "SoundTriggerDbHelper";
|
||||
static final boolean DBG = false;
|
||||
|
||||
private static final String NAME = "st_sound_model.db";
|
||||
private static final int VERSION = 1;
|
||||
|
||||
// Sound trigger-based sound models.
|
||||
public static interface GenericSoundModelContract {
|
||||
public static final String TABLE = "st_sound_model";
|
||||
public static final String KEY_MODEL_UUID = "model_uuid";
|
||||
public static final String KEY_VENDOR_UUID = "vendor_uuid";
|
||||
public static final String KEY_DATA = "data";
|
||||
}
|
||||
|
||||
|
||||
// Table Create Statement for the sound trigger table
|
||||
private static final String CREATE_TABLE_ST_SOUND_MODEL = "CREATE TABLE "
|
||||
+ GenericSoundModelContract.TABLE + "("
|
||||
+ GenericSoundModelContract.KEY_MODEL_UUID + " TEXT PRIMARY KEY,"
|
||||
+ GenericSoundModelContract.KEY_DATA + " BLOB" + " )";
|
||||
|
||||
|
||||
public SoundTriggerDbHelper(Context context) {
|
||||
super(context, NAME, null, VERSION);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onCreate(SQLiteDatabase db) {
|
||||
// creating required tables
|
||||
db.execSQL(CREATE_TABLE_ST_SOUND_MODEL);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
|
||||
// TODO: For now, drop older tables and recreate new ones.
|
||||
db.execSQL("DROP TABLE IF EXISTS " + GenericSoundModelContract.TABLE);
|
||||
onCreate(db);
|
||||
}
|
||||
|
||||
/**
|
||||
* Updates the given sound trigger model, adds it, if it doesn't already exist.
|
||||
*
|
||||
*/
|
||||
public boolean updateGenericSoundModel(GenericSoundModel soundModel) {
|
||||
synchronized(this) {
|
||||
SQLiteDatabase db = getWritableDatabase();
|
||||
ContentValues values = new ContentValues();
|
||||
values.put(GenericSoundModelContract.KEY_MODEL_UUID, soundModel.uuid.toString());
|
||||
values.put(GenericSoundModelContract.KEY_VENDOR_UUID, soundModel.vendorUuid.toString());
|
||||
values.put(GenericSoundModelContract.KEY_DATA, soundModel.data);
|
||||
|
||||
try {
|
||||
return db.insertWithOnConflict(GenericSoundModelContract.TABLE, null, values,
|
||||
SQLiteDatabase.CONFLICT_REPLACE) != -1;
|
||||
} finally {
|
||||
db.close();
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
public GenericSoundModel getGenericSoundModel(UUID model_uuid) {
|
||||
synchronized(this) {
|
||||
|
||||
// Find the corresponding sound model ID for the keyphrase.
|
||||
String selectQuery = "SELECT * FROM " + GenericSoundModelContract.TABLE
|
||||
+ " WHERE " + GenericSoundModelContract.KEY_MODEL_UUID + "= '" +
|
||||
model_uuid + "'";
|
||||
SQLiteDatabase db = getReadableDatabase();
|
||||
Cursor c = db.rawQuery(selectQuery, null);
|
||||
try {
|
||||
if (c.moveToFirst()) {
|
||||
do {
|
||||
byte[] data = c.getBlob(c.getColumnIndex(
|
||||
GenericSoundModelContract.KEY_DATA));
|
||||
String vendor_uuid = c.getString(
|
||||
c.getColumnIndex(GenericSoundModelContract.KEY_VENDOR_UUID));
|
||||
return new GenericSoundModel(model_uuid, UUID.fromString(vendor_uuid),
|
||||
data);
|
||||
} while (c.moveToNext());
|
||||
}
|
||||
} finally {
|
||||
c.close();
|
||||
db.close();
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
public boolean deleteGenericSoundModel(UUID model_uuid) {
|
||||
synchronized(this) {
|
||||
GenericSoundModel soundModel = getGenericSoundModel(model_uuid);
|
||||
if (soundModel == null) {
|
||||
return false;
|
||||
}
|
||||
// Delete all sound models for the given keyphrase and specified user.
|
||||
SQLiteDatabase db = getWritableDatabase();
|
||||
String soundModelClause = GenericSoundModelContract.KEY_MODEL_UUID
|
||||
+ "='" + soundModel.uuid.toString() + "'";
|
||||
try {
|
||||
return db.delete(GenericSoundModelContract.TABLE, soundModelClause, null) != 0;
|
||||
} finally {
|
||||
db.close();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -14,7 +14,7 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package com.android.server.voiceinteraction;
|
||||
package com.android.server.soundtrigger;
|
||||
|
||||
import android.content.BroadcastReceiver;
|
||||
import android.content.Context;
|
||||
@@ -0,0 +1,84 @@
|
||||
/**
|
||||
* Copyright (C) 2014 The Android Open Source Project
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package com.android.server.soundtrigger;
|
||||
|
||||
import android.hardware.soundtrigger.IRecognitionStatusCallback;
|
||||
import android.hardware.soundtrigger.SoundTrigger;
|
||||
import android.hardware.soundtrigger.SoundTrigger.Keyphrase;
|
||||
import android.hardware.soundtrigger.SoundTrigger.KeyphraseRecognitionEvent;
|
||||
import android.hardware.soundtrigger.SoundTrigger.KeyphraseRecognitionExtra;
|
||||
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 java.io.FileDescriptor;
|
||||
import java.io.PrintWriter;
|
||||
|
||||
/**
|
||||
* Provides a local service for managing voice-related recoginition models. This is primarily used
|
||||
* by the {@link VoiceInteractionManagerService}.
|
||||
*/
|
||||
public abstract class SoundTriggerInternal {
|
||||
/**
|
||||
* Return codes for {@link #startRecognition(int, KeyphraseSoundModel,
|
||||
* IRecognitionStatusCallback, RecognitionConfig)},
|
||||
* {@link #stopRecognition(int, IRecognitionStatusCallback)}
|
||||
*/
|
||||
public static final int STATUS_ERROR = SoundTrigger.STATUS_ERROR;
|
||||
public static final int STATUS_OK = SoundTrigger.STATUS_OK;
|
||||
|
||||
/** The {@link ModuleProperties} for the system, or null if none exists. */
|
||||
private ModuleProperties moduleProperties;
|
||||
|
||||
/**
|
||||
* Starts recognition for the given keyphraseId.
|
||||
*
|
||||
* @param keyphraseId The identifier of the keyphrase for which
|
||||
* the recognition is to be started.
|
||||
* @param soundModel The sound model to use for recognition.
|
||||
* @param listener The listener for the recognition events related to the given keyphrase.
|
||||
* @return One of {@link #STATUS_ERROR} or {@link #STATUS_OK}.
|
||||
*/
|
||||
public abstract int startRecognition(int keyphraseId, KeyphraseSoundModel soundModel,
|
||||
IRecognitionStatusCallback listener, RecognitionConfig recognitionConfig);
|
||||
|
||||
/**
|
||||
* Stops recognition for the given {@link Keyphrase} if a recognition is
|
||||
* currently active.
|
||||
*
|
||||
* @param keyphraseId The identifier of the keyphrase for which
|
||||
* the recognition is to be stopped.
|
||||
* @param listener The listener for the recognition events related to the given keyphrase.
|
||||
*
|
||||
* @return One of {@link #STATUS_ERROR} or {@link #STATUS_OK}.
|
||||
*/
|
||||
public abstract int stopRecognition(int keyphraseId, IRecognitionStatusCallback listener);
|
||||
|
||||
/**
|
||||
* Stops all recognitions active currently and clears the internal state.
|
||||
*/
|
||||
public abstract void stopAllRecognitions();
|
||||
|
||||
public ModuleProperties getModuleProperties() {
|
||||
return moduleProperties;
|
||||
}
|
||||
|
||||
public abstract void dump(FileDescriptor fd, PrintWriter pw, String[] args);
|
||||
}
|
||||
@@ -0,0 +1,194 @@
|
||||
/*
|
||||
* Copyright (C) 2014 The Android Open Source Project
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package com.android.server.soundtrigger;
|
||||
|
||||
import android.content.Context;
|
||||
import android.content.pm.PackageManager;
|
||||
import android.Manifest;
|
||||
import android.hardware.soundtrigger.IRecognitionStatusCallback;
|
||||
import android.hardware.soundtrigger.SoundTrigger;
|
||||
import android.hardware.soundtrigger.SoundTrigger.KeyphraseSoundModel;
|
||||
import android.hardware.soundtrigger.SoundTrigger.RecognitionConfig;
|
||||
import android.hardware.soundtrigger.SoundTrigger.SoundTriggerModel;
|
||||
import android.os.Parcel;
|
||||
import android.os.ParcelUuid;
|
||||
import android.os.RemoteException;
|
||||
import android.util.Slog;
|
||||
|
||||
import com.android.server.SystemService;
|
||||
import com.android.internal.app.ISoundTriggerService;
|
||||
|
||||
import java.io.FileDescriptor;
|
||||
import java.io.PrintWriter;
|
||||
import java.util.UUID;
|
||||
|
||||
/**
|
||||
* A single SystemService to manage all sound/voice-based sound models on the DSP.
|
||||
* This services provides apis to manage sound trigger-based sound models via
|
||||
* the ISoundTriggerService interface. This class also publishes a local interface encapsulating
|
||||
* the functionality provided by {@link SoundTriggerHelper} for use by
|
||||
* {@link VoiceInteractionManagerService}.
|
||||
*
|
||||
* @hide
|
||||
*/
|
||||
public class SoundTriggerService extends SystemService {
|
||||
static final String TAG = "SoundTriggerService";
|
||||
static final boolean DEBUG = false;
|
||||
|
||||
final Context mContext;
|
||||
private final SoundTriggerServiceStub mServiceStub;
|
||||
private final LocalSoundTriggerService mLocalSoundTriggerService;
|
||||
private SoundTriggerDbHelper mDbHelper;
|
||||
|
||||
public SoundTriggerService(Context context) {
|
||||
super(context);
|
||||
mContext = context;
|
||||
mServiceStub = new SoundTriggerServiceStub();
|
||||
mLocalSoundTriggerService = new LocalSoundTriggerService(context);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onStart() {
|
||||
publishBinderService(Context.SOUND_TRIGGER_SERVICE, mServiceStub);
|
||||
publishLocalService(SoundTriggerInternal.class, mLocalSoundTriggerService);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onBootPhase(int phase) {
|
||||
if (PHASE_SYSTEM_SERVICES_READY == phase) {
|
||||
mLocalSoundTriggerService.initSoundTriggerHelper();
|
||||
} else if (PHASE_THIRD_PARTY_APPS_CAN_START == phase) {
|
||||
mDbHelper = new SoundTriggerDbHelper(mContext);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onStartUser(int userHandle) {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onSwitchUser(int userHandle) {
|
||||
}
|
||||
|
||||
class SoundTriggerServiceStub extends ISoundTriggerService.Stub {
|
||||
@Override
|
||||
public boolean onTransact(int code, Parcel data, Parcel reply, int flags)
|
||||
throws RemoteException {
|
||||
try {
|
||||
return super.onTransact(code, data, reply, flags);
|
||||
} catch (RuntimeException e) {
|
||||
// The activity manager only throws security exceptions, so let's
|
||||
// log all others.
|
||||
if (!(e instanceof SecurityException)) {
|
||||
Slog.wtf(TAG, "SoundTriggerService Crash", e);
|
||||
}
|
||||
throw e;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void startRecognition(ParcelUuid parcelUuid, IRecognitionStatusCallback callback) {
|
||||
enforceCallingPermission(Manifest.permission.MANAGE_SOUND_TRIGGER);
|
||||
if (DEBUG) {
|
||||
Slog.i(TAG, "startRecognition(): Uuid : " + parcelUuid);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void stopRecognition(ParcelUuid parcelUuid, IRecognitionStatusCallback callback) {
|
||||
enforceCallingPermission(Manifest.permission.MANAGE_SOUND_TRIGGER);
|
||||
if (DEBUG) {
|
||||
Slog.i(TAG, "stopRecognition(): Uuid : " + parcelUuid);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public SoundTrigger.SoundTriggerModel getSoundModel(ParcelUuid soundModelId) {
|
||||
enforceCallingPermission(Manifest.permission.MANAGE_SOUND_TRIGGER);
|
||||
if (DEBUG) {
|
||||
Slog.i(TAG, "getSoundModel(): id = " + soundModelId);
|
||||
}
|
||||
SoundTrigger.SoundTriggerModel model = mDbHelper.getSoundTriggerModel(soundModelId.getUuid());
|
||||
if (model == null) {
|
||||
Slog.e(TAG, "Null model in database.");
|
||||
}
|
||||
return model;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void updateSoundModel(SoundTrigger.SoundTriggerModel soundModel) {
|
||||
enforceCallingPermission(Manifest.permission.MANAGE_SOUND_TRIGGER);
|
||||
if (DEBUG) {
|
||||
Slog.i(TAG, "updateSoundModel(): model = " + soundModel);
|
||||
}
|
||||
mDbHelper.updateSoundTriggerModel(soundModel);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void deleteSoundModel(ParcelUuid soundModelId) {
|
||||
enforceCallingPermission(Manifest.permission.MANAGE_SOUND_TRIGGER);
|
||||
if (DEBUG) {
|
||||
Slog.i(TAG, "deleteSoundModel(): id = " + soundModelId);
|
||||
}
|
||||
mDbHelper.deleteSoundTriggerModel(soundModelId.getUuid());
|
||||
}
|
||||
}
|
||||
|
||||
public final class LocalSoundTriggerService extends SoundTriggerInternal {
|
||||
private final Context mContext;
|
||||
private SoundTriggerHelper mSoundTriggerHelper;
|
||||
|
||||
LocalSoundTriggerService(Context context) {
|
||||
mContext = context;
|
||||
}
|
||||
|
||||
void initSoundTriggerHelper() {
|
||||
if (mSoundTriggerHelper == null) {
|
||||
mSoundTriggerHelper = new SoundTriggerHelper(mContext);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public int startRecognition(int keyphraseId, KeyphraseSoundModel soundModel,
|
||||
IRecognitionStatusCallback listener, RecognitionConfig recognitionConfig) {
|
||||
return mSoundTriggerHelper.startRecognition(keyphraseId, soundModel, listener,
|
||||
recognitionConfig);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int stopRecognition(int keyphraseId, IRecognitionStatusCallback listener) {
|
||||
return mSoundTriggerHelper.stopRecognition(keyphraseId, listener);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void stopAllRecognitions() {
|
||||
mSoundTriggerHelper.stopAllRecognitions();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
|
||||
mSoundTriggerHelper.dump(fd, pw, args);
|
||||
}
|
||||
}
|
||||
|
||||
private void enforceCallingPermission(String permission) {
|
||||
if (mContext.checkCallingOrSelfPermission(permission)
|
||||
!= PackageManager.PERMISSION_GRANTED) {
|
||||
throw new SecurityException("Caller does not hold the permission " + permission);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -62,6 +62,7 @@ import com.android.internal.app.IVoiceInteractor;
|
||||
import com.android.internal.content.PackageMonitor;
|
||||
import com.android.internal.os.BackgroundThread;
|
||||
import com.android.server.LocalServices;
|
||||
import com.android.server.soundtrigger.SoundTriggerInternal;
|
||||
import com.android.server.SystemService;
|
||||
import com.android.server.UiThread;
|
||||
|
||||
@@ -79,15 +80,14 @@ public class VoiceInteractionManagerService extends SystemService {
|
||||
final Context mContext;
|
||||
final ContentResolver mResolver;
|
||||
final DatabaseHelper mDbHelper;
|
||||
final SoundTriggerHelper mSoundTriggerHelper;
|
||||
final ActivityManagerInternal mAmInternal;
|
||||
SoundTriggerInternal mSoundTriggerInternal;
|
||||
|
||||
public VoiceInteractionManagerService(Context context) {
|
||||
super(context);
|
||||
mContext = context;
|
||||
mResolver = context.getContentResolver();
|
||||
mDbHelper = new DatabaseHelper(context);
|
||||
mSoundTriggerHelper = new SoundTriggerHelper(context);
|
||||
mServiceStub = new VoiceInteractionManagerServiceStub();
|
||||
mAmInternal = LocalServices.getService(ActivityManagerInternal.class);
|
||||
|
||||
@@ -115,7 +115,9 @@ public class VoiceInteractionManagerService extends SystemService {
|
||||
|
||||
@Override
|
||||
public void onBootPhase(int phase) {
|
||||
if (phase == PHASE_THIRD_PARTY_APPS_CAN_START) {
|
||||
if (PHASE_SYSTEM_SERVICES_READY == phase) {
|
||||
mSoundTriggerInternal = LocalServices.getService(SoundTriggerInternal.class);
|
||||
} else if (phase == PHASE_THIRD_PARTY_APPS_CAN_START) {
|
||||
mServiceStub.systemRunning(isSafeMode());
|
||||
}
|
||||
}
|
||||
@@ -380,7 +382,7 @@ public class VoiceInteractionManagerService extends SystemService {
|
||||
|
||||
if (force || mImpl == null || mImpl.mUser != mCurUser
|
||||
|| !mImpl.mComponent.equals(serviceComponent)) {
|
||||
mSoundTriggerHelper.stopAllRecognitions();
|
||||
mSoundTriggerInternal.stopAllRecognitions();
|
||||
if (mImpl != null) {
|
||||
mImpl.shutdownLocked();
|
||||
}
|
||||
@@ -736,9 +738,9 @@ public class VoiceInteractionManagerService extends SystemService {
|
||||
mImpl.notifySoundModelsChangedLocked();
|
||||
}
|
||||
}
|
||||
return SoundTriggerHelper.STATUS_OK;
|
||||
return SoundTriggerInternal.STATUS_OK;
|
||||
} else {
|
||||
return SoundTriggerHelper.STATUS_ERROR;
|
||||
return SoundTriggerInternal.STATUS_ERROR;
|
||||
}
|
||||
} finally {
|
||||
Binder.restoreCallingIdentity(caller);
|
||||
@@ -759,7 +761,7 @@ public class VoiceInteractionManagerService extends SystemService {
|
||||
boolean deleted = false;
|
||||
try {
|
||||
deleted = mDbHelper.deleteKeyphraseSoundModel(keyphraseId, callingUid, bcp47Locale);
|
||||
return deleted ? SoundTriggerHelper.STATUS_OK : SoundTriggerHelper.STATUS_ERROR;
|
||||
return deleted ? SoundTriggerInternal.STATUS_OK : SoundTriggerInternal.STATUS_ERROR;
|
||||
} finally {
|
||||
if (deleted) {
|
||||
synchronized (this) {
|
||||
@@ -812,7 +814,7 @@ public class VoiceInteractionManagerService extends SystemService {
|
||||
|
||||
final long caller = Binder.clearCallingIdentity();
|
||||
try {
|
||||
return mSoundTriggerHelper.moduleProperties;
|
||||
return mSoundTriggerInternal.getModuleProperties();
|
||||
} finally {
|
||||
Binder.restoreCallingIdentity(caller);
|
||||
}
|
||||
@@ -845,9 +847,9 @@ public class VoiceInteractionManagerService extends SystemService {
|
||||
|| soundModel.uuid == null
|
||||
|| soundModel.keyphrases == null) {
|
||||
Slog.w(TAG, "No matching sound model found in startRecognition");
|
||||
return SoundTriggerHelper.STATUS_ERROR;
|
||||
return SoundTriggerInternal.STATUS_ERROR;
|
||||
} else {
|
||||
return mSoundTriggerHelper.startRecognition(
|
||||
return mSoundTriggerInternal.startRecognition(
|
||||
keyphraseId, soundModel, callback, recognitionConfig);
|
||||
}
|
||||
} finally {
|
||||
@@ -869,7 +871,7 @@ public class VoiceInteractionManagerService extends SystemService {
|
||||
|
||||
final long caller = Binder.clearCallingIdentity();
|
||||
try {
|
||||
return mSoundTriggerHelper.stopRecognition(keyphraseId, callback);
|
||||
return mSoundTriggerInternal.stopRecognition(keyphraseId, callback);
|
||||
} finally {
|
||||
Binder.restoreCallingIdentity(caller);
|
||||
}
|
||||
@@ -1011,7 +1013,7 @@ public class VoiceInteractionManagerService extends SystemService {
|
||||
}
|
||||
mImpl.dumpLocked(fd, pw, args);
|
||||
}
|
||||
mSoundTriggerHelper.dump(fd, pw, args);
|
||||
mSoundTriggerInternal.dump(fd, pw, args);
|
||||
}
|
||||
|
||||
private void enforceCallingPermission(String permission) {
|
||||
@@ -1060,7 +1062,7 @@ public class VoiceInteractionManagerService extends SystemService {
|
||||
// The user is force stopping our current interactor/recognizer.
|
||||
// Clear the current settings and restore default state.
|
||||
synchronized (VoiceInteractionManagerService.this) {
|
||||
mSoundTriggerHelper.stopAllRecognitions();
|
||||
mSoundTriggerInternal.stopAllRecognitions();
|
||||
if (mImpl != null) {
|
||||
mImpl.shutdownLocked();
|
||||
mImpl = null;
|
||||
|
||||
12
tests/SoundTriggerTestApp/Android.mk
Normal file
12
tests/SoundTriggerTestApp/Android.mk
Normal file
@@ -0,0 +1,12 @@
|
||||
LOCAL_PATH:= $(call my-dir)
|
||||
include $(CLEAR_VARS)
|
||||
|
||||
LOCAL_SRC_FILES := $(call all-subdir-java-files)
|
||||
|
||||
LOCAL_PACKAGE_NAME := SoundTriggerTestApp
|
||||
|
||||
LOCAL_MODULE_TAGS := optional
|
||||
|
||||
LOCAL_PRIVILEGED_MODULE := true
|
||||
|
||||
include $(BUILD_PACKAGE)
|
||||
17
tests/SoundTriggerTestApp/AndroidManifest.xml
Normal file
17
tests/SoundTriggerTestApp/AndroidManifest.xml
Normal file
@@ -0,0 +1,17 @@
|
||||
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
package="com.android.test.soundtrigger">
|
||||
|
||||
<uses-permission android:name="android.permission.MANAGE_SOUND_TRIGGER" />
|
||||
<application
|
||||
android:permission="android.permission.MANAGE_SOUND_TRIGGER">
|
||||
<activity
|
||||
android:name="TestSoundTriggerActivity"
|
||||
android:label="SoundTrigger Test Application"
|
||||
android:theme="@android:style/Theme.Material.Light.Voice">
|
||||
<intent-filter>
|
||||
<action android:name="com.android.intent.action.MANAGE_SOUND_TRIGGER" />
|
||||
<category android:name="android.intent.category.DEFAULT" />
|
||||
</intent-filter>
|
||||
</activity>
|
||||
</application>
|
||||
</manifest>
|
||||
43
tests/SoundTriggerTestApp/res/layout/main.xml
Normal file
43
tests/SoundTriggerTestApp/res/layout/main.xml
Normal file
@@ -0,0 +1,43 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!--
|
||||
Copyright (C) 2014 Google Inc.
|
||||
|
||||
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.
|
||||
-->
|
||||
|
||||
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
>
|
||||
|
||||
<Button
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="@string/enroll"
|
||||
android:onClick="onEnrollButtonClicked"
|
||||
android:padding="20dp" />
|
||||
|
||||
<Button
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="@string/reenroll"
|
||||
android:onClick="onReEnrollButtonClicked"
|
||||
android:padding="20dp" />
|
||||
|
||||
<Button
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="@string/unenroll"
|
||||
android:onClick="onUnEnrollButtonClicked"
|
||||
android:padding="20dp" />
|
||||
</LinearLayout>
|
||||
22
tests/SoundTriggerTestApp/res/values/strings.xml
Normal file
22
tests/SoundTriggerTestApp/res/values/strings.xml
Normal file
@@ -0,0 +1,22 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!--
|
||||
Copyright (C) 2014 Google Inc.
|
||||
|
||||
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.
|
||||
-->
|
||||
<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
|
||||
|
||||
<string name="enroll">Enroll</string>
|
||||
<string name="reenroll">Re-enroll</string>
|
||||
<string name="unenroll">Un-enroll</string>
|
||||
</resources>
|
||||
@@ -0,0 +1,115 @@
|
||||
/*
|
||||
* Copyright (C) 2014 The Android Open Source Project
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package com.android.test.soundtrigger;
|
||||
|
||||
import android.annotation.Nullable;
|
||||
import android.content.Context;
|
||||
import android.hardware.soundtrigger.SoundTrigger;
|
||||
import android.hardware.soundtrigger.SoundTrigger.SoundTriggerModel;
|
||||
import android.media.soundtrigger.SoundTriggerManager;
|
||||
import android.os.RemoteException;
|
||||
import android.os.ServiceManager;
|
||||
import android.os.ParcelUuid;
|
||||
import android.util.Log;
|
||||
|
||||
import com.android.internal.app.ISoundTriggerService;
|
||||
|
||||
import java.util.UUID;
|
||||
|
||||
/**
|
||||
* Utility class for the managing sound trigger sound models.
|
||||
*/
|
||||
public class SoundTriggerUtil {
|
||||
private static final String TAG = "TestSoundTriggerUtil:Hotsound";
|
||||
|
||||
private final ISoundTriggerService mSoundTriggerService;
|
||||
private final SoundTriggerManager mSoundTriggerManager;
|
||||
private final Context mContext;
|
||||
|
||||
public SoundTriggerUtil(Context context) {
|
||||
mSoundTriggerService = ISoundTriggerService.Stub.asInterface(
|
||||
ServiceManager.getService(Context.SOUND_TRIGGER_SERVICE));
|
||||
mSoundTriggerManager = (SoundTriggerManager) context.getSystemService(
|
||||
Context.SOUND_TRIGGER_SERVICE);
|
||||
mContext = context;
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds/Updates a sound model.
|
||||
* The sound model must contain a valid UUID.
|
||||
*
|
||||
* @param soundModel The sound model to add/update.
|
||||
*/
|
||||
public boolean addOrUpdateSoundModel(SoundTriggerModel soundModel) {
|
||||
try {
|
||||
mSoundTriggerService.updateSoundModel(soundModel);
|
||||
} catch (RemoteException e) {
|
||||
Log.e(TAG, "RemoteException in updateSoundModel", e);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
public void addOrUpdateSoundModel(SoundTriggerManager.Model soundModel) {
|
||||
mSoundTriggerManager.updateModel(soundModel);
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the sound model for the given keyphrase, null if none exists.
|
||||
* If a sound model for a given keyphrase exists, and it needs to be updated,
|
||||
* it should be obtained using this method, updated and then passed in to
|
||||
* {@link #addOrUpdateSoundModel(SoundTriggerModel)} without changing the IDs.
|
||||
*
|
||||
* @param modelId The model ID to look-up the sound model for.
|
||||
* @return The sound model if one was found, null otherwise.
|
||||
*/
|
||||
@Nullable
|
||||
public SoundTriggerModel getSoundModel(UUID modelId) {
|
||||
SoundTriggerModel model = null;
|
||||
try {
|
||||
model = mSoundTriggerService.getSoundModel(new ParcelUuid(modelId));
|
||||
} catch (RemoteException e) {
|
||||
Log.e(TAG, "RemoteException in updateKeyphraseSoundModel");
|
||||
}
|
||||
|
||||
if (model == null) {
|
||||
Log.w(TAG, "No models present for the gien keyphrase ID");
|
||||
return null;
|
||||
} else {
|
||||
return model;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Deletes the sound model for the given keyphrase id.
|
||||
*
|
||||
* @param modelId The model ID to look-up the sound model for.
|
||||
* @return {@code true} if the call succeeds, {@code false} otherwise.
|
||||
*/
|
||||
@Nullable
|
||||
public boolean deleteSoundModel(UUID modelId) {
|
||||
try {
|
||||
mSoundTriggerService.deleteSoundModel(new ParcelUuid(modelId));
|
||||
} catch (RemoteException e) {
|
||||
Log.e(TAG, "RemoteException in updateSoundModel");
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
public void deleteSoundModelUsingManager(UUID modelId) {
|
||||
mSoundTriggerManager.deleteModel(modelId);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,121 @@
|
||||
/*
|
||||
* Copyright (C) 2014 The Android Open Source Project
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package com.android.test.soundtrigger;
|
||||
|
||||
import java.util.Random;
|
||||
import java.util.UUID;
|
||||
|
||||
import android.app.Activity;
|
||||
import android.hardware.soundtrigger.SoundTrigger;
|
||||
import android.hardware.soundtrigger.SoundTrigger.SoundTriggerModel;
|
||||
import android.media.soundtrigger.SoundTriggerManager;
|
||||
import android.os.Bundle;
|
||||
import android.os.UserManager;
|
||||
import android.util.Log;
|
||||
import android.view.View;
|
||||
import android.widget.Toast;
|
||||
|
||||
public class TestSoundTriggerActivity extends Activity {
|
||||
private static final String TAG = "TestSoundTriggerActivity";
|
||||
private static final boolean DBG = true;
|
||||
|
||||
private SoundTriggerUtil mSoundTriggerUtil;
|
||||
private Random mRandom;
|
||||
private UUID mModelUuid = UUID.randomUUID();
|
||||
private UUID mModelUuid2 = UUID.randomUUID();
|
||||
private UUID mVendorUuid = UUID.randomUUID();
|
||||
|
||||
@Override
|
||||
protected void onCreate(Bundle savedInstanceState) {
|
||||
if (DBG) Log.d(TAG, "onCreate");
|
||||
super.onCreate(savedInstanceState);
|
||||
setContentView(R.layout.main);
|
||||
mSoundTriggerUtil = new SoundTriggerUtil(this);
|
||||
mRandom = new Random();
|
||||
}
|
||||
|
||||
/**
|
||||
* Called when the user clicks the enroll button.
|
||||
* Performs a fresh enrollment.
|
||||
*/
|
||||
public void onEnrollButtonClicked(View v) {
|
||||
// Generate a fake model to push.
|
||||
byte[] data = new byte[1024];
|
||||
mRandom.nextBytes(data);
|
||||
SoundTriggerModel model = new SoundTriggerModel(mModelUuid, mVendorUuid, data);
|
||||
|
||||
boolean status = mSoundTriggerUtil.addOrUpdateSoundModel(model);
|
||||
if (status) {
|
||||
Toast.makeText(
|
||||
this, "Successfully created sound trigger model UUID=" + mModelUuid, Toast.LENGTH_SHORT)
|
||||
.show();
|
||||
} else {
|
||||
Toast.makeText(this, "Failed to enroll!!!" + mModelUuid, Toast.LENGTH_SHORT).show();
|
||||
}
|
||||
|
||||
// Test the SoundManager API.
|
||||
SoundTriggerManager.Model tmpModel = SoundTriggerManager.Model.create(mModelUuid2,
|
||||
mVendorUuid, data);
|
||||
mSoundTriggerUtil.addOrUpdateSoundModel(tmpModel);
|
||||
}
|
||||
|
||||
/**
|
||||
* Called when the user clicks the un-enroll button.
|
||||
* Clears the enrollment information for the user.
|
||||
*/
|
||||
public void onUnEnrollButtonClicked(View v) {
|
||||
SoundTriggerModel soundModel = mSoundTriggerUtil.getSoundModel(mModelUuid);
|
||||
if (soundModel == null) {
|
||||
Toast.makeText(this, "Sound model not found!!!", Toast.LENGTH_SHORT).show();
|
||||
return;
|
||||
}
|
||||
boolean status = mSoundTriggerUtil.deleteSoundModel(mModelUuid);
|
||||
if (status) {
|
||||
Toast.makeText(this, "Successfully deleted model UUID=" + soundModel.uuid,
|
||||
Toast.LENGTH_SHORT)
|
||||
.show();
|
||||
} else {
|
||||
Toast.makeText(this, "Failed to delete sound model!!!", Toast.LENGTH_SHORT).show();
|
||||
}
|
||||
mSoundTriggerUtil.deleteSoundModelUsingManager(mModelUuid2);
|
||||
}
|
||||
|
||||
/**
|
||||
* Called when the user clicks the re-enroll button.
|
||||
* Uses the previously enrolled sound model and makes changes to it before pushing it back.
|
||||
*/
|
||||
public void onReEnrollButtonClicked(View v) {
|
||||
SoundTriggerModel soundModel = mSoundTriggerUtil.getSoundModel(mModelUuid);
|
||||
if (soundModel == null) {
|
||||
Toast.makeText(this, "Sound model not found!!!", Toast.LENGTH_SHORT).show();
|
||||
return;
|
||||
}
|
||||
// Generate a fake model to push.
|
||||
byte[] data = new byte[2048];
|
||||
mRandom.nextBytes(data);
|
||||
SoundTriggerModel updated = new SoundTriggerModel(soundModel.uuid,
|
||||
soundModel.vendorUuid, data);
|
||||
boolean status = mSoundTriggerUtil.addOrUpdateSoundModel(updated);
|
||||
if (status) {
|
||||
Toast.makeText(this, "Successfully re-enrolled, model UUID=" + updated.uuid,
|
||||
Toast.LENGTH_SHORT)
|
||||
.show();
|
||||
} else {
|
||||
Toast.makeText(this, "Failed to re-enroll!!!", Toast.LENGTH_SHORT).show();
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user