1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 385 386 387 388 389 390 391 392 393 394 395 396 397 398 399 400 401 402 403 404 405 406 407 408 409 410 411 412 413 414 415 416 417 418 419 420 421 422 423 424 425 426
|
/*
* 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.hardware.soundtrigger;
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.compat.annotation.UnsupportedAppUsage;
import android.media.permission.ClearCallingIdentityContext;
import android.media.permission.Identity;
import android.media.permission.SafeCloseable;
import android.media.soundtrigger.PhraseRecognitionEvent;
import android.media.soundtrigger.PhraseSoundModel;
import android.media.soundtrigger.RecognitionEvent;
import android.media.soundtrigger.SoundModel;
import android.media.soundtrigger_middleware.ISoundTriggerCallback;
import android.media.soundtrigger_middleware.ISoundTriggerMiddlewareService;
import android.media.soundtrigger_middleware.ISoundTriggerModule;
import android.os.Build;
import android.os.Handler;
import android.os.IBinder;
import android.os.Looper;
import android.os.Message;
import android.os.RemoteException;
import android.util.Log;
import java.io.IOException;
/**
* The SoundTriggerModule provides APIs to control sound models and sound detection
* on a given sound trigger hardware module.
*
* @hide
*/
public class SoundTriggerModule {
private static final String TAG = "SoundTriggerModule";
private static final int EVENT_RECOGNITION = 1;
private static final int EVENT_SERVICE_DIED = 2;
private static final int EVENT_RESOURCES_AVAILABLE = 3;
private static final int EVENT_MODEL_UNLOADED = 4;
@UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
private int mId;
private EventHandlerDelegate mEventHandlerDelegate;
private ISoundTriggerModule mService;
/**
* This variant is intended for use when the caller is acting an originator, rather than on
* behalf of a different entity, as far as authorization goes.
*/
SoundTriggerModule(@NonNull ISoundTriggerMiddlewareService service,
int moduleId, @NonNull SoundTrigger.StatusListener listener, @NonNull Looper looper,
@NonNull Identity originatorIdentity)
throws RemoteException {
mId = moduleId;
mEventHandlerDelegate = new EventHandlerDelegate(listener, looper);
try (SafeCloseable ignored = ClearCallingIdentityContext.create()) {
mService = service.attachAsOriginator(moduleId, originatorIdentity,
mEventHandlerDelegate);
}
mService.asBinder().linkToDeath(mEventHandlerDelegate, 0);
}
/**
* This variant is intended for use when the caller is acting as a middleman, i.e. on behalf of
* a different entity, as far as authorization goes.
*/
SoundTriggerModule(@NonNull ISoundTriggerMiddlewareService service,
int moduleId, @NonNull SoundTrigger.StatusListener listener, @NonNull Looper looper,
@NonNull Identity middlemanIdentity, @NonNull Identity originatorIdentity)
throws RemoteException {
mId = moduleId;
mEventHandlerDelegate = new EventHandlerDelegate(listener, looper);
try (SafeCloseable ignored = ClearCallingIdentityContext.create()) {
mService = service.attachAsMiddleman(moduleId, middlemanIdentity, originatorIdentity,
mEventHandlerDelegate);
}
mService.asBinder().linkToDeath(mEventHandlerDelegate, 0);
}
@Override
protected void finalize() {
detach();
}
/**
* Detach from this module. The {@link SoundTrigger.StatusListener} callback will not be called
* anymore and associated resources will be released.
* All models must have been unloaded prior to detaching.
*/
@UnsupportedAppUsage
public synchronized void detach() {
try {
if (mService != null) {
mService.asBinder().unlinkToDeath(mEventHandlerDelegate, 0);
mService.detach();
mService = null;
}
} catch (Exception e) {
SoundTrigger.handleException(e);
}
}
/**
* Load a {@link SoundTrigger.SoundModel} to the hardware. A sound model must be loaded in
* order to start listening to a key phrase in this model.
* @param model The sound model to load.
* @param soundModelHandle an array of int where the sound model handle will be returned.
* @return - {@link SoundTrigger#STATUS_OK} in case of success
* - {@link SoundTrigger#STATUS_ERROR} in case of unspecified error
* - {@link SoundTrigger#STATUS_BUSY} in case of transient resource constraints
* - {@link SoundTrigger#STATUS_PERMISSION_DENIED} if the caller does not have
* system permission
* - {@link SoundTrigger#STATUS_NO_INIT} if the native service cannot be reached
* - {@link SoundTrigger#STATUS_BAD_VALUE} if parameters are invalid
* - {@link SoundTrigger#STATUS_DEAD_OBJECT} if the binder transaction to the native
* service fails
* - {@link SoundTrigger#STATUS_INVALID_OPERATION} if the call is out of sequence
*/
@UnsupportedAppUsage
public synchronized int loadSoundModel(@NonNull SoundTrigger.SoundModel model,
@NonNull int[] soundModelHandle) {
try {
if (model instanceof SoundTrigger.GenericSoundModel) {
SoundModel aidlModel = ConversionUtil.api2aidlGenericSoundModel(
(SoundTrigger.GenericSoundModel) model);
try {
soundModelHandle[0] = mService.loadModel(aidlModel);
} finally {
// TODO(b/219825762): We should be able to use the entire object in a
// try-with-resources
// clause, instead of having to explicitly close internal fields.
if (aidlModel.data != null) {
try {
aidlModel.data.close();
} catch (IOException e) {
Log.e(TAG, "Failed to close file", e);
}
}
}
return SoundTrigger.STATUS_OK;
}
if (model instanceof SoundTrigger.KeyphraseSoundModel) {
PhraseSoundModel aidlModel = ConversionUtil.api2aidlPhraseSoundModel(
(SoundTrigger.KeyphraseSoundModel) model);
try {
soundModelHandle[0] = mService.loadPhraseModel(aidlModel);
} finally {
// TODO(b/219825762): We should be able to use the entire object in a
// try-with-resources
// clause, instead of having to explicitly close internal fields.
if (aidlModel.common.data != null) {
try {
aidlModel.common.data.close();
} catch (IOException e) {
Log.e(TAG, "Failed to close file", e);
}
}
}
return SoundTrigger.STATUS_OK;
}
return SoundTrigger.STATUS_BAD_VALUE;
} catch (Exception e) {
return SoundTrigger.handleException(e);
}
}
/**
* Unload a {@link SoundTrigger.SoundModel} and abort any pendiong recognition
* @param soundModelHandle The sound model handle
* @return - {@link SoundTrigger#STATUS_OK} in case of success
* - {@link SoundTrigger#STATUS_ERROR} in case of unspecified error
* - {@link SoundTrigger#STATUS_PERMISSION_DENIED} if the caller does not have
* system permission
* - {@link SoundTrigger#STATUS_NO_INIT} if the native service cannot be reached
* - {@link SoundTrigger#STATUS_BAD_VALUE} if the sound model handle is invalid
* - {@link SoundTrigger#STATUS_DEAD_OBJECT} if the binder transaction to the native
* service fails
*/
@UnsupportedAppUsage
public synchronized int unloadSoundModel(int soundModelHandle) {
try {
mService.unloadModel(soundModelHandle);
return SoundTrigger.STATUS_OK;
} catch (Exception e) {
return SoundTrigger.handleException(e);
}
}
/**
* Start listening to all key phrases in a {@link SoundTrigger.SoundModel}.
* Recognition must be restarted after each callback (success or failure) received on
* the {@link SoundTrigger.StatusListener}.
* @param soundModelHandle The sound model handle to start listening to
* @param config contains configuration information for this recognition request:
* recognition mode, keyphrases, users, minimum confidence levels...
* @return - {@link SoundTrigger#STATUS_OK} in case of success
* - {@link SoundTrigger#STATUS_ERROR} in case of unspecified error
* - {@link SoundTrigger#STATUS_BUSY} in case of transient resource constraints
* - {@link SoundTrigger#STATUS_PERMISSION_DENIED} if the caller does not have
* system permission
* - {@link SoundTrigger#STATUS_NO_INIT} if the native service cannot be reached
* - {@link SoundTrigger#STATUS_BAD_VALUE} if the sound model handle is invalid
* - {@link SoundTrigger#STATUS_DEAD_OBJECT} if the binder transaction to the native
* service fails
* - {@link SoundTrigger#STATUS_INVALID_OPERATION} if the call is out of sequence
*/
@UnsupportedAppUsage
public synchronized int startRecognition(int soundModelHandle,
SoundTrigger.RecognitionConfig config) {
try {
mService.startRecognition(soundModelHandle,
ConversionUtil.api2aidlRecognitionConfig(config));
return SoundTrigger.STATUS_OK;
} catch (Exception e) {
return SoundTrigger.handleException(e);
}
}
/**
* Stop listening to all key phrases in a {@link SoundTrigger.SoundModel}
* @param soundModelHandle The sound model handle to stop listening to
* @return - {@link SoundTrigger#STATUS_OK} in case of success
* - {@link SoundTrigger#STATUS_ERROR} in case of unspecified error
* - {@link SoundTrigger#STATUS_PERMISSION_DENIED} if the caller does not have
* system permission
* - {@link SoundTrigger#STATUS_NO_INIT} if the native service cannot be reached
* - {@link SoundTrigger#STATUS_BAD_VALUE} if the sound model handle is invalid
* - {@link SoundTrigger#STATUS_DEAD_OBJECT} if the binder transaction to the native
* service fails
* - {@link SoundTrigger#STATUS_INVALID_OPERATION} if the call is out of sequence
*/
@UnsupportedAppUsage
public synchronized int stopRecognition(int soundModelHandle) {
try {
mService.stopRecognition(soundModelHandle);
return SoundTrigger.STATUS_OK;
} catch (Exception e) {
return SoundTrigger.handleException(e);
}
}
/**
* Get the current state of a {@link SoundTrigger.SoundModel}.
* The state will be returned asynchronously as a {@link SoundTrigger.RecognitionEvent}
* in the callback registered in the
* {@link SoundTrigger#attachModule(int, SoundTrigger.StatusListener, Handler)} method.
* @param soundModelHandle The sound model handle indicating which model's state to return
* @return - {@link SoundTrigger#STATUS_OK} in case of success
* - {@link SoundTrigger#STATUS_ERROR} in case of unspecified error
* - {@link SoundTrigger#STATUS_PERMISSION_DENIED} if the caller does not have
* system permission
* - {@link SoundTrigger#STATUS_NO_INIT} if the native service cannot be reached
* - {@link SoundTrigger#STATUS_BAD_VALUE} if the sound model handle is invalid
* - {@link SoundTrigger#STATUS_DEAD_OBJECT} if the binder transaction to the native
* service fails
* - {@link SoundTrigger#STATUS_INVALID_OPERATION} if the call is out of sequence
*/
public synchronized int getModelState(int soundModelHandle) {
try {
mService.forceRecognitionEvent(soundModelHandle);
return SoundTrigger.STATUS_OK;
} catch (Exception e) {
return SoundTrigger.handleException(e);
}
}
/**
* Set a model specific {@link ModelParams} with the given value. This
* parameter will keep its value for the duration the model is loaded regardless of starting
* and stopping recognition. Once the model is unloaded, the value will be lost.
* {@link #queryParameter} should be checked first before calling this method.
*
* @param soundModelHandle handle of model to apply parameter
* @param modelParam {@link ModelParams}
* @param value Value to set
* @return - {@link SoundTrigger#STATUS_OK} in case of success
* - {@link SoundTrigger#STATUS_NO_INIT} if the native service cannot be reached
* - {@link SoundTrigger#STATUS_BAD_VALUE} invalid input parameter
* - {@link SoundTrigger#STATUS_INVALID_OPERATION} if the call is out of sequence or
* if API is not supported by HAL
*/
public synchronized int setParameter(int soundModelHandle, @ModelParams int modelParam,
int value) {
try {
mService.setModelParameter(soundModelHandle,
ConversionUtil.api2aidlModelParameter(modelParam), value);
return SoundTrigger.STATUS_OK;
} catch (Exception e) {
return SoundTrigger.handleException(e);
}
}
/**
* Get a model specific {@link ModelParams}. This parameter will keep its value
* for the duration the model is loaded regardless of starting and stopping recognition.
* Once the model is unloaded, the value will be lost. If the value is not set, a default
* value is returned. See {@link ModelParams} for parameter default values.
* {@link #queryParameter} should be checked first before
* calling this method. Otherwise, an exception can be thrown.
*
* @param soundModelHandle handle of model to get parameter
* @param modelParam {@link ModelParams}
* @return value of parameter
*/
public synchronized int getParameter(int soundModelHandle, @ModelParams int modelParam) {
try {
return mService.getModelParameter(soundModelHandle,
ConversionUtil.api2aidlModelParameter(modelParam));
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
}
}
/**
* Query the parameter support and range for a given {@link ModelParams}.
* This method should be check prior to calling {@link #setParameter} or {@link #getParameter}.
*
* @param soundModelHandle handle of model to get parameter
* @param modelParam {@link ModelParams}
* @return supported range of parameter, null if not supported
*/
@Nullable
public synchronized SoundTrigger.ModelParamRange queryParameter(int soundModelHandle,
@ModelParams int modelParam) {
try {
return ConversionUtil.aidl2apiModelParameterRange(mService.queryModelParameterSupport(
soundModelHandle,
ConversionUtil.api2aidlModelParameter(modelParam)));
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
}
}
private class EventHandlerDelegate extends ISoundTriggerCallback.Stub implements
IBinder.DeathRecipient {
private final Handler mHandler;
EventHandlerDelegate(@NonNull final SoundTrigger.StatusListener listener,
@NonNull Looper looper) {
// construct the event handler with this looper
// implement the event handler delegate
mHandler = new Handler(looper) {
@Override
public void handleMessage(Message msg) {
switch (msg.what) {
case EVENT_RECOGNITION:
listener.onRecognition(
(SoundTrigger.RecognitionEvent) msg.obj);
break;
case EVENT_RESOURCES_AVAILABLE:
listener.onResourcesAvailable();
break;
case EVENT_MODEL_UNLOADED:
listener.onModelUnloaded((Integer) msg.obj);
break;
case EVENT_SERVICE_DIED:
listener.onServiceDied();
break;
default:
Log.e(TAG, "Unknown message: " + msg.toString());
break;
}
}
};
}
@Override
public synchronized void onRecognition(int handle, RecognitionEvent event,
int captureSession)
throws RemoteException {
Message m = mHandler.obtainMessage(EVENT_RECOGNITION,
ConversionUtil.aidl2apiRecognitionEvent(handle, captureSession, event));
mHandler.sendMessage(m);
}
@Override
public synchronized void onPhraseRecognition(int handle, PhraseRecognitionEvent event,
int captureSession)
throws RemoteException {
Message m = mHandler.obtainMessage(EVENT_RECOGNITION,
ConversionUtil.aidl2apiPhraseRecognitionEvent(handle, captureSession, event));
mHandler.sendMessage(m);
}
@Override
public void onModelUnloaded(int modelHandle) throws RemoteException {
Message m = mHandler.obtainMessage(EVENT_MODEL_UNLOADED, modelHandle);
mHandler.sendMessage(m);
}
@Override
public synchronized void onResourcesAvailable() throws RemoteException {
Message m = mHandler.obtainMessage(EVENT_RESOURCES_AVAILABLE);
mHandler.sendMessage(m);
}
@Override
public synchronized void onModuleDied() {
Message m = mHandler.obtainMessage(EVENT_SERVICE_DIED);
mHandler.sendMessage(m);
}
@Override
public synchronized void binderDied() {
Message m = mHandler.obtainMessage(EVENT_SERVICE_DIED);
mHandler.sendMessage(m);
}
}
}
|