1 /* 2 * Copyright (C) 2014 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17 package com.android.server.soundtrigger; 18 import static android.hardware.soundtrigger.SoundTrigger.STATUS_ERROR; 19 20 import android.content.Context; 21 import android.content.pm.PackageManager; 22 import android.Manifest; 23 import android.hardware.soundtrigger.IRecognitionStatusCallback; 24 import android.hardware.soundtrigger.SoundTrigger; 25 import android.hardware.soundtrigger.SoundTrigger.GenericSoundModel; 26 import android.hardware.soundtrigger.SoundTrigger.KeyphraseSoundModel; 27 import android.hardware.soundtrigger.SoundTrigger.ModuleProperties; 28 import android.hardware.soundtrigger.SoundTrigger.RecognitionConfig; 29 import android.os.Parcel; 30 import android.os.ParcelUuid; 31 import android.os.RemoteException; 32 import android.util.Slog; 33 34 import com.android.server.SystemService; 35 import com.android.internal.app.ISoundTriggerService; 36 37 import java.io.FileDescriptor; 38 import java.io.PrintWriter; 39 import java.util.UUID; 40 41 /** 42 * A single SystemService to manage all sound/voice-based sound models on the DSP. 43 * This services provides apis to manage sound trigger-based sound models via 44 * the ISoundTriggerService interface. This class also publishes a local interface encapsulating 45 * the functionality provided by {@link SoundTriggerHelper} for use by 46 * {@link VoiceInteractionManagerService}. 47 * 48 * @hide 49 */ 50 public class SoundTriggerService extends SystemService { 51 private static final String TAG = "SoundTriggerService"; 52 private static final boolean DEBUG = true; 53 54 final Context mContext; 55 private final SoundTriggerServiceStub mServiceStub; 56 private final LocalSoundTriggerService mLocalSoundTriggerService; 57 private SoundTriggerDbHelper mDbHelper; 58 private SoundTriggerHelper mSoundTriggerHelper; 59 60 public SoundTriggerService(Context context) { 61 super(context); 62 mContext = context; 63 mServiceStub = new SoundTriggerServiceStub(); 64 mLocalSoundTriggerService = new LocalSoundTriggerService(context); 65 } 66 67 @Override 68 public void onStart() { 69 publishBinderService(Context.SOUND_TRIGGER_SERVICE, mServiceStub); 70 publishLocalService(SoundTriggerInternal.class, mLocalSoundTriggerService); 71 } 72 73 @Override 74 public void onBootPhase(int phase) { 75 if (PHASE_SYSTEM_SERVICES_READY == phase) { 76 initSoundTriggerHelper(); 77 mLocalSoundTriggerService.setSoundTriggerHelper(mSoundTriggerHelper); 78 } else if (PHASE_THIRD_PARTY_APPS_CAN_START == phase) { 79 mDbHelper = new SoundTriggerDbHelper(mContext); 80 } 81 } 82 83 @Override 84 public void onStartUser(int userHandle) { 85 } 86 87 @Override 88 public void onSwitchUser(int userHandle) { 89 } 90 91 private synchronized void initSoundTriggerHelper() { 92 if (mSoundTriggerHelper == null) { 93 mSoundTriggerHelper = new SoundTriggerHelper(mContext); 94 } 95 } 96 97 private synchronized boolean isInitialized() { 98 if (mSoundTriggerHelper == null ) { 99 Slog.e(TAG, "SoundTriggerHelper not initialized."); 100 return false; 101 } 102 return true; 103 } 104 105 class SoundTriggerServiceStub extends ISoundTriggerService.Stub { 106 @Override 107 public boolean onTransact(int code, Parcel data, Parcel reply, int flags) 108 throws RemoteException { 109 try { 110 return super.onTransact(code, data, reply, flags); 111 } catch (RuntimeException e) { 112 // The activity manager only throws security exceptions, so let's 113 // log all others. 114 if (!(e instanceof SecurityException)) { 115 Slog.wtf(TAG, "SoundTriggerService Crash", e); 116 } 117 throw e; 118 } 119 } 120 121 @Override 122 public int startRecognition(ParcelUuid parcelUuid, IRecognitionStatusCallback callback, 123 RecognitionConfig config) { 124 enforceCallingPermission(Manifest.permission.MANAGE_SOUND_TRIGGER); 125 if (!isInitialized()) return STATUS_ERROR; 126 if (DEBUG) { 127 Slog.i(TAG, "startRecognition(): Uuid : " + parcelUuid); 128 } 129 130 GenericSoundModel model = getSoundModel(parcelUuid); 131 if (model == null) { 132 Slog.e(TAG, "Null model in database for id: " + parcelUuid); 133 return STATUS_ERROR; 134 } 135 136 return mSoundTriggerHelper.startGenericRecognition(parcelUuid.getUuid(), model, 137 callback, config); 138 } 139 140 @Override 141 public int stopRecognition(ParcelUuid parcelUuid, IRecognitionStatusCallback callback) { 142 enforceCallingPermission(Manifest.permission.MANAGE_SOUND_TRIGGER); 143 if (DEBUG) { 144 Slog.i(TAG, "stopRecognition(): Uuid : " + parcelUuid); 145 } 146 if (!isInitialized()) return STATUS_ERROR; 147 return mSoundTriggerHelper.stopGenericRecognition(parcelUuid.getUuid(), callback); 148 } 149 150 @Override 151 public SoundTrigger.GenericSoundModel getSoundModel(ParcelUuid soundModelId) { 152 enforceCallingPermission(Manifest.permission.MANAGE_SOUND_TRIGGER); 153 if (DEBUG) { 154 Slog.i(TAG, "getSoundModel(): id = " + soundModelId); 155 } 156 SoundTrigger.GenericSoundModel model = mDbHelper.getGenericSoundModel( 157 soundModelId.getUuid()); 158 return model; 159 } 160 161 @Override 162 public void updateSoundModel(SoundTrigger.GenericSoundModel soundModel) { 163 enforceCallingPermission(Manifest.permission.MANAGE_SOUND_TRIGGER); 164 if (DEBUG) { 165 Slog.i(TAG, "updateSoundModel(): model = " + soundModel); 166 } 167 mDbHelper.updateGenericSoundModel(soundModel); 168 } 169 170 @Override 171 public void deleteSoundModel(ParcelUuid soundModelId) { 172 enforceCallingPermission(Manifest.permission.MANAGE_SOUND_TRIGGER); 173 if (DEBUG) { 174 Slog.i(TAG, "deleteSoundModel(): id = " + soundModelId); 175 } 176 // Unload the model if it is loaded. 177 mSoundTriggerHelper.unloadGenericSoundModel(soundModelId.getUuid()); 178 mDbHelper.deleteGenericSoundModel(soundModelId.getUuid()); 179 } 180 } 181 182 public final class LocalSoundTriggerService extends SoundTriggerInternal { 183 private final Context mContext; 184 private SoundTriggerHelper mSoundTriggerHelper; 185 186 LocalSoundTriggerService(Context context) { 187 mContext = context; 188 } 189 190 synchronized void setSoundTriggerHelper(SoundTriggerHelper helper) { 191 mSoundTriggerHelper = helper; 192 } 193 194 @Override 195 public int startRecognition(int keyphraseId, KeyphraseSoundModel soundModel, 196 IRecognitionStatusCallback listener, RecognitionConfig recognitionConfig) { 197 if (!isInitialized()) return STATUS_ERROR; 198 return mSoundTriggerHelper.startKeyphraseRecognition(keyphraseId, soundModel, listener, 199 recognitionConfig); 200 } 201 202 @Override 203 public synchronized int stopRecognition(int keyphraseId, IRecognitionStatusCallback listener) { 204 if (!isInitialized()) return STATUS_ERROR; 205 return mSoundTriggerHelper.stopKeyphraseRecognition(keyphraseId, listener); 206 } 207 208 @Override 209 public ModuleProperties getModuleProperties() { 210 if (!isInitialized()) return null; 211 return mSoundTriggerHelper.getModuleProperties(); 212 } 213 214 @Override 215 public int unloadKeyphraseModel(int keyphraseId) { 216 if (!isInitialized()) return STATUS_ERROR; 217 return mSoundTriggerHelper.unloadKeyphraseSoundModel(keyphraseId); 218 } 219 220 @Override 221 public void dump(FileDescriptor fd, PrintWriter pw, String[] args) { 222 if (!isInitialized()) return; 223 mSoundTriggerHelper.dump(fd, pw, args); 224 } 225 226 private synchronized boolean isInitialized() { 227 if (mSoundTriggerHelper == null ) { 228 Slog.e(TAG, "SoundTriggerHelper not initialized."); 229 return false; 230 } 231 return true; 232 } 233 } 234 235 private void enforceCallingPermission(String permission) { 236 if (mContext.checkCallingOrSelfPermission(permission) 237 != PackageManager.PERMISSION_GRANTED) { 238 throw new SecurityException("Caller does not hold the permission " + permission); 239 } 240 } 241 } 242