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.NonNull; 21 import android.annotation.Nullable; 22 import android.annotation.RequiresPermission; 23 import android.annotation.SdkConstant; 24 import android.annotation.SuppressLint; 25 import android.annotation.SystemApi; 26 import android.app.Service; 27 import android.compat.annotation.UnsupportedAppUsage; 28 import android.content.ComponentName; 29 import android.content.Context; 30 import android.content.Intent; 31 import android.hardware.soundtrigger.KeyphraseEnrollmentInfo; 32 import android.media.voice.KeyphraseModelManager; 33 import android.os.Bundle; 34 import android.os.Handler; 35 import android.os.IBinder; 36 import android.os.PersistableBundle; 37 import android.os.RemoteException; 38 import android.os.ServiceManager; 39 import android.os.SharedMemory; 40 import android.provider.Settings; 41 import android.util.ArraySet; 42 import android.util.Log; 43 44 import com.android.internal.annotations.VisibleForTesting; 45 import com.android.internal.app.IVoiceActionCheckCallback; 46 import com.android.internal.app.IVoiceInteractionManagerService; 47 import com.android.internal.util.function.pooled.PooledLambda; 48 49 import java.io.FileDescriptor; 50 import java.io.PrintWriter; 51 import java.util.ArrayList; 52 import java.util.Collections; 53 import java.util.List; 54 import java.util.Locale; 55 import java.util.Objects; 56 import java.util.Set; 57 58 /** 59 * Top-level service of the current global voice interactor, which is providing 60 * support for hotwording, the back-end of a {@link android.app.VoiceInteractor}, etc. 61 * The current VoiceInteractionService that has been selected by the user is kept 62 * always running by the system, to allow it to do things like listen for hotwords 63 * in the background to instigate voice interactions. 64 * 65 * <p>Because this service is always running, it should be kept as lightweight as 66 * possible. Heavy-weight operations (including showing UI) should be implemented 67 * in the associated {@link android.service.voice.VoiceInteractionSessionService} when 68 * an actual voice interaction is taking place, and that service should run in a 69 * separate process from this one. 70 */ 71 public class VoiceInteractionService extends Service { 72 static final String TAG = VoiceInteractionService.class.getSimpleName(); 73 74 /** 75 * The {@link Intent} that must be declared as handled by the service. 76 * To be supported, the service must also require the 77 * {@link android.Manifest.permission#BIND_VOICE_INTERACTION} permission so 78 * that other applications can not abuse it. 79 */ 80 @SdkConstant(SdkConstant.SdkConstantType.SERVICE_ACTION) 81 public static final String SERVICE_INTERFACE = 82 "android.service.voice.VoiceInteractionService"; 83 84 /** 85 * Name under which a VoiceInteractionService component publishes information about itself. 86 * This meta-data should reference an XML resource containing a 87 * <code><{@link 88 * android.R.styleable#VoiceInteractionService voice-interaction-service}></code> tag. 89 */ 90 public static final String SERVICE_META_DATA = "android.voice_interaction"; 91 92 IVoiceInteractionService mInterface = new IVoiceInteractionService.Stub() { 93 @Override 94 public void ready() { 95 Handler.getMain().executeOrSendMessage(PooledLambda.obtainMessage( 96 VoiceInteractionService::onReady, VoiceInteractionService.this)); 97 } 98 99 @Override 100 public void shutdown() { 101 Handler.getMain().executeOrSendMessage(PooledLambda.obtainMessage( 102 VoiceInteractionService::onShutdownInternal, VoiceInteractionService.this)); 103 } 104 105 @Override 106 public void soundModelsChanged() { 107 Handler.getMain().executeOrSendMessage(PooledLambda.obtainMessage( 108 VoiceInteractionService::onSoundModelsChangedInternal, 109 VoiceInteractionService.this)); 110 } 111 112 @Override 113 public void launchVoiceAssistFromKeyguard() { 114 Handler.getMain().executeOrSendMessage(PooledLambda.obtainMessage( 115 VoiceInteractionService::onLaunchVoiceAssistFromKeyguard, 116 VoiceInteractionService.this)); 117 } 118 119 @Override 120 public void getActiveServiceSupportedActions(List<String> voiceActions, 121 IVoiceActionCheckCallback callback) { 122 Handler.getMain().executeOrSendMessage( 123 PooledLambda.obtainMessage(VoiceInteractionService::onHandleVoiceActionCheck, 124 VoiceInteractionService.this, 125 voiceActions, 126 callback)); 127 } 128 }; 129 130 IVoiceInteractionManagerService mSystemService; 131 132 private final Object mLock = new Object(); 133 134 private KeyphraseEnrollmentInfo mKeyphraseEnrollmentInfo; 135 136 private AlwaysOnHotwordDetector mHotwordDetector; 137 private SoftwareHotwordDetector mSoftwareHotwordDetector; 138 139 /** 140 * Called when a user has activated an affordance to launch voice assist from the Keyguard. 141 * 142 * <p>This method will only be called if the VoiceInteractionService has set 143 * {@link android.R.attr#supportsLaunchVoiceAssistFromKeyguard} and the Keyguard is showing.</p> 144 * 145 * <p>A valid implementation must start a new activity that should use {@link 146 * android.view.WindowManager.LayoutParams#FLAG_SHOW_WHEN_LOCKED} to display 147 * on top of the lock screen.</p> 148 */ onLaunchVoiceAssistFromKeyguard()149 public void onLaunchVoiceAssistFromKeyguard() { 150 } 151 152 /** 153 * Check whether the given service component is the currently active 154 * VoiceInteractionService. 155 */ isActiveService(Context context, ComponentName service)156 public static boolean isActiveService(Context context, ComponentName service) { 157 String cur = Settings.Secure.getString(context.getContentResolver(), 158 Settings.Secure.VOICE_INTERACTION_SERVICE); 159 if (cur == null || cur.isEmpty()) { 160 return false; 161 } 162 ComponentName curComp = ComponentName.unflattenFromString(cur); 163 if (curComp == null) { 164 return false; 165 } 166 return curComp.equals(service); 167 } 168 169 /** 170 * Set contextual options you would always like to have disabled when a session 171 * is shown. The flags may be any combination of 172 * {@link VoiceInteractionSession#SHOW_WITH_ASSIST VoiceInteractionSession.SHOW_WITH_ASSIST} and 173 * {@link VoiceInteractionSession#SHOW_WITH_SCREENSHOT 174 * VoiceInteractionSession.SHOW_WITH_SCREENSHOT}. 175 */ setDisabledShowContext(int flags)176 public void setDisabledShowContext(int flags) { 177 try { 178 mSystemService.setDisabledShowContext(flags); 179 } catch (RemoteException e) { 180 } 181 } 182 183 /** 184 * Return the value set by {@link #setDisabledShowContext}. 185 */ getDisabledShowContext()186 public int getDisabledShowContext() { 187 try { 188 return mSystemService.getDisabledShowContext(); 189 } catch (RemoteException e) { 190 return 0; 191 } 192 } 193 194 /** 195 * Request that the associated {@link android.service.voice.VoiceInteractionSession} be 196 * shown to the user, starting it if necessary. 197 * @param args Arbitrary arguments that will be propagated to the session. 198 * @param flags Indicates additional optional behavior that should be performed. May 199 * be any combination of 200 * {@link VoiceInteractionSession#SHOW_WITH_ASSIST VoiceInteractionSession.SHOW_WITH_ASSIST} and 201 * {@link VoiceInteractionSession#SHOW_WITH_SCREENSHOT 202 * VoiceInteractionSession.SHOW_WITH_SCREENSHOT} 203 * to request that the system generate and deliver assist data on the current foreground 204 * app as part of showing the session UI. 205 */ showSession(Bundle args, int flags)206 public void showSession(Bundle args, int flags) { 207 if (mSystemService == null) { 208 throw new IllegalStateException("Not available until onReady() is called"); 209 } 210 try { 211 mSystemService.showSession(args, flags); 212 } catch (RemoteException e) { 213 } 214 } 215 216 /** 217 * Request to query for what extended voice actions this service supports. This method will 218 * be called when the system checks the supported actions of this 219 * {@link VoiceInteractionService}. Supported actions may be delivered to 220 * {@link VoiceInteractionSession} later to request a session to perform an action. 221 * 222 * <p>Voice actions are defined in support libraries and could vary based on platform context. 223 * For example, car related voice actions will be defined in car support libraries. 224 * 225 * @param voiceActions A set of checked voice actions. 226 * @return Returns a subset of checked voice actions. Additional voice actions in the 227 * returned set will be ignored. Returns empty set if no actions are supported. 228 */ 229 @NonNull onGetSupportedVoiceActions(@onNull Set<String> voiceActions)230 public Set<String> onGetSupportedVoiceActions(@NonNull Set<String> voiceActions) { 231 return Collections.emptySet(); 232 } 233 234 @Override onBind(Intent intent)235 public IBinder onBind(Intent intent) { 236 if (SERVICE_INTERFACE.equals(intent.getAction())) { 237 return mInterface.asBinder(); 238 } 239 return null; 240 } 241 242 /** 243 * Called during service initialization to tell you when the system is ready 244 * to receive interaction from it. You should generally do initialization here 245 * rather than in {@link #onCreate}. Methods such as {@link #showSession} will 246 * not be operational until this point. 247 */ onReady()248 public void onReady() { 249 mSystemService = IVoiceInteractionManagerService.Stub.asInterface( 250 ServiceManager.getService(Context.VOICE_INTERACTION_MANAGER_SERVICE)); 251 Objects.requireNonNull(mSystemService); 252 try { 253 mSystemService.asBinder().linkToDeath(mDeathRecipient, 0); 254 } catch (RemoteException e) { 255 Log.wtf(TAG, "unable to link to death with system service"); 256 } 257 mKeyphraseEnrollmentInfo = new KeyphraseEnrollmentInfo(getPackageManager()); 258 } 259 260 private IBinder.DeathRecipient mDeathRecipient = () -> { 261 Log.e(TAG, "system service binder died shutting down"); 262 Handler.getMain().executeOrSendMessage(PooledLambda.obtainMessage( 263 VoiceInteractionService::onShutdownInternal, VoiceInteractionService.this)); 264 }; 265 266 onShutdownInternal()267 private void onShutdownInternal() { 268 onShutdown(); 269 // Stop any active recognitions when shutting down. 270 // This ensures that if implementations forget to stop any active recognition, 271 // It's still guaranteed to have been stopped. 272 // This helps with cases where the voice interaction implementation is changed 273 // by the user. 274 safelyShutdownHotwordDetector(); 275 } 276 277 /** 278 * Called during service de-initialization to tell you when the system is shutting the 279 * service down. 280 * At this point this service may no longer be the active {@link VoiceInteractionService}. 281 */ onShutdown()282 public void onShutdown() { 283 } 284 onSoundModelsChangedInternal()285 private void onSoundModelsChangedInternal() { 286 synchronized (this) { 287 if (mHotwordDetector != null) { 288 // TODO: Stop recognition if a sound model that was being recognized gets deleted. 289 mHotwordDetector.onSoundModelsChanged(); 290 } 291 } 292 } 293 onHandleVoiceActionCheck(List<String> voiceActions, IVoiceActionCheckCallback callback)294 private void onHandleVoiceActionCheck(List<String> voiceActions, 295 IVoiceActionCheckCallback callback) { 296 if (callback != null) { 297 try { 298 Set<String> voiceActionsSet = new ArraySet<>(voiceActions); 299 Set<String> resultSet = onGetSupportedVoiceActions(voiceActionsSet); 300 callback.onComplete(new ArrayList<>(resultSet)); 301 } catch (RemoteException e) { 302 } 303 } 304 } 305 306 /** 307 * Creates an {@link AlwaysOnHotwordDetector} for the given keyphrase and locale. 308 * This instance must be retained and used by the client. 309 * Calling this a second time invalidates the previously created hotword detector 310 * which can no longer be used to manage recognition. 311 * 312 * @param keyphrase The keyphrase that's being used, for example "Hello Android". 313 * @param locale The locale for which the enrollment needs to be performed. 314 * @param callback The callback to notify of detection events. 315 * @return An always-on hotword detector for the given keyphrase and locale. 316 * 317 * @hide 318 */ 319 @SystemApi 320 @NonNull createAlwaysOnHotwordDetector( @uppressLintR) String keyphrase, @SuppressLint({R, R}) Locale locale, @SuppressLint(R) AlwaysOnHotwordDetector.Callback callback)321 public final AlwaysOnHotwordDetector createAlwaysOnHotwordDetector( 322 @SuppressLint("MissingNullability") String keyphrase, // TODO: nullability properly 323 @SuppressLint({"MissingNullability", "UseIcu"}) Locale locale, 324 @SuppressLint("MissingNullability") AlwaysOnHotwordDetector.Callback callback) { 325 return createAlwaysOnHotwordDetectorInternal(keyphrase, locale, 326 /* supportHotwordDetectionService= */ false, /* options= */ null, 327 /* sharedMemory= */ null, callback); 328 } 329 330 /** 331 * Create an {@link AlwaysOnHotwordDetector} and trigger a {@link HotwordDetectionService} 332 * service, then it will also pass the read-only data to hotword detection service. 333 * 334 * Like {@see #createAlwaysOnHotwordDetector(String, Locale, AlwaysOnHotwordDetector.Callback) 335 * }. Before calling this function, you should set a valid hotword detection service with 336 * android:hotwordDetectionService in an android.voice_interaction metadata file and set 337 * android:isolatedProcess="true" in the AndroidManifest.xml of hotword detection service. 338 * Otherwise it will throw IllegalStateException. After calling this function, the system will 339 * also trigger a hotword detection service and pass the read-only data back to it. 340 * 341 * <p>Note: The system will trigger hotword detection service after calling this function when 342 * all conditions meet the requirements. 343 * 344 * @param keyphrase The keyphrase that's being used, for example "Hello Android". 345 * @param locale The locale for which the enrollment needs to be performed. 346 * @param options Application configuration data provided by the 347 * {@link VoiceInteractionService}. PersistableBundle does not allow any remotable objects or 348 * other contents that can be used to communicate with other processes. 349 * @param sharedMemory The unrestricted data blob provided by the 350 * {@link VoiceInteractionService}. Use this to provide the hotword models data or other 351 * such data to the trusted process. 352 * @param callback The callback to notify of detection events. 353 * @return An always-on hotword detector for the given keyphrase and locale. 354 * 355 * @hide 356 */ 357 @SystemApi 358 @RequiresPermission(Manifest.permission.MANAGE_HOTWORD_DETECTION) 359 @NonNull createAlwaysOnHotwordDetector( @uppressLintR) String keyphrase, @SuppressLint({R, R}) Locale locale, @Nullable PersistableBundle options, @Nullable SharedMemory sharedMemory, @SuppressLint(R) AlwaysOnHotwordDetector.Callback callback)360 public final AlwaysOnHotwordDetector createAlwaysOnHotwordDetector( 361 @SuppressLint("MissingNullability") String keyphrase, // TODO: nullability properly 362 @SuppressLint({"MissingNullability", "UseIcu"}) Locale locale, 363 @Nullable PersistableBundle options, 364 @Nullable SharedMemory sharedMemory, 365 @SuppressLint("MissingNullability") AlwaysOnHotwordDetector.Callback callback) { 366 return createAlwaysOnHotwordDetectorInternal(keyphrase, locale, 367 /* supportHotwordDetectionService= */ true, options, 368 sharedMemory, callback); 369 } 370 createAlwaysOnHotwordDetectorInternal( @uppressLintR) String keyphrase, @SuppressLint({R, R}) Locale locale, boolean supportHotwordDetectionService, @Nullable PersistableBundle options, @Nullable SharedMemory sharedMemory, @SuppressLint(R) AlwaysOnHotwordDetector.Callback callback)371 private AlwaysOnHotwordDetector createAlwaysOnHotwordDetectorInternal( 372 @SuppressLint("MissingNullability") String keyphrase, // TODO: nullability properly 373 @SuppressLint({"MissingNullability", "UseIcu"}) Locale locale, 374 boolean supportHotwordDetectionService, 375 @Nullable PersistableBundle options, 376 @Nullable SharedMemory sharedMemory, 377 @SuppressLint("MissingNullability") AlwaysOnHotwordDetector.Callback callback) { 378 if (mSystemService == null) { 379 throw new IllegalStateException("Not available until onReady() is called"); 380 } 381 synchronized (mLock) { 382 // Allow only one concurrent recognition via the APIs. 383 safelyShutdownHotwordDetector(); 384 mHotwordDetector = new AlwaysOnHotwordDetector(keyphrase, locale, callback, 385 mKeyphraseEnrollmentInfo, mSystemService, 386 getApplicationContext().getApplicationInfo().targetSdkVersion, 387 supportHotwordDetectionService, options, sharedMemory); 388 } 389 return mHotwordDetector; 390 } 391 392 /** 393 * Creates a {@link HotwordDetector} and initializes the application's 394 * {@link HotwordDetectionService} using {@code options} and {code sharedMemory}. 395 * 396 * <p>To be able to call this, you need to set android:hotwordDetectionService in the 397 * android.voice_interaction metadata file to a valid hotword detection service, and set 398 * android:isolatedProcess="true" in the hotword detection service's declaration. Otherwise, 399 * this throws an {@link IllegalStateException}. 400 * 401 * <p>This instance must be retained and used by the client. 402 * Calling this a second time invalidates the previously created hotword detector 403 * which can no longer be used to manage recognition. 404 * 405 * <p>Using this has a noticeable impact on battery, since the microphone is kept open 406 * for the lifetime of the recognition {@link HotwordDetector#startRecognition() session}. On 407 * devices where hardware filtering is available (such as through a DSP), it's highly 408 * recommended to use {@link #createAlwaysOnHotwordDetector} instead. 409 * 410 * @param options Application configuration data to be provided to the 411 * {@link HotwordDetectionService}. PersistableBundle does not allow any remotable objects or 412 * other contents that can be used to communicate with other processes. 413 * @param sharedMemory The unrestricted data blob to be provided to the 414 * {@link HotwordDetectionService}. Use this to provide hotword models or other such data to the 415 * sandboxed process. 416 * @param callback The callback to notify of detection events. 417 * @return A hotword detector for the given audio format. 418 * 419 * @see #createAlwaysOnHotwordDetector(String, Locale, PersistableBundle, SharedMemory, 420 * AlwaysOnHotwordDetector.Callback) 421 * 422 * @hide 423 */ 424 @SystemApi 425 @RequiresPermission(Manifest.permission.MANAGE_HOTWORD_DETECTION) 426 @NonNull createHotwordDetector( @ullable PersistableBundle options, @Nullable SharedMemory sharedMemory, @NonNull HotwordDetector.Callback callback)427 public final HotwordDetector createHotwordDetector( 428 @Nullable PersistableBundle options, 429 @Nullable SharedMemory sharedMemory, 430 @NonNull HotwordDetector.Callback callback) { 431 if (mSystemService == null) { 432 throw new IllegalStateException("Not available until onReady() is called"); 433 } 434 synchronized (mLock) { 435 // Allow only one concurrent recognition via the APIs. 436 safelyShutdownHotwordDetector(); 437 mSoftwareHotwordDetector = 438 new SoftwareHotwordDetector( 439 mSystemService, null, options, sharedMemory, callback); 440 } 441 return mSoftwareHotwordDetector; 442 } 443 444 /** 445 * Creates an {@link KeyphraseModelManager} to use for enrolling voice models outside of the 446 * pre-bundled system voice models. 447 * @hide 448 */ 449 @SystemApi 450 @RequiresPermission(Manifest.permission.MANAGE_VOICE_KEYPHRASES) 451 @NonNull createKeyphraseModelManager()452 public final KeyphraseModelManager createKeyphraseModelManager() { 453 if (mSystemService == null) { 454 throw new IllegalStateException("Not available until onReady() is called"); 455 } 456 synchronized (mLock) { 457 return new KeyphraseModelManager(mSystemService); 458 } 459 } 460 461 /** 462 * @return Details of keyphrases available for enrollment. 463 * @hide 464 */ 465 @VisibleForTesting getKeyphraseEnrollmentInfo()466 protected final KeyphraseEnrollmentInfo getKeyphraseEnrollmentInfo() { 467 return mKeyphraseEnrollmentInfo; 468 } 469 470 /** 471 * Checks if a given keyphrase and locale are supported to create an 472 * {@link AlwaysOnHotwordDetector}. 473 * 474 * @return true if the keyphrase and locale combination is supported, false otherwise. 475 * @hide 476 */ 477 @UnsupportedAppUsage isKeyphraseAndLocaleSupportedForHotword(String keyphrase, Locale locale)478 public final boolean isKeyphraseAndLocaleSupportedForHotword(String keyphrase, Locale locale) { 479 if (mKeyphraseEnrollmentInfo == null) { 480 return false; 481 } 482 return mKeyphraseEnrollmentInfo.getKeyphraseMetadata(keyphrase, locale) != null; 483 } 484 safelyShutdownHotwordDetector()485 private void safelyShutdownHotwordDetector() { 486 synchronized (mLock) { 487 shutdownDspHotwordDetectorLocked(); 488 shutdownMicrophoneHotwordDetectorLocked(); 489 } 490 } 491 shutdownDspHotwordDetectorLocked()492 private void shutdownDspHotwordDetectorLocked() { 493 if (mHotwordDetector == null) { 494 return; 495 } 496 497 try { 498 mHotwordDetector.stopRecognition(); 499 } catch (Exception ex) { 500 // Ignore. 501 } 502 503 try { 504 mHotwordDetector.invalidate(); 505 } catch (Exception ex) { 506 // Ignore. 507 } 508 509 mHotwordDetector = null; 510 } 511 shutdownMicrophoneHotwordDetectorLocked()512 private void shutdownMicrophoneHotwordDetectorLocked() { 513 if (mSoftwareHotwordDetector == null) { 514 return; 515 } 516 517 try { 518 mSoftwareHotwordDetector.stopRecognition(); 519 } catch (Exception ex) { 520 // Ignore. 521 } 522 523 try { 524 mSystemService.shutdownHotwordDetectionService(); 525 } catch (Exception ex) { 526 // Ignore. 527 } 528 529 mSoftwareHotwordDetector = null; 530 } 531 532 /** 533 * Provide hints to be reflected in the system UI. 534 * 535 * @param hints Arguments used to show UI. 536 */ setUiHints(@onNull Bundle hints)537 public final void setUiHints(@NonNull Bundle hints) { 538 if (hints == null) { 539 throw new IllegalArgumentException("Hints must be non-null"); 540 } 541 542 try { 543 mSystemService.setUiHints(hints); 544 } catch (RemoteException e) { 545 throw e.rethrowFromSystemServer(); 546 } 547 } 548 549 @Override dump(FileDescriptor fd, PrintWriter pw, String[] args)550 protected void dump(FileDescriptor fd, PrintWriter pw, String[] args) { 551 pw.println("VOICE INTERACTION"); 552 synchronized (mLock) { 553 pw.println(" AlwaysOnHotwordDetector"); 554 if (mHotwordDetector == null) { 555 pw.println(" NULL"); 556 } else { 557 mHotwordDetector.dump(" ", pw); 558 } 559 560 pw.println(" MicrophoneHotwordDetector"); 561 if (mSoftwareHotwordDetector == null) { 562 pw.println(" NULL"); 563 } else { 564 mSoftwareHotwordDetector.dump(" ", pw); 565 } 566 } 567 } 568 } 569