1 /*
2  * Copyright (C) 2021 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 com.android.internal.util.FrameworkStatsLog.HOTWORD_DETECTION_SERVICE_RESTARTED__REASON__AUDIO_SERVICE_DIED;
20 import static com.android.internal.util.FrameworkStatsLog.HOTWORD_DETECTION_SERVICE_RESTARTED__REASON__SCHEDULE;
21 import static com.android.internal.util.FrameworkStatsLog.HOTWORD_DETECTOR_EVENTS__EVENT__ON_CONNECTED;
22 import static com.android.internal.util.FrameworkStatsLog.HOTWORD_DETECTOR_EVENTS__EVENT__ON_DISCONNECTED;
23 import static com.android.internal.util.FrameworkStatsLog.HOTWORD_DETECTOR_EVENTS__EVENT__REQUEST_BIND_SERVICE;
24 import static com.android.internal.util.FrameworkStatsLog.HOTWORD_DETECTOR_EVENTS__EVENT__REQUEST_BIND_SERVICE_FAIL;
25 import static com.android.internal.util.FrameworkStatsLog.HOTWORD_DETECTOR_KEYPHRASE_TRIGGERED__DETECTOR_TYPE__NORMAL_DETECTOR;
26 import static com.android.internal.util.FrameworkStatsLog.HOTWORD_DETECTOR_KEYPHRASE_TRIGGERED__DETECTOR_TYPE__TRUSTED_DETECTOR_DSP;
27 import static com.android.internal.util.FrameworkStatsLog.HOTWORD_DETECTOR_KEYPHRASE_TRIGGERED__RESULT__KEYPHRASE_TRIGGER;
28 import static com.android.internal.util.FrameworkStatsLog.HOTWORD_DETECTOR_KEYPHRASE_TRIGGERED__RESULT__SERVICE_CRASH;
29 
30 import android.annotation.NonNull;
31 import android.annotation.Nullable;
32 import android.app.AppOpsManager;
33 import android.compat.annotation.ChangeId;
34 import android.compat.annotation.Disabled;
35 import android.content.ComponentName;
36 import android.content.ContentCaptureOptions;
37 import android.content.Context;
38 import android.content.Intent;
39 import android.hardware.soundtrigger.IRecognitionStatusCallback;
40 import android.hardware.soundtrigger.SoundTrigger;
41 import android.media.AudioFormat;
42 import android.media.AudioManagerInternal;
43 import android.media.permission.Identity;
44 import android.os.Binder;
45 import android.os.Bundle;
46 import android.os.IBinder;
47 import android.os.IRemoteCallback;
48 import android.os.ParcelFileDescriptor;
49 import android.os.PersistableBundle;
50 import android.os.RemoteException;
51 import android.os.ServiceManager;
52 import android.os.SharedMemory;
53 import android.os.SystemProperties;
54 import android.provider.DeviceConfig;
55 import android.service.voice.HotwordDetectionService;
56 import android.service.voice.HotwordDetectionServiceFailure;
57 import android.service.voice.HotwordDetector;
58 import android.service.voice.IDetectorSessionStorageService;
59 import android.service.voice.IMicrophoneHotwordDetectionVoiceInteractionCallback;
60 import android.service.voice.ISandboxedDetectionService;
61 import android.service.voice.IVisualQueryDetectionVoiceInteractionCallback;
62 import android.service.voice.SoundTriggerFailure;
63 import android.service.voice.VisualQueryDetectionService;
64 import android.service.voice.VisualQueryDetectionServiceFailure;
65 import android.service.voice.VoiceInteractionManagerInternal.HotwordDetectionServiceIdentity;
66 import android.speech.IRecognitionServiceManager;
67 import android.util.Slog;
68 import android.util.SparseArray;
69 import android.view.contentcapture.IContentCaptureManager;
70 
71 import com.android.internal.annotations.GuardedBy;
72 import com.android.internal.app.IHotwordRecognitionStatusCallback;
73 import com.android.internal.app.IVisualQueryDetectionAttentionListener;
74 import com.android.internal.infra.AndroidFuture;
75 import com.android.internal.infra.ServiceConnector;
76 import com.android.server.LocalServices;
77 import com.android.server.pm.permission.PermissionManagerServiceInternal;
78 import com.android.server.voiceinteraction.VoiceInteractionManagerServiceImpl.DetectorRemoteExceptionListener;
79 
80 import java.io.PrintWriter;
81 import java.time.Instant;
82 import java.util.concurrent.ScheduledFuture;
83 import java.util.concurrent.ScheduledThreadPoolExecutor;
84 import java.util.concurrent.TimeUnit;
85 import java.util.function.Consumer;
86 import java.util.function.Function;
87 
88 /**
89  * A class that provides the communication with the {@link HotwordDetectionService} and
90  * {@link VisualQueryDetectionService}.
91  */
92 final class HotwordDetectionConnection {
93     private static final String TAG = "HotwordDetectionConnection";
94     static final boolean DEBUG = false;
95 
96     /**
97      * Implementors of the HotwordDetectionService must not augment the phrase IDs which are
98      * supplied via HotwordDetectionService
99      * #onDetect(AlwaysOnHotwordDetector.EventPayload, long, HotwordDetectionService.Callback).
100      *
101      * <p>The HotwordDetectedResult#getHotwordPhraseId() must match one of the phrase IDs
102      * from the AlwaysOnHotwordDetector.EventPayload#getKeyphraseRecognitionExtras() list.
103      * </p>
104      *
105      * <p>This behavior change is made to ensure the HotwordDetectionService honors what
106      * it receives from the android.hardware.soundtrigger.SoundTriggerModule, and it
107      * cannot signal to the client application a phrase which was not originally detected.
108      * </p>
109      */
110     @ChangeId
111     @Disabled
112     public static final long ENFORCE_HOTWORD_PHRASE_ID = 215066299L;
113 
114     private static final String KEY_RESTART_PERIOD_IN_SECONDS = "restart_period_in_seconds";
115     private static final long RESET_DEBUG_HOTWORD_LOGGING_TIMEOUT_MILLIS = 60 * 60 * 1000; // 1 hour
116     private static final int MAX_ISOLATED_PROCESS_NUMBER = 10;
117 
118     private static final boolean SYSPROP_VISUAL_QUERY_SERVICE_ENABLED =
119             SystemProperties.getBoolean("ro.hotword.visual_query_service_enabled", false);
120 
121     /**
122      * Indicates the {@link HotwordDetectionService} is created.
123      */
124     private static final int DETECTION_SERVICE_TYPE_HOTWORD = 1;
125 
126     /**
127      * Indicates the {@link VisualQueryDetectionService} is created.
128      */
129     private static final int DETECTION_SERVICE_TYPE_VISUAL_QUERY = 2;
130 
131     // TODO: This may need to be a Handler(looper)
132     private final ScheduledThreadPoolExecutor mScheduledExecutorService =
133             new ScheduledThreadPoolExecutor(1);
134     @Nullable private final ScheduledFuture<?> mCancellationTaskFuture;
135     private final IBinder.DeathRecipient mAudioServerDeathRecipient = this::audioServerDied;
136     @NonNull private final ServiceConnectionFactory mHotwordDetectionServiceConnectionFactory;
137     @NonNull private final ServiceConnectionFactory mVisualQueryDetectionServiceConnectionFactory;
138     private int mDetectorType;
139     /**
140      * Time after which each HotwordDetectionService process is stopped and replaced by a new one.
141      * 0 indicates no restarts.
142      */
143     private final int mReStartPeriodSeconds;
144 
145     final Object mLock;
146     final int mVoiceInteractionServiceUid;
147     final ComponentName mHotwordDetectionComponentName;
148     final ComponentName mVisualQueryDetectionComponentName;
149     final int mUser;
150     final Context mContext;
151     volatile HotwordDetectionServiceIdentity mIdentity;
152     //TODO: Consider rename this to SandboxedDetectionIdentity
153     private Instant mLastRestartInstant;
154 
155     private ScheduledFuture<?> mDebugHotwordLoggingTimeoutFuture = null;
156 
157     /** Identity used for attributing app ops when delivering data to the Interactor. */
158     @GuardedBy("mLock")
159     @Nullable
160     private final Identity mVoiceInteractorIdentity;
161     private int mRestartCount = 0;
162     @NonNull private ServiceConnection mRemoteHotwordDetectionService;
163     @NonNull private ServiceConnection mRemoteVisualQueryDetectionService;
164     @GuardedBy("mLock")
165     @Nullable private IBinder mAudioFlinger;
166 
167     @Nullable private IHotwordRecognitionStatusCallback mHotwordRecognitionCallback;
168     @GuardedBy("mLock")
169     private boolean mDebugHotwordLogging = false;
170 
171     private DetectorRemoteExceptionListener mRemoteExceptionListener;
172 
173     /**
174      * For multiple detectors feature, we only support one AlwaysOnHotwordDetector and one
175      * SoftwareHotwordDetector at the same time. We use SparseArray with detector type as the key
176      * to record the detectors.
177      */
178     @GuardedBy("mLock")
179     private final SparseArray<DetectorSession> mDetectorSessions =
180             new SparseArray<>();
181 
HotwordDetectionConnection(Object lock, Context context, int voiceInteractionServiceUid, Identity voiceInteractorIdentity, ComponentName hotwordDetectionServiceName, ComponentName visualQueryDetectionServiceName, int userId, boolean bindInstantServiceAllowed, int detectorType, DetectorRemoteExceptionListener listener)182     HotwordDetectionConnection(Object lock, Context context, int voiceInteractionServiceUid,
183             Identity voiceInteractorIdentity, ComponentName hotwordDetectionServiceName,
184             ComponentName visualQueryDetectionServiceName, int userId,
185             boolean bindInstantServiceAllowed, int detectorType,
186             DetectorRemoteExceptionListener listener) {
187         mLock = lock;
188         mContext = context;
189         mVoiceInteractionServiceUid = voiceInteractionServiceUid;
190         mVoiceInteractorIdentity = voiceInteractorIdentity;
191         mHotwordDetectionComponentName = hotwordDetectionServiceName;
192         mVisualQueryDetectionComponentName = visualQueryDetectionServiceName;
193         mUser = userId;
194         mDetectorType = detectorType;
195         mRemoteExceptionListener = listener;
196         mReStartPeriodSeconds = DeviceConfig.getInt(DeviceConfig.NAMESPACE_VOICE_INTERACTION,
197                 KEY_RESTART_PERIOD_IN_SECONDS, 0);
198 
199         final Intent hotwordDetectionServiceIntent =
200                 new Intent(HotwordDetectionService.SERVICE_INTERFACE);
201         hotwordDetectionServiceIntent.setComponent(mHotwordDetectionComponentName);
202 
203         final Intent visualQueryDetectionServiceIntent =
204                 new Intent(VisualQueryDetectionService.SERVICE_INTERFACE);
205         visualQueryDetectionServiceIntent.setComponent(mVisualQueryDetectionComponentName);
206 
207         initAudioFlinger();
208 
209         mHotwordDetectionServiceConnectionFactory =
210                 new ServiceConnectionFactory(hotwordDetectionServiceIntent,
211                         bindInstantServiceAllowed, DETECTION_SERVICE_TYPE_HOTWORD);
212 
213         mVisualQueryDetectionServiceConnectionFactory =
214                 new ServiceConnectionFactory(visualQueryDetectionServiceIntent,
215                         bindInstantServiceAllowed, DETECTION_SERVICE_TYPE_VISUAL_QUERY);
216 
217 
218         mLastRestartInstant = Instant.now();
219 
220         if (mReStartPeriodSeconds <= 0) {
221             mCancellationTaskFuture = null;
222         } else {
223             mScheduledExecutorService.setRemoveOnCancelPolicy(true);
224             // TODO: we need to be smarter here, e.g. schedule it a bit more often,
225             //  but wait until the current session is closed.
226             mCancellationTaskFuture = mScheduledExecutorService.scheduleAtFixedRate(() -> {
227                 Slog.v(TAG, "Time to restart the process, TTL has passed");
228                 synchronized (mLock) {
229                     restartProcessLocked();
230                     if (mDetectorType != HotwordDetector.DETECTOR_TYPE_VISUAL_QUERY_DETECTOR) {
231                         HotwordMetricsLogger.writeServiceRestartEvent(mDetectorType,
232                                 HOTWORD_DETECTION_SERVICE_RESTARTED__REASON__SCHEDULE,
233                                 mVoiceInteractionServiceUid);
234                     }
235                 }
236             }, mReStartPeriodSeconds, mReStartPeriodSeconds, TimeUnit.SECONDS);
237         }
238     }
239 
initAudioFlinger()240     private void initAudioFlinger() {
241         if (DEBUG) {
242             Slog.d(TAG, "initAudioFlinger");
243         }
244         final IBinder audioFlinger = ServiceManager.waitForService("media.audio_flinger");
245         if (audioFlinger == null) {
246             setAudioFlinger(null);
247             throw new IllegalStateException("Service media.audio_flinger wasn't found.");
248         }
249         if (DEBUG) {
250             Slog.d(TAG, "Obtained audio_flinger binder.");
251         }
252         try {
253             audioFlinger.linkToDeath(mAudioServerDeathRecipient, /* flags= */ 0);
254         } catch (RemoteException e) {
255             Slog.w(TAG, "Audio server died before we registered a DeathRecipient; "
256                     + "retrying init.", e);
257             initAudioFlinger();
258             return;
259         }
260 
261         setAudioFlinger(audioFlinger);
262     }
263 
setAudioFlinger(@ullable IBinder audioFlinger)264     private void setAudioFlinger(@Nullable IBinder audioFlinger) {
265         synchronized (mLock) {
266             mAudioFlinger = audioFlinger;
267         }
268     }
269 
audioServerDied()270     private void audioServerDied() {
271         Slog.w(TAG, "Audio server died; restarting the HotwordDetectionService.");
272         // TODO: Check if this needs to be scheduled on a different thread.
273         initAudioFlinger();
274         synchronized (mLock) {
275             // We restart the process instead of simply sending over the new binder, to avoid race
276             // conditions with audio reading in the service.
277             restartProcessLocked();
278             if (mDetectorType != HotwordDetector.DETECTOR_TYPE_VISUAL_QUERY_DETECTOR) {
279                 HotwordMetricsLogger.writeServiceRestartEvent(mDetectorType,
280                         HOTWORD_DETECTION_SERVICE_RESTARTED__REASON__AUDIO_SERVICE_DIED,
281                         mVoiceInteractionServiceUid);
282             }
283         }
284     }
285 
286     @SuppressWarnings("GuardedBy")
cancelLocked()287     void cancelLocked() {
288         Slog.v(TAG, "cancelLocked");
289         clearDebugHotwordLoggingTimeoutLocked();
290         mRemoteExceptionListener = null;
291         runForEachDetectorSessionLocked((session) -> {
292             session.destroyLocked();
293         });
294         mDetectorSessions.clear();
295         mDebugHotwordLogging = false;
296         unbindVisualQueryDetectionService();
297         unbindHotwordDetectionService();
298         if (mCancellationTaskFuture != null) {
299             mCancellationTaskFuture.cancel(/* mayInterruptIfRunning= */ true);
300         }
301         if (mAudioFlinger != null) {
302             mAudioFlinger.unlinkToDeath(mAudioServerDeathRecipient, /* flags= */ 0);
303         }
304     }
305 
306     @SuppressWarnings("GuardedBy")
unbindVisualQueryDetectionService()307     private void unbindVisualQueryDetectionService() {
308         if (mRemoteVisualQueryDetectionService != null) {
309             mRemoteVisualQueryDetectionService.unbind();
310             mRemoteVisualQueryDetectionService = null;
311         }
312         resetDetectionProcessIdentityIfEmptyLocked();
313     }
314 
315     @SuppressWarnings("GuardedBy")
unbindHotwordDetectionService()316     private void unbindHotwordDetectionService() {
317         if (mRemoteHotwordDetectionService != null) {
318             mRemoteHotwordDetectionService.unbind();
319             mRemoteHotwordDetectionService = null;
320         }
321         resetDetectionProcessIdentityIfEmptyLocked();
322     }
323 
324     // TODO(b/266669849): Clean up SuppressWarnings for calling methods.
325     @GuardedBy("mLock")
resetDetectionProcessIdentityIfEmptyLocked()326     private void resetDetectionProcessIdentityIfEmptyLocked() {
327         if (mRemoteHotwordDetectionService == null && mRemoteVisualQueryDetectionService == null) {
328             LocalServices.getService(PermissionManagerServiceInternal.class)
329                 .setHotwordDetectionServiceProvider(null);
330             if (mIdentity != null) {
331                 removeServiceUidForAudioPolicy(mIdentity.getIsolatedUid());
332             }
333             mIdentity = null;
334         }
335     }
336 
337     @SuppressWarnings("GuardedBy")
updateStateLocked(PersistableBundle options, SharedMemory sharedMemory, @NonNull IBinder token)338     void updateStateLocked(PersistableBundle options, SharedMemory sharedMemory,
339             @NonNull IBinder token) {
340         final DetectorSession session = getDetectorSessionByTokenLocked(token);
341         if (session == null) {
342             Slog.v(TAG, "Not found the detector by token");
343             return;
344         }
345         session.updateStateLocked(options, sharedMemory, mLastRestartInstant);
346     }
347 
348     /**
349      * This method is only used by SoftwareHotwordDetector.
350      */
startListeningFromMicLocked( AudioFormat audioFormat, IMicrophoneHotwordDetectionVoiceInteractionCallback callback)351     void startListeningFromMicLocked(
352             AudioFormat audioFormat,
353             IMicrophoneHotwordDetectionVoiceInteractionCallback callback) {
354         if (DEBUG) {
355             Slog.d(TAG, "startListeningFromMicLocked");
356         }
357         // We only support one Dsp trusted hotword detector and one software hotword detector at
358         // the same time, so we can reuse original single software trusted hotword mechanism.
359         final SoftwareTrustedHotwordDetectorSession session =
360                 getSoftwareTrustedHotwordDetectorSessionLocked();
361         if (session == null) {
362             return;
363         }
364         session.startListeningFromMicLocked(audioFormat, callback);
365     }
366 
setVisualQueryDetectionAttentionListenerLocked( @ullable IVisualQueryDetectionAttentionListener listener)367     public void setVisualQueryDetectionAttentionListenerLocked(
368             @Nullable IVisualQueryDetectionAttentionListener listener) {
369         final VisualQueryDetectorSession session = getVisualQueryDetectorSessionLocked();
370         if (session == null) {
371             return;
372         }
373         session.setVisualQueryDetectionAttentionListenerLocked(listener);
374     }
375 
376     /**
377      * This method is only used by VisualQueryDetector.
378      */
startPerceivingLocked(IVisualQueryDetectionVoiceInteractionCallback callback)379     boolean startPerceivingLocked(IVisualQueryDetectionVoiceInteractionCallback callback) {
380         if (DEBUG) {
381             Slog.d(TAG, "startPerceivingLocked");
382         }
383         final VisualQueryDetectorSession session = getVisualQueryDetectorSessionLocked();
384         if (session == null) {
385             return false;
386         }
387         return session.startPerceivingLocked(callback);
388     }
389 
390     /**
391      * This method is only used by VisaulQueryDetector.
392      */
stopPerceivingLocked()393     boolean stopPerceivingLocked() {
394         if (DEBUG) {
395             Slog.d(TAG, "stopPerceivingLocked");
396         }
397         final VisualQueryDetectorSession session = getVisualQueryDetectorSessionLocked();
398         if (session == null) {
399             return false;
400         }
401         return session.stopPerceivingLocked();
402     }
403 
startListeningFromExternalSourceLocked( ParcelFileDescriptor audioStream, AudioFormat audioFormat, @Nullable PersistableBundle options, @NonNull IBinder token, IMicrophoneHotwordDetectionVoiceInteractionCallback callback)404     public void startListeningFromExternalSourceLocked(
405             ParcelFileDescriptor audioStream,
406             AudioFormat audioFormat,
407             @Nullable PersistableBundle options,
408             @NonNull IBinder token,
409             IMicrophoneHotwordDetectionVoiceInteractionCallback callback) {
410         if (DEBUG) {
411             Slog.d(TAG, "startListeningFromExternalSourceLocked");
412         }
413         final DetectorSession session = getDetectorSessionByTokenLocked(token);
414         if (session == null) {
415             Slog.v(TAG, "Not found the detector by token");
416             return;
417         }
418         session.startListeningFromExternalSourceLocked(audioStream, audioFormat, options, callback);
419     }
420 
421     /**
422      * This method is only used by SoftwareHotwordDetector.
423      */
stopListeningFromMicLocked()424     void stopListeningFromMicLocked() {
425         if (DEBUG) {
426             Slog.d(TAG, "stopListeningFromMicLocked");
427         }
428         final SoftwareTrustedHotwordDetectorSession session =
429                 getSoftwareTrustedHotwordDetectorSessionLocked();
430         if (session == null) {
431             return;
432         }
433         session.stopListeningFromMicLocked();
434     }
435 
triggerHardwareRecognitionEventForTestLocked( SoundTrigger.KeyphraseRecognitionEvent event, IHotwordRecognitionStatusCallback callback)436     void triggerHardwareRecognitionEventForTestLocked(
437             SoundTrigger.KeyphraseRecognitionEvent event,
438             IHotwordRecognitionStatusCallback callback) {
439         if (DEBUG) {
440             Slog.d(TAG, "triggerHardwareRecognitionEventForTestLocked");
441         }
442         detectFromDspSource(event, callback);
443     }
444 
detectFromDspSource(SoundTrigger.KeyphraseRecognitionEvent recognitionEvent, IHotwordRecognitionStatusCallback externalCallback)445     private void detectFromDspSource(SoundTrigger.KeyphraseRecognitionEvent recognitionEvent,
446             IHotwordRecognitionStatusCallback externalCallback) {
447         if (DEBUG) {
448             Slog.d(TAG, "detectFromDspSource");
449         }
450         // We only support one Dsp trusted hotword detector and one software hotword detector at
451         // the same time, so we can reuse original single Dsp trusted hotword mechanism.
452         synchronized (mLock) {
453             final DspTrustedHotwordDetectorSession session =
454                     getDspTrustedHotwordDetectorSessionLocked();
455             if (session == null || !session.isSameCallback(externalCallback)) {
456                 Slog.v(TAG, "Not found the Dsp detector by callback");
457                 return;
458             }
459             session.detectFromDspSourceLocked(recognitionEvent, externalCallback);
460         }
461     }
462 
forceRestart()463     void forceRestart() {
464         Slog.v(TAG, "Requested to restart the service internally. Performing the restart");
465         synchronized (mLock) {
466             restartProcessLocked();
467         }
468     }
469 
470     @SuppressWarnings("GuardedBy")
setDebugHotwordLoggingLocked(boolean logging)471     void setDebugHotwordLoggingLocked(boolean logging) {
472         Slog.v(TAG, "setDebugHotwordLoggingLocked: " + logging);
473         clearDebugHotwordLoggingTimeoutLocked();
474         mDebugHotwordLogging = logging;
475         runForEachDetectorSessionLocked((session) -> {
476             session.setDebugHotwordLoggingLocked(logging);
477         });
478 
479         if (logging) {
480             // Reset mDebugHotwordLogging to false after one hour
481             mDebugHotwordLoggingTimeoutFuture = mScheduledExecutorService.schedule(() -> {
482                 Slog.v(TAG, "Timeout to reset mDebugHotwordLogging to false");
483                 synchronized (mLock) {
484                     mDebugHotwordLogging = false;
485                     runForEachDetectorSessionLocked((session) -> {
486                         session.setDebugHotwordLoggingLocked(false);
487                     });
488                 }
489             }, RESET_DEBUG_HOTWORD_LOGGING_TIMEOUT_MILLIS, TimeUnit.MILLISECONDS);
490         }
491     }
492 
setDetectorType(int detectorType)493     void setDetectorType(int detectorType) {
494         mDetectorType = detectorType;
495     }
496 
clearDebugHotwordLoggingTimeoutLocked()497     private void clearDebugHotwordLoggingTimeoutLocked() {
498         if (mDebugHotwordLoggingTimeoutFuture != null) {
499             mDebugHotwordLoggingTimeoutFuture.cancel(/* mayInterruptIfRunning= */ true);
500             mDebugHotwordLoggingTimeoutFuture = null;
501         }
502     }
503 
504     @SuppressWarnings("GuardedBy")
restartProcessLocked()505     private void restartProcessLocked() {
506         // TODO(b/244598068): Check HotwordAudioStreamManager first
507         Slog.v(TAG, "Restarting hotword detection process");
508 
509         ServiceConnection oldHotwordConnection = mRemoteHotwordDetectionService;
510         ServiceConnection oldVisualQueryDetectionConnection = mRemoteVisualQueryDetectionService;
511         HotwordDetectionServiceIdentity previousIdentity = mIdentity;
512 
513         mLastRestartInstant = Instant.now();
514         // Recreate connection to reset the cache.
515         mRestartCount++;
516 
517         if (oldHotwordConnection != null) {
518             mRemoteHotwordDetectionService =
519                     mHotwordDetectionServiceConnectionFactory.createLocked();
520         }
521 
522         if (oldVisualQueryDetectionConnection != null) {
523             mRemoteVisualQueryDetectionService =
524                     mVisualQueryDetectionServiceConnectionFactory.createLocked();
525         }
526 
527         Slog.v(TAG, "Started the new process, dispatching processRestarted to detector");
528         runForEachDetectorSessionLocked((session) -> {
529             HotwordDetectionConnection.ServiceConnection newRemoteService =
530                     (session instanceof VisualQueryDetectorSession)
531                             ? mRemoteVisualQueryDetectionService : mRemoteHotwordDetectionService;
532             session.updateRemoteSandboxedDetectionServiceLocked(newRemoteService);
533             session.informRestartProcessLocked();
534         });
535         if (DEBUG) {
536             Slog.i(TAG, "processRestarted is dispatched done, unbinding from the old process");
537         }
538 
539         if (oldHotwordConnection != null) {
540             oldHotwordConnection.ignoreConnectionStatusEvents();
541             oldHotwordConnection.unbind();
542         }
543 
544         if (oldVisualQueryDetectionConnection != null) {
545             oldVisualQueryDetectionConnection.ignoreConnectionStatusEvents();
546             oldVisualQueryDetectionConnection.unbind();
547         }
548 
549         // TODO(b/266670431): Handles identity resetting for the new process to make sure the
550         // correct identity is provided.
551 
552         if (previousIdentity != null) {
553             removeServiceUidForAudioPolicy(previousIdentity.getIsolatedUid());
554         }
555     }
556 
557     static final class SoundTriggerCallback extends IRecognitionStatusCallback.Stub {
558         private final HotwordDetectionConnection mHotwordDetectionConnection;
559         private final IHotwordRecognitionStatusCallback mExternalCallback;
560         private final Identity mVoiceInteractorIdentity;
561         private final Context mContext;
562 
SoundTriggerCallback(Context context, IHotwordRecognitionStatusCallback callback, HotwordDetectionConnection connection, Identity voiceInteractorIdentity)563         SoundTriggerCallback(Context context, IHotwordRecognitionStatusCallback callback,
564                 HotwordDetectionConnection connection, Identity voiceInteractorIdentity) {
565             mContext = context;
566             mHotwordDetectionConnection = connection;
567             mExternalCallback = callback;
568             mVoiceInteractorIdentity = voiceInteractorIdentity;
569         }
570 
571         @Override
onKeyphraseDetected(SoundTrigger.KeyphraseRecognitionEvent recognitionEvent)572         public void onKeyphraseDetected(SoundTrigger.KeyphraseRecognitionEvent recognitionEvent)
573                 throws RemoteException {
574             if (DEBUG) {
575                 Slog.d(TAG, "onKeyphraseDetected recognitionEvent : " + recognitionEvent);
576             }
577             final boolean useHotwordDetectionService = mHotwordDetectionConnection != null;
578             if (useHotwordDetectionService) {
579                 HotwordMetricsLogger.writeKeyphraseTriggerEvent(
580                         HOTWORD_DETECTOR_KEYPHRASE_TRIGGERED__DETECTOR_TYPE__TRUSTED_DETECTOR_DSP,
581                         HOTWORD_DETECTOR_KEYPHRASE_TRIGGERED__RESULT__KEYPHRASE_TRIGGER,
582                         mVoiceInteractorIdentity.uid);
583                 mHotwordDetectionConnection.detectFromDspSource(
584                         recognitionEvent, mExternalCallback);
585             } else {
586                 // We have to attribute ops here, since we configure all st clients as trusted to
587                 // enable a partial exemption.
588                 // TODO (b/292012931) remove once trusted uniformly required.
589                 int result = mContext.getSystemService(AppOpsManager.class)
590                         .noteOpNoThrow(AppOpsManager.OP_RECORD_AUDIO_HOTWORD,
591                             mVoiceInteractorIdentity.uid, mVoiceInteractorIdentity.packageName,
592                             mVoiceInteractorIdentity.attributionTag,
593                             "Non-HDS keyphrase recognition to VoiceInteractionService");
594 
595                 if (result != AppOpsManager.MODE_ALLOWED) {
596                     Slog.w(TAG, "onKeyphraseDetected suppressed, permission check returned: "
597                             + result);
598                     mExternalCallback.onRecognitionPaused();
599                 } else {
600                     HotwordMetricsLogger.writeKeyphraseTriggerEvent(
601                             HOTWORD_DETECTOR_KEYPHRASE_TRIGGERED__DETECTOR_TYPE__NORMAL_DETECTOR,
602                             HOTWORD_DETECTOR_KEYPHRASE_TRIGGERED__RESULT__KEYPHRASE_TRIGGER,
603                             mVoiceInteractorIdentity.uid);
604                     mExternalCallback.onKeyphraseDetected(recognitionEvent, null);
605                 }
606             }
607         }
608 
609         @Override
onGenericSoundTriggerDetected( SoundTrigger.GenericRecognitionEvent recognitionEvent)610         public void onGenericSoundTriggerDetected(
611                 SoundTrigger.GenericRecognitionEvent recognitionEvent)
612                 throws RemoteException {
613             mExternalCallback.onGenericSoundTriggerDetected(recognitionEvent);
614         }
615 
616         @Override
onPreempted()617         public void onPreempted() throws RemoteException {
618             mExternalCallback.onSoundTriggerFailure(new SoundTriggerFailure(
619                         SoundTriggerFailure.ERROR_CODE_UNEXPECTED_PREEMPTION,
620                         "Unexpected startRecognition on already started ST session"));
621         }
622 
623         @Override
onModuleDied()624         public void onModuleDied() throws RemoteException {
625             mExternalCallback.onSoundTriggerFailure(new SoundTriggerFailure(
626                         SoundTriggerFailure.ERROR_CODE_MODULE_DIED,
627                         "STHAL died"));
628         }
629 
630         @Override
onResumeFailed(int status)631         public void onResumeFailed(int status) throws RemoteException {
632             mExternalCallback.onSoundTriggerFailure(new SoundTriggerFailure(
633                         SoundTriggerFailure.ERROR_CODE_RECOGNITION_RESUME_FAILED,
634                         "STService recognition resume failed with: " + status));
635         }
636 
637         @Override
onPauseFailed(int status)638         public void onPauseFailed(int status) throws RemoteException {
639             mExternalCallback.onSoundTriggerFailure(new SoundTriggerFailure(
640                         SoundTriggerFailure.ERROR_CODE_RECOGNITION_RESUME_FAILED,
641                         "STService recognition pause failed with: " + status));
642         }
643 
644         @Override
onRecognitionPaused()645         public void onRecognitionPaused() throws RemoteException {
646             mExternalCallback.onRecognitionPaused();
647         }
648 
649         @Override
onRecognitionResumed()650         public void onRecognitionResumed() throws RemoteException {
651             mExternalCallback.onRecognitionResumed();
652         }
653     }
654 
dump(String prefix, PrintWriter pw)655     public void dump(String prefix, PrintWriter pw) {
656         synchronized (mLock) {
657             pw.print(prefix); pw.print("mReStartPeriodSeconds="); pw.println(mReStartPeriodSeconds);
658             pw.print(prefix); pw.print("bound for HotwordDetectionService=");
659             pw.println(mRemoteHotwordDetectionService != null
660                     && mRemoteHotwordDetectionService.isBound());
661             pw.print(prefix); pw.print("bound for VisualQueryDetectionService=");
662             pw.println(mRemoteVisualQueryDetectionService != null
663                     && mRemoteHotwordDetectionService != null
664                     && mRemoteHotwordDetectionService.isBound());
665             pw.print(prefix); pw.print("mRestartCount=");
666             pw.println(mRestartCount);
667             pw.print(prefix); pw.print("mLastRestartInstant="); pw.println(mLastRestartInstant);
668             pw.print(prefix); pw.println("DetectorSession(s):");
669             pw.print(prefix); pw.print("Num of DetectorSession(s)=");
670             pw.println(mDetectorSessions.size());
671             runForEachDetectorSessionLocked((session) -> {
672                 session.dumpLocked(prefix, pw);
673             });
674         }
675     }
676 
677     private class ServiceConnectionFactory {
678         private final Intent mIntent;
679         private final int mBindingFlags;
680         private final int mDetectionServiceType;
681 
ServiceConnectionFactory(@onNull Intent intent, boolean bindInstantServiceAllowed, int detectionServiceType)682         ServiceConnectionFactory(@NonNull Intent intent, boolean bindInstantServiceAllowed,
683                 int detectionServiceType) {
684             mIntent = intent;
685             mDetectionServiceType = detectionServiceType;
686             int flags = bindInstantServiceAllowed ? Context.BIND_ALLOW_INSTANT : 0;
687             if (SYSPROP_VISUAL_QUERY_SERVICE_ENABLED
688                     && mVisualQueryDetectionComponentName != null
689                     && mHotwordDetectionComponentName != null) {
690                 flags |= Context.BIND_SHARED_ISOLATED_PROCESS;
691             }
692             mBindingFlags = flags;
693         }
694 
createLocked()695         ServiceConnection createLocked() {
696             ServiceConnection connection =
697                     new ServiceConnection(mContext, mIntent, mBindingFlags, mUser,
698                             ISandboxedDetectionService.Stub::asInterface,
699                             mRestartCount % MAX_ISOLATED_PROCESS_NUMBER, mDetectionServiceType);
700             connection.connect();
701 
702             updateAudioFlinger(connection, mAudioFlinger);
703             updateContentCaptureManager(connection);
704             updateSpeechService(connection);
705             updateServiceIdentity(connection);
706             updateStorageService(connection);
707             return connection;
708         }
709     }
710 
711     class ServiceConnection extends ServiceConnector.Impl<ISandboxedDetectionService> {
712         private final Object mLock = new Object();
713 
714         private final Intent mIntent;
715         private final int mBindingFlags;
716         private final int mInstanceNumber;
717 
718         private boolean mRespectServiceConnectionStatusChanged = true;
719         private boolean mIsBound = false;
720         private boolean mIsLoggedFirstConnect = false;
721         private final int mDetectionServiceType;
722 
ServiceConnection(@onNull Context context, @NonNull Intent serviceIntent, int bindingFlags, int userId, @Nullable Function<IBinder, ISandboxedDetectionService> binderAsInterface, int instanceNumber, int detectionServiceType)723         ServiceConnection(@NonNull Context context,
724                 @NonNull Intent serviceIntent, int bindingFlags, int userId,
725                 @Nullable Function<IBinder, ISandboxedDetectionService> binderAsInterface,
726                 int instanceNumber, int detectionServiceType) {
727             super(context, serviceIntent, bindingFlags, userId, binderAsInterface);
728             this.mIntent = serviceIntent;
729             this.mBindingFlags = bindingFlags;
730             this.mInstanceNumber = instanceNumber;
731             this.mDetectionServiceType = detectionServiceType;
732         }
733 
734         @Override // from ServiceConnector.Impl
onServiceConnectionStatusChanged(ISandboxedDetectionService service, boolean connected)735         protected void onServiceConnectionStatusChanged(ISandboxedDetectionService service,
736                 boolean connected) {
737             if (DEBUG) {
738                 Slog.d(TAG, "onServiceConnectionStatusChanged connected = " + connected);
739             }
740             synchronized (mLock) {
741                 if (!mRespectServiceConnectionStatusChanged) {
742                     Slog.v(TAG, "Ignored onServiceConnectionStatusChanged event");
743                     return;
744                 }
745                 mIsBound = connected;
746 
747                 if (!connected) {
748                     if (mDetectionServiceType != DETECTION_SERVICE_TYPE_VISUAL_QUERY) {
749                         HotwordMetricsLogger.writeDetectorEvent(mDetectorType,
750                                 HOTWORD_DETECTOR_EVENTS__EVENT__ON_DISCONNECTED,
751                                 mVoiceInteractionServiceUid);
752                     }
753                 } else if (!mIsLoggedFirstConnect) {
754                     mIsLoggedFirstConnect = true;
755                     if (mDetectionServiceType != DETECTION_SERVICE_TYPE_VISUAL_QUERY) {
756                         HotwordMetricsLogger.writeDetectorEvent(mDetectorType,
757                                 HOTWORD_DETECTOR_EVENTS__EVENT__ON_CONNECTED,
758                                 mVoiceInteractionServiceUid);
759                     }
760                 }
761             }
762         }
763 
764         @Override
getAutoDisconnectTimeoutMs()765         protected long getAutoDisconnectTimeoutMs() {
766             return -1;
767         }
768 
769         @Override
binderDied()770         public void binderDied() {
771             super.binderDied();
772             Slog.w(TAG, "binderDied mDetectionServiceType = " + mDetectionServiceType);
773             synchronized (mLock) {
774                 if (!mRespectServiceConnectionStatusChanged) {
775                     Slog.v(TAG, "Ignored #binderDied event");
776                     return;
777                 }
778             }
779             //TODO(b265535257): report error to either service only.
780             synchronized (HotwordDetectionConnection.this.mLock) {
781                 runForEachDetectorSessionLocked(this::reportBinderDiedLocked);
782             }
783             // Can improve to log exit reason if needed
784             if (mDetectionServiceType != DETECTION_SERVICE_TYPE_VISUAL_QUERY) {
785                 HotwordMetricsLogger.writeKeyphraseTriggerEvent(
786                         mDetectorType,
787                         HOTWORD_DETECTOR_KEYPHRASE_TRIGGERED__RESULT__SERVICE_CRASH,
788                         mVoiceInteractionServiceUid);
789             }
790         }
791 
792         @Override
bindService( @onNull android.content.ServiceConnection serviceConnection)793         protected boolean bindService(
794                 @NonNull android.content.ServiceConnection serviceConnection) {
795             try {
796                 if (mDetectionServiceType != DETECTION_SERVICE_TYPE_VISUAL_QUERY) {
797                     HotwordMetricsLogger.writeDetectorEvent(mDetectorType,
798                             HOTWORD_DETECTOR_EVENTS__EVENT__REQUEST_BIND_SERVICE,
799                             mVoiceInteractionServiceUid);
800                 }
801                 boolean bindResult = mContext.bindIsolatedService(
802                         mIntent,
803                         Context.BIND_AUTO_CREATE | Context.BIND_FOREGROUND_SERVICE | mBindingFlags,
804                         "hotword_detector_" + mInstanceNumber,
805                         mExecutor,
806                         serviceConnection);
807                 if (!bindResult) {
808                     Slog.w(TAG,
809                             "bindService failure mDetectionServiceType = " + mDetectionServiceType);
810                     synchronized (HotwordDetectionConnection.this.mLock) {
811                         runForEachDetectorSessionLocked(this::reportBindServiceFailureLocked);
812                     }
813                     if (mDetectionServiceType != DETECTION_SERVICE_TYPE_VISUAL_QUERY) {
814                         HotwordMetricsLogger.writeDetectorEvent(mDetectorType,
815                                 HOTWORD_DETECTOR_EVENTS__EVENT__REQUEST_BIND_SERVICE_FAIL,
816                                 mVoiceInteractionServiceUid);
817                     }
818                 }
819                 return bindResult;
820             } catch (IllegalArgumentException e) {
821                 if (mDetectionServiceType != DETECTION_SERVICE_TYPE_VISUAL_QUERY) {
822                     HotwordMetricsLogger.writeDetectorEvent(mDetectorType,
823                             HOTWORD_DETECTOR_EVENTS__EVENT__REQUEST_BIND_SERVICE_FAIL,
824                             mVoiceInteractionServiceUid);
825                 }
826                 Slog.wtf(TAG, "Can't bind to the hotword detection service!", e);
827                 return false;
828             }
829         }
830 
isBound()831         boolean isBound() {
832             synchronized (mLock) {
833                 return mIsBound;
834             }
835         }
836 
ignoreConnectionStatusEvents()837         void ignoreConnectionStatusEvents() {
838             synchronized (mLock) {
839                 mRespectServiceConnectionStatusChanged = false;
840             }
841         }
842 
reportBinderDiedLocked(DetectorSession detectorSession)843         private void reportBinderDiedLocked(DetectorSession detectorSession) {
844             if (mDetectionServiceType == DETECTION_SERVICE_TYPE_HOTWORD && (
845                     detectorSession instanceof DspTrustedHotwordDetectorSession
846                             || detectorSession instanceof SoftwareTrustedHotwordDetectorSession)) {
847                 detectorSession.reportErrorLocked(new HotwordDetectionServiceFailure(
848                         HotwordDetectionServiceFailure.ERROR_CODE_BINDING_DIED,
849                         "Detection service is dead."));
850             } else if (mDetectionServiceType == DETECTION_SERVICE_TYPE_VISUAL_QUERY
851                     && detectorSession instanceof VisualQueryDetectorSession) {
852                 detectorSession.reportErrorLocked(new VisualQueryDetectionServiceFailure(
853                         VisualQueryDetectionServiceFailure.ERROR_CODE_BINDING_DIED,
854                         "Detection service is dead."));
855             } else {
856                 detectorSession.reportErrorLocked(
857                         "Detection service is dead with unknown detection service type.");
858             }
859         }
860 
reportBindServiceFailureLocked(DetectorSession detectorSession)861         private void reportBindServiceFailureLocked(DetectorSession detectorSession) {
862             if (mDetectionServiceType == DETECTION_SERVICE_TYPE_HOTWORD && (
863                     detectorSession instanceof DspTrustedHotwordDetectorSession
864                             || detectorSession instanceof SoftwareTrustedHotwordDetectorSession)) {
865                 detectorSession.reportErrorLocked(new HotwordDetectionServiceFailure(
866                         HotwordDetectionServiceFailure.ERROR_CODE_BIND_FAILURE,
867                         "Bind detection service failure."));
868             } else if (mDetectionServiceType == DETECTION_SERVICE_TYPE_VISUAL_QUERY
869                     && detectorSession instanceof VisualQueryDetectorSession) {
870                 detectorSession.reportErrorLocked(new VisualQueryDetectionServiceFailure(
871                         VisualQueryDetectionServiceFailure.ERROR_CODE_BIND_FAILURE,
872                         "Bind detection service failure."));
873             } else {
874                 detectorSession.reportErrorLocked(
875                         "Bind detection service failure with unknown detection service type.");
876             }
877         }
878     }
879 
880     @SuppressWarnings("GuardedBy")
createDetectorLocked( @ullable PersistableBundle options, @Nullable SharedMemory sharedMemory, @NonNull IBinder token, @NonNull IHotwordRecognitionStatusCallback callback, int detectorType)881     void createDetectorLocked(
882             @Nullable PersistableBundle options,
883             @Nullable SharedMemory sharedMemory,
884             @NonNull IBinder token,
885             @NonNull IHotwordRecognitionStatusCallback callback,
886             int detectorType) {
887         // We only support one Dsp trusted hotword detector and one software hotword detector at
888         // the same time, remove existing one.
889         DetectorSession removeSession = mDetectorSessions.get(detectorType);
890         if (removeSession != null) {
891             removeSession.destroyLocked();
892             mDetectorSessions.remove(detectorType);
893         }
894         final DetectorSession session;
895         if (detectorType == HotwordDetector.DETECTOR_TYPE_TRUSTED_HOTWORD_DSP) {
896             if (mRemoteHotwordDetectionService == null) {
897                 mRemoteHotwordDetectionService =
898                         mHotwordDetectionServiceConnectionFactory.createLocked();
899             }
900             session = new DspTrustedHotwordDetectorSession(mRemoteHotwordDetectionService,
901                     mLock, mContext, token, callback, mVoiceInteractionServiceUid,
902                     mVoiceInteractorIdentity, mScheduledExecutorService, mDebugHotwordLogging,
903                     mRemoteExceptionListener);
904         } else if (detectorType == HotwordDetector.DETECTOR_TYPE_VISUAL_QUERY_DETECTOR) {
905             if (mRemoteVisualQueryDetectionService == null) {
906                 mRemoteVisualQueryDetectionService =
907                         mVisualQueryDetectionServiceConnectionFactory.createLocked();
908             }
909             session = new VisualQueryDetectorSession(
910                     mRemoteVisualQueryDetectionService, mLock, mContext, token, callback,
911                     mVoiceInteractionServiceUid, mVoiceInteractorIdentity,
912                     mScheduledExecutorService, mDebugHotwordLogging, mRemoteExceptionListener);
913         } else {
914             if (mRemoteHotwordDetectionService == null) {
915                 mRemoteHotwordDetectionService =
916                         mHotwordDetectionServiceConnectionFactory.createLocked();
917             }
918             session = new SoftwareTrustedHotwordDetectorSession(
919                     mRemoteHotwordDetectionService, mLock, mContext, token, callback,
920                     mVoiceInteractionServiceUid, mVoiceInteractorIdentity,
921                     mScheduledExecutorService, mDebugHotwordLogging, mRemoteExceptionListener);
922         }
923         mHotwordRecognitionCallback = callback;
924         mDetectorSessions.put(detectorType, session);
925         session.initialize(options, sharedMemory);
926     }
927 
928     @SuppressWarnings("GuardedBy")
destroyDetectorLocked(@onNull IBinder token)929     void destroyDetectorLocked(@NonNull IBinder token) {
930         final DetectorSession session = getDetectorSessionByTokenLocked(token);
931         if (session == null) {
932             return;
933         }
934         session.destroyLocked();
935         final int index = mDetectorSessions.indexOfValue(session);
936         if (index < 0 || index > mDetectorSessions.size() - 1) {
937             return;
938         }
939         mDetectorSessions.removeAt(index);
940         if (session instanceof VisualQueryDetectorSession) {
941             unbindVisualQueryDetectionService();
942         }
943         // Handle case where all hotword detector sessions are destroyed with only the visual
944         // detector session left
945         boolean allHotwordDetectionServiceSessionsRemoved = mDetectorSessions.size() == 0
946                 || (mDetectorSessions.size() == 1 && mDetectorSessions.get(0)
947                 instanceof VisualQueryDetectorSession);
948         if (allHotwordDetectionServiceSessionsRemoved) {
949             unbindHotwordDetectionService();
950         }
951     }
952 
953     @SuppressWarnings("GuardedBy")
getDetectorSessionByTokenLocked(IBinder token)954     private DetectorSession getDetectorSessionByTokenLocked(IBinder token) {
955         if (token == null) {
956             return null;
957         }
958         for (int i = 0; i < mDetectorSessions.size(); i++) {
959             final DetectorSession session = mDetectorSessions.valueAt(i);
960             if (!session.isDestroyed() && session.isSameToken(token)) {
961                 return session;
962             }
963         }
964         return null;
965     }
966 
967     @SuppressWarnings("GuardedBy")
getDspTrustedHotwordDetectorSessionLocked()968     private DspTrustedHotwordDetectorSession getDspTrustedHotwordDetectorSessionLocked() {
969         final DetectorSession session = mDetectorSessions.get(
970                 HotwordDetector.DETECTOR_TYPE_TRUSTED_HOTWORD_DSP);
971         if (session == null || session.isDestroyed()) {
972             Slog.v(TAG, "Not found the Dsp detector");
973             return null;
974         }
975         return (DspTrustedHotwordDetectorSession) session;
976     }
977 
978     @SuppressWarnings("GuardedBy")
getSoftwareTrustedHotwordDetectorSessionLocked()979     private SoftwareTrustedHotwordDetectorSession getSoftwareTrustedHotwordDetectorSessionLocked() {
980         final DetectorSession session = mDetectorSessions.get(
981                 HotwordDetector.DETECTOR_TYPE_TRUSTED_HOTWORD_SOFTWARE);
982         if (session == null || session.isDestroyed()) {
983             Slog.v(TAG, "Not found the software detector");
984             return null;
985         }
986         return (SoftwareTrustedHotwordDetectorSession) session;
987     }
988 
989     @SuppressWarnings("GuardedBy")
getVisualQueryDetectorSessionLocked()990     private VisualQueryDetectorSession getVisualQueryDetectorSessionLocked() {
991         final DetectorSession session = mDetectorSessions.get(
992                 HotwordDetector.DETECTOR_TYPE_VISUAL_QUERY_DETECTOR);
993         if (session == null || session.isDestroyed()) {
994             Slog.v(TAG, "Not found the visual query detector");
995             return null;
996         }
997         return (VisualQueryDetectorSession) session;
998     }
runForEachDetectorSessionLocked( @onNull Consumer<DetectorSession> action)999     private void runForEachDetectorSessionLocked(
1000             @NonNull Consumer<DetectorSession> action) {
1001         for (int i = 0; i < mDetectorSessions.size(); i++) {
1002             DetectorSession session = mDetectorSessions.valueAt(i);
1003             action.accept(session);
1004         }
1005     }
1006 
updateAudioFlinger(ServiceConnection connection, IBinder audioFlinger)1007     private static void updateAudioFlinger(ServiceConnection connection, IBinder audioFlinger) {
1008         // TODO: Consider using a proxy that limits the exposed API surface.
1009         connection.run(service -> service.updateAudioFlinger(audioFlinger));
1010     }
1011 
updateContentCaptureManager(ServiceConnection connection)1012     private static void updateContentCaptureManager(ServiceConnection connection) {
1013         IBinder b = ServiceManager
1014                 .getService(Context.CONTENT_CAPTURE_MANAGER_SERVICE);
1015         IContentCaptureManager binderService = IContentCaptureManager.Stub.asInterface(b);
1016         connection.run(
1017                 service -> service.updateContentCaptureManager(binderService,
1018                         new ContentCaptureOptions(null)));
1019     }
1020 
updateSpeechService(ServiceConnection connection)1021     private static void updateSpeechService(ServiceConnection connection) {
1022         IBinder b = ServiceManager.getService(Context.SPEECH_RECOGNITION_SERVICE);
1023         IRecognitionServiceManager binderService = IRecognitionServiceManager.Stub.asInterface(b);
1024         connection.run(service -> {
1025             service.updateRecognitionServiceManager(binderService);
1026         });
1027     }
1028 
updateServiceIdentity(ServiceConnection connection)1029     private void updateServiceIdentity(ServiceConnection connection) {
1030         connection.run(service -> service.ping(new IRemoteCallback.Stub() {
1031             @Override
1032             public void sendResult(Bundle bundle) throws RemoteException {
1033                 // TODO: Exit if the service has been unbound already (though there's a very low
1034                 // chance this happens).
1035                 if (DEBUG) {
1036                     Slog.d(TAG, "updating hotword UID " + Binder.getCallingUid());
1037                 }
1038                 // TODO: Have the provider point to the current state stored in
1039                 // VoiceInteractionManagerServiceImpl.
1040                 final int uid = Binder.getCallingUid();
1041                 LocalServices.getService(PermissionManagerServiceInternal.class)
1042                         .setHotwordDetectionServiceProvider(() -> uid);
1043                 mIdentity = new HotwordDetectionServiceIdentity(uid, mVoiceInteractionServiceUid);
1044                 addServiceUidForAudioPolicy(uid);
1045             }
1046         }));
1047     }
1048 
updateStorageService(ServiceConnection connection)1049     private void updateStorageService(ServiceConnection connection) {
1050         connection.run(service -> {
1051             service.registerRemoteStorageService(new IDetectorSessionStorageService.Stub() {
1052                 @Override
1053                 public void openFile(String filename, AndroidFuture future)
1054                         throws RemoteException {
1055                     Slog.v(TAG, "BinderCallback#onFileOpen");
1056                     try {
1057                         mHotwordRecognitionCallback.onOpenFile(filename, future);
1058                     } catch (RemoteException e) {
1059                         e.rethrowFromSystemServer();
1060                     }
1061                 }
1062             });
1063         });
1064     }
1065 
addServiceUidForAudioPolicy(int uid)1066     private void addServiceUidForAudioPolicy(int uid) {
1067         mScheduledExecutorService.execute(() -> {
1068             AudioManagerInternal audioManager =
1069                     LocalServices.getService(AudioManagerInternal.class);
1070             if (audioManager != null) {
1071                 audioManager.addAssistantServiceUid(uid);
1072             }
1073         });
1074     }
1075 
removeServiceUidForAudioPolicy(int uid)1076     private void removeServiceUidForAudioPolicy(int uid) {
1077         mScheduledExecutorService.execute(() -> {
1078             AudioManagerInternal audioManager =
1079                     LocalServices.getService(AudioManagerInternal.class);
1080             if (audioManager != null) {
1081                 audioManager.removeAssistantServiceUid(uid);
1082             }
1083         });
1084     }
1085 }
1086