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 android.media.soundtrigger; 18 19 import static android.hardware.soundtrigger.SoundTrigger.STATUS_ERROR; 20 21 import android.annotation.NonNull; 22 import android.annotation.Nullable; 23 import android.annotation.RequiresPermission; 24 import android.annotation.SystemApi; 25 import android.annotation.SystemService; 26 import android.app.PendingIntent; 27 import android.content.ComponentName; 28 import android.content.Context; 29 import android.hardware.soundtrigger.SoundTrigger; 30 import android.hardware.soundtrigger.SoundTrigger.GenericSoundModel; 31 import android.hardware.soundtrigger.SoundTrigger.KeyphraseSoundModel; 32 import android.hardware.soundtrigger.SoundTrigger.RecognitionConfig; 33 import android.hardware.soundtrigger.SoundTrigger.SoundModel; 34 import android.os.Bundle; 35 import android.os.Handler; 36 import android.os.ParcelUuid; 37 import android.os.RemoteException; 38 import android.provider.Settings; 39 import android.util.Slog; 40 41 import com.android.internal.app.ISoundTriggerService; 42 import com.android.internal.util.Preconditions; 43 44 import java.util.HashMap; 45 import java.util.UUID; 46 47 /** 48 * This class provides management of non-voice (general sound trigger) based sound recognition 49 * models. Usage of this class is restricted to system or signature applications only. This allows 50 * OEMs to write apps that can manage non-voice based sound trigger models. 51 * 52 * @hide 53 */ 54 @SystemApi 55 @SystemService(Context.SOUND_TRIGGER_SERVICE) 56 public final class SoundTriggerManager { 57 private static final boolean DBG = false; 58 private static final String TAG = "SoundTriggerManager"; 59 60 private final Context mContext; 61 private final ISoundTriggerService mSoundTriggerService; 62 63 // Stores a mapping from the sound model UUID to the SoundTriggerInstance created by 64 // the createSoundTriggerDetector() call. 65 private final HashMap<UUID, SoundTriggerDetector> mReceiverInstanceMap; 66 67 /** 68 * @hide 69 */ 70 public SoundTriggerManager(Context context, ISoundTriggerService soundTriggerService ) { 71 if (DBG) { 72 Slog.i(TAG, "SoundTriggerManager created."); 73 } 74 mSoundTriggerService = soundTriggerService; 75 mContext = context; 76 mReceiverInstanceMap = new HashMap<UUID, SoundTriggerDetector>(); 77 } 78 79 /** 80 * Updates the given sound trigger model. 81 */ 82 @RequiresPermission(android.Manifest.permission.MANAGE_SOUND_TRIGGER) 83 public void updateModel(Model model) { 84 try { 85 mSoundTriggerService.updateSoundModel(model.getGenericSoundModel()); 86 } catch (RemoteException e) { 87 throw e.rethrowFromSystemServer(); 88 } 89 } 90 91 /** 92 * Returns the sound trigger model represented by the given UUID. An instance of {@link Model} 93 * is returned. 94 */ 95 @RequiresPermission(android.Manifest.permission.MANAGE_SOUND_TRIGGER) 96 public Model getModel(UUID soundModelId) { 97 try { 98 return new Model(mSoundTriggerService.getSoundModel( 99 new ParcelUuid(soundModelId))); 100 } catch (RemoteException e) { 101 throw e.rethrowFromSystemServer(); 102 } 103 } 104 105 /** 106 * Deletes the sound model represented by the provided UUID. 107 */ 108 @RequiresPermission(android.Manifest.permission.MANAGE_SOUND_TRIGGER) 109 public void deleteModel(UUID soundModelId) { 110 try { 111 mSoundTriggerService.deleteSoundModel(new ParcelUuid(soundModelId)); 112 } catch (RemoteException e) { 113 throw e.rethrowFromSystemServer(); 114 } 115 } 116 117 /** 118 * Creates an instance of {@link SoundTriggerDetector} which can be used to start/stop 119 * recognition on the model and register for triggers from the model. Note that this call 120 * invalidates any previously returned instances for the same sound model Uuid. 121 * 122 * @param soundModelId UUID of the sound model to create the receiver object for. 123 * @param callback Instance of the {@link SoundTriggerDetector#Callback} object for the 124 * callbacks for the given sound model. 125 * @param handler The Handler to use for the callback operations. A null value will use the 126 * current thread's Looper. 127 * @return Instance of {@link SoundTriggerDetector} or null on error. 128 */ 129 @Nullable 130 @RequiresPermission(android.Manifest.permission.MANAGE_SOUND_TRIGGER) 131 public SoundTriggerDetector createSoundTriggerDetector(UUID soundModelId, 132 @NonNull SoundTriggerDetector.Callback callback, @Nullable Handler handler) { 133 if (soundModelId == null) { 134 return null; 135 } 136 137 SoundTriggerDetector oldInstance = mReceiverInstanceMap.get(soundModelId); 138 if (oldInstance != null) { 139 // Shutdown old instance. 140 } 141 SoundTriggerDetector newInstance = new SoundTriggerDetector(mSoundTriggerService, 142 soundModelId, callback, handler); 143 mReceiverInstanceMap.put(soundModelId, newInstance); 144 return newInstance; 145 } 146 147 /** 148 * Class captures the data and fields that represent a non-keyphrase sound model. Use the 149 * factory constructor {@link Model#create()} to create an instance. 150 */ 151 // We use encapsulation to expose the SoundTrigger.GenericSoundModel as a SystemApi. This 152 // prevents us from exposing SoundTrigger.GenericSoundModel as an Api. 153 public static class Model { 154 155 private SoundTrigger.GenericSoundModel mGenericSoundModel; 156 157 /** 158 * @hide 159 */ 160 Model(SoundTrigger.GenericSoundModel soundTriggerModel) { 161 mGenericSoundModel = soundTriggerModel; 162 } 163 164 /** 165 * Factory constructor to create a SoundModel instance for use with methods in this 166 * class. 167 */ 168 public static Model create(UUID modelUuid, UUID vendorUuid, byte[] data) { 169 return new Model(new SoundTrigger.GenericSoundModel(modelUuid, 170 vendorUuid, data)); 171 } 172 173 public UUID getModelUuid() { 174 return mGenericSoundModel.uuid; 175 } 176 177 public UUID getVendorUuid() { 178 return mGenericSoundModel.vendorUuid; 179 } 180 181 public byte[] getModelData() { 182 return mGenericSoundModel.data; 183 } 184 185 /** 186 * @hide 187 */ 188 SoundTrigger.GenericSoundModel getGenericSoundModel() { 189 return mGenericSoundModel; 190 } 191 } 192 193 194 /** 195 * Default message type. 196 * @hide 197 */ 198 public static final int FLAG_MESSAGE_TYPE_UNKNOWN = -1; 199 /** 200 * Contents of EXTRA_MESSAGE_TYPE extra for a RecognitionEvent. 201 * @hide 202 */ 203 public static final int FLAG_MESSAGE_TYPE_RECOGNITION_EVENT = 0; 204 /** 205 * Contents of EXTRA_MESSAGE_TYPE extra for recognition error events. 206 * @hide 207 */ 208 public static final int FLAG_MESSAGE_TYPE_RECOGNITION_ERROR = 1; 209 /** 210 * Contents of EXTRA_MESSAGE_TYPE extra for a recognition paused events. 211 * @hide 212 */ 213 public static final int FLAG_MESSAGE_TYPE_RECOGNITION_PAUSED = 2; 214 /** 215 * Contents of EXTRA_MESSAGE_TYPE extra for recognition resumed events. 216 * @hide 217 */ 218 public static final int FLAG_MESSAGE_TYPE_RECOGNITION_RESUMED = 3; 219 220 /** 221 * Extra key in the intent for the type of the message. 222 * @hide 223 */ 224 public static final String EXTRA_MESSAGE_TYPE = "android.media.soundtrigger.MESSAGE_TYPE"; 225 /** 226 * Extra key in the intent that holds the RecognitionEvent parcelable. 227 * @hide 228 */ 229 public static final String EXTRA_RECOGNITION_EVENT = "android.media.soundtrigger.RECOGNITION_EVENT"; 230 /** 231 * Extra key in the intent that holds the status in an error message. 232 * @hide 233 */ 234 public static final String EXTRA_STATUS = "android.media.soundtrigger.STATUS"; 235 236 /** 237 * Loads a given sound model into the sound trigger. Note the model will be unloaded if there is 238 * an error/the system service is restarted. 239 * @hide 240 */ 241 @RequiresPermission(android.Manifest.permission.MANAGE_SOUND_TRIGGER) 242 public int loadSoundModel(SoundModel soundModel) { 243 if (soundModel == null) { 244 return STATUS_ERROR; 245 } 246 247 try { 248 switch (soundModel.type) { 249 case SoundModel.TYPE_GENERIC_SOUND: 250 return mSoundTriggerService.loadGenericSoundModel( 251 (GenericSoundModel) soundModel); 252 case SoundModel.TYPE_KEYPHRASE: 253 return mSoundTriggerService.loadKeyphraseSoundModel( 254 (KeyphraseSoundModel) soundModel); 255 default: 256 Slog.e(TAG, "Unkown model type"); 257 return STATUS_ERROR; 258 } 259 } catch (RemoteException e) { 260 throw e.rethrowFromSystemServer(); 261 } 262 } 263 264 /** 265 * Starts recognition on the given model id. All events from the model will be sent to the 266 * PendingIntent. 267 * @hide 268 */ 269 @RequiresPermission(android.Manifest.permission.MANAGE_SOUND_TRIGGER) 270 public int startRecognition(UUID soundModelId, PendingIntent callbackIntent, 271 RecognitionConfig config) { 272 if (soundModelId == null || callbackIntent == null || config == null) { 273 return STATUS_ERROR; 274 } 275 try { 276 return mSoundTriggerService.startRecognitionForIntent(new ParcelUuid(soundModelId), 277 callbackIntent, config); 278 } catch (RemoteException e) { 279 throw e.rethrowFromSystemServer(); 280 } 281 } 282 283 /** 284 * Starts recognition for the given model id. All events from the model will be sent to the 285 * service. 286 * 287 * <p>This only supports generic sound trigger events. For keyphrase events, please use 288 * {@link android.service.voice.VoiceInteractionService}. 289 * 290 * @param soundModelId Id of the sound model 291 * @param params Opaque data sent to each service call of the service as the {@code params} 292 * argument 293 * @param detectionService The component name of the service that should receive the events. 294 * Needs to subclass {@link SoundTriggerDetectionService} 295 * @param config Configures the recognition 296 * 297 * @return {@link SoundTrigger#STATUS_OK} if the recognition could be started, error code 298 * otherwise 299 * 300 * @hide 301 */ 302 @RequiresPermission(android.Manifest.permission.MANAGE_SOUND_TRIGGER) 303 public int startRecognition(@NonNull UUID soundModelId, @Nullable Bundle params, 304 @NonNull ComponentName detectionService, @NonNull RecognitionConfig config) { 305 Preconditions.checkNotNull(soundModelId); 306 Preconditions.checkNotNull(detectionService); 307 Preconditions.checkNotNull(config); 308 309 try { 310 return mSoundTriggerService.startRecognitionForService(new ParcelUuid(soundModelId), 311 params, detectionService, config); 312 } catch (RemoteException e) { 313 throw e.rethrowFromSystemServer(); 314 } 315 } 316 317 /** 318 * Stops the given model's recognition. 319 * @hide 320 */ 321 @RequiresPermission(android.Manifest.permission.MANAGE_SOUND_TRIGGER) 322 public int stopRecognition(UUID soundModelId) { 323 if (soundModelId == null) { 324 return STATUS_ERROR; 325 } 326 try { 327 return mSoundTriggerService.stopRecognitionForIntent(new ParcelUuid(soundModelId)); 328 } catch (RemoteException e) { 329 throw e.rethrowFromSystemServer(); 330 } 331 } 332 333 /** 334 * Removes the given model from memory. Will also stop any pending recognitions. 335 * @hide 336 */ 337 @RequiresPermission(android.Manifest.permission.MANAGE_SOUND_TRIGGER) 338 public int unloadSoundModel(UUID soundModelId) { 339 if (soundModelId == null) { 340 return STATUS_ERROR; 341 } 342 try { 343 return mSoundTriggerService.unloadSoundModel( 344 new ParcelUuid(soundModelId)); 345 } catch (RemoteException e) { 346 throw e.rethrowFromSystemServer(); 347 } 348 } 349 350 /** 351 * Returns true if the given model has had detection started on it. 352 * @hide 353 */ 354 @RequiresPermission(android.Manifest.permission.MANAGE_SOUND_TRIGGER) 355 public boolean isRecognitionActive(UUID soundModelId) { 356 if (soundModelId == null) { 357 return false; 358 } 359 try { 360 return mSoundTriggerService.isRecognitionActive( 361 new ParcelUuid(soundModelId)); 362 } catch (RemoteException e) { 363 throw e.rethrowFromSystemServer(); 364 } 365 } 366 367 /** 368 * Get the amount of time (in milliseconds) an operation of the 369 * {@link ISoundTriggerDetectionService} is allowed to ask. 370 * 371 * @return The amount of time an sound trigger detection service operation is allowed to last 372 */ 373 public int getDetectionServiceOperationsTimeout() { 374 try { 375 return Settings.Global.getInt(mContext.getContentResolver(), 376 Settings.Global.SOUND_TRIGGER_DETECTION_SERVICE_OP_TIMEOUT); 377 } catch (Settings.SettingNotFoundException e) { 378 return Integer.MAX_VALUE; 379 } 380 } 381 } 382