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 static android.Manifest.permission.CAPTURE_AUDIO_HOTWORD; 20 import static android.Manifest.permission.RECORD_AUDIO; 21 import static android.service.voice.SoundTriggerFailure.ERROR_CODE_UNKNOWN; 22 import static android.service.voice.VoiceInteractionService.MULTIPLE_ACTIVE_HOTWORD_DETECTORS; 23 24 import android.annotation.ElapsedRealtimeLong; 25 import android.annotation.IntDef; 26 import android.annotation.NonNull; 27 import android.annotation.Nullable; 28 import android.annotation.RequiresPermission; 29 import android.annotation.SuppressLint; 30 import android.annotation.SystemApi; 31 import android.annotation.TestApi; 32 import android.app.ActivityThread; 33 import android.app.compat.CompatChanges; 34 import android.compat.annotation.ChangeId; 35 import android.compat.annotation.EnabledSince; 36 import android.compat.annotation.UnsupportedAppUsage; 37 import android.content.Context; 38 import android.content.Intent; 39 import android.hardware.soundtrigger.KeyphraseEnrollmentInfo; 40 import android.hardware.soundtrigger.KeyphraseMetadata; 41 import android.hardware.soundtrigger.SoundTrigger; 42 import android.hardware.soundtrigger.SoundTrigger.ConfidenceLevel; 43 import android.hardware.soundtrigger.SoundTrigger.KeyphraseRecognitionEvent; 44 import android.hardware.soundtrigger.SoundTrigger.KeyphraseRecognitionExtra; 45 import android.hardware.soundtrigger.SoundTrigger.ModuleProperties; 46 import android.hardware.soundtrigger.SoundTrigger.RecognitionConfig; 47 import android.media.AudioFormat; 48 import android.media.permission.Identity; 49 import android.os.AsyncTask; 50 import android.os.Binder; 51 import android.os.Build; 52 import android.os.Handler; 53 import android.os.HandlerExecutor; 54 import android.os.IBinder; 55 import android.os.Looper; 56 import android.os.Message; 57 import android.os.ParcelFileDescriptor; 58 import android.os.PersistableBundle; 59 import android.os.RemoteException; 60 import android.os.SharedMemory; 61 import android.os.SystemClock; 62 import android.text.TextUtils; 63 import android.util.Log; 64 import android.util.Slog; 65 66 import com.android.internal.annotations.GuardedBy; 67 import com.android.internal.app.IHotwordRecognitionStatusCallback; 68 import com.android.internal.app.IVoiceInteractionManagerService; 69 import com.android.internal.app.IVoiceInteractionSoundTriggerSession; 70 import com.android.internal.infra.AndroidFuture; 71 72 import java.io.PrintWriter; 73 import java.lang.annotation.Retention; 74 import java.lang.annotation.RetentionPolicy; 75 import java.util.Arrays; 76 import java.util.Collections; 77 import java.util.HashSet; 78 import java.util.List; 79 import java.util.Locale; 80 import java.util.Objects; 81 import java.util.Set; 82 import java.util.concurrent.Executor; 83 84 /** 85 * A class that lets a VoiceInteractionService implementation interact with 86 * always-on keyphrase detection APIs. 87 * 88 * @hide 89 * TODO(b/168605867): Once Metalava supports expressing a removed public, but current system API, 90 * mark and track it as such. 91 */ 92 @SystemApi 93 public class AlwaysOnHotwordDetector extends AbstractDetector { 94 //---- States of Keyphrase availability. Return codes for onAvailabilityChanged() ----// 95 /** 96 * Indicates that this hotword detector is no longer valid for any recognition 97 * and should not be used anymore. 98 */ 99 private static final int STATE_INVALID = -3; 100 101 /** 102 * Indicates that recognition for the given keyphrase is not available on the system 103 * because of the hardware configuration. 104 * No further interaction should be performed with the detector that returns this availability. 105 */ 106 public static final int STATE_HARDWARE_UNAVAILABLE = -2; 107 108 /** 109 * Indicates that recognition for the given keyphrase is not supported. 110 * No further interaction should be performed with the detector that returns this availability. 111 * 112 * @deprecated This is no longer a valid state. Enrollment can occur outside of 113 * {@link KeyphraseEnrollmentInfo} through another privileged application. We can no longer 114 * determine ahead of time if the keyphrase and locale are unsupported by the system. 115 */ 116 @Deprecated 117 public static final int STATE_KEYPHRASE_UNSUPPORTED = -1; 118 119 /** 120 * Indicates that the given keyphrase is not enrolled. 121 * The caller may choose to begin an enrollment flow for the keyphrase. 122 */ 123 public static final int STATE_KEYPHRASE_UNENROLLED = 1; 124 125 /** 126 * Indicates that the given keyphrase is currently enrolled and it's possible to start 127 * recognition for it. 128 */ 129 public static final int STATE_KEYPHRASE_ENROLLED = 2; 130 131 /** 132 * Indicates that the availability state of the active keyphrase can't be known due to an error. 133 * 134 * <p>NOTE: No further interaction should be performed with the detector that returns this 135 * state, it would be better to create {@link AlwaysOnHotwordDetector} again. 136 */ 137 public static final int STATE_ERROR = 3; 138 139 /** 140 * Indicates that the detector isn't ready currently. 141 */ 142 private static final int STATE_NOT_READY = 0; 143 144 //-- Flags for startRecognition ----// 145 /** @hide */ 146 @Retention(RetentionPolicy.SOURCE) 147 @IntDef(flag = true, prefix = { "RECOGNITION_FLAG_" }, value = { 148 RECOGNITION_FLAG_NONE, 149 RECOGNITION_FLAG_CAPTURE_TRIGGER_AUDIO, 150 RECOGNITION_FLAG_ALLOW_MULTIPLE_TRIGGERS, 151 RECOGNITION_FLAG_ENABLE_AUDIO_ECHO_CANCELLATION, 152 RECOGNITION_FLAG_ENABLE_AUDIO_NOISE_SUPPRESSION, 153 RECOGNITION_FLAG_RUN_IN_BATTERY_SAVER, 154 }) 155 public @interface RecognitionFlags {} 156 157 /** 158 * Empty flag for {@link #startRecognition(int)}. 159 * 160 * @hide 161 */ 162 public static final int RECOGNITION_FLAG_NONE = 0; 163 164 /** 165 * Recognition flag for {@link #startRecognition(int)} that indicates 166 * whether the trigger audio for hotword needs to be captured. 167 */ 168 public static final int RECOGNITION_FLAG_CAPTURE_TRIGGER_AUDIO = 0x1; 169 170 /** 171 * Recognition flag for {@link #startRecognition(int)} that indicates 172 * whether the recognition should keep going on even after the keyphrase triggers. 173 * If this flag is specified, it's possible to get multiple triggers after a 174 * call to {@link #startRecognition(int)} if the user speaks the keyphrase multiple times. 175 * When this isn't specified, the default behavior is to stop recognition once the 176 * keyphrase is spoken, till the caller starts recognition again. 177 */ 178 public static final int RECOGNITION_FLAG_ALLOW_MULTIPLE_TRIGGERS = 0x2; 179 180 /** 181 * Audio capabilities flag for {@link #startRecognition(int)} that indicates 182 * if the underlying recognition should use AEC. 183 * This capability may or may not be supported by the system, and support can be queried 184 * by calling {@link #getSupportedAudioCapabilities()}. The corresponding capabilities field for 185 * this flag is {@link #AUDIO_CAPABILITY_ECHO_CANCELLATION}. If this flag is passed without the 186 * audio capability supported, there will be no audio effect applied. 187 */ 188 public static final int RECOGNITION_FLAG_ENABLE_AUDIO_ECHO_CANCELLATION = 0x4; 189 190 /** 191 * Audio capabilities flag for {@link #startRecognition(int)} that indicates 192 * if the underlying recognition should use noise suppression. 193 * This capability may or may not be supported by the system, and support can be queried 194 * by calling {@link #getSupportedAudioCapabilities()}. The corresponding capabilities field for 195 * this flag is {@link #AUDIO_CAPABILITY_NOISE_SUPPRESSION}. If this flag is passed without the 196 * audio capability supported, there will be no audio effect applied. 197 */ 198 public static final int RECOGNITION_FLAG_ENABLE_AUDIO_NOISE_SUPPRESSION = 0x8; 199 200 /** 201 * Recognition flag for {@link #startRecognition(int)} that indicates whether the recognition 202 * should continue after battery saver mode is enabled. 203 * When this flag is specified, the caller will be checked for 204 * {@link android.Manifest.permission#SOUND_TRIGGER_RUN_IN_BATTERY_SAVER} permission granted. 205 */ 206 public static final int RECOGNITION_FLAG_RUN_IN_BATTERY_SAVER = 0x10; 207 208 //---- Recognition mode flags. Return codes for getSupportedRecognitionModes() ----// 209 // Must be kept in sync with the related attribute defined as searchKeyphraseRecognitionFlags. 210 211 /** @hide */ 212 @Retention(RetentionPolicy.SOURCE) 213 @IntDef(flag = true, prefix = { "RECOGNITION_MODE_" }, value = { 214 RECOGNITION_MODE_VOICE_TRIGGER, 215 RECOGNITION_MODE_USER_IDENTIFICATION, 216 }) 217 public @interface RecognitionModes {} 218 219 /** 220 * Simple recognition of the key phrase. 221 * Returned by {@link #getSupportedRecognitionModes()} 222 */ 223 public static final int RECOGNITION_MODE_VOICE_TRIGGER 224 = SoundTrigger.RECOGNITION_MODE_VOICE_TRIGGER; 225 226 /** 227 * User identification performed with the keyphrase recognition. 228 * Returned by {@link #getSupportedRecognitionModes()} 229 */ 230 public static final int RECOGNITION_MODE_USER_IDENTIFICATION 231 = SoundTrigger.RECOGNITION_MODE_USER_IDENTIFICATION; 232 233 //-- Audio capabilities. Values in returned bit field for getSupportedAudioCapabilities() --// 234 235 /** @hide */ 236 @Retention(RetentionPolicy.SOURCE) 237 @IntDef(flag = true, prefix = { "AUDIO_CAPABILITY_" }, value = { 238 AUDIO_CAPABILITY_ECHO_CANCELLATION, 239 AUDIO_CAPABILITY_NOISE_SUPPRESSION, 240 }) 241 public @interface AudioCapabilities {} 242 243 /** 244 * If set the underlying module supports AEC. 245 * Returned by {@link #getSupportedAudioCapabilities()} 246 */ 247 public static final int AUDIO_CAPABILITY_ECHO_CANCELLATION = 248 SoundTrigger.ModuleProperties.AUDIO_CAPABILITY_ECHO_CANCELLATION; 249 250 /** 251 * If set, the underlying module supports noise suppression. 252 * Returned by {@link #getSupportedAudioCapabilities()} 253 */ 254 public static final int AUDIO_CAPABILITY_NOISE_SUPPRESSION = 255 SoundTrigger.ModuleProperties.AUDIO_CAPABILITY_NOISE_SUPPRESSION; 256 257 /** @hide */ 258 @Retention(RetentionPolicy.SOURCE) 259 @IntDef(flag = true, prefix = { "MODEL_PARAM_" }, value = { 260 MODEL_PARAM_THRESHOLD_FACTOR, 261 }) 262 public @interface ModelParams {} 263 264 /** 265 * Gates returning {@code IllegalStateException} in {@link #initialize( 266 * PersistableBundle, SharedMemory, SoundTrigger.ModuleProperties)} when no DSP module 267 * is available. If the change is not enabled, the existing behavior of not throwing an 268 * exception and delivering {@link STATE_HARDWARE_UNAVAILABLE} is retained. 269 */ 270 @ChangeId 271 @EnabledSince(targetSdkVersion = Build.VERSION_CODES.UPSIDE_DOWN_CAKE) 272 static final long THROW_ON_INITIALIZE_IF_NO_DSP = 269165460L; 273 274 /** 275 * Gates returning {@link Callback#onFailure} and {@link Callback#onUnknownFailure} 276 * when asynchronous exceptions are propagated to the client. If the change is not enabled, 277 * the existing behavior of delivering {@link #STATE_ERROR} is retained. 278 */ 279 @ChangeId 280 @EnabledSince(targetSdkVersion = Build.VERSION_CODES.UPSIDE_DOWN_CAKE) 281 static final long SEND_ON_FAILURE_FOR_ASYNC_EXCEPTIONS = 280471513L; 282 283 /** 284 * Controls the sensitivity threshold adjustment factor for a given model. 285 * Negative value corresponds to less sensitive model (high threshold) and 286 * a positive value corresponds to a more sensitive model (low threshold). 287 * Default value is 0. 288 */ 289 public static final int MODEL_PARAM_THRESHOLD_FACTOR = 290 android.hardware.soundtrigger.ModelParams.THRESHOLD_FACTOR; 291 292 static final String TAG = "AlwaysOnHotwordDetector"; 293 static final boolean DBG = false; 294 295 private static final int STATUS_ERROR = SoundTrigger.STATUS_ERROR; 296 private static final int STATUS_OK = SoundTrigger.STATUS_OK; 297 298 private static final int MSG_AVAILABILITY_CHANGED = 1; 299 private static final int MSG_HOTWORD_DETECTED = 2; 300 private static final int MSG_DETECTION_ERROR = 3; 301 private static final int MSG_DETECTION_PAUSE = 4; 302 private static final int MSG_DETECTION_RESUME = 5; 303 private static final int MSG_HOTWORD_REJECTED = 6; 304 private static final int MSG_HOTWORD_STATUS_REPORTED = 7; 305 private static final int MSG_PROCESS_RESTARTED = 8; 306 private static final int MSG_DETECTION_HOTWORD_DETECTION_SERVICE_FAILURE = 9; 307 private static final int MSG_DETECTION_SOUND_TRIGGER_FAILURE = 10; 308 private static final int MSG_DETECTION_UNKNOWN_FAILURE = 11; 309 310 private final String mText; 311 private final Locale mLocale; 312 /** 313 * The metadata of the Keyphrase, derived from the enrollment application. 314 * This may be null if this keyphrase isn't supported by the enrollment application. 315 */ 316 @GuardedBy("mLock") 317 @Nullable 318 private KeyphraseMetadata mKeyphraseMetadata; 319 private final KeyphraseEnrollmentInfo mKeyphraseEnrollmentInfo; 320 private final IVoiceInteractionManagerService mModelManagementService; 321 private IVoiceInteractionSoundTriggerSession mSoundTriggerSession; 322 private final SoundTriggerListener mInternalCallback; 323 private final Callback mExternalCallback; 324 private final Executor mExternalExecutor; 325 private final Handler mHandler; 326 private final IBinder mBinder = new Binder(); 327 private final boolean mSupportSandboxedDetectionService; 328 329 @GuardedBy("mLock") 330 private boolean mIsAvailabilityOverriddenByTestApi = false; 331 @GuardedBy("mLock") 332 private int mAvailability = STATE_NOT_READY; 333 334 /** 335 * A ModelParamRange is a representation of supported parameter range for a 336 * given loaded model. 337 */ 338 public static final class ModelParamRange { 339 private final SoundTrigger.ModelParamRange mModelParamRange; 340 341 /** @hide */ ModelParamRange(SoundTrigger.ModelParamRange modelParamRange)342 ModelParamRange(SoundTrigger.ModelParamRange modelParamRange) { 343 mModelParamRange = modelParamRange; 344 } 345 346 /** 347 * Get the beginning of the param range 348 * 349 * @return The inclusive start of the supported range. 350 */ getStart()351 public int getStart() { 352 return mModelParamRange.getStart(); 353 } 354 355 /** 356 * Get the end of the param range 357 * 358 * @return The inclusive end of the supported range. 359 */ getEnd()360 public int getEnd() { 361 return mModelParamRange.getEnd(); 362 } 363 364 @Override 365 @NonNull toString()366 public String toString() { 367 return mModelParamRange.toString(); 368 } 369 370 @Override equals(@ullable Object obj)371 public boolean equals(@Nullable Object obj) { 372 return mModelParamRange.equals(obj); 373 } 374 375 @Override hashCode()376 public int hashCode() { 377 return mModelParamRange.hashCode(); 378 } 379 } 380 381 /** 382 * Additional payload for {@link Callback#onDetected}. 383 */ 384 public static class EventPayload { 385 386 /** 387 * Flags for describing the data format provided in the event payload. 388 * 389 * @hide 390 */ 391 @Retention(RetentionPolicy.SOURCE) 392 @IntDef(prefix = {"DATA_FORMAT_"}, value = { 393 DATA_FORMAT_RAW, 394 DATA_FORMAT_TRIGGER_AUDIO, 395 }) 396 public @interface DataFormat { 397 } 398 399 /** 400 * Data format is not strictly defined by the framework, and the 401 * {@link android.hardware.soundtrigger.SoundTriggerModule} voice engine may populate this 402 * field in any format. 403 */ 404 public static final int DATA_FORMAT_RAW = 0; 405 406 /** 407 * Data format is defined as trigger audio. 408 * 409 * <p>When this format is used, {@link #getCaptureAudioFormat()} can be used to understand 410 * further the audio format for reading the data. 411 * 412 * @see AlwaysOnHotwordDetector#RECOGNITION_FLAG_CAPTURE_TRIGGER_AUDIO 413 */ 414 public static final int DATA_FORMAT_TRIGGER_AUDIO = 1; 415 416 @DataFormat 417 private final int mDataFormat; 418 // Indicates if {@code captureSession} can be used to continue capturing more audio 419 // from the DSP hardware. 420 private final boolean mCaptureAvailable; 421 // The session to use when attempting to capture more audio from the DSP hardware. 422 private final int mCaptureSession; 423 private final AudioFormat mAudioFormat; 424 // Raw data associated with the event. 425 // This is the audio that triggered the keyphrase if {@code isTriggerAudio} is true. 426 private final byte[] mData; 427 private final HotwordDetectedResult mHotwordDetectedResult; 428 private final ParcelFileDescriptor mAudioStream; 429 private final List<KeyphraseRecognitionExtra> mKephraseExtras; 430 431 @ElapsedRealtimeLong 432 private final long mHalEventReceivedMillis; 433 EventPayload(boolean captureAvailable, @Nullable AudioFormat audioFormat, int captureSession, @DataFormat int dataFormat, @Nullable byte[] data, @Nullable HotwordDetectedResult hotwordDetectedResult, @Nullable ParcelFileDescriptor audioStream, @NonNull List<KeyphraseRecognitionExtra> keyphraseExtras, @ElapsedRealtimeLong long halEventReceivedMillis)434 private EventPayload(boolean captureAvailable, 435 @Nullable AudioFormat audioFormat, 436 int captureSession, 437 @DataFormat int dataFormat, 438 @Nullable byte[] data, 439 @Nullable HotwordDetectedResult hotwordDetectedResult, 440 @Nullable ParcelFileDescriptor audioStream, 441 @NonNull List<KeyphraseRecognitionExtra> keyphraseExtras, 442 @ElapsedRealtimeLong long halEventReceivedMillis) { 443 mCaptureAvailable = captureAvailable; 444 mCaptureSession = captureSession; 445 mAudioFormat = audioFormat; 446 mDataFormat = dataFormat; 447 mData = data; 448 mHotwordDetectedResult = hotwordDetectedResult; 449 mAudioStream = audioStream; 450 mKephraseExtras = keyphraseExtras; 451 mHalEventReceivedMillis = halEventReceivedMillis; 452 } 453 454 /** 455 * Gets the format of the audio obtained using {@link #getTriggerAudio()}. 456 * May be null if there's no audio present. 457 */ 458 @Nullable getCaptureAudioFormat()459 public AudioFormat getCaptureAudioFormat() { 460 return mAudioFormat; 461 } 462 463 /** 464 * Gets the raw audio that triggered the keyphrase. 465 * This may be null if the trigger audio isn't available. 466 * If non-null, the format of the audio can be obtained by calling 467 * {@link #getCaptureAudioFormat()}. 468 * 469 * @see AlwaysOnHotwordDetector#RECOGNITION_FLAG_CAPTURE_TRIGGER_AUDIO 470 * @deprecated Use {@link #getData()} instead. 471 */ 472 @Deprecated 473 @Nullable getTriggerAudio()474 public byte[] getTriggerAudio() { 475 if (mDataFormat == DATA_FORMAT_TRIGGER_AUDIO) { 476 return mData; 477 } else { 478 return null; 479 } 480 } 481 482 /** 483 * Conveys the format of the additional data that is triggered with the keyphrase event. 484 * 485 * @see AlwaysOnHotwordDetector#RECOGNITION_FLAG_CAPTURE_TRIGGER_AUDIO 486 * @see DataFormat 487 */ 488 @DataFormat getDataFormat()489 public int getDataFormat() { 490 return mDataFormat; 491 } 492 493 /** 494 * Gets additional raw data that is triggered with the keyphrase event. 495 * 496 * <p>A {@link android.hardware.soundtrigger.SoundTriggerModule} may populate this 497 * field with opaque data for use by system applications who know about voice 498 * engine internals. Data may be null if the field is not populated by the 499 * {@link android.hardware.soundtrigger.SoundTriggerModule}. 500 * 501 * <p>If {@link #getDataFormat()} is {@link #DATA_FORMAT_TRIGGER_AUDIO}, then the 502 * entirety of this buffer is expected to be of the format from 503 * {@link #getCaptureAudioFormat()}. 504 * 505 * @see AlwaysOnHotwordDetector#RECOGNITION_FLAG_CAPTURE_TRIGGER_AUDIO 506 */ 507 @Nullable getData()508 public byte[] getData() { 509 return mData; 510 } 511 512 /** 513 * Gets the session ID to start a capture from the DSP. 514 * This may be null if streaming capture isn't possible. 515 * If non-null, the format of the audio that can be captured can be 516 * obtained using {@link #getCaptureAudioFormat()}. 517 * 518 * TODO: Candidate for Public API when the API to start capture with a session ID 519 * is made public. 520 * 521 * TODO: Add this to {@link #getCaptureAudioFormat()}: 522 * "Gets the format of the audio obtained using {@link #getTriggerAudio()} 523 * or {@link #getCaptureSession()}. May be null if no audio can be obtained 524 * for either the trigger or a streaming session." 525 * 526 * TODO: Should this return a known invalid value instead? 527 * 528 * @hide 529 */ 530 @Nullable 531 @UnsupportedAppUsage getCaptureSession()532 public Integer getCaptureSession() { 533 if (mCaptureAvailable) { 534 return mCaptureSession; 535 } else { 536 return null; 537 } 538 } 539 540 /** 541 * Returns {@link HotwordDetectedResult} associated with the hotword event, passed from 542 * {@link HotwordDetectionService}. 543 */ 544 @Nullable getHotwordDetectedResult()545 public HotwordDetectedResult getHotwordDetectedResult() { 546 return mHotwordDetectedResult; 547 } 548 549 /** 550 * Returns a stream with bytes corresponding to the open audio stream with hotword data. 551 * 552 * <p>This data represents an audio stream in the format returned by 553 * {@link #getCaptureAudioFormat}. 554 * 555 * <p>Clients are expected to start consuming the stream within 1 second of receiving the 556 * event. 557 * 558 * <p>When this method returns a non-null, clients must close this stream when it's no 559 * longer needed. Failing to do so will result in microphone being open for longer periods 560 * of time, and app being attributed for microphone usage. 561 */ 562 @Nullable getAudioStream()563 public ParcelFileDescriptor getAudioStream() { 564 return mAudioStream; 565 } 566 567 /** 568 * Returns the keyphrases recognized by the voice engine with additional confidence 569 * information 570 * 571 * @return List of keyphrase extras describing additional data for each keyphrase the voice 572 * engine triggered on for this event. The ordering of the list is preserved based on what 573 * the ordering provided by {@link android.hardware.soundtrigger.SoundTriggerModule}. 574 */ 575 @NonNull getKeyphraseRecognitionExtras()576 public List<KeyphraseRecognitionExtra> getKeyphraseRecognitionExtras() { 577 return mKephraseExtras; 578 } 579 580 /** 581 * Timestamp of when the trigger event from SoundTriggerHal was received by the system 582 * server. 583 * 584 * Clock monotonic including suspend time or its equivalent on the system, 585 * in the same units and timebase as {@link SystemClock#elapsedRealtime()}. 586 * 587 * @return Elapsed realtime in milliseconds when the event was received from the HAL. 588 * Returns -1 if the event was not generated from the HAL. 589 */ 590 @ElapsedRealtimeLong getHalEventReceivedMillis()591 public long getHalEventReceivedMillis() { 592 return mHalEventReceivedMillis; 593 } 594 595 /** 596 * Builder class for {@link EventPayload} objects 597 * 598 * @hide 599 */ 600 @TestApi 601 public static final class Builder { 602 private boolean mCaptureAvailable = false; 603 private int mCaptureSession = -1; 604 private AudioFormat mAudioFormat = null; 605 @DataFormat 606 private int mDataFormat = DATA_FORMAT_RAW; 607 private byte[] mData = null; 608 private HotwordDetectedResult mHotwordDetectedResult = null; 609 private ParcelFileDescriptor mAudioStream = null; 610 private List<KeyphraseRecognitionExtra> mKeyphraseExtras = Collections.emptyList(); 611 @ElapsedRealtimeLong 612 private long mHalEventReceivedMillis = -1; 613 Builder()614 public Builder() {} 615 Builder(SoundTrigger.KeyphraseRecognitionEvent keyphraseRecognitionEvent)616 Builder(SoundTrigger.KeyphraseRecognitionEvent keyphraseRecognitionEvent) { 617 setCaptureAvailable(keyphraseRecognitionEvent.isCaptureAvailable()); 618 setCaptureSession(keyphraseRecognitionEvent.getCaptureSession()); 619 if (keyphraseRecognitionEvent.getCaptureFormat() != null) { 620 setCaptureAudioFormat(keyphraseRecognitionEvent.getCaptureFormat()); 621 } 622 setDataFormat((keyphraseRecognitionEvent.triggerInData) ? DATA_FORMAT_TRIGGER_AUDIO 623 : DATA_FORMAT_RAW); 624 if (keyphraseRecognitionEvent.getData() != null) { 625 setData(keyphraseRecognitionEvent.getData()); 626 } 627 if (keyphraseRecognitionEvent.keyphraseExtras != null) { 628 setKeyphraseRecognitionExtras( 629 Arrays.asList(keyphraseRecognitionEvent.keyphraseExtras)); 630 } 631 setHalEventReceivedMillis(keyphraseRecognitionEvent.getHalEventReceivedMillis()); 632 } 633 634 /** 635 * Indicates if {@code captureSession} can be used to continue capturing more audio from 636 * the DSP hardware. 637 */ 638 @SuppressLint("MissingGetterMatchingBuilder") 639 @NonNull setCaptureAvailable(boolean captureAvailable)640 public Builder setCaptureAvailable(boolean captureAvailable) { 641 mCaptureAvailable = captureAvailable; 642 return this; 643 } 644 645 /** 646 * Sets the session ID to start a capture from the DSP. 647 */ 648 @SuppressLint("MissingGetterMatchingBuilder") 649 @NonNull setCaptureSession(int captureSession)650 public Builder setCaptureSession(int captureSession) { 651 mCaptureSession = captureSession; 652 return this; 653 } 654 655 /** 656 * Sets the format of the audio obtained using {@link #getTriggerAudio()}. 657 */ 658 @NonNull setCaptureAudioFormat(@onNull AudioFormat audioFormat)659 public Builder setCaptureAudioFormat(@NonNull AudioFormat audioFormat) { 660 mAudioFormat = audioFormat; 661 return this; 662 } 663 664 /** 665 * Conveys the format of the additional data that is triggered with the keyphrase event. 666 * 667 * @see AlwaysOnHotwordDetector#RECOGNITION_FLAG_CAPTURE_TRIGGER_AUDIO 668 * @see DataFormat 669 */ 670 @NonNull setDataFormat(@ataFormat int dataFormat)671 public Builder setDataFormat(@DataFormat int dataFormat) { 672 mDataFormat = dataFormat; 673 return this; 674 } 675 676 /** 677 * Sets additional raw data that is triggered with the keyphrase event. 678 * 679 * <p>A {@link android.hardware.soundtrigger.SoundTriggerModule} may populate this 680 * field with opaque data for use by system applications who know about voice 681 * engine internals. Data may be null if the field is not populated by the 682 * {@link android.hardware.soundtrigger.SoundTriggerModule}. 683 * 684 * <p>If {@link #getDataFormat()} is {@link #DATA_FORMAT_TRIGGER_AUDIO}, then the 685 * entirety of this 686 * buffer is expected to be of the format from {@link #getCaptureAudioFormat()}. 687 * 688 * @see AlwaysOnHotwordDetector#RECOGNITION_FLAG_CAPTURE_TRIGGER_AUDIO 689 */ 690 @NonNull setData(@onNull byte[] data)691 public Builder setData(@NonNull byte[] data) { 692 mData = data; 693 return this; 694 } 695 696 /** 697 * Sets {@link HotwordDetectedResult} associated with the hotword event, passed from 698 * {@link HotwordDetectionService}. 699 */ 700 @NonNull setHotwordDetectedResult( @onNull HotwordDetectedResult hotwordDetectedResult)701 public Builder setHotwordDetectedResult( 702 @NonNull HotwordDetectedResult hotwordDetectedResult) { 703 mHotwordDetectedResult = hotwordDetectedResult; 704 return this; 705 } 706 707 /** 708 * Sets a stream with bytes corresponding to the open audio stream with hotword data. 709 * 710 * <p>This data represents an audio stream in the format returned by 711 * {@link #getCaptureAudioFormat}. 712 * 713 * <p>Clients are expected to start consuming the stream within 1 second of receiving 714 * the 715 * event. 716 */ 717 @NonNull setAudioStream(@onNull ParcelFileDescriptor audioStream)718 public Builder setAudioStream(@NonNull ParcelFileDescriptor audioStream) { 719 mAudioStream = audioStream; 720 return this; 721 } 722 723 /** 724 * Sets the keyphrases recognized by the voice engine with additional confidence 725 * information 726 */ 727 @NonNull setKeyphraseRecognitionExtras( @onNull List<KeyphraseRecognitionExtra> keyphraseRecognitionExtras)728 public Builder setKeyphraseRecognitionExtras( 729 @NonNull List<KeyphraseRecognitionExtra> keyphraseRecognitionExtras) { 730 mKeyphraseExtras = keyphraseRecognitionExtras; 731 return this; 732 } 733 734 /** 735 * Timestamp of when the trigger event from SoundTriggerHal was received by the 736 * framework. 737 * 738 * Clock monotonic including suspend time or its equivalent on the system, 739 * in the same units and timebase as {@link SystemClock#elapsedRealtime()}. 740 */ 741 @NonNull setHalEventReceivedMillis( @lapsedRealtimeLong long halEventReceivedMillis)742 public Builder setHalEventReceivedMillis( 743 @ElapsedRealtimeLong long halEventReceivedMillis) { 744 mHalEventReceivedMillis = halEventReceivedMillis; 745 return this; 746 } 747 748 /** 749 * Builds an {@link EventPayload} instance 750 */ 751 @NonNull build()752 public EventPayload build() { 753 return new EventPayload(mCaptureAvailable, mAudioFormat, mCaptureSession, 754 mDataFormat, mData, mHotwordDetectedResult, mAudioStream, 755 mKeyphraseExtras, mHalEventReceivedMillis); 756 } 757 } 758 } 759 760 /** 761 * Callbacks for always-on hotword detection. 762 */ 763 public abstract static class Callback implements HotwordDetector.Callback { 764 765 /** 766 * Updates the availability state of the active keyphrase and locale on every keyphrase 767 * sound model change. 768 * 769 * <p>This API is called whenever there's a possibility that the keyphrase associated 770 * with this detector has been updated. It is not guaranteed that there is in fact any 771 * change, as it may be called for other reasons.</p> 772 * 773 * <p>This API is also guaranteed to be called right after an AlwaysOnHotwordDetector 774 * instance is created to updated the current availability state.</p> 775 * 776 * <p>Availability implies the current enrollment state of the given keyphrase. If the 777 * hardware on this system is not capable of listening for the given keyphrase, 778 * {@link AlwaysOnHotwordDetector#STATE_HARDWARE_UNAVAILABLE} will be returned. 779 * 780 * @see AlwaysOnHotwordDetector#STATE_HARDWARE_UNAVAILABLE 781 * @see AlwaysOnHotwordDetector#STATE_KEYPHRASE_UNENROLLED 782 * @see AlwaysOnHotwordDetector#STATE_KEYPHRASE_ENROLLED 783 * @see AlwaysOnHotwordDetector#STATE_ERROR 784 */ onAvailabilityChanged(int status)785 public abstract void onAvailabilityChanged(int status); 786 787 /** 788 * Called when the keyphrase is spoken. 789 * This implicitly stops listening for the keyphrase once it's detected. 790 * Clients should start a recognition again once they are done handling this 791 * detection. 792 * 793 * @param eventPayload Payload data for the detection event. 794 * This may contain the trigger audio, if requested when calling 795 * {@link AlwaysOnHotwordDetector#startRecognition(int)}. 796 */ onDetected(@onNull EventPayload eventPayload)797 public abstract void onDetected(@NonNull EventPayload eventPayload); 798 799 /** 800 * {@inheritDoc} 801 * 802 * @deprecated On {@link android.os.Build.VERSION_CODES#UPSIDE_DOWN_CAKE} and above, 803 * implement {@link HotwordDetector.Callback#onFailure(HotwordDetectionServiceFailure)}, 804 * {@link AlwaysOnHotwordDetector.Callback#onFailure(SoundTriggerFailure)}, 805 * {@link HotwordDetector.Callback#onUnknownFailure(String)} instead. 806 */ 807 @Deprecated 808 @Override onError()809 public abstract void onError(); 810 811 /** 812 * Called when the detection fails due to an error occurs in the 813 * {@link com.android.server.soundtrigger.SoundTriggerService} and 814 * {@link com.android.server.soundtrigger_middleware.SoundTriggerMiddlewareService}, 815 * {@link SoundTriggerFailure} will be reported to the detector. 816 * 817 * @param soundTriggerFailure It provides the error code, error message and suggested 818 * action. 819 */ onFailure(@onNull SoundTriggerFailure soundTriggerFailure)820 public void onFailure(@NonNull SoundTriggerFailure soundTriggerFailure) { 821 onError(); 822 } 823 824 /** {@inheritDoc} */ onRecognitionPaused()825 public abstract void onRecognitionPaused(); 826 827 /** {@inheritDoc} */ onRecognitionResumed()828 public abstract void onRecognitionResumed(); 829 830 /** {@inheritDoc} */ onRejected(@onNull HotwordRejectedResult result)831 public void onRejected(@NonNull HotwordRejectedResult result) { 832 } 833 834 /** {@inheritDoc} */ onHotwordDetectionServiceInitialized(int status)835 public void onHotwordDetectionServiceInitialized(int status) { 836 } 837 838 /** {@inheritDoc} */ onHotwordDetectionServiceRestarted()839 public void onHotwordDetectionServiceRestarted() { 840 } 841 } 842 843 /** 844 * @param text The keyphrase text to get the detector for. 845 * @param locale The java locale for the detector. 846 * @param callback A non-null Callback for receiving the recognition events. 847 * @param modelManagementService A service that allows management of sound models. 848 * @param targetSdkVersion The target SDK version. 849 * @param SupportSandboxedDetectionService {@code true} if HotwordDetectionService should be 850 * triggered, otherwise {@code false}. 851 * 852 * @hide 853 */ AlwaysOnHotwordDetector(String text, Locale locale, Executor executor, Callback callback, KeyphraseEnrollmentInfo keyphraseEnrollmentInfo, IVoiceInteractionManagerService modelManagementService, int targetSdkVersion, boolean supportSandboxedDetectionService)854 public AlwaysOnHotwordDetector(String text, Locale locale, Executor executor, Callback callback, 855 KeyphraseEnrollmentInfo keyphraseEnrollmentInfo, 856 IVoiceInteractionManagerService modelManagementService, int targetSdkVersion, 857 boolean supportSandboxedDetectionService) { 858 super(modelManagementService, executor, callback); 859 860 mHandler = new MyHandler(Looper.getMainLooper()); 861 mText = text; 862 mLocale = locale; 863 mKeyphraseEnrollmentInfo = keyphraseEnrollmentInfo; 864 mExternalCallback = callback; 865 mExternalExecutor = executor != null ? executor : new HandlerExecutor( 866 new Handler(Looper.myLooper())); 867 mInternalCallback = new SoundTriggerListener(mHandler); 868 mModelManagementService = modelManagementService; 869 mSupportSandboxedDetectionService = supportSandboxedDetectionService; 870 } 871 872 // Do nothing. This method should not be abstract. 873 // TODO (b/269355519) un-subclass AOHD. 874 @Override initialize(@ullable PersistableBundle options, @Nullable SharedMemory sharedMemory)875 void initialize(@Nullable PersistableBundle options, @Nullable SharedMemory sharedMemory) {} 876 initialize(@ullable PersistableBundle options, @Nullable SharedMemory sharedMemory, @Nullable SoundTrigger.ModuleProperties moduleProperties)877 void initialize(@Nullable PersistableBundle options, @Nullable SharedMemory sharedMemory, 878 @Nullable SoundTrigger.ModuleProperties moduleProperties) { 879 if (mSupportSandboxedDetectionService) { 880 initAndVerifyDetector(options, sharedMemory, mInternalCallback, 881 DETECTOR_TYPE_TRUSTED_HOTWORD_DSP); 882 } 883 try { 884 Identity identity = new Identity(); 885 identity.packageName = ActivityThread.currentOpPackageName(); 886 if (moduleProperties == null) { 887 moduleProperties = mModelManagementService 888 .listModuleProperties(identity) 889 .stream() 890 .filter(prop -> !prop.getSupportedModelArch() 891 .equals(SoundTrigger.FAKE_HAL_ARCH)) 892 .findFirst() 893 .orElse(null); 894 if (CompatChanges.isChangeEnabled(THROW_ON_INITIALIZE_IF_NO_DSP) && 895 moduleProperties == null) { 896 throw new IllegalStateException("No DSP module available to attach to"); 897 } 898 } 899 mSoundTriggerSession = 900 mModelManagementService.createSoundTriggerSessionAsOriginator( 901 identity, mBinder, moduleProperties); 902 } catch (RemoteException e) { 903 throw e.rethrowAsRuntimeException(); 904 } 905 new RefreshAvailabilityTask().execute(); 906 } 907 908 /** 909 * {@inheritDoc} 910 * 911 * @throws IllegalStateException if this AlwaysOnHotwordDetector wasn't specified to use a 912 * {@link HotwordDetectionService} when it was created. In addition, if this 913 * AlwaysOnHotwordDetector is in an invalid or error state. 914 */ 915 @Override updateState(@ullable PersistableBundle options, @Nullable SharedMemory sharedMemory)916 public final void updateState(@Nullable PersistableBundle options, 917 @Nullable SharedMemory sharedMemory) { 918 synchronized (mLock) { 919 if (!mSupportSandboxedDetectionService) { 920 throw new IllegalStateException( 921 "updateState called, but it doesn't support hotword detection service"); 922 } 923 if (mAvailability == STATE_INVALID || mAvailability == STATE_ERROR) { 924 throw new IllegalStateException( 925 "updateState called on an invalid detector or error state"); 926 } 927 } 928 929 super.updateState(options, sharedMemory); 930 } 931 932 /** 933 * Test API for manipulating the voice engine and sound model availability. 934 * 935 * After overriding the availability status, the client's 936 * {@link Callback#onAvailabilityChanged(int)} will be called to reflect the updated state. 937 * 938 * When this override is set, all system updates to availability will be ignored. 939 * @hide 940 */ 941 @TestApi overrideAvailability(int availability)942 public void overrideAvailability(int availability) { 943 synchronized (mLock) { 944 mAvailability = availability; 945 mIsAvailabilityOverriddenByTestApi = true; 946 // ENROLLED state requires there to be metadata about the sound model so a fake one 947 // is created. 948 if (mKeyphraseMetadata == null && mAvailability == STATE_KEYPHRASE_ENROLLED) { 949 Set<Locale> fakeSupportedLocales = new HashSet<>(); 950 fakeSupportedLocales.add(mLocale); 951 mKeyphraseMetadata = new KeyphraseMetadata(1, mText, fakeSupportedLocales, 952 AlwaysOnHotwordDetector.RECOGNITION_MODE_VOICE_TRIGGER); 953 } 954 notifyStateChangedLocked(); 955 } 956 } 957 958 /** 959 * Test API for clearing an availability override set by {@link #overrideAvailability(int)} 960 * 961 * This method will restore the availability to the current system state. 962 * @hide 963 */ 964 @TestApi resetAvailability()965 public void resetAvailability() { 966 synchronized (mLock) { 967 mIsAvailabilityOverriddenByTestApi = false; 968 } 969 // Execute a refresh availability task - which should then notify of a change. 970 new RefreshAvailabilityTask().execute(); 971 } 972 973 /** 974 * Test API to simulate to trigger hardware recognition event for test. 975 * 976 * @hide 977 */ 978 @TestApi 979 @RequiresPermission(allOf = {RECORD_AUDIO, CAPTURE_AUDIO_HOTWORD}) triggerHardwareRecognitionEventForTest(int status, int soundModelHandle, @ElapsedRealtimeLong long halEventReceivedMillis, boolean captureAvailable, int captureSession, int captureDelayMs, int capturePreambleMs, boolean triggerInData, @NonNull AudioFormat captureFormat, @Nullable byte[] data, @NonNull List<KeyphraseRecognitionExtra> keyphraseRecognitionExtras)980 public void triggerHardwareRecognitionEventForTest(int status, int soundModelHandle, 981 @ElapsedRealtimeLong long halEventReceivedMillis, boolean captureAvailable, 982 int captureSession, int captureDelayMs, int capturePreambleMs, boolean triggerInData, 983 @NonNull AudioFormat captureFormat, @Nullable byte[] data, 984 @NonNull List<KeyphraseRecognitionExtra> keyphraseRecognitionExtras) { 985 Log.d(TAG, "triggerHardwareRecognitionEventForTest()"); 986 synchronized (mLock) { 987 if (mAvailability == STATE_INVALID || mAvailability == STATE_ERROR) { 988 throw new IllegalStateException("triggerHardwareRecognitionEventForTest called on" 989 + " an invalid detector or error state"); 990 } 991 try { 992 mModelManagementService.triggerHardwareRecognitionEventForTest( 993 new KeyphraseRecognitionEvent(status, soundModelHandle, captureAvailable, 994 captureSession, captureDelayMs, capturePreambleMs, triggerInData, 995 captureFormat, data, keyphraseRecognitionExtras.toArray( 996 new KeyphraseRecognitionExtra[0]), halEventReceivedMillis, 997 new Binder()), 998 mInternalCallback); 999 } catch (RemoteException e) { 1000 throw e.rethrowFromSystemServer(); 1001 } 1002 } 1003 } 1004 1005 /** 1006 * Gets the recognition modes supported by the associated keyphrase. 1007 * 1008 * @see #RECOGNITION_MODE_USER_IDENTIFICATION 1009 * @see #RECOGNITION_MODE_VOICE_TRIGGER 1010 * 1011 * @throws UnsupportedOperationException if the keyphrase itself isn't supported. 1012 * Callers should only call this method after a supported state callback on 1013 * {@link Callback#onAvailabilityChanged(int)} to avoid this exception. 1014 * @throws IllegalStateException if the detector is in an invalid or error state. 1015 * This may happen if another detector has been instantiated or the 1016 * {@link VoiceInteractionService} hosting this detector has been shut down. 1017 */ getSupportedRecognitionModes()1018 public @RecognitionModes int getSupportedRecognitionModes() { 1019 if (DBG) Slog.d(TAG, "getSupportedRecognitionModes()"); 1020 synchronized (mLock) { 1021 return getSupportedRecognitionModesLocked(); 1022 } 1023 } 1024 1025 @GuardedBy("mLock") getSupportedRecognitionModesLocked()1026 private int getSupportedRecognitionModesLocked() { 1027 if (mAvailability == STATE_INVALID || mAvailability == STATE_ERROR) { 1028 throw new IllegalStateException( 1029 "getSupportedRecognitionModes called on an invalid detector or error state"); 1030 } 1031 1032 // This method only makes sense if we can actually support a recognition. 1033 if (mAvailability != STATE_KEYPHRASE_ENROLLED || mKeyphraseMetadata == null) { 1034 throw new UnsupportedOperationException( 1035 "Getting supported recognition modes for the keyphrase is not supported"); 1036 } 1037 1038 return mKeyphraseMetadata.getRecognitionModeFlags(); 1039 } 1040 1041 /** 1042 * Get the audio capabilities supported by the platform which can be enabled when 1043 * starting a recognition. 1044 * Caller must be the active voice interaction service via 1045 * Settings.Secure.VOICE_INTERACTION_SERVICE. 1046 * 1047 * @see #AUDIO_CAPABILITY_ECHO_CANCELLATION 1048 * @see #AUDIO_CAPABILITY_NOISE_SUPPRESSION 1049 * 1050 * @return Bit field encoding of the AudioCapabilities supported. 1051 */ 1052 @AudioCapabilities getSupportedAudioCapabilities()1053 public int getSupportedAudioCapabilities() { 1054 if (DBG) Slog.d(TAG, "getSupportedAudioCapabilities()"); 1055 synchronized (mLock) { 1056 return getSupportedAudioCapabilitiesLocked(); 1057 } 1058 } 1059 1060 @GuardedBy("mLock") getSupportedAudioCapabilitiesLocked()1061 private int getSupportedAudioCapabilitiesLocked() { 1062 try { 1063 ModuleProperties properties = 1064 mSoundTriggerSession.getDspModuleProperties(); 1065 if (properties != null) { 1066 return properties.getAudioCapabilities(); 1067 } 1068 1069 return 0; 1070 } catch (RemoteException e) { 1071 throw e.rethrowFromSystemServer(); 1072 } 1073 } 1074 1075 /** 1076 * Starts recognition for the associated keyphrase. 1077 * Caller must be the active voice interaction service via 1078 * Settings.Secure.VOICE_INTERACTION_SERVICE. 1079 * 1080 * @see #RECOGNITION_FLAG_CAPTURE_TRIGGER_AUDIO 1081 * @see #RECOGNITION_FLAG_ALLOW_MULTIPLE_TRIGGERS 1082 * 1083 * @param recognitionFlags The flags to control the recognition properties. 1084 * @param data Additional pass-through data to the system voice engine along with the 1085 * startRecognition request. This data is intended to provide additional parameters 1086 * when starting the opaque sound model. 1087 * @return Indicates whether the call succeeded or not. 1088 * @throws UnsupportedOperationException if the recognition isn't supported. 1089 * Callers should only call this method after a supported state callback on 1090 * {@link Callback#onAvailabilityChanged(int)} to avoid this exception. 1091 * @throws IllegalStateException if the detector is in an invalid or error state. 1092 * This may happen if another detector has been instantiated or the 1093 * {@link VoiceInteractionService} hosting this detector has been shut down. 1094 */ 1095 @RequiresPermission(allOf = {RECORD_AUDIO, CAPTURE_AUDIO_HOTWORD}) startRecognition(@ecognitionFlags int recognitionFlags, @NonNull byte[] data)1096 public boolean startRecognition(@RecognitionFlags int recognitionFlags, @NonNull byte[] data) { 1097 synchronized (mLock) { 1098 return startRecognitionLocked(recognitionFlags, data) 1099 == STATUS_OK; 1100 } 1101 } 1102 1103 /** 1104 * Starts recognition for the associated keyphrase. 1105 * Caller must be the active voice interaction service via 1106 * Settings.Secure.VOICE_INTERACTION_SERVICE. 1107 * 1108 * @see #RECOGNITION_FLAG_CAPTURE_TRIGGER_AUDIO 1109 * @see #RECOGNITION_FLAG_ALLOW_MULTIPLE_TRIGGERS 1110 * 1111 * @param recognitionFlags The flags to control the recognition properties. 1112 * @return Indicates whether the call succeeded or not. 1113 * @throws UnsupportedOperationException if the recognition isn't supported. 1114 * Callers should only call this method after a supported state callback on 1115 * {@link Callback#onAvailabilityChanged(int)} to avoid this exception. 1116 * @throws IllegalStateException if the detector is in an invalid or error state. 1117 * This may happen if another detector has been instantiated or the 1118 * {@link VoiceInteractionService} hosting this detector has been shut down. 1119 */ 1120 @RequiresPermission(allOf = {RECORD_AUDIO, CAPTURE_AUDIO_HOTWORD}) startRecognition(@ecognitionFlags int recognitionFlags)1121 public boolean startRecognition(@RecognitionFlags int recognitionFlags) { 1122 if (DBG) Slog.d(TAG, "startRecognition(" + recognitionFlags + ")"); 1123 synchronized (mLock) { 1124 return startRecognitionLocked(recognitionFlags, null /* data */) == STATUS_OK; 1125 } 1126 } 1127 1128 /** 1129 * Starts recognition for the associated keyphrase. 1130 * 1131 * @see #startRecognition(int) 1132 */ 1133 @RequiresPermission(allOf = {RECORD_AUDIO, CAPTURE_AUDIO_HOTWORD}) 1134 @Override startRecognition()1135 public boolean startRecognition() { 1136 return startRecognition(0); 1137 } 1138 1139 /** 1140 * Stops recognition for the associated keyphrase. 1141 * Caller must be the active voice interaction service via 1142 * Settings.Secure.VOICE_INTERACTION_SERVICE. 1143 * 1144 * @return Indicates whether the call succeeded or not. 1145 * @throws UnsupportedOperationException if the recognition isn't supported. 1146 * Callers should only call this method after a supported state callback on 1147 * {@link Callback#onAvailabilityChanged(int)} to avoid this exception. 1148 * @throws IllegalStateException if the detector is in an invalid or error state. 1149 * This may happen if another detector has been instantiated or the 1150 * {@link VoiceInteractionService} hosting this detector has been shut down. 1151 */ 1152 // TODO: Remove this RequiresPermission since it isn't actually enforced. Also fix the javadoc 1153 // about permissions enforcement (when it throws vs when it just returns false) for other 1154 // methods in this class. 1155 @RequiresPermission(allOf = {RECORD_AUDIO, CAPTURE_AUDIO_HOTWORD}) 1156 @Override stopRecognition()1157 public boolean stopRecognition() { 1158 if (DBG) Slog.d(TAG, "stopRecognition()"); 1159 synchronized (mLock) { 1160 if (mAvailability == STATE_INVALID || mAvailability == STATE_ERROR) { 1161 throw new IllegalStateException( 1162 "stopRecognition called on an invalid detector or error state"); 1163 } 1164 1165 // Check if we can start/stop a recognition. 1166 if (mAvailability != STATE_KEYPHRASE_ENROLLED) { 1167 throw new UnsupportedOperationException( 1168 "Recognition for the given keyphrase is not supported"); 1169 } 1170 1171 return stopRecognitionLocked() == STATUS_OK; 1172 } 1173 } 1174 1175 /** 1176 * Set a model specific {@link ModelParams} with the given value. This 1177 * parameter will keep its value for the duration the model is loaded regardless of starting and 1178 * stopping recognition. Once the model is unloaded, the value will be lost. 1179 * {@link AlwaysOnHotwordDetector#queryParameter} should be checked first before calling this 1180 * method. 1181 * Caller must be the active voice interaction service via 1182 * Settings.Secure.VOICE_INTERACTION_SERVICE. 1183 * 1184 * @param modelParam {@link ModelParams} 1185 * @param value Value to set 1186 * @return - {@link SoundTrigger#STATUS_OK} in case of success 1187 * - {@link SoundTrigger#STATUS_NO_INIT} if the native service cannot be reached 1188 * - {@link SoundTrigger#STATUS_BAD_VALUE} invalid input parameter 1189 * - {@link SoundTrigger#STATUS_INVALID_OPERATION} if the call is out of sequence or 1190 * if API is not supported by HAL 1191 * @throws IllegalStateException if the detector is in an invalid or error state. 1192 * This may happen if another detector has been instantiated or the 1193 * {@link VoiceInteractionService} hosting this detector has been shut down. 1194 */ 1195 @RequiresPermission(allOf = {RECORD_AUDIO, CAPTURE_AUDIO_HOTWORD}) setParameter(@odelParams int modelParam, int value)1196 public int setParameter(@ModelParams int modelParam, int value) { 1197 if (DBG) { 1198 Slog.d(TAG, "setParameter(" + modelParam + ", " + value + ")"); 1199 } 1200 1201 synchronized (mLock) { 1202 if (mAvailability == STATE_INVALID || mAvailability == STATE_ERROR) { 1203 throw new IllegalStateException( 1204 "setParameter called on an invalid detector or error state"); 1205 } 1206 1207 return setParameterLocked(modelParam, value); 1208 } 1209 } 1210 1211 /** 1212 * Get a model specific {@link ModelParams}. This parameter will keep its value 1213 * for the duration the model is loaded regardless of starting and stopping recognition. 1214 * Once the model is unloaded, the value will be lost. If the value is not set, a default 1215 * value is returned. See {@link ModelParams} for parameter default values. 1216 * {@link AlwaysOnHotwordDetector#queryParameter} should be checked first before 1217 * calling this method. 1218 * Caller must be the active voice interaction service via 1219 * Settings.Secure.VOICE_INTERACTION_SERVICE. 1220 * 1221 * @param modelParam {@link ModelParams} 1222 * @return value of parameter 1223 * @throws IllegalStateException if the detector is in an invalid or error state. 1224 * This may happen if another detector has been instantiated or the 1225 * {@link VoiceInteractionService} hosting this detector has been shut down. 1226 */ 1227 @RequiresPermission(allOf = {RECORD_AUDIO, CAPTURE_AUDIO_HOTWORD}) getParameter(@odelParams int modelParam)1228 public int getParameter(@ModelParams int modelParam) { 1229 if (DBG) { 1230 Slog.d(TAG, "getParameter(" + modelParam + ")"); 1231 } 1232 1233 synchronized (mLock) { 1234 if (mAvailability == STATE_INVALID || mAvailability == STATE_ERROR) { 1235 throw new IllegalStateException( 1236 "getParameter called on an invalid detector or error state"); 1237 } 1238 1239 return getParameterLocked(modelParam); 1240 } 1241 } 1242 1243 /** 1244 * Determine if parameter control is supported for the given model handle. 1245 * This method should be checked prior to calling {@link AlwaysOnHotwordDetector#setParameter} 1246 * or {@link AlwaysOnHotwordDetector#getParameter}. 1247 * Caller must be the active voice interaction service via 1248 * Settings.Secure.VOICE_INTERACTION_SERVICE. 1249 * 1250 * @param modelParam {@link ModelParams} 1251 * @return supported range of parameter, null if not supported 1252 * @throws IllegalStateException if the detector is in an invalid or error state. 1253 * This may happen if another detector has been instantiated or the 1254 * {@link VoiceInteractionService} hosting this detector has been shut down. 1255 */ 1256 @RequiresPermission(allOf = {RECORD_AUDIO, CAPTURE_AUDIO_HOTWORD}) 1257 @Nullable queryParameter(@odelParams int modelParam)1258 public ModelParamRange queryParameter(@ModelParams int modelParam) { 1259 if (DBG) { 1260 Slog.d(TAG, "queryParameter(" + modelParam + ")"); 1261 } 1262 1263 synchronized (mLock) { 1264 if (mAvailability == STATE_INVALID || mAvailability == STATE_ERROR) { 1265 throw new IllegalStateException( 1266 "queryParameter called on an invalid detector or error state"); 1267 } 1268 1269 return queryParameterLocked(modelParam); 1270 } 1271 } 1272 1273 /** 1274 * Creates an intent to start the enrollment for the associated keyphrase. 1275 * This intent must be invoked using {@link Context#startForegroundService(Intent)}. 1276 * Starting re-enrollment is only valid if the keyphrase is un-enrolled, 1277 * i.e. {@link #STATE_KEYPHRASE_UNENROLLED}, 1278 * otherwise {@link #createReEnrollIntent()} should be preferred. 1279 * 1280 * @return An {@link Intent} to start enrollment for the given keyphrase. 1281 * @throws UnsupportedOperationException if managing they keyphrase isn't supported. 1282 * Callers should only call this method after a supported state callback on 1283 * {@link Callback#onAvailabilityChanged(int)} to avoid this exception. 1284 * @throws IllegalStateException if the detector is in an invalid state. 1285 * This may happen if another detector has been instantiated or the 1286 * {@link VoiceInteractionService} hosting this detector has been shut down. 1287 */ 1288 @Nullable createEnrollIntent()1289 public Intent createEnrollIntent() { 1290 if (DBG) Slog.d(TAG, "createEnrollIntent"); 1291 synchronized (mLock) { 1292 return getManageIntentLocked(KeyphraseEnrollmentInfo.MANAGE_ACTION_ENROLL); 1293 } 1294 } 1295 1296 /** 1297 * Creates an intent to start the un-enrollment for the associated keyphrase. 1298 * This intent must be invoked using {@link Context#startForegroundService(Intent)}. 1299 * Starting re-enrollment is only valid if the keyphrase is already enrolled, 1300 * i.e. {@link #STATE_KEYPHRASE_ENROLLED}, otherwise invoking this may result in an error. 1301 * 1302 * @return An {@link Intent} to start un-enrollment for the given keyphrase. 1303 * @throws UnsupportedOperationException if managing they keyphrase isn't supported. 1304 * Callers should only call this method after a supported state callback on 1305 * {@link Callback#onAvailabilityChanged(int)} to avoid this exception. 1306 * @throws IllegalStateException if the detector is in an invalid state. 1307 * This may happen if another detector has been instantiated or the 1308 * {@link VoiceInteractionService} hosting this detector has been shut down. 1309 */ 1310 @Nullable createUnEnrollIntent()1311 public Intent createUnEnrollIntent() { 1312 if (DBG) Slog.d(TAG, "createUnEnrollIntent"); 1313 synchronized (mLock) { 1314 return getManageIntentLocked(KeyphraseEnrollmentInfo.MANAGE_ACTION_UN_ENROLL); 1315 } 1316 } 1317 1318 /** 1319 * Creates an intent to start the re-enrollment for the associated keyphrase. 1320 * This intent must be invoked using {@link Context#startForegroundService(Intent)}. 1321 * Starting re-enrollment is only valid if the keyphrase is already enrolled, 1322 * i.e. {@link #STATE_KEYPHRASE_ENROLLED}, otherwise invoking this may result in an error. 1323 * 1324 * @return An {@link Intent} to start re-enrollment for the given keyphrase. 1325 * @throws UnsupportedOperationException if managing they keyphrase isn't supported. 1326 * Callers should only call this method after a supported state callback on 1327 * {@link Callback#onAvailabilityChanged(int)} to avoid this exception. 1328 * @throws IllegalStateException if the detector is in an invalid or error state. 1329 * This may happen if another detector has been instantiated or the 1330 * {@link VoiceInteractionService} hosting this detector has been shut down. 1331 */ 1332 @Nullable createReEnrollIntent()1333 public Intent createReEnrollIntent() { 1334 if (DBG) Slog.d(TAG, "createReEnrollIntent"); 1335 synchronized (mLock) { 1336 return getManageIntentLocked(KeyphraseEnrollmentInfo.MANAGE_ACTION_RE_ENROLL); 1337 } 1338 } 1339 1340 @GuardedBy("mLock") getManageIntentLocked(@eyphraseEnrollmentInfo.ManageActions int action)1341 private Intent getManageIntentLocked(@KeyphraseEnrollmentInfo.ManageActions int action) { 1342 if (mAvailability == STATE_INVALID || mAvailability == STATE_ERROR) { 1343 throw new IllegalStateException( 1344 "getManageIntent called on an invalid detector or error state"); 1345 } 1346 1347 // This method only makes sense if we can actually support a recognition. 1348 if (mAvailability != STATE_KEYPHRASE_ENROLLED 1349 && mAvailability != STATE_KEYPHRASE_UNENROLLED) { 1350 throw new UnsupportedOperationException( 1351 "Managing the given keyphrase is not supported"); 1352 } 1353 1354 return mKeyphraseEnrollmentInfo.getManageKeyphraseIntent(action, mText, mLocale); 1355 } 1356 1357 /** {@inheritDoc} */ 1358 @Override destroy()1359 public void destroy() { 1360 synchronized (mLock) { 1361 detachSessionLocked(); 1362 1363 mAvailability = STATE_INVALID; 1364 mIsAvailabilityOverriddenByTestApi = false; 1365 notifyStateChangedLocked(); 1366 } 1367 super.destroy(); 1368 } 1369 detachSessionLocked()1370 private void detachSessionLocked() { 1371 try { 1372 if (DBG) Slog.d(TAG, "detachSessionLocked() " + mSoundTriggerSession); 1373 if (mSoundTriggerSession != null) { 1374 mSoundTriggerSession.detach(); 1375 } 1376 } catch (RemoteException e) { 1377 e.rethrowFromSystemServer(); 1378 } 1379 } 1380 1381 /** 1382 * @hide 1383 */ 1384 @Override isUsingSandboxedDetectionService()1385 public boolean isUsingSandboxedDetectionService() { 1386 return mSupportSandboxedDetectionService; 1387 } 1388 1389 /** 1390 * Reloads the sound models from the service. 1391 * 1392 * @hide 1393 */ 1394 // TODO(b/281608561): remove the enrollment flow from AlwaysOnHotwordDetector onSoundModelsChanged()1395 void onSoundModelsChanged() { 1396 synchronized (mLock) { 1397 if (mAvailability == STATE_INVALID 1398 || mAvailability == STATE_HARDWARE_UNAVAILABLE 1399 || mAvailability == STATE_ERROR) { 1400 Slog.w(TAG, "Received onSoundModelsChanged for an unsupported keyphrase/config" 1401 + " or in the error state"); 1402 return; 1403 } 1404 1405 // Because this method reflects an update from the system service models, we should not 1406 // update the client of an availability change when the availability has been overridden 1407 // via a test API. 1408 if (mIsAvailabilityOverriddenByTestApi) { 1409 Slog.w(TAG, "Suppressing system availability update. " 1410 + "Availability is overridden by test API."); 1411 return; 1412 } 1413 1414 // Stop the recognition before proceeding if we are in the enrolled state. 1415 // The framework makes the guarantee that an actively used model is present in the 1416 // system server's enrollment database. For this reason we much stop an actively running 1417 // model when the underlying sound model in enrollment database no longer match. 1418 if (mAvailability == STATE_KEYPHRASE_ENROLLED) { 1419 // A SoundTriggerFailure will be sent to the client if the model state was 1420 // changed. This is an overloading of the onFailure usage because we are sending a 1421 // callback even in the successful stop case. If stopRecognition is successful, 1422 // suggested next action RESTART_RECOGNITION will be sent. 1423 // TODO(b/281608561): This code path will be removed with other enrollment flows in 1424 // this class. 1425 try { 1426 int result = stopRecognitionLocked(); 1427 if (result == STATUS_OK) { 1428 sendSoundTriggerFailure(new SoundTriggerFailure(ERROR_CODE_UNKNOWN, 1429 "stopped recognition because of enrollment update", 1430 FailureSuggestedAction.RESTART_RECOGNITION)); 1431 } 1432 // only log to logcat here because many failures can be false positives such as 1433 // calling stopRecognition where there is no started session. 1434 Log.w(TAG, "Failed to stop recognition after enrollment update: code=" 1435 + result); 1436 } catch (Exception e) { 1437 Slog.w(TAG, "Failed to stop recognition after enrollment update", e); 1438 if (CompatChanges.isChangeEnabled(SEND_ON_FAILURE_FOR_ASYNC_EXCEPTIONS)) { 1439 sendSoundTriggerFailure(new SoundTriggerFailure(ERROR_CODE_UNKNOWN, 1440 "Failed to stop recognition after enrollment update: " 1441 + Log.getStackTraceString(e), 1442 FailureSuggestedAction.RECREATE_DETECTOR)); 1443 } else { 1444 updateAndNotifyStateChangedLocked(STATE_ERROR); 1445 } 1446 return; 1447 } 1448 } 1449 1450 // Execute a refresh availability task - which should then notify of a change. 1451 new RefreshAvailabilityTask().execute(); 1452 } 1453 } 1454 1455 @GuardedBy("mLock") startRecognitionLocked(int recognitionFlags, @Nullable byte[] data)1456 private int startRecognitionLocked(int recognitionFlags, 1457 @Nullable byte[] data) { 1458 if (DBG) { 1459 Slog.d(TAG, "startRecognition(" 1460 + recognitionFlags 1461 + ", " + Arrays.toString(data) + ")"); 1462 } 1463 if (mAvailability == STATE_INVALID || mAvailability == STATE_ERROR) { 1464 throw new IllegalStateException( 1465 "startRecognition called on an invalid detector or error state"); 1466 } 1467 1468 // Check if we can start/stop a recognition. 1469 if (mAvailability != STATE_KEYPHRASE_ENROLLED) { 1470 throw new UnsupportedOperationException( 1471 "Recognition for the given keyphrase is not supported"); 1472 } 1473 1474 KeyphraseRecognitionExtra[] recognitionExtra = new KeyphraseRecognitionExtra[1]; 1475 // TODO: Do we need to do something about the confidence level here? 1476 recognitionExtra[0] = new KeyphraseRecognitionExtra(mKeyphraseMetadata.getId(), 1477 mKeyphraseMetadata.getRecognitionModeFlags(), 0, new ConfidenceLevel[0]); 1478 boolean captureTriggerAudio = 1479 (recognitionFlags&RECOGNITION_FLAG_CAPTURE_TRIGGER_AUDIO) != 0; 1480 boolean allowMultipleTriggers = 1481 (recognitionFlags&RECOGNITION_FLAG_ALLOW_MULTIPLE_TRIGGERS) != 0; 1482 boolean runInBatterySaver = (recognitionFlags&RECOGNITION_FLAG_RUN_IN_BATTERY_SAVER) != 0; 1483 1484 int audioCapabilities = 0; 1485 if ((recognitionFlags & RECOGNITION_FLAG_ENABLE_AUDIO_ECHO_CANCELLATION) != 0) { 1486 audioCapabilities |= AUDIO_CAPABILITY_ECHO_CANCELLATION; 1487 } 1488 if ((recognitionFlags & RECOGNITION_FLAG_ENABLE_AUDIO_NOISE_SUPPRESSION) != 0) { 1489 audioCapabilities |= AUDIO_CAPABILITY_NOISE_SUPPRESSION; 1490 } 1491 1492 int code; 1493 try { 1494 code = mSoundTriggerSession.startRecognition( 1495 mKeyphraseMetadata.getId(), mLocale.toLanguageTag(), mInternalCallback, 1496 new RecognitionConfig(captureTriggerAudio, allowMultipleTriggers, 1497 recognitionExtra, data, audioCapabilities), 1498 runInBatterySaver); 1499 } catch (RemoteException e) { 1500 throw e.rethrowFromSystemServer(); 1501 } 1502 1503 if (code != STATUS_OK) { 1504 Slog.w(TAG, "startRecognition() failed with error code " + code); 1505 } 1506 return code; 1507 } 1508 1509 @GuardedBy("mLock") stopRecognitionLocked()1510 private int stopRecognitionLocked() { 1511 int code; 1512 try { 1513 code = mSoundTriggerSession.stopRecognition(mKeyphraseMetadata.getId(), 1514 mInternalCallback); 1515 } catch (RemoteException e) { 1516 throw e.rethrowFromSystemServer(); 1517 } 1518 1519 if (code != STATUS_OK) { 1520 Slog.w(TAG, "stopRecognition() failed with error code " + code); 1521 } 1522 return code; 1523 } 1524 1525 @GuardedBy("mLock") setParameterLocked(@odelParams int modelParam, int value)1526 private int setParameterLocked(@ModelParams int modelParam, int value) { 1527 try { 1528 int code = mSoundTriggerSession.setParameter(mKeyphraseMetadata.getId(), modelParam, 1529 value); 1530 1531 if (code != STATUS_OK) { 1532 Slog.w(TAG, "setParameter failed with error code " + code); 1533 } 1534 1535 return code; 1536 } catch (RemoteException e) { 1537 throw e.rethrowFromSystemServer(); 1538 } 1539 } 1540 1541 @GuardedBy("mLock") getParameterLocked(@odelParams int modelParam)1542 private int getParameterLocked(@ModelParams int modelParam) { 1543 try { 1544 return mSoundTriggerSession.getParameter(mKeyphraseMetadata.getId(), modelParam); 1545 } catch (RemoteException e) { 1546 throw e.rethrowFromSystemServer(); 1547 } 1548 } 1549 1550 @GuardedBy("mLock") 1551 @Nullable queryParameterLocked(@odelParams int modelParam)1552 private ModelParamRange queryParameterLocked(@ModelParams int modelParam) { 1553 try { 1554 SoundTrigger.ModelParamRange modelParamRange = 1555 mSoundTriggerSession.queryParameter(mKeyphraseMetadata.getId(), modelParam); 1556 1557 if (modelParamRange == null) { 1558 return null; 1559 } 1560 1561 return new ModelParamRange(modelParamRange); 1562 } catch (RemoteException e) { 1563 throw e.rethrowFromSystemServer(); 1564 } 1565 } 1566 1567 @GuardedBy("mLock") updateAndNotifyStateChangedLocked(int availability)1568 private void updateAndNotifyStateChangedLocked(int availability) { 1569 updateAvailabilityLocked(availability); 1570 notifyStateChangedLocked(); 1571 } 1572 1573 @GuardedBy("mLock") updateAvailabilityLocked(int availability)1574 private void updateAvailabilityLocked(int availability) { 1575 if (DBG) { 1576 Slog.d(TAG, "Hotword availability changed from " + mAvailability 1577 + " -> " + availability); 1578 } 1579 if (!mIsAvailabilityOverriddenByTestApi) { 1580 mAvailability = availability; 1581 } 1582 } 1583 1584 @GuardedBy("mLock") notifyStateChangedLocked()1585 private void notifyStateChangedLocked() { 1586 Message message = Message.obtain(mHandler, MSG_AVAILABILITY_CHANGED); 1587 message.arg1 = mAvailability; 1588 message.sendToTarget(); 1589 } 1590 1591 @GuardedBy("mLock") sendUnknownFailure(String failureMessage)1592 private void sendUnknownFailure(String failureMessage) { 1593 // update but do not call onAvailabilityChanged callback for STATE_ERROR 1594 updateAvailabilityLocked(STATE_ERROR); 1595 Message.obtain(mHandler, MSG_DETECTION_UNKNOWN_FAILURE, failureMessage).sendToTarget(); 1596 } 1597 sendSoundTriggerFailure(@onNull SoundTriggerFailure soundTriggerFailure)1598 private void sendSoundTriggerFailure(@NonNull SoundTriggerFailure soundTriggerFailure) { 1599 Message.obtain(mHandler, MSG_DETECTION_SOUND_TRIGGER_FAILURE, soundTriggerFailure) 1600 .sendToTarget(); 1601 } 1602 1603 /** @hide */ 1604 static final class SoundTriggerListener extends IHotwordRecognitionStatusCallback.Stub { 1605 private final Handler mHandler; 1606 SoundTriggerListener(Handler handler)1607 public SoundTriggerListener(Handler handler) { 1608 mHandler = handler; 1609 } 1610 1611 @Override onKeyphraseDetected( KeyphraseRecognitionEvent event, HotwordDetectedResult result)1612 public void onKeyphraseDetected( 1613 KeyphraseRecognitionEvent event, HotwordDetectedResult result) { 1614 if (DBG) { 1615 Slog.d(TAG, "onDetected(" + event + ")"); 1616 } else { 1617 Slog.i(TAG, "onDetected"); 1618 } 1619 Message.obtain(mHandler, MSG_HOTWORD_DETECTED, 1620 new EventPayload.Builder(event) 1621 .setHotwordDetectedResult(result) 1622 .build()) 1623 .sendToTarget(); 1624 } 1625 1626 @Override onGenericSoundTriggerDetected(SoundTrigger.GenericRecognitionEvent event)1627 public void onGenericSoundTriggerDetected(SoundTrigger.GenericRecognitionEvent event) { 1628 Slog.w(TAG, "Generic sound trigger event detected at AOHD: " + event); 1629 } 1630 1631 @Override onRejected(@onNull HotwordRejectedResult result)1632 public void onRejected(@NonNull HotwordRejectedResult result) { 1633 if (DBG) { 1634 Slog.d(TAG, "onRejected(" + result + ")"); 1635 } else { 1636 Slog.i(TAG, "onRejected"); 1637 } 1638 Message.obtain(mHandler, MSG_HOTWORD_REJECTED, result).sendToTarget(); 1639 } 1640 1641 @Override onHotwordDetectionServiceFailure( HotwordDetectionServiceFailure hotwordDetectionServiceFailure)1642 public void onHotwordDetectionServiceFailure( 1643 HotwordDetectionServiceFailure hotwordDetectionServiceFailure) { 1644 Slog.v(TAG, "onHotwordDetectionServiceFailure: " + hotwordDetectionServiceFailure); 1645 if (hotwordDetectionServiceFailure != null) { 1646 Message.obtain(mHandler, MSG_DETECTION_HOTWORD_DETECTION_SERVICE_FAILURE, 1647 hotwordDetectionServiceFailure).sendToTarget(); 1648 } else { 1649 Message.obtain(mHandler, MSG_DETECTION_UNKNOWN_FAILURE, 1650 "Error data is null").sendToTarget(); 1651 } 1652 } 1653 1654 @Override onVisualQueryDetectionServiceFailure( VisualQueryDetectionServiceFailure visualQueryDetectionServiceFailure)1655 public void onVisualQueryDetectionServiceFailure( 1656 VisualQueryDetectionServiceFailure visualQueryDetectionServiceFailure) 1657 throws RemoteException { 1658 // It should never be called here. 1659 Slog.w(TAG, 1660 "onVisualQueryDetectionServiceFailure: " + visualQueryDetectionServiceFailure); 1661 } 1662 1663 @Override onSoundTriggerFailure(SoundTriggerFailure soundTriggerFailure)1664 public void onSoundTriggerFailure(SoundTriggerFailure soundTriggerFailure) { 1665 Message.obtain(mHandler, MSG_DETECTION_SOUND_TRIGGER_FAILURE, 1666 Objects.requireNonNull(soundTriggerFailure)).sendToTarget(); 1667 } 1668 1669 @Override onUnknownFailure(String errorMessage)1670 public void onUnknownFailure(String errorMessage) throws RemoteException { 1671 Slog.v(TAG, "onUnknownFailure: " + errorMessage); 1672 Message.obtain(mHandler, MSG_DETECTION_UNKNOWN_FAILURE, 1673 !TextUtils.isEmpty(errorMessage) ? errorMessage 1674 : "Error data is null").sendToTarget(); 1675 } 1676 1677 @Override onRecognitionPaused()1678 public void onRecognitionPaused() { 1679 Slog.i(TAG, "onRecognitionPaused"); 1680 mHandler.sendEmptyMessage(MSG_DETECTION_PAUSE); 1681 } 1682 1683 @Override onRecognitionResumed()1684 public void onRecognitionResumed() { 1685 Slog.i(TAG, "onRecognitionResumed"); 1686 mHandler.sendEmptyMessage(MSG_DETECTION_RESUME); 1687 } 1688 1689 @Override onStatusReported(int status)1690 public void onStatusReported(int status) { 1691 if (DBG) { 1692 Slog.d(TAG, "onStatusReported(" + status + ")"); 1693 } else { 1694 Slog.i(TAG, "onStatusReported"); 1695 } 1696 Message message = Message.obtain(mHandler, MSG_HOTWORD_STATUS_REPORTED); 1697 message.arg1 = status; 1698 message.sendToTarget(); 1699 } 1700 1701 @Override onProcessRestarted()1702 public void onProcessRestarted() { 1703 Slog.i(TAG, "onProcessRestarted"); 1704 mHandler.sendEmptyMessage(MSG_PROCESS_RESTARTED); 1705 } 1706 1707 @Override onOpenFile(String filename, AndroidFuture future)1708 public void onOpenFile(String filename, AndroidFuture future) throws RemoteException { 1709 throw new UnsupportedOperationException("Hotword cannot access files from the disk."); 1710 } 1711 } 1712 onDetectorRemoteException()1713 void onDetectorRemoteException() { 1714 Message.obtain(mHandler, MSG_DETECTION_HOTWORD_DETECTION_SERVICE_FAILURE, 1715 new HotwordDetectionServiceFailure( 1716 HotwordDetectionServiceFailure.ERROR_CODE_REMOTE_EXCEPTION, 1717 "Detector remote exception occurs")).sendToTarget(); 1718 } 1719 1720 class MyHandler extends Handler { MyHandler(@onNull Looper looper)1721 MyHandler(@NonNull Looper looper) { 1722 super(looper); 1723 } 1724 1725 @Override handleMessage(Message msg)1726 public void handleMessage(Message msg) { 1727 synchronized (mLock) { 1728 if (mAvailability == STATE_INVALID) { 1729 Slog.w(TAG, "Received message: " + msg.what + " for an invalid detector"); 1730 return; 1731 } 1732 } 1733 final Message message = Message.obtain(msg); 1734 Binder.withCleanCallingIdentity(() -> mExternalExecutor.execute(() -> { 1735 Slog.i(TAG, "handle message " + message.what); 1736 switch (message.what) { 1737 case MSG_AVAILABILITY_CHANGED: 1738 mExternalCallback.onAvailabilityChanged(message.arg1); 1739 break; 1740 case MSG_HOTWORD_DETECTED: 1741 mExternalCallback.onDetected((EventPayload) message.obj); 1742 break; 1743 case MSG_DETECTION_ERROR: 1744 // TODO(b/271534248): After reverting the workaround, this logic is still 1745 // necessary. 1746 mExternalCallback.onError(); 1747 break; 1748 case MSG_DETECTION_PAUSE: 1749 mExternalCallback.onRecognitionPaused(); 1750 break; 1751 case MSG_DETECTION_RESUME: 1752 mExternalCallback.onRecognitionResumed(); 1753 break; 1754 case MSG_HOTWORD_REJECTED: 1755 mExternalCallback.onRejected((HotwordRejectedResult) message.obj); 1756 break; 1757 case MSG_HOTWORD_STATUS_REPORTED: 1758 mExternalCallback.onHotwordDetectionServiceInitialized(message.arg1); 1759 break; 1760 case MSG_PROCESS_RESTARTED: 1761 mExternalCallback.onHotwordDetectionServiceRestarted(); 1762 break; 1763 case MSG_DETECTION_HOTWORD_DETECTION_SERVICE_FAILURE: 1764 mExternalCallback.onFailure((HotwordDetectionServiceFailure) message.obj); 1765 break; 1766 case MSG_DETECTION_SOUND_TRIGGER_FAILURE: 1767 mExternalCallback.onFailure((SoundTriggerFailure) message.obj); 1768 break; 1769 case MSG_DETECTION_UNKNOWN_FAILURE: 1770 mExternalCallback.onUnknownFailure((String) message.obj); 1771 break; 1772 default: 1773 super.handleMessage(message); 1774 } 1775 message.recycle(); 1776 })); 1777 } 1778 } 1779 1780 // TODO(b/267681692): remove the AsyncTask usage 1781 class RefreshAvailabilityTask extends AsyncTask<Void, Void, Void> { 1782 1783 @Override doInBackground(Void... params)1784 public Void doInBackground(Void... params) { 1785 try { 1786 int availability = internalGetInitialAvailability(); 1787 1788 synchronized (mLock) { 1789 if (availability == STATE_NOT_READY) { 1790 internalUpdateEnrolledKeyphraseMetadata(); 1791 if (mKeyphraseMetadata != null) { 1792 availability = STATE_KEYPHRASE_ENROLLED; 1793 } else { 1794 availability = STATE_KEYPHRASE_UNENROLLED; 1795 } 1796 } 1797 updateAndNotifyStateChangedLocked(availability); 1798 } 1799 } catch (Exception e) { 1800 // Any exception here not caught will crash the process because AsyncTask does not 1801 // bubble up the exceptions to the client app, so we must propagate it to the app. 1802 Slog.w(TAG, "Failed to refresh availability", e); 1803 synchronized (mLock) { 1804 if (CompatChanges.isChangeEnabled(SEND_ON_FAILURE_FOR_ASYNC_EXCEPTIONS)) { 1805 sendUnknownFailure( 1806 "Failed to refresh availability: " + Log.getStackTraceString(e)); 1807 } else { 1808 updateAndNotifyStateChangedLocked(STATE_ERROR); 1809 } 1810 } 1811 } 1812 1813 return null; 1814 } 1815 1816 /** 1817 * @return The initial availability without checking the enrollment status. 1818 */ internalGetInitialAvailability()1819 private int internalGetInitialAvailability() { 1820 synchronized (mLock) { 1821 // This detector has already been invalidated. 1822 if (mAvailability == STATE_INVALID) { 1823 return STATE_INVALID; 1824 } 1825 } 1826 1827 if (!CompatChanges.isChangeEnabled(THROW_ON_INITIALIZE_IF_NO_DSP)) { 1828 ModuleProperties dspModuleProperties; 1829 try { 1830 dspModuleProperties = 1831 mSoundTriggerSession.getDspModuleProperties(); 1832 } catch (RemoteException e) { 1833 throw e.rethrowFromSystemServer(); 1834 } 1835 1836 // No DSP available 1837 if (dspModuleProperties == null) { 1838 return STATE_HARDWARE_UNAVAILABLE; 1839 } 1840 } 1841 1842 return STATE_NOT_READY; 1843 } 1844 internalUpdateEnrolledKeyphraseMetadata()1845 private void internalUpdateEnrolledKeyphraseMetadata() { 1846 try { 1847 mKeyphraseMetadata = mModelManagementService.getEnrolledKeyphraseMetadata( 1848 mText, mLocale.toLanguageTag()); 1849 } catch (RemoteException e) { 1850 throw e.rethrowFromSystemServer(); 1851 } 1852 } 1853 } 1854 1855 @Override equals(Object obj)1856 public boolean equals(Object obj) { 1857 if (CompatChanges.isChangeEnabled(MULTIPLE_ACTIVE_HOTWORD_DETECTORS)) { 1858 if (!(obj instanceof AlwaysOnHotwordDetector)) { 1859 return false; 1860 } 1861 AlwaysOnHotwordDetector other = (AlwaysOnHotwordDetector) obj; 1862 return TextUtils.equals(mText, other.mText) && mLocale.equals(other.mLocale); 1863 } 1864 1865 return super.equals(obj); 1866 } 1867 1868 @Override hashCode()1869 public int hashCode() { 1870 return Objects.hash(mText, mLocale); 1871 } 1872 1873 /** @hide */ 1874 @Override dump(String prefix, PrintWriter pw)1875 public void dump(String prefix, PrintWriter pw) { 1876 synchronized (mLock) { 1877 pw.print(prefix); pw.print("Text="); pw.println(mText); 1878 pw.print(prefix); pw.print("Locale="); pw.println(mLocale); 1879 pw.print(prefix); pw.print("Availability="); pw.println(mAvailability); 1880 pw.print(prefix); pw.print("KeyphraseMetadata="); pw.println(mKeyphraseMetadata); 1881 pw.print(prefix); pw.print("EnrollmentInfo="); pw.println(mKeyphraseEnrollmentInfo); 1882 } 1883 } 1884 } 1885