• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2014 The Android Open Source Project
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *      http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 
17 package com.android.server.voiceinteraction;
18 
19 import static android.app.ActivityManager.START_ASSISTANT_HIDDEN_SESSION;
20 import static android.app.ActivityManager.START_ASSISTANT_NOT_ACTIVE_SESSION;
21 import static android.app.ActivityManager.START_VOICE_HIDDEN_SESSION;
22 import static android.app.ActivityManager.START_VOICE_NOT_ACTIVE_SESSION;
23 import static android.app.WindowConfiguration.ACTIVITY_TYPE_ASSISTANT;
24 import static android.service.voice.VoiceInteractionSession.KEY_SHOW_SESSION_ID;
25 
26 import static com.android.server.policy.PhoneWindowManager.SYSTEM_DIALOG_REASON_ASSIST;
27 
28 import android.Manifest;
29 import android.annotation.NonNull;
30 import android.annotation.Nullable;
31 import android.app.ActivityManager;
32 import android.app.ActivityTaskManager;
33 import android.app.AppGlobals;
34 import android.app.ApplicationExitInfo;
35 import android.app.IActivityManager;
36 import android.app.IActivityTaskManager;
37 import android.content.BroadcastReceiver;
38 import android.content.ComponentName;
39 import android.content.ContentResolver;
40 import android.content.Context;
41 import android.content.Intent;
42 import android.content.IntentFilter;
43 import android.content.ServiceConnection;
44 import android.content.pm.ApplicationInfo;
45 import android.content.pm.PackageManager;
46 import android.content.pm.PackageManagerInternal;
47 import android.content.pm.ParceledListSlice;
48 import android.content.pm.ServiceInfo;
49 import android.database.ContentObserver;
50 import android.hardware.soundtrigger.IRecognitionStatusCallback;
51 import android.hardware.soundtrigger.SoundTrigger;
52 import android.media.AudioFormat;
53 import android.media.permission.Identity;
54 import android.net.Uri;
55 import android.os.Bundle;
56 import android.os.Handler;
57 import android.os.IBinder;
58 import android.os.ParcelFileDescriptor;
59 import android.os.PersistableBundle;
60 import android.os.RemoteCallback;
61 import android.os.RemoteException;
62 import android.os.ServiceManager;
63 import android.os.SharedMemory;
64 import android.os.SystemProperties;
65 import android.os.UserHandle;
66 import android.provider.Settings;
67 import android.service.voice.HotwordDetector;
68 import android.service.voice.IMicrophoneHotwordDetectionVoiceInteractionCallback;
69 import android.service.voice.IVisualQueryDetectionVoiceInteractionCallback;
70 import android.service.voice.IVoiceInteractionService;
71 import android.service.voice.IVoiceInteractionSession;
72 import android.service.voice.VoiceInteractionManagerInternal.WearableHotwordDetectionCallback;
73 import android.service.voice.VoiceInteractionService;
74 import android.service.voice.VoiceInteractionServiceInfo;
75 import android.system.OsConstants;
76 import android.text.TextUtils;
77 import android.util.PrintWriterPrinter;
78 import android.util.Slog;
79 import android.view.IWindowManager;
80 
81 import com.android.internal.app.IHotwordRecognitionStatusCallback;
82 import com.android.internal.app.IVisualQueryDetectionAttentionListener;
83 import com.android.internal.app.IVoiceActionCheckCallback;
84 import com.android.internal.app.IVoiceInteractionAccessibilitySettingsListener;
85 import com.android.internal.app.IVoiceInteractionSessionShowCallback;
86 import com.android.internal.app.IVoiceInteractor;
87 import com.android.internal.util.function.pooled.PooledLambda;
88 import com.android.server.LocalServices;
89 import com.android.server.wm.ActivityAssistInfo;
90 import com.android.server.wm.ActivityTaskManagerInternal;
91 import com.android.server.wm.ActivityTaskManagerInternal.ActivityTokens;
92 
93 import java.io.FileDescriptor;
94 import java.io.PrintWriter;
95 import java.util.ArrayList;
96 import java.util.List;
97 import java.util.Objects;
98 
99 class VoiceInteractionManagerServiceImpl implements VoiceInteractionSessionConnection.Callback {
100     final static String TAG = "VoiceInteractionServiceManager";
101     static final boolean DEBUG = false;
102 
103     final static String CLOSE_REASON_VOICE_INTERACTION = "voiceinteraction";
104 
105     /** The delay time for retrying to request DirectActions. */
106     private static final long REQUEST_DIRECT_ACTIONS_RETRY_TIME_MS = 200;
107     private static final boolean SYSPROP_VISUAL_QUERY_SERVICE_ENABLED =
108             SystemProperties.getBoolean("ro.hotword.visual_query_service_enabled", false);
109 
110     final boolean mValid;
111 
112     final Context mContext;
113     final Handler mHandler;
114     final Handler mDirectActionsHandler;
115     final VoiceInteractionManagerService.VoiceInteractionManagerServiceStub mServiceStub;
116     final int mUser;
117     final ComponentName mComponent;
118     final IActivityManager mAm;
119     final IActivityTaskManager mAtm;
120     final PackageManagerInternal mPackageManagerInternal;
121     final VoiceInteractionServiceInfo mInfo;
122     final ComponentName mSessionComponentName;
123     final IWindowManager mIWindowManager;
124     final ComponentName mHotwordDetectionComponentName;
125     final ComponentName mVisualQueryDetectionComponentName;
126     boolean mBound = false;
127     IVoiceInteractionService mService;
128     volatile HotwordDetectionConnection mHotwordDetectionConnection;
129 
130     VoiceInteractionSessionConnection mActiveSession;
131     int mDisabledShowContext;
132 
133     final BroadcastReceiver mBroadcastReceiver = new BroadcastReceiver() {
134         @Override
135         public void onReceive(Context context, Intent intent) {
136             if (Intent.ACTION_CLOSE_SYSTEM_DIALOGS.equals(intent.getAction())) {
137                 String reason = intent.getStringExtra("reason");
138                 if (!CLOSE_REASON_VOICE_INTERACTION.equals(reason)
139                         && !TextUtils.equals("dream", reason)
140                         && !SYSTEM_DIALOG_REASON_ASSIST.equals(reason)) {
141                     synchronized (mServiceStub) {
142                         if (mActiveSession != null && mActiveSession.mSession != null) {
143                             try {
144                                 mActiveSession.mSession.closeSystemDialogs();
145                             } catch (RemoteException e) {
146                             }
147                         }
148                     }
149                 }
150             }
151         }
152     };
153 
154     final ServiceConnection mConnection = new ServiceConnection() {
155         @Override
156         public void onServiceConnected(ComponentName name, IBinder service) {
157             if (DEBUG) {
158                 Slog.d(TAG, "onServiceConnected to " + name + " for user(" + mUser + ")");
159             }
160             synchronized (mServiceStub) {
161                 mService = IVoiceInteractionService.Stub.asInterface(service);
162                 try {
163                     mService.ready();
164                 } catch (RemoteException e) {
165                 }
166             }
167         }
168 
169         @Override
170         public void onServiceDisconnected(ComponentName name) {
171             if (DEBUG) {
172                 Slog.d(TAG, "onServiceDisconnected to " + name);
173             }
174             synchronized (mServiceStub) {
175                 mService = null;
176                 resetHotwordDetectionConnectionLocked();
177             }
178         }
179 
180         @Override
181         public void onBindingDied(ComponentName name) {
182             Slog.d(TAG, "onBindingDied to " + name);
183             String packageName = name.getPackageName();
184             ParceledListSlice<ApplicationExitInfo> plistSlice = null;
185             try {
186                 plistSlice = mAm.getHistoricalProcessExitReasons(packageName, 0, 1, mUser);
187             } catch (RemoteException e) {
188                 // do nothing. The local binder so it can not throw it.
189             }
190             if (plistSlice == null) {
191                 return;
192             }
193             List<ApplicationExitInfo> list = plistSlice.getList();
194             if (list.isEmpty()) {
195                 return;
196             }
197             // TODO(b/229956310): Refactor the logic of PackageMonitor and onBindingDied
198             ApplicationExitInfo info = list.get(0);
199             if (info.getReason() == ApplicationExitInfo.REASON_USER_REQUESTED
200                     && info.getSubReason() == ApplicationExitInfo.SUBREASON_STOP_APP) {
201                 // only handle user stopped the application from the task manager
202                 mServiceStub.handleUserStop(packageName, mUser);
203             }
204         }
205     };
206 
207     final ArrayList<
208             IVoiceInteractionAccessibilitySettingsListener> mAccessibilitySettingsListeners =
209             new ArrayList<IVoiceInteractionAccessibilitySettingsListener>();
210 
VoiceInteractionManagerServiceImpl(Context context, Handler handler, VoiceInteractionManagerService.VoiceInteractionManagerServiceStub stub, int userHandle, ComponentName service)211     VoiceInteractionManagerServiceImpl(Context context, Handler handler,
212             VoiceInteractionManagerService.VoiceInteractionManagerServiceStub stub,
213             int userHandle, ComponentName service) {
214         mContext = context;
215         mHandler = handler;
216         mDirectActionsHandler = new Handler(true);
217         mServiceStub = stub;
218         mUser = userHandle;
219         mComponent = service;
220         mAm = ActivityManager.getService();
221         mAtm = ActivityTaskManager.getService();
222         mPackageManagerInternal = Objects.requireNonNull(
223                 LocalServices.getService(PackageManagerInternal.class));
224         VoiceInteractionServiceInfo info;
225         try {
226             info = new VoiceInteractionServiceInfo(context.getPackageManager(), service, mUser);
227         } catch (PackageManager.NameNotFoundException e) {
228             Slog.w(TAG, "Voice interaction service not found: " + service, e);
229             mInfo = null;
230             mSessionComponentName = null;
231             mHotwordDetectionComponentName = null;
232             mVisualQueryDetectionComponentName = null;
233             mIWindowManager = null;
234             mValid = false;
235             return;
236         }
237         mInfo = info;
238         if (mInfo.getParseError() != null) {
239             Slog.w(TAG, "Bad voice interaction service: " + mInfo.getParseError());
240             mSessionComponentName = null;
241             mHotwordDetectionComponentName = null;
242             mVisualQueryDetectionComponentName = null;
243             mIWindowManager = null;
244             mValid = false;
245             return;
246         }
247         mValid = true;
248         mSessionComponentName = new ComponentName(service.getPackageName(),
249                 mInfo.getSessionService());
250         final String hotwordDetectionServiceName = mInfo.getHotwordDetectionService();
251         mHotwordDetectionComponentName = hotwordDetectionServiceName != null
252                 ? new ComponentName(service.getPackageName(), hotwordDetectionServiceName) : null;
253         final String visualQueryDetectionServiceName = mInfo.getVisualQueryDetectionService();
254         mVisualQueryDetectionComponentName = visualQueryDetectionServiceName != null ? new
255                 ComponentName(service.getPackageName(), visualQueryDetectionServiceName) : null;
256         mIWindowManager = IWindowManager.Stub.asInterface(
257                 ServiceManager.getService(Context.WINDOW_SERVICE));
258         IntentFilter filter = new IntentFilter();
259         filter.addAction(Intent.ACTION_CLOSE_SYSTEM_DIALOGS);
260         mContext.registerReceiver(mBroadcastReceiver, filter, null, handler,
261                 Context.RECEIVER_EXPORTED);
262         new AccessibilitySettingsContentObserver().register(mContext.getContentResolver());
263     }
264 
grantImplicitAccessLocked(int grantRecipientUid, @Nullable Intent intent)265     public void grantImplicitAccessLocked(int grantRecipientUid, @Nullable Intent intent) {
266         final int grantRecipientAppId = UserHandle.getAppId(grantRecipientUid);
267         final int grantRecipientUserId = UserHandle.getUserId(grantRecipientUid);
268         final int voiceInteractionUid = mInfo.getServiceInfo().applicationInfo.uid;
269         mPackageManagerInternal.grantImplicitAccess(
270                 grantRecipientUserId, intent, grantRecipientAppId, voiceInteractionUid,
271                 /* direct= */ true);
272     }
273 
showSessionLocked(@ullable Bundle args, int flags, @Nullable String attributionTag, @Nullable IVoiceInteractionSessionShowCallback showCallback, @Nullable IBinder activityToken)274     public boolean showSessionLocked(@Nullable Bundle args, int flags,
275             @Nullable String attributionTag,
276             @Nullable IVoiceInteractionSessionShowCallback showCallback,
277             @Nullable IBinder activityToken) {
278         final int sessionId = mServiceStub.getNextShowSessionId();
279         final Bundle newArgs = args == null ? new Bundle() : args;
280         newArgs.putInt(KEY_SHOW_SESSION_ID, sessionId);
281 
282         try {
283             if (mService != null) {
284                 mService.prepareToShowSession(newArgs, flags);
285             }
286         } catch (RemoteException e) {
287             Slog.w(TAG, "RemoteException while calling prepareToShowSession", e);
288         }
289 
290         if (mActiveSession == null) {
291             mActiveSession = new VoiceInteractionSessionConnection(mServiceStub,
292                     mSessionComponentName, mUser, mContext, this,
293                     mInfo.getServiceInfo().applicationInfo.uid, mHandler);
294         }
295         if (!mActiveSession.mBound) {
296             try {
297                 if (mService != null) {
298                     Bundle failedArgs = new Bundle();
299                     failedArgs.putInt(KEY_SHOW_SESSION_ID, sessionId);
300                     mService.showSessionFailed(failedArgs);
301                 }
302             } catch (RemoteException e) {
303                 Slog.w(TAG, "RemoteException while calling showSessionFailed", e);
304             }
305         }
306 
307         List<ActivityAssistInfo> allVisibleActivities =
308                 LocalServices.getService(ActivityTaskManagerInternal.class)
309                         .getTopVisibleActivities();
310 
311         List<ActivityAssistInfo> visibleActivities = null;
312         if (activityToken != null) {
313             visibleActivities = new ArrayList();
314             int activitiesCount = allVisibleActivities.size();
315             for (int i = 0; i < activitiesCount; i++) {
316                 ActivityAssistInfo info = allVisibleActivities.get(i);
317                 if (info.getActivityToken() == activityToken) {
318                     visibleActivities.add(info);
319                     break;
320                 }
321             }
322         } else {
323             visibleActivities = allVisibleActivities;
324         }
325         return mActiveSession.showLocked(newArgs, flags, attributionTag, mDisabledShowContext,
326                 showCallback, visibleActivities);
327     }
328 
getActiveServiceSupportedActions(List<String> commands, IVoiceActionCheckCallback callback)329     public void getActiveServiceSupportedActions(List<String> commands,
330             IVoiceActionCheckCallback callback) {
331         if (mService == null) {
332             Slog.w(TAG, "Not bound to voice interaction service " + mComponent);
333             try {
334                 callback.onComplete(null);
335             } catch (RemoteException e) {
336             }
337             return;
338         }
339         try {
340             mService.getActiveServiceSupportedActions(commands, callback);
341         } catch (RemoteException e) {
342             Slog.w(TAG, "RemoteException while calling getActiveServiceSupportedActions", e);
343         }
344     }
345 
hideSessionLocked()346     public boolean hideSessionLocked() {
347         if (mActiveSession != null) {
348             return mActiveSession.hideLocked();
349         }
350         return false;
351     }
352 
deliverNewSessionLocked(IBinder token, IVoiceInteractionSession session, IVoiceInteractor interactor)353     public boolean deliverNewSessionLocked(IBinder token,
354             IVoiceInteractionSession session, IVoiceInteractor interactor) {
355         if (mActiveSession == null || token != mActiveSession.mToken) {
356             Slog.w(TAG, "deliverNewSession does not match active session");
357             return false;
358         }
359         mActiveSession.deliverNewSessionLocked(session, interactor);
360         return true;
361     }
362 
startVoiceActivityLocked(@ullable String callingFeatureId, int callingPid, int callingUid, IBinder token, Intent intent, String resolvedType)363     public int startVoiceActivityLocked(@Nullable String callingFeatureId, int callingPid,
364             int callingUid, IBinder token, Intent intent, String resolvedType) {
365         try {
366             if (mActiveSession == null || token != mActiveSession.mToken) {
367                 Slog.w(TAG, "startVoiceActivity does not match active session");
368                 return START_VOICE_NOT_ACTIVE_SESSION;
369             }
370             if (!mActiveSession.mShown) {
371                 Slog.w(TAG, "startVoiceActivity not allowed on hidden session");
372                 return START_VOICE_HIDDEN_SESSION;
373             }
374             intent = new Intent(intent);
375             intent.addCategory(Intent.CATEGORY_VOICE);
376             intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_MULTIPLE_TASK);
377             return mAtm.startVoiceActivity(mComponent.getPackageName(), callingFeatureId,
378                     callingPid, callingUid, intent, resolvedType, mActiveSession.mSession,
379                     mActiveSession.mInteractor, 0, null, null, mUser);
380         } catch (RemoteException e) {
381             throw new IllegalStateException("Unexpected remote error", e);
382         }
383     }
384 
startAssistantActivityLocked(@ullable String callingFeatureId, int callingPid, int callingUid, IBinder token, Intent intent, String resolvedType, @NonNull Bundle bundle)385     public int startAssistantActivityLocked(@Nullable String callingFeatureId, int callingPid,
386             int callingUid, IBinder token, Intent intent, String resolvedType,
387             @NonNull Bundle bundle) {
388         try {
389             if (mActiveSession == null || token != mActiveSession.mToken) {
390                 Slog.w(TAG, "startAssistantActivity does not match active session");
391                 return START_ASSISTANT_NOT_ACTIVE_SESSION;
392             }
393             if (!mActiveSession.mShown) {
394                 Slog.w(TAG, "startAssistantActivity not allowed on hidden session");
395                 return START_ASSISTANT_HIDDEN_SESSION;
396             }
397             intent = new Intent(intent);
398             intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
399             // TODO: make the key public hidden
400             bundle.putInt("android.activity.activityType", ACTIVITY_TYPE_ASSISTANT);
401             return mAtm.startAssistantActivity(mComponent.getPackageName(), callingFeatureId,
402                     callingPid, callingUid, intent, resolvedType, bundle, mUser);
403         } catch (RemoteException e) {
404             throw new IllegalStateException("Unexpected remote error", e);
405         }
406     }
407 
requestDirectActionsLocked(@onNull IBinder token, int taskId, @NonNull IBinder assistToken, @Nullable RemoteCallback cancellationCallback, @NonNull RemoteCallback callback)408     public void requestDirectActionsLocked(@NonNull IBinder token, int taskId,
409             @NonNull IBinder assistToken,  @Nullable RemoteCallback cancellationCallback,
410             @NonNull RemoteCallback callback) {
411         if (mActiveSession == null || token != mActiveSession.mToken) {
412             Slog.w(TAG, "requestDirectActionsLocked does not match active session");
413             callback.sendResult(null);
414             return;
415         }
416         final ActivityTokens tokens = LocalServices.getService(ActivityTaskManagerInternal.class)
417                 .getAttachedNonFinishingActivityForTask(taskId, null);
418         if (tokens == null || tokens.getAssistToken() != assistToken) {
419             Slog.w(TAG, "Unknown activity to query for direct actions");
420             mDirectActionsHandler.sendMessageDelayed(PooledLambda.obtainMessage(
421                     VoiceInteractionManagerServiceImpl::retryRequestDirectActions,
422                     VoiceInteractionManagerServiceImpl.this, token, taskId, assistToken,
423                     cancellationCallback, callback), REQUEST_DIRECT_ACTIONS_RETRY_TIME_MS);
424         } else {
425             grantImplicitAccessLocked(tokens.getUid(), /* intent= */ null);
426             try {
427                 tokens.getApplicationThread().requestDirectActions(tokens.getActivityToken(),
428                         mActiveSession.mInteractor, cancellationCallback, callback);
429             } catch (RemoteException e) {
430                 Slog.w("Unexpected remote error", e);
431                 callback.sendResult(null);
432             }
433         }
434     }
435 
retryRequestDirectActions(@onNull IBinder token, int taskId, @NonNull IBinder assistToken, @Nullable RemoteCallback cancellationCallback, @NonNull RemoteCallback callback)436     private void retryRequestDirectActions(@NonNull IBinder token, int taskId,
437             @NonNull IBinder assistToken,  @Nullable RemoteCallback cancellationCallback,
438             @NonNull RemoteCallback callback) {
439         synchronized (mServiceStub) {
440             if (mActiveSession == null || token != mActiveSession.mToken) {
441                 Slog.w(TAG, "retryRequestDirectActions does not match active session");
442                 callback.sendResult(null);
443                 return;
444             }
445             final ActivityTokens tokens = LocalServices.getService(
446                             ActivityTaskManagerInternal.class)
447                     .getAttachedNonFinishingActivityForTask(taskId, null);
448             if (tokens == null || tokens.getAssistToken() != assistToken) {
449                 Slog.w(TAG, "Unknown activity to query for direct actions during retrying");
450                 callback.sendResult(null);
451             } else {
452                 try {
453                     tokens.getApplicationThread().requestDirectActions(tokens.getActivityToken(),
454                             mActiveSession.mInteractor, cancellationCallback, callback);
455                 } catch (RemoteException e) {
456                     Slog.w("Unexpected remote error", e);
457                     callback.sendResult(null);
458                 }
459             }
460         }
461     }
462 
performDirectActionLocked(@onNull IBinder token, @NonNull String actionId, @Nullable Bundle arguments, int taskId, IBinder assistToken, @Nullable RemoteCallback cancellationCallback, @NonNull RemoteCallback resultCallback)463     void performDirectActionLocked(@NonNull IBinder token, @NonNull String actionId,
464             @Nullable Bundle arguments, int taskId, IBinder assistToken,
465             @Nullable RemoteCallback cancellationCallback,
466             @NonNull RemoteCallback resultCallback) {
467         if (mActiveSession == null || token != mActiveSession.mToken) {
468             Slog.w(TAG, "performDirectActionLocked does not match active session");
469             resultCallback.sendResult(null);
470             return;
471         }
472         final ActivityTokens tokens = LocalServices.getService(ActivityTaskManagerInternal.class)
473                 .getAttachedNonFinishingActivityForTask(taskId, null);
474         if (tokens == null || tokens.getAssistToken() != assistToken) {
475             Slog.w(TAG, "Unknown activity to perform a direct action");
476             resultCallback.sendResult(null);
477         } else {
478             try {
479                 tokens.getApplicationThread().performDirectAction(tokens.getActivityToken(),
480                         actionId, arguments, cancellationCallback,
481                         resultCallback);
482             } catch (RemoteException e) {
483                 Slog.w("Unexpected remote error", e);
484                 resultCallback.sendResult(null);
485             }
486         }
487     }
488 
setKeepAwakeLocked(IBinder token, boolean keepAwake)489     public void setKeepAwakeLocked(IBinder token, boolean keepAwake) {
490         try {
491             if (mActiveSession == null || token != mActiveSession.mToken) {
492                 Slog.w(TAG, "setKeepAwake does not match active session");
493                 return;
494             }
495             mAtm.setVoiceKeepAwake(mActiveSession.mSession, keepAwake);
496         } catch (RemoteException e) {
497             throw new IllegalStateException("Unexpected remote error", e);
498         }
499     }
500 
closeSystemDialogsLocked(IBinder token)501     public void closeSystemDialogsLocked(IBinder token) {
502         try {
503             if (mActiveSession == null || token != mActiveSession.mToken) {
504                 Slog.w(TAG, "closeSystemDialogs does not match active session");
505                 return;
506             }
507             mAm.closeSystemDialogs(CLOSE_REASON_VOICE_INTERACTION);
508         } catch (RemoteException e) {
509             throw new IllegalStateException("Unexpected remote error", e);
510         }
511     }
512 
finishLocked(IBinder token, boolean finishTask)513     public void finishLocked(IBinder token, boolean finishTask) {
514         if (mActiveSession == null || (!finishTask && token != mActiveSession.mToken)) {
515             Slog.w(TAG, "finish does not match active session");
516             return;
517         }
518         mActiveSession.cancelLocked(finishTask);
519         mActiveSession = null;
520     }
521 
setDisabledShowContextLocked(int callingUid, int flags)522     public void setDisabledShowContextLocked(int callingUid, int flags) {
523         int activeUid = mInfo.getServiceInfo().applicationInfo.uid;
524         if (callingUid != activeUid) {
525             throw new SecurityException("Calling uid " + callingUid
526                     + " does not match active uid " + activeUid);
527         }
528         mDisabledShowContext = flags;
529     }
530 
getDisabledShowContextLocked(int callingUid)531     public int getDisabledShowContextLocked(int callingUid) {
532         int activeUid = mInfo.getServiceInfo().applicationInfo.uid;
533         if (callingUid != activeUid) {
534             throw new SecurityException("Calling uid " + callingUid
535                     + " does not match active uid " + activeUid);
536         }
537         return mDisabledShowContext;
538     }
539 
getUserDisabledShowContextLocked(int callingUid)540     public int getUserDisabledShowContextLocked(int callingUid) {
541         int activeUid = mInfo.getServiceInfo().applicationInfo.uid;
542         if (callingUid != activeUid) {
543             throw new SecurityException("Calling uid " + callingUid
544                     + " does not match active uid " + activeUid);
545         }
546         return mActiveSession != null ? mActiveSession.getUserDisabledShowContextLocked() : 0;
547     }
548 
supportsLocalVoiceInteraction()549     public boolean supportsLocalVoiceInteraction() {
550         return mInfo.getSupportsLocalInteraction();
551     }
552 
getApplicationInfo()553     public ApplicationInfo getApplicationInfo() {
554         return mInfo.getServiceInfo().applicationInfo;
555     }
556 
startListeningVisibleActivityChangedLocked(@onNull IBinder token)557     public void startListeningVisibleActivityChangedLocked(@NonNull IBinder token) {
558         if (DEBUG) {
559             Slog.d(TAG, "startListeningVisibleActivityChangedLocked: token=" + token);
560         }
561         if (mActiveSession == null || token != mActiveSession.mToken) {
562             Slog.w(TAG, "startListeningVisibleActivityChangedLocked does not match"
563                     + " active session");
564             return;
565         }
566         mActiveSession.startListeningVisibleActivityChangedLocked();
567     }
568 
stopListeningVisibleActivityChangedLocked(@onNull IBinder token)569     public void stopListeningVisibleActivityChangedLocked(@NonNull IBinder token) {
570         if (DEBUG) {
571             Slog.d(TAG, "stopListeningVisibleActivityChangedLocked: token=" + token);
572         }
573         if (mActiveSession == null || token != mActiveSession.mToken) {
574             Slog.w(TAG, "stopListeningVisibleActivityChangedLocked does not match"
575                     + " active session");
576             return;
577         }
578         mActiveSession.stopListeningVisibleActivityChangedLocked();
579     }
580 
notifyActivityDestroyedLocked(@onNull IBinder activityToken)581     public void notifyActivityDestroyedLocked(@NonNull IBinder activityToken) {
582         if (DEBUG) {
583             Slog.d(TAG, "notifyActivityDestroyedLocked activityToken=" + activityToken);
584         }
585         if (mActiveSession == null || !mActiveSession.mShown) {
586             if (DEBUG) {
587                 Slog.d(TAG, "notifyActivityDestroyedLocked not allowed on no session or"
588                         + " hidden session");
589             }
590             return;
591         }
592         mActiveSession.notifyActivityDestroyedLocked(activityToken);
593     }
594 
notifyActivityEventChangedLocked(@onNull IBinder activityToken, int type)595     public void notifyActivityEventChangedLocked(@NonNull IBinder activityToken, int type) {
596         if (DEBUG) {
597             Slog.d(TAG, "notifyActivityEventChangedLocked type=" + type);
598         }
599         if (mActiveSession == null || !mActiveSession.mShown) {
600             if (DEBUG) {
601                 Slog.d(TAG, "notifyActivityEventChangedLocked not allowed on no session or"
602                         + " hidden session");
603             }
604             return;
605         }
606         mActiveSession.notifyActivityEventChangedLocked(activityToken, type);
607     }
608 
updateStateLocked( @ullable PersistableBundle options, @Nullable SharedMemory sharedMemory, @NonNull IBinder token)609     public void updateStateLocked(
610             @Nullable PersistableBundle options,
611             @Nullable SharedMemory sharedMemory,
612             @NonNull IBinder token) {
613         Slog.v(TAG, "updateStateLocked");
614 
615         if (sharedMemory != null && !sharedMemory.setProtect(OsConstants.PROT_READ)) {
616             Slog.w(TAG, "Can't set sharedMemory to be read-only");
617             throw new IllegalStateException("Can't set sharedMemory to be read-only");
618         }
619 
620         if (mHotwordDetectionConnection == null) {
621             Slog.w(TAG, "update State, but no hotword detection connection");
622             throw new IllegalStateException("Hotword detection connection not found");
623         }
624         synchronized (mHotwordDetectionConnection.mLock) {
625             mHotwordDetectionConnection.updateStateLocked(options, sharedMemory, token);
626         }
627     }
628 
verifyDetectorForHotwordDetectionLocked( @ullable SharedMemory sharedMemory, IHotwordRecognitionStatusCallback callback, int detectorType)629     private void verifyDetectorForHotwordDetectionLocked(
630             @Nullable SharedMemory sharedMemory,
631             IHotwordRecognitionStatusCallback callback,
632             int detectorType) {
633         Slog.v(TAG, "verifyDetectorForHotwordDetectionLocked");
634         int voiceInteractionServiceUid = mInfo.getServiceInfo().applicationInfo.uid;
635         if (mHotwordDetectionComponentName == null) {
636             Slog.w(TAG, "Hotword detection service name not found");
637             logDetectorCreateEventIfNeeded(callback, detectorType, false,
638                     voiceInteractionServiceUid);
639             throw new IllegalStateException("Hotword detection service name not found");
640         }
641         ServiceInfo hotwordDetectionServiceInfo = getServiceInfoLocked(
642                 mHotwordDetectionComponentName, mUser);
643         if (hotwordDetectionServiceInfo == null) {
644             Slog.w(TAG, "Hotword detection service info not found");
645             logDetectorCreateEventIfNeeded(callback, detectorType, false,
646                     voiceInteractionServiceUid);
647             throw new IllegalStateException("Hotword detection service info not found");
648         }
649         if (!isIsolatedProcessLocked(hotwordDetectionServiceInfo)) {
650             Slog.w(TAG, "Hotword detection service not in isolated process");
651             logDetectorCreateEventIfNeeded(callback, detectorType, false,
652                     voiceInteractionServiceUid);
653             throw new IllegalStateException("Hotword detection service not in isolated process");
654         }
655         if (!Manifest.permission.BIND_HOTWORD_DETECTION_SERVICE.equals(
656                 hotwordDetectionServiceInfo.permission)) {
657             Slog.w(TAG, "Hotword detection service does not require permission "
658                     + Manifest.permission.BIND_HOTWORD_DETECTION_SERVICE);
659             logDetectorCreateEventIfNeeded(callback, detectorType, false,
660                     voiceInteractionServiceUid);
661             throw new SecurityException("Hotword detection service does not require permission "
662                     + Manifest.permission.BIND_HOTWORD_DETECTION_SERVICE);
663         }
664         if (mContext.getPackageManager().checkPermission(
665                 Manifest.permission.BIND_HOTWORD_DETECTION_SERVICE,
666                 mInfo.getServiceInfo().packageName) == PackageManager.PERMISSION_GRANTED) {
667             Slog.w(TAG, "Voice interaction service should not hold permission "
668                     + Manifest.permission.BIND_HOTWORD_DETECTION_SERVICE);
669             logDetectorCreateEventIfNeeded(callback, detectorType, false,
670                     voiceInteractionServiceUid);
671             throw new SecurityException("Voice interaction service should not hold permission "
672                     + Manifest.permission.BIND_HOTWORD_DETECTION_SERVICE);
673         }
674 
675         if (sharedMemory != null && !sharedMemory.setProtect(OsConstants.PROT_READ)) {
676             Slog.w(TAG, "Can't set sharedMemory to be read-only");
677             logDetectorCreateEventIfNeeded(callback, detectorType, false,
678                     voiceInteractionServiceUid);
679             throw new IllegalStateException("Can't set sharedMemory to be read-only");
680         }
681 
682         logDetectorCreateEventIfNeeded(callback, detectorType, true,
683                 voiceInteractionServiceUid);
684     }
685 
verifyDetectorForVisualQueryDetectionLocked(@ullable SharedMemory sharedMemory)686     private void verifyDetectorForVisualQueryDetectionLocked(@Nullable SharedMemory sharedMemory) {
687         Slog.v(TAG, "verifyDetectorForVisualQueryDetectionLocked");
688 
689         if (mVisualQueryDetectionComponentName == null) {
690             Slog.w(TAG, "Visual query detection service name not found");
691             throw new IllegalStateException("Visual query detection service name not found");
692         }
693         ServiceInfo visualQueryDetectionServiceInfo = getServiceInfoLocked(
694                 mVisualQueryDetectionComponentName, mUser);
695         if (visualQueryDetectionServiceInfo == null) {
696             Slog.w(TAG, "Visual query detection service info not found");
697             throw new IllegalStateException("Visual query detection service name not found");
698         }
699         if (!isIsolatedProcessLocked(visualQueryDetectionServiceInfo)) {
700             Slog.w(TAG, "Visual query detection service not in isolated process");
701             throw new IllegalStateException("Visual query detection not in isolated process");
702         }
703         if (!Manifest.permission.BIND_VISUAL_QUERY_DETECTION_SERVICE.equals(
704                 visualQueryDetectionServiceInfo.permission)) {
705             Slog.w(TAG, "Visual query detection does not require permission "
706                     + Manifest.permission.BIND_VISUAL_QUERY_DETECTION_SERVICE);
707             throw new SecurityException("Visual query detection does not require permission "
708                     + Manifest.permission.BIND_VISUAL_QUERY_DETECTION_SERVICE);
709         }
710         if (mContext.getPackageManager().checkPermission(
711                 Manifest.permission.BIND_VISUAL_QUERY_DETECTION_SERVICE,
712                 mInfo.getServiceInfo().packageName) == PackageManager.PERMISSION_GRANTED) {
713             Slog.w(TAG, "Voice interaction service should not hold permission "
714                     + Manifest.permission.BIND_VISUAL_QUERY_DETECTION_SERVICE);
715             throw new SecurityException("Voice interaction service should not hold permission "
716                     + Manifest.permission.BIND_VISUAL_QUERY_DETECTION_SERVICE);
717         }
718         if (sharedMemory != null && !sharedMemory.setProtect(OsConstants.PROT_READ)) {
719             Slog.w(TAG, "Can't set sharedMemory to be read-only");
720             throw new IllegalStateException("Can't set sharedMemory to be read-only");
721         }
722     }
723 
initAndVerifyDetectorLocked( @onNull Identity voiceInteractorIdentity, @Nullable PersistableBundle options, @Nullable SharedMemory sharedMemory, @NonNull IBinder token, IHotwordRecognitionStatusCallback callback, int detectorType)724     public void initAndVerifyDetectorLocked(
725             @NonNull Identity voiceInteractorIdentity,
726             @Nullable PersistableBundle options,
727             @Nullable SharedMemory sharedMemory,
728             @NonNull IBinder token,
729             IHotwordRecognitionStatusCallback callback,
730             int detectorType) {
731 
732         if (detectorType != HotwordDetector.DETECTOR_TYPE_VISUAL_QUERY_DETECTOR) {
733             verifyDetectorForHotwordDetectionLocked(sharedMemory, callback, detectorType);
734         } else {
735             verifyDetectorForVisualQueryDetectionLocked(sharedMemory);
736         }
737         if (SYSPROP_VISUAL_QUERY_SERVICE_ENABLED && !verifyProcessSharingLocked()) {
738             Slog.w(TAG, "Sandboxed detection service not in shared isolated process");
739             throw new IllegalStateException("VisualQueryDetectionService or HotworDetectionService "
740                     + "not in a shared isolated process. Please make sure to set "
741                     + "android:allowSharedIsolatedProcess and android:isolatedProcess to be true "
742                     + "and android:externalService to be false in the manifest file");
743         }
744 
745         if (mHotwordDetectionConnection == null) {
746             mHotwordDetectionConnection = new HotwordDetectionConnection(mServiceStub, mContext,
747                     mInfo.getServiceInfo().applicationInfo.uid, voiceInteractorIdentity,
748                     mHotwordDetectionComponentName, mVisualQueryDetectionComponentName, mUser,
749                     /* bindInstantServiceAllowed= */ false, detectorType,
750                     (token1, detectorType1) -> {
751                         try {
752                             mService.detectorRemoteExceptionOccurred(token1, detectorType1);
753                         } catch (RemoteException e) {
754                             Slog.w(TAG, "Fail to notify client detector remote "
755                                     + "exception occurred.");
756                         }
757                     });
758             registerAccessibilityDetectionSettingsListenerLocked(
759                     mHotwordDetectionConnection.mAccessibilitySettingsListener);
760         } else if (detectorType != HotwordDetector.DETECTOR_TYPE_VISUAL_QUERY_DETECTOR) {
761             // TODO: Logger events should be handled in session instead. Temporary adding the
762             //  checking to prevent confusion so VisualQueryDetection events won't be logged if the
763             //  connection is instantiated by the VisualQueryDetector.
764             mHotwordDetectionConnection.setDetectorType(detectorType);
765         }
766         mHotwordDetectionConnection.createDetectorLocked(options, sharedMemory, token, callback,
767                 detectorType);
768     }
769 
destroyDetectorLocked(IBinder token)770     public void destroyDetectorLocked(IBinder token) {
771         Slog.v(TAG, "destroyDetectorLocked");
772 
773         if (mHotwordDetectionConnection == null) {
774             Slog.w(TAG, "destroy detector callback, but no hotword detection connection");
775             return;
776         }
777         mHotwordDetectionConnection.destroyDetectorLocked(token);
778     }
779 
logDetectorCreateEventIfNeeded(IHotwordRecognitionStatusCallback callback, int detectorType, boolean isCreated, int voiceInteractionServiceUid)780     private void logDetectorCreateEventIfNeeded(IHotwordRecognitionStatusCallback callback,
781             int detectorType, boolean isCreated, int voiceInteractionServiceUid) {
782         if (callback != null) {
783             HotwordMetricsLogger.writeDetectorCreateEvent(detectorType, isCreated,
784                     voiceInteractionServiceUid);
785         }
786     }
787 
shutdownHotwordDetectionServiceLocked()788     public void shutdownHotwordDetectionServiceLocked() {
789         if (DEBUG) {
790             Slog.d(TAG, "shutdownHotwordDetectionServiceLocked");
791         }
792         if (mHotwordDetectionConnection == null) {
793             Slog.w(TAG, "shutdown, but no hotword detection connection");
794             return;
795         }
796         mHotwordDetectionConnection.cancelLocked();
797         unregisterAccessibilityDetectionSettingsListenerLocked(
798                 mHotwordDetectionConnection.mAccessibilitySettingsListener);
799         mHotwordDetectionConnection = null;
800     }
801 
setVisualQueryDetectionAttentionListenerLocked( @ullable IVisualQueryDetectionAttentionListener listener)802     public void setVisualQueryDetectionAttentionListenerLocked(
803             @Nullable IVisualQueryDetectionAttentionListener listener) {
804         if (mHotwordDetectionConnection == null) {
805             return;
806         }
807         mHotwordDetectionConnection.setVisualQueryDetectionAttentionListenerLocked(listener);
808     }
809 
startPerceivingLocked(IVisualQueryDetectionVoiceInteractionCallback callback)810     public boolean startPerceivingLocked(IVisualQueryDetectionVoiceInteractionCallback callback) {
811         if (DEBUG) {
812             Slog.d(TAG, "startPerceivingLocked");
813         }
814 
815         if (mHotwordDetectionConnection == null) {
816             // TODO: callback.onError();
817             return false;
818         }
819 
820         return mHotwordDetectionConnection.startPerceivingLocked(callback);
821     }
822 
stopPerceivingLocked()823     public boolean stopPerceivingLocked() {
824         if (DEBUG) {
825             Slog.d(TAG, "stopPerceivingLocked");
826         }
827 
828         if (mHotwordDetectionConnection == null) {
829             Slog.w(TAG, "stopPerceivingLocked() called but connection isn't established");
830             return false;
831         }
832 
833         return mHotwordDetectionConnection.stopPerceivingLocked();
834     }
835 
startListeningFromMicLocked( AudioFormat audioFormat, IMicrophoneHotwordDetectionVoiceInteractionCallback callback)836     public void startListeningFromMicLocked(
837             AudioFormat audioFormat,
838             IMicrophoneHotwordDetectionVoiceInteractionCallback callback) {
839         if (DEBUG) {
840             Slog.d(TAG, "startListeningFromMicLocked");
841         }
842 
843         if (mHotwordDetectionConnection == null) {
844             // TODO: callback.onError();
845             return;
846         }
847 
848         mHotwordDetectionConnection.startListeningFromMicLocked(audioFormat, callback);
849     }
850 
startListeningFromExternalSourceLocked( ParcelFileDescriptor audioStream, AudioFormat audioFormat, @Nullable PersistableBundle options, @NonNull IBinder token, IMicrophoneHotwordDetectionVoiceInteractionCallback callback)851     public void startListeningFromExternalSourceLocked(
852             ParcelFileDescriptor audioStream,
853             AudioFormat audioFormat,
854             @Nullable PersistableBundle options,
855             @NonNull IBinder token,
856             IMicrophoneHotwordDetectionVoiceInteractionCallback callback) {
857         if (DEBUG) {
858             Slog.d(TAG, "startListeningFromExternalSourceLocked");
859         }
860 
861         if (mHotwordDetectionConnection == null) {
862             // TODO: callback.onError();
863             return;
864         }
865 
866         if (audioStream == null) {
867             Slog.w(TAG, "External source is null for hotword detector");
868             throw new IllegalStateException("External source is null for hotword detector");
869         }
870 
871         mHotwordDetectionConnection.startListeningFromExternalSourceLocked(audioStream, audioFormat,
872                 options, token, callback);
873     }
874 
startListeningFromWearableLocked( ParcelFileDescriptor audioStream, AudioFormat audioFormat, PersistableBundle options, WearableHotwordDetectionCallback callback)875     public void startListeningFromWearableLocked(
876             ParcelFileDescriptor audioStream,
877             AudioFormat audioFormat,
878             PersistableBundle options,
879             WearableHotwordDetectionCallback callback) {
880         if (DEBUG) {
881             Slog.d(TAG, "startListeningFromWearable");
882         }
883         if (mHotwordDetectionConnection == null) {
884             callback.onError(
885                     "Unable to start listening from wearable because the hotword detection"
886                             + " connection is null.");
887             return;
888         }
889         mHotwordDetectionConnection.startListeningFromWearableLocked(
890                 audioStream, audioFormat, options, callback);
891     }
892 
stopListeningFromMicLocked()893     public void stopListeningFromMicLocked() {
894         if (DEBUG) {
895             Slog.d(TAG, "stopListeningFromMicLocked");
896         }
897 
898         if (mHotwordDetectionConnection == null) {
899             Slog.w(TAG, "stopListeningFromMicLocked() called but connection isn't established");
900             return;
901         }
902 
903         mHotwordDetectionConnection.stopListeningFromMicLocked();
904     }
905 
triggerHardwareRecognitionEventForTestLocked( SoundTrigger.KeyphraseRecognitionEvent event, IHotwordRecognitionStatusCallback callback)906     public void triggerHardwareRecognitionEventForTestLocked(
907             SoundTrigger.KeyphraseRecognitionEvent event,
908             IHotwordRecognitionStatusCallback callback) {
909         if (DEBUG) {
910             Slog.d(TAG, "triggerHardwareRecognitionEventForTestLocked");
911         }
912         if (mHotwordDetectionConnection == null) {
913             Slog.w(TAG, "triggerHardwareRecognitionEventForTestLocked() called but connection"
914                     + " isn't established");
915             return;
916         }
917         mHotwordDetectionConnection.triggerHardwareRecognitionEventForTestLocked(event, callback);
918     }
919 
createSoundTriggerCallbackLocked( Context context, IHotwordRecognitionStatusCallback callback, Identity voiceInteractorIdentity)920     public IRecognitionStatusCallback createSoundTriggerCallbackLocked(
921             Context context, IHotwordRecognitionStatusCallback callback,
922             Identity voiceInteractorIdentity) {
923         if (DEBUG) {
924             Slog.d(TAG, "createSoundTriggerCallbackLocked");
925         }
926         return new HotwordDetectionConnection.SoundTriggerCallback(context, callback,
927                 mHotwordDetectionConnection, voiceInteractorIdentity);
928     }
929 
getServiceInfoLocked(@onNull ComponentName componentName, int userHandle)930     private static ServiceInfo getServiceInfoLocked(@NonNull ComponentName componentName,
931             int userHandle) {
932         try {
933             return AppGlobals.getPackageManager().getServiceInfo(componentName,
934                     PackageManager.GET_META_DATA
935                             | PackageManager.MATCH_DIRECT_BOOT_AWARE
936                             | PackageManager.MATCH_DIRECT_BOOT_UNAWARE, userHandle);
937         } catch (RemoteException e) {
938             if (DEBUG) {
939                 Slog.w(TAG, "getServiceInfoLocked RemoteException : " + e);
940             }
941         }
942         return null;
943     }
944 
isIsolatedProcessLocked(@onNull ServiceInfo serviceInfo)945     boolean isIsolatedProcessLocked(@NonNull ServiceInfo serviceInfo) {
946         return (serviceInfo.flags & ServiceInfo.FLAG_ISOLATED_PROCESS) != 0
947                 && (serviceInfo.flags & ServiceInfo.FLAG_EXTERNAL_SERVICE) == 0;
948     }
949 
verifyProcessSharingLocked()950     boolean verifyProcessSharingLocked() {
951         // only check this if both VQDS and HDS are declared in the app
952         ServiceInfo hotwordInfo = getServiceInfoLocked(mHotwordDetectionComponentName, mUser);
953         ServiceInfo visualQueryInfo =
954                 getServiceInfoLocked(mVisualQueryDetectionComponentName, mUser);
955         if (hotwordInfo == null || visualQueryInfo == null) {
956             return true;
957         }
958         // Enforce shared isolated option is used when VisualQueryDetectionservice is enabled
959         return (hotwordInfo.flags & ServiceInfo.FLAG_ALLOW_SHARED_ISOLATED_PROCESS) != 0
960                 && (visualQueryInfo.flags & ServiceInfo.FLAG_ALLOW_SHARED_ISOLATED_PROCESS) != 0;
961     }
962 
963 
forceRestartHotwordDetector()964     void forceRestartHotwordDetector() {
965         if (mHotwordDetectionConnection == null) {
966             Slog.w(TAG, "Failed to force-restart hotword detection: no hotword detection active");
967             return;
968         }
969         mHotwordDetectionConnection.forceRestart();
970     }
971 
setDebugHotwordLoggingLocked(boolean logging)972     void setDebugHotwordLoggingLocked(boolean logging) {
973         if (mHotwordDetectionConnection == null) {
974             Slog.w(TAG, "Failed to set temporary debug logging: no hotword detection active");
975             return;
976         }
977         mHotwordDetectionConnection.setDebugHotwordLoggingLocked(logging);
978     }
979 
resetHotwordDetectionConnectionLocked()980     void resetHotwordDetectionConnectionLocked() {
981         if (DEBUG) {
982             Slog.d(TAG, "resetHotwordDetectionConnectionLocked");
983         }
984         if (mHotwordDetectionConnection == null) {
985             if (DEBUG) {
986                 Slog.w(TAG, "reset, but no hotword detection connection");
987             }
988             return;
989         }
990         mHotwordDetectionConnection.cancelLocked();
991         unregisterAccessibilityDetectionSettingsListenerLocked(
992                 mHotwordDetectionConnection.mAccessibilitySettingsListener);
993         mHotwordDetectionConnection = null;
994     }
995 
dumpLocked(FileDescriptor fd, PrintWriter pw, String[] args)996     public void dumpLocked(FileDescriptor fd, PrintWriter pw, String[] args) {
997         if (!mValid) {
998             pw.print("  NOT VALID: ");
999             if (mInfo == null) {
1000                 pw.println("no info");
1001             } else {
1002                 pw.println(mInfo.getParseError());
1003             }
1004             return;
1005         }
1006         pw.print("  mUser="); pw.println(mUser);
1007         pw.print("  mComponent="); pw.println(mComponent.flattenToShortString());
1008         pw.print("  Session service="); pw.println(mInfo.getSessionService());
1009         pw.println("  Service info:");
1010         mInfo.getServiceInfo().dump(new PrintWriterPrinter(pw), "    ");
1011         pw.print("  Recognition service="); pw.println(mInfo.getRecognitionService());
1012         pw.print("  Hotword detection service="); pw.println(mInfo.getHotwordDetectionService());
1013         pw.print("  Settings activity="); pw.println(mInfo.getSettingsActivity());
1014         pw.print("  Supports assist="); pw.println(mInfo.getSupportsAssist());
1015         pw.print("  Supports launch from keyguard=");
1016         pw.println(mInfo.getSupportsLaunchFromKeyguard());
1017         if (mDisabledShowContext != 0) {
1018             pw.print("  mDisabledShowContext=");
1019             pw.println(Integer.toHexString(mDisabledShowContext));
1020         }
1021         pw.print("  mBound="); pw.print(mBound);  pw.print(" mService="); pw.println(mService);
1022         if (mHotwordDetectionConnection != null) {
1023             pw.println("  Hotword detection connection:");
1024             mHotwordDetectionConnection.dump("    ", pw);
1025         } else {
1026             pw.println("  No Hotword detection connection");
1027         }
1028         if (mActiveSession != null) {
1029             pw.println("  Active session:");
1030             mActiveSession.dump("    ", pw);
1031         }
1032     }
1033 
getAccessibilityDetectionEnabled()1034     boolean getAccessibilityDetectionEnabled() {
1035         return Settings.Secure.getIntForUser(
1036                 mContext.getContentResolver(),
1037                 Settings.Secure.VISUAL_QUERY_ACCESSIBILITY_DETECTION_ENABLED, 0,
1038                 mUser) == 1;
1039     }
1040 
registerAccessibilityDetectionSettingsListenerLocked( IVoiceInteractionAccessibilitySettingsListener listener)1041     void registerAccessibilityDetectionSettingsListenerLocked(
1042             IVoiceInteractionAccessibilitySettingsListener listener) {
1043         if (DEBUG) {
1044             Slog.d(TAG, "registerAccessibilityDetectionSettingsListener");
1045         }
1046         mAccessibilitySettingsListeners.add(listener);
1047     }
1048 
unregisterAccessibilityDetectionSettingsListenerLocked( IVoiceInteractionAccessibilitySettingsListener listener)1049     void unregisterAccessibilityDetectionSettingsListenerLocked(
1050             IVoiceInteractionAccessibilitySettingsListener listener) {
1051         if (DEBUG) {
1052             Slog.d(TAG, "unregisterAccessibilityDetectionSettingsListener");
1053         }
1054         mAccessibilitySettingsListeners.remove(listener);
1055     }
1056 
startLocked()1057     void startLocked() {
1058         Intent intent = new Intent(VoiceInteractionService.SERVICE_INTERFACE);
1059         intent.setComponent(mComponent);
1060         mBound = mContext.bindServiceAsUser(intent, mConnection,
1061                 Context.BIND_AUTO_CREATE | Context.BIND_FOREGROUND_SERVICE
1062                 | Context.BIND_INCLUDE_CAPABILITIES
1063                 | Context.BIND_ALLOW_BACKGROUND_ACTIVITY_STARTS, new UserHandle(mUser));
1064         if (!mBound) {
1065             Slog.w(TAG, "Failed binding to voice interaction service " + mComponent);
1066         }
1067     }
1068 
launchVoiceAssistFromKeyguard()1069     public void launchVoiceAssistFromKeyguard() {
1070         if (mService == null) {
1071             Slog.w(TAG, "Not bound to voice interaction service " + mComponent);
1072             return;
1073         }
1074         try {
1075             mService.launchVoiceAssistFromKeyguard();
1076         } catch (RemoteException e) {
1077             Slog.w(TAG, "RemoteException while calling launchVoiceAssistFromKeyguard", e);
1078         }
1079     }
1080 
shutdownLocked()1081     void shutdownLocked() {
1082         // If there is an active session, cancel it to allow it to clean up its window and other
1083         // state.
1084         if (mActiveSession != null) {
1085             mActiveSession.cancelLocked(false);
1086             mActiveSession = null;
1087         }
1088         try {
1089             if (mService != null) {
1090                 mService.shutdown();
1091             }
1092         } catch (RemoteException e) {
1093             Slog.w(TAG, "RemoteException in shutdown", e);
1094         }
1095         if (mHotwordDetectionConnection != null) {
1096             mHotwordDetectionConnection.cancelLocked();
1097             unregisterAccessibilityDetectionSettingsListenerLocked(
1098                     mHotwordDetectionConnection.mAccessibilitySettingsListener);
1099             mHotwordDetectionConnection = null;
1100         }
1101         if (mBound) {
1102             mContext.unbindService(mConnection);
1103             mBound = false;
1104         }
1105         if (mValid) {
1106             mContext.unregisterReceiver(mBroadcastReceiver);
1107         }
1108     }
1109 
notifySoundModelsChangedLocked()1110     void notifySoundModelsChangedLocked() {
1111         if (mService == null) {
1112             Slog.w(TAG, "Not bound to voice interaction service " + mComponent);
1113             return;
1114         }
1115         try {
1116             mService.soundModelsChanged();
1117         } catch (RemoteException e) {
1118             Slog.w(TAG, "RemoteException while calling soundModelsChanged", e);
1119         }
1120     }
1121 
1122     @Override
sessionConnectionGone(VoiceInteractionSessionConnection connection)1123     public void sessionConnectionGone(VoiceInteractionSessionConnection connection) {
1124         synchronized (mServiceStub) {
1125             finishLocked(connection.mToken, false);
1126         }
1127     }
1128 
1129     @Override
onSessionShown(VoiceInteractionSessionConnection connection)1130     public void onSessionShown(VoiceInteractionSessionConnection connection) {
1131         mServiceStub.onSessionShown();
1132     }
1133 
1134     @Override
onSessionHidden(VoiceInteractionSessionConnection connection)1135     public void onSessionHidden(VoiceInteractionSessionConnection connection) {
1136         mServiceStub.onSessionHidden();
1137         // Notifies visibility change here can cause duplicate events, it is added to make sure
1138         // client always get the callback even if session is unexpectedly closed.
1139         mServiceStub.setSessionWindowVisible(connection.mToken, false);
1140     }
1141 
1142     interface DetectorRemoteExceptionListener {
onDetectorRemoteException(@onNull IBinder token, int detectorType)1143         void onDetectorRemoteException(@NonNull IBinder token, int detectorType);
1144     }
1145 
1146     private final class AccessibilitySettingsContentObserver extends ContentObserver {
1147         private Uri mAccessibilitySettingsEnabledUri = Settings.Secure.getUriFor(
1148                 Settings.Secure.VISUAL_QUERY_ACCESSIBILITY_DETECTION_ENABLED);
1149 
AccessibilitySettingsContentObserver()1150         AccessibilitySettingsContentObserver() {
1151             super(null);
1152         }
1153 
register(ContentResolver contentResolver)1154         public void register(ContentResolver contentResolver) {
1155             contentResolver.registerContentObserver(
1156                     mAccessibilitySettingsEnabledUri, false, this, UserHandle.USER_ALL);
1157         }
1158 
1159         @Override
onChange(boolean selfChange, Uri uri)1160         public void onChange(boolean selfChange, Uri uri) {
1161             Slog.i(TAG, "OnChange called with uri:" + uri);
1162             if (mAccessibilitySettingsEnabledUri.equals(uri)) {
1163                     boolean enable = Settings.Secure.getIntForUser(
1164                             mContext.getContentResolver(),
1165                             Settings.Secure.VISUAL_QUERY_ACCESSIBILITY_DETECTION_ENABLED, 0,
1166                             mUser) == 1;
1167                     Slog.i(TAG, "Notifying listeners with Accessibility setting set to "
1168                             + enable);
1169                     mAccessibilitySettingsListeners.forEach(
1170                             listener -> {
1171                                 try {
1172                                     listener.onAccessibilityDetectionChanged(enable);
1173                                 } catch (RemoteException e) {
1174                                     e.rethrowFromSystemServer();
1175                                 }
1176                             }
1177                     );
1178 
1179             }
1180         }
1181     }
1182 }
1183