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