/* * 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 {@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. * *
This service has to be explicitly started by an app, the system does not scan for and start * these services. * *
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. * *
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 {@link #operationFinished(UUID, int)}
* callbacks.
*/
@GuardedBy("mLock")
private final ArrayMap 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
* {@code uuid}.
*
* Once this is called {@link #operationFinished} cannot be called anymore for
* {@code uuid}.
*
* {@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.
*
* @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.
*
* @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.
*
* @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.
*
* @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
*/
@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 */
@GuardedBy("mBinderLock")
public final ArrayMap