1 /* 2 * Copyright (C) 2022 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 com.android.server.voiceinteraction; 18 19 import static android.Manifest.permission.CAPTURE_AUDIO_HOTWORD; 20 import static android.Manifest.permission.LOG_COMPAT_CHANGE; 21 import static android.Manifest.permission.READ_COMPAT_CHANGE_CONFIG; 22 import static android.Manifest.permission.RECORD_AUDIO; 23 import static android.app.AppOpsManager.MODE_ALLOWED; 24 import static android.app.AppOpsManager.MODE_DEFAULT; 25 import static android.service.attention.AttentionService.PROXIMITY_UNKNOWN; 26 import static android.service.voice.HotwordDetectionService.AUDIO_SOURCE_EXTERNAL; 27 import static android.service.voice.HotwordDetectionService.ENABLE_PROXIMITY_RESULT; 28 import static android.service.voice.HotwordDetectionService.INITIALIZATION_STATUS_SUCCESS; 29 import static android.service.voice.HotwordDetectionService.INITIALIZATION_STATUS_UNKNOWN; 30 import static android.service.voice.HotwordDetectionService.KEY_INITIALIZATION_STATUS; 31 import static android.service.voice.HotwordDetectionServiceFailure.ERROR_CODE_COPY_AUDIO_DATA_FAILURE; 32 33 import static com.android.internal.util.FrameworkStatsLog.HOTWORD_DETECTION_SERVICE_INIT_RESULT_REPORTED__RESULT__CALLBACK_INIT_STATE_ERROR; 34 import static com.android.internal.util.FrameworkStatsLog.HOTWORD_DETECTION_SERVICE_INIT_RESULT_REPORTED__RESULT__CALLBACK_INIT_STATE_SUCCESS; 35 import static com.android.internal.util.FrameworkStatsLog.HOTWORD_DETECTION_SERVICE_INIT_RESULT_REPORTED__RESULT__CALLBACK_INIT_STATE_UNKNOWN_NO_VALUE; 36 import static com.android.internal.util.FrameworkStatsLog.HOTWORD_DETECTION_SERVICE_INIT_RESULT_REPORTED__RESULT__CALLBACK_INIT_STATE_UNKNOWN_OVER_MAX_CUSTOM_VALUE; 37 import static com.android.internal.util.FrameworkStatsLog.HOTWORD_DETECTION_SERVICE_INIT_RESULT_REPORTED__RESULT__CALLBACK_INIT_STATE_UNKNOWN_TIMEOUT; 38 import static com.android.internal.util.FrameworkStatsLog.HOTWORD_DETECTOR_EVENTS__EVENT__APP_REQUEST_UPDATE_STATE; 39 import static com.android.internal.util.FrameworkStatsLog.HOTWORD_DETECTOR_EVENTS__EVENT__CALLBACK_ON_ERROR_EXCEPTION; 40 import static com.android.internal.util.FrameworkStatsLog.HOTWORD_DETECTOR_EVENTS__EVENT__CALLBACK_ON_STATUS_REPORTED_EXCEPTION; 41 import static com.android.internal.util.FrameworkStatsLog.HOTWORD_DETECTOR_EVENTS__EVENT__CALLBACK_UPDATE_STATE_AFTER_TIMEOUT; 42 import static com.android.internal.util.FrameworkStatsLog.HOTWORD_DETECTOR_EVENTS__EVENT__CALL_UPDATE_STATE_EXCEPTION; 43 import static com.android.internal.util.FrameworkStatsLog.HOTWORD_DETECTOR_EVENTS__EVENT__EXTERNAL_SOURCE_DETECTED; 44 import static com.android.internal.util.FrameworkStatsLog.HOTWORD_DETECTOR_EVENTS__EVENT__EXTERNAL_SOURCE_DETECT_SECURITY_EXCEPTION; 45 import static com.android.internal.util.FrameworkStatsLog.HOTWORD_DETECTOR_EVENTS__EVENT__EXTERNAL_SOURCE_REJECTED; 46 import static com.android.internal.util.FrameworkStatsLog.HOTWORD_DETECTOR_EVENTS__EVENT__REQUEST_UPDATE_STATE; 47 import static com.android.internal.util.FrameworkStatsLog.HOTWORD_DETECTOR_EVENTS__EVENT__START_EXTERNAL_SOURCE_DETECTION; 48 import static com.android.internal.util.FrameworkStatsLog.HOTWORD_DETECTOR_KEYPHRASE_TRIGGERED__RESULT__DETECT_SECURITY_EXCEPTION; 49 import static com.android.internal.util.FrameworkStatsLog.HOTWORD_DETECTOR_KEYPHRASE_TRIGGERED__RESULT__DETECT_UNEXPECTED_CALLBACK; 50 import static com.android.internal.util.FrameworkStatsLog.HOTWORD_DETECTOR_KEYPHRASE_TRIGGERED__RESULT__REJECT_UNEXPECTED_CALLBACK; 51 import static com.android.internal.util.FrameworkStatsLog.HOTWORD_EVENT_EGRESS_SIZE__EVENT_TYPE__HOTWORD_DETECTION; 52 import static com.android.internal.util.FrameworkStatsLog.HOTWORD_EVENT_EGRESS_SIZE__EVENT_TYPE__HOTWORD_REJECTION; 53 import static com.android.internal.util.FrameworkStatsLog.HOTWORD_EVENT_EGRESS_SIZE__EVENT_TYPE__HOTWORD_TRAINING_DATA; 54 import static com.android.server.voiceinteraction.HotwordDetectionConnection.ENFORCE_HOTWORD_PHRASE_ID; 55 56 import android.annotation.NonNull; 57 import android.annotation.Nullable; 58 import android.annotation.RequiresPermission; 59 import android.app.AppOpsManager; 60 import android.app.compat.CompatChanges; 61 import android.attention.AttentionManagerInternal; 62 import android.content.Context; 63 import android.content.PermissionChecker; 64 import android.hardware.soundtrigger.SoundTrigger; 65 import android.media.AudioFormat; 66 import android.media.permission.Identity; 67 import android.media.permission.PermissionUtil; 68 import android.os.Binder; 69 import android.os.Bundle; 70 import android.os.IBinder; 71 import android.os.IRemoteCallback; 72 import android.os.ParcelFileDescriptor; 73 import android.os.PersistableBundle; 74 import android.os.RemoteException; 75 import android.os.SharedMemory; 76 import android.service.voice.AlwaysOnHotwordDetector; 77 import android.service.voice.HotwordAudioStream; 78 import android.service.voice.HotwordDetectedResult; 79 import android.service.voice.HotwordDetectionService; 80 import android.service.voice.HotwordDetectionServiceFailure; 81 import android.service.voice.HotwordDetector; 82 import android.service.voice.HotwordRejectedResult; 83 import android.service.voice.IDspHotwordDetectionCallback; 84 import android.service.voice.IMicrophoneHotwordDetectionVoiceInteractionCallback; 85 import android.service.voice.VisualQueryDetectionServiceFailure; 86 import android.service.voice.VoiceInteractionManagerInternal.WearableHotwordDetectionCallback; 87 import android.text.TextUtils; 88 import android.util.Pair; 89 import android.util.Slog; 90 91 import com.android.internal.annotations.GuardedBy; 92 import com.android.internal.app.IHotwordRecognitionStatusCallback; 93 import com.android.internal.infra.AndroidFuture; 94 import com.android.server.LocalServices; 95 import com.android.server.policy.AppOpsPolicy; 96 import com.android.server.voiceinteraction.VoiceInteractionManagerServiceImpl.DetectorRemoteExceptionListener; 97 98 import java.io.Closeable; 99 import java.io.IOException; 100 import java.io.InputStream; 101 import java.io.OutputStream; 102 import java.io.PrintWriter; 103 import java.time.Duration; 104 import java.time.Instant; 105 import java.util.concurrent.Executor; 106 import java.util.concurrent.Executors; 107 import java.util.concurrent.ScheduledExecutorService; 108 import java.util.concurrent.TimeUnit; 109 import java.util.concurrent.TimeoutException; 110 import java.util.concurrent.atomic.AtomicBoolean; 111 112 /** 113 * A class that provides sandboxed detector to communicate with the {@link 114 * HotwordDetectionService} and {@link VisualQueryDetectionService}. 115 * 116 * Trusted hotword detectors such as {@link SoftwareHotwordDetector} and 117 * {@link AlwaysOnHotwordDetector} will leverage this class to communitcate with 118 * {@link HotwordDetectionService}; similarly, {@link VisualQueryDetector} will communicate with 119 * {@link VisualQueryDetectionService}. 120 * 121 * This class provides the methods to do initialization with the {@link HotwordDetectionService} and 122 * {@link VisualQueryDetectionService} handles external source detection for 123 * {@link HotwordDetectionService}. It also provides the methods to check if we can egress the data 124 * from the {@link HotwordDetectionService} and {@link VisualQueryDetectionService}. 125 * 126 * The subclass should override the {@link #informRestartProcessLocked()} to handle the trusted 127 * process restart. 128 */ 129 abstract class DetectorSession { 130 private static final String TAG = "DetectorSession"; 131 static final boolean DEBUG = false; 132 133 private static final String HOTWORD_DETECTION_OP_MESSAGE = 134 "Providing hotword detection result to VoiceInteractionService"; 135 136 // The error codes are used for onHotwordDetectionServiceFailure callback. 137 // Define these due to lines longer than 100 characters. 138 static final int ONDETECTED_GOT_SECURITY_EXCEPTION = 139 HotwordDetectionServiceFailure.ERROR_CODE_ON_DETECTED_SECURITY_EXCEPTION; 140 static final int ONDETECTED_STREAM_COPY_ERROR = 141 HotwordDetectionServiceFailure.ERROR_CODE_ON_DETECTED_STREAM_COPY_FAILURE; 142 143 // TODO: These constants need to be refined. 144 private static final long MAX_UPDATE_TIMEOUT_MILLIS = 30000; 145 private static final long EXTERNAL_HOTWORD_CLEANUP_MILLIS = 2000; 146 private static final Duration MAX_UPDATE_TIMEOUT_DURATION = 147 Duration.ofMillis(MAX_UPDATE_TIMEOUT_MILLIS); 148 149 // Hotword metrics 150 private static final int METRICS_INIT_UNKNOWN_TIMEOUT = 151 HOTWORD_DETECTION_SERVICE_INIT_RESULT_REPORTED__RESULT__CALLBACK_INIT_STATE_UNKNOWN_TIMEOUT; 152 private static final int METRICS_INIT_UNKNOWN_NO_VALUE = 153 HOTWORD_DETECTION_SERVICE_INIT_RESULT_REPORTED__RESULT__CALLBACK_INIT_STATE_UNKNOWN_NO_VALUE; 154 private static final int METRICS_INIT_UNKNOWN_OVER_MAX_CUSTOM_VALUE = 155 HOTWORD_DETECTION_SERVICE_INIT_RESULT_REPORTED__RESULT__CALLBACK_INIT_STATE_UNKNOWN_OVER_MAX_CUSTOM_VALUE; 156 private static final int METRICS_INIT_CALLBACK_STATE_ERROR = 157 HOTWORD_DETECTION_SERVICE_INIT_RESULT_REPORTED__RESULT__CALLBACK_INIT_STATE_ERROR; 158 private static final int METRICS_INIT_CALLBACK_STATE_SUCCESS = 159 HOTWORD_DETECTION_SERVICE_INIT_RESULT_REPORTED__RESULT__CALLBACK_INIT_STATE_SUCCESS; 160 161 static final int METRICS_KEYPHRASE_TRIGGERED_DETECT_SECURITY_EXCEPTION = 162 HOTWORD_DETECTOR_KEYPHRASE_TRIGGERED__RESULT__DETECT_SECURITY_EXCEPTION; 163 static final int METRICS_KEYPHRASE_TRIGGERED_DETECT_UNEXPECTED_CALLBACK = 164 HOTWORD_DETECTOR_KEYPHRASE_TRIGGERED__RESULT__DETECT_UNEXPECTED_CALLBACK; 165 static final int METRICS_KEYPHRASE_TRIGGERED_REJECT_UNEXPECTED_CALLBACK = 166 HOTWORD_DETECTOR_KEYPHRASE_TRIGGERED__RESULT__REJECT_UNEXPECTED_CALLBACK; 167 168 private static final int METRICS_EXTERNAL_SOURCE_DETECTED = 169 HOTWORD_DETECTOR_EVENTS__EVENT__EXTERNAL_SOURCE_DETECTED; 170 private static final int METRICS_EXTERNAL_SOURCE_REJECTED = 171 HOTWORD_DETECTOR_EVENTS__EVENT__EXTERNAL_SOURCE_REJECTED; 172 private static final int EXTERNAL_SOURCE_DETECT_SECURITY_EXCEPTION = 173 HOTWORD_DETECTOR_EVENTS__EVENT__EXTERNAL_SOURCE_DETECT_SECURITY_EXCEPTION; 174 private static final int METRICS_CALLBACK_ON_STATUS_REPORTED_EXCEPTION = 175 HOTWORD_DETECTOR_EVENTS__EVENT__CALLBACK_ON_STATUS_REPORTED_EXCEPTION; 176 177 private static final int HOTWORD_EVENT_TYPE_DETECTION = 178 HOTWORD_EVENT_EGRESS_SIZE__EVENT_TYPE__HOTWORD_DETECTION; 179 private static final int HOTWORD_EVENT_TYPE_REJECTION = 180 HOTWORD_EVENT_EGRESS_SIZE__EVENT_TYPE__HOTWORD_REJECTION; 181 private static final int HOTWORD_EVENT_TYPE_TRAINING_DATA = 182 HOTWORD_EVENT_EGRESS_SIZE__EVENT_TYPE__HOTWORD_TRAINING_DATA; 183 184 private final Executor mAudioCopyExecutor = Executors.newCachedThreadPool(); 185 // TODO: This may need to be a Handler(looper) 186 final ScheduledExecutorService mScheduledExecutorService; 187 final AppOpsManager mAppOpsManager; 188 final HotwordAudioStreamCopier mHotwordAudioStreamCopier; 189 final AtomicBoolean mUpdateStateAfterStartFinished = new AtomicBoolean(false); 190 final IHotwordRecognitionStatusCallback mCallback; 191 192 final Object mLock; 193 final int mVoiceInteractionServiceUid; 194 final Context mContext; 195 final int mUserId; 196 197 @Nullable AttentionManagerInternal mAttentionManagerInternal = null; 198 199 final AttentionManagerInternal.ProximityUpdateCallbackInternal mProximityCallbackInternal = 200 this::setProximityValue; 201 202 /** Identity used for attributing app ops when delivering data to the Interactor. */ 203 @Nullable 204 final Identity mVoiceInteractorIdentity; 205 @GuardedBy("mLock") 206 ParcelFileDescriptor mCurrentAudioSink; 207 @GuardedBy("mLock") 208 @NonNull HotwordDetectionConnection.ServiceConnection mRemoteDetectionService; 209 boolean mDebugHotwordLogging = false; 210 @GuardedBy("mLock") 211 private double mProximityMeters = PROXIMITY_UNKNOWN; 212 @GuardedBy("mLock") 213 private boolean mInitialized = false; 214 @GuardedBy("mLock") 215 private boolean mDestroyed = false; 216 @GuardedBy("mLock") 217 boolean mPerformingExternalSourceHotwordDetection; 218 @NonNull final IBinder mToken; 219 220 @NonNull DetectorRemoteExceptionListener mRemoteExceptionListener; 221 DetectorSession( @onNull HotwordDetectionConnection.ServiceConnection remoteDetectionService, @NonNull Object lock, @NonNull Context context, @NonNull IBinder token, @NonNull IHotwordRecognitionStatusCallback callback, int voiceInteractionServiceUid, Identity voiceInteractorIdentity, @NonNull ScheduledExecutorService scheduledExecutorService, boolean logging, @NonNull DetectorRemoteExceptionListener listener, int userId)222 DetectorSession( 223 @NonNull HotwordDetectionConnection.ServiceConnection remoteDetectionService, 224 @NonNull Object lock, @NonNull Context context, @NonNull IBinder token, 225 @NonNull IHotwordRecognitionStatusCallback callback, int voiceInteractionServiceUid, 226 Identity voiceInteractorIdentity, 227 @NonNull ScheduledExecutorService scheduledExecutorService, boolean logging, 228 @NonNull DetectorRemoteExceptionListener listener, int userId) { 229 mRemoteExceptionListener = listener; 230 mRemoteDetectionService = remoteDetectionService; 231 mLock = lock; 232 mContext = context; 233 mToken = token; 234 mUserId = userId; 235 mCallback = callback; 236 mVoiceInteractionServiceUid = voiceInteractionServiceUid; 237 mVoiceInteractorIdentity = voiceInteractorIdentity; 238 mAppOpsManager = mContext.getSystemService(AppOpsManager.class); 239 if (getDetectorType() != HotwordDetector.DETECTOR_TYPE_VISUAL_QUERY_DETECTOR) { 240 mHotwordAudioStreamCopier = new HotwordAudioStreamCopier(mAppOpsManager, 241 getDetectorType(), 242 mVoiceInteractorIdentity.uid, mVoiceInteractorIdentity.packageName, 243 mVoiceInteractorIdentity.attributionTag); 244 } else { 245 mHotwordAudioStreamCopier = null; 246 } 247 248 mScheduledExecutorService = scheduledExecutorService; 249 mDebugHotwordLogging = logging; 250 251 if (ENABLE_PROXIMITY_RESULT) { 252 mAttentionManagerInternal = LocalServices.getService(AttentionManagerInternal.class); 253 if (mAttentionManagerInternal != null 254 && mAttentionManagerInternal.isProximitySupported()) { 255 mAttentionManagerInternal.onStartProximityUpdates(mProximityCallbackInternal); 256 } 257 } 258 } 259 notifyOnDetectorRemoteException()260 void notifyOnDetectorRemoteException() { 261 Slog.d(TAG, "notifyOnDetectorRemoteException: mRemoteExceptionListener=" 262 + mRemoteExceptionListener); 263 if (mRemoteExceptionListener != null) { 264 mRemoteExceptionListener.onDetectorRemoteException(mToken, getDetectorType()); 265 } 266 } 267 268 @SuppressWarnings("GuardedBy") updateStateAfterProcessStartLocked(PersistableBundle options, SharedMemory sharedMemory)269 private void updateStateAfterProcessStartLocked(PersistableBundle options, 270 SharedMemory sharedMemory) { 271 if (DEBUG) { 272 Slog.d(TAG, "updateStateAfterProcessStartLocked"); 273 } 274 AndroidFuture<Void> voidFuture = mRemoteDetectionService.postAsync(service -> { 275 AndroidFuture<Void> future = new AndroidFuture<>(); 276 IRemoteCallback statusCallback = new IRemoteCallback.Stub() { 277 @Override 278 public void sendResult(Bundle bundle) throws RemoteException { 279 if (DEBUG) { 280 Slog.d(TAG, "updateState finish"); 281 } 282 future.complete(null); 283 if (mUpdateStateAfterStartFinished.getAndSet(true)) { 284 Slog.w(TAG, "call callback after timeout"); 285 if (getDetectorType() 286 != HotwordDetector.DETECTOR_TYPE_VISUAL_QUERY_DETECTOR) { 287 HotwordMetricsLogger.writeDetectorEvent(getDetectorType(), 288 HOTWORD_DETECTOR_EVENTS__EVENT__CALLBACK_UPDATE_STATE_AFTER_TIMEOUT, 289 mVoiceInteractionServiceUid); 290 } 291 return; 292 } 293 Pair<Integer, Integer> statusResultPair = getInitStatusAndMetricsResult(bundle); 294 int status = statusResultPair.first; 295 int initResultMetricsResult = statusResultPair.second; 296 try { 297 mCallback.onStatusReported(status); 298 if (getDetectorType() 299 != HotwordDetector.DETECTOR_TYPE_VISUAL_QUERY_DETECTOR) { 300 HotwordMetricsLogger.writeServiceInitResultEvent(getDetectorType(), 301 initResultMetricsResult, mVoiceInteractionServiceUid); 302 } 303 } catch (RemoteException e) { 304 Slog.w(TAG, "Failed to report initialization status: " + e); 305 if (getDetectorType() 306 != HotwordDetector.DETECTOR_TYPE_VISUAL_QUERY_DETECTOR) { 307 HotwordMetricsLogger.writeDetectorEvent(getDetectorType(), 308 METRICS_CALLBACK_ON_STATUS_REPORTED_EXCEPTION, 309 mVoiceInteractionServiceUid); 310 } 311 notifyOnDetectorRemoteException(); 312 } 313 } 314 }; 315 try { 316 service.updateState(options, sharedMemory, statusCallback); 317 if (getDetectorType() != HotwordDetector.DETECTOR_TYPE_VISUAL_QUERY_DETECTOR) { 318 HotwordMetricsLogger.writeDetectorEvent(getDetectorType(), 319 HOTWORD_DETECTOR_EVENTS__EVENT__REQUEST_UPDATE_STATE, 320 mVoiceInteractionServiceUid); 321 } 322 } catch (RemoteException e) { 323 // TODO: (b/181842909) Report an error to voice interactor 324 Slog.w(TAG, "Failed to updateState for HotwordDetectionService", e); 325 if (getDetectorType() != HotwordDetector.DETECTOR_TYPE_VISUAL_QUERY_DETECTOR) { 326 HotwordMetricsLogger.writeDetectorEvent(getDetectorType(), 327 HOTWORD_DETECTOR_EVENTS__EVENT__CALL_UPDATE_STATE_EXCEPTION, 328 mVoiceInteractionServiceUid); 329 } 330 } 331 return future.orTimeout(MAX_UPDATE_TIMEOUT_MILLIS, TimeUnit.MILLISECONDS); 332 }).whenComplete((res, err) -> { 333 if (err instanceof TimeoutException) { 334 Slog.w(TAG, "updateState timed out"); 335 if (mUpdateStateAfterStartFinished.getAndSet(true)) { 336 return; 337 } 338 try { 339 mCallback.onStatusReported(INITIALIZATION_STATUS_UNKNOWN); 340 if (getDetectorType() != HotwordDetector.DETECTOR_TYPE_VISUAL_QUERY_DETECTOR) { 341 HotwordMetricsLogger.writeServiceInitResultEvent(getDetectorType(), 342 METRICS_INIT_UNKNOWN_TIMEOUT, mVoiceInteractionServiceUid); 343 } 344 } catch (RemoteException e) { 345 Slog.w(TAG, "Failed to report initialization status UNKNOWN", e); 346 if (getDetectorType() != HotwordDetector.DETECTOR_TYPE_VISUAL_QUERY_DETECTOR) { 347 HotwordMetricsLogger.writeDetectorEvent(getDetectorType(), 348 METRICS_CALLBACK_ON_STATUS_REPORTED_EXCEPTION, 349 mVoiceInteractionServiceUid); 350 } 351 notifyOnDetectorRemoteException(); 352 } 353 } else if (err != null) { 354 Slog.w(TAG, "Failed to update state: " + err); 355 } 356 }); 357 if (voidFuture == null) { 358 Slog.w(TAG, "Failed to create AndroidFuture"); 359 } 360 } 361 getInitStatusAndMetricsResult(Bundle bundle)362 private static Pair<Integer, Integer> getInitStatusAndMetricsResult(Bundle bundle) { 363 if (bundle == null) { 364 return new Pair<>(INITIALIZATION_STATUS_UNKNOWN, METRICS_INIT_UNKNOWN_NO_VALUE); 365 } 366 int status = bundle.getInt(KEY_INITIALIZATION_STATUS, INITIALIZATION_STATUS_UNKNOWN); 367 if (status > HotwordDetectionService.getMaxCustomInitializationStatus()) { 368 return new Pair<>(INITIALIZATION_STATUS_UNKNOWN, 369 status == INITIALIZATION_STATUS_UNKNOWN 370 ? METRICS_INIT_UNKNOWN_NO_VALUE 371 : METRICS_INIT_UNKNOWN_OVER_MAX_CUSTOM_VALUE); 372 } 373 // TODO: should guard against negative here 374 int metricsResult = status == INITIALIZATION_STATUS_SUCCESS 375 ? METRICS_INIT_CALLBACK_STATE_SUCCESS 376 : METRICS_INIT_CALLBACK_STATE_ERROR; 377 return new Pair<>(status, metricsResult); 378 } 379 380 @SuppressWarnings("GuardedBy") updateStateLocked(PersistableBundle options, SharedMemory sharedMemory, Instant lastRestartInstant)381 void updateStateLocked(PersistableBundle options, SharedMemory sharedMemory, 382 Instant lastRestartInstant) { 383 if (getDetectorType() != HotwordDetector.DETECTOR_TYPE_VISUAL_QUERY_DETECTOR) { 384 HotwordMetricsLogger.writeDetectorEvent(getDetectorType(), 385 HOTWORD_DETECTOR_EVENTS__EVENT__APP_REQUEST_UPDATE_STATE, 386 mVoiceInteractionServiceUid); 387 } 388 // Prevent doing the init late, so restart is handled equally to a clean process start. 389 // TODO(b/191742511): this logic needs a test 390 if (!mUpdateStateAfterStartFinished.get() && Instant.now().minus( 391 MAX_UPDATE_TIMEOUT_DURATION).isBefore(lastRestartInstant)) { 392 Slog.v(TAG, "call updateStateAfterProcessStartLocked"); 393 updateStateAfterProcessStartLocked(options, sharedMemory); 394 } else { 395 mRemoteDetectionService.run( 396 service -> service.updateState(options, sharedMemory, /* callback= */ null)); 397 } 398 } 399 startListeningFromExternalSourceLocked( ParcelFileDescriptor audioStream, AudioFormat audioFormat, @Nullable PersistableBundle options, IMicrophoneHotwordDetectionVoiceInteractionCallback callback)400 void startListeningFromExternalSourceLocked( 401 ParcelFileDescriptor audioStream, 402 AudioFormat audioFormat, 403 @Nullable PersistableBundle options, 404 IMicrophoneHotwordDetectionVoiceInteractionCallback callback) { 405 if (DEBUG) { 406 Slog.d(TAG, "startListeningFromExternalSourceLocked"); 407 } 408 409 handleExternalSourceHotwordDetectionLocked( 410 audioStream, 411 audioFormat, 412 options, 413 callback, 414 /* shouldCloseAudioStreamWithDelayOnDetect= */ true, 415 /* shouldCheckPermissionsAndAppOpsOnDetected= */ true); 416 } 417 startListeningFromWearableLocked( ParcelFileDescriptor audioStream, AudioFormat audioFormat, PersistableBundle options, WearableHotwordDetectionCallback wearableCallback)418 void startListeningFromWearableLocked( 419 ParcelFileDescriptor audioStream, 420 AudioFormat audioFormat, 421 PersistableBundle options, 422 WearableHotwordDetectionCallback wearableCallback) { 423 if (DEBUG) { 424 Slog.d(TAG, "startListeningFromWearableLocked"); 425 } 426 IMicrophoneHotwordDetectionVoiceInteractionCallback voiceInteractionCallback = 427 new IMicrophoneHotwordDetectionVoiceInteractionCallback() { 428 @Override 429 public void onDetected( 430 HotwordDetectedResult hotwordDetectedResult, 431 AudioFormat audioFormatFromCallback, 432 ParcelFileDescriptor audioStreamFromCallback) { 433 wearableCallback.onDetected(); 434 try { 435 // This uses the DSP hotword code path to send the result to 436 // AlwaysOnHotwordDetector. DSP trigger and wearable trigger operates 437 // independently. 438 mCallback.onKeyphraseDetectedFromExternalSource(hotwordDetectedResult); 439 } catch (RemoteException ex) { 440 Slog.w( 441 TAG, 442 "RemoteException when sending HotwordDetectedResult to" 443 + " VoiceInteractionService.", 444 ex); 445 wearableCallback.onError( 446 "RemoteException when sending HotwordDetectedResult to" 447 + " VoiceInteractionService."); 448 notifyOnDetectorRemoteException(); 449 } 450 451 // Close the local copies of the file descriptors after sending them to 452 // another process. 453 for (HotwordAudioStream resultAudioStream : 454 hotwordDetectedResult.getAudioStreams()) { 455 try { 456 resultAudioStream.getAudioStreamParcelFileDescriptor().close(); 457 } catch (IOException ex) { 458 Slog.i( 459 TAG, 460 "Unable to close audio stream parcel file descriptor,", 461 ex); 462 } 463 } 464 } 465 466 @Override 467 public void onHotwordDetectionServiceFailure( 468 HotwordDetectionServiceFailure hotwordDetectionServiceFailure) { 469 wearableCallback.onError( 470 "onHotwordDetectionServiceFailure: " 471 + hotwordDetectionServiceFailure); 472 } 473 474 @Override 475 public void onRejected(HotwordRejectedResult hotwordRejectedResult) { 476 wearableCallback.onRejected(); 477 } 478 479 @Override 480 public IBinder asBinder() { 481 // This callback will only be used locally within the same process. 482 return null; 483 } 484 }; 485 /* 486 * By setting shouldCheckPermissionsAndAppOpsOnDetected to false, when the audio 487 * stream is sent from the sandboxed HotwordDetectionService to the non-sandboxed 488 * VoiceInteractionService as a result of second-stage hotword detection, audio-related 489 * permissions will not be checked against the VoiceInteractionService and the AppOpsManager 490 * will not be notified of the data flow to the VoiceInteractionService. These checks are 491 * not performed because the audio stream here originates from a remotely connected wearable 492 * device. It does not originate from the microphone of the device where this code runs on, 493 * or a microphone directly controlled by this system. Permission checks are expected to 494 * happen on the remote wearable device. From the perspective of this system, the audio 495 * stream is data received from an external source. 496 * 497 * Not notifying AppOpsManager allows this device's microphone indicator to remain off when 498 * this data flow happens. It avoids confusion since the audio does not originate from 499 * this device. The wearable is expected to turn on its own microphone indicator. 500 */ 501 handleExternalSourceHotwordDetectionLocked( 502 audioStream, 503 audioFormat, 504 options, 505 voiceInteractionCallback, 506 /* shouldCloseAudioStreamWithDelayOnDetect= */ false, 507 /* shouldCheckPermissionsAndAppOpsOnDetected= */ false); 508 } 509 510 @SuppressWarnings("GuardedBy") handleExternalSourceHotwordDetectionLocked( ParcelFileDescriptor audioStream, AudioFormat audioFormat, @Nullable PersistableBundle options, IMicrophoneHotwordDetectionVoiceInteractionCallback callback, boolean shouldCloseAudioStreamWithDelayOnDetect, boolean shouldCheckPermissionsAndAppOpsOnDetected)511 private void handleExternalSourceHotwordDetectionLocked( 512 ParcelFileDescriptor audioStream, 513 AudioFormat audioFormat, 514 @Nullable PersistableBundle options, 515 IMicrophoneHotwordDetectionVoiceInteractionCallback callback, 516 boolean shouldCloseAudioStreamWithDelayOnDetect, 517 boolean shouldCheckPermissionsAndAppOpsOnDetected) { 518 if (DEBUG) { 519 Slog.d(TAG, "#handleExternalSourceHotwordDetectionLocked"); 520 } 521 if (mPerformingExternalSourceHotwordDetection) { 522 Slog.i(TAG, "Hotword validation is already in progress for external source."); 523 return; 524 } 525 526 InputStream audioSource = new ParcelFileDescriptor.AutoCloseInputStream(audioStream); 527 528 Pair<ParcelFileDescriptor, ParcelFileDescriptor> clientPipe = createPipe(); 529 if (clientPipe == null) { 530 // TODO: Need to propagate as unknown error or something? 531 return; 532 } 533 ParcelFileDescriptor serviceAudioSink = clientPipe.second; 534 ParcelFileDescriptor serviceAudioSource = clientPipe.first; 535 536 mCurrentAudioSink = serviceAudioSink; 537 mPerformingExternalSourceHotwordDetection = true; 538 539 mAudioCopyExecutor.execute(() -> { 540 try (InputStream source = audioSource; 541 OutputStream fos = 542 new ParcelFileDescriptor.AutoCloseOutputStream(serviceAudioSink)) { 543 544 byte[] buffer = new byte[1024]; 545 while (true) { 546 int bytesRead = source.read(buffer, 0, 1024); 547 548 if (bytesRead < 0) { 549 Slog.i(TAG, "Reached end of stream for external hotword"); 550 break; 551 } 552 553 // TODO: First write to ring buffer to make sure we don't lose data if the next 554 // statement fails. 555 // ringBuffer.append(buffer, bytesRead); 556 fos.write(buffer, 0, bytesRead); 557 } 558 } catch (IOException e) { 559 Slog.w(TAG, "Failed supplying audio data to validator", e); 560 561 try { 562 callback.onHotwordDetectionServiceFailure( 563 new HotwordDetectionServiceFailure(ERROR_CODE_COPY_AUDIO_DATA_FAILURE, 564 "Copy audio data failure for external source detection.")); 565 } catch (RemoteException ex) { 566 Slog.w(TAG, "Failed to report onHotwordDetectionServiceFailure status: " + ex); 567 if (getDetectorType() != HotwordDetector.DETECTOR_TYPE_VISUAL_QUERY_DETECTOR) { 568 HotwordMetricsLogger.writeDetectorEvent(getDetectorType(), 569 HOTWORD_DETECTOR_EVENTS__EVENT__CALLBACK_ON_ERROR_EXCEPTION, 570 mVoiceInteractionServiceUid); 571 } 572 notifyOnDetectorRemoteException(); 573 } 574 } finally { 575 synchronized (mLock) { 576 mPerformingExternalSourceHotwordDetection = false; 577 closeExternalAudioStreamLocked("start external source"); 578 } 579 } 580 }); 581 582 // TODO: handle cancellations well 583 // TODO: what if we cancelled and started a new one? 584 mRemoteDetectionService.run( 585 service -> { 586 PersistableBundle optionsToSend = options; 587 if (android.app.wearable.Flags.enableHotwordWearableSensingApi()) { 588 if (optionsToSend == null) { 589 optionsToSend = new PersistableBundle(); 590 } 591 optionsToSend.putBoolean( 592 HotwordDetectionService 593 .KEY_SYSTEM_WILL_CLOSE_AUDIO_STREAM_AFTER_CALLBACK, 594 shouldCloseAudioStreamWithDelayOnDetect); 595 } 596 service.detectFromMicrophoneSource( 597 serviceAudioSource, 598 // TODO: consider making a proxy callback + copy of audio format 599 AUDIO_SOURCE_EXTERNAL, 600 audioFormat, 601 optionsToSend, 602 new IDspHotwordDetectionCallback.Stub() { 603 @Override 604 public void onRejected(HotwordRejectedResult result) 605 throws RemoteException { 606 synchronized (mLock) { 607 mPerformingExternalSourceHotwordDetection = false; 608 HotwordMetricsLogger.writeDetectorEvent( 609 getDetectorType(), 610 METRICS_EXTERNAL_SOURCE_REJECTED, 611 mVoiceInteractionServiceUid); 612 mScheduledExecutorService.schedule( 613 () -> { 614 bestEffortClose(serviceAudioSink, audioSource); 615 }, 616 EXTERNAL_HOTWORD_CLEANUP_MILLIS, 617 TimeUnit.MILLISECONDS); 618 619 try { 620 callback.onRejected(result); 621 } catch (RemoteException e) { 622 notifyOnDetectorRemoteException(); 623 throw e; 624 } 625 if (result != null) { 626 Slog.i(TAG, "Egressed 'hotword rejected result' " 627 + "from hotword trusted process"); 628 if (mDebugHotwordLogging) { 629 Slog.i(TAG, "Egressed detected result: " + result); 630 } 631 } 632 } 633 } 634 635 @Override 636 public void onDetected(HotwordDetectedResult triggerResult) 637 throws RemoteException { 638 synchronized (mLock) { 639 mPerformingExternalSourceHotwordDetection = false; 640 HotwordMetricsLogger.writeDetectorEvent( 641 getDetectorType(), 642 METRICS_EXTERNAL_SOURCE_DETECTED, 643 mVoiceInteractionServiceUid); 644 if (shouldCloseAudioStreamWithDelayOnDetect) { 645 mScheduledExecutorService.schedule( 646 () -> { 647 bestEffortClose( 648 serviceAudioSink, audioSource); 649 }, 650 EXTERNAL_HOTWORD_CLEANUP_MILLIS, 651 TimeUnit.MILLISECONDS); 652 } 653 if (shouldCheckPermissionsAndAppOpsOnDetected) { 654 try { 655 enforcePermissionsForDataDelivery(); 656 } catch (SecurityException e) { 657 Slog.w( 658 TAG, 659 "Ignoring #onDetected due to a " 660 + "SecurityException", 661 e); 662 HotwordMetricsLogger.writeDetectorEvent( 663 getDetectorType(), 664 EXTERNAL_SOURCE_DETECT_SECURITY_EXCEPTION, 665 mVoiceInteractionServiceUid); 666 try { 667 callback.onHotwordDetectionServiceFailure( 668 new HotwordDetectionServiceFailure( 669 ONDETECTED_GOT_SECURITY_EXCEPTION, 670 "Security exception occurs in " 671 + "#onDetected method")); 672 } catch (RemoteException e1) { 673 notifyOnDetectorRemoteException(); 674 throw e1; 675 } 676 return; 677 } 678 } 679 HotwordDetectedResult newResult; 680 try { 681 newResult = 682 mHotwordAudioStreamCopier 683 .startCopyingAudioStreams( 684 triggerResult, 685 shouldCheckPermissionsAndAppOpsOnDetected); 686 } catch (IOException e) { 687 Slog.w( 688 TAG, 689 "Ignoring #onDetected due to a " 690 + "IOException", 691 e); 692 // TODO: Write event 693 try { 694 callback.onHotwordDetectionServiceFailure( 695 new HotwordDetectionServiceFailure( 696 ONDETECTED_STREAM_COPY_ERROR, 697 "Copy audio stream failure.")); 698 } catch (RemoteException e1) { 699 notifyOnDetectorRemoteException(); 700 throw e1; 701 } 702 return; 703 } 704 try { 705 // The ParcelFileDescriptors in newResult might be 706 // closed after this call. Parcelling newResult can 707 // throw an exception 708 callback.onDetected( 709 newResult, 710 /* audioFormat= */ null, 711 /* audioStream= */ null); 712 } catch (RemoteException e) { 713 notifyOnDetectorRemoteException(); 714 throw e; 715 } 716 Slog.i(TAG, "Egressed " 717 + HotwordDetectedResult.getUsageSize(newResult) 718 + " bits from hotword trusted process"); 719 if (mDebugHotwordLogging) { 720 Slog.i(TAG, "Egressed detected result: " + newResult); 721 } 722 } 723 } 724 }); 725 726 // A copy of this has been created and passed to the hotword validator 727 bestEffortClose(serviceAudioSource); 728 }); 729 HotwordMetricsLogger.writeDetectorEvent(getDetectorType(), 730 HOTWORD_DETECTOR_EVENTS__EVENT__START_EXTERNAL_SOURCE_DETECTION, 731 mVoiceInteractionServiceUid); 732 } 733 initialize(@ullable PersistableBundle options, @Nullable SharedMemory sharedMemory)734 void initialize(@Nullable PersistableBundle options, @Nullable SharedMemory sharedMemory) { 735 synchronized (mLock) { 736 if (mInitialized || mDestroyed) { 737 return; 738 } 739 updateStateAfterProcessStartLocked(options, sharedMemory); 740 mInitialized = true; 741 } 742 } 743 744 @SuppressWarnings("GuardedBy") destroyLocked()745 void destroyLocked() { 746 mDestroyed = true; 747 mDebugHotwordLogging = false; 748 mRemoteDetectionService = null; 749 mRemoteExceptionListener = null; 750 if (mAttentionManagerInternal != null) { 751 mAttentionManagerInternal.onStopProximityUpdates(mProximityCallbackInternal); 752 } 753 } 754 setDebugHotwordLoggingLocked(boolean logging)755 void setDebugHotwordLoggingLocked(boolean logging) { 756 Slog.v(TAG, "setDebugHotwordLoggingLocked: " + logging); 757 mDebugHotwordLogging = logging; 758 } 759 760 @SuppressWarnings("GuardedBy") updateRemoteSandboxedDetectionServiceLocked( @onNull HotwordDetectionConnection.ServiceConnection remoteDetectionService)761 void updateRemoteSandboxedDetectionServiceLocked( 762 @NonNull HotwordDetectionConnection.ServiceConnection remoteDetectionService) { 763 mRemoteDetectionService = remoteDetectionService; 764 } 765 reportErrorGetRemoteException()766 private void reportErrorGetRemoteException() { 767 if (getDetectorType() != HotwordDetector.DETECTOR_TYPE_VISUAL_QUERY_DETECTOR) { 768 HotwordMetricsLogger.writeDetectorEvent(getDetectorType(), 769 HOTWORD_DETECTOR_EVENTS__EVENT__CALLBACK_ON_ERROR_EXCEPTION, 770 mVoiceInteractionServiceUid); 771 } 772 notifyOnDetectorRemoteException(); 773 } 774 reportErrorLocked(@onNull HotwordDetectionServiceFailure hotwordDetectionServiceFailure)775 void reportErrorLocked(@NonNull HotwordDetectionServiceFailure hotwordDetectionServiceFailure) { 776 try { 777 mCallback.onHotwordDetectionServiceFailure(hotwordDetectionServiceFailure); 778 } catch (RemoteException e) { 779 Slog.w(TAG, "Failed to call onHotwordDetectionServiceFailure: " + e); 780 reportErrorGetRemoteException(); 781 } 782 } 783 reportErrorLocked( @onNull VisualQueryDetectionServiceFailure visualQueryDetectionServiceFailure)784 void reportErrorLocked( 785 @NonNull VisualQueryDetectionServiceFailure visualQueryDetectionServiceFailure) { 786 try { 787 mCallback.onVisualQueryDetectionServiceFailure(visualQueryDetectionServiceFailure); 788 } catch (RemoteException e) { 789 Slog.w(TAG, "Failed to call onVisualQueryDetectionServiceFailure: " + e); 790 reportErrorGetRemoteException(); 791 } 792 } 793 reportErrorLocked(@onNull String errorMessage)794 void reportErrorLocked(@NonNull String errorMessage) { 795 try { 796 mCallback.onUnknownFailure(errorMessage); 797 } catch (RemoteException e) { 798 Slog.w(TAG, "Failed to call onUnknownFailure: " + e); 799 reportErrorGetRemoteException(); 800 } 801 } 802 803 /** 804 * Called when the trusted process is restarted. 805 */ informRestartProcessLocked()806 abstract void informRestartProcessLocked(); 807 isSameCallback(@ullable IHotwordRecognitionStatusCallback callback)808 boolean isSameCallback(@Nullable IHotwordRecognitionStatusCallback callback) { 809 synchronized (mLock) { 810 if (callback == null) { 811 return false; 812 } 813 return mCallback.asBinder().equals(callback.asBinder()); 814 } 815 } 816 isSameToken(@onNull IBinder token)817 boolean isSameToken(@NonNull IBinder token) { 818 synchronized (mLock) { 819 if (token == null) { 820 return false; 821 } 822 return mToken == token; 823 } 824 } 825 isDestroyed()826 boolean isDestroyed() { 827 synchronized (mLock) { 828 return mDestroyed; 829 } 830 } 831 createPipe()832 private static Pair<ParcelFileDescriptor, ParcelFileDescriptor> createPipe() { 833 ParcelFileDescriptor[] fileDescriptors; 834 try { 835 fileDescriptors = ParcelFileDescriptor.createPipe(); 836 } catch (IOException e) { 837 Slog.e(TAG, "Failed to create audio stream pipe", e); 838 return null; 839 } 840 841 return Pair.create(fileDescriptors[0], fileDescriptors[1]); 842 } 843 saveProximityValueToBundle(HotwordDetectedResult result)844 void saveProximityValueToBundle(HotwordDetectedResult result) { 845 synchronized (mLock) { 846 if (result != null && mProximityMeters != PROXIMITY_UNKNOWN) { 847 result.setProximity(mProximityMeters); 848 } 849 } 850 } 851 setProximityValue(double proximityMeters)852 private void setProximityValue(double proximityMeters) { 853 synchronized (mLock) { 854 mProximityMeters = proximityMeters; 855 } 856 } 857 858 @SuppressWarnings("GuardedBy") closeExternalAudioStreamLocked(String reason)859 void closeExternalAudioStreamLocked(String reason) { 860 if (mCurrentAudioSink != null) { 861 Slog.i(TAG, "Closing external audio stream to hotword detector: " + reason); 862 bestEffortClose(mCurrentAudioSink); 863 mCurrentAudioSink = null; 864 } 865 } 866 bestEffortClose(Closeable... closeables)867 private static void bestEffortClose(Closeable... closeables) { 868 for (Closeable closeable : closeables) { 869 bestEffortClose(closeable); 870 } 871 } 872 bestEffortClose(Closeable closeable)873 private static void bestEffortClose(Closeable closeable) { 874 try { 875 closeable.close(); 876 } catch (IOException e) { 877 if (DEBUG) { 878 Slog.w(TAG, "Failed closing", e); 879 } 880 } 881 } 882 883 // TODO: Share this code with SoundTriggerMiddlewarePermission. enforcePermissionsForDataDelivery()884 void enforcePermissionsForDataDelivery() { 885 Binder.withCleanCallingIdentity(() -> { 886 synchronized (mLock) { 887 if (AppOpsPolicy.isHotwordDetectionServiceRequired(mContext.getPackageManager())) { 888 int result = PermissionChecker.checkPermissionForPreflight( 889 mContext, RECORD_AUDIO, /* pid */ -1, mVoiceInteractorIdentity.uid, 890 mVoiceInteractorIdentity.packageName); 891 if (result != PermissionChecker.PERMISSION_GRANTED) { 892 throw new SecurityException( 893 "Failed to obtain permission RECORD_AUDIO for identity " 894 + mVoiceInteractorIdentity); 895 } 896 int opMode = mAppOpsManager.unsafeCheckOpNoThrow( 897 AppOpsManager.opToPublicName(AppOpsPolicy.getVoiceActivationOp()), 898 mVoiceInteractorIdentity.uid, 899 mVoiceInteractorIdentity.packageName); 900 if (opMode == MODE_DEFAULT || opMode == MODE_ALLOWED) { 901 mAppOpsManager.noteOpNoThrow( 902 AppOpsPolicy.getVoiceActivationOp(), 903 mVoiceInteractorIdentity.uid, mVoiceInteractorIdentity.packageName, 904 mVoiceInteractorIdentity.attributionTag, 905 HOTWORD_DETECTION_OP_MESSAGE); 906 } else { 907 throw new SecurityException( 908 "The app op OP_RECEIVE_SANDBOX_TRIGGER_AUDIO is denied for " 909 + "identity" + mVoiceInteractorIdentity); 910 } 911 } else { 912 enforcePermissionForDataDelivery(mContext, mVoiceInteractorIdentity, 913 RECORD_AUDIO, HOTWORD_DETECTION_OP_MESSAGE); 914 } 915 enforcePermissionForDataDelivery(mContext, mVoiceInteractorIdentity, 916 CAPTURE_AUDIO_HOTWORD, HOTWORD_DETECTION_OP_MESSAGE); 917 } 918 }); 919 } 920 921 /** 922 * Throws a {@link SecurityException} if the given identity has no permission to receive data. 923 * 924 * @param context A {@link Context}, used for permission checks. 925 * @param identity The identity to check. 926 * @param permission The identifier of the permission we want to check. 927 * @param reason The reason why we're requesting the permission, for auditing purposes. 928 */ enforcePermissionForDataDelivery(@onNull Context context, @NonNull Identity identity, @NonNull String permission, @NonNull String reason)929 protected static void enforcePermissionForDataDelivery(@NonNull Context context, 930 @NonNull Identity identity, @NonNull String permission, @NonNull String reason) { 931 final int status = PermissionUtil.checkPermissionForDataDelivery(context, identity, 932 permission, reason); 933 if (status != PermissionChecker.PERMISSION_GRANTED) { 934 throw new SecurityException( 935 TextUtils.formatSimple("Failed to obtain permission %s for identity %s", 936 permission, 937 identity)); 938 } 939 } 940 941 @RequiresPermission(allOf = {READ_COMPAT_CHANGE_CONFIG, LOG_COMPAT_CHANGE}) enforceExtraKeyphraseIdNotLeaked(HotwordDetectedResult result, SoundTrigger.KeyphraseRecognitionEvent recognitionEvent)942 void enforceExtraKeyphraseIdNotLeaked(HotwordDetectedResult result, 943 SoundTrigger.KeyphraseRecognitionEvent recognitionEvent) { 944 if (!CompatChanges.isChangeEnabled(ENFORCE_HOTWORD_PHRASE_ID, 945 mVoiceInteractionServiceUid)) { 946 return; 947 } 948 // verify the phrase ID in HotwordDetectedResult is not exposing extra phrases 949 // the DSP did not detect 950 for (SoundTrigger.KeyphraseRecognitionExtra keyphrase : recognitionEvent.keyphraseExtras) { 951 if (keyphrase.getKeyphraseId() == result.getHotwordPhraseId()) { 952 return; 953 } 954 } 955 throw new SecurityException("Ignoring #onDetected due to trusted service " 956 + "sharing a keyphrase ID which the DSP did not detect"); 957 } 958 getDetectorType()959 private int getDetectorType() { 960 if (this instanceof DspTrustedHotwordDetectorSession) { 961 return HotwordDetector.DETECTOR_TYPE_TRUSTED_HOTWORD_DSP; 962 } else if (this instanceof SoftwareTrustedHotwordDetectorSession) { 963 return HotwordDetector.DETECTOR_TYPE_TRUSTED_HOTWORD_SOFTWARE; 964 } else if (this instanceof VisualQueryDetectorSession) { 965 return HotwordDetector.DETECTOR_TYPE_VISUAL_QUERY_DETECTOR; 966 } 967 Slog.v(TAG, "Unexpected detector type"); 968 return -1; 969 } 970 971 @SuppressWarnings("GuardedBy") dumpLocked(String prefix, PrintWriter pw)972 public void dumpLocked(String prefix, PrintWriter pw) { 973 pw.print(prefix); pw.print("mCallback="); pw.println(mCallback); 974 pw.print(prefix); pw.print("mUpdateStateAfterStartFinished="); 975 pw.println(mUpdateStateAfterStartFinished); 976 pw.print(prefix); pw.print("mInitialized="); pw.println(mInitialized); 977 pw.print(prefix); pw.print("mDestroyed="); pw.println(mDestroyed); 978 pw.print(prefix); pw.print("DetectorType="); 979 pw.println(HotwordDetector.detectorTypeToString(getDetectorType())); 980 pw.print(prefix); pw.print("mPerformingExternalSourceHotwordDetection="); 981 pw.println(mPerformingExternalSourceHotwordDetection); 982 } 983 } 984