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.compat.annotation.UnsupportedAppUsage; 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 */ SoundTriggerManager(Context context, ISoundTriggerService soundTriggerService )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) updateModel(Model model)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) getModel(UUID soundModelId)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) deleteModel(UUID soundModelId)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) createSoundTriggerDetector(UUID soundModelId, @NonNull SoundTriggerDetector.Callback callback, @Nullable Handler handler)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 */ Model(SoundTrigger.GenericSoundModel soundTriggerModel)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 */ create(UUID modelUuid, UUID vendorUuid, byte[] data)168 public static Model create(UUID modelUuid, UUID vendorUuid, byte[] data) { 169 return new Model(new SoundTrigger.GenericSoundModel(modelUuid, 170 vendorUuid, data)); 171 } 172 getModelUuid()173 public UUID getModelUuid() { 174 return mGenericSoundModel.uuid; 175 } 176 getVendorUuid()177 public UUID getVendorUuid() { 178 return mGenericSoundModel.vendorUuid; 179 } 180 getModelData()181 public byte[] getModelData() { 182 return mGenericSoundModel.data; 183 } 184 185 /** 186 * @hide 187 */ getGenericSoundModel()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 @UnsupportedAppUsage loadSoundModel(SoundModel soundModel)243 public int loadSoundModel(SoundModel soundModel) { 244 if (soundModel == null) { 245 return STATUS_ERROR; 246 } 247 248 try { 249 switch (soundModel.type) { 250 case SoundModel.TYPE_GENERIC_SOUND: 251 return mSoundTriggerService.loadGenericSoundModel( 252 (GenericSoundModel) soundModel); 253 case SoundModel.TYPE_KEYPHRASE: 254 return mSoundTriggerService.loadKeyphraseSoundModel( 255 (KeyphraseSoundModel) soundModel); 256 default: 257 Slog.e(TAG, "Unkown model type"); 258 return STATUS_ERROR; 259 } 260 } catch (RemoteException e) { 261 throw e.rethrowFromSystemServer(); 262 } 263 } 264 265 /** 266 * Starts recognition for the given model id. All events from the model will be sent to the 267 * service. 268 * 269 * <p>This only supports generic sound trigger events. For keyphrase events, please use 270 * {@link android.service.voice.VoiceInteractionService}. 271 * 272 * @param soundModelId Id of the sound model 273 * @param params Opaque data sent to each service call of the service as the {@code params} 274 * argument 275 * @param detectionService The component name of the service that should receive the events. 276 * Needs to subclass {@link SoundTriggerDetectionService} 277 * @param config Configures the recognition 278 * 279 * @return {@link SoundTrigger#STATUS_OK} if the recognition could be started, error code 280 * otherwise 281 * 282 * @hide 283 */ 284 @RequiresPermission(android.Manifest.permission.MANAGE_SOUND_TRIGGER) 285 @UnsupportedAppUsage startRecognition(@onNull UUID soundModelId, @Nullable Bundle params, @NonNull ComponentName detectionService, @NonNull RecognitionConfig config)286 public int startRecognition(@NonNull UUID soundModelId, @Nullable Bundle params, 287 @NonNull ComponentName detectionService, @NonNull RecognitionConfig config) { 288 Preconditions.checkNotNull(soundModelId); 289 Preconditions.checkNotNull(detectionService); 290 Preconditions.checkNotNull(config); 291 292 try { 293 return mSoundTriggerService.startRecognitionForService(new ParcelUuid(soundModelId), 294 params, detectionService, config); 295 } catch (RemoteException e) { 296 throw e.rethrowFromSystemServer(); 297 } 298 } 299 300 /** 301 * Stops the given model's recognition. 302 * @hide 303 */ 304 @RequiresPermission(android.Manifest.permission.MANAGE_SOUND_TRIGGER) 305 @UnsupportedAppUsage stopRecognition(UUID soundModelId)306 public int stopRecognition(UUID soundModelId) { 307 if (soundModelId == null) { 308 return STATUS_ERROR; 309 } 310 try { 311 return mSoundTriggerService.stopRecognitionForService(new ParcelUuid(soundModelId)); 312 } catch (RemoteException e) { 313 throw e.rethrowFromSystemServer(); 314 } 315 } 316 317 /** 318 * Removes the given model from memory. Will also stop any pending recognitions. 319 * @hide 320 */ 321 @RequiresPermission(android.Manifest.permission.MANAGE_SOUND_TRIGGER) 322 @UnsupportedAppUsage unloadSoundModel(UUID soundModelId)323 public int unloadSoundModel(UUID soundModelId) { 324 if (soundModelId == null) { 325 return STATUS_ERROR; 326 } 327 try { 328 return mSoundTriggerService.unloadSoundModel( 329 new ParcelUuid(soundModelId)); 330 } catch (RemoteException e) { 331 throw e.rethrowFromSystemServer(); 332 } 333 } 334 335 /** 336 * Returns true if the given model has had detection started on it. 337 * @hide 338 */ 339 @RequiresPermission(android.Manifest.permission.MANAGE_SOUND_TRIGGER) 340 @UnsupportedAppUsage isRecognitionActive(UUID soundModelId)341 public boolean isRecognitionActive(UUID soundModelId) { 342 if (soundModelId == null) { 343 return false; 344 } 345 try { 346 return mSoundTriggerService.isRecognitionActive( 347 new ParcelUuid(soundModelId)); 348 } catch (RemoteException e) { 349 throw e.rethrowFromSystemServer(); 350 } 351 } 352 353 /** 354 * Get the amount of time (in milliseconds) an operation of the 355 * {@link ISoundTriggerDetectionService} is allowed to ask. 356 * 357 * @return The amount of time an sound trigger detection service operation is allowed to last 358 */ getDetectionServiceOperationsTimeout()359 public int getDetectionServiceOperationsTimeout() { 360 try { 361 return Settings.Global.getInt(mContext.getContentResolver(), 362 Settings.Global.SOUND_TRIGGER_DETECTION_SERVICE_OP_TIMEOUT); 363 } catch (Settings.SettingNotFoundException e) { 364 return Integer.MAX_VALUE; 365 } 366 } 367 368 /** 369 * Asynchronously get state of the indicated model. The model state is returned as 370 * a recognition event in the callback that was registered in the startRecognition 371 * method. 372 * @hide 373 */ 374 @RequiresPermission(android.Manifest.permission.MANAGE_SOUND_TRIGGER) 375 @UnsupportedAppUsage getModelState(UUID soundModelId)376 public int getModelState(UUID soundModelId) { 377 if (soundModelId == null) { 378 return STATUS_ERROR; 379 } 380 try { 381 return mSoundTriggerService.getModelState(new ParcelUuid(soundModelId)); 382 } catch (RemoteException e) { 383 throw e.rethrowFromSystemServer(); 384 } 385 } 386 } 387