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