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 onShutdownInternal()388 private void onShutdownInternal() { 389 onShutdown(); 390 // Stop any active recognitions when shutting down. 391 // This ensures that if implementations forget to stop any active recognition, 392 // It's still guaranteed to have been stopped. 393 // This helps with cases where the voice interaction implementation is changed 394 // by the user. 395 safelyShutdownAllHotwordDetectors(true); 396 } 397 398 /** 399 * Called during service de-initialization to tell you when the system is shutting the 400 * service down. 401 * At this point this service may no longer be the active {@link VoiceInteractionService}. 402 */ onShutdown()403 public void onShutdown() { 404 } 405 onSoundModelsChangedInternal()406 private void onSoundModelsChangedInternal() { 407 synchronized (this) { 408 // TODO: Stop recognition if a sound model that was being recognized gets deleted. 409 mActiveDetectors.forEach(detector -> { 410 if (detector instanceof AlwaysOnHotwordDetector) { 411 ((AlwaysOnHotwordDetector) detector).onSoundModelsChanged(); 412 } 413 }); 414 } 415 } 416 onHandleVoiceActionCheck(List<String> voiceActions, IVoiceActionCheckCallback callback)417 private void onHandleVoiceActionCheck(List<String> voiceActions, 418 IVoiceActionCheckCallback callback) { 419 if (callback != null) { 420 try { 421 Set<String> voiceActionsSet = new ArraySet<>(voiceActions); 422 Set<String> resultSet = onGetSupportedVoiceActions(voiceActionsSet); 423 callback.onComplete(new ArrayList<>(resultSet)); 424 } catch (RemoteException e) { 425 } 426 } 427 } 428 429 /** 430 * List available ST modules to attach to for test purposes. 431 * @hide 432 */ 433 @TestApi 434 @NonNull listModuleProperties()435 public final List<SoundTrigger.ModuleProperties> listModuleProperties() { 436 Identity identity = new Identity(); 437 identity.packageName = ActivityThread.currentOpPackageName(); 438 try { 439 return mSystemService.listModuleProperties(identity); 440 } catch (RemoteException e) { 441 throw e.rethrowFromSystemServer(); 442 } 443 } 444 445 /** 446 * Creates an {@link AlwaysOnHotwordDetector} for the given keyphrase and locale. 447 * This instance must be retained and used by the client. 448 * Calling this a second time invalidates the previously created hotword detector 449 * which can no longer be used to manage recognition. 450 * 451 * <p>Note: If there are any active detectors that are created by using 452 * {@link #createAlwaysOnHotwordDetector(String, Locale, PersistableBundle, SharedMemory, 453 * AlwaysOnHotwordDetector.Callback)} or {@link #createAlwaysOnHotwordDetector(String, Locale, 454 * PersistableBundle, SharedMemory, Executor, AlwaysOnHotwordDetector.Callback)} or 455 * {@link #createHotwordDetector(PersistableBundle, SharedMemory, HotwordDetector.Callback)} or 456 * {@link #createHotwordDetector(PersistableBundle, SharedMemory, Executor, 457 * HotwordDetector.Callback)}, call this will throw an {@link IllegalStateException}. 458 * 459 * <p>Note that the callback will be executed on the current thread. If the current thread 460 * doesn't have a looper, it will throw a {@link RuntimeException}. To specify the execution 461 * thread, use {@link #createAlwaysOnHotwordDetector(String, Locale, Executor, 462 * AlwaysOnHotwordDetector.Callback)}. 463 * 464 * @param keyphrase The keyphrase that's being used, for example "Hello Android". 465 * @param locale The locale for which the enrollment needs to be performed. 466 * @param callback The callback to notify of detection events. 467 * @return An always-on hotword detector for the given keyphrase and locale. 468 * 469 * @throws SecurityException if the caller does not hold required permissions 470 * @throws IllegalStateException if there is no DSP hardware support when a caller has a 471 * target SDK of API level 34 or above. 472 * 473 * @deprecated Use {@link #createAlwaysOnHotwordDetector(String, Locale, Executor, 474 * AlwaysOnHotwordDetector.Callback)} instead. 475 * @hide 476 */ 477 @SystemApi 478 @Deprecated 479 @NonNull createAlwaysOnHotwordDetector( @uppressLint"MissingNullability") String keyphrase, @SuppressLint({"MissingNullability", "UseIcu"}) Locale locale, @SuppressLint("MissingNullability") AlwaysOnHotwordDetector.Callback callback)480 public final AlwaysOnHotwordDetector createAlwaysOnHotwordDetector( 481 @SuppressLint("MissingNullability") String keyphrase, // TODO: nullability properly 482 @SuppressLint({"MissingNullability", "UseIcu"}) Locale locale, 483 @SuppressLint("MissingNullability") AlwaysOnHotwordDetector.Callback callback) { 484 return createAlwaysOnHotwordDetectorInternal(keyphrase, locale, 485 /* supportHotwordDetectionService= */ false, /* options= */ null, 486 /* sharedMemory= */ null, /* moduleProperties */ null, 487 /* executor= */ null, callback); 488 } 489 490 /** 491 * Creates an {@link AlwaysOnHotwordDetector} for the given keyphrase and locale. 492 * This instance must be retained and used by the client. 493 * Calling this a second time invalidates the previously created hotword detector 494 * which can no longer be used to manage recognition. 495 * 496 * <p>Note: If there are any active detectors that are created by using 497 * {@link #createAlwaysOnHotwordDetector(String, Locale, PersistableBundle, SharedMemory, 498 * AlwaysOnHotwordDetector.Callback)} or {@link #createAlwaysOnHotwordDetector(String, Locale, 499 * PersistableBundle, SharedMemory, Executor, AlwaysOnHotwordDetector.Callback)} or 500 * {@link #createHotwordDetector(PersistableBundle, SharedMemory, HotwordDetector.Callback)} or 501 * {@link #createHotwordDetector(PersistableBundle, SharedMemory, Executor, 502 * HotwordDetector.Callback)}, call this will throw an {@link IllegalStateException}. 503 * 504 * @param keyphrase The keyphrase that's being used, for example "Hello Android". 505 * @param locale The locale for which the enrollment needs to be performed. 506 * @param executor The executor on which to run the callback. 507 * @param callback The callback to notify of detection events. 508 * @return An always-on hotword detector for the given keyphrase and locale. 509 * 510 * @throws SecurityException if the caller does not hold required permissions 511 * @throws IllegalStateException if there is no DSP hardware support when a caller has a 512 * target SDK of API level 34 or above. 513 * 514 * @hide 515 */ 516 @SystemApi 517 @NonNull createAlwaysOnHotwordDetector( @onNull String keyphrase, @SuppressLint("UseIcu") @NonNull Locale locale, @NonNull @CallbackExecutor Executor executor, @NonNull AlwaysOnHotwordDetector.Callback callback)518 public final AlwaysOnHotwordDetector createAlwaysOnHotwordDetector( 519 @NonNull String keyphrase, @SuppressLint("UseIcu") @NonNull Locale locale, 520 @NonNull @CallbackExecutor Executor executor, 521 @NonNull AlwaysOnHotwordDetector.Callback callback) { 522 // TODO (b/269080850): Resolve AndroidFrameworkRequiresPermission lint warning 523 524 Objects.requireNonNull(keyphrase); 525 Objects.requireNonNull(locale); 526 Objects.requireNonNull(executor); 527 Objects.requireNonNull(callback); 528 return createAlwaysOnHotwordDetectorInternal(keyphrase, locale, 529 /* supportHotwordDetectionService= */ false, /* options= */ null, 530 /* sharedMemory= */ null, /* moduleProperties= */ null, executor, callback); 531 } 532 533 /** 534 * Same as {@link createAlwaysOnHotwordDetector(String, Locale, Executor, 535 * AlwaysOnHotwordDetector.Callback)}, but allow explicit selection of the underlying ST 536 * module to attach to. 537 * Use {@link #listModuleProperties()} to get available modules to attach to. 538 * @hide 539 */ 540 @TestApi 541 @RequiresPermission(Manifest.permission.MANAGE_HOTWORD_DETECTION) 542 @NonNull createAlwaysOnHotwordDetectorForTest( @onNull String keyphrase, @SuppressLint("UseIcu") @NonNull Locale locale, @NonNull SoundTrigger.ModuleProperties moduleProperties, @NonNull @CallbackExecutor Executor executor, @NonNull AlwaysOnHotwordDetector.Callback callback)543 public final AlwaysOnHotwordDetector createAlwaysOnHotwordDetectorForTest( 544 @NonNull String keyphrase, @SuppressLint("UseIcu") @NonNull Locale locale, 545 @NonNull SoundTrigger.ModuleProperties moduleProperties, 546 @NonNull @CallbackExecutor Executor executor, 547 @NonNull AlwaysOnHotwordDetector.Callback callback) { 548 549 Objects.requireNonNull(keyphrase); 550 Objects.requireNonNull(locale); 551 Objects.requireNonNull(moduleProperties); 552 Objects.requireNonNull(executor); 553 Objects.requireNonNull(callback); 554 return createAlwaysOnHotwordDetectorInternal(keyphrase, locale, 555 /* supportHotwordDetectionService= */ false, /* options= */ null, 556 /* sharedMemory= */ null, moduleProperties, executor, callback); 557 } 558 559 560 /** 561 * Create an {@link AlwaysOnHotwordDetector} and trigger a {@link HotwordDetectionService} 562 * service, then it will also pass the read-only data to hotword detection service. 563 * 564 * Like {@link #createAlwaysOnHotwordDetector(String, Locale, AlwaysOnHotwordDetector.Callback) 565 * }. Before calling this function, you should set a valid hotword detection service with 566 * android:hotwordDetectionService in an android.voice_interaction metadata file and set 567 * android:isolatedProcess="true" in the AndroidManifest.xml of hotword detection service. 568 * Otherwise it will throw IllegalStateException. After calling this function, the system will 569 * also trigger a hotword detection service and pass the read-only data back to it. 570 * 571 * <p>Note: The system will trigger hotword detection service after calling this function when 572 * all conditions meet the requirements. 573 * 574 * <p>Note: If there are any active detectors that are created by using 575 * {@link #createAlwaysOnHotwordDetector(String, Locale, AlwaysOnHotwordDetector.Callback)} or 576 * {@link #createAlwaysOnHotwordDetector(String, Locale, Executor, 577 * AlwaysOnHotwordDetector.Callback)}, call this will throw an {@link IllegalStateException}. 578 * 579 * <p>Note that the callback will be executed on the current thread. If the current thread 580 * doesn't have a looper, it will throw a {@link RuntimeException}. To specify the execution 581 * thread, use {@link #createAlwaysOnHotwordDetector(String, Locale, PersistableBundle, 582 * SharedMemory, Executor, AlwaysOnHotwordDetector.Callback)}. 583 * 584 * @param keyphrase The keyphrase that's being used, for example "Hello Android". 585 * @param locale The locale for which the enrollment needs to be performed. 586 * @param options Application configuration data provided by the 587 * {@link VoiceInteractionService}. PersistableBundle does not allow any remotable objects or 588 * other contents that can be used to communicate with other processes. 589 * @param sharedMemory The unrestricted data blob provided by the 590 * {@link VoiceInteractionService}. Use this to provide the hotword models data or other 591 * such data to the trusted process. 592 * @param callback The callback to notify of detection events. 593 * @return An always-on hotword detector for the given keyphrase and locale. 594 * 595 * @throws SecurityException if the caller does not hold required permissions 596 * @throws IllegalStateException if the hotword detection service is not set, isolated process 597 * is not set, or there is no DSP hardware support when a caller has a target SDK of API 598 * level 34 or above. 599 * 600 * @deprecated Use {@link #createAlwaysOnHotwordDetector(String, Locale, PersistableBundle, 601 * SharedMemory, Executor, AlwaysOnHotwordDetector.Callback)} instead. 602 * @hide 603 */ 604 @SystemApi 605 @RequiresPermission(Manifest.permission.MANAGE_HOTWORD_DETECTION) 606 @Deprecated 607 @NonNull createAlwaysOnHotwordDetector( @uppressLint"MissingNullability") String keyphrase, @SuppressLint({"MissingNullability", "UseIcu"}) Locale locale, @Nullable PersistableBundle options, @Nullable SharedMemory sharedMemory, @SuppressLint("MissingNullability") AlwaysOnHotwordDetector.Callback callback)608 public final AlwaysOnHotwordDetector createAlwaysOnHotwordDetector( 609 @SuppressLint("MissingNullability") String keyphrase, // TODO: nullability properly 610 @SuppressLint({"MissingNullability", "UseIcu"}) Locale locale, 611 @Nullable PersistableBundle options, 612 @Nullable SharedMemory sharedMemory, 613 @SuppressLint("MissingNullability") AlwaysOnHotwordDetector.Callback callback) { 614 return createAlwaysOnHotwordDetectorInternal(keyphrase, locale, 615 /* supportHotwordDetectionService= */ true, options, sharedMemory, 616 /* modulProperties */ null, /* executor= */ null, callback); 617 } 618 619 /** 620 * Create an {@link AlwaysOnHotwordDetector} and trigger a {@link HotwordDetectionService} 621 * service, then it will also pass the read-only data to hotword detection service. 622 * 623 * Like {@link #createAlwaysOnHotwordDetector(String, Locale, AlwaysOnHotwordDetector.Callback) 624 * }. Before calling this function, you should set a valid hotword detection service with 625 * android:hotwordDetectionService in an android.voice_interaction metadata file and set 626 * android:isolatedProcess="true" in the AndroidManifest.xml of hotword detection service. 627 * Otherwise it will throw IllegalStateException. After calling this function, the system will 628 * also trigger a hotword detection service and pass the read-only data back to it. 629 * 630 * <p>Note: The system will trigger hotword detection service after calling this function when 631 * all conditions meet the requirements. 632 * 633 * <p>Note: If there are any active detectors that are created by using 634 * {@link #createAlwaysOnHotwordDetector(String, Locale, AlwaysOnHotwordDetector.Callback)} or 635 * {@link #createAlwaysOnHotwordDetector(String, Locale, Executor, 636 * AlwaysOnHotwordDetector.Callback)}, call this will throw an {@link IllegalStateException}. 637 * 638 * @param keyphrase The keyphrase that's being used, for example "Hello Android". 639 * @param locale The locale for which the enrollment needs to be performed. 640 * @param options Application configuration data provided by the 641 * {@link VoiceInteractionService}. PersistableBundle does not allow any remotable objects or 642 * other contents that can be used to communicate with other processes. 643 * @param sharedMemory The unrestricted data blob provided by the 644 * {@link VoiceInteractionService}. Use this to provide the hotword models data or other 645 * such data to the trusted process. 646 * @param executor The executor on which to run the callback. 647 * @param callback The callback to notify of detection events. 648 * @return An always-on hotword detector for the given keyphrase and locale. 649 * 650 * @throws SecurityException if the caller does not hold required permissions 651 * @throws IllegalStateException if the hotword detection service is not set, isolated process 652 * is not set, or there is no DSP hardware support when a caller has a target SDK of API level 653 * 34 or above. 654 * 655 * @hide 656 */ 657 @SystemApi 658 @RequiresPermission(Manifest.permission.MANAGE_HOTWORD_DETECTION) 659 @NonNull createAlwaysOnHotwordDetector( @onNull String keyphrase, @SuppressLint("UseIcu") @NonNull Locale locale, @Nullable PersistableBundle options, @Nullable SharedMemory sharedMemory, @NonNull @CallbackExecutor Executor executor, @NonNull AlwaysOnHotwordDetector.Callback callback)660 public final AlwaysOnHotwordDetector createAlwaysOnHotwordDetector( 661 @NonNull String keyphrase, @SuppressLint("UseIcu") @NonNull Locale locale, 662 @Nullable PersistableBundle options, @Nullable SharedMemory sharedMemory, 663 @NonNull @CallbackExecutor Executor executor, 664 @NonNull AlwaysOnHotwordDetector.Callback callback) { 665 // TODO (b/269080850): Resolve AndroidFrameworkRequiresPermission lint warning 666 667 Objects.requireNonNull(keyphrase); 668 Objects.requireNonNull(locale); 669 Objects.requireNonNull(executor); 670 Objects.requireNonNull(callback); 671 return createAlwaysOnHotwordDetectorInternal(keyphrase, locale, 672 /* supportHotwordDetectionService= */ true, options, sharedMemory, 673 /* moduleProperties= */ null, executor, callback); 674 } 675 676 /** 677 * Same as {@link createAlwaysOnHotwordDetector(String, Locale, 678 * PersistableBundle, SharedMemory, Executor, AlwaysOnHotwordDetector.Callback)}, 679 * but allow explicit selection of the underlying ST module to attach to. 680 * Use {@link #listModuleProperties()} to get available modules to attach to. 681 * @hide 682 */ 683 @TestApi 684 @RequiresPermission(Manifest.permission.MANAGE_HOTWORD_DETECTION) 685 @NonNull createAlwaysOnHotwordDetectorForTest( @onNull String keyphrase, @SuppressLint("UseIcu") @NonNull Locale locale, @Nullable PersistableBundle options, @Nullable SharedMemory sharedMemory, @NonNull SoundTrigger.ModuleProperties moduleProperties, @NonNull @CallbackExecutor Executor executor, @NonNull AlwaysOnHotwordDetector.Callback callback)686 public final AlwaysOnHotwordDetector createAlwaysOnHotwordDetectorForTest( 687 @NonNull String keyphrase, @SuppressLint("UseIcu") @NonNull Locale locale, 688 @Nullable PersistableBundle options, @Nullable SharedMemory sharedMemory, 689 @NonNull SoundTrigger.ModuleProperties moduleProperties, 690 @NonNull @CallbackExecutor Executor executor, 691 @NonNull AlwaysOnHotwordDetector.Callback callback) { 692 693 Objects.requireNonNull(keyphrase); 694 Objects.requireNonNull(locale); 695 Objects.requireNonNull(moduleProperties); 696 Objects.requireNonNull(executor); 697 Objects.requireNonNull(callback); 698 return createAlwaysOnHotwordDetectorInternal(keyphrase, locale, 699 /* supportHotwordDetectionService= */ true, options, sharedMemory, 700 moduleProperties, executor, callback); 701 } 702 703 704 createAlwaysOnHotwordDetectorInternal( @uppressLint"MissingNullability") String keyphrase, @SuppressLint({"MissingNullability", "UseIcu"}) Locale locale, boolean supportHotwordDetectionService, @Nullable PersistableBundle options, @Nullable SharedMemory sharedMemory, @Nullable SoundTrigger.ModuleProperties moduleProperties, @Nullable @CallbackExecutor Executor executor, @SuppressLint("MissingNullability") AlwaysOnHotwordDetector.Callback callback)705 private AlwaysOnHotwordDetector createAlwaysOnHotwordDetectorInternal( 706 @SuppressLint("MissingNullability") String keyphrase, // TODO: nullability properly 707 @SuppressLint({"MissingNullability", "UseIcu"}) Locale locale, 708 boolean supportHotwordDetectionService, 709 @Nullable PersistableBundle options, 710 @Nullable SharedMemory sharedMemory, 711 @Nullable SoundTrigger.ModuleProperties moduleProperties, 712 @Nullable @CallbackExecutor Executor executor, 713 @SuppressLint("MissingNullability") AlwaysOnHotwordDetector.Callback callback) { 714 715 if (mSystemService == null) { 716 throw new IllegalStateException("Not available until onReady() is called"); 717 } 718 synchronized (mLock) { 719 if (!CompatChanges.isChangeEnabled(MULTIPLE_ACTIVE_HOTWORD_DETECTORS)) { 720 // Allow only one concurrent recognition via the APIs. 721 safelyShutdownAllHotwordDetectors(false); 722 } else { 723 for (HotwordDetector detector : mActiveDetectors) { 724 if (detector.isUsingSandboxedDetectionService() 725 != supportHotwordDetectionService) { 726 throw new IllegalStateException( 727 "It disallows to create trusted and non-trusted detectors " 728 + "at the same time."); 729 } else if (detector instanceof AlwaysOnHotwordDetector) { 730 throw new IllegalStateException( 731 "There is already an active AlwaysOnHotwordDetector. " 732 + "It must be destroyed to create a new one."); 733 } 734 } 735 } 736 737 AlwaysOnHotwordDetector dspDetector = new AlwaysOnHotwordDetector(keyphrase, locale, 738 executor, callback, mKeyphraseEnrollmentInfo, mSystemService, 739 getApplicationContext().getApplicationInfo().targetSdkVersion, 740 supportHotwordDetectionService, getAttributionTag()); 741 mActiveDetectors.add(dspDetector); 742 743 try { 744 dspDetector.registerOnDestroyListener(this::onHotwordDetectorDestroyed); 745 // Check if we are currently overridden, and should use the test module. 746 if (mTestModuleForAlwaysOnHotwordDetectorEnabled) { 747 moduleProperties = getTestModuleProperties(); 748 } 749 // If moduleProperties is null, the default STModule is used. 750 dspDetector.initialize(options, sharedMemory, moduleProperties); 751 } catch (Exception e) { 752 mActiveDetectors.remove(dspDetector); 753 dspDetector.destroy(); 754 throw e; 755 } 756 return dspDetector; 757 } 758 } 759 760 /** 761 * Creates a {@link HotwordDetector} and initializes the application's 762 * {@link HotwordDetectionService} using {@code options} and {code sharedMemory}. 763 * 764 * <p>To be able to call this, you need to set android:hotwordDetectionService in the 765 * android.voice_interaction metadata file to a valid hotword detection service, and set 766 * android:isolatedProcess="true" in the hotword detection service's declaration. Otherwise, 767 * this throws an {@link IllegalStateException}. 768 * 769 * <p>This instance must be retained and used by the client. 770 * Calling this a second time invalidates the previously created hotword detector 771 * which can no longer be used to manage recognition. 772 * 773 * <p>Using this has a noticeable impact on battery, since the microphone is kept open 774 * for the lifetime of the recognition {@link HotwordDetector#startRecognition() session}. On 775 * devices where hardware filtering is available (such as through a DSP), it's highly 776 * recommended to use {@link #createAlwaysOnHotwordDetector} instead. 777 * 778 * <p>Note: If there are any active detectors that are created by using 779 * {@link #createAlwaysOnHotwordDetector(String, Locale, AlwaysOnHotwordDetector.Callback)} or 780 * {@link #createAlwaysOnHotwordDetector(String, Locale, Executor, 781 * AlwaysOnHotwordDetector.Callback)}, call this will throw an {@link IllegalStateException}. 782 * 783 * <p>Note that the callback will be executed on the main thread. To specify the execution 784 * thread, use {@link #createHotwordDetector(PersistableBundle, SharedMemory, Executor, 785 * HotwordDetector.Callback)}. 786 * 787 * @param options Application configuration data to be provided to the 788 * {@link HotwordDetectionService}. PersistableBundle does not allow any remotable objects or 789 * other contents that can be used to communicate with other processes. 790 * @param sharedMemory The unrestricted data blob to be provided to the 791 * {@link HotwordDetectionService}. Use this to provide hotword models or other such data to the 792 * sandboxed process. 793 * @param callback The callback to notify of detection events. 794 * @return A hotword detector for the given audio format. 795 * 796 * @see #createAlwaysOnHotwordDetector(String, Locale, PersistableBundle, SharedMemory, 797 * AlwaysOnHotwordDetector.Callback) 798 * 799 * @see #createAlwaysOnHotwordDetector(String, Locale, PersistableBundle, SharedMemory, 800 * Executor, AlwaysOnHotwordDetector.Callback) 801 * 802 * @deprecated Use {@link #createHotwordDetector(PersistableBundle, SharedMemory, Executor, 803 * HotwordDetector.Callback)} instead. 804 * @hide 805 */ 806 @SystemApi 807 @RequiresPermission(Manifest.permission.MANAGE_HOTWORD_DETECTION) 808 @Deprecated 809 @NonNull createHotwordDetector( @ullable PersistableBundle options, @Nullable SharedMemory sharedMemory, @NonNull HotwordDetector.Callback callback)810 public final HotwordDetector createHotwordDetector( 811 @Nullable PersistableBundle options, 812 @Nullable SharedMemory sharedMemory, 813 @NonNull HotwordDetector.Callback callback) { 814 return createHotwordDetectorInternal(options, sharedMemory, /* executor= */ null, callback); 815 } 816 817 /** 818 * Creates a {@link HotwordDetector} and initializes the application's 819 * {@link HotwordDetectionService} using {@code options} and {code sharedMemory}. 820 * 821 * <p>To be able to call this, you need to set android:hotwordDetectionService in the 822 * android.voice_interaction metadata file to a valid hotword detection service, and set 823 * android:isolatedProcess="true" in the hotword detection service's declaration. Otherwise, 824 * this throws an {@link IllegalStateException}. 825 * 826 * <p>This instance must be retained and used by the client. 827 * Calling this a second time invalidates the previously created hotword detector 828 * which can no longer be used to manage recognition. 829 * 830 * <p>Using this has a noticeable impact on battery, since the microphone is kept open 831 * for the lifetime of the recognition {@link HotwordDetector#startRecognition() session}. On 832 * devices where hardware filtering is available (such as through a DSP), it's highly 833 * recommended to use {@link #createAlwaysOnHotwordDetector} instead. 834 * 835 * <p>Note: If there are any active detectors that are created by using 836 * {@link #createAlwaysOnHotwordDetector(String, Locale, AlwaysOnHotwordDetector.Callback)} or 837 * {@link #createAlwaysOnHotwordDetector(String, Locale, Executor, 838 * AlwaysOnHotwordDetector.Callback)}, call this will throw an {@link IllegalStateException}. 839 * 840 * @param options Application configuration data to be provided to the 841 * {@link HotwordDetectionService}. PersistableBundle does not allow any remotable objects or 842 * other contents that can be used to communicate with other processes. 843 * @param sharedMemory The unrestricted data blob to be provided to the 844 * {@link HotwordDetectionService}. Use this to provide hotword models or other such data to the 845 * sandboxed process. 846 * @param executor The executor on which to run the callback. 847 * @param callback The callback to notify of detection events. 848 * @return A hotword detector for the given audio format. 849 * 850 * @see #createAlwaysOnHotwordDetector(String, Locale, PersistableBundle, SharedMemory, 851 * AlwaysOnHotwordDetector.Callback) 852 * 853 * @see #createAlwaysOnHotwordDetector(String, Locale, PersistableBundle, SharedMemory, 854 * Executor, AlwaysOnHotwordDetector.Callback) 855 * 856 * @hide 857 */ 858 @SystemApi 859 @RequiresPermission(Manifest.permission.MANAGE_HOTWORD_DETECTION) 860 @NonNull createHotwordDetector( @ullable PersistableBundle options, @Nullable SharedMemory sharedMemory, @NonNull @CallbackExecutor Executor executor, @NonNull HotwordDetector.Callback callback)861 public final HotwordDetector createHotwordDetector( 862 @Nullable PersistableBundle options, 863 @Nullable SharedMemory sharedMemory, 864 @NonNull @CallbackExecutor Executor executor, 865 @NonNull HotwordDetector.Callback callback) { 866 // TODO (b/269080850): Resolve AndroidFrameworkRequiresPermission lint warning 867 868 Objects.requireNonNull(executor); 869 Objects.requireNonNull(callback); 870 return createHotwordDetectorInternal(options, sharedMemory, executor, callback); 871 } 872 createHotwordDetectorInternal( @ullable PersistableBundle options, @Nullable SharedMemory sharedMemory, @Nullable @CallbackExecutor Executor executor, @NonNull HotwordDetector.Callback callback)873 private HotwordDetector createHotwordDetectorInternal( 874 @Nullable PersistableBundle options, 875 @Nullable SharedMemory sharedMemory, 876 @Nullable @CallbackExecutor Executor executor, 877 @NonNull HotwordDetector.Callback callback) { 878 if (mSystemService == null) { 879 throw new IllegalStateException("Not available until onReady() is called"); 880 } 881 synchronized (mLock) { 882 if (!CompatChanges.isChangeEnabled(MULTIPLE_ACTIVE_HOTWORD_DETECTORS)) { 883 // Allow only one concurrent recognition via the APIs. 884 safelyShutdownAllHotwordDetectors(false); 885 } else { 886 for (HotwordDetector detector : mActiveDetectors) { 887 if (!detector.isUsingSandboxedDetectionService()) { 888 throw new IllegalStateException( 889 "It disallows to create trusted and non-trusted detectors " 890 + "at the same time."); 891 } else if (detector instanceof SoftwareHotwordDetector) { 892 throw new IllegalStateException( 893 "There is already an active SoftwareHotwordDetector. " 894 + "It must be destroyed to create a new one."); 895 } 896 } 897 } 898 899 SoftwareHotwordDetector softwareHotwordDetector = 900 new SoftwareHotwordDetector(mSystemService, /* audioFormat= */ null, 901 executor, callback, getAttributionTag()); 902 mActiveDetectors.add(softwareHotwordDetector); 903 904 try { 905 softwareHotwordDetector.registerOnDestroyListener( 906 this::onHotwordDetectorDestroyed); 907 softwareHotwordDetector.initialize(options, sharedMemory); 908 } catch (Exception e) { 909 mActiveDetectors.remove(softwareHotwordDetector); 910 softwareHotwordDetector.destroy(); 911 throw e; 912 } 913 return softwareHotwordDetector; 914 } 915 } 916 917 /** 918 * Creates a {@link VisualQueryDetector} and initializes the application's 919 * {@link VisualQueryDetectionService} using {@code options} and {@code sharedMemory}. 920 * 921 * <p>To be able to call this, you need to set android:visualQueryDetectionService in the 922 * android.voice_interaction metadata file to a valid visual query detection service, and set 923 * android:isolatedProcess="true" in the service's declaration. Otherwise, this throws an 924 * {@link IllegalStateException}. 925 * 926 * <p>Using this has a noticeable impact on battery, since the microphone is kept open 927 * for the lifetime of the recognition {@link VisualQueryDetector#startRecognition() session}. 928 * 929 * @param options Application configuration data to be provided to the 930 * {@link VisualQueryDetectionService}. PersistableBundle does not allow any remotable objects 931 * or other contents that can be used to communicate with other processes. 932 * @param sharedMemory The unrestricted data blob to be provided to the 933 * {@link VisualQueryDetectionService}. Use this to provide models or other such data to the 934 * sandboxed process. 935 * @param callback The callback to notify of detection events. Single threaded or sequential 936 * executors are recommended for the callback are not guaranteed to be executed 937 * in the order of how they were called from the 938 * {@link VisualQueryDetectionService}. 939 * @return An instanece of {@link VisualQueryDetector}. 940 * @throws IllegalStateException when there is an existing {@link VisualQueryDetector}, or when 941 * there is a non-trusted hotword detector running. 942 * 943 * @hide 944 */ 945 @SystemApi 946 @RequiresPermission(Manifest.permission.MANAGE_HOTWORD_DETECTION) 947 @NonNull createVisualQueryDetector( @ullable PersistableBundle options, @Nullable SharedMemory sharedMemory, @NonNull @CallbackExecutor Executor executor, @NonNull VisualQueryDetector.Callback callback)948 public final VisualQueryDetector createVisualQueryDetector( 949 @Nullable PersistableBundle options, 950 @Nullable SharedMemory sharedMemory, 951 @NonNull @CallbackExecutor Executor executor, 952 @NonNull VisualQueryDetector.Callback callback) { 953 Objects.requireNonNull(executor); 954 Objects.requireNonNull(callback); 955 956 if (!SYSPROP_VISUAL_QUERY_SERVICE_ENABLED) { 957 throw new IllegalStateException("VisualQueryDetectionService is not enabled on this " 958 + "system. Please set ro.hotword.visual_query_service_enabled to true."); 959 } 960 if (mSystemService == null) { 961 throw new IllegalStateException("Not available until onReady() is called"); 962 } 963 synchronized (mLock) { 964 if (mActiveVisualQueryDetector != null) { 965 throw new IllegalStateException( 966 "There is already an active VisualQueryDetector. " 967 + "It must be destroyed to create a new one."); 968 } 969 for (HotwordDetector detector : mActiveDetectors) { 970 if (!detector.isUsingSandboxedDetectionService()) { 971 throw new IllegalStateException( 972 "It disallows to create trusted and non-trusted detectors " 973 + "at the same time."); 974 } 975 } 976 977 VisualQueryDetector visualQueryDetector = 978 new VisualQueryDetector(mSystemService, executor, callback, this, 979 getAttributionTag()); 980 HotwordDetector visualQueryDetectorInitializationDelegate = 981 visualQueryDetector.getInitializationDelegate(); 982 mActiveDetectors.add(visualQueryDetectorInitializationDelegate); 983 984 try { 985 visualQueryDetector.registerOnDestroyListener(this::onHotwordDetectorDestroyed); 986 visualQueryDetector.initialize(options, sharedMemory); 987 } catch (Exception e) { 988 mActiveDetectors.remove(visualQueryDetectorInitializationDelegate); 989 visualQueryDetector.destroy(); 990 throw e; 991 } 992 mActiveVisualQueryDetector = visualQueryDetector; 993 return visualQueryDetector; 994 } 995 } 996 997 /** 998 * Creates an {@link KeyphraseModelManager} to use for enrolling voice models outside of the 999 * pre-bundled system voice models. 1000 * @hide 1001 */ 1002 @SystemApi 1003 @RequiresPermission(Manifest.permission.MANAGE_VOICE_KEYPHRASES) 1004 @NonNull createKeyphraseModelManager()1005 public final KeyphraseModelManager createKeyphraseModelManager() { 1006 if (mSystemService == null) { 1007 throw new IllegalStateException("Not available until onReady() is called"); 1008 } 1009 synchronized (mLock) { 1010 return new KeyphraseModelManager(mSystemService); 1011 } 1012 } 1013 1014 /** 1015 * @return Details of keyphrases available for enrollment. 1016 * @hide 1017 */ 1018 @VisibleForTesting getKeyphraseEnrollmentInfo()1019 protected final KeyphraseEnrollmentInfo getKeyphraseEnrollmentInfo() { 1020 return mKeyphraseEnrollmentInfo; 1021 } 1022 1023 1024 /** 1025 * Configure {@link createAlwaysOnHotwordDetector(String, Locale, 1026 * SoundTrigger.ModuleProperties, Executor, AlwaysOnHotwordDetector.Callback)} 1027 * and similar overloads to utilize the test SoundTrigger module instead of the 1028 * actual DSP module. 1029 * @param isEnabled - {@code true} if subsequently created {@link AlwaysOnHotwordDetector} 1030 * objects should attach to a test module. {@code false} if subsequently created 1031 * {@link AlwaysOnHotwordDetector} should attach to the actual DSP module. 1032 * @hide 1033 */ 1034 @TestApi setTestModuleForAlwaysOnHotwordDetectorEnabled(boolean isEnabled)1035 public final void setTestModuleForAlwaysOnHotwordDetectorEnabled(boolean isEnabled) { 1036 synchronized (mLock) { 1037 mTestModuleForAlwaysOnHotwordDetectorEnabled = isEnabled; 1038 } 1039 } 1040 1041 /** 1042 * Get the {@link SoundTrigger.ModuleProperties} representing the fake 1043 * STHAL to attach to via {@link createAlwaysOnHotwordDetector(String, Locale, 1044 * SoundTrigger.ModuleProperties, Executor, AlwaysOnHotwordDetector.Callback)} and 1045 * similar overloads for test purposes. 1046 * @return ModuleProperties to use for test purposes. 1047 */ getTestModuleProperties()1048 private final @NonNull SoundTrigger.ModuleProperties getTestModuleProperties() { 1049 var moduleProps = listModuleProperties() 1050 .stream() 1051 .filter((SoundTrigger.ModuleProperties prop) 1052 -> prop.getSupportedModelArch().equals(SoundTrigger.FAKE_HAL_ARCH)) 1053 .findFirst() 1054 .orElse(null); 1055 if (moduleProps == null) { 1056 throw new IllegalStateException("Fake ST HAL should always be available"); 1057 } 1058 return moduleProps; 1059 } 1060 1061 /** 1062 * Checks if a given keyphrase and locale are supported to create an 1063 * {@link AlwaysOnHotwordDetector}. 1064 * 1065 * @return true if the keyphrase and locale combination is supported, false otherwise. 1066 * @hide 1067 */ 1068 @UnsupportedAppUsage isKeyphraseAndLocaleSupportedForHotword(String keyphrase, Locale locale)1069 public final boolean isKeyphraseAndLocaleSupportedForHotword(String keyphrase, Locale locale) { 1070 if (mKeyphraseEnrollmentInfo == null) { 1071 return false; 1072 } 1073 return mKeyphraseEnrollmentInfo.getKeyphraseMetadata(keyphrase, locale) != null; 1074 } 1075 safelyShutdownAllHotwordDetectors(boolean shouldShutDownVisualQueryDetector)1076 private void safelyShutdownAllHotwordDetectors(boolean shouldShutDownVisualQueryDetector) { 1077 synchronized (mLock) { 1078 mActiveDetectors.forEach(detector -> { 1079 try { 1080 // Skip destroying VisualQueryDetector if HotwordDetectors are created 1081 if (!(mActiveVisualQueryDetector != null 1082 && detector == mActiveVisualQueryDetector.getInitializationDelegate()) 1083 || shouldShutDownVisualQueryDetector) { 1084 detector.destroy(); 1085 } 1086 } catch (Exception ex) { 1087 Log.i(TAG, "exception destroying HotwordDetector", ex); 1088 } 1089 }); 1090 } 1091 } 1092 onHotwordDetectorDestroyed(@onNull HotwordDetector detector)1093 private void onHotwordDetectorDestroyed(@NonNull HotwordDetector detector) { 1094 synchronized (mLock) { 1095 if (mActiveVisualQueryDetector != null 1096 && detector == mActiveVisualQueryDetector.getInitializationDelegate()) { 1097 mActiveVisualQueryDetector = null; 1098 } 1099 mActiveDetectors.remove(detector); 1100 } 1101 } 1102 1103 /** 1104 * Provide hints to be reflected in the system UI. 1105 * 1106 * @param hints Arguments used to show UI. 1107 */ setUiHints(@onNull Bundle hints)1108 public final void setUiHints(@NonNull Bundle hints) { 1109 if (hints == null) { 1110 throw new IllegalArgumentException("Hints must be non-null"); 1111 } 1112 1113 try { 1114 mSystemService.setUiHints(hints); 1115 } catch (RemoteException e) { 1116 throw e.rethrowFromSystemServer(); 1117 } 1118 } 1119 1120 @Override dump(FileDescriptor fd, PrintWriter pw, String[] args)1121 protected void dump(FileDescriptor fd, PrintWriter pw, String[] args) { 1122 pw.println("VOICE INTERACTION"); 1123 synchronized (mLock) { 1124 pw.println(" Sandboxed Detector(s):"); 1125 if (mActiveDetectors.size() == 0) { 1126 pw.println(" No detector."); 1127 } else { 1128 mActiveDetectors.forEach(detector -> { 1129 pw.print(" Using sandboxed detection service="); 1130 pw.println(detector.isUsingSandboxedDetectionService()); 1131 detector.dump(" ", pw); 1132 pw.println(); 1133 }); 1134 } 1135 pw.println("Available Model Enrollment Applications:"); 1136 pw.println(" " + mKeyphraseEnrollmentInfo); 1137 } 1138 } 1139 } 1140