blob: f3ca6165a60bd0a6fc320c7fbb2a626d8999e714 [file] [log] [blame]
Arunesh Mishraa772e5f2016-01-25 10:33:11 -08001/*
2 * Copyright (C) 2014 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
17package com.android.server.soundtrigger;
Philip P. Moltmann18e3eb82018-03-09 16:55:55 -080018
19import static android.Manifest.permission.BIND_SOUND_TRIGGER_DETECTION_SERVICE;
20import static android.content.Context.BIND_AUTO_CREATE;
21import static android.content.Context.BIND_FOREGROUND_SERVICE;
22import static android.content.pm.PackageManager.GET_META_DATA;
23import static android.content.pm.PackageManager.GET_SERVICES;
24import static android.content.pm.PackageManager.MATCH_DEBUG_TRIAGED_MISSING;
Arunesh Mishra3fff7f52016-02-09 12:15:19 -080025import static android.hardware.soundtrigger.SoundTrigger.STATUS_ERROR;
Chris Thorntonba08b792017-06-08 22:34:37 -070026import static android.hardware.soundtrigger.SoundTrigger.STATUS_OK;
Philip P. Moltmann18e3eb82018-03-09 16:55:55 -080027import static android.provider.Settings.Global.SOUND_TRIGGER_DETECTION_SERVICE_OP_TIMEOUT;
Arunesh Mishraa772e5f2016-01-25 10:33:11 -080028
Philip P. Moltmann18e3eb82018-03-09 16:55:55 -080029import static com.android.internal.util.function.pooled.PooledLambda.obtainMessage;
30
31import android.Manifest;
32import android.annotation.NonNull;
33import android.annotation.Nullable;
Chris Thorntonba08b792017-06-08 22:34:37 -070034import android.app.PendingIntent;
Philip P. Moltmann18e3eb82018-03-09 16:55:55 -080035import android.content.ComponentName;
Arunesh Mishraa772e5f2016-01-25 10:33:11 -080036import android.content.Context;
Chris Thorntonba08b792017-06-08 22:34:37 -070037import android.content.Intent;
Philip P. Moltmann18e3eb82018-03-09 16:55:55 -080038import android.content.ServiceConnection;
Arunesh Mishraa772e5f2016-01-25 10:33:11 -080039import android.content.pm.PackageManager;
Philip P. Moltmann18e3eb82018-03-09 16:55:55 -080040import android.content.pm.ResolveInfo;
Arunesh Mishraa772e5f2016-01-25 10:33:11 -080041import android.hardware.soundtrigger.IRecognitionStatusCallback;
42import android.hardware.soundtrigger.SoundTrigger;
Arunesh Mishrac722ec412016-01-27 13:29:12 -080043import android.hardware.soundtrigger.SoundTrigger.GenericSoundModel;
Arunesh Mishraa772e5f2016-01-25 10:33:11 -080044import android.hardware.soundtrigger.SoundTrigger.KeyphraseSoundModel;
Arunesh Mishra55a9b002016-02-01 14:06:37 -080045import android.hardware.soundtrigger.SoundTrigger.ModuleProperties;
Arunesh Mishraa772e5f2016-01-25 10:33:11 -080046import android.hardware.soundtrigger.SoundTrigger.RecognitionConfig;
Philip P. Moltmann18e3eb82018-03-09 16:55:55 -080047import android.hardware.soundtrigger.SoundTrigger.SoundModel;
48import android.media.soundtrigger.ISoundTriggerDetectionService;
49import android.media.soundtrigger.ISoundTriggerDetectionServiceClient;
50import android.media.soundtrigger.SoundTriggerDetectionService;
Chris Thorntonba08b792017-06-08 22:34:37 -070051import android.media.soundtrigger.SoundTriggerManager;
Philip P. Moltmann18e3eb82018-03-09 16:55:55 -080052import android.os.Binder;
Chris Thorntonba08b792017-06-08 22:34:37 -070053import android.os.Bundle;
Philip P. Moltmann18e3eb82018-03-09 16:55:55 -080054import android.os.Handler;
55import android.os.IBinder;
56import android.os.Looper;
Arunesh Mishraa772e5f2016-01-25 10:33:11 -080057import android.os.Parcel;
58import android.os.ParcelUuid;
Chris Thorntonba08b792017-06-08 22:34:37 -070059import android.os.PowerManager;
Arunesh Mishraa772e5f2016-01-25 10:33:11 -080060import android.os.RemoteException;
Philip P. Moltmann18e3eb82018-03-09 16:55:55 -080061import android.os.UserHandle;
62import android.provider.Settings;
63import android.util.ArraySet;
Arunesh Mishraa772e5f2016-01-25 10:33:11 -080064import android.util.Slog;
65
Philip P. Moltmann18e3eb82018-03-09 16:55:55 -080066import com.android.internal.annotations.GuardedBy;
Arunesh Mishraa772e5f2016-01-25 10:33:11 -080067import com.android.internal.app.ISoundTriggerService;
Philip P. Moltmann18e3eb82018-03-09 16:55:55 -080068import com.android.internal.util.DumpUtils;
69import com.android.internal.util.Preconditions;
70import com.android.server.SystemService;
Arunesh Mishraa772e5f2016-01-25 10:33:11 -080071
72import java.io.FileDescriptor;
73import java.io.PrintWriter;
Philip P. Moltmann18e3eb82018-03-09 16:55:55 -080074import java.util.ArrayList;
75import java.util.List;
Chris Thorntonba08b792017-06-08 22:34:37 -070076import java.util.TreeMap;
Arunesh Mishraa772e5f2016-01-25 10:33:11 -080077import java.util.UUID;
78
79/**
80 * A single SystemService to manage all sound/voice-based sound models on the DSP.
81 * This services provides apis to manage sound trigger-based sound models via
82 * the ISoundTriggerService interface. This class also publishes a local interface encapsulating
83 * the functionality provided by {@link SoundTriggerHelper} for use by
84 * {@link VoiceInteractionManagerService}.
85 *
86 * @hide
87 */
88public class SoundTriggerService extends SystemService {
Arunesh Mishra3fff7f52016-02-09 12:15:19 -080089 private static final String TAG = "SoundTriggerService";
90 private static final boolean DEBUG = true;
Arunesh Mishraa772e5f2016-01-25 10:33:11 -080091
92 final Context mContext;
Chris Thorntonba08b792017-06-08 22:34:37 -070093 private Object mLock;
Arunesh Mishraa772e5f2016-01-25 10:33:11 -080094 private final SoundTriggerServiceStub mServiceStub;
95 private final LocalSoundTriggerService mLocalSoundTriggerService;
96 private SoundTriggerDbHelper mDbHelper;
Arunesh Mishra3fff7f52016-02-09 12:15:19 -080097 private SoundTriggerHelper mSoundTriggerHelper;
Chris Thorntonba08b792017-06-08 22:34:37 -070098 private final TreeMap<UUID, SoundModel> mLoadedModels;
Chris Thorntonae5fb992017-12-07 18:26:31 -080099 private Object mCallbacksLock;
Philip P. Moltmann18e3eb82018-03-09 16:55:55 -0800100 private final TreeMap<UUID, IRecognitionStatusCallback> mCallbacks;
Chris Thorntonba08b792017-06-08 22:34:37 -0700101 private PowerManager.WakeLock mWakelock;
Arunesh Mishraa772e5f2016-01-25 10:33:11 -0800102
103 public SoundTriggerService(Context context) {
104 super(context);
105 mContext = context;
106 mServiceStub = new SoundTriggerServiceStub();
107 mLocalSoundTriggerService = new LocalSoundTriggerService(context);
Chris Thorntonba08b792017-06-08 22:34:37 -0700108 mLoadedModels = new TreeMap<UUID, SoundModel>();
Chris Thorntonae5fb992017-12-07 18:26:31 -0800109 mCallbacksLock = new Object();
Philip P. Moltmann18e3eb82018-03-09 16:55:55 -0800110 mCallbacks = new TreeMap<>();
Chris Thorntonba08b792017-06-08 22:34:37 -0700111 mLock = new Object();
Arunesh Mishraa772e5f2016-01-25 10:33:11 -0800112 }
113
114 @Override
115 public void onStart() {
116 publishBinderService(Context.SOUND_TRIGGER_SERVICE, mServiceStub);
117 publishLocalService(SoundTriggerInternal.class, mLocalSoundTriggerService);
118 }
119
120 @Override
121 public void onBootPhase(int phase) {
122 if (PHASE_SYSTEM_SERVICES_READY == phase) {
Arunesh Mishra3fff7f52016-02-09 12:15:19 -0800123 initSoundTriggerHelper();
124 mLocalSoundTriggerService.setSoundTriggerHelper(mSoundTriggerHelper);
Arunesh Mishraa772e5f2016-01-25 10:33:11 -0800125 } else if (PHASE_THIRD_PARTY_APPS_CAN_START == phase) {
126 mDbHelper = new SoundTriggerDbHelper(mContext);
127 }
128 }
129
130 @Override
131 public void onStartUser(int userHandle) {
132 }
133
134 @Override
135 public void onSwitchUser(int userHandle) {
136 }
137
Arunesh Mishra3fff7f52016-02-09 12:15:19 -0800138 private synchronized void initSoundTriggerHelper() {
139 if (mSoundTriggerHelper == null) {
140 mSoundTriggerHelper = new SoundTriggerHelper(mContext);
141 }
142 }
143
144 private synchronized boolean isInitialized() {
145 if (mSoundTriggerHelper == null ) {
146 Slog.e(TAG, "SoundTriggerHelper not initialized.");
147 return false;
148 }
149 return true;
150 }
151
Arunesh Mishraa772e5f2016-01-25 10:33:11 -0800152 class SoundTriggerServiceStub extends ISoundTriggerService.Stub {
153 @Override
154 public boolean onTransact(int code, Parcel data, Parcel reply, int flags)
155 throws RemoteException {
156 try {
157 return super.onTransact(code, data, reply, flags);
158 } catch (RuntimeException e) {
159 // The activity manager only throws security exceptions, so let's
160 // log all others.
161 if (!(e instanceof SecurityException)) {
162 Slog.wtf(TAG, "SoundTriggerService Crash", e);
163 }
164 throw e;
165 }
166 }
167
168 @Override
Arunesh Mishra3fff7f52016-02-09 12:15:19 -0800169 public int startRecognition(ParcelUuid parcelUuid, IRecognitionStatusCallback callback,
170 RecognitionConfig config) {
Arunesh Mishraa772e5f2016-01-25 10:33:11 -0800171 enforceCallingPermission(Manifest.permission.MANAGE_SOUND_TRIGGER);
Arunesh Mishra2d1de782016-02-21 18:10:28 -0800172 if (!isInitialized()) return STATUS_ERROR;
Arunesh Mishraa772e5f2016-01-25 10:33:11 -0800173 if (DEBUG) {
174 Slog.i(TAG, "startRecognition(): Uuid : " + parcelUuid);
175 }
Arunesh Mishra3fff7f52016-02-09 12:15:19 -0800176
177 GenericSoundModel model = getSoundModel(parcelUuid);
178 if (model == null) {
179 Slog.e(TAG, "Null model in database for id: " + parcelUuid);
180 return STATUS_ERROR;
181 }
182
183 return mSoundTriggerHelper.startGenericRecognition(parcelUuid.getUuid(), model,
184 callback, config);
Arunesh Mishraa772e5f2016-01-25 10:33:11 -0800185 }
186
187 @Override
Arunesh Mishra3fff7f52016-02-09 12:15:19 -0800188 public int stopRecognition(ParcelUuid parcelUuid, IRecognitionStatusCallback callback) {
Arunesh Mishraa772e5f2016-01-25 10:33:11 -0800189 enforceCallingPermission(Manifest.permission.MANAGE_SOUND_TRIGGER);
190 if (DEBUG) {
191 Slog.i(TAG, "stopRecognition(): Uuid : " + parcelUuid);
192 }
Arunesh Mishra3fff7f52016-02-09 12:15:19 -0800193 if (!isInitialized()) return STATUS_ERROR;
194 return mSoundTriggerHelper.stopGenericRecognition(parcelUuid.getUuid(), callback);
Arunesh Mishraa772e5f2016-01-25 10:33:11 -0800195 }
196
197 @Override
Arunesh Mishrac722ec412016-01-27 13:29:12 -0800198 public SoundTrigger.GenericSoundModel getSoundModel(ParcelUuid soundModelId) {
Arunesh Mishraa772e5f2016-01-25 10:33:11 -0800199 enforceCallingPermission(Manifest.permission.MANAGE_SOUND_TRIGGER);
200 if (DEBUG) {
201 Slog.i(TAG, "getSoundModel(): id = " + soundModelId);
202 }
Arunesh Mishra3fff7f52016-02-09 12:15:19 -0800203 SoundTrigger.GenericSoundModel model = mDbHelper.getGenericSoundModel(
204 soundModelId.getUuid());
Arunesh Mishraa772e5f2016-01-25 10:33:11 -0800205 return model;
206 }
207
208 @Override
Arunesh Mishrac722ec412016-01-27 13:29:12 -0800209 public void updateSoundModel(SoundTrigger.GenericSoundModel soundModel) {
Arunesh Mishraa772e5f2016-01-25 10:33:11 -0800210 enforceCallingPermission(Manifest.permission.MANAGE_SOUND_TRIGGER);
211 if (DEBUG) {
212 Slog.i(TAG, "updateSoundModel(): model = " + soundModel);
213 }
Arunesh Mishrac722ec412016-01-27 13:29:12 -0800214 mDbHelper.updateGenericSoundModel(soundModel);
Arunesh Mishraa772e5f2016-01-25 10:33:11 -0800215 }
216
217 @Override
218 public void deleteSoundModel(ParcelUuid soundModelId) {
219 enforceCallingPermission(Manifest.permission.MANAGE_SOUND_TRIGGER);
220 if (DEBUG) {
221 Slog.i(TAG, "deleteSoundModel(): id = " + soundModelId);
222 }
Arunesh Mishra2d1de782016-02-21 18:10:28 -0800223 // Unload the model if it is loaded.
224 mSoundTriggerHelper.unloadGenericSoundModel(soundModelId.getUuid());
Arunesh Mishrac722ec412016-01-27 13:29:12 -0800225 mDbHelper.deleteGenericSoundModel(soundModelId.getUuid());
Arunesh Mishraa772e5f2016-01-25 10:33:11 -0800226 }
Chris Thorntonba08b792017-06-08 22:34:37 -0700227
228 @Override
229 public int loadGenericSoundModel(GenericSoundModel soundModel) {
230 enforceCallingPermission(Manifest.permission.MANAGE_SOUND_TRIGGER);
231 if (!isInitialized()) return STATUS_ERROR;
232 if (soundModel == null || soundModel.uuid == null) {
233 Slog.e(TAG, "Invalid sound model");
234 return STATUS_ERROR;
235 }
236 if (DEBUG) {
237 Slog.i(TAG, "loadGenericSoundModel(): id = " + soundModel.uuid);
238 }
239 synchronized (mLock) {
240 SoundModel oldModel = mLoadedModels.get(soundModel.uuid);
241 // If the model we're loading is actually different than what we had loaded, we
242 // should unload that other model now. We don't care about return codes since we
243 // don't know if the other model is loaded.
244 if (oldModel != null && !oldModel.equals(soundModel)) {
245 mSoundTriggerHelper.unloadGenericSoundModel(soundModel.uuid);
Chris Thorntonae5fb992017-12-07 18:26:31 -0800246 synchronized (mCallbacksLock) {
Philip P. Moltmann18e3eb82018-03-09 16:55:55 -0800247 mCallbacks.remove(soundModel.uuid);
Chris Thorntonae5fb992017-12-07 18:26:31 -0800248 }
Chris Thorntonba08b792017-06-08 22:34:37 -0700249 }
250 mLoadedModels.put(soundModel.uuid, soundModel);
251 }
252 return STATUS_OK;
253 }
254
255 @Override
256 public int loadKeyphraseSoundModel(KeyphraseSoundModel soundModel) {
257 enforceCallingPermission(Manifest.permission.MANAGE_SOUND_TRIGGER);
258 if (!isInitialized()) return STATUS_ERROR;
259 if (soundModel == null || soundModel.uuid == null) {
260 Slog.e(TAG, "Invalid sound model");
261 return STATUS_ERROR;
262 }
263 if (soundModel.keyphrases == null || soundModel.keyphrases.length != 1) {
264 Slog.e(TAG, "Only one keyphrase per model is currently supported.");
265 return STATUS_ERROR;
266 }
267 if (DEBUG) {
268 Slog.i(TAG, "loadKeyphraseSoundModel(): id = " + soundModel.uuid);
269 }
270 synchronized (mLock) {
271 SoundModel oldModel = mLoadedModels.get(soundModel.uuid);
272 // If the model we're loading is actually different than what we had loaded, we
273 // should unload that other model now. We don't care about return codes since we
274 // don't know if the other model is loaded.
275 if (oldModel != null && !oldModel.equals(soundModel)) {
276 mSoundTriggerHelper.unloadKeyphraseSoundModel(soundModel.keyphrases[0].id);
Chris Thorntonae5fb992017-12-07 18:26:31 -0800277 synchronized (mCallbacksLock) {
Philip P. Moltmann18e3eb82018-03-09 16:55:55 -0800278 mCallbacks.remove(soundModel.uuid);
Chris Thorntonae5fb992017-12-07 18:26:31 -0800279 }
Chris Thorntonba08b792017-06-08 22:34:37 -0700280 }
281 mLoadedModels.put(soundModel.uuid, soundModel);
282 }
283 return STATUS_OK;
284 }
285
286 @Override
Philip P. Moltmann18e3eb82018-03-09 16:55:55 -0800287 public int startRecognitionForService(ParcelUuid soundModelId, Bundle params,
288 ComponentName detectionService, SoundTrigger.RecognitionConfig config) {
289 Preconditions.checkNotNull(soundModelId);
290 Preconditions.checkNotNull(detectionService);
291 Preconditions.checkNotNull(config);
292
293 return startRecognitionForInt(soundModelId,
294 new RemoteSoundTriggerDetectionService(soundModelId.getUuid(),
295 params, detectionService, Binder.getCallingUserHandle(), config), config);
296
297 }
298
299 @Override
Chris Thorntonba08b792017-06-08 22:34:37 -0700300 public int startRecognitionForIntent(ParcelUuid soundModelId, PendingIntent callbackIntent,
301 SoundTrigger.RecognitionConfig config) {
Philip P. Moltmann18e3eb82018-03-09 16:55:55 -0800302 return startRecognitionForInt(soundModelId,
303 new LocalSoundTriggerRecognitionStatusIntentCallback(soundModelId.getUuid(),
304 callbackIntent, config), config);
305 }
306
307 private int startRecognitionForInt(ParcelUuid soundModelId,
308 IRecognitionStatusCallback callback, SoundTrigger.RecognitionConfig config) {
Chris Thorntonba08b792017-06-08 22:34:37 -0700309 enforceCallingPermission(Manifest.permission.MANAGE_SOUND_TRIGGER);
310 if (!isInitialized()) return STATUS_ERROR;
311 if (DEBUG) {
312 Slog.i(TAG, "startRecognition(): id = " + soundModelId);
313 }
314
315 synchronized (mLock) {
316 SoundModel soundModel = mLoadedModels.get(soundModelId.getUuid());
317 if (soundModel == null) {
318 Slog.e(TAG, soundModelId + " is not loaded");
319 return STATUS_ERROR;
320 }
Philip P. Moltmann18e3eb82018-03-09 16:55:55 -0800321 IRecognitionStatusCallback existingCallback = null;
Chris Thorntonae5fb992017-12-07 18:26:31 -0800322 synchronized (mCallbacksLock) {
Philip P. Moltmann18e3eb82018-03-09 16:55:55 -0800323 existingCallback = mCallbacks.get(soundModelId.getUuid());
Chris Thorntonae5fb992017-12-07 18:26:31 -0800324 }
Philip P. Moltmann18e3eb82018-03-09 16:55:55 -0800325 if (existingCallback != null) {
Chris Thorntonba08b792017-06-08 22:34:37 -0700326 Slog.e(TAG, soundModelId + " is already running");
327 return STATUS_ERROR;
328 }
Chris Thorntonba08b792017-06-08 22:34:37 -0700329 int ret;
330 switch (soundModel.type) {
331 case SoundModel.TYPE_KEYPHRASE: {
332 KeyphraseSoundModel keyphraseSoundModel = (KeyphraseSoundModel) soundModel;
333 ret = mSoundTriggerHelper.startKeyphraseRecognition(
Philip P. Moltmann18e3eb82018-03-09 16:55:55 -0800334 keyphraseSoundModel.keyphrases[0].id, keyphraseSoundModel, callback,
335 config);
Chris Thorntonba08b792017-06-08 22:34:37 -0700336 } break;
337 case SoundModel.TYPE_GENERIC_SOUND:
338 ret = mSoundTriggerHelper.startGenericRecognition(soundModel.uuid,
Philip P. Moltmann18e3eb82018-03-09 16:55:55 -0800339 (GenericSoundModel) soundModel, callback, config);
Chris Thorntonba08b792017-06-08 22:34:37 -0700340 break;
341 default:
342 Slog.e(TAG, "Unknown model type");
343 return STATUS_ERROR;
344 }
345
346 if (ret != STATUS_OK) {
347 Slog.e(TAG, "Failed to start model: " + ret);
348 return ret;
349 }
Chris Thorntonae5fb992017-12-07 18:26:31 -0800350 synchronized (mCallbacksLock) {
Philip P. Moltmann18e3eb82018-03-09 16:55:55 -0800351 mCallbacks.put(soundModelId.getUuid(), callback);
Chris Thorntonae5fb992017-12-07 18:26:31 -0800352 }
Chris Thorntonba08b792017-06-08 22:34:37 -0700353 }
354 return STATUS_OK;
355 }
356
357 @Override
358 public int stopRecognitionForIntent(ParcelUuid soundModelId) {
359 enforceCallingPermission(Manifest.permission.MANAGE_SOUND_TRIGGER);
360 if (!isInitialized()) return STATUS_ERROR;
361 if (DEBUG) {
362 Slog.i(TAG, "stopRecognition(): id = " + soundModelId);
363 }
364
365 synchronized (mLock) {
366 SoundModel soundModel = mLoadedModels.get(soundModelId.getUuid());
367 if (soundModel == null) {
368 Slog.e(TAG, soundModelId + " is not loaded");
369 return STATUS_ERROR;
370 }
Philip P. Moltmann18e3eb82018-03-09 16:55:55 -0800371 IRecognitionStatusCallback callback = null;
Chris Thorntonae5fb992017-12-07 18:26:31 -0800372 synchronized (mCallbacksLock) {
Philip P. Moltmann18e3eb82018-03-09 16:55:55 -0800373 callback = mCallbacks.get(soundModelId.getUuid());
Chris Thorntonae5fb992017-12-07 18:26:31 -0800374 }
Chris Thorntonba08b792017-06-08 22:34:37 -0700375 if (callback == null) {
376 Slog.e(TAG, soundModelId + " is not running");
377 return STATUS_ERROR;
378 }
379 int ret;
380 switch (soundModel.type) {
381 case SoundModel.TYPE_KEYPHRASE:
382 ret = mSoundTriggerHelper.stopKeyphraseRecognition(
383 ((KeyphraseSoundModel)soundModel).keyphrases[0].id, callback);
384 break;
385 case SoundModel.TYPE_GENERIC_SOUND:
386 ret = mSoundTriggerHelper.stopGenericRecognition(soundModel.uuid, callback);
387 break;
388 default:
389 Slog.e(TAG, "Unknown model type");
390 return STATUS_ERROR;
391 }
392
393 if (ret != STATUS_OK) {
394 Slog.e(TAG, "Failed to stop model: " + ret);
395 return ret;
396 }
Chris Thorntonae5fb992017-12-07 18:26:31 -0800397 synchronized (mCallbacksLock) {
Philip P. Moltmann18e3eb82018-03-09 16:55:55 -0800398 mCallbacks.remove(soundModelId.getUuid());
Chris Thorntonae5fb992017-12-07 18:26:31 -0800399 }
Chris Thorntonba08b792017-06-08 22:34:37 -0700400 }
401 return STATUS_OK;
402 }
403
404 @Override
405 public int unloadSoundModel(ParcelUuid soundModelId) {
406 enforceCallingPermission(Manifest.permission.MANAGE_SOUND_TRIGGER);
407 if (!isInitialized()) return STATUS_ERROR;
408 if (DEBUG) {
409 Slog.i(TAG, "unloadSoundModel(): id = " + soundModelId);
410 }
411
412 synchronized (mLock) {
413 SoundModel soundModel = mLoadedModels.get(soundModelId.getUuid());
414 if (soundModel == null) {
415 Slog.e(TAG, soundModelId + " is not loaded");
416 return STATUS_ERROR;
417 }
418 int ret;
419 switch (soundModel.type) {
420 case SoundModel.TYPE_KEYPHRASE:
421 ret = mSoundTriggerHelper.unloadKeyphraseSoundModel(
422 ((KeyphraseSoundModel)soundModel).keyphrases[0].id);
423 break;
424 case SoundModel.TYPE_GENERIC_SOUND:
425 ret = mSoundTriggerHelper.unloadGenericSoundModel(soundModel.uuid);
426 break;
427 default:
428 Slog.e(TAG, "Unknown model type");
429 return STATUS_ERROR;
430 }
431 if (ret != STATUS_OK) {
432 Slog.e(TAG, "Failed to unload model");
433 return ret;
434 }
435 mLoadedModels.remove(soundModelId.getUuid());
436 return STATUS_OK;
437 }
438 }
439
440 @Override
441 public boolean isRecognitionActive(ParcelUuid parcelUuid) {
442 enforceCallingPermission(Manifest.permission.MANAGE_SOUND_TRIGGER);
443 if (!isInitialized()) return false;
Chris Thorntonae5fb992017-12-07 18:26:31 -0800444 synchronized (mCallbacksLock) {
Philip P. Moltmann18e3eb82018-03-09 16:55:55 -0800445 IRecognitionStatusCallback callback = mCallbacks.get(parcelUuid.getUuid());
Chris Thorntonba08b792017-06-08 22:34:37 -0700446 if (callback == null) {
447 return false;
448 }
Chris Thorntonba08b792017-06-08 22:34:37 -0700449 }
Chris Thorntonae5fb992017-12-07 18:26:31 -0800450 return mSoundTriggerHelper.isRecognitionRequested(parcelUuid.getUuid());
Chris Thorntonba08b792017-06-08 22:34:37 -0700451 }
Arunesh Mishraa772e5f2016-01-25 10:33:11 -0800452 }
453
Philip P. Moltmann18e3eb82018-03-09 16:55:55 -0800454 private final class LocalSoundTriggerRecognitionStatusIntentCallback
Chris Thorntonba08b792017-06-08 22:34:37 -0700455 extends IRecognitionStatusCallback.Stub {
456 private UUID mUuid;
457 private PendingIntent mCallbackIntent;
458 private RecognitionConfig mRecognitionConfig;
459
Philip P. Moltmann18e3eb82018-03-09 16:55:55 -0800460 public LocalSoundTriggerRecognitionStatusIntentCallback(UUID modelUuid,
Chris Thorntonba08b792017-06-08 22:34:37 -0700461 PendingIntent callbackIntent,
462 RecognitionConfig config) {
463 mUuid = modelUuid;
464 mCallbackIntent = callbackIntent;
465 mRecognitionConfig = config;
466 }
467
468 @Override
469 public boolean pingBinder() {
470 return mCallbackIntent != null;
471 }
472
473 @Override
474 public void onKeyphraseDetected(SoundTrigger.KeyphraseRecognitionEvent event) {
475 if (mCallbackIntent == null) {
476 return;
477 }
478 grabWakeLock();
479
480 Slog.w(TAG, "Keyphrase sound trigger event: " + event);
481 Intent extras = new Intent();
482 extras.putExtra(SoundTriggerManager.EXTRA_MESSAGE_TYPE,
483 SoundTriggerManager.FLAG_MESSAGE_TYPE_RECOGNITION_EVENT);
484 extras.putExtra(SoundTriggerManager.EXTRA_RECOGNITION_EVENT, event);
485 try {
486 mCallbackIntent.send(mContext, 0, extras, mCallbackCompletedHandler, null);
487 if (!mRecognitionConfig.allowMultipleTriggers) {
488 removeCallback(/*releaseWakeLock=*/false);
489 }
490 } catch (PendingIntent.CanceledException e) {
491 removeCallback(/*releaseWakeLock=*/true);
492 }
493 }
494
495 @Override
496 public void onGenericSoundTriggerDetected(SoundTrigger.GenericRecognitionEvent event) {
497 if (mCallbackIntent == null) {
498 return;
499 }
500 grabWakeLock();
501
502 Slog.w(TAG, "Generic sound trigger event: " + event);
503 Intent extras = new Intent();
504 extras.putExtra(SoundTriggerManager.EXTRA_MESSAGE_TYPE,
505 SoundTriggerManager.FLAG_MESSAGE_TYPE_RECOGNITION_EVENT);
506 extras.putExtra(SoundTriggerManager.EXTRA_RECOGNITION_EVENT, event);
507 try {
508 mCallbackIntent.send(mContext, 0, extras, mCallbackCompletedHandler, null);
509 if (!mRecognitionConfig.allowMultipleTriggers) {
510 removeCallback(/*releaseWakeLock=*/false);
511 }
512 } catch (PendingIntent.CanceledException e) {
513 removeCallback(/*releaseWakeLock=*/true);
514 }
515 }
516
517 @Override
518 public void onError(int status) {
519 if (mCallbackIntent == null) {
520 return;
521 }
522 grabWakeLock();
523
524 Slog.i(TAG, "onError: " + status);
525 Intent extras = new Intent();
526 extras.putExtra(SoundTriggerManager.EXTRA_MESSAGE_TYPE,
527 SoundTriggerManager.FLAG_MESSAGE_TYPE_RECOGNITION_ERROR);
528 extras.putExtra(SoundTriggerManager.EXTRA_STATUS, status);
529 try {
530 mCallbackIntent.send(mContext, 0, extras, mCallbackCompletedHandler, null);
531 // Remove the callback, but wait for the intent to finish before we let go of the
532 // wake lock
533 removeCallback(/*releaseWakeLock=*/false);
534 } catch (PendingIntent.CanceledException e) {
535 removeCallback(/*releaseWakeLock=*/true);
536 }
537 }
538
539 @Override
540 public void onRecognitionPaused() {
541 if (mCallbackIntent == null) {
542 return;
543 }
544 grabWakeLock();
545
546 Slog.i(TAG, "onRecognitionPaused");
547 Intent extras = new Intent();
548 extras.putExtra(SoundTriggerManager.EXTRA_MESSAGE_TYPE,
549 SoundTriggerManager.FLAG_MESSAGE_TYPE_RECOGNITION_PAUSED);
550 try {
551 mCallbackIntent.send(mContext, 0, extras, mCallbackCompletedHandler, null);
552 } catch (PendingIntent.CanceledException e) {
553 removeCallback(/*releaseWakeLock=*/true);
554 }
555 }
556
557 @Override
558 public void onRecognitionResumed() {
559 if (mCallbackIntent == null) {
560 return;
561 }
562 grabWakeLock();
563
564 Slog.i(TAG, "onRecognitionResumed");
565 Intent extras = new Intent();
566 extras.putExtra(SoundTriggerManager.EXTRA_MESSAGE_TYPE,
567 SoundTriggerManager.FLAG_MESSAGE_TYPE_RECOGNITION_RESUMED);
568 try {
569 mCallbackIntent.send(mContext, 0, extras, mCallbackCompletedHandler, null);
570 } catch (PendingIntent.CanceledException e) {
571 removeCallback(/*releaseWakeLock=*/true);
572 }
573 }
574
575 private void removeCallback(boolean releaseWakeLock) {
576 mCallbackIntent = null;
Chris Thorntonae5fb992017-12-07 18:26:31 -0800577 synchronized (mCallbacksLock) {
Philip P. Moltmann18e3eb82018-03-09 16:55:55 -0800578 mCallbacks.remove(mUuid);
Chris Thorntonba08b792017-06-08 22:34:37 -0700579 if (releaseWakeLock) {
580 mWakelock.release();
581 }
582 }
583 }
584 }
585
Philip P. Moltmann18e3eb82018-03-09 16:55:55 -0800586 private interface Operation {
587 void run(int opId, ISoundTriggerDetectionService service) throws RemoteException;
588 }
589
590 /**
591 * Local end for a {@link SoundTriggerDetectionService}. Operations are queued up and executed
592 * when the service connects.
593 *
594 * <p>If operations take too long they are forcefully aborted.
595 *
596 * <p>This also limits the amount of operations in 24 hours.
597 */
598 private class RemoteSoundTriggerDetectionService
599 extends IRecognitionStatusCallback.Stub implements ServiceConnection {
600 private static final int MSG_STOP_ALL_PENDING_OPERATIONS = 1;
601
602 private final Object mRemoteServiceLock = new Object();
603
604 /** UUID of the model the service is started for */
605 private final @NonNull ParcelUuid mPuuid;
606 /** Params passed into the start method for the service */
607 private final @Nullable Bundle mParams;
608 /** Component name passed when starting the service */
609 private final @NonNull ComponentName mServiceName;
610 /** User that started the service */
611 private final @NonNull UserHandle mUser;
612 /** Configuration of the recognition the service is handling */
613 private final @NonNull RecognitionConfig mRecognitionConfig;
614 /** Wake lock keeping the remote service alive */
615 private final @NonNull PowerManager.WakeLock mRemoteServiceWakeLock;
616
617 private final @NonNull Handler mHandler;
618
619 /** Callbacks that are called by the service */
620 private final @NonNull ISoundTriggerDetectionServiceClient mClient;
621
622 /** Operations that are pending because the service is not yet connected */
623 @GuardedBy("mRemoteServiceLock")
624 private final ArrayList<Operation> mPendingOps = new ArrayList<>();
625 /** Operations that have been send to the service but have no yet finished */
626 @GuardedBy("mRemoteServiceLock")
627 private final ArraySet<Integer> mRunningOpIds = new ArraySet<>();
628
629 /** The service binder if connected */
630 @GuardedBy("mRemoteServiceLock")
631 private @Nullable ISoundTriggerDetectionService mService;
632 /** Whether the service has been bound */
633 @GuardedBy("mRemoteServiceLock")
634 private boolean mIsBound;
635 /** Whether the service has been destroyed */
636 @GuardedBy("mRemoteServiceLock")
637 private boolean mIsDestroyed;
638 /**
639 * Set once a final op is scheduled. No further ops can be added and the service is
640 * destroyed once the op finishes.
641 */
642 @GuardedBy("mRemoteServiceLock")
643 private boolean mDestroyOnceRunningOpsDone;
644
645 /** Total number of operations performed by this service */
646 @GuardedBy("mRemoteServiceLock")
647 private int mNumTotalOpsPerformed;
648
649 /**
650 * Create a new remote sound trigger detection service. This only binds to the service when
651 * operations are in flight. Each operation has a certain time it can run. Once no
652 * operations are allowed to run anymore, {@link #stopAllPendingOperations() all operations
653 * are aborted and stopped} and the service is disconnected.
654 *
655 * @param modelUuid The UUID of the model the recognition is for
656 * @param params The params passed to each method of the service
657 * @param serviceName The component name of the service
658 * @param user The user of the service
659 * @param config The configuration of the recognition
660 */
661 public RemoteSoundTriggerDetectionService(@NonNull UUID modelUuid,
662 @Nullable Bundle params, @NonNull ComponentName serviceName, @NonNull UserHandle user,
663 @NonNull RecognitionConfig config) {
664 mPuuid = new ParcelUuid(modelUuid);
665 mParams = params;
666 mServiceName = serviceName;
667 mUser = user;
668 mRecognitionConfig = config;
669 mHandler = new Handler(Looper.getMainLooper());
670
671 PowerManager pm = ((PowerManager) mContext.getSystemService(Context.POWER_SERVICE));
672 mRemoteServiceWakeLock = pm.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK,
673 "RemoteSoundTriggerDetectionService " + mServiceName.getPackageName() + ":"
674 + mServiceName.getClassName());
675
676 mClient = new ISoundTriggerDetectionServiceClient.Stub() {
677 @Override
678 public void onOpFinished(int opId) {
679 long token = Binder.clearCallingIdentity();
680 try {
681 synchronized (mRemoteServiceLock) {
682 mRunningOpIds.remove(opId);
683
684 if (mRunningOpIds.isEmpty() && mPendingOps.isEmpty()) {
685 if (mDestroyOnceRunningOpsDone) {
686 destroy();
687 } else {
688 disconnectLocked();
689 }
690 }
691 }
692 } finally {
693 Binder.restoreCallingIdentity(token);
694 }
695 }
696 };
697 }
698
699 @Override
700 public boolean pingBinder() {
701 return !(mIsDestroyed || mDestroyOnceRunningOpsDone);
702 }
703
704 /**
705 * Disconnect from the service, but allow to re-connect when new operations are triggered.
706 */
707 private void disconnectLocked() {
708 if (mService != null) {
709 try {
710 mService.removeClient(mPuuid);
711 } catch (Exception e) {
712 Slog.e(TAG, mPuuid + ": Cannot remove client", e);
713 }
714
715 mService = null;
716 }
717
718 if (mIsBound) {
719 mContext.unbindService(RemoteSoundTriggerDetectionService.this);
720 mIsBound = false;
721
722 synchronized (mCallbacksLock) {
723 mRemoteServiceWakeLock.release();
724 }
725 }
726 }
727
728 /**
729 * Disconnect, do not allow to reconnect to the service. All further operations will be
730 * dropped.
731 */
732 private void destroy() {
733 if (DEBUG) Slog.v(TAG, mPuuid + ": destroy");
734
735 synchronized (mRemoteServiceLock) {
736 disconnectLocked();
737
738 mIsDestroyed = true;
739 }
740
741 // The callback is removed before the flag is set
742 if (!mDestroyOnceRunningOpsDone) {
743 synchronized (mCallbacksLock) {
744 mCallbacks.remove(mPuuid.getUuid());
745 }
746 }
747 }
748
749 /**
750 * Stop all pending operations and then disconnect for the service.
751 */
752 private void stopAllPendingOperations() {
753 synchronized (mRemoteServiceLock) {
754 if (mIsDestroyed) {
755 return;
756 }
757
758 if (mService != null) {
759 int numOps = mRunningOpIds.size();
760 for (int i = 0; i < numOps; i++) {
761 try {
762 mService.onStopOperation(mPuuid, mRunningOpIds.valueAt(i));
763 } catch (Exception e) {
764 Slog.e(TAG, mPuuid + ": Could not stop operation "
765 + mRunningOpIds.valueAt(i), e);
766 }
767 }
768
769 mRunningOpIds.clear();
770 }
771
772 disconnectLocked();
773 }
774 }
775
776 /**
777 * Verify that the service has the expected properties and then bind to the service
778 */
779 private void bind() {
780 long token = Binder.clearCallingIdentity();
781 try {
782 Intent i = new Intent();
783 i.setComponent(mServiceName);
784
785 ResolveInfo ri = mContext.getPackageManager().resolveServiceAsUser(i,
786 GET_SERVICES | GET_META_DATA | MATCH_DEBUG_TRIAGED_MISSING,
787 mUser.getIdentifier());
788
789 if (ri == null) {
790 Slog.w(TAG, mPuuid + ": " + mServiceName + " not found");
791 return;
792 }
793
794 if (!BIND_SOUND_TRIGGER_DETECTION_SERVICE
795 .equals(ri.serviceInfo.permission)) {
796 Slog.w(TAG, mPuuid + ": " + mServiceName + " does not require "
797 + BIND_SOUND_TRIGGER_DETECTION_SERVICE);
798 return;
799 }
800
801 mIsBound = mContext.bindServiceAsUser(i, this,
802 BIND_AUTO_CREATE | BIND_FOREGROUND_SERVICE, mUser);
803
804 if (mIsBound) {
805 mRemoteServiceWakeLock.acquire();
806 } else {
807 Slog.w(TAG, mPuuid + ": Could not bind to " + mServiceName);
808 }
809 } finally {
810 Binder.restoreCallingIdentity(token);
811 }
812 }
813
814 /**
815 * Run an operation (i.e. send it do the service). If the service is not connected, this
816 * binds the service and then runs the operation once connected.
817 *
818 * @param op The operation to run
819 */
820 private void runOrAddOperation(Operation op) {
821 synchronized (mRemoteServiceLock) {
822 if (mIsDestroyed || mDestroyOnceRunningOpsDone) {
823 return;
824 }
825
826 if (mService == null) {
827 mPendingOps.add(op);
828
829 if (!mIsBound) {
830 bind();
831 }
832 } else {
833 // Find a free opID
834 int opId = mNumTotalOpsPerformed;
835 do {
836 mNumTotalOpsPerformed++;
837 } while (mRunningOpIds.contains(opId));
838
839 // Run OP
840 try {
841 if (DEBUG) Slog.v(TAG, mPuuid + ": runOp " + opId);
842
843 op.run(opId, mService);
844 mRunningOpIds.add(opId);
845 } catch (Exception e) {
846 Slog.e(TAG, mPuuid + ": Could not run operation " + opId, e);
847 }
848
849 // Unbind from service if no operations are left (i.e. if the operation failed)
850 if (mPendingOps.isEmpty() && mRunningOpIds.isEmpty()) {
851 if (mDestroyOnceRunningOpsDone) {
852 destroy();
853 } else {
854 disconnectLocked();
855 }
856 } else {
857 mHandler.removeMessages(MSG_STOP_ALL_PENDING_OPERATIONS);
858 mHandler.sendMessageDelayed(obtainMessage(
859 RemoteSoundTriggerDetectionService::stopAllPendingOperations, this)
860 .setWhat(MSG_STOP_ALL_PENDING_OPERATIONS),
861 Settings.Global.getLong(mContext.getContentResolver(),
862 SOUND_TRIGGER_DETECTION_SERVICE_OP_TIMEOUT,
863 Long.MAX_VALUE));
864 }
865 }
866 }
867 }
868
869 @Override
870 public void onKeyphraseDetected(SoundTrigger.KeyphraseRecognitionEvent event) {
871 Slog.w(TAG, mPuuid + "->" + mServiceName + ": IGNORED onKeyphraseDetected(" + event
872 + ")");
873 }
874
875 @Override
876 public void onGenericSoundTriggerDetected(SoundTrigger.GenericRecognitionEvent event) {
877 if (DEBUG) Slog.v(TAG, mPuuid + ": Generic sound trigger event: " + event);
878
879 runOrAddOperation((opId, service) -> {
880 if (!mRecognitionConfig.allowMultipleTriggers) {
881 synchronized (mCallbacksLock) {
882 mCallbacks.remove(mPuuid.getUuid());
883 }
884 mDestroyOnceRunningOpsDone = true;
885 }
886
887 service.onGenericRecognitionEvent(mPuuid, opId, event);
888 });
889 }
890
891 @Override
892 public void onError(int status) {
893 if (DEBUG) Slog.v(TAG, mPuuid + ": onError: " + status);
894
895 runOrAddOperation((opId, service) -> {
896 synchronized (mCallbacksLock) {
897 mCallbacks.remove(mPuuid.getUuid());
898 }
899 mDestroyOnceRunningOpsDone = true;
900
901 service.onError(mPuuid, opId, status);
902 });
903 }
904
905 @Override
906 public void onRecognitionPaused() {
907 Slog.i(TAG, mPuuid + "->" + mServiceName + ": IGNORED onRecognitionPaused");
908 }
909
910 @Override
911 public void onRecognitionResumed() {
912 Slog.i(TAG, mPuuid + "->" + mServiceName + ": IGNORED onRecognitionResumed");
913 }
914
915 @Override
916 public void onServiceConnected(ComponentName name, IBinder service) {
917 if (DEBUG) Slog.v(TAG, mPuuid + ": onServiceConnected(" + service + ")");
918
919 synchronized (mRemoteServiceLock) {
920 mService = ISoundTriggerDetectionService.Stub.asInterface(service);
921
922 try {
923 mService.setClient(mPuuid, mParams, mClient);
924 } catch (Exception e) {
925 Slog.e(TAG, mPuuid + ": Could not init " + mServiceName, e);
926 return;
927 }
928
929 while (!mPendingOps.isEmpty()) {
930 runOrAddOperation(mPendingOps.remove(0));
931 }
932 }
933 }
934
935 @Override
936 public void onServiceDisconnected(ComponentName name) {
937 if (DEBUG) Slog.v(TAG, mPuuid + ": onServiceDisconnected");
938
939 synchronized (mRemoteServiceLock) {
940 mService = null;
941 }
942 }
943
944 @Override
945 public void onBindingDied(ComponentName name) {
946 if (DEBUG) Slog.v(TAG, mPuuid + ": onBindingDied");
947
948 synchronized (mRemoteServiceLock) {
949 destroy();
950 }
951 }
952
953 @Override
954 public void onNullBinding(ComponentName name) {
955 Slog.w(TAG, name + " for model " + mPuuid + " returned a null binding");
956
957 synchronized (mRemoteServiceLock) {
958 disconnectLocked();
959 }
960 }
961 }
962
Chris Thorntonba08b792017-06-08 22:34:37 -0700963 private void grabWakeLock() {
Chris Thorntonae5fb992017-12-07 18:26:31 -0800964 synchronized (mCallbacksLock) {
Chris Thorntonba08b792017-06-08 22:34:37 -0700965 if (mWakelock == null) {
966 PowerManager pm = ((PowerManager) mContext.getSystemService(Context.POWER_SERVICE));
967 mWakelock = pm.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, TAG);
968 }
969 mWakelock.acquire();
970 }
971 }
972
973 private PendingIntent.OnFinished mCallbackCompletedHandler = new PendingIntent.OnFinished() {
974 @Override
975 public void onSendFinished(PendingIntent pendingIntent, Intent intent, int resultCode,
976 String resultData, Bundle resultExtras) {
977 // We're only ever invoked when the callback is done, so release the lock.
Chris Thorntonae5fb992017-12-07 18:26:31 -0800978 synchronized (mCallbacksLock) {
Chris Thorntonba08b792017-06-08 22:34:37 -0700979 mWakelock.release();
980 }
981 }
982 };
983
Arunesh Mishraa772e5f2016-01-25 10:33:11 -0800984 public final class LocalSoundTriggerService extends SoundTriggerInternal {
985 private final Context mContext;
986 private SoundTriggerHelper mSoundTriggerHelper;
987
988 LocalSoundTriggerService(Context context) {
989 mContext = context;
990 }
991
Arunesh Mishra3fff7f52016-02-09 12:15:19 -0800992 synchronized void setSoundTriggerHelper(SoundTriggerHelper helper) {
993 mSoundTriggerHelper = helper;
Arunesh Mishraa772e5f2016-01-25 10:33:11 -0800994 }
995
996 @Override
997 public int startRecognition(int keyphraseId, KeyphraseSoundModel soundModel,
998 IRecognitionStatusCallback listener, RecognitionConfig recognitionConfig) {
Arunesh Mishra3fff7f52016-02-09 12:15:19 -0800999 if (!isInitialized()) return STATUS_ERROR;
1000 return mSoundTriggerHelper.startKeyphraseRecognition(keyphraseId, soundModel, listener,
Arunesh Mishraa772e5f2016-01-25 10:33:11 -08001001 recognitionConfig);
1002 }
1003
1004 @Override
Arunesh Mishra3fff7f52016-02-09 12:15:19 -08001005 public synchronized int stopRecognition(int keyphraseId, IRecognitionStatusCallback listener) {
1006 if (!isInitialized()) return STATUS_ERROR;
1007 return mSoundTriggerHelper.stopKeyphraseRecognition(keyphraseId, listener);
Arunesh Mishraa772e5f2016-01-25 10:33:11 -08001008 }
1009
1010 @Override
Arunesh Mishra55a9b002016-02-01 14:06:37 -08001011 public ModuleProperties getModuleProperties() {
Arunesh Mishra3fff7f52016-02-09 12:15:19 -08001012 if (!isInitialized()) return null;
Arunesh Mishra55a9b002016-02-01 14:06:37 -08001013 return mSoundTriggerHelper.getModuleProperties();
1014 }
1015
1016 @Override
Arunesh Mishra2d1de782016-02-21 18:10:28 -08001017 public int unloadKeyphraseModel(int keyphraseId) {
1018 if (!isInitialized()) return STATUS_ERROR;
1019 return mSoundTriggerHelper.unloadKeyphraseSoundModel(keyphraseId);
1020 }
1021
1022 @Override
Arunesh Mishraa772e5f2016-01-25 10:33:11 -08001023 public void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
Arunesh Mishra3fff7f52016-02-09 12:15:19 -08001024 if (!isInitialized()) return;
Arunesh Mishraa772e5f2016-01-25 10:33:11 -08001025 mSoundTriggerHelper.dump(fd, pw, args);
1026 }
Arunesh Mishra3fff7f52016-02-09 12:15:19 -08001027
1028 private synchronized boolean isInitialized() {
1029 if (mSoundTriggerHelper == null ) {
1030 Slog.e(TAG, "SoundTriggerHelper not initialized.");
1031 return false;
1032 }
1033 return true;
1034 }
Arunesh Mishraa772e5f2016-01-25 10:33:11 -08001035 }
1036
1037 private void enforceCallingPermission(String permission) {
1038 if (mContext.checkCallingOrSelfPermission(permission)
1039 != PackageManager.PERMISSION_GRANTED) {
1040 throw new SecurityException("Caller does not hold the permission " + permission);
1041 }
1042 }
1043}