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.hardware.soundtrigger; 18 19 import static android.Manifest.permission.CAPTURE_AUDIO_HOTWORD; 20 import static android.Manifest.permission.RECORD_AUDIO; 21 import static android.Manifest.permission.SOUNDTRIGGER_DELEGATE_IDENTITY; 22 import static android.system.OsConstants.EBUSY; 23 import static android.system.OsConstants.EINVAL; 24 import static android.system.OsConstants.ENODEV; 25 import static android.system.OsConstants.ENOSYS; 26 import static android.system.OsConstants.EPERM; 27 import static android.system.OsConstants.EPIPE; 28 29 import static java.util.Objects.requireNonNull; 30 31 import android.annotation.IntDef; 32 import android.annotation.IntRange; 33 import android.annotation.NonNull; 34 import android.annotation.Nullable; 35 import android.annotation.RequiresPermission; 36 import android.annotation.SuppressLint; 37 import android.annotation.SystemApi; 38 import android.annotation.TestApi; 39 import android.app.ActivityThread; 40 import android.compat.annotation.UnsupportedAppUsage; 41 import android.content.Context; 42 import android.media.AudioFormat; 43 import android.media.permission.ClearCallingIdentityContext; 44 import android.media.permission.Identity; 45 import android.media.permission.SafeCloseable; 46 import android.media.soundtrigger.Status; 47 import android.media.soundtrigger_middleware.ISoundTriggerMiddlewareService; 48 import android.media.soundtrigger_middleware.SoundTriggerModuleDescriptor; 49 import android.os.Binder; 50 import android.os.Build; 51 import android.os.Handler; 52 import android.os.IBinder; 53 import android.os.Looper; 54 import android.os.Parcel; 55 import android.os.Parcelable; 56 import android.os.RemoteException; 57 import android.os.ServiceManager; 58 import android.os.ServiceSpecificException; 59 import android.util.Log; 60 61 import java.lang.annotation.Retention; 62 import java.lang.annotation.RetentionPolicy; 63 import java.util.ArrayList; 64 import java.util.Arrays; 65 import java.util.Collection; 66 import java.util.Locale; 67 import java.util.UUID; 68 69 /** 70 * The SoundTrigger class provides access to the service managing the sound trigger HAL. 71 * 72 * @hide 73 */ 74 @SystemApi 75 public class SoundTrigger { 76 private static final String TAG = "SoundTrigger"; 77 SoundTrigger()78 private SoundTrigger() { 79 } 80 81 /** 82 * Status code used when the operation succeeded 83 */ 84 public static final int STATUS_OK = 0; 85 /** @hide */ 86 public static final int STATUS_ERROR = Integer.MIN_VALUE; 87 /** @hide */ 88 public static final int STATUS_PERMISSION_DENIED = -EPERM; 89 /** @hide */ 90 public static final int STATUS_NO_INIT = -ENODEV; 91 /** @hide */ 92 public static final int STATUS_BAD_VALUE = -EINVAL; 93 /** @hide */ 94 public static final int STATUS_DEAD_OBJECT = -EPIPE; 95 /** @hide */ 96 public static final int STATUS_INVALID_OPERATION = -ENOSYS; 97 /** @hide */ 98 public static final int STATUS_BUSY = -EBUSY; 99 100 /***************************************************************************** 101 * A ModuleProperties describes a given sound trigger hardware module 102 * managed by the native sound trigger service. Each module has a unique 103 * ID used to target any API call to this paricular module. Module 104 * properties are returned by listModules() method. 105 * 106 ****************************************************************************/ 107 public static final class ModuleProperties implements Parcelable { 108 109 /** 110 * Bit field values of AudioCapabilities supported by the implemented HAL 111 * driver. 112 * @hide 113 */ 114 @Retention(RetentionPolicy.SOURCE) 115 @IntDef(flag = true, prefix = { "AUDIO_CAPABILITY_" }, value = { 116 AUDIO_CAPABILITY_ECHO_CANCELLATION, 117 AUDIO_CAPABILITY_NOISE_SUPPRESSION 118 }) 119 public @interface AudioCapabilities {} 120 121 /** 122 * If set the underlying module supports AEC. 123 * Describes bit field {@link ModuleProperties#mAudioCapabilities} 124 */ 125 public static final int AUDIO_CAPABILITY_ECHO_CANCELLATION = 0x1; 126 /** 127 * If set, the underlying module supports noise suppression. 128 * Describes bit field {@link ModuleProperties#mAudioCapabilities} 129 */ 130 public static final int AUDIO_CAPABILITY_NOISE_SUPPRESSION = 0x2; 131 132 private final int mId; 133 @NonNull 134 private final String mImplementor; 135 @NonNull 136 private final String mDescription; 137 @NonNull 138 private final UUID mUuid; 139 private final int mVersion; 140 @NonNull 141 private final String mSupportedModelArch; 142 private final int mMaxSoundModels; 143 private final int mMaxKeyphrases; 144 private final int mMaxUsers; 145 @RecognitionModes 146 private final int mRecognitionModes; 147 private final boolean mSupportsCaptureTransition; 148 private final int mMaxBufferMillis; 149 private final boolean mSupportsConcurrentCapture; 150 private final int mPowerConsumptionMw; 151 private final boolean mReturnsTriggerInEvent; 152 @AudioCapabilities 153 private final int mAudioCapabilities; 154 155 /** @hide */ 156 @TestApi ModuleProperties(int id, @NonNull String implementor, @NonNull String description, @NonNull String uuid, int version, @NonNull String supportedModelArch, int maxSoundModels, int maxKeyphrases, int maxUsers, @RecognitionModes int recognitionModes, boolean supportsCaptureTransition, int maxBufferMs, boolean supportsConcurrentCapture, int powerConsumptionMw, boolean returnsTriggerInEvent, int audioCapabilities)157 public ModuleProperties(int id, @NonNull String implementor, @NonNull String description, 158 @NonNull String uuid, int version, @NonNull String supportedModelArch, 159 int maxSoundModels, int maxKeyphrases, int maxUsers, 160 @RecognitionModes int recognitionModes, boolean supportsCaptureTransition, 161 int maxBufferMs, boolean supportsConcurrentCapture, int powerConsumptionMw, 162 boolean returnsTriggerInEvent, int audioCapabilities) { 163 this.mId = id; 164 this.mImplementor = requireNonNull(implementor); 165 this.mDescription = requireNonNull(description); 166 this.mUuid = UUID.fromString(requireNonNull(uuid)); 167 this.mVersion = version; 168 this.mSupportedModelArch = requireNonNull(supportedModelArch); 169 this.mMaxSoundModels = maxSoundModels; 170 this.mMaxKeyphrases = maxKeyphrases; 171 this.mMaxUsers = maxUsers; 172 this.mRecognitionModes = recognitionModes; 173 this.mSupportsCaptureTransition = supportsCaptureTransition; 174 this.mMaxBufferMillis = maxBufferMs; 175 this.mSupportsConcurrentCapture = supportsConcurrentCapture; 176 this.mPowerConsumptionMw = powerConsumptionMw; 177 this.mReturnsTriggerInEvent = returnsTriggerInEvent; 178 this.mAudioCapabilities = audioCapabilities; 179 } 180 181 /** Unique module ID provided by the native service */ getId()182 public int getId() { 183 return mId; 184 } 185 186 /** human readable voice detection engine implementor */ 187 @NonNull getImplementor()188 public String getImplementor() { 189 return mImplementor; 190 } 191 192 /** human readable voice detection engine description */ 193 @NonNull getDescription()194 public String getDescription() { 195 return mDescription; 196 } 197 198 /** Unique voice engine Id (changes with each version) */ 199 @NonNull getUuid()200 public UUID getUuid() { 201 return mUuid; 202 } 203 204 /** Voice detection engine version */ getVersion()205 public int getVersion() { 206 return mVersion; 207 } 208 209 /** 210 * String naming the architecture used for running the supported models. 211 * (eg. a platform running models on a DSP could implement this string to convey the DSP 212 * architecture used) 213 */ 214 @NonNull getSupportedModelArch()215 public String getSupportedModelArch() { 216 return mSupportedModelArch; 217 } 218 219 /** Maximum number of active sound models */ getMaxSoundModels()220 public int getMaxSoundModels() { 221 return mMaxSoundModels; 222 } 223 224 /** Maximum number of key phrases */ getMaxKeyphrases()225 public int getMaxKeyphrases() { 226 return mMaxKeyphrases; 227 } 228 229 /** Maximum number of users per key phrase */ getMaxUsers()230 public int getMaxUsers() { 231 return mMaxUsers; 232 } 233 234 /** Supported recognition modes (bit field, RECOGNITION_MODE_VOICE_TRIGGER ...) */ 235 @RecognitionModes getRecognitionModes()236 public int getRecognitionModes() { 237 return mRecognitionModes; 238 } 239 240 /** Supports seamless transition to capture mode after recognition */ isCaptureTransitionSupported()241 public boolean isCaptureTransitionSupported() { 242 return mSupportsCaptureTransition; 243 } 244 245 /** Maximum buffering capacity in ms if supportsCaptureTransition() is true */ getMaxBufferMillis()246 public int getMaxBufferMillis() { 247 return mMaxBufferMillis; 248 } 249 250 /** Supports capture by other use cases while detection is active */ isConcurrentCaptureSupported()251 public boolean isConcurrentCaptureSupported() { 252 return mSupportsConcurrentCapture; 253 } 254 255 /** Rated power consumption when detection is active with TDB silence/sound/speech ratio */ getPowerConsumptionMw()256 public int getPowerConsumptionMw() { 257 return mPowerConsumptionMw; 258 } 259 260 /** Returns the trigger (key phrase) capture in the binary data of the 261 * recognition callback event */ isTriggerReturnedInEvent()262 public boolean isTriggerReturnedInEvent() { 263 return mReturnsTriggerInEvent; 264 } 265 266 /** 267 * Bit field encoding of the AudioCapabilities 268 * supported by the firmware. 269 */ 270 @AudioCapabilities getAudioCapabilities()271 public int getAudioCapabilities() { 272 return mAudioCapabilities; 273 } 274 275 public static final @android.annotation.NonNull Parcelable.Creator<ModuleProperties> CREATOR 276 = new Parcelable.Creator<ModuleProperties>() { 277 public ModuleProperties createFromParcel(Parcel in) { 278 return ModuleProperties.fromParcel(in); 279 } 280 281 public ModuleProperties[] newArray(int size) { 282 return new ModuleProperties[size]; 283 } 284 }; 285 fromParcel(Parcel in)286 private static ModuleProperties fromParcel(Parcel in) { 287 int id = in.readInt(); 288 String implementor = in.readString(); 289 String description = in.readString(); 290 String uuid = in.readString(); 291 int version = in.readInt(); 292 String supportedModelArch = in.readString(); 293 int maxSoundModels = in.readInt(); 294 int maxKeyphrases = in.readInt(); 295 int maxUsers = in.readInt(); 296 int recognitionModes = in.readInt(); 297 boolean supportsCaptureTransition = in.readByte() == 1; 298 int maxBufferMs = in.readInt(); 299 boolean supportsConcurrentCapture = in.readByte() == 1; 300 int powerConsumptionMw = in.readInt(); 301 boolean returnsTriggerInEvent = in.readByte() == 1; 302 int audioCapabilities = in.readInt(); 303 return new ModuleProperties(id, implementor, description, uuid, version, 304 supportedModelArch, maxSoundModels, maxKeyphrases, maxUsers, recognitionModes, 305 supportsCaptureTransition, maxBufferMs, supportsConcurrentCapture, 306 powerConsumptionMw, returnsTriggerInEvent, audioCapabilities); 307 } 308 309 @Override writeToParcel(@uppressLint"MissingNullability") Parcel dest, int flags)310 public void writeToParcel(@SuppressLint("MissingNullability") Parcel dest, int flags) { 311 dest.writeInt(getId()); 312 dest.writeString(getImplementor()); 313 dest.writeString(getDescription()); 314 dest.writeString(getUuid().toString()); 315 dest.writeInt(getVersion()); 316 dest.writeString(getSupportedModelArch()); 317 dest.writeInt(getMaxSoundModels()); 318 dest.writeInt(getMaxKeyphrases()); 319 dest.writeInt(getMaxUsers()); 320 dest.writeInt(getRecognitionModes()); 321 dest.writeByte((byte) (isCaptureTransitionSupported() ? 1 : 0)); 322 dest.writeInt(getMaxBufferMillis()); 323 dest.writeByte((byte) (isConcurrentCaptureSupported() ? 1 : 0)); 324 dest.writeInt(getPowerConsumptionMw()); 325 dest.writeByte((byte) (isTriggerReturnedInEvent() ? 1 : 0)); 326 dest.writeInt(getAudioCapabilities()); 327 } 328 329 @Override describeContents()330 public int describeContents() { 331 return 0; 332 } 333 334 @Override equals(@ullable Object obj)335 public boolean equals(@Nullable Object obj) { 336 if (this == obj) { 337 return true; 338 } 339 if (obj == null) { 340 return false; 341 } 342 if (!(obj instanceof ModuleProperties)) { 343 return false; 344 } 345 ModuleProperties other = (ModuleProperties) obj; 346 if (mId != other.mId) { 347 return false; 348 } 349 if (!mImplementor.equals(other.mImplementor)) { 350 return false; 351 } 352 if (!mDescription.equals(other.mDescription)) { 353 return false; 354 } 355 if (!mUuid.equals(other.mUuid)) { 356 return false; 357 } 358 if (mVersion != other.mVersion) { 359 return false; 360 } 361 if (!mSupportedModelArch.equals(other.mSupportedModelArch)) { 362 return false; 363 } 364 if (mMaxSoundModels != other.mMaxSoundModels) { 365 return false; 366 } 367 if (mMaxKeyphrases != other.mMaxKeyphrases) { 368 return false; 369 } 370 if (mMaxUsers != other.mMaxUsers) { 371 return false; 372 } 373 if (mRecognitionModes != other.mRecognitionModes) { 374 return false; 375 } 376 if (mSupportsCaptureTransition != other.mSupportsCaptureTransition) { 377 return false; 378 } 379 if (mMaxBufferMillis != other.mMaxBufferMillis) { 380 return false; 381 } 382 if (mSupportsConcurrentCapture != other.mSupportsConcurrentCapture) { 383 return false; 384 } 385 if (mPowerConsumptionMw != other.mPowerConsumptionMw) { 386 return false; 387 } 388 if (mReturnsTriggerInEvent != other.mReturnsTriggerInEvent) { 389 return false; 390 } 391 if (mAudioCapabilities != other.mAudioCapabilities) { 392 return false; 393 } 394 return true; 395 } 396 397 @Override hashCode()398 public int hashCode() { 399 final int prime = 31; 400 int result = 1; 401 result = prime * result + mId; 402 result = prime * result + mImplementor.hashCode(); 403 result = prime * result + mDescription.hashCode(); 404 result = prime * result + mUuid.hashCode(); 405 result = prime * result + mVersion; 406 result = prime * result + mSupportedModelArch.hashCode(); 407 result = prime * result + mMaxSoundModels; 408 result = prime * result + mMaxKeyphrases; 409 result = prime * result + mMaxUsers; 410 result = prime * result + mRecognitionModes; 411 result = prime * result + (mSupportsCaptureTransition ? 1 : 0); 412 result = prime * result + mMaxBufferMillis; 413 result = prime * result + (mSupportsConcurrentCapture ? 1 : 0); 414 result = prime * result + mPowerConsumptionMw; 415 result = prime * result + (mReturnsTriggerInEvent ? 1 : 0); 416 result = prime * result + mAudioCapabilities; 417 return result; 418 } 419 420 @Override toString()421 public String toString() { 422 return "ModuleProperties [id=" + getId() + ", implementor=" + getImplementor() 423 + ", description=" + getDescription() + ", uuid=" + getUuid() 424 + ", version=" + getVersion() + " , supportedModelArch=" 425 + getSupportedModelArch() + ", maxSoundModels=" + getMaxSoundModels() 426 + ", maxKeyphrases=" + getMaxKeyphrases() + ", maxUsers=" + getMaxUsers() 427 + ", recognitionModes=" + getRecognitionModes() + ", supportsCaptureTransition=" 428 + isCaptureTransitionSupported() + ", maxBufferMs=" + getMaxBufferMillis() 429 + ", supportsConcurrentCapture=" + isConcurrentCaptureSupported() 430 + ", powerConsumptionMw=" + getPowerConsumptionMw() 431 + ", returnsTriggerInEvent=" + isTriggerReturnedInEvent() 432 + ", audioCapabilities=" + getAudioCapabilities() + "]"; 433 } 434 } 435 436 /** 437 * A SoundModel describes the attributes and contains the binary data used by the hardware 438 * implementation to detect a particular sound pattern. 439 * A specialized version {@link KeyphraseSoundModel} is defined for key phrase 440 * sound models. 441 */ 442 public static class SoundModel { 443 444 /** @hide */ 445 @Retention(RetentionPolicy.SOURCE) 446 @IntDef({ 447 TYPE_GENERIC_SOUND, 448 TYPE_KEYPHRASE, 449 TYPE_UNKNOWN, 450 }) 451 public @interface SoundModelType {} 452 453 /** 454 * Undefined sound model type 455 * @hide 456 */ 457 public static final int TYPE_UNKNOWN = -1; 458 459 /** Keyphrase sound model */ 460 public static final int TYPE_KEYPHRASE = 0; 461 462 /** 463 * A generic sound model. Use this type only for non-keyphrase sound models such as 464 * ones that match a particular sound pattern. 465 */ 466 public static final int TYPE_GENERIC_SOUND = 1; 467 468 @NonNull 469 private final UUID mUuid; 470 @SoundModelType 471 private final int mType; 472 @NonNull 473 private final UUID mVendorUuid; 474 private final int mVersion; 475 @NonNull 476 private final byte[] mData; 477 478 /** @hide */ SoundModel(@onNull UUID uuid, @Nullable UUID vendorUuid, @SoundModelType int type, @Nullable byte[] data, int version)479 public SoundModel(@NonNull UUID uuid, @Nullable UUID vendorUuid, @SoundModelType int type, 480 @Nullable byte[] data, int version) { 481 this.mUuid = requireNonNull(uuid); 482 this.mVendorUuid = vendorUuid != null ? vendorUuid : new UUID(0, 0); 483 this.mType = type; 484 this.mVersion = version; 485 this.mData = data != null ? data : new byte[0]; 486 } 487 488 /** Unique sound model identifier */ 489 @NonNull getUuid()490 public UUID getUuid() { 491 return mUuid; 492 } 493 494 /** Sound model type (e.g. TYPE_KEYPHRASE); */ 495 @SoundModelType getType()496 public int getType() { 497 return mType; 498 } 499 500 /** Unique sound model vendor identifier */ 501 @NonNull getVendorUuid()502 public UUID getVendorUuid() { 503 return mVendorUuid; 504 } 505 506 /** vendor specific version number of the model */ getVersion()507 public int getVersion() { 508 return mVersion; 509 } 510 511 /** Opaque data. For use by vendor implementation and enrollment application */ 512 @NonNull getData()513 public byte[] getData() { 514 return mData; 515 } 516 517 @Override hashCode()518 public int hashCode() { 519 final int prime = 31; 520 int result = 1; 521 result = prime * result + getVersion(); 522 result = prime * result + Arrays.hashCode(getData()); 523 result = prime * result + getType(); 524 result = prime * result + ((getUuid() == null) ? 0 : getUuid().hashCode()); 525 result = prime * result + ((getVendorUuid() == null) ? 0 : getVendorUuid().hashCode()); 526 return result; 527 } 528 529 @Override equals(@ullable Object obj)530 public boolean equals(@Nullable Object obj) { 531 if (this == obj) { 532 return true; 533 } 534 if (obj == null) { 535 return false; 536 } 537 if (!(obj instanceof SoundModel)) { 538 return false; 539 } 540 SoundModel other = (SoundModel) obj; 541 if (getType() != other.getType()) { 542 return false; 543 } 544 if (getUuid() == null) { 545 if (other.getUuid() != null) { 546 return false; 547 } 548 } else if (!getUuid().equals(other.getUuid())) { 549 return false; 550 } 551 if (getVendorUuid() == null) { 552 if (other.getVendorUuid() != null) { 553 return false; 554 } 555 } else if (!getVendorUuid().equals(other.getVendorUuid())) { 556 return false; 557 } 558 if (!Arrays.equals(getData(), other.getData())) { 559 return false; 560 } 561 if (getVersion() != other.getVersion()) { 562 return false; 563 } 564 return true; 565 } 566 } 567 568 /** 569 * A Keyphrase describes a key phrase that can be detected by a 570 * {@link KeyphraseSoundModel} 571 */ 572 public static final class Keyphrase implements Parcelable { 573 574 private final int mId; 575 @RecognitionModes 576 private final int mRecognitionModes; 577 @NonNull 578 private final Locale mLocale; 579 @NonNull 580 private final String mText; 581 @NonNull 582 private final int[] mUsers; 583 584 /** 585 * Constructor for Keyphrase describes a key phrase that can be detected by a 586 * {@link KeyphraseSoundModel} 587 * 588 * @param id Unique keyphrase identifier for this keyphrase 589 * @param recognitionModes Bit field representation of recognition modes this keyphrase 590 * supports 591 * @param locale Locale of the keyphrase 592 * @param text Key phrase text 593 * @param users Users this key phrase has been trained for. 594 */ Keyphrase(int id, @RecognitionModes int recognitionModes, @NonNull Locale locale, @NonNull String text, @Nullable int[] users)595 public Keyphrase(int id, @RecognitionModes int recognitionModes, @NonNull Locale locale, 596 @NonNull String text, @Nullable int[] users) { 597 this.mId = id; 598 this.mRecognitionModes = recognitionModes; 599 this.mLocale = requireNonNull(locale); 600 this.mText = requireNonNull(text); 601 this.mUsers = users != null ? users : new int[0]; 602 } 603 604 /** Unique identifier for this keyphrase */ getId()605 public int getId() { 606 return mId; 607 } 608 609 /** 610 * Recognition modes supported for this key phrase in the model 611 * 612 * @see #RECOGNITION_MODE_VOICE_TRIGGER 613 * @see #RECOGNITION_MODE_USER_IDENTIFICATION 614 * @see #RECOGNITION_MODE_USER_AUTHENTICATION 615 * @see #RECOGNITION_MODE_GENERIC 616 */ 617 @RecognitionModes getRecognitionModes()618 public int getRecognitionModes() { 619 return mRecognitionModes; 620 } 621 622 /** Locale of the keyphrase. */ 623 @NonNull getLocale()624 public Locale getLocale() { 625 return mLocale; 626 } 627 628 /** Key phrase text */ 629 @NonNull getText()630 public String getText() { 631 return mText; 632 } 633 634 /** 635 * Users this key phrase has been trained for. countains sound trigger specific user IDs 636 * derived from system user IDs {@link android.os.UserHandle#getIdentifier()}. 637 */ 638 @NonNull getUsers()639 public int[] getUsers() { 640 return mUsers; 641 } 642 643 public static final @NonNull Parcelable.Creator<Keyphrase> CREATOR = 644 new Parcelable.Creator<Keyphrase>() { 645 @NonNull 646 public Keyphrase createFromParcel(@NonNull Parcel in) { 647 return Keyphrase.readFromParcel(in); 648 } 649 650 @NonNull 651 public Keyphrase[] newArray(int size) { 652 return new Keyphrase[size]; 653 } 654 }; 655 656 /** 657 * Read from Parcel to generate keyphrase 658 */ 659 @NonNull readFromParcel(@onNull Parcel in)660 public static Keyphrase readFromParcel(@NonNull Parcel in) { 661 int id = in.readInt(); 662 int recognitionModes = in.readInt(); 663 Locale locale = Locale.forLanguageTag(in.readString()); 664 String text = in.readString(); 665 int[] users = null; 666 int numUsers = in.readInt(); 667 if (numUsers >= 0) { 668 users = new int[numUsers]; 669 in.readIntArray(users); 670 } 671 return new Keyphrase(id, recognitionModes, locale, text, users); 672 } 673 674 @Override writeToParcel(@onNull Parcel dest, int flags)675 public void writeToParcel(@NonNull Parcel dest, int flags) { 676 dest.writeInt(getId()); 677 dest.writeInt(getRecognitionModes()); 678 dest.writeString(getLocale().toLanguageTag()); 679 dest.writeString(getText()); 680 if (getUsers() != null) { 681 dest.writeInt(getUsers().length); 682 dest.writeIntArray(getUsers()); 683 } else { 684 dest.writeInt(-1); 685 } 686 } 687 688 /** @hide */ 689 @Override describeContents()690 public int describeContents() { 691 return 0; 692 } 693 694 @Override hashCode()695 public int hashCode() { 696 final int prime = 31; 697 int result = 1; 698 result = prime * result + ((getText() == null) ? 0 : getText().hashCode()); 699 result = prime * result + getId(); 700 result = prime * result + ((getLocale() == null) ? 0 : getLocale().hashCode()); 701 result = prime * result + getRecognitionModes(); 702 result = prime * result + Arrays.hashCode(getUsers()); 703 return result; 704 } 705 706 @Override equals(@ullable Object obj)707 public boolean equals(@Nullable Object obj) { 708 if (this == obj) { 709 return true; 710 } 711 if (obj == null) { 712 return false; 713 } 714 if (getClass() != obj.getClass()) { 715 return false; 716 } 717 Keyphrase other = (Keyphrase) obj; 718 if (getText() == null) { 719 if (other.getText() != null) { 720 return false; 721 } 722 } else if (!getText().equals(other.getText())) { 723 return false; 724 } 725 if (getId() != other.getId()) { 726 return false; 727 } 728 if (getLocale() == null) { 729 if (other.getLocale() != null) { 730 return false; 731 } 732 } else if (!getLocale().equals(other.getLocale())) { 733 return false; 734 } 735 if (getRecognitionModes() != other.getRecognitionModes()) { 736 return false; 737 } 738 if (!Arrays.equals(getUsers(), other.getUsers())) { 739 return false; 740 } 741 return true; 742 } 743 744 @Override toString()745 public String toString() { 746 return "Keyphrase [id=" + getId() + ", recognitionModes=" + getRecognitionModes() 747 + ", locale=" + getLocale().toLanguageTag() + ", text=" + getText() 748 + ", users=" + Arrays.toString(getUsers()) + "]"; 749 } 750 } 751 752 /** 753 * A KeyphraseSoundModel is a specialized {@link SoundModel} for key phrases. 754 * It contains data needed by the hardware to detect a certain number of key phrases 755 * and the list of corresponding {@link Keyphrase} descriptors. 756 */ 757 public static final class KeyphraseSoundModel extends SoundModel implements Parcelable { 758 759 @NonNull 760 private final Keyphrase[] mKeyphrases; 761 KeyphraseSoundModel( @onNull UUID uuid, @NonNull UUID vendorUuid, @Nullable byte[] data, @Nullable Keyphrase[] keyphrases, int version)762 public KeyphraseSoundModel( 763 @NonNull UUID uuid, @NonNull UUID vendorUuid, @Nullable byte[] data, 764 @Nullable Keyphrase[] keyphrases, int version) { 765 super(uuid, vendorUuid, TYPE_KEYPHRASE, data, version); 766 this.mKeyphrases = keyphrases != null ? keyphrases : new Keyphrase[0]; 767 } 768 KeyphraseSoundModel(@onNull UUID uuid, @NonNull UUID vendorUuid, @Nullable byte[] data, @Nullable Keyphrase[] keyphrases)769 public KeyphraseSoundModel(@NonNull UUID uuid, @NonNull UUID vendorUuid, 770 @Nullable byte[] data, @Nullable Keyphrase[] keyphrases) { 771 this(uuid, vendorUuid, data, keyphrases, -1); 772 } 773 774 /** Key phrases in this sound model */ 775 @NonNull getKeyphrases()776 public Keyphrase[] getKeyphrases() { 777 return mKeyphrases; 778 } 779 780 public static final @NonNull Parcelable.Creator<KeyphraseSoundModel> CREATOR = 781 new Parcelable.Creator<KeyphraseSoundModel>() { 782 @NonNull 783 public KeyphraseSoundModel createFromParcel(@NonNull Parcel in) { 784 return KeyphraseSoundModel.readFromParcel(in); 785 } 786 787 @NonNull 788 public KeyphraseSoundModel[] newArray(int size) { 789 return new KeyphraseSoundModel[size]; 790 } 791 }; 792 793 /** 794 * Read from Parcel to generate KeyphraseSoundModel 795 */ 796 @NonNull readFromParcel(@onNull Parcel in)797 public static KeyphraseSoundModel readFromParcel(@NonNull Parcel in) { 798 UUID uuid = UUID.fromString(in.readString()); 799 UUID vendorUuid = null; 800 int length = in.readInt(); 801 if (length >= 0) { 802 vendorUuid = UUID.fromString(in.readString()); 803 } 804 int version = in.readInt(); 805 byte[] data = in.readBlob(); 806 Keyphrase[] keyphrases = in.createTypedArray(Keyphrase.CREATOR); 807 return new KeyphraseSoundModel(uuid, vendorUuid, data, keyphrases, version); 808 } 809 810 /** @hide */ 811 @Override describeContents()812 public int describeContents() { 813 return 0; 814 } 815 816 @Override writeToParcel(@onNull Parcel dest, int flags)817 public void writeToParcel(@NonNull Parcel dest, int flags) { 818 dest.writeString(getUuid().toString()); 819 if (getVendorUuid() == null) { 820 dest.writeInt(-1); 821 } else { 822 dest.writeInt(getVendorUuid().toString().length()); 823 dest.writeString(getVendorUuid().toString()); 824 } 825 dest.writeInt(getVersion()); 826 dest.writeBlob(getData()); 827 dest.writeTypedArray(getKeyphrases(), flags); 828 } 829 830 @Override toString()831 public String toString() { 832 return "KeyphraseSoundModel [keyphrases=" + Arrays.toString(getKeyphrases()) 833 + ", uuid=" + getUuid() + ", vendorUuid=" + getVendorUuid() 834 + ", type=" + getType() 835 + ", data=" + (getData() == null ? 0 : getData().length) 836 + ", version=" + getVersion() + "]"; 837 } 838 839 @Override hashCode()840 public int hashCode() { 841 final int prime = 31; 842 int result = super.hashCode(); 843 result = prime * result + Arrays.hashCode(getKeyphrases()); 844 return result; 845 } 846 847 @Override equals(@ullable Object obj)848 public boolean equals(@Nullable Object obj) { 849 if (this == obj) { 850 return true; 851 } 852 if (!super.equals(obj)) { 853 return false; 854 } 855 if (!(obj instanceof KeyphraseSoundModel)) { 856 return false; 857 } 858 KeyphraseSoundModel other = (KeyphraseSoundModel) obj; 859 if (!Arrays.equals(getKeyphrases(), other.getKeyphrases())) { 860 return false; 861 } 862 return true; 863 } 864 } 865 866 867 /***************************************************************************** 868 * A GenericSoundModel is a specialized {@link SoundModel} for non-voice sound 869 * patterns. 870 * 871 * @hide 872 ****************************************************************************/ 873 public static class GenericSoundModel extends SoundModel implements Parcelable { 874 875 public static final @android.annotation.NonNull Parcelable.Creator<GenericSoundModel> CREATOR 876 = new Parcelable.Creator<GenericSoundModel>() { 877 public GenericSoundModel createFromParcel(Parcel in) { 878 return GenericSoundModel.fromParcel(in); 879 } 880 881 public GenericSoundModel[] newArray(int size) { 882 return new GenericSoundModel[size]; 883 } 884 }; 885 GenericSoundModel(@onNull UUID uuid, @NonNull UUID vendorUuid, @Nullable byte[] data, int version)886 public GenericSoundModel(@NonNull UUID uuid, @NonNull UUID vendorUuid, 887 @Nullable byte[] data, int version) { 888 super(uuid, vendorUuid, TYPE_GENERIC_SOUND, data, version); 889 } 890 891 @UnsupportedAppUsage GenericSoundModel(@onNull UUID uuid, @NonNull UUID vendorUuid, @Nullable byte[] data)892 public GenericSoundModel(@NonNull UUID uuid, @NonNull UUID vendorUuid, 893 @Nullable byte[] data) { 894 this(uuid, vendorUuid, data, -1); 895 } 896 897 @Override describeContents()898 public int describeContents() { 899 return 0; 900 } 901 fromParcel(Parcel in)902 private static GenericSoundModel fromParcel(Parcel in) { 903 UUID uuid = UUID.fromString(in.readString()); 904 UUID vendorUuid = null; 905 int length = in.readInt(); 906 if (length >= 0) { 907 vendorUuid = UUID.fromString(in.readString()); 908 } 909 byte[] data = in.readBlob(); 910 int version = in.readInt(); 911 return new GenericSoundModel(uuid, vendorUuid, data, version); 912 } 913 914 @Override writeToParcel(Parcel dest, int flags)915 public void writeToParcel(Parcel dest, int flags) { 916 dest.writeString(getUuid().toString()); 917 if (getVendorUuid() == null) { 918 dest.writeInt(-1); 919 } else { 920 dest.writeInt(getVendorUuid().toString().length()); 921 dest.writeString(getVendorUuid().toString()); 922 } 923 dest.writeBlob(getData()); 924 dest.writeInt(getVersion()); 925 } 926 927 @Override toString()928 public String toString() { 929 return "GenericSoundModel [uuid=" + getUuid() + ", vendorUuid=" + getVendorUuid() 930 + ", type=" + getType() 931 + ", data=" + (getData() == null ? 0 : getData().length) 932 + ", version=" + getVersion() + "]"; 933 } 934 } 935 936 /** 937 * A ModelParamRange is a representation of supported parameter range for a 938 * given loaded model. 939 */ 940 public static final class ModelParamRange implements Parcelable { 941 942 /** 943 * The inclusive start of supported range. 944 */ 945 private final int mStart; 946 947 /** 948 * The inclusive end of supported range. 949 */ 950 private final int mEnd; 951 952 /** @hide */ 953 @TestApi ModelParamRange(int start, int end)954 public ModelParamRange(int start, int end) { 955 this.mStart = start; 956 this.mEnd = end; 957 } 958 959 /** @hide */ ModelParamRange(@onNull Parcel in)960 private ModelParamRange(@NonNull Parcel in) { 961 this.mStart = in.readInt(); 962 this.mEnd = in.readInt(); 963 } 964 965 /** 966 * Get the beginning of the param range 967 * 968 * @return The inclusive start of the supported range. 969 */ getStart()970 public int getStart() { 971 return mStart; 972 } 973 974 /** 975 * Get the end of the param range 976 * 977 * @return The inclusive end of the supported range. 978 */ getEnd()979 public int getEnd() { 980 return mEnd; 981 } 982 983 @NonNull 984 public static final Creator<ModelParamRange> CREATOR = 985 new Creator<ModelParamRange>() { 986 @Override 987 @NonNull 988 public ModelParamRange createFromParcel(@NonNull Parcel in) { 989 return new ModelParamRange(in); 990 } 991 992 @Override 993 @NonNull 994 public ModelParamRange[] newArray(int size) { 995 return new ModelParamRange[size]; 996 } 997 }; 998 999 /** @hide */ 1000 @Override describeContents()1001 public int describeContents() { 1002 return 0; 1003 } 1004 1005 /** @hide */ 1006 @Override hashCode()1007 public int hashCode() { 1008 final int prime = 31; 1009 int result = 1; 1010 result = prime * result + (mStart); 1011 result = prime * result + (mEnd); 1012 return result; 1013 } 1014 1015 @Override equals(@ullable Object obj)1016 public boolean equals(@Nullable Object obj) { 1017 if (this == obj) { 1018 return true; 1019 } 1020 if (obj == null) { 1021 return false; 1022 } 1023 if (getClass() != obj.getClass()) { 1024 return false; 1025 } 1026 ModelParamRange other = (ModelParamRange) obj; 1027 if (mStart != other.mStart) { 1028 return false; 1029 } 1030 if (mEnd != other.mEnd) { 1031 return false; 1032 } 1033 return true; 1034 } 1035 1036 @Override writeToParcel(@onNull Parcel dest, int flags)1037 public void writeToParcel(@NonNull Parcel dest, int flags) { 1038 dest.writeInt(mStart); 1039 dest.writeInt(mEnd); 1040 } 1041 1042 @Override 1043 @NonNull toString()1044 public String toString() { 1045 return "ModelParamRange [start=" + mStart + ", end=" + mEnd + "]"; 1046 } 1047 } 1048 1049 /** 1050 * Modes for key phrase recognition 1051 * @hide 1052 */ 1053 @Retention(RetentionPolicy.SOURCE) 1054 @IntDef(flag = true, prefix = { "RECOGNITION_MODE_" }, value = { 1055 RECOGNITION_MODE_VOICE_TRIGGER, 1056 RECOGNITION_MODE_USER_IDENTIFICATION, 1057 RECOGNITION_MODE_USER_AUTHENTICATION, 1058 RECOGNITION_MODE_GENERIC 1059 }) 1060 public @interface RecognitionModes {} 1061 1062 /** 1063 * Trigger on recognition of a key phrase 1064 */ 1065 public static final int RECOGNITION_MODE_VOICE_TRIGGER = 0x1; 1066 /** 1067 * Trigger only if one user is identified 1068 */ 1069 public static final int RECOGNITION_MODE_USER_IDENTIFICATION = 0x2; 1070 /** 1071 * Trigger only if one user is authenticated 1072 */ 1073 public static final int RECOGNITION_MODE_USER_AUTHENTICATION = 0x4; 1074 /** 1075 * Generic (non-speech) recognition. 1076 */ 1077 public static final int RECOGNITION_MODE_GENERIC = 0x8; 1078 1079 /** 1080 * Status codes for {@link RecognitionEvent} 1081 */ 1082 /** 1083 * Recognition success 1084 * 1085 * @hide 1086 */ 1087 public static final int RECOGNITION_STATUS_SUCCESS = 0; 1088 /** 1089 * Recognition aborted (e.g. capture preempted by anotehr use case 1090 * 1091 * @hide 1092 */ 1093 public static final int RECOGNITION_STATUS_ABORT = 1; 1094 /** 1095 * Recognition failure 1096 * 1097 * @hide 1098 */ 1099 public static final int RECOGNITION_STATUS_FAILURE = 2; 1100 /** 1101 * Recognition event was triggered by a getModelState request, not by the 1102 * DSP. 1103 * 1104 * @hide 1105 */ 1106 public static final int RECOGNITION_STATUS_GET_STATE_RESPONSE = 3; 1107 1108 /** 1109 * A RecognitionEvent is provided by the 1110 * {@code StatusListener#onRecognition(RecognitionEvent)} 1111 * callback upon recognition success or failure. 1112 */ 1113 public static class RecognitionEvent { 1114 /** 1115 * Recognition status e.g RECOGNITION_STATUS_SUCCESS 1116 * 1117 * @hide 1118 */ 1119 @UnsupportedAppUsage 1120 public final int status; 1121 /** 1122 * 1123 * Sound Model corresponding to this event callback 1124 * 1125 * @hide 1126 */ 1127 @UnsupportedAppUsage 1128 public final int soundModelHandle; 1129 /** 1130 * True if it is possible to capture audio from this utterance buffered by the hardware 1131 * 1132 * @hide 1133 */ 1134 @UnsupportedAppUsage 1135 public final boolean captureAvailable; 1136 /** 1137 * Audio session ID to be used when capturing the utterance with an AudioRecord 1138 * if captureAvailable() is true. 1139 * 1140 * @hide 1141 */ 1142 @UnsupportedAppUsage 1143 public final int captureSession; 1144 /** 1145 * Delay in ms between end of model detection and start of audio available for capture. 1146 * A negative value is possible (e.g. if keyphrase is also available for capture) 1147 * 1148 * @hide 1149 */ 1150 public final int captureDelayMs; 1151 /** 1152 * Duration in ms of audio captured before the start of the trigger. 0 if none. 1153 * 1154 * @hide 1155 */ 1156 public final int capturePreambleMs; 1157 /** 1158 * True if the trigger (key phrase capture is present in binary data 1159 * 1160 * @hide 1161 */ 1162 public final boolean triggerInData; 1163 /** 1164 * Audio format of either the trigger in event data or to use for capture of the 1165 * rest of the utterance 1166 * 1167 * @hide 1168 */ 1169 @NonNull 1170 public final AudioFormat captureFormat; 1171 /** 1172 * Opaque data for use by system applications who know about voice engine internals, 1173 * typically during enrollment. 1174 * 1175 * @hide 1176 */ 1177 @UnsupportedAppUsage 1178 @NonNull 1179 public final byte[] data; 1180 /** 1181 * Is recognition still active after this event. 1182 * @hide 1183 */ 1184 public final boolean recognitionStillActive; 1185 1186 /** @hide */ 1187 @TestApi 1188 @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553) RecognitionEvent(int status, int soundModelHandle, boolean captureAvailable, int captureSession, int captureDelayMs, int capturePreambleMs, boolean triggerInData, @NonNull AudioFormat captureFormat, @Nullable byte[] data)1189 public RecognitionEvent(int status, int soundModelHandle, boolean captureAvailable, 1190 int captureSession, int captureDelayMs, int capturePreambleMs, 1191 boolean triggerInData, @NonNull AudioFormat captureFormat, @Nullable byte[] data) { 1192 this(status, soundModelHandle, captureAvailable, captureSession, captureDelayMs, 1193 capturePreambleMs, triggerInData, captureFormat, data, 1194 status == RECOGNITION_STATUS_GET_STATE_RESPONSE); 1195 } 1196 1197 /** @hide */ RecognitionEvent(int status, int soundModelHandle, boolean captureAvailable, int captureSession, int captureDelayMs, int capturePreambleMs, boolean triggerInData, @NonNull AudioFormat captureFormat, @Nullable byte[] data, boolean recognitionStillActive)1198 public RecognitionEvent(int status, int soundModelHandle, boolean captureAvailable, 1199 int captureSession, int captureDelayMs, int capturePreambleMs, 1200 boolean triggerInData, @NonNull AudioFormat captureFormat, @Nullable byte[] data, 1201 boolean recognitionStillActive) { 1202 this.status = status; 1203 this.soundModelHandle = soundModelHandle; 1204 this.captureAvailable = captureAvailable; 1205 this.captureSession = captureSession; 1206 this.captureDelayMs = captureDelayMs; 1207 this.capturePreambleMs = capturePreambleMs; 1208 this.triggerInData = triggerInData; 1209 this.captureFormat = requireNonNull(captureFormat); 1210 this.data = data != null ? data : new byte[0]; 1211 this.recognitionStillActive = recognitionStillActive; 1212 } 1213 1214 /** 1215 * Check if is possible to capture audio from this utterance buffered by the hardware. 1216 * 1217 * @return {@code true} iff a capturing is possible 1218 */ isCaptureAvailable()1219 public boolean isCaptureAvailable() { 1220 return captureAvailable; 1221 } 1222 1223 /** 1224 * Get the audio format of either the trigger in event data or to use for capture of the 1225 * rest of the utterance 1226 * 1227 * @return the audio format 1228 */ getCaptureFormat()1229 @Nullable public AudioFormat getCaptureFormat() { 1230 return captureFormat; 1231 } 1232 1233 /** 1234 * Get Audio session ID to be used when capturing the utterance with an {@link AudioRecord} 1235 * if {@link #isCaptureAvailable()} is true. 1236 * 1237 * @return The id of the capture session 1238 */ getCaptureSession()1239 public int getCaptureSession() { 1240 return captureSession; 1241 } 1242 1243 /** 1244 * Get the opaque data for use by system applications who know about voice engine 1245 * internals, typically during enrollment. 1246 * 1247 * @return The data of the event 1248 */ 1249 @SuppressLint("MissingNullability") getData()1250 public byte[] getData() { 1251 return data; 1252 } 1253 1254 /** @hide */ 1255 public static final @android.annotation.NonNull Parcelable.Creator<RecognitionEvent> CREATOR 1256 = new Parcelable.Creator<RecognitionEvent>() { 1257 public RecognitionEvent createFromParcel(Parcel in) { 1258 return RecognitionEvent.fromParcel(in); 1259 } 1260 1261 public RecognitionEvent[] newArray(int size) { 1262 return new RecognitionEvent[size]; 1263 } 1264 }; 1265 1266 /** @hide */ fromParcel(Parcel in)1267 protected static RecognitionEvent fromParcel(Parcel in) { 1268 int status = in.readInt(); 1269 int soundModelHandle = in.readInt(); 1270 boolean captureAvailable = in.readByte() == 1; 1271 int captureSession = in.readInt(); 1272 int captureDelayMs = in.readInt(); 1273 int capturePreambleMs = in.readInt(); 1274 boolean triggerInData = in.readByte() == 1; 1275 AudioFormat captureFormat = null; 1276 if (in.readByte() == 1) { 1277 int sampleRate = in.readInt(); 1278 int encoding = in.readInt(); 1279 int channelMask = in.readInt(); 1280 captureFormat = (new AudioFormat.Builder()) 1281 .setChannelMask(channelMask) 1282 .setEncoding(encoding) 1283 .setSampleRate(sampleRate) 1284 .build(); 1285 } 1286 byte[] data = in.readBlob(); 1287 boolean recognitionStillActive = in.readBoolean(); 1288 return new RecognitionEvent(status, soundModelHandle, captureAvailable, captureSession, 1289 captureDelayMs, capturePreambleMs, triggerInData, captureFormat, data, 1290 recognitionStillActive); 1291 } 1292 1293 /** @hide */ describeContents()1294 public int describeContents() { 1295 return 0; 1296 } 1297 1298 /** @hide */ writeToParcel(Parcel dest, int flags)1299 public void writeToParcel(Parcel dest, int flags) { 1300 dest.writeInt(status); 1301 dest.writeInt(soundModelHandle); 1302 dest.writeByte((byte) (captureAvailable ? 1 : 0)); 1303 dest.writeInt(captureSession); 1304 dest.writeInt(captureDelayMs); 1305 dest.writeInt(capturePreambleMs); 1306 dest.writeByte((byte) (triggerInData ? 1 : 0)); 1307 if (captureFormat != null) { 1308 dest.writeByte((byte)1); 1309 dest.writeInt(captureFormat.getSampleRate()); 1310 dest.writeInt(captureFormat.getEncoding()); 1311 dest.writeInt(captureFormat.getChannelMask()); 1312 } else { 1313 dest.writeByte((byte)0); 1314 } 1315 dest.writeBlob(data); 1316 dest.writeBoolean(recognitionStillActive); 1317 } 1318 @Override hashCode()1319 public int hashCode() { 1320 final int prime = 31; 1321 int result = 1; 1322 result = prime * result + (captureAvailable ? 1231 : 1237); 1323 result = prime * result + captureDelayMs; 1324 result = prime * result + capturePreambleMs; 1325 result = prime * result + captureSession; 1326 result = prime * result + (triggerInData ? 1231 : 1237); 1327 if (captureFormat != null) { 1328 result = prime * result + captureFormat.getSampleRate(); 1329 result = prime * result + captureFormat.getEncoding(); 1330 result = prime * result + captureFormat.getChannelMask(); 1331 } 1332 result = prime * result + Arrays.hashCode(data); 1333 result = prime * result + soundModelHandle; 1334 result = prime * result + status; 1335 result = result + (recognitionStillActive ? 1289 : 1291); 1336 return result; 1337 } 1338 1339 @Override equals(@ullable Object obj)1340 public boolean equals(@Nullable Object obj) { 1341 if (this == obj) 1342 return true; 1343 if (obj == null) 1344 return false; 1345 if (getClass() != obj.getClass()) 1346 return false; 1347 RecognitionEvent other = (RecognitionEvent) obj; 1348 if (captureAvailable != other.captureAvailable) 1349 return false; 1350 if (captureDelayMs != other.captureDelayMs) 1351 return false; 1352 if (capturePreambleMs != other.capturePreambleMs) 1353 return false; 1354 if (captureSession != other.captureSession) 1355 return false; 1356 if (!Arrays.equals(data, other.data)) 1357 return false; 1358 if (recognitionStillActive != other.recognitionStillActive) 1359 return false; 1360 if (soundModelHandle != other.soundModelHandle) 1361 return false; 1362 if (status != other.status) 1363 return false; 1364 if (triggerInData != other.triggerInData) 1365 return false; 1366 if (captureFormat == null) { 1367 if (other.captureFormat != null) 1368 return false; 1369 } else { 1370 if (other.captureFormat == null) 1371 return false; 1372 if (captureFormat.getSampleRate() != other.captureFormat.getSampleRate()) 1373 return false; 1374 if (captureFormat.getEncoding() != other.captureFormat.getEncoding()) 1375 return false; 1376 if (captureFormat.getChannelMask() != other.captureFormat.getChannelMask()) 1377 return false; 1378 } 1379 return true; 1380 } 1381 1382 @NonNull 1383 @Override toString()1384 public String toString() { 1385 return "RecognitionEvent [status=" + status + ", soundModelHandle=" + soundModelHandle 1386 + ", captureAvailable=" + captureAvailable + ", captureSession=" 1387 + captureSession + ", captureDelayMs=" + captureDelayMs 1388 + ", capturePreambleMs=" + capturePreambleMs 1389 + ", triggerInData=" + triggerInData 1390 + ((captureFormat == null) ? "" : 1391 (", sampleRate=" + captureFormat.getSampleRate())) 1392 + ((captureFormat == null) ? "" : 1393 (", encoding=" + captureFormat.getEncoding())) 1394 + ((captureFormat == null) ? "" : 1395 (", channelMask=" + captureFormat.getChannelMask())) 1396 + ", data=" + (data == null ? 0 : data.length) 1397 + ", recognitionStillActive=" + recognitionStillActive 1398 + "]"; 1399 } 1400 } 1401 1402 /** 1403 * A RecognitionConfig is provided to 1404 * {@link SoundTriggerModule#startRecognition(int, RecognitionConfig)} to configure the 1405 * recognition request. 1406 * 1407 * @hide 1408 */ 1409 public static class RecognitionConfig implements Parcelable { 1410 /** True if the DSP should capture the trigger sound and make it available for further 1411 * capture. */ 1412 @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553) 1413 public final boolean captureRequested; 1414 /** 1415 * True if the service should restart listening after the DSP triggers. 1416 * Note: This config flag is currently used at the service layer rather than by the DSP. 1417 */ 1418 public final boolean allowMultipleTriggers; 1419 /** List of all keyphrases in the sound model for which recognition should be performed with 1420 * options for each keyphrase. */ 1421 @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553) 1422 @NonNull 1423 public final KeyphraseRecognitionExtra keyphrases[]; 1424 /** Opaque data for use by system applications who know about voice engine internals, 1425 * typically during enrollment. */ 1426 @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553) 1427 @NonNull 1428 public final byte[] data; 1429 1430 /** 1431 * Bit field encoding of the AudioCapabilities 1432 * supported by the firmware. 1433 */ 1434 @ModuleProperties.AudioCapabilities 1435 public final int audioCapabilities; 1436 RecognitionConfig(boolean captureRequested, boolean allowMultipleTriggers, @Nullable KeyphraseRecognitionExtra[] keyphrases, @Nullable byte[] data, int audioCapabilities)1437 public RecognitionConfig(boolean captureRequested, boolean allowMultipleTriggers, 1438 @Nullable KeyphraseRecognitionExtra[] keyphrases, @Nullable byte[] data, 1439 int audioCapabilities) { 1440 this.captureRequested = captureRequested; 1441 this.allowMultipleTriggers = allowMultipleTriggers; 1442 this.keyphrases = keyphrases != null ? keyphrases : new KeyphraseRecognitionExtra[0]; 1443 this.data = data != null ? data : new byte[0]; 1444 this.audioCapabilities = audioCapabilities; 1445 } 1446 1447 @UnsupportedAppUsage RecognitionConfig(boolean captureRequested, boolean allowMultipleTriggers, @Nullable KeyphraseRecognitionExtra[] keyphrases, @Nullable byte[] data)1448 public RecognitionConfig(boolean captureRequested, boolean allowMultipleTriggers, 1449 @Nullable KeyphraseRecognitionExtra[] keyphrases, @Nullable byte[] data) { 1450 this(captureRequested, allowMultipleTriggers, keyphrases, data, 0); 1451 } 1452 1453 public static final @android.annotation.NonNull Parcelable.Creator<RecognitionConfig> CREATOR 1454 = new Parcelable.Creator<RecognitionConfig>() { 1455 public RecognitionConfig createFromParcel(Parcel in) { 1456 return RecognitionConfig.fromParcel(in); 1457 } 1458 1459 public RecognitionConfig[] newArray(int size) { 1460 return new RecognitionConfig[size]; 1461 } 1462 }; 1463 fromParcel(Parcel in)1464 private static RecognitionConfig fromParcel(Parcel in) { 1465 boolean captureRequested = in.readByte() == 1; 1466 boolean allowMultipleTriggers = in.readByte() == 1; 1467 KeyphraseRecognitionExtra[] keyphrases = 1468 in.createTypedArray(KeyphraseRecognitionExtra.CREATOR); 1469 byte[] data = in.readBlob(); 1470 int audioCapabilities = in.readInt(); 1471 return new RecognitionConfig(captureRequested, allowMultipleTriggers, keyphrases, data, 1472 audioCapabilities); 1473 } 1474 1475 @Override writeToParcel(Parcel dest, int flags)1476 public void writeToParcel(Parcel dest, int flags) { 1477 dest.writeByte((byte) (captureRequested ? 1 : 0)); 1478 dest.writeByte((byte) (allowMultipleTriggers ? 1 : 0)); 1479 dest.writeTypedArray(keyphrases, flags); 1480 dest.writeBlob(data); 1481 dest.writeInt(audioCapabilities); 1482 } 1483 1484 @Override describeContents()1485 public int describeContents() { 1486 return 0; 1487 } 1488 1489 @Override toString()1490 public String toString() { 1491 return "RecognitionConfig [captureRequested=" + captureRequested 1492 + ", allowMultipleTriggers=" + allowMultipleTriggers + ", keyphrases=" 1493 + Arrays.toString(keyphrases) + ", data=" + Arrays.toString(data) 1494 + ", audioCapabilities=" + Integer.toHexString(audioCapabilities) + "]"; 1495 } 1496 } 1497 1498 /** 1499 * Confidence level for users defined in a keyphrase. 1500 * - The confidence level is expressed in percent (0% -100%). 1501 * When used in a {@link KeyphraseRecognitionEvent} it indicates the detected confidence level 1502 * When used in a {@link RecognitionConfig} it indicates the minimum confidence level that 1503 * should trigger a recognition. 1504 * - The user ID is derived from the system ID {@link android.os.UserHandle#getIdentifier()}. 1505 * 1506 * @hide 1507 */ 1508 public static class ConfidenceLevel implements Parcelable { 1509 @UnsupportedAppUsage 1510 public final int userId; 1511 @UnsupportedAppUsage 1512 public final int confidenceLevel; 1513 1514 @UnsupportedAppUsage ConfidenceLevel(int userId, int confidenceLevel)1515 public ConfidenceLevel(int userId, int confidenceLevel) { 1516 this.userId = userId; 1517 this.confidenceLevel = confidenceLevel; 1518 } 1519 1520 public static final @android.annotation.NonNull Parcelable.Creator<ConfidenceLevel> CREATOR 1521 = new Parcelable.Creator<ConfidenceLevel>() { 1522 public ConfidenceLevel createFromParcel(Parcel in) { 1523 return ConfidenceLevel.fromParcel(in); 1524 } 1525 1526 public ConfidenceLevel[] newArray(int size) { 1527 return new ConfidenceLevel[size]; 1528 } 1529 }; 1530 fromParcel(Parcel in)1531 private static ConfidenceLevel fromParcel(Parcel in) { 1532 int userId = in.readInt(); 1533 int confidenceLevel = in.readInt(); 1534 return new ConfidenceLevel(userId, confidenceLevel); 1535 } 1536 1537 @Override writeToParcel(Parcel dest, int flags)1538 public void writeToParcel(Parcel dest, int flags) { 1539 dest.writeInt(userId); 1540 dest.writeInt(confidenceLevel); 1541 } 1542 1543 @Override describeContents()1544 public int describeContents() { 1545 return 0; 1546 } 1547 1548 @Override hashCode()1549 public int hashCode() { 1550 final int prime = 31; 1551 int result = 1; 1552 result = prime * result + confidenceLevel; 1553 result = prime * result + userId; 1554 return result; 1555 } 1556 1557 @Override equals(@ullable Object obj)1558 public boolean equals(@Nullable Object obj) { 1559 if (this == obj) 1560 return true; 1561 if (obj == null) 1562 return false; 1563 if (getClass() != obj.getClass()) 1564 return false; 1565 ConfidenceLevel other = (ConfidenceLevel) obj; 1566 if (confidenceLevel != other.confidenceLevel) 1567 return false; 1568 if (userId != other.userId) 1569 return false; 1570 return true; 1571 } 1572 1573 @Override toString()1574 public String toString() { 1575 return "ConfidenceLevel [userId=" + userId 1576 + ", confidenceLevel=" + confidenceLevel + "]"; 1577 } 1578 } 1579 1580 /** 1581 * Additional data conveyed by a {@link KeyphraseRecognitionEvent} 1582 * for a key phrase detection. 1583 */ 1584 public static final class KeyphraseRecognitionExtra implements Parcelable { 1585 /** 1586 * The keyphrase ID 1587 * 1588 * @hide 1589 */ 1590 @UnsupportedAppUsage 1591 public final int id; 1592 1593 /** 1594 * Recognition modes matched for this event 1595 * 1596 * @hide 1597 */ 1598 @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553) 1599 public final int recognitionModes; 1600 1601 /** 1602 * Confidence level for mode RECOGNITION_MODE_VOICE_TRIGGER when user identification 1603 * is not performed 1604 * 1605 * @hide 1606 */ 1607 @UnsupportedAppUsage 1608 public final int coarseConfidenceLevel; 1609 1610 /** 1611 * Confidence levels for all users recognized (KeyphraseRecognitionEvent) or to 1612 * be recognized (RecognitionConfig) 1613 * 1614 * @hide 1615 */ 1616 @UnsupportedAppUsage 1617 @NonNull 1618 public final ConfidenceLevel[] confidenceLevels; 1619 1620 1621 /** 1622 * @hide 1623 */ 1624 @TestApi KeyphraseRecognitionExtra(int id, @RecognitionModes int recognitionModes, int coarseConfidenceLevel)1625 public KeyphraseRecognitionExtra(int id, @RecognitionModes int recognitionModes, 1626 int coarseConfidenceLevel) { 1627 this(id, recognitionModes, coarseConfidenceLevel, new ConfidenceLevel[0]); 1628 } 1629 1630 /** 1631 * @hide 1632 */ 1633 @UnsupportedAppUsage KeyphraseRecognitionExtra(int id, int recognitionModes, @IntRange(from = 0, to = 100) int coarseConfidenceLevel, @Nullable ConfidenceLevel[] confidenceLevels)1634 public KeyphraseRecognitionExtra(int id, int recognitionModes, 1635 @IntRange(from = 0, to = 100) int coarseConfidenceLevel, 1636 @Nullable ConfidenceLevel[] confidenceLevels) { 1637 this.id = id; 1638 this.recognitionModes = recognitionModes; 1639 this.coarseConfidenceLevel = coarseConfidenceLevel; 1640 this.confidenceLevels = 1641 confidenceLevels != null ? confidenceLevels : new ConfidenceLevel[0]; 1642 } 1643 1644 /** 1645 * The keyphrase ID associated with this class' additional data 1646 */ getKeyphraseId()1647 public int getKeyphraseId() { 1648 return id; 1649 } 1650 1651 /** 1652 * Recognition modes matched for this event 1653 */ 1654 @RecognitionModes getRecognitionModes()1655 public int getRecognitionModes() { 1656 return recognitionModes; 1657 } 1658 1659 /** 1660 * Confidence level for mode RECOGNITION_MODE_VOICE_TRIGGER when user identification 1661 * is not performed 1662 * 1663 * <p>The confidence level is expressed in percent (0% -100%). 1664 */ 1665 @IntRange(from = 0, to = 100) getCoarseConfidenceLevel()1666 public int getCoarseConfidenceLevel() { 1667 return coarseConfidenceLevel; 1668 } 1669 1670 /** 1671 * Detected confidence level for users defined in a keyphrase. 1672 * 1673 * <p>The confidence level is expressed in percent (0% -100%). 1674 * 1675 * <p>The user ID is derived from the system ID 1676 * {@link android.os.UserHandle#getIdentifier()}. 1677 * 1678 * @hide 1679 */ 1680 @NonNull getConfidenceLevels()1681 public Collection<ConfidenceLevel> getConfidenceLevels() { 1682 return Arrays.asList(confidenceLevels); 1683 } 1684 1685 public static final @NonNull Parcelable.Creator<KeyphraseRecognitionExtra> CREATOR 1686 = new Parcelable.Creator<KeyphraseRecognitionExtra>() { 1687 public KeyphraseRecognitionExtra createFromParcel(Parcel in) { 1688 return KeyphraseRecognitionExtra.fromParcel(in); 1689 } 1690 1691 public KeyphraseRecognitionExtra[] newArray(int size) { 1692 return new KeyphraseRecognitionExtra[size]; 1693 } 1694 }; 1695 fromParcel(Parcel in)1696 private static KeyphraseRecognitionExtra fromParcel(Parcel in) { 1697 int id = in.readInt(); 1698 int recognitionModes = in.readInt(); 1699 int coarseConfidenceLevel = in.readInt(); 1700 ConfidenceLevel[] confidenceLevels = in.createTypedArray(ConfidenceLevel.CREATOR); 1701 return new KeyphraseRecognitionExtra(id, recognitionModes, coarseConfidenceLevel, 1702 confidenceLevels); 1703 } 1704 1705 @Override writeToParcel(@onNull Parcel dest, int flags)1706 public void writeToParcel(@NonNull Parcel dest, int flags) { 1707 dest.writeInt(id); 1708 dest.writeInt(recognitionModes); 1709 dest.writeInt(coarseConfidenceLevel); 1710 dest.writeTypedArray(confidenceLevels, flags); 1711 } 1712 1713 @Override describeContents()1714 public int describeContents() { 1715 return 0; 1716 } 1717 1718 @Override hashCode()1719 public int hashCode() { 1720 final int prime = 31; 1721 int result = 1; 1722 result = prime * result + Arrays.hashCode(confidenceLevels); 1723 result = prime * result + id; 1724 result = prime * result + recognitionModes; 1725 result = prime * result + coarseConfidenceLevel; 1726 return result; 1727 } 1728 1729 @Override equals(@ullable Object obj)1730 public boolean equals(@Nullable Object obj) { 1731 if (this == obj) { 1732 return true; 1733 } 1734 if (obj == null) { 1735 return false; 1736 } 1737 if (getClass() != obj.getClass()) { 1738 return false; 1739 } 1740 KeyphraseRecognitionExtra other = (KeyphraseRecognitionExtra) obj; 1741 if (!Arrays.equals(confidenceLevels, other.confidenceLevels)) { 1742 return false; 1743 } 1744 if (id != other.id) { 1745 return false; 1746 } 1747 if (recognitionModes != other.recognitionModes) { 1748 return false; 1749 } 1750 if (coarseConfidenceLevel != other.coarseConfidenceLevel) { 1751 return false; 1752 } 1753 return true; 1754 } 1755 1756 @Override toString()1757 public String toString() { 1758 return "KeyphraseRecognitionExtra [id=" + id + ", recognitionModes=" + recognitionModes 1759 + ", coarseConfidenceLevel=" + coarseConfidenceLevel 1760 + ", confidenceLevels=" + Arrays.toString(confidenceLevels) + "]"; 1761 } 1762 } 1763 1764 /** 1765 * Specialized {@link RecognitionEvent} for a key phrase detection. 1766 * 1767 * @hide 1768 */ 1769 public static class KeyphraseRecognitionEvent extends RecognitionEvent implements Parcelable { 1770 /** Indicates if the key phrase is present in the buffered audio available for capture */ 1771 @UnsupportedAppUsage 1772 @NonNull 1773 public final KeyphraseRecognitionExtra[] keyphraseExtras; 1774 1775 @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553) KeyphraseRecognitionEvent(int status, int soundModelHandle, boolean captureAvailable, int captureSession, int captureDelayMs, int capturePreambleMs, boolean triggerInData, @NonNull AudioFormat captureFormat, @Nullable byte[] data, @Nullable KeyphraseRecognitionExtra[] keyphraseExtras)1776 public KeyphraseRecognitionEvent(int status, int soundModelHandle, boolean captureAvailable, 1777 int captureSession, int captureDelayMs, int capturePreambleMs, 1778 boolean triggerInData, @NonNull AudioFormat captureFormat, @Nullable byte[] data, 1779 @Nullable KeyphraseRecognitionExtra[] keyphraseExtras) { 1780 this(status, soundModelHandle, captureAvailable, captureSession, captureDelayMs, 1781 capturePreambleMs, triggerInData, captureFormat, data, keyphraseExtras, 1782 status == RECOGNITION_STATUS_GET_STATE_RESPONSE); 1783 } 1784 KeyphraseRecognitionEvent(int status, int soundModelHandle, boolean captureAvailable, int captureSession, int captureDelayMs, int capturePreambleMs, boolean triggerInData, @NonNull AudioFormat captureFormat, @Nullable byte[] data, @Nullable KeyphraseRecognitionExtra[] keyphraseExtras, boolean recognitionStillActive)1785 public KeyphraseRecognitionEvent(int status, int soundModelHandle, boolean captureAvailable, 1786 int captureSession, int captureDelayMs, int capturePreambleMs, 1787 boolean triggerInData, @NonNull AudioFormat captureFormat, @Nullable byte[] data, 1788 @Nullable KeyphraseRecognitionExtra[] keyphraseExtras, 1789 boolean recognitionStillActive) { 1790 super(status, soundModelHandle, captureAvailable, captureSession, captureDelayMs, 1791 capturePreambleMs, triggerInData, captureFormat, data, recognitionStillActive); 1792 this.keyphraseExtras = 1793 keyphraseExtras != null ? keyphraseExtras : new KeyphraseRecognitionExtra[0]; 1794 } 1795 1796 public static final @NonNull Parcelable.Creator<KeyphraseRecognitionEvent> CREATOR 1797 = new Parcelable.Creator<KeyphraseRecognitionEvent>() { 1798 public KeyphraseRecognitionEvent createFromParcel(Parcel in) { 1799 return KeyphraseRecognitionEvent.fromParcelForKeyphrase(in); 1800 } 1801 1802 public KeyphraseRecognitionEvent[] newArray(int size) { 1803 return new KeyphraseRecognitionEvent[size]; 1804 } 1805 }; 1806 fromParcelForKeyphrase(Parcel in)1807 private static KeyphraseRecognitionEvent fromParcelForKeyphrase(Parcel in) { 1808 int status = in.readInt(); 1809 int soundModelHandle = in.readInt(); 1810 boolean captureAvailable = in.readByte() == 1; 1811 int captureSession = in.readInt(); 1812 int captureDelayMs = in.readInt(); 1813 int capturePreambleMs = in.readInt(); 1814 boolean triggerInData = in.readByte() == 1; 1815 AudioFormat captureFormat = null; 1816 if (in.readByte() == 1) { 1817 int sampleRate = in.readInt(); 1818 int encoding = in.readInt(); 1819 int channelMask = in.readInt(); 1820 captureFormat = (new AudioFormat.Builder()) 1821 .setChannelMask(channelMask) 1822 .setEncoding(encoding) 1823 .setSampleRate(sampleRate) 1824 .build(); 1825 } 1826 byte[] data = in.readBlob(); 1827 boolean recognitionStillActive = in.readBoolean(); 1828 KeyphraseRecognitionExtra[] keyphraseExtras = 1829 in.createTypedArray(KeyphraseRecognitionExtra.CREATOR); 1830 return new KeyphraseRecognitionEvent(status, soundModelHandle, captureAvailable, 1831 captureSession, captureDelayMs, capturePreambleMs, triggerInData, 1832 captureFormat, data, keyphraseExtras, recognitionStillActive); 1833 } 1834 1835 @Override writeToParcel(Parcel dest, int flags)1836 public void writeToParcel(Parcel dest, int flags) { 1837 dest.writeInt(status); 1838 dest.writeInt(soundModelHandle); 1839 dest.writeByte((byte) (captureAvailable ? 1 : 0)); 1840 dest.writeInt(captureSession); 1841 dest.writeInt(captureDelayMs); 1842 dest.writeInt(capturePreambleMs); 1843 dest.writeByte((byte) (triggerInData ? 1 : 0)); 1844 if (captureFormat != null) { 1845 dest.writeByte((byte)1); 1846 dest.writeInt(captureFormat.getSampleRate()); 1847 dest.writeInt(captureFormat.getEncoding()); 1848 dest.writeInt(captureFormat.getChannelMask()); 1849 } else { 1850 dest.writeByte((byte)0); 1851 } 1852 dest.writeBlob(data); 1853 dest.writeBoolean(recognitionStillActive); 1854 dest.writeTypedArray(keyphraseExtras, flags); 1855 } 1856 1857 @Override describeContents()1858 public int describeContents() { 1859 return 0; 1860 } 1861 1862 @Override hashCode()1863 public int hashCode() { 1864 final int prime = 31; 1865 int result = super.hashCode(); 1866 result = prime * result + Arrays.hashCode(keyphraseExtras); 1867 return result; 1868 } 1869 1870 @Override equals(@ullable Object obj)1871 public boolean equals(@Nullable Object obj) { 1872 if (this == obj) 1873 return true; 1874 if (!super.equals(obj)) 1875 return false; 1876 if (getClass() != obj.getClass()) 1877 return false; 1878 KeyphraseRecognitionEvent other = (KeyphraseRecognitionEvent) obj; 1879 if (!Arrays.equals(keyphraseExtras, other.keyphraseExtras)) 1880 return false; 1881 return true; 1882 } 1883 1884 @Override toString()1885 public String toString() { 1886 return "KeyphraseRecognitionEvent [keyphraseExtras=" + Arrays.toString(keyphraseExtras) 1887 + ", status=" + status 1888 + ", soundModelHandle=" + soundModelHandle + ", captureAvailable=" 1889 + captureAvailable + ", captureSession=" + captureSession + ", captureDelayMs=" 1890 + captureDelayMs + ", capturePreambleMs=" + capturePreambleMs 1891 + ", triggerInData=" + triggerInData 1892 + ((captureFormat == null) ? "" : 1893 (", sampleRate=" + captureFormat.getSampleRate())) 1894 + ((captureFormat == null) ? "" : 1895 (", encoding=" + captureFormat.getEncoding())) 1896 + ((captureFormat == null) ? "" : 1897 (", channelMask=" + captureFormat.getChannelMask())) 1898 + ", data=" + (data == null ? 0 : data.length) 1899 + ", recognitionStillActive=" + recognitionStillActive 1900 + "]"; 1901 } 1902 } 1903 1904 /** 1905 * Sub-class of RecognitionEvent specifically for sound-trigger based sound 1906 * models(non-keyphrase). Currently does not contain any additional fields. 1907 * 1908 * @hide 1909 */ 1910 public static class GenericRecognitionEvent extends RecognitionEvent implements Parcelable { 1911 @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553) GenericRecognitionEvent(int status, int soundModelHandle, boolean captureAvailable, int captureSession, int captureDelayMs, int capturePreambleMs, boolean triggerInData, @NonNull AudioFormat captureFormat, @Nullable byte[] data)1912 public GenericRecognitionEvent(int status, int soundModelHandle, 1913 boolean captureAvailable, int captureSession, int captureDelayMs, 1914 int capturePreambleMs, boolean triggerInData, @NonNull AudioFormat captureFormat, 1915 @Nullable byte[] data) { 1916 this(status, soundModelHandle, captureAvailable, captureSession, captureDelayMs, 1917 capturePreambleMs, triggerInData, captureFormat, data, 1918 status == RECOGNITION_STATUS_GET_STATE_RESPONSE); 1919 } 1920 GenericRecognitionEvent(int status, int soundModelHandle, boolean captureAvailable, int captureSession, int captureDelayMs, int capturePreambleMs, boolean triggerInData, @NonNull AudioFormat captureFormat, @Nullable byte[] data, boolean recognitionStillActive)1921 public GenericRecognitionEvent(int status, int soundModelHandle, 1922 boolean captureAvailable, int captureSession, int captureDelayMs, 1923 int capturePreambleMs, boolean triggerInData, @NonNull AudioFormat captureFormat, 1924 @Nullable byte[] data, boolean recognitionStillActive) { 1925 super(status, soundModelHandle, captureAvailable, captureSession, captureDelayMs, 1926 capturePreambleMs, triggerInData, captureFormat, data, recognitionStillActive); 1927 } 1928 1929 public static final @android.annotation.NonNull Parcelable.Creator<GenericRecognitionEvent> CREATOR 1930 = new Parcelable.Creator<GenericRecognitionEvent>() { 1931 public GenericRecognitionEvent createFromParcel(Parcel in) { 1932 return GenericRecognitionEvent.fromParcelForGeneric(in); 1933 } 1934 1935 public GenericRecognitionEvent[] newArray(int size) { 1936 return new GenericRecognitionEvent[size]; 1937 } 1938 }; 1939 fromParcelForGeneric(Parcel in)1940 private static GenericRecognitionEvent fromParcelForGeneric(Parcel in) { 1941 RecognitionEvent event = RecognitionEvent.fromParcel(in); 1942 return new GenericRecognitionEvent(event.status, event.soundModelHandle, 1943 event.captureAvailable, event.captureSession, event.captureDelayMs, 1944 event.capturePreambleMs, event.triggerInData, event.captureFormat, event.data, 1945 event.recognitionStillActive); 1946 } 1947 1948 @Override equals(@ullable Object obj)1949 public boolean equals(@Nullable Object obj) { 1950 if (this == obj) 1951 return true; 1952 if (obj == null) 1953 return false; 1954 if (getClass() != obj.getClass()) return false; 1955 RecognitionEvent other = (RecognitionEvent) obj; 1956 return super.equals(obj); 1957 } 1958 1959 @Override toString()1960 public String toString() { 1961 return "GenericRecognitionEvent ::" + super.toString(); 1962 } 1963 } 1964 1965 private static Object mServiceLock = new Object(); 1966 1967 /** 1968 * Translate an exception thrown from interaction with the underlying service to an error code. 1969 * Throws a runtime exception for unexpected conditions. 1970 * @param e The caught exception. 1971 * @return The error code. 1972 * 1973 * @hide 1974 */ handleException(Exception e)1975 static int handleException(Exception e) { 1976 Log.w(TAG, "Exception caught", e); 1977 if (e instanceof RemoteException) { 1978 return STATUS_DEAD_OBJECT; 1979 } 1980 if (e instanceof ServiceSpecificException) { 1981 switch (((ServiceSpecificException) e).errorCode) { 1982 case Status.OPERATION_NOT_SUPPORTED: 1983 return STATUS_INVALID_OPERATION; 1984 case Status.TEMPORARY_PERMISSION_DENIED: 1985 return STATUS_PERMISSION_DENIED; 1986 case Status.DEAD_OBJECT: 1987 return STATUS_DEAD_OBJECT; 1988 case Status.INTERNAL_ERROR: 1989 return STATUS_ERROR; 1990 case Status.RESOURCE_CONTENTION: 1991 return STATUS_BUSY; 1992 } 1993 return STATUS_ERROR; 1994 } 1995 if (e instanceof SecurityException) { 1996 return STATUS_PERMISSION_DENIED; 1997 } 1998 if (e instanceof IllegalStateException) { 1999 return STATUS_INVALID_OPERATION; 2000 } 2001 if (e instanceof IllegalArgumentException || e instanceof NullPointerException) { 2002 return STATUS_BAD_VALUE; 2003 } 2004 // This is not one of the conditions represented by our error code, escalate to a 2005 // RuntimeException. 2006 Log.e(TAG, "Escalating unexpected exception: ", e); 2007 throw new RuntimeException(e); 2008 } 2009 2010 /** 2011 * Returns a list of descriptors for all hardware modules loaded. 2012 * @param modules A ModuleProperties array where the list will be returned. 2013 * @return - {@link #STATUS_OK} in case of success 2014 * - {@link #STATUS_ERROR} in case of unspecified error 2015 * - {@link #STATUS_PERMISSION_DENIED} if the caller does not have system permission 2016 * - {@link #STATUS_NO_INIT} if the native service cannot be reached 2017 * - {@link #STATUS_BAD_VALUE} if modules is null 2018 * - {@link #STATUS_DEAD_OBJECT} if the binder transaction to the native service fails 2019 * 2020 * @deprecated Please use {@link #listModulesAsOriginator(ArrayList, Identity)} or 2021 * {@link #listModulesAsMiddleman(ArrayList, Identity, Identity)}, based on whether the 2022 * client is acting on behalf of its own identity or a separate identity. 2023 * @hide 2024 */ 2025 @UnsupportedAppUsage listModules(@onNull ArrayList<ModuleProperties> modules)2026 public static int listModules(@NonNull ArrayList<ModuleProperties> modules) { 2027 // TODO(ytai): This is a temporary hack to retain prior behavior, which makes 2028 // assumptions about process affinity and Binder context, namely that the binder calling ID 2029 // reliably reflects the originator identity. 2030 Identity middlemanIdentity = new Identity(); 2031 middlemanIdentity.packageName = ActivityThread.currentOpPackageName(); 2032 2033 Identity originatorIdentity = new Identity(); 2034 originatorIdentity.pid = Binder.getCallingPid(); 2035 originatorIdentity.uid = Binder.getCallingUid(); 2036 2037 try (SafeCloseable ignored = ClearCallingIdentityContext.create()) { 2038 return listModulesAsMiddleman(modules, middlemanIdentity, originatorIdentity); 2039 } 2040 } 2041 2042 /** 2043 * Returns a list of descriptors for all hardware modules loaded. 2044 * This variant is intended for use when the caller itself is the originator of the operation. 2045 * @param modules A ModuleProperties array where the list will be returned. 2046 * @param originatorIdentity The identity of the originator, which will be used for permission 2047 * purposes. 2048 * @return - {@link #STATUS_OK} in case of success 2049 * - {@link #STATUS_ERROR} in case of unspecified error 2050 * - {@link #STATUS_PERMISSION_DENIED} if the caller does not have system permission 2051 * - {@link #STATUS_NO_INIT} if the native service cannot be reached 2052 * - {@link #STATUS_BAD_VALUE} if modules is null 2053 * - {@link #STATUS_DEAD_OBJECT} if the binder transaction to the native service fails 2054 * 2055 * @hide 2056 */ 2057 @RequiresPermission(allOf = {RECORD_AUDIO, CAPTURE_AUDIO_HOTWORD}) listModulesAsOriginator(@onNull ArrayList<ModuleProperties> modules, @NonNull Identity originatorIdentity)2058 public static int listModulesAsOriginator(@NonNull ArrayList<ModuleProperties> modules, 2059 @NonNull Identity originatorIdentity) { 2060 try { 2061 SoundTriggerModuleDescriptor[] descs = getService().listModulesAsOriginator( 2062 originatorIdentity); 2063 convertDescriptorsToModuleProperties(descs, modules); 2064 return STATUS_OK; 2065 } catch (Exception e) { 2066 return handleException(e); 2067 } 2068 } 2069 2070 /** 2071 * Returns a list of descriptors for all hardware modules loaded. 2072 * This variant is intended for use when the caller is acting on behalf of a different identity 2073 * for permission purposes. 2074 * @param modules A ModuleProperties array where the list will be returned. 2075 * @param middlemanIdentity The identity of the caller, acting as middleman. 2076 * @param originatorIdentity The identity of the originator, which will be used for permission 2077 * purposes. 2078 * @return - {@link #STATUS_OK} in case of success 2079 * - {@link #STATUS_ERROR} in case of unspecified error 2080 * - {@link #STATUS_PERMISSION_DENIED} if the caller does not have system permission 2081 * - {@link #STATUS_NO_INIT} if the native service cannot be reached 2082 * - {@link #STATUS_BAD_VALUE} if modules is null 2083 * - {@link #STATUS_DEAD_OBJECT} if the binder transaction to the native service fails 2084 * 2085 * @hide 2086 */ 2087 @RequiresPermission(SOUNDTRIGGER_DELEGATE_IDENTITY) listModulesAsMiddleman(@onNull ArrayList<ModuleProperties> modules, @NonNull Identity middlemanIdentity, @NonNull Identity originatorIdentity)2088 public static int listModulesAsMiddleman(@NonNull ArrayList<ModuleProperties> modules, 2089 @NonNull Identity middlemanIdentity, 2090 @NonNull Identity originatorIdentity) { 2091 try { 2092 SoundTriggerModuleDescriptor[] descs = getService().listModulesAsMiddleman( 2093 middlemanIdentity, originatorIdentity); 2094 convertDescriptorsToModuleProperties(descs, modules); 2095 return STATUS_OK; 2096 } catch (Exception e) { 2097 return handleException(e); 2098 } 2099 } 2100 2101 /** 2102 * Converts an array of SoundTriggerModuleDescriptor into an (existing) ArrayList of 2103 * ModuleProperties. 2104 * @param descsIn The input descriptors. 2105 * @param modulesOut The output list. 2106 */ convertDescriptorsToModuleProperties( @onNull SoundTriggerModuleDescriptor[] descsIn, @NonNull ArrayList<ModuleProperties> modulesOut)2107 private static void convertDescriptorsToModuleProperties( 2108 @NonNull SoundTriggerModuleDescriptor[] descsIn, 2109 @NonNull ArrayList<ModuleProperties> modulesOut) { 2110 modulesOut.clear(); 2111 modulesOut.ensureCapacity(descsIn.length); 2112 for (SoundTriggerModuleDescriptor desc : descsIn) { 2113 modulesOut.add(ConversionUtil.aidl2apiModuleDescriptor(desc)); 2114 } 2115 } 2116 2117 /** 2118 * Get an interface on a hardware module to control sound models and recognition on 2119 * this module. 2120 * @param moduleId Sound module system identifier {@link ModuleProperties#mId}. mandatory. 2121 * @param listener {@link StatusListener} interface. Mandatory. 2122 * @param handler the Handler that will receive the callabcks. Can be null if default handler 2123 * is OK. 2124 * @return a valid sound module in case of success or null in case of error. 2125 * 2126 * @deprecated Please use 2127 * {@link #attachModuleAsOriginator(int, StatusListener, Handler, Identity)} or 2128 * {@link #attachModuleAsMiddleman(int, StatusListener, Handler, Identity, Identity)}, based 2129 * on whether the client is acting on behalf of its own identity or a separate identity. 2130 * @hide 2131 */ 2132 @UnsupportedAppUsage attachModule(int moduleId, @NonNull StatusListener listener, @Nullable Handler handler)2133 private static SoundTriggerModule attachModule(int moduleId, 2134 @NonNull StatusListener listener, 2135 @Nullable Handler handler) { 2136 // TODO(ytai): This is a temporary hack to retain prior behavior, which makes 2137 // assumptions about process affinity and Binder context, namely that the binder calling ID 2138 // reliably reflects the originator identity. 2139 Identity middlemanIdentity = new Identity(); 2140 middlemanIdentity.packageName = ActivityThread.currentOpPackageName(); 2141 2142 Identity originatorIdentity = new Identity(); 2143 originatorIdentity.pid = Binder.getCallingPid(); 2144 originatorIdentity.uid = Binder.getCallingUid(); 2145 2146 try (SafeCloseable ignored = ClearCallingIdentityContext.create()) { 2147 return attachModuleAsMiddleman(moduleId, listener, handler, middlemanIdentity, 2148 originatorIdentity); 2149 } 2150 } 2151 2152 /** 2153 * Get an interface on a hardware module to control sound models and recognition on 2154 * this module. 2155 * This variant is intended for use when the caller is acting on behalf of a different identity 2156 * for permission purposes. 2157 * @param moduleId Sound module system identifier {@link ModuleProperties#mId}. mandatory. 2158 * @param listener {@link StatusListener} interface. Mandatory. 2159 * @param handler the Handler that will receive the callabcks. Can be null if default handler 2160 * is OK. 2161 * @param middlemanIdentity The identity of the caller, acting as middleman. 2162 * @param originatorIdentity The identity of the originator, which will be used for permission 2163 * purposes. 2164 * @return a valid sound module in case of success or null in case of error. 2165 * 2166 * @hide 2167 */ 2168 @RequiresPermission(SOUNDTRIGGER_DELEGATE_IDENTITY) attachModuleAsMiddleman(int moduleId, @NonNull SoundTrigger.StatusListener listener, @Nullable Handler handler, Identity middlemanIdentity, Identity originatorIdentity)2169 public static SoundTriggerModule attachModuleAsMiddleman(int moduleId, 2170 @NonNull SoundTrigger.StatusListener listener, 2171 @Nullable Handler handler, Identity middlemanIdentity, 2172 Identity originatorIdentity) { 2173 Looper looper = handler != null ? handler.getLooper() : Looper.getMainLooper(); 2174 try { 2175 return new SoundTriggerModule(getService(), moduleId, listener, looper, 2176 middlemanIdentity, originatorIdentity); 2177 } catch (Exception e) { 2178 Log.e(TAG, "", e); 2179 return null; 2180 } 2181 } 2182 2183 /** 2184 * Get an interface on a hardware module to control sound models and recognition on 2185 * this module. 2186 * This variant is intended for use when the caller itself is the originator of the operation. 2187 * @param moduleId Sound module system identifier {@link ModuleProperties#mId}. mandatory. 2188 * @param listener {@link StatusListener} interface. Mandatory. 2189 * @param handler the Handler that will receive the callabcks. Can be null if default handler 2190 * is OK. 2191 * @param originatorIdentity The identity of the originator, which will be used for permission 2192 * purposes. 2193 * @return a valid sound module in case of success or null in case of error. 2194 * 2195 * @hide 2196 */ 2197 @RequiresPermission(allOf = {RECORD_AUDIO, CAPTURE_AUDIO_HOTWORD}) attachModuleAsOriginator(int moduleId, @NonNull SoundTrigger.StatusListener listener, @Nullable Handler handler, @NonNull Identity originatorIdentity)2198 public static SoundTriggerModule attachModuleAsOriginator(int moduleId, 2199 @NonNull SoundTrigger.StatusListener listener, 2200 @Nullable Handler handler, @NonNull Identity originatorIdentity) { 2201 Looper looper = handler != null ? handler.getLooper() : Looper.getMainLooper(); 2202 try { 2203 return new SoundTriggerModule(getService(), moduleId, listener, looper, 2204 originatorIdentity); 2205 } catch (Exception e) { 2206 Log.e(TAG, "", e); 2207 return null; 2208 } 2209 } 2210 getService()2211 private static ISoundTriggerMiddlewareService getService() { 2212 synchronized (mServiceLock) { 2213 while (true) { 2214 IBinder binder = null; 2215 try { 2216 binder = 2217 ServiceManager.getServiceOrThrow( 2218 Context.SOUND_TRIGGER_MIDDLEWARE_SERVICE); 2219 return ISoundTriggerMiddlewareService.Stub.asInterface(binder); 2220 } catch (Exception e) { 2221 Log.e(TAG, "Failed to bind to soundtrigger service", e); 2222 } 2223 } 2224 } 2225 } 2226 2227 /** 2228 * Interface provided by the client application when attaching to a {@link SoundTriggerModule} 2229 * to received recognition and error notifications. 2230 * 2231 * @hide 2232 */ 2233 public interface StatusListener { 2234 /** 2235 * Called when recognition succeeds of fails 2236 */ onRecognition(RecognitionEvent event)2237 void onRecognition(RecognitionEvent event); 2238 2239 /** 2240 * Called when a sound model has been preemptively unloaded by the underlying 2241 * implementation. 2242 */ onModelUnloaded(int modelHandle)2243 void onModelUnloaded(int modelHandle); 2244 2245 /** 2246 * Called whenever underlying conditions change, such that load/start operations that have 2247 * previously failed or got preempted may now succeed. This is not a guarantee, merely a 2248 * hint that the client may want to retry operations. 2249 */ onResourcesAvailable()2250 void onResourcesAvailable(); 2251 2252 /** 2253 * Called when the sound trigger native service dies 2254 */ onServiceDied()2255 void onServiceDied(); 2256 } 2257 } 2258