/*
 * Copyright (C) 2018 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.media.soundtrigger;

import static com.android.internal.util.function.pooled.PooledLambda.obtainMessage;

import android.annotation.CallSuper;
import android.annotation.MainThread;
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.annotation.SystemApi;
import android.app.Service;
import android.content.Context;
import android.content.Intent;
import android.hardware.soundtrigger.SoundTrigger;
import android.os.Bundle;
import android.os.Handler;
import android.os.IBinder;
import android.os.ParcelUuid;
import android.os.RemoteException;
import android.util.ArrayMap;
import android.util.Log;

import com.android.internal.annotations.GuardedBy;

import java.util.UUID;

A service that allows interaction with the actual sound trigger detection on the system.

Sound trigger detection refers to detectors that match generic sound patterns that are not voice-based. The voice-based recognition models should utilize the VoiceInteractionService instead. Access to this class needs to be protected by the {@value android.Manifest.permission.BIND_SOUND_TRIGGER_DETECTION_SERVICE} permission granted only to the system.

This service has to be explicitly started by an app, the system does not scan for and start these services.

If an operation (onGenericRecognitionEvent, onError, onRecognitionPaused, onRecognitionResumed) is triggered the service is considered as running in the foreground. Once the operation is processed the service should call operationFinished(UUID, int). If this does not happen in SoundTriggerManager.getDetectionServiceOperationsTimeout() milliseconds onStopOperation(UUID, Bundle, int) is called and the service is unbound.

The total amount of operations per day might be limited.

@hide
/** * A service that allows interaction with the actual sound trigger detection on the system. * * <p> Sound trigger detection refers to detectors that match generic sound patterns that are * not voice-based. The voice-based recognition models should utilize the {@link * android.service.voice.VoiceInteractionService} instead. Access to this class needs to be * protected by the {@value android.Manifest.permission.BIND_SOUND_TRIGGER_DETECTION_SERVICE} * permission granted only to the system. * * <p>This service has to be explicitly started by an app, the system does not scan for and start * these services. * * <p>If an operation ({@link #onGenericRecognitionEvent}, {@link #onError}, * {@link #onRecognitionPaused}, {@link #onRecognitionResumed}) is triggered the service is * considered as running in the foreground. Once the operation is processed the service should call * {@link #operationFinished(UUID, int)}. If this does not happen in * {@link SoundTriggerManager#getDetectionServiceOperationsTimeout()} milliseconds * {@link #onStopOperation(UUID, Bundle, int)} is called and the service is unbound. * * <p>The total amount of operations per day might be limited. * * @hide */
@SystemApi public abstract class SoundTriggerDetectionService extends Service { private static final String LOG_TAG = SoundTriggerDetectionService.class.getSimpleName(); private static final boolean DEBUG = false; private final Object mLock = new Object();
Client indexed by model uuid. This is needed for the operationFinished(UUID, int) callbacks.
/** * Client indexed by model uuid. This is needed for the {@link #operationFinished(UUID, int)} * callbacks. */
@GuardedBy("mLock") private final ArrayMap<UUID, ISoundTriggerDetectionServiceClient> mClients = new ArrayMap<>(); private Handler mHandler;
@hide
/** * @hide */
@Override protected final void attachBaseContext(Context base) { super.attachBaseContext(base); mHandler = new Handler(base.getMainLooper()); } private void setClient(@NonNull UUID uuid, @Nullable Bundle params, @NonNull ISoundTriggerDetectionServiceClient client) { if (DEBUG) Log.i(LOG_TAG, uuid + ": handle setClient"); synchronized (mLock) { mClients.put(uuid, client); } onConnected(uuid, params); } private void removeClient(@NonNull UUID uuid, @Nullable Bundle params) { if (DEBUG) Log.i(LOG_TAG, uuid + ": handle removeClient"); synchronized (mLock) { mClients.remove(uuid); } onDisconnected(uuid, params); }
The system has connected to this service for the recognition registered for the model uuid.

This is called before any operations are delivered.

Params:
  • uuid – The uuid of the model the recognitions is registered for
  • params – The params passed when the recognition was started
/** * The system has connected to this service for the recognition registered for the model * {@code uuid}. * * <p> This is called before any operations are delivered. * * @param uuid The {@code uuid} of the model the recognitions is registered for * @param params The {@code params} passed when the recognition was started */
@MainThread public void onConnected(@NonNull UUID uuid, @Nullable Bundle params) { /* do nothing */ }
The system has disconnected from this service for the recognition registered for the model uuid.

Once this is called operationFinished cannot be called anymore for uuid.

onConnected(UUID, Bundle) is called before any further operations are delivered.

Params:
  • uuid – The uuid of the model the recognitions is registered for
  • params – The params passed when the recognition was started
/** * The system has disconnected from this service for the recognition registered for the model * {@code uuid}. * * <p>Once this is called {@link #operationFinished} cannot be called anymore for * {@code uuid}. * * <p> {@link #onConnected(UUID, Bundle)} is called before any further operations are delivered. * * @param uuid The {@code uuid} of the model the recognitions is registered for * @param params The {@code params} passed when the recognition was started */
@MainThread public void onDisconnected(@NonNull UUID uuid, @Nullable Bundle params) { /* do nothing */ }
A new generic sound trigger event has been detected.
Params:
  • uuid – The uuid of the model the recognition is registered for
  • params – The params passed when the recognition was started
  • opId – The id of this operation. Once the operation is done, this service needs to call operationFinished(UUID, int)
  • event – The event that has been detected
/** * A new generic sound trigger event has been detected. * * @param uuid The {@code uuid} of the model the recognition is registered for * @param params The {@code params} passed when the recognition was started * @param opId The id of this operation. Once the operation is done, this service needs to call * {@link #operationFinished(UUID, int)} * @param event The event that has been detected */
@MainThread public void onGenericRecognitionEvent(@NonNull UUID uuid, @Nullable Bundle params, int opId, @NonNull SoundTrigger.RecognitionEvent event) { operationFinished(uuid, opId); }
A error has been detected.
Params:
  • uuid – The uuid of the model the recognition is registered for
  • params – The params passed when the recognition was started
  • opId – The id of this operation. Once the operation is done, this service needs to call operationFinished(UUID, int)
  • status – The error code detected
/** * A error has been detected. * * @param uuid The {@code uuid} of the model the recognition is registered for * @param params The {@code params} passed when the recognition was started * @param opId The id of this operation. Once the operation is done, this service needs to call * {@link #operationFinished(UUID, int)} * @param status The error code detected */
@MainThread public void onError(@NonNull UUID uuid, @Nullable Bundle params, int opId, int status) { operationFinished(uuid, opId); }
An operation took too long and should be stopped.
Params:
  • uuid – The uuid of the model the recognition is registered for
  • params – The params passed when the recognition was started
  • opId – The id of the operation that took too long
/** * An operation took too long and should be stopped. * * @param uuid The {@code uuid} of the model the recognition is registered for * @param params The {@code params} passed when the recognition was started * @param opId The id of the operation that took too long */
@MainThread public abstract void onStopOperation(@NonNull UUID uuid, @Nullable Bundle params, int opId);
Tell that the system that an operation has been fully processed.
Params:
  • uuid – The uuid of the model the recognition is registered for
  • opId – The id of the operation that is processed
/** * Tell that the system that an operation has been fully processed. * * @param uuid The {@code uuid} of the model the recognition is registered for * @param opId The id of the operation that is processed */
public final void operationFinished(@Nullable UUID uuid, int opId) { try { ISoundTriggerDetectionServiceClient client; synchronized (mLock) { client = mClients.get(uuid); if (client == null) { Log.w(LOG_TAG, "operationFinished called, but no client for " + uuid + ". Was this called after onDisconnected?"); return; } } client.onOpFinished(opId); } catch (RemoteException e) { Log.e(LOG_TAG, "operationFinished, remote exception for client " + uuid, e); } }
@hide
/** * @hide */
@Override public final IBinder onBind(Intent intent) { return new ISoundTriggerDetectionService.Stub() { private final Object mBinderLock = new Object();
Cached params bundles indexed by the model uuid
/** Cached params bundles indexed by the model uuid */
@GuardedBy("mBinderLock") public final ArrayMap<UUID, Bundle> mParams = new ArrayMap<>(); @Override public void setClient(ParcelUuid puuid, Bundle params, ISoundTriggerDetectionServiceClient client) { UUID uuid = puuid.getUuid(); synchronized (mBinderLock) { mParams.put(uuid, params); } if (DEBUG) Log.i(LOG_TAG, uuid + ": setClient(" + params + ")"); mHandler.sendMessage(obtainMessage(SoundTriggerDetectionService::setClient, SoundTriggerDetectionService.this, uuid, params, client)); } @Override public void removeClient(ParcelUuid puuid) { UUID uuid = puuid.getUuid(); Bundle params; synchronized (mBinderLock) { params = mParams.remove(uuid); } if (DEBUG) Log.i(LOG_TAG, uuid + ": removeClient"); mHandler.sendMessage(obtainMessage(SoundTriggerDetectionService::removeClient, SoundTriggerDetectionService.this, uuid, params)); } @Override public void onGenericRecognitionEvent(ParcelUuid puuid, int opId, SoundTrigger.GenericRecognitionEvent event) { UUID uuid = puuid.getUuid(); Bundle params; synchronized (mBinderLock) { params = mParams.get(uuid); } if (DEBUG) Log.i(LOG_TAG, uuid + "(" + opId + "): onGenericRecognitionEvent"); mHandler.sendMessage( obtainMessage(SoundTriggerDetectionService::onGenericRecognitionEvent, SoundTriggerDetectionService.this, uuid, params, opId, event)); } @Override public void onError(ParcelUuid puuid, int opId, int status) { UUID uuid = puuid.getUuid(); Bundle params; synchronized (mBinderLock) { params = mParams.get(uuid); } if (DEBUG) Log.i(LOG_TAG, uuid + "(" + opId + "): onError(" + status + ")"); mHandler.sendMessage(obtainMessage(SoundTriggerDetectionService::onError, SoundTriggerDetectionService.this, uuid, params, opId, status)); } @Override public void onStopOperation(ParcelUuid puuid, int opId) { UUID uuid = puuid.getUuid(); Bundle params; synchronized (mBinderLock) { params = mParams.get(uuid); } if (DEBUG) Log.i(LOG_TAG, uuid + "(" + opId + "): onStopOperation"); mHandler.sendMessage(obtainMessage(SoundTriggerDetectionService::onStopOperation, SoundTriggerDetectionService.this, uuid, params, opId)); } }; } @CallSuper @Override public boolean onUnbind(Intent intent) { mClients.clear(); return false; } }