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 attachBaseContext(Context base)87 protected final void attachBaseContext(Context base) { 88 super.attachBaseContext(base); 89 mHandler = new Handler(base.getMainLooper()); 90 } 91 setClient(@onNull UUID uuid, @Nullable Bundle params, @NonNull ISoundTriggerDetectionServiceClient client)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 removeClient(@onNull UUID uuid, @Nullable Bundle params)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 onConnected(@onNull UUID uuid, @Nullable Bundle params)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 onDisconnected(@onNull UUID uuid, @Nullable Bundle params)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 onGenericRecognitionEvent(@onNull UUID uuid, @Nullable Bundle params, int opId, @NonNull SoundTrigger.RecognitionEvent event)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 onError(@onNull UUID uuid, @Nullable Bundle params, int opId, int status)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 onStopOperation(@onNull UUID uuid, @Nullable Bundle params, int opId)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 */ operationFinished(@ullable UUID uuid, int opId)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 onBind(Intent intent)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 onUnbind(Intent intent)288 public boolean onUnbind(Intent intent) { 289 mClients.clear(); 290 291 return false; 292 } 293 } 294