• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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