Home | History | Annotate | Download | only in soundtrigger
      1 /*
      2  * Copyright (C) 2018 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 com.android.internal.util.function.pooled.PooledLambda.obtainMessage;
     20 
     21 import android.annotation.CallSuper;
     22 import android.annotation.MainThread;
     23 import android.annotation.NonNull;
     24 import android.annotation.Nullable;
     25 import android.annotation.SystemApi;
     26 import android.app.Service;
     27 import android.content.Context;
     28 import android.content.Intent;
     29 import android.hardware.soundtrigger.SoundTrigger;
     30 import android.os.Bundle;
     31 import android.os.Handler;
     32 import android.os.IBinder;
     33 import android.os.ParcelUuid;
     34 import android.os.RemoteException;
     35 import android.util.ArrayMap;
     36 import android.util.Log;
     37 
     38 import com.android.internal.annotations.GuardedBy;
     39 
     40 import java.util.UUID;
     41 
     42 /**
     43  * A service that allows interaction with the actual sound trigger detection on the system.
     44  *
     45  * <p> Sound trigger detection refers to detectors that match generic sound patterns that are
     46  * not voice-based. The voice-based recognition models should utilize the {@link
     47  * android.service.voice.VoiceInteractionService} instead. Access to this class needs to be
     48  * protected by the {@value android.Manifest.permission.BIND_SOUND_TRIGGER_DETECTION_SERVICE}
     49  * permission granted only to the system.
     50  *
     51  * <p>This service has to be explicitly started by an app, the system does not scan for and start
     52  * these services.
     53  *
     54  * <p>If an operation ({@link #onGenericRecognitionEvent}, {@link #onError},
     55  * {@link #onRecognitionPaused}, {@link #onRecognitionResumed}) is triggered the service is
     56  * considered as running in the foreground. Once the operation is processed the service should call
     57  * {@link #operationFinished(UUID, int)}. If this does not happen in
     58  * {@link SoundTriggerManager#getDetectionServiceOperationsTimeout()} milliseconds
     59  * {@link #onStopOperation(UUID, Bundle, int)} is called and the service is unbound.
     60  *
     61  * <p>The total amount of operations per day might be limited.
     62  *
     63  * @hide
     64  */
     65 @SystemApi
     66 public abstract class SoundTriggerDetectionService extends Service {
     67     private static final String LOG_TAG = SoundTriggerDetectionService.class.getSimpleName();
     68 
     69     private static final boolean DEBUG = false;
     70 
     71     private final Object mLock = new Object();
     72 
     73     /**
     74      * Client indexed by model uuid. This is needed for the {@link #operationFinished(UUID, int)}
     75      * callbacks.
     76      */
     77     @GuardedBy("mLock")
     78     private final ArrayMap<UUID, ISoundTriggerDetectionServiceClient> mClients =
     79             new ArrayMap<>();
     80 
     81     private Handler mHandler;
     82 
     83     /**
     84      * @hide
     85      */
     86     @Override
     87     protected final void attachBaseContext(Context base) {
     88         super.attachBaseContext(base);
     89         mHandler = new Handler(base.getMainLooper());
     90     }
     91 
     92     private void setClient(@NonNull UUID uuid, @Nullable Bundle params,
     93             @NonNull ISoundTriggerDetectionServiceClient client) {
     94         if (DEBUG) Log.i(LOG_TAG, uuid + ": handle setClient");
     95 
     96         synchronized (mLock) {
     97             mClients.put(uuid, client);
     98         }
     99         onConnected(uuid, params);
    100     }
    101 
    102     private void removeClient(@NonNull UUID uuid, @Nullable Bundle params) {
    103         if (DEBUG) Log.i(LOG_TAG, uuid + ": handle removeClient");
    104 
    105         synchronized (mLock) {
    106             mClients.remove(uuid);
    107         }
    108         onDisconnected(uuid, params);
    109     }
    110 
    111     /**
    112      * The system has connected to this service for the recognition registered for the model
    113      * {@code uuid}.
    114      *
    115      * <p> This is called before any operations are delivered.
    116      *
    117      * @param uuid   The {@code uuid} of the model the recognitions is registered for
    118      * @param params The {@code params} passed when the recognition was started
    119      */
    120     @MainThread
    121     public void onConnected(@NonNull UUID uuid, @Nullable Bundle params) {
    122         /* do nothing */
    123     }
    124 
    125     /**
    126      * The system has disconnected from this service for the recognition registered for the model
    127      * {@code uuid}.
    128      *
    129      * <p>Once this is called {@link #operationFinished} cannot be called anymore for
    130      * {@code uuid}.
    131      *
    132      * <p> {@link #onConnected(UUID, Bundle)} is called before any further operations are delivered.
    133      *
    134      * @param uuid   The {@code uuid} of the model the recognitions is registered for
    135      * @param params The {@code params} passed when the recognition was started
    136      */
    137     @MainThread
    138     public void onDisconnected(@NonNull UUID uuid, @Nullable Bundle params) {
    139         /* do nothing */
    140     }
    141 
    142     /**
    143      * A new generic sound trigger event has been detected.
    144      *
    145      * @param uuid   The {@code uuid} of the model the recognition is registered for
    146      * @param params The {@code params} passed when the recognition was started
    147      * @param opId The id of this operation. Once the operation is done, this service needs to call
    148      *             {@link #operationFinished(UUID, int)}
    149      * @param event The event that has been detected
    150      */
    151     @MainThread
    152     public void onGenericRecognitionEvent(@NonNull UUID uuid, @Nullable Bundle params, int opId,
    153             @NonNull SoundTrigger.RecognitionEvent event) {
    154         operationFinished(uuid, opId);
    155     }
    156 
    157     /**
    158      * A error has been detected.
    159      *
    160      * @param uuid   The {@code uuid} of the model the recognition is registered for
    161      * @param params The {@code params} passed when the recognition was started
    162      * @param opId The id of this operation. Once the operation is done, this service needs to call
    163      *             {@link #operationFinished(UUID, int)}
    164      * @param status The error code detected
    165      */
    166     @MainThread
    167     public void onError(@NonNull UUID uuid, @Nullable Bundle params, int opId, int status) {
    168         operationFinished(uuid, opId);
    169     }
    170 
    171     /**
    172      * An operation took too long and should be stopped.
    173      *
    174      * @param uuid   The {@code uuid} of the model the recognition is registered for
    175      * @param params The {@code params} passed when the recognition was started
    176      * @param opId The id of the operation that took too long
    177      */
    178     @MainThread
    179     public abstract void onStopOperation(@NonNull UUID uuid, @Nullable Bundle params, int opId);
    180 
    181     /**
    182      * Tell that the system that an operation has been fully processed.
    183      *
    184      * @param uuid The {@code uuid} of the model the recognition is registered for
    185      * @param opId The id of the operation that is processed
    186      */
    187     public final void operationFinished(@Nullable UUID uuid, int opId) {
    188         try {
    189             ISoundTriggerDetectionServiceClient client;
    190             synchronized (mLock) {
    191                 client = mClients.get(uuid);
    192 
    193                 if (client == null) {
    194                     Log.w(LOG_TAG, "operationFinished called, but no client for "
    195                             + uuid + ". Was this called after onDisconnected?");
    196                     return;
    197                 }
    198             }
    199             client.onOpFinished(opId);
    200         } catch (RemoteException e) {
    201             Log.e(LOG_TAG, "operationFinished, remote exception for client " + uuid, e);
    202         }
    203     }
    204 
    205     /**
    206      * @hide
    207      */
    208     @Override
    209     public final IBinder onBind(Intent intent) {
    210         return new ISoundTriggerDetectionService.Stub() {
    211             private final Object mBinderLock = new Object();
    212 
    213             /** Cached params bundles indexed by the model uuid */
    214             @GuardedBy("mBinderLock")
    215             public final ArrayMap<UUID, Bundle> mParams = new ArrayMap<>();
    216 
    217             @Override
    218             public void setClient(ParcelUuid puuid, Bundle params,
    219                     ISoundTriggerDetectionServiceClient client) {
    220                 UUID uuid = puuid.getUuid();
    221                 synchronized (mBinderLock) {
    222                     mParams.put(uuid, params);
    223                 }
    224 
    225                 if (DEBUG) Log.i(LOG_TAG, uuid + ": setClient(" + params + ")");
    226                 mHandler.sendMessage(obtainMessage(SoundTriggerDetectionService::setClient,
    227                         SoundTriggerDetectionService.this, uuid, params, client));
    228             }
    229 
    230             @Override
    231             public void removeClient(ParcelUuid puuid) {
    232                 UUID uuid = puuid.getUuid();
    233                 Bundle params;
    234                 synchronized (mBinderLock) {
    235                     params = mParams.remove(uuid);
    236                 }
    237 
    238                 if (DEBUG) Log.i(LOG_TAG, uuid + ": removeClient");
    239                 mHandler.sendMessage(obtainMessage(SoundTriggerDetectionService::removeClient,
    240                         SoundTriggerDetectionService.this, uuid, params));
    241             }
    242 
    243             @Override
    244             public void onGenericRecognitionEvent(ParcelUuid puuid, int opId,
    245                     SoundTrigger.GenericRecognitionEvent event) {
    246                 UUID uuid = puuid.getUuid();
    247                 Bundle params;
    248                 synchronized (mBinderLock) {
    249                     params = mParams.get(uuid);
    250                 }
    251 
    252                 if (DEBUG) Log.i(LOG_TAG, uuid + "(" + opId + "): onGenericRecognitionEvent");
    253                 mHandler.sendMessage(
    254                         obtainMessage(SoundTriggerDetectionService::onGenericRecognitionEvent,
    255                                 SoundTriggerDetectionService.this, uuid, params, opId, event));
    256             }
    257 
    258             @Override
    259             public void onError(ParcelUuid puuid, int opId, int status) {
    260                 UUID uuid = puuid.getUuid();
    261                 Bundle params;
    262                 synchronized (mBinderLock) {
    263                     params = mParams.get(uuid);
    264                 }
    265 
    266                 if (DEBUG) Log.i(LOG_TAG, uuid + "(" + opId + "): onError(" + status + ")");
    267                 mHandler.sendMessage(obtainMessage(SoundTriggerDetectionService::onError,
    268                         SoundTriggerDetectionService.this, uuid, params, opId, status));
    269             }
    270 
    271             @Override
    272             public void onStopOperation(ParcelUuid puuid, int opId) {
    273                 UUID uuid = puuid.getUuid();
    274                 Bundle params;
    275                 synchronized (mBinderLock) {
    276                     params = mParams.get(uuid);
    277                 }
    278 
    279                 if (DEBUG) Log.i(LOG_TAG, uuid + "(" + opId + "): onStopOperation");
    280                 mHandler.sendMessage(obtainMessage(SoundTriggerDetectionService::onStopOperation,
    281                         SoundTriggerDetectionService.this, uuid, params, opId));
    282             }
    283         };
    284     }
    285 
    286     @CallSuper
    287     @Override
    288     public boolean onUnbind(Intent intent) {
    289         mClients.clear();
    290 
    291         return false;
    292     }
    293 }
    294