summaryrefslogtreecommitdiffstats
path: root/services
diff options
context:
space:
mode:
authorSandeep Siddhartha <sansid@google.com>2014-07-17 16:21:54 -0700
committerSandeep Siddhartha <sansid@google.com>2014-07-20 11:22:55 -0700
commit055897208d659e9734a82def88be4a806ff55448 (patch)
tree4540186364f0a3fc3a3675119846448215696f68 /services
parent6eb262c3515c927df19340b3eee8c74bc9478d16 (diff)
downloadframeworks_base-055897208d659e9734a82def88be4a806ff55448.zip
frameworks_base-055897208d659e9734a82def88be4a806ff55448.tar.gz
frameworks_base-055897208d659e9734a82def88be4a806ff55448.tar.bz2
Move sound trigger calls to VoiceInteractionManagerService
- This ensures that any data being loaded on the DSP comes from the framework Change-Id: Ie15f0994850ba8f298ca07c49fe0b89e066d9e2b
Diffstat (limited to 'services')
-rw-r--r--services/voiceinteraction/java/com/android/server/voiceinteraction/DatabaseHelper.java44
-rw-r--r--services/voiceinteraction/java/com/android/server/voiceinteraction/SoundTriggerHelper.java246
-rw-r--r--services/voiceinteraction/java/com/android/server/voiceinteraction/VoiceInteractionManagerService.java83
3 files changed, 356 insertions, 17 deletions
diff --git a/services/voiceinteraction/java/com/android/server/voiceinteraction/DatabaseHelper.java b/services/voiceinteraction/java/com/android/server/voiceinteraction/DatabaseHelper.java
index 27bec9f..eed2d44 100644
--- a/services/voiceinteraction/java/com/android/server/voiceinteraction/DatabaseHelper.java
+++ b/services/voiceinteraction/java/com/android/server/voiceinteraction/DatabaseHelper.java
@@ -22,8 +22,9 @@ import android.database.Cursor;
import android.database.sqlite.SQLiteDatabase;
import android.database.sqlite.SQLiteOpenHelper;
import android.hardware.soundtrigger.SoundTrigger;
-import android.hardware.soundtrigger.Keyphrase;
-import android.hardware.soundtrigger.KeyphraseSoundModel;
+import android.hardware.soundtrigger.SoundTrigger.Keyphrase;
+import android.hardware.soundtrigger.SoundTrigger.KeyphraseSoundModel;
+import android.text.TextUtils;
import android.util.Slog;
import java.util.ArrayList;
@@ -39,7 +40,7 @@ public class DatabaseHelper extends SQLiteOpenHelper {
static final String TAG = "SoundModelDBHelper";
private static final String NAME = "sound_model.db";
- private static final int VERSION = 1;
+ private static final int VERSION = 2;
public static interface KeyphraseContract {
public static final String TABLE = "keyphrase";
@@ -63,7 +64,7 @@ public class DatabaseHelper extends SQLiteOpenHelper {
+ KeyphraseContract.TABLE + "("
+ KeyphraseContract.KEY_ID + " INTEGER PRIMARY KEY,"
+ KeyphraseContract.KEY_RECOGNITION_MODES + " INTEGER,"
- + KeyphraseContract.KEY_USERS + " INTEGER,"
+ + KeyphraseContract.KEY_USERS + " TEXT,"
+ KeyphraseContract.KEY_SOUND_MODEL_ID + " TEXT,"
+ KeyphraseContract.KEY_LOCALE + " TEXT,"
+ KeyphraseContract.KEY_HINT_TEXT + " TEXT" + ")";
@@ -119,10 +120,11 @@ public class DatabaseHelper extends SQLiteOpenHelper {
private boolean addOrUpdateKeyphrase(SQLiteDatabase db, UUID modelId, Keyphrase keyphrase) {
ContentValues values = new ContentValues();
values.put(KeyphraseContract.KEY_ID, keyphrase.id);
- values.put(KeyphraseContract.KEY_RECOGNITION_MODES, keyphrase.recognitionModeFlags);
+ values.put(KeyphraseContract.KEY_RECOGNITION_MODES, keyphrase.recognitionModes);
values.put(KeyphraseContract.KEY_SOUND_MODEL_ID, modelId.toString());
- values.put(KeyphraseContract.KEY_HINT_TEXT, keyphrase.hintText);
+ values.put(KeyphraseContract.KEY_HINT_TEXT, keyphrase.text);
values.put(KeyphraseContract.KEY_LOCALE, keyphrase.locale);
+ values.put(KeyphraseContract.KEY_USERS, getCommaSeparatedString(keyphrase.users));
if (db.insertWithOnConflict(
KeyphraseContract.TABLE, null, values, SQLiteDatabase.CONFLICT_REPLACE) != -1) {
return true;
@@ -193,11 +195,12 @@ public class DatabaseHelper extends SQLiteOpenHelper {
do {
int id = c.getInt(c.getColumnIndex(KeyphraseContract.KEY_ID));
int modes = c.getInt(c.getColumnIndex(KeyphraseContract.KEY_RECOGNITION_MODES));
- int[] users = {c.getInt(c.getColumnIndex(KeyphraseContract.KEY_USERS))};
+ int[] users = getArrayForCommaSeparatedString(
+ c.getString(c.getColumnIndex(KeyphraseContract.KEY_USERS)));
String locale = c.getString(c.getColumnIndex(KeyphraseContract.KEY_LOCALE));
String hintText = c.getString(c.getColumnIndex(KeyphraseContract.KEY_HINT_TEXT));
- keyphrases.add(new Keyphrase(id, hintText, locale, modes, users));
+ keyphrases.add(new Keyphrase(id, modes, locale, hintText, users));
} while (c.moveToNext());
}
Keyphrase[] keyphraseArr = new Keyphrase[keyphrases.size()];
@@ -205,4 +208,29 @@ public class DatabaseHelper extends SQLiteOpenHelper {
c.close();
return keyphraseArr;
}
+
+
+ private String getCommaSeparatedString(int[] users) {
+ if (users == null || users.length == 0) {
+ return "";
+ }
+ String csv = "";
+ for (int user : users) {
+ csv += String.valueOf(user);
+ csv += ",";
+ }
+ return csv.substring(0, csv.length() - 1);
+ }
+
+ private int[] getArrayForCommaSeparatedString(String text) {
+ if (TextUtils.isEmpty(text)) {
+ return null;
+ }
+ String[] usersStr = text.split(",");
+ int[] users = new int[usersStr.length];
+ for (int i = 0; i < usersStr.length; i++) {
+ users[i] = Integer.valueOf(usersStr[i]);
+ }
+ return users;
+ }
}
diff --git a/services/voiceinteraction/java/com/android/server/voiceinteraction/SoundTriggerHelper.java b/services/voiceinteraction/java/com/android/server/voiceinteraction/SoundTriggerHelper.java
new file mode 100644
index 0000000..6842f7d
--- /dev/null
+++ b/services/voiceinteraction/java/com/android/server/voiceinteraction/SoundTriggerHelper.java
@@ -0,0 +1,246 @@
+/**
+ * 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.voiceinteraction;
+
+import android.hardware.soundtrigger.IRecognitionStatusCallback;
+import android.hardware.soundtrigger.SoundTrigger;
+import android.hardware.soundtrigger.SoundTrigger.Keyphrase;
+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.SoundTriggerModule;
+import android.os.RemoteException;
+import android.util.Slog;
+import android.util.SparseArray;
+
+import java.util.ArrayList;
+
+/**
+ * Helper for {@link SoundTrigger} APIs.
+ * Currently this just acts as an abstraction over all SoundTrigger API calls.
+ *
+ * @hide
+ */
+public class SoundTriggerHelper implements SoundTrigger.StatusListener {
+ static final String TAG = "SoundTriggerHelper";
+ // TODO: Remove this.
+ static final int TEMP_KEYPHRASE_ID = 1;
+
+ /**
+ * 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;
+
+ private static final int INVALID_SOUND_MODEL_HANDLE = -1;
+
+ /** The {@link DspInfo} for the system, or null if none exists. */
+ final ModuleProperties moduleProperties;
+
+ /** The properties for the DSP module */
+ private final SoundTriggerModule mModule;
+
+ // Use a RemoteCallbackList here?
+ private final SparseArray<IRecognitionStatusCallback> mActiveListeners;
+
+ private int mCurrentSoundModelHandle = INVALID_SOUND_MODEL_HANDLE;
+
+ SoundTriggerHelper() {
+ ArrayList <ModuleProperties> modules = new ArrayList<>();
+ int status = SoundTrigger.listModules(modules);
+ mActiveListeners = new SparseArray<>(1);
+ if (status != SoundTrigger.STATUS_OK || modules.size() == 0) {
+ Slog.w(TAG, "listModules status=" + status + ", # of modules=" + modules.size());
+ moduleProperties = null;
+ mModule = null;
+ } else {
+ // TODO: Figure out how to determine which module corresponds to the DSP hardware.
+ moduleProperties = modules.get(0);
+ mModule = SoundTrigger.attachModule(moduleProperties.id, this, null);
+ }
+ }
+
+ /**
+ * @return True, if a recognition for the given {@link Keyphrase} is active.
+ */
+ synchronized boolean isKeyphraseActive(Keyphrase keyphrase) {
+ if (keyphrase == null) {
+ Slog.w(TAG, "isKeyphraseActive requires a non-null keyphrase");
+ return false;
+ }
+ return mActiveListeners.get(keyphrase.id) != null;
+ }
+
+ /**
+ * 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}.
+ */
+ synchronized int startRecognition(int keyphraseId,
+ KeyphraseSoundModel soundModel,
+ IRecognitionStatusCallback listener,
+ RecognitionConfig recognitionConfig) {
+ if (moduleProperties == null || mModule == null) {
+ Slog.w(TAG, "Attempting startRecognition without the capability");
+ return STATUS_ERROR;
+ }
+
+ IRecognitionStatusCallback oldListener = mActiveListeners.get(keyphraseId);
+ if (oldListener != null && oldListener != listener) {
+ if (mCurrentSoundModelHandle != INVALID_SOUND_MODEL_HANDLE) {
+ Slog.w(TAG, "Canceling previous recognition");
+ // TODO: Inspect the return codes here.
+ mModule.unloadSoundModel(mCurrentSoundModelHandle);
+ }
+ try {
+ mActiveListeners.get(keyphraseId).onDetectionStopped();
+ } catch (RemoteException e) {
+ Slog.w(TAG, "RemoteException in onDetectionStopped");
+ }
+ mActiveListeners.remove(keyphraseId);
+ }
+
+ int[] handle = new int[] { INVALID_SOUND_MODEL_HANDLE };
+ int status = mModule.loadSoundModel(soundModel, handle);
+ if (status != SoundTrigger.STATUS_OK) {
+ Slog.w(TAG, "loadSoundModel call failed with " + status);
+ return STATUS_ERROR;
+ }
+ if (handle[0] == INVALID_SOUND_MODEL_HANDLE) {
+ Slog.w(TAG, "loadSoundModel call returned invalid sound model handle");
+ return STATUS_ERROR;
+ }
+
+ // Start the recognition.
+ status = mModule.startRecognition(handle[0], recognitionConfig);
+ if (status != SoundTrigger.STATUS_OK) {
+ Slog.w(TAG, "startRecognition failed with " + status);
+ return STATUS_ERROR;
+ }
+
+ // Everything went well!
+ mCurrentSoundModelHandle = handle[0];
+ // Register the new listener. This replaces the old one.
+ // There can only be a maximum of one active listener for a keyphrase
+ // at any given time.
+ mActiveListeners.put(keyphraseId, listener);
+ return STATUS_OK;
+ }
+
+ /**
+ * 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}.
+ */
+ synchronized int stopRecognition(int keyphraseId, IRecognitionStatusCallback listener) {
+ if (moduleProperties == null || mModule == null) {
+ Slog.w(TAG, "Attempting stopRecognition without the capability");
+ return STATUS_ERROR;
+ }
+
+ IRecognitionStatusCallback currentListener = mActiveListeners.get(keyphraseId);
+ if (currentListener == null) {
+ // startRecognition hasn't been called or it failed.
+ Slog.w(TAG, "Attempting stopRecognition without a successful startRecognition");
+ return STATUS_ERROR;
+ } else if (currentListener != listener) {
+ // TODO: Figure out if this should match the listener that was passed in during
+ // startRecognition, or should we allow a different listener to stop the recognition,
+ // in which case we don't need to pass in a listener here.
+ Slog.w(TAG, "Attempting stopRecognition for another recognition");
+ return STATUS_ERROR;
+ } else {
+ // Stop recognition if it's the current one, ignore otherwise.
+ // TODO: Inspect the return codes here.
+ int status = mModule.stopRecognition(mCurrentSoundModelHandle);
+ if (status != SoundTrigger.STATUS_OK) {
+ Slog.w(TAG, "stopRecognition call failed with " + status);
+ return STATUS_ERROR;
+ }
+ status = mModule.unloadSoundModel(mCurrentSoundModelHandle);
+ if (status != SoundTrigger.STATUS_OK) {
+ Slog.w(TAG, "unloadSoundModel call failed with " + status);
+ return STATUS_ERROR;
+ }
+
+ mCurrentSoundModelHandle = INVALID_SOUND_MODEL_HANDLE;
+ mActiveListeners.remove(keyphraseId);
+ return STATUS_OK;
+ }
+ }
+
+ //---- SoundTrigger.StatusListener methods
+ @Override
+ public void onRecognition(RecognitionEvent event) {
+ // Check which keyphrase triggered, and fire the appropriate event.
+ // TODO: Get the keyphrase out of the event and fire events on it.
+ // For now, as a nasty workaround, we fire all events to the listener for
+ // keyphrase with TEMP_KEYPHRASE_ID.
+ IRecognitionStatusCallback listener = null;
+ synchronized(this) {
+ // TODO: The keyphrase should come from the recognition event
+ // as it may be for a different keyphrase than the current one.
+ listener = mActiveListeners.get(TEMP_KEYPHRASE_ID);
+ }
+ if (listener == null) {
+ Slog.w(TAG, "received onRecognition event without any listener for it");
+ return;
+ }
+
+ switch (event.status) {
+ case SoundTrigger.RECOGNITION_STATUS_SUCCESS:
+ // TODO: Pass the captured audio back.
+ try {
+ listener.onDetected(null);
+ } catch (RemoteException e) {
+ Slog.w(TAG, "RemoteException in onDetected");
+ }
+ break;
+ case SoundTrigger.RECOGNITION_STATUS_ABORT:
+ try {
+ listener.onDetectionStopped();
+ } catch (RemoteException e) {
+ Slog.w(TAG, "RemoteException in onDetectionStopped");
+ }
+ break;
+ case SoundTrigger.RECOGNITION_STATUS_FAILURE:
+ try {
+ listener.onDetectionStopped();
+ } catch (RemoteException e) {
+ Slog.w(TAG, "RemoteException in onDetectionStopped");
+ }
+ break;
+ }
+ }
+
+ @Override
+ public void onServiceDied() {
+ // TODO: Figure out how to restart the recognition here.
+ }
+}
diff --git a/services/voiceinteraction/java/com/android/server/voiceinteraction/VoiceInteractionManagerService.java b/services/voiceinteraction/java/com/android/server/voiceinteraction/VoiceInteractionManagerService.java
index 63702ba..2ce4971 100644
--- a/services/voiceinteraction/java/com/android/server/voiceinteraction/VoiceInteractionManagerService.java
+++ b/services/voiceinteraction/java/com/android/server/voiceinteraction/VoiceInteractionManagerService.java
@@ -24,7 +24,10 @@ import android.content.Context;
import android.content.Intent;
import android.content.pm.PackageManager;
import android.database.ContentObserver;
-import android.hardware.soundtrigger.KeyphraseSoundModel;
+import android.hardware.soundtrigger.IRecognitionStatusCallback;
+import android.hardware.soundtrigger.SoundTrigger.KeyphraseSoundModel;
+import android.hardware.soundtrigger.SoundTrigger.ModuleProperties;
+import android.hardware.soundtrigger.SoundTrigger.RecognitionConfig;
import android.os.Binder;
import android.os.Bundle;
import android.os.Handler;
@@ -56,19 +59,17 @@ public class VoiceInteractionManagerService extends SystemService {
static final String TAG = "VoiceInteractionManagerService";
- // TODO: Add descriptive error codes.
- public static final int STATUS_ERROR = -1;
- public static final int STATUS_OK = 1;
-
final Context mContext;
final ContentResolver mResolver;
final DatabaseHelper mDbHelper;
+ final SoundTriggerHelper mSoundTriggerHelper;
public VoiceInteractionManagerService(Context context) {
super(context);
mContext = context;
mResolver = context.getContentResolver();
mDbHelper = new DatabaseHelper(context);
+ mSoundTriggerHelper = new SoundTriggerHelper();
}
@Override
@@ -239,6 +240,8 @@ public class VoiceInteractionManagerService extends SystemService {
}
}
+ //----------------- Model management APIs --------------------------------//
+
@Override
public List<KeyphraseSoundModel> listRegisteredKeyphraseSoundModels(
IVoiceInteractionService service) {
@@ -290,15 +293,15 @@ public class VoiceInteractionManagerService extends SystemService {
// If the keyphrases are not present in the model, delete the model.
if (model.keyphrases == null) {
if (mDbHelper.deleteKeyphraseSoundModel(model.uuid)) {
- return STATUS_OK;
+ return SoundTriggerHelper.STATUS_OK;
} else {
- return STATUS_ERROR;
+ return SoundTriggerHelper.STATUS_ERROR;
}
} else {
if (mDbHelper.addOrUpdateKeyphraseSoundModel(model)) {
- return STATUS_OK;
+ return SoundTriggerHelper.STATUS_OK;
} else {
- return STATUS_ERROR;
+ return SoundTriggerHelper.STATUS_ERROR;
}
}
} finally {
@@ -307,6 +310,68 @@ public class VoiceInteractionManagerService extends SystemService {
}
}
+ //----------------- SoundTrigger APIs --------------------------------//
+ @Override
+ public ModuleProperties getDspModuleProperties(IVoiceInteractionService service) {
+ // Allow the call if this is the current voice interaction service.
+ synchronized (this) {
+ if (mImpl == null || mImpl.mService == null
+ || service == null || service.asBinder() != mImpl.mService.asBinder()) {
+ throw new SecurityException(
+ "Caller is not the current voice interaction service");
+ }
+
+ final long caller = Binder.clearCallingIdentity();
+ try {
+ return mSoundTriggerHelper.moduleProperties;
+ } finally {
+ Binder.restoreCallingIdentity(caller);
+ }
+ }
+ }
+
+ @Override
+ public int startRecognition(IVoiceInteractionService service, int keyphraseId,
+ KeyphraseSoundModel soundModel, IRecognitionStatusCallback callback,
+ RecognitionConfig recognitionConfig) {
+ // Allow the call if this is the current voice interaction service.
+ synchronized (this) {
+ if (mImpl == null || mImpl.mService == null
+ || service == null || service.asBinder() != mImpl.mService.asBinder()) {
+ throw new SecurityException(
+ "Caller is not the current voice interaction service");
+ }
+
+ final long caller = Binder.clearCallingIdentity();
+ try {
+ return mSoundTriggerHelper.startRecognition(
+ keyphraseId, soundModel, callback, recognitionConfig);
+ } finally {
+ Binder.restoreCallingIdentity(caller);
+ }
+ }
+ }
+
+ @Override
+ public int stopRecognition(IVoiceInteractionService service, int keyphraseId,
+ IRecognitionStatusCallback callback) {
+ // Allow the call if this is the current voice interaction service.
+ synchronized (this) {
+ if (mImpl == null || mImpl.mService == null
+ || service == null || service.asBinder() != mImpl.mService.asBinder()) {
+ throw new SecurityException(
+ "Caller is not the current voice interaction service");
+ }
+
+ final long caller = Binder.clearCallingIdentity();
+ try {
+ return mSoundTriggerHelper.stopRecognition(keyphraseId, callback);
+ } finally {
+ Binder.restoreCallingIdentity(caller);
+ }
+ }
+ }
+
@Override
public void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
if (mContext.checkCallingOrSelfPermission(Manifest.permission.DUMP)