1 /** 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 17 package android.service.voice; 18 19 import android.Manifest; 20 import android.annotation.CallbackExecutor; 21 import android.annotation.NonNull; 22 import android.annotation.Nullable; 23 import android.annotation.RequiresPermission; 24 import android.annotation.SdkConstant; 25 import android.annotation.SuppressLint; 26 import android.annotation.SystemApi; 27 import android.annotation.TestApi; 28 import android.app.ActivityThread; 29 import android.app.Service; 30 import android.app.compat.CompatChanges; 31 import android.compat.annotation.ChangeId; 32 import android.compat.annotation.EnabledSince; 33 import android.compat.annotation.UnsupportedAppUsage; 34 import android.content.ComponentName; 35 import android.content.Context; 36 import android.content.Intent; 37 import android.hardware.soundtrigger.KeyphraseEnrollmentInfo; 38 import android.hardware.soundtrigger.SoundTrigger; 39 import android.media.permission.Identity; 40 import android.media.voice.KeyphraseModelManager; 41 import android.os.Build; 42 import android.os.Bundle; 43 import android.os.Handler; 44 import android.os.IBinder; 45 import android.os.PersistableBundle; 46 import android.os.RemoteException; 47 import android.os.ServiceManager; 48 import android.os.SharedMemory; 49 import android.os.SystemProperties; 50 import android.provider.Settings; 51 import android.util.ArraySet; 52 import android.util.Log; 53 54 import com.android.internal.annotations.GuardedBy; 55 import com.android.internal.annotations.VisibleForTesting; 56 import com.android.internal.app.IVoiceActionCheckCallback; 57 import com.android.internal.app.IVoiceInteractionManagerService; 58 import com.android.internal.util.function.pooled.PooledLambda; 59 60 import java.io.FileDescriptor; 61 import java.io.PrintWriter; 62 import java.util.ArrayList; 63 import java.util.Collections; 64 import java.util.List; 65 import java.util.Locale; 66 import java.util.Objects; 67 import java.util.Set; 68 import java.util.concurrent.Executor; 69 70 /** 71 * Top-level service of the current global voice interactor, which is providing 72 * support for hotwording, the back-end of a {@link android.app.VoiceInteractor}, etc. 73 * The current VoiceInteractionService that has been selected by the user is kept 74 * always running by the system, to allow it to do things like listen for hotwords 75 * in the background to instigate voice interactions. 76 * 77 * <p>Because this service is always running, it should be kept as lightweight as 78 * possible. Heavy-weight operations (including showing UI) should be implemented 79 * in the associated {@link android.service.voice.VoiceInteractionSessionService} when 80 * an actual voice interaction is taking place, and that service should run in a 81 * separate process from this one. 82 */ 83 public class VoiceInteractionService extends Service { 84 static final String TAG = VoiceInteractionService.class.getSimpleName(); 85 86 /** 87 * The {@link Intent} that must be declared as handled by the service. 88 * To be supported, the service must also require the 89 * {@link android.Manifest.permission#BIND_VOICE_INTERACTION} permission so 90 * that other applications can not abuse it. 91 */ 92 @SdkConstant(SdkConstant.SdkConstantType.SERVICE_ACTION) 93 public static final String SERVICE_INTERFACE = 94 "android.service.voice.VoiceInteractionService"; 95 96 /** 97 * Name under which a VoiceInteractionService component publishes information about itself. 98 * This meta-data should reference an XML resource containing a 99 * <code><{@link 100 * android.R.styleable#VoiceInteractionService voice-interaction-service}></code> tag. 101 */ 102 public static final String SERVICE_META_DATA = "android.voice_interaction"; 103 104 /** 105 * For apps targeting Build.VERSION_CODES.UPSIDE_DOWN_CAKE and above, implementors of this 106 * service can create multiple AlwaysOnHotwordDetector instances in parallel. They will 107 * also e ale to create a single SoftwareHotwordDetector in parallel with any other 108 * active AlwaysOnHotwordDetector instances. 109 * 110 * <p>Requirements when this change is enabled: 111 * <ul> 112 * <li> 113 * Any number of AlwaysOnHotwordDetector instances can be created in parallel 114 * as long as they are unique to any other active AlwaysOnHotwordDetector. 115 * </li> 116 * <li> 117 * Only a single instance of SoftwareHotwordDetector can be active at a given 118 * time. It can be active at the same time as any number of 119 * AlwaysOnHotwordDetector instances. 120 * </li> 121 * <li> 122 * To release that reference and any resources associated with that reference, 123 * HotwordDetector#destroy() must be called. An attempt to create an 124 * HotwordDetector equal to an active HotwordDetector will be rejected 125 * until HotwordDetector#destroy() is called on the active instance. 126 * </li> 127 * </ul> 128 * 129 * @hide 130 */ 131 @ChangeId 132 @EnabledSince(targetSdkVersion = Build.VERSION_CODES.UPSIDE_DOWN_CAKE) 133 static final long MULTIPLE_ACTIVE_HOTWORD_DETECTORS = 193232191L; 134 135 private static final boolean SYSPROP_VISUAL_QUERY_SERVICE_ENABLED = 136 SystemProperties.getBoolean("ro.hotword.visual_query_service_enabled", false); 137 138 IVoiceInteractionService mInterface = new IVoiceInteractionService.Stub() { 139 @Override 140 public void ready() { 141 Handler.getMain().executeOrSendMessage(PooledLambda.obtainMessage( 142 VoiceInteractionService::onReady, VoiceInteractionService.this)); 143 } 144 145 @Override 146 public void shutdown() { 147 Handler.getMain().executeOrSendMessage(PooledLambda.obtainMessage( 148 VoiceInteractionService::onShutdownInternal, VoiceInteractionService.this)); 149 } 150 151 @Override 152 public void soundModelsChanged() { 153 Handler.getMain().executeOrSendMessage(PooledLambda.obtainMessage( 154 VoiceInteractionService::onSoundModelsChangedInternal, 155 VoiceInteractionService.this)); 156 } 157 158 @Override 159 public void launchVoiceAssistFromKeyguard() { 160 Handler.getMain().executeOrSendMessage(PooledLambda.obtainMessage( 161 VoiceInteractionService::onLaunchVoiceAssistFromKeyguard, 162 VoiceInteractionService.this)); 163 } 164 165 @Override 166 public void getActiveServiceSupportedActions(List<String> voiceActions, 167 IVoiceActionCheckCallback callback) { 168 Handler.getMain().executeOrSendMessage( 169 PooledLambda.obtainMessage(VoiceInteractionService::onHandleVoiceActionCheck, 170 VoiceInteractionService.this, 171 voiceActions, 172 callback)); 173 } 174 175 @Override 176 public void prepareToShowSession(Bundle args, int flags) { 177 Handler.getMain().executeOrSendMessage(PooledLambda.obtainMessage( 178 VoiceInteractionService::onPrepareToShowSession, 179 VoiceInteractionService.this, args, flags)); 180 } 181 182 @Override 183 public void showSessionFailed(@NonNull Bundle args) { 184 Handler.getMain().executeOrSendMessage(PooledLambda.obtainMessage( 185 VoiceInteractionService::onShowSessionFailed, 186 VoiceInteractionService.this, args)); 187 } 188 189 @Override 190 public void detectorRemoteExceptionOccurred(@NonNull IBinder token, int detectorType) { 191 Log.d(TAG, "detectorRemoteExceptionOccurred"); 192 Handler.getMain().executeOrSendMessage(PooledLambda.obtainMessage( 193 VoiceInteractionService::onDetectorRemoteException, 194 VoiceInteractionService.this, token, detectorType)); 195 } 196 }; 197 198 IVoiceInteractionManagerService mSystemService; 199 200 private VisualQueryDetector mActiveVisualQueryDetector; 201 202 private final Object mLock = new Object(); 203 204 private KeyphraseEnrollmentInfo mKeyphraseEnrollmentInfo; 205 206 private final Set<HotwordDetector> mActiveDetectors = new ArraySet<>(); 207 208 // True if any of the createAOHD methods should use the test ST module. 209 @GuardedBy("mLock") 210 private boolean mTestModuleForAlwaysOnHotwordDetectorEnabled = false; 211 onDetectorRemoteException(@onNull IBinder token, int detectorType)212 private void onDetectorRemoteException(@NonNull IBinder token, int detectorType) { 213 Log.d(TAG, "onDetectorRemoteException for " + HotwordDetector.detectorTypeToString( 214 detectorType)); 215 mActiveDetectors.forEach(detector -> { 216 // TODO: handle normal detector, VQD 217 if (detectorType == HotwordDetector.DETECTOR_TYPE_TRUSTED_HOTWORD_DSP 218 && detector instanceof AlwaysOnHotwordDetector) { 219 AlwaysOnHotwordDetector alwaysOnDetector = (AlwaysOnHotwordDetector) detector; 220 if (alwaysOnDetector.isSameToken(token)) { 221 alwaysOnDetector.onDetectorRemoteException(); 222 } 223 } else if (detectorType == HotwordDetector.DETECTOR_TYPE_TRUSTED_HOTWORD_SOFTWARE 224 && detector instanceof SoftwareHotwordDetector) { 225 SoftwareHotwordDetector softwareDetector = (SoftwareHotwordDetector) detector; 226 if (softwareDetector.isSameToken(token)) { 227 softwareDetector.onDetectorRemoteException(); 228 } 229 } 230 }); 231 } 232 233 /** 234 * Called when a user has activated an affordance to launch voice assist from the Keyguard. 235 * 236 * <p>This method will only be called if the VoiceInteractionService has set 237 * {@link android.R.attr#supportsLaunchVoiceAssistFromKeyguard} and the Keyguard is showing.</p> 238 * 239 * <p>A valid implementation must start a new activity that should use {@link 240 * android.view.WindowManager.LayoutParams#FLAG_SHOW_WHEN_LOCKED} to display 241 * on top of the lock screen.</p> 242 */ onLaunchVoiceAssistFromKeyguard()243 public void onLaunchVoiceAssistFromKeyguard() { 244 } 245 246 /** 247 * Notify the interactor when the system prepares to show session. The system is going to 248 * bind the session service. 249 * 250 * @param args The arguments that were supplied to {@link #showSession(Bundle, int)}. 251 * It always includes {@link VoiceInteractionSession#KEY_SHOW_SESSION_ID}. 252 * @param flags The show flags originally provided to {@link #showSession(Bundle, int)}. 253 * @see #showSession(Bundle, int) 254 * @see #onShowSessionFailed(Bundle) 255 * @see VoiceInteractionSession#onShow(Bundle, int) 256 * @see VoiceInteractionSession#show(Bundle, int) 257 */ onPrepareToShowSession(@onNull Bundle args, int flags)258 public void onPrepareToShowSession(@NonNull Bundle args, int flags) { 259 } 260 261 /** 262 * Called when the show session failed. E.g. When the system bound the session service failed. 263 * 264 * @param args Additional info about the show session attempt that failed. For now, includes 265 * {@link VoiceInteractionSession#KEY_SHOW_SESSION_ID}. 266 * @see #showSession(Bundle, int) 267 * @see #onPrepareToShowSession(Bundle, int) 268 * @see VoiceInteractionSession#onShow(Bundle, int) 269 * @see VoiceInteractionSession#show(Bundle, int) 270 */ onShowSessionFailed(@onNull Bundle args)271 public void onShowSessionFailed(@NonNull Bundle args) { 272 } 273 274 /** 275 * Check whether the given service component is the currently active 276 * VoiceInteractionService. 277 */ isActiveService(Context context, ComponentName service)278 public static boolean isActiveService(Context context, ComponentName service) { 279 String cur = Settings.Secure.getString(context.getContentResolver(), 280 Settings.Secure.VOICE_INTERACTION_SERVICE); 281 if (cur == null || cur.isEmpty()) { 282 return false; 283 } 284 ComponentName curComp = ComponentName.unflattenFromString(cur); 285 if (curComp == null) { 286 return false; 287 } 288 return curComp.equals(service); 289 } 290 291 /** 292 * Set contextual options you would always like to have disabled when a session 293 * is shown. The flags may be any combination of 294 * {@link VoiceInteractionSession#SHOW_WITH_ASSIST VoiceInteractionSession.SHOW_WITH_ASSIST} and 295 * {@link VoiceInteractionSession#SHOW_WITH_SCREENSHOT 296 * VoiceInteractionSession.SHOW_WITH_SCREENSHOT}. 297 */ setDisabledShowContext(int flags)298 public void setDisabledShowContext(int flags) { 299 try { 300 mSystemService.setDisabledShowContext(flags); 301 } catch (RemoteException e) { 302 } 303 } 304 305 /** 306 * Return the value set by {@link #setDisabledShowContext}. 307 */ getDisabledShowContext()308 public int getDisabledShowContext() { 309 try { 310 return mSystemService.getDisabledShowContext(); 311 } catch (RemoteException e) { 312 return 0; 313 } 314 } 315 316 /** 317 * Request that the associated {@link android.service.voice.VoiceInteractionSession} be 318 * shown to the user, starting it if necessary. 319 * @param args Arbitrary arguments that will be propagated to the session. 320 * @param flags Indicates additional optional behavior that should be performed. May 321 * be any combination of 322 * {@link VoiceInteractionSession#SHOW_WITH_ASSIST VoiceInteractionSession.SHOW_WITH_ASSIST} and 323 * {@link VoiceInteractionSession#SHOW_WITH_SCREENSHOT 324 * VoiceInteractionSession.SHOW_WITH_SCREENSHOT} 325 * to request that the system generate and deliver assist data on the current foreground 326 * app as part of showing the session UI. 327 */ showSession(Bundle args, int flags)328 public void showSession(Bundle args, int flags) { 329 if (mSystemService == null) { 330 throw new IllegalStateException("Not available until onReady() is called"); 331 } 332 try { 333 mSystemService.showSession(args, flags, getAttributionTag()); 334 } catch (RemoteException e) { 335 } 336 } 337 338 /** 339 * Request to query for what extended voice actions this service supports. This method will 340 * be called when the system checks the supported actions of this 341 * {@link VoiceInteractionService}. Supported actions may be delivered to 342 * {@link VoiceInteractionSession} later to request a session to perform an action. 343 * 344 * <p>Voice actions are defined in support libraries and could vary based on platform context. 345 * For example, car related voice actions will be defined in car support libraries. 346 * 347 * @param voiceActions A set of checked voice actions. 348 * @return Returns a subset of checked voice actions. Additional voice actions in the 349 * returned set will be ignored. Returns empty set if no actions are supported. 350 */ 351 @NonNull onGetSupportedVoiceActions(@onNull Set<String> voiceActions)352 public Set<String> onGetSupportedVoiceActions(@NonNull Set<String> voiceActions) { 353 return Collections.emptySet(); 354 } 355 356 @Override onBind(Intent intent)357 public IBinder onBind(Intent intent) { 358 if (SERVICE_INTERFACE.equals(intent.getAction())) { 359 return mInterface.asBinder(); 360 } 361 return null; 362 } 363 364 /** 365 * Called during service initialization to tell you when the system is ready 366 * to receive interaction from it. You should generally do initialization here 367 * rather than in {@link #onCreate}. Methods such as {@link #showSession} will 368 * not be operational until this point. 369 */ onReady()370 public void onReady() { 371 mSystemService = IVoiceInteractionManagerService.Stub.asInterface( 372 ServiceManager.getService(Context.VOICE_INTERACTION_MANAGER_SERVICE)); 373 Objects.requireNonNull(mSystemService); 374 try { 375 mSystemService.asBinder().linkToDeath(mDeathRecipient, 0); 376 } catch (RemoteException e) { 377 Log.wtf(TAG, "unable to link to death with system service"); 378 } 379 mKeyphraseEnrollmentInfo = new KeyphraseEnrollmentInfo(getPackageManager()); 380 } 381 382 private IBinder.DeathRecipient mDeathRecipient = () -> { 383 Log.e(TAG, "system service binder died shutting down"); 384 Handler.getMain().executeOrSendMessage(PooledLambda.obtainMessage( 385 VoiceInteractionService::onShutdownInternal, VoiceInteractionService.this)); 386 }; 387 388 onShutdownInternal()389 private void onShutdownInternal() { 390 onShutdown(); 391 // Stop any active recognitions when shutting down. 392 // This ensures that if implementations forget to stop any active recognition, 393 // It's still guaranteed to have been stopped. 394 // This helps with cases where the voice interaction implementation is changed 395 // by the user. 396 safelyShutdownAllHotwordDetectors(true); 397 } 398 399 /** 400 * Called during service de-initialization to tell you when the system is shutting the 401 * service down. 402 * At this point this service may no longer be the active {@link VoiceInteractionService}. 403 */ onShutdown()404 public void onShutdown() { 405 } 406 onSoundModelsChangedInternal()407 private void onSoundModelsChangedInternal() { 408 synchronized (this) { 409 // TODO: Stop recognition if a sound model that was being recognized gets deleted. 410 mActiveDetectors.forEach(detector -> { 411 if (detector instanceof AlwaysOnHotwordDetector) { 412 ((AlwaysOnHotwordDetector) detector).onSoundModelsChanged(); 413 } 414 }); 415 } 416 } 417 onHandleVoiceActionCheck(List<String> voiceActions, IVoiceActionCheckCallback callback)418 private void onHandleVoiceActionCheck(List<String> voiceActions, 419 IVoiceActionCheckCallback callback) { 420 if (callback != null) { 421 try { 422 Set<String> voiceActionsSet = new ArraySet<>(voiceActions); 423 Set<String> resultSet = onGetSupportedVoiceActions(voiceActionsSet); 424 callback.onComplete(new ArrayList<>(resultSet)); 425 } catch (RemoteException e) { 426 } 427 } 428 } 429 430 /** 431 * List available ST modules to attach to for test purposes. 432 * @hide 433 */ 434 @TestApi 435 @NonNull listModuleProperties()436 public final List<SoundTrigger.ModuleProperties> listModuleProperties() { 437 Identity identity = new Identity(); 438 identity.packageName = ActivityThread.currentOpPackageName(); 439 try { 440 return mSystemService.listModuleProperties(identity); 441 } catch (RemoteException e) { 442 throw e.rethrowFromSystemServer(); 443 } 444 } 445 446 /** 447 * Creates an {@link AlwaysOnHotwordDetector} for the given keyphrase and locale. 448 * This instance must be retained and used by the client. 449 * Calling this a second time invalidates the previously created hotword detector 450 * which can no longer be used to manage recognition. 451 * 452 * <p>Note: If there are any active detectors that are created by using 453 * {@link #createAlwaysOnHotwordDetector(String, Locale, PersistableBundle, SharedMemory, 454 * AlwaysOnHotwordDetector.Callback)} or {@link #createAlwaysOnHotwordDetector(String, Locale, 455 * PersistableBundle, SharedMemory, Executor, AlwaysOnHotwordDetector.Callback)} or 456 * {@link #createHotwordDetector(PersistableBundle, SharedMemory, HotwordDetector.Callback)} or 457 * {@link #createHotwordDetector(PersistableBundle, SharedMemory, Executor, 458 * HotwordDetector.Callback)}, call this will throw an {@link IllegalStateException}. 459 * 460 * <p>Note that the callback will be executed on the current thread. If the current thread 461 * doesn't have a looper, it will throw a {@link RuntimeException}. To specify the execution 462 * thread, use {@link #createAlwaysOnHotwordDetector(String, Locale, Executor, 463 * AlwaysOnHotwordDetector.Callback)}. 464 * 465 * @param keyphrase The keyphrase that's being used, for example "Hello Android". 466 * @param locale The locale for which the enrollment needs to be performed. 467 * @param callback The callback to notify of detection events. 468 * @return An always-on hotword detector for the given keyphrase and locale. 469 * 470 * @throws SecurityException if the caller does not hold required permissions 471 * @throws IllegalStateException if there is no DSP hardware support when a caller has a 472 * target SDK of API level 34 or above. 473 * 474 * @deprecated Use {@link #createAlwaysOnHotwordDetector(String, Locale, Executor, 475 * AlwaysOnHotwordDetector.Callback)} instead. 476 * @hide 477 */ 478 @SystemApi 479 @Deprecated 480 @NonNull createAlwaysOnHotwordDetector( @uppressLintR) String keyphrase, @SuppressLint({R, R}) Locale locale, @SuppressLint(R) AlwaysOnHotwordDetector.Callback callback)481 public final AlwaysOnHotwordDetector createAlwaysOnHotwordDetector( 482 @SuppressLint("MissingNullability") String keyphrase, // TODO: nullability properly 483 @SuppressLint({"MissingNullability", "UseIcu"}) Locale locale, 484 @SuppressLint("MissingNullability") AlwaysOnHotwordDetector.Callback callback) { 485 return createAlwaysOnHotwordDetectorInternal(keyphrase, locale, 486 /* supportHotwordDetectionService= */ false, /* options= */ null, 487 /* sharedMemory= */ null, /* moduleProperties */ null, 488 /* executor= */ null, callback); 489 } 490 491 /** 492 * Creates an {@link AlwaysOnHotwordDetector} for the given keyphrase and locale. 493 * This instance must be retained and used by the client. 494 * Calling this a second time invalidates the previously created hotword detector 495 * which can no longer be used to manage recognition. 496 * 497 * <p>Note: If there are any active detectors that are created by using 498 * {@link #createAlwaysOnHotwordDetector(String, Locale, PersistableBundle, SharedMemory, 499 * AlwaysOnHotwordDetector.Callback)} or {@link #createAlwaysOnHotwordDetector(String, Locale, 500 * PersistableBundle, SharedMemory, Executor, AlwaysOnHotwordDetector.Callback)} or 501 * {@link #createHotwordDetector(PersistableBundle, SharedMemory, HotwordDetector.Callback)} or 502 * {@link #createHotwordDetector(PersistableBundle, SharedMemory, Executor, 503 * HotwordDetector.Callback)}, call this will throw an {@link IllegalStateException}. 504 * 505 * @param keyphrase The keyphrase that's being used, for example "Hello Android". 506 * @param locale The locale for which the enrollment needs to be performed. 507 * @param executor The executor on which to run the callback. 508 * @param callback The callback to notify of detection events. 509 * @return An always-on hotword detector for the given keyphrase and locale. 510 * 511 * @throws SecurityException if the caller does not hold required permissions 512 * @throws IllegalStateException if there is no DSP hardware support when a caller has a 513 * target SDK of API level 34 or above. 514 * 515 * @hide 516 */ 517 @SystemApi 518 @NonNull createAlwaysOnHotwordDetector( @onNull String keyphrase, @SuppressLint(R) @NonNull Locale locale, @NonNull @CallbackExecutor Executor executor, @NonNull AlwaysOnHotwordDetector.Callback callback)519 public final AlwaysOnHotwordDetector createAlwaysOnHotwordDetector( 520 @NonNull String keyphrase, @SuppressLint("UseIcu") @NonNull Locale locale, 521 @NonNull @CallbackExecutor Executor executor, 522 @NonNull AlwaysOnHotwordDetector.Callback callback) { 523 // TODO (b/269080850): Resolve AndroidFrameworkRequiresPermission lint warning 524 525 Objects.requireNonNull(keyphrase); 526 Objects.requireNonNull(locale); 527 Objects.requireNonNull(executor); 528 Objects.requireNonNull(callback); 529 return createAlwaysOnHotwordDetectorInternal(keyphrase, locale, 530 /* supportHotwordDetectionService= */ false, /* options= */ null, 531 /* sharedMemory= */ null, /* moduleProperties= */ null, executor, callback); 532 } 533 534 /** 535 * Same as {@link createAlwaysOnHotwordDetector(String, Locale, Executor, 536 * AlwaysOnHotwordDetector.Callback)}, but allow explicit selection of the underlying ST 537 * module to attach to. 538 * Use {@link #listModuleProperties()} to get available modules to attach to. 539 * @hide 540 */ 541 @TestApi 542 @RequiresPermission(Manifest.permission.MANAGE_HOTWORD_DETECTION) 543 @NonNull createAlwaysOnHotwordDetectorForTest( @onNull String keyphrase, @SuppressLint(R) @NonNull Locale locale, @NonNull SoundTrigger.ModuleProperties moduleProperties, @NonNull @CallbackExecutor Executor executor, @NonNull AlwaysOnHotwordDetector.Callback callback)544 public final AlwaysOnHotwordDetector createAlwaysOnHotwordDetectorForTest( 545 @NonNull String keyphrase, @SuppressLint("UseIcu") @NonNull Locale locale, 546 @NonNull SoundTrigger.ModuleProperties moduleProperties, 547 @NonNull @CallbackExecutor Executor executor, 548 @NonNull AlwaysOnHotwordDetector.Callback callback) { 549 550 Objects.requireNonNull(keyphrase); 551 Objects.requireNonNull(locale); 552 Objects.requireNonNull(moduleProperties); 553 Objects.requireNonNull(executor); 554 Objects.requireNonNull(callback); 555 return createAlwaysOnHotwordDetectorInternal(keyphrase, locale, 556 /* supportHotwordDetectionService= */ false, /* options= */ null, 557 /* sharedMemory= */ null, moduleProperties, executor, callback); 558 } 559 560 561 /** 562 * Create an {@link AlwaysOnHotwordDetector} and trigger a {@link HotwordDetectionService} 563 * service, then it will also pass the read-only data to hotword detection service. 564 * 565 * Like {@link #createAlwaysOnHotwordDetector(String, Locale, AlwaysOnHotwordDetector.Callback) 566 * }. Before calling this function, you should set a valid hotword detection service with 567 * android:hotwordDetectionService in an android.voice_interaction metadata file and set 568 * android:isolatedProcess="true" in the AndroidManifest.xml of hotword detection service. 569 * Otherwise it will throw IllegalStateException. After calling this function, the system will 570 * also trigger a hotword detection service and pass the read-only data back to it. 571 * 572 * <p>Note: The system will trigger hotword detection service after calling this function when 573 * all conditions meet the requirements. 574 * 575 * <p>Note: If there are any active detectors that are created by using 576 * {@link #createAlwaysOnHotwordDetector(String, Locale, AlwaysOnHotwordDetector.Callback)} or 577 * {@link #createAlwaysOnHotwordDetector(String, Locale, Executor, 578 * AlwaysOnHotwordDetector.Callback)}, call this will throw an {@link IllegalStateException}. 579 * 580 * <p>Note that the callback will be executed on the current thread. If the current thread 581 * doesn't have a looper, it will throw a {@link RuntimeException}. To specify the execution 582 * thread, use {@link #createAlwaysOnHotwordDetector(String, Locale, PersistableBundle, 583 * SharedMemory, Executor, AlwaysOnHotwordDetector.Callback)}. 584 * 585 * @param keyphrase The keyphrase that's being used, for example "Hello Android". 586 * @param locale The locale for which the enrollment needs to be performed. 587 * @param options Application configuration data provided by the 588 * {@link VoiceInteractionService}. PersistableBundle does not allow any remotable objects or 589 * other contents that can be used to communicate with other processes. 590 * @param sharedMemory The unrestricted data blob provided by the 591 * {@link VoiceInteractionService}. Use this to provide the hotword models data or other 592 * such data to the trusted process. 593 * @param callback The callback to notify of detection events. 594 * @return An always-on hotword detector for the given keyphrase and locale. 595 * 596 * @throws SecurityException if the caller does not hold required permissions 597 * @throws IllegalStateException if the hotword detection service is not set, isolated process 598 * is not set, or there is no DSP hardware support when a caller has a target SDK of API 599 * level 34 or above. 600 * 601 * @deprecated Use {@link #createAlwaysOnHotwordDetector(String, Locale, PersistableBundle, 602 * SharedMemory, Executor, AlwaysOnHotwordDetector.Callback)} instead. 603 * @hide 604 */ 605 @SystemApi 606 @RequiresPermission(Manifest.permission.MANAGE_HOTWORD_DETECTION) 607 @Deprecated 608 @NonNull createAlwaysOnHotwordDetector( @uppressLintR) String keyphrase, @SuppressLint({R, R}) Locale locale, @Nullable PersistableBundle options, @Nullable SharedMemory sharedMemory, @SuppressLint(R) AlwaysOnHotwordDetector.Callback callback)609 public final AlwaysOnHotwordDetector createAlwaysOnHotwordDetector( 610 @SuppressLint("MissingNullability") String keyphrase, // TODO: nullability properly 611 @SuppressLint({"MissingNullability", "UseIcu"}) Locale locale, 612 @Nullable PersistableBundle options, 613 @Nullable SharedMemory sharedMemory, 614 @SuppressLint("MissingNullability") AlwaysOnHotwordDetector.Callback callback) { 615 return createAlwaysOnHotwordDetectorInternal(keyphrase, locale, 616 /* supportHotwordDetectionService= */ true, options, sharedMemory, 617 /* modulProperties */ null, /* executor= */ null, callback); 618 } 619 620 /** 621 * Create an {@link AlwaysOnHotwordDetector} and trigger a {@link HotwordDetectionService} 622 * service, then it will also pass the read-only data to hotword detection service. 623 * 624 * Like {@link #createAlwaysOnHotwordDetector(String, Locale, AlwaysOnHotwordDetector.Callback) 625 * }. Before calling this function, you should set a valid hotword detection service with 626 * android:hotwordDetectionService in an android.voice_interaction metadata file and set 627 * android:isolatedProcess="true" in the AndroidManifest.xml of hotword detection service. 628 * Otherwise it will throw IllegalStateException. After calling this function, the system will 629 * also trigger a hotword detection service and pass the read-only data back to it. 630 * 631 * <p>Note: The system will trigger hotword detection service after calling this function when 632 * all conditions meet the requirements. 633 * 634 * <p>Note: If there are any active detectors that are created by using 635 * {@link #createAlwaysOnHotwordDetector(String, Locale, AlwaysOnHotwordDetector.Callback)} or 636 * {@link #createAlwaysOnHotwordDetector(String, Locale, Executor, 637 * AlwaysOnHotwordDetector.Callback)}, call this will throw an {@link IllegalStateException}. 638 * 639 * @param keyphrase The keyphrase that's being used, for example "Hello Android". 640 * @param locale The locale for which the enrollment needs to be performed. 641 * @param options Application configuration data provided by the 642 * {@link VoiceInteractionService}. PersistableBundle does not allow any remotable objects or 643 * other contents that can be used to communicate with other processes. 644 * @param sharedMemory The unrestricted data blob provided by the 645 * {@link VoiceInteractionService}. Use this to provide the hotword models data or other 646 * such data to the trusted process. 647 * @param executor The executor on which to run the callback. 648 * @param callback The callback to notify of detection events. 649 * @return An always-on hotword detector for the given keyphrase and locale. 650 * 651 * @throws SecurityException if the caller does not hold required permissions 652 * @throws IllegalStateException if the hotword detection service is not set, isolated process 653 * is not set, or there is no DSP hardware support when a caller has a target SDK of API level 654 * 34 or above. 655 * 656 * @hide 657 */ 658 @SystemApi 659 @RequiresPermission(Manifest.permission.MANAGE_HOTWORD_DETECTION) 660 @NonNull createAlwaysOnHotwordDetector( @onNull String keyphrase, @SuppressLint(R) @NonNull Locale locale, @Nullable PersistableBundle options, @Nullable SharedMemory sharedMemory, @NonNull @CallbackExecutor Executor executor, @NonNull AlwaysOnHotwordDetector.Callback callback)661 public final AlwaysOnHotwordDetector createAlwaysOnHotwordDetector( 662 @NonNull String keyphrase, @SuppressLint("UseIcu") @NonNull Locale locale, 663 @Nullable PersistableBundle options, @Nullable SharedMemory sharedMemory, 664 @NonNull @CallbackExecutor Executor executor, 665 @NonNull AlwaysOnHotwordDetector.Callback callback) { 666 // TODO (b/269080850): Resolve AndroidFrameworkRequiresPermission lint warning 667 668 Objects.requireNonNull(keyphrase); 669 Objects.requireNonNull(locale); 670 Objects.requireNonNull(executor); 671 Objects.requireNonNull(callback); 672 return createAlwaysOnHotwordDetectorInternal(keyphrase, locale, 673 /* supportHotwordDetectionService= */ true, options, sharedMemory, 674 /* moduleProperties= */ null, executor, callback); 675 } 676 677 /** 678 * Same as {@link createAlwaysOnHotwordDetector(String, Locale, 679 * PersistableBundle, SharedMemory, Executor, AlwaysOnHotwordDetector.Callback)}, 680 * but allow explicit selection of the underlying ST module to attach to. 681 * Use {@link #listModuleProperties()} to get available modules to attach to. 682 * @hide 683 */ 684 @TestApi 685 @RequiresPermission(Manifest.permission.MANAGE_HOTWORD_DETECTION) 686 @NonNull createAlwaysOnHotwordDetectorForTest( @onNull String keyphrase, @SuppressLint(R) @NonNull Locale locale, @Nullable PersistableBundle options, @Nullable SharedMemory sharedMemory, @NonNull SoundTrigger.ModuleProperties moduleProperties, @NonNull @CallbackExecutor Executor executor, @NonNull AlwaysOnHotwordDetector.Callback callback)687 public final AlwaysOnHotwordDetector createAlwaysOnHotwordDetectorForTest( 688 @NonNull String keyphrase, @SuppressLint("UseIcu") @NonNull Locale locale, 689 @Nullable PersistableBundle options, @Nullable SharedMemory sharedMemory, 690 @NonNull SoundTrigger.ModuleProperties moduleProperties, 691 @NonNull @CallbackExecutor Executor executor, 692 @NonNull AlwaysOnHotwordDetector.Callback callback) { 693 694 Objects.requireNonNull(keyphrase); 695 Objects.requireNonNull(locale); 696 Objects.requireNonNull(moduleProperties); 697 Objects.requireNonNull(executor); 698 Objects.requireNonNull(callback); 699 return createAlwaysOnHotwordDetectorInternal(keyphrase, locale, 700 /* supportHotwordDetectionService= */ true, options, sharedMemory, 701 moduleProperties, executor, callback); 702 } 703 704 705 createAlwaysOnHotwordDetectorInternal( @uppressLintR) String keyphrase, @SuppressLint({R, R}) Locale locale, boolean supportHotwordDetectionService, @Nullable PersistableBundle options, @Nullable SharedMemory sharedMemory, @Nullable SoundTrigger.ModuleProperties moduleProperties, @Nullable @CallbackExecutor Executor executor, @SuppressLint(R) AlwaysOnHotwordDetector.Callback callback)706 private AlwaysOnHotwordDetector createAlwaysOnHotwordDetectorInternal( 707 @SuppressLint("MissingNullability") String keyphrase, // TODO: nullability properly 708 @SuppressLint({"MissingNullability", "UseIcu"}) Locale locale, 709 boolean supportHotwordDetectionService, 710 @Nullable PersistableBundle options, 711 @Nullable SharedMemory sharedMemory, 712 @Nullable SoundTrigger.ModuleProperties moduleProperties, 713 @Nullable @CallbackExecutor Executor executor, 714 @SuppressLint("MissingNullability") AlwaysOnHotwordDetector.Callback callback) { 715 716 if (mSystemService == null) { 717 throw new IllegalStateException("Not available until onReady() is called"); 718 } 719 synchronized (mLock) { 720 if (!CompatChanges.isChangeEnabled(MULTIPLE_ACTIVE_HOTWORD_DETECTORS)) { 721 // Allow only one concurrent recognition via the APIs. 722 safelyShutdownAllHotwordDetectors(false); 723 } else { 724 for (HotwordDetector detector : mActiveDetectors) { 725 if (detector.isUsingSandboxedDetectionService() 726 != supportHotwordDetectionService) { 727 throw new IllegalStateException( 728 "It disallows to create trusted and non-trusted detectors " 729 + "at the same time."); 730 } else if (detector instanceof AlwaysOnHotwordDetector) { 731 throw new IllegalStateException( 732 "There is already an active AlwaysOnHotwordDetector. " 733 + "It must be destroyed to create a new one."); 734 } 735 } 736 } 737 738 AlwaysOnHotwordDetector dspDetector = new AlwaysOnHotwordDetector(keyphrase, locale, 739 executor, callback, mKeyphraseEnrollmentInfo, mSystemService, 740 getApplicationContext().getApplicationInfo().targetSdkVersion, 741 supportHotwordDetectionService); 742 mActiveDetectors.add(dspDetector); 743 744 try { 745 dspDetector.registerOnDestroyListener(this::onHotwordDetectorDestroyed); 746 // Check if we are currently overridden, and should use the test module. 747 if (mTestModuleForAlwaysOnHotwordDetectorEnabled) { 748 moduleProperties = getTestModuleProperties(); 749 } 750 // If moduleProperties is null, the default STModule is used. 751 dspDetector.initialize(options, sharedMemory, moduleProperties); 752 } catch (Exception e) { 753 mActiveDetectors.remove(dspDetector); 754 dspDetector.destroy(); 755 throw e; 756 } 757 return dspDetector; 758 } 759 } 760 761 /** 762 * Creates a {@link HotwordDetector} and initializes the application's 763 * {@link HotwordDetectionService} using {@code options} and {code sharedMemory}. 764 * 765 * <p>To be able to call this, you need to set android:hotwordDetectionService in the 766 * android.voice_interaction metadata file to a valid hotword detection service, and set 767 * android:isolatedProcess="true" in the hotword detection service's declaration. Otherwise, 768 * this throws an {@link IllegalStateException}. 769 * 770 * <p>This instance must be retained and used by the client. 771 * Calling this a second time invalidates the previously created hotword detector 772 * which can no longer be used to manage recognition. 773 * 774 * <p>Using this has a noticeable impact on battery, since the microphone is kept open 775 * for the lifetime of the recognition {@link HotwordDetector#startRecognition() session}. On 776 * devices where hardware filtering is available (such as through a DSP), it's highly 777 * recommended to use {@link #createAlwaysOnHotwordDetector} instead. 778 * 779 * <p>Note: If there are any active detectors that are created by using 780 * {@link #createAlwaysOnHotwordDetector(String, Locale, AlwaysOnHotwordDetector.Callback)} or 781 * {@link #createAlwaysOnHotwordDetector(String, Locale, Executor, 782 * AlwaysOnHotwordDetector.Callback)}, call this will throw an {@link IllegalStateException}. 783 * 784 * <p>Note that the callback will be executed on the main thread. To specify the execution 785 * thread, use {@link #createHotwordDetector(PersistableBundle, SharedMemory, Executor, 786 * HotwordDetector.Callback)}. 787 * 788 * @param options Application configuration data to be provided to the 789 * {@link HotwordDetectionService}. PersistableBundle does not allow any remotable objects or 790 * other contents that can be used to communicate with other processes. 791 * @param sharedMemory The unrestricted data blob to be provided to the 792 * {@link HotwordDetectionService}. Use this to provide hotword models or other such data to the 793 * sandboxed process. 794 * @param callback The callback to notify of detection events. 795 * @return A hotword detector for the given audio format. 796 * 797 * @see #createAlwaysOnHotwordDetector(String, Locale, PersistableBundle, SharedMemory, 798 * AlwaysOnHotwordDetector.Callback) 799 * 800 * @see #createAlwaysOnHotwordDetector(String, Locale, PersistableBundle, SharedMemory, 801 * Executor, AlwaysOnHotwordDetector.Callback) 802 * 803 * @deprecated Use {@link #createHotwordDetector(PersistableBundle, SharedMemory, Executor, 804 * HotwordDetector.Callback)} instead. 805 * @hide 806 */ 807 @SystemApi 808 @RequiresPermission(Manifest.permission.MANAGE_HOTWORD_DETECTION) 809 @Deprecated 810 @NonNull createHotwordDetector( @ullable PersistableBundle options, @Nullable SharedMemory sharedMemory, @NonNull HotwordDetector.Callback callback)811 public final HotwordDetector createHotwordDetector( 812 @Nullable PersistableBundle options, 813 @Nullable SharedMemory sharedMemory, 814 @NonNull HotwordDetector.Callback callback) { 815 return createHotwordDetectorInternal(options, sharedMemory, /* executor= */ null, callback); 816 } 817 818 /** 819 * Creates a {@link HotwordDetector} and initializes the application's 820 * {@link HotwordDetectionService} using {@code options} and {code sharedMemory}. 821 * 822 * <p>To be able to call this, you need to set android:hotwordDetectionService in the 823 * android.voice_interaction metadata file to a valid hotword detection service, and set 824 * android:isolatedProcess="true" in the hotword detection service's declaration. Otherwise, 825 * this throws an {@link IllegalStateException}. 826 * 827 * <p>This instance must be retained and used by the client. 828 * Calling this a second time invalidates the previously created hotword detector 829 * which can no longer be used to manage recognition. 830 * 831 * <p>Using this has a noticeable impact on battery, since the microphone is kept open 832 * for the lifetime of the recognition {@link HotwordDetector#startRecognition() session}. On 833 * devices where hardware filtering is available (such as through a DSP), it's highly 834 * recommended to use {@link #createAlwaysOnHotwordDetector} instead. 835 * 836 * <p>Note: If there are any active detectors that are created by using 837 * {@link #createAlwaysOnHotwordDetector(String, Locale, AlwaysOnHotwordDetector.Callback)} or 838 * {@link #createAlwaysOnHotwordDetector(String, Locale, Executor, 839 * AlwaysOnHotwordDetector.Callback)}, call this will throw an {@link IllegalStateException}. 840 * 841 * @param options Application configuration data to be provided to the 842 * {@link HotwordDetectionService}. PersistableBundle does not allow any remotable objects or 843 * other contents that can be used to communicate with other processes. 844 * @param sharedMemory The unrestricted data blob to be provided to the 845 * {@link HotwordDetectionService}. Use this to provide hotword models or other such data to the 846 * sandboxed process. 847 * @param executor The executor on which to run the callback. 848 * @param callback The callback to notify of detection events. 849 * @return A hotword detector for the given audio format. 850 * 851 * @see #createAlwaysOnHotwordDetector(String, Locale, PersistableBundle, SharedMemory, 852 * AlwaysOnHotwordDetector.Callback) 853 * 854 * @see #createAlwaysOnHotwordDetector(String, Locale, PersistableBundle, SharedMemory, 855 * Executor, AlwaysOnHotwordDetector.Callback) 856 * 857 * @hide 858 */ 859 @SystemApi 860 @RequiresPermission(Manifest.permission.MANAGE_HOTWORD_DETECTION) 861 @NonNull createHotwordDetector( @ullable PersistableBundle options, @Nullable SharedMemory sharedMemory, @NonNull @CallbackExecutor Executor executor, @NonNull HotwordDetector.Callback callback)862 public final HotwordDetector createHotwordDetector( 863 @Nullable PersistableBundle options, 864 @Nullable SharedMemory sharedMemory, 865 @NonNull @CallbackExecutor Executor executor, 866 @NonNull HotwordDetector.Callback callback) { 867 // TODO (b/269080850): Resolve AndroidFrameworkRequiresPermission lint warning 868 869 Objects.requireNonNull(executor); 870 Objects.requireNonNull(callback); 871 return createHotwordDetectorInternal(options, sharedMemory, executor, callback); 872 } 873 createHotwordDetectorInternal( @ullable PersistableBundle options, @Nullable SharedMemory sharedMemory, @Nullable @CallbackExecutor Executor executor, @NonNull HotwordDetector.Callback callback)874 private HotwordDetector createHotwordDetectorInternal( 875 @Nullable PersistableBundle options, 876 @Nullable SharedMemory sharedMemory, 877 @Nullable @CallbackExecutor Executor executor, 878 @NonNull HotwordDetector.Callback callback) { 879 if (mSystemService == null) { 880 throw new IllegalStateException("Not available until onReady() is called"); 881 } 882 synchronized (mLock) { 883 if (!CompatChanges.isChangeEnabled(MULTIPLE_ACTIVE_HOTWORD_DETECTORS)) { 884 // Allow only one concurrent recognition via the APIs. 885 safelyShutdownAllHotwordDetectors(false); 886 } else { 887 for (HotwordDetector detector : mActiveDetectors) { 888 if (!detector.isUsingSandboxedDetectionService()) { 889 throw new IllegalStateException( 890 "It disallows to create trusted and non-trusted detectors " 891 + "at the same time."); 892 } else if (detector instanceof SoftwareHotwordDetector) { 893 throw new IllegalStateException( 894 "There is already an active SoftwareHotwordDetector. " 895 + "It must be destroyed to create a new one."); 896 } 897 } 898 } 899 900 SoftwareHotwordDetector softwareHotwordDetector = 901 new SoftwareHotwordDetector(mSystemService, /* audioFormat= */ null, 902 executor, callback); 903 mActiveDetectors.add(softwareHotwordDetector); 904 905 try { 906 softwareHotwordDetector.registerOnDestroyListener( 907 this::onHotwordDetectorDestroyed); 908 softwareHotwordDetector.initialize(options, sharedMemory); 909 } catch (Exception e) { 910 mActiveDetectors.remove(softwareHotwordDetector); 911 softwareHotwordDetector.destroy(); 912 throw e; 913 } 914 return softwareHotwordDetector; 915 } 916 } 917 918 /** 919 * Creates a {@link VisualQueryDetector} and initializes the application's 920 * {@link VisualQueryDetectionService} using {@code options} and {@code sharedMemory}. 921 * 922 * <p>To be able to call this, you need to set android:visualQueryDetectionService in the 923 * android.voice_interaction metadata file to a valid visual query detection service, and set 924 * android:isolatedProcess="true" in the service's declaration. Otherwise, this throws an 925 * {@link IllegalStateException}. 926 * 927 * <p>Using this has a noticeable impact on battery, since the microphone is kept open 928 * for the lifetime of the recognition {@link VisualQueryDetector#startRecognition() session}. 929 * 930 * @param options Application configuration data to be provided to the 931 * {@link VisualQueryDetectionService}. PersistableBundle does not allow any remotable objects 932 * or other contents that can be used to communicate with other processes. 933 * @param sharedMemory The unrestricted data blob to be provided to the 934 * {@link VisualQueryDetectionService}. Use this to provide models or other such data to the 935 * sandboxed process. 936 * @param callback The callback to notify of detection events. 937 * @return An instanece of {@link VisualQueryDetector}. 938 * @throws IllegalStateException when there is an existing {@link VisualQueryDetector}, or when 939 * there is a non-trusted hotword detector running. 940 * 941 * @hide 942 */ 943 @SystemApi 944 @RequiresPermission(Manifest.permission.MANAGE_HOTWORD_DETECTION) 945 @NonNull createVisualQueryDetector( @ullable PersistableBundle options, @Nullable SharedMemory sharedMemory, @NonNull @CallbackExecutor Executor executor, @NonNull VisualQueryDetector.Callback callback)946 public final VisualQueryDetector createVisualQueryDetector( 947 @Nullable PersistableBundle options, 948 @Nullable SharedMemory sharedMemory, 949 @NonNull @CallbackExecutor Executor executor, 950 @NonNull VisualQueryDetector.Callback callback) { 951 Objects.requireNonNull(executor); 952 Objects.requireNonNull(callback); 953 954 if (!SYSPROP_VISUAL_QUERY_SERVICE_ENABLED) { 955 throw new IllegalStateException("VisualQueryDetectionService is not enabled on this " 956 + "system. Please set ro.hotword.visual_query_service_enabled to true."); 957 } 958 if (mSystemService == null) { 959 throw new IllegalStateException("Not available until onReady() is called"); 960 } 961 synchronized (mLock) { 962 if (mActiveVisualQueryDetector != null) { 963 throw new IllegalStateException( 964 "There is already an active VisualQueryDetector. " 965 + "It must be destroyed to create a new one."); 966 } 967 for (HotwordDetector detector : mActiveDetectors) { 968 if (!detector.isUsingSandboxedDetectionService()) { 969 throw new IllegalStateException( 970 "It disallows to create trusted and non-trusted detectors " 971 + "at the same time."); 972 } 973 } 974 975 VisualQueryDetector visualQueryDetector = 976 new VisualQueryDetector(mSystemService, executor, callback, this); 977 HotwordDetector visualQueryDetectorInitializationDelegate = 978 visualQueryDetector.getInitializationDelegate(); 979 mActiveDetectors.add(visualQueryDetectorInitializationDelegate); 980 981 try { 982 visualQueryDetector.registerOnDestroyListener(this::onHotwordDetectorDestroyed); 983 visualQueryDetector.initialize(options, sharedMemory); 984 } catch (Exception e) { 985 mActiveDetectors.remove(visualQueryDetectorInitializationDelegate); 986 visualQueryDetector.destroy(); 987 throw e; 988 } 989 mActiveVisualQueryDetector = visualQueryDetector; 990 return visualQueryDetector; 991 } 992 } 993 994 /** 995 * Creates an {@link KeyphraseModelManager} to use for enrolling voice models outside of the 996 * pre-bundled system voice models. 997 * @hide 998 */ 999 @SystemApi 1000 @RequiresPermission(Manifest.permission.MANAGE_VOICE_KEYPHRASES) 1001 @NonNull createKeyphraseModelManager()1002 public final KeyphraseModelManager createKeyphraseModelManager() { 1003 if (mSystemService == null) { 1004 throw new IllegalStateException("Not available until onReady() is called"); 1005 } 1006 synchronized (mLock) { 1007 return new KeyphraseModelManager(mSystemService); 1008 } 1009 } 1010 1011 /** 1012 * @return Details of keyphrases available for enrollment. 1013 * @hide 1014 */ 1015 @VisibleForTesting getKeyphraseEnrollmentInfo()1016 protected final KeyphraseEnrollmentInfo getKeyphraseEnrollmentInfo() { 1017 return mKeyphraseEnrollmentInfo; 1018 } 1019 1020 1021 /** 1022 * Configure {@link createAlwaysOnHotwordDetector(String, Locale, 1023 * SoundTrigger.ModuleProperties, Executor, AlwaysOnHotwordDetector.Callback)} 1024 * and similar overloads to utilize the test SoundTrigger module instead of the 1025 * actual DSP module. 1026 * @param isEnabled - {@code true} if subsequently created {@link AlwaysOnHotwordDetector} 1027 * objects should attach to a test module. {@code false} if subsequently created 1028 * {@link AlwaysOnHotwordDetector} should attach to the actual DSP module. 1029 * @hide 1030 */ 1031 @TestApi setTestModuleForAlwaysOnHotwordDetectorEnabled(boolean isEnabled)1032 public final void setTestModuleForAlwaysOnHotwordDetectorEnabled(boolean isEnabled) { 1033 synchronized (mLock) { 1034 mTestModuleForAlwaysOnHotwordDetectorEnabled = isEnabled; 1035 } 1036 } 1037 1038 /** 1039 * Get the {@link SoundTrigger.ModuleProperties} representing the fake 1040 * STHAL to attach to via {@link createAlwaysOnHotwordDetector(String, Locale, 1041 * SoundTrigger.ModuleProperties, Executor, AlwaysOnHotwordDetector.Callback)} and 1042 * similar overloads for test purposes. 1043 * @return ModuleProperties to use for test purposes. 1044 */ getTestModuleProperties()1045 private final @NonNull SoundTrigger.ModuleProperties getTestModuleProperties() { 1046 var moduleProps = listModuleProperties() 1047 .stream() 1048 .filter((SoundTrigger.ModuleProperties prop) 1049 -> prop.getSupportedModelArch().equals(SoundTrigger.FAKE_HAL_ARCH)) 1050 .findFirst() 1051 .orElse(null); 1052 if (moduleProps == null) { 1053 throw new IllegalStateException("Fake ST HAL should always be available"); 1054 } 1055 return moduleProps; 1056 } 1057 1058 /** 1059 * Checks if a given keyphrase and locale are supported to create an 1060 * {@link AlwaysOnHotwordDetector}. 1061 * 1062 * @return true if the keyphrase and locale combination is supported, false otherwise. 1063 * @hide 1064 */ 1065 @UnsupportedAppUsage isKeyphraseAndLocaleSupportedForHotword(String keyphrase, Locale locale)1066 public final boolean isKeyphraseAndLocaleSupportedForHotword(String keyphrase, Locale locale) { 1067 if (mKeyphraseEnrollmentInfo == null) { 1068 return false; 1069 } 1070 return mKeyphraseEnrollmentInfo.getKeyphraseMetadata(keyphrase, locale) != null; 1071 } 1072 safelyShutdownAllHotwordDetectors(boolean shouldShutDownVisualQueryDetector)1073 private void safelyShutdownAllHotwordDetectors(boolean shouldShutDownVisualQueryDetector) { 1074 synchronized (mLock) { 1075 mActiveDetectors.forEach(detector -> { 1076 try { 1077 // Skip destroying VisualQueryDetector if HotwordDetectors are created 1078 if (!(mActiveVisualQueryDetector != null 1079 && detector == mActiveVisualQueryDetector.getInitializationDelegate()) 1080 || shouldShutDownVisualQueryDetector) { 1081 detector.destroy(); 1082 } 1083 } catch (Exception ex) { 1084 Log.i(TAG, "exception destroying HotwordDetector", ex); 1085 } 1086 }); 1087 } 1088 } 1089 onHotwordDetectorDestroyed(@onNull HotwordDetector detector)1090 private void onHotwordDetectorDestroyed(@NonNull HotwordDetector detector) { 1091 synchronized (mLock) { 1092 if (mActiveVisualQueryDetector != null 1093 && detector == mActiveVisualQueryDetector.getInitializationDelegate()) { 1094 mActiveVisualQueryDetector = null; 1095 } 1096 mActiveDetectors.remove(detector); 1097 } 1098 } 1099 1100 /** 1101 * Provide hints to be reflected in the system UI. 1102 * 1103 * @param hints Arguments used to show UI. 1104 */ setUiHints(@onNull Bundle hints)1105 public final void setUiHints(@NonNull Bundle hints) { 1106 if (hints == null) { 1107 throw new IllegalArgumentException("Hints must be non-null"); 1108 } 1109 1110 try { 1111 mSystemService.setUiHints(hints); 1112 } catch (RemoteException e) { 1113 throw e.rethrowFromSystemServer(); 1114 } 1115 } 1116 1117 @Override dump(FileDescriptor fd, PrintWriter pw, String[] args)1118 protected void dump(FileDescriptor fd, PrintWriter pw, String[] args) { 1119 pw.println("VOICE INTERACTION"); 1120 synchronized (mLock) { 1121 pw.println(" Sandboxed Detector(s):"); 1122 if (mActiveDetectors.size() == 0) { 1123 pw.println(" No detector."); 1124 } else { 1125 mActiveDetectors.forEach(detector -> { 1126 pw.print(" Using sandboxed detection service="); 1127 pw.println(detector.isUsingSandboxedDetectionService()); 1128 detector.dump(" ", pw); 1129 pw.println(); 1130 }); 1131 } 1132 pw.println("Available Model Enrollment Applications:"); 1133 pw.println(" " + mKeyphraseEnrollmentInfo); 1134 } 1135 } 1136 } 1137