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 24 import android.app.ActivityManager; 25 import android.app.ActivityManager.StackId; 26 import android.app.ActivityManagerInternal; 27 import android.app.ActivityOptions; 28 import android.app.IActivityManager; 29 import android.content.BroadcastReceiver; 30 import android.content.ComponentName; 31 import android.content.Context; 32 import android.content.Intent; 33 import android.content.IntentFilter; 34 import android.content.ServiceConnection; 35 import android.content.pm.PackageManager; 36 import android.os.Bundle; 37 import android.os.Handler; 38 import android.os.IBinder; 39 import android.os.RemoteException; 40 import android.os.ServiceManager; 41 import android.os.UserHandle; 42 import android.service.voice.IVoiceInteractionService; 43 import android.service.voice.IVoiceInteractionSession; 44 import android.service.voice.VoiceInteractionService; 45 import android.service.voice.VoiceInteractionServiceInfo; 46 import android.util.PrintWriterPrinter; 47 import android.util.Slog; 48 import android.view.IWindowManager; 49 50 import com.android.internal.app.IVoiceInteractionSessionListener; 51 import com.android.internal.app.IVoiceInteractionSessionShowCallback; 52 import com.android.internal.app.IVoiceInteractor; 53 import com.android.server.LocalServices; 54 55 import java.io.FileDescriptor; 56 import java.io.PrintWriter; 57 import java.util.List; 58 59 class VoiceInteractionManagerServiceImpl implements VoiceInteractionSessionConnection.Callback { 60 final static String TAG = "VoiceInteractionServiceManager"; 61 62 final static String CLOSE_REASON_VOICE_INTERACTION = "voiceinteraction"; 63 64 final boolean mValid; 65 66 final Context mContext; 67 final Handler mHandler; 68 final VoiceInteractionManagerService.VoiceInteractionManagerServiceStub mServiceStub; 69 final int mUser; 70 final ComponentName mComponent; 71 final IActivityManager mAm; 72 final VoiceInteractionServiceInfo mInfo; 73 final ComponentName mSessionComponentName; 74 final IWindowManager mIWindowManager; 75 boolean mBound = false; 76 IVoiceInteractionService mService; 77 78 VoiceInteractionSessionConnection mActiveSession; 79 int mDisabledShowContext; 80 81 final BroadcastReceiver mBroadcastReceiver = new BroadcastReceiver() { 82 @Override 83 public void onReceive(Context context, Intent intent) { 84 if (Intent.ACTION_CLOSE_SYSTEM_DIALOGS.equals(intent.getAction())) { 85 String reason = intent.getStringExtra("reason"); 86 if (!CLOSE_REASON_VOICE_INTERACTION.equals(reason) && !"dream".equals(reason)) { 87 synchronized (mServiceStub) { 88 if (mActiveSession != null && mActiveSession.mSession != null) { 89 try { 90 mActiveSession.mSession.closeSystemDialogs(); 91 } catch (RemoteException e) { 92 } 93 } 94 } 95 } 96 } 97 } 98 }; 99 100 final ServiceConnection mConnection = new ServiceConnection() { 101 @Override 102 public void onServiceConnected(ComponentName name, IBinder service) { 103 synchronized (mServiceStub) { 104 mService = IVoiceInteractionService.Stub.asInterface(service); 105 try { 106 mService.ready(); 107 } catch (RemoteException e) { 108 } 109 } 110 } 111 112 @Override 113 public void onServiceDisconnected(ComponentName name) { 114 mService = null; 115 } 116 }; 117 VoiceInteractionManagerServiceImpl(Context context, Handler handler, VoiceInteractionManagerService.VoiceInteractionManagerServiceStub stub, int userHandle, ComponentName service)118 VoiceInteractionManagerServiceImpl(Context context, Handler handler, 119 VoiceInteractionManagerService.VoiceInteractionManagerServiceStub stub, 120 int userHandle, ComponentName service) { 121 mContext = context; 122 mHandler = handler; 123 mServiceStub = stub; 124 mUser = userHandle; 125 mComponent = service; 126 mAm = ActivityManager.getService(); 127 VoiceInteractionServiceInfo info; 128 try { 129 info = new VoiceInteractionServiceInfo(context.getPackageManager(), service, mUser); 130 } catch (PackageManager.NameNotFoundException e) { 131 Slog.w(TAG, "Voice interaction service not found: " + service, e); 132 mInfo = null; 133 mSessionComponentName = null; 134 mIWindowManager = null; 135 mValid = false; 136 return; 137 } 138 mInfo = info; 139 if (mInfo.getParseError() != null) { 140 Slog.w(TAG, "Bad voice interaction service: " + mInfo.getParseError()); 141 mSessionComponentName = null; 142 mIWindowManager = null; 143 mValid = false; 144 return; 145 } 146 mValid = true; 147 mSessionComponentName = new ComponentName(service.getPackageName(), 148 mInfo.getSessionService()); 149 mIWindowManager = IWindowManager.Stub.asInterface( 150 ServiceManager.getService(Context.WINDOW_SERVICE)); 151 IntentFilter filter = new IntentFilter(); 152 filter.addAction(Intent.ACTION_CLOSE_SYSTEM_DIALOGS); 153 mContext.registerReceiver(mBroadcastReceiver, filter, null, handler); 154 } 155 showSessionLocked(Bundle args, int flags, IVoiceInteractionSessionShowCallback showCallback, IBinder activityToken)156 public boolean showSessionLocked(Bundle args, int flags, 157 IVoiceInteractionSessionShowCallback showCallback, IBinder activityToken) { 158 if (mActiveSession == null) { 159 mActiveSession = new VoiceInteractionSessionConnection(mServiceStub, 160 mSessionComponentName, mUser, mContext, this, 161 mInfo.getServiceInfo().applicationInfo.uid, mHandler); 162 } 163 List<IBinder> activityTokens = null; 164 if (activityToken == null) { 165 // Let's get top activities from all visible stacks 166 activityTokens = LocalServices.getService(ActivityManagerInternal.class) 167 .getTopVisibleActivities(); 168 } 169 return mActiveSession.showLocked(args, flags, mDisabledShowContext, showCallback, 170 activityToken, activityTokens); 171 } 172 hideSessionLocked()173 public boolean hideSessionLocked() { 174 if (mActiveSession != null) { 175 return mActiveSession.hideLocked(); 176 } 177 return false; 178 } 179 deliverNewSessionLocked(IBinder token, IVoiceInteractionSession session, IVoiceInteractor interactor)180 public boolean deliverNewSessionLocked(IBinder token, 181 IVoiceInteractionSession session, IVoiceInteractor interactor) { 182 if (mActiveSession == null || token != mActiveSession.mToken) { 183 Slog.w(TAG, "deliverNewSession does not match active session"); 184 return false; 185 } 186 mActiveSession.deliverNewSessionLocked(session, interactor); 187 return true; 188 } 189 startVoiceActivityLocked(int callingPid, int callingUid, IBinder token, Intent intent, String resolvedType)190 public int startVoiceActivityLocked(int callingPid, int callingUid, IBinder token, 191 Intent intent, String resolvedType) { 192 try { 193 if (mActiveSession == null || token != mActiveSession.mToken) { 194 Slog.w(TAG, "startVoiceActivity does not match active session"); 195 return START_VOICE_NOT_ACTIVE_SESSION; 196 } 197 if (!mActiveSession.mShown) { 198 Slog.w(TAG, "startVoiceActivity not allowed on hidden session"); 199 return START_VOICE_HIDDEN_SESSION; 200 } 201 intent = new Intent(intent); 202 intent.addCategory(Intent.CATEGORY_VOICE); 203 intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_MULTIPLE_TASK); 204 return mAm.startVoiceActivity(mComponent.getPackageName(), callingPid, callingUid, 205 intent, resolvedType, mActiveSession.mSession, mActiveSession.mInteractor, 206 0, null, null, mUser); 207 } catch (RemoteException e) { 208 throw new IllegalStateException("Unexpected remote error", e); 209 } 210 } 211 startAssistantActivityLocked(int callingPid, int callingUid, IBinder token, Intent intent, String resolvedType)212 public int startAssistantActivityLocked(int callingPid, int callingUid, IBinder token, 213 Intent intent, String resolvedType) { 214 try { 215 if (mActiveSession == null || token != mActiveSession.mToken) { 216 Slog.w(TAG, "startAssistantActivity does not match active session"); 217 return START_ASSISTANT_NOT_ACTIVE_SESSION; 218 } 219 if (!mActiveSession.mShown) { 220 Slog.w(TAG, "startAssistantActivity not allowed on hidden session"); 221 return START_ASSISTANT_HIDDEN_SESSION; 222 } 223 intent = new Intent(intent); 224 intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK); 225 ActivityOptions options = ActivityOptions.makeBasic(); 226 options.setLaunchStackId(StackId.ASSISTANT_STACK_ID); 227 return mAm.startAssistantActivity(mComponent.getPackageName(), callingPid, callingUid, 228 intent, resolvedType, options.toBundle(), mUser); 229 } catch (RemoteException e) { 230 throw new IllegalStateException("Unexpected remote error", e); 231 } 232 } 233 setKeepAwakeLocked(IBinder token, boolean keepAwake)234 public void setKeepAwakeLocked(IBinder token, boolean keepAwake) { 235 try { 236 if (mActiveSession == null || token != mActiveSession.mToken) { 237 Slog.w(TAG, "setKeepAwake does not match active session"); 238 return; 239 } 240 mAm.setVoiceKeepAwake(mActiveSession.mSession, keepAwake); 241 } catch (RemoteException e) { 242 throw new IllegalStateException("Unexpected remote error", e); 243 } 244 } 245 closeSystemDialogsLocked(IBinder token)246 public void closeSystemDialogsLocked(IBinder token) { 247 try { 248 if (mActiveSession == null || token != mActiveSession.mToken) { 249 Slog.w(TAG, "closeSystemDialogs does not match active session"); 250 return; 251 } 252 mAm.closeSystemDialogs(CLOSE_REASON_VOICE_INTERACTION); 253 } catch (RemoteException e) { 254 throw new IllegalStateException("Unexpected remote error", e); 255 } 256 } 257 finishLocked(IBinder token, boolean finishTask)258 public void finishLocked(IBinder token, boolean finishTask) { 259 if (mActiveSession == null || (!finishTask && token != mActiveSession.mToken)) { 260 Slog.w(TAG, "finish does not match active session"); 261 return; 262 } 263 mActiveSession.cancelLocked(finishTask); 264 mActiveSession = null; 265 } 266 setDisabledShowContextLocked(int callingUid, int flags)267 public void setDisabledShowContextLocked(int callingUid, int flags) { 268 int activeUid = mInfo.getServiceInfo().applicationInfo.uid; 269 if (callingUid != activeUid) { 270 throw new SecurityException("Calling uid " + callingUid 271 + " does not match active uid " + activeUid); 272 } 273 mDisabledShowContext = flags; 274 } 275 getDisabledShowContextLocked(int callingUid)276 public int getDisabledShowContextLocked(int callingUid) { 277 int activeUid = mInfo.getServiceInfo().applicationInfo.uid; 278 if (callingUid != activeUid) { 279 throw new SecurityException("Calling uid " + callingUid 280 + " does not match active uid " + activeUid); 281 } 282 return mDisabledShowContext; 283 } 284 getUserDisabledShowContextLocked(int callingUid)285 public int getUserDisabledShowContextLocked(int callingUid) { 286 int activeUid = mInfo.getServiceInfo().applicationInfo.uid; 287 if (callingUid != activeUid) { 288 throw new SecurityException("Calling uid " + callingUid 289 + " does not match active uid " + activeUid); 290 } 291 return mActiveSession != null ? mActiveSession.getUserDisabledShowContextLocked() : 0; 292 } 293 supportsLocalVoiceInteraction()294 public boolean supportsLocalVoiceInteraction() { 295 return mInfo.getSupportsLocalInteraction(); 296 } 297 dumpLocked(FileDescriptor fd, PrintWriter pw, String[] args)298 public void dumpLocked(FileDescriptor fd, PrintWriter pw, String[] args) { 299 if (!mValid) { 300 pw.print(" NOT VALID: "); 301 if (mInfo == null) { 302 pw.println("no info"); 303 } else { 304 pw.println(mInfo.getParseError()); 305 } 306 return; 307 } 308 pw.print(" mUser="); pw.println(mUser); 309 pw.print(" mComponent="); pw.println(mComponent.flattenToShortString()); 310 pw.print(" Session service="); pw.println(mInfo.getSessionService()); 311 pw.println(" Service info:"); 312 mInfo.getServiceInfo().dump(new PrintWriterPrinter(pw), " "); 313 pw.print(" Recognition service="); pw.println(mInfo.getRecognitionService()); 314 pw.print(" Settings activity="); pw.println(mInfo.getSettingsActivity()); 315 pw.print(" Supports assist="); pw.println(mInfo.getSupportsAssist()); 316 pw.print(" Supports launch from keyguard="); 317 pw.println(mInfo.getSupportsLaunchFromKeyguard()); 318 if (mDisabledShowContext != 0) { 319 pw.print(" mDisabledShowContext="); 320 pw.println(Integer.toHexString(mDisabledShowContext)); 321 } 322 pw.print(" mBound="); pw.print(mBound); pw.print(" mService="); pw.println(mService); 323 if (mActiveSession != null) { 324 pw.println(" Active session:"); 325 mActiveSession.dump(" ", pw); 326 } 327 } 328 startLocked()329 void startLocked() { 330 Intent intent = new Intent(VoiceInteractionService.SERVICE_INTERFACE); 331 intent.setComponent(mComponent); 332 mBound = mContext.bindServiceAsUser(intent, mConnection, 333 Context.BIND_AUTO_CREATE | Context.BIND_FOREGROUND_SERVICE, new UserHandle(mUser)); 334 if (!mBound) { 335 Slog.w(TAG, "Failed binding to voice interaction service " + mComponent); 336 } 337 } 338 launchVoiceAssistFromKeyguard()339 public void launchVoiceAssistFromKeyguard() { 340 if (mService == null) { 341 Slog.w(TAG, "Not bound to voice interaction service " + mComponent); 342 return; 343 } 344 try { 345 mService.launchVoiceAssistFromKeyguard(); 346 } catch (RemoteException e) { 347 Slog.w(TAG, "RemoteException while calling launchVoiceAssistFromKeyguard", e); 348 } 349 } 350 shutdownLocked()351 void shutdownLocked() { 352 // If there is an active session, cancel it to allow it to clean up its window and other 353 // state. 354 if (mActiveSession != null) { 355 mActiveSession.cancelLocked(false); 356 mActiveSession = null; 357 } 358 try { 359 if (mService != null) { 360 mService.shutdown(); 361 } 362 } catch (RemoteException e) { 363 Slog.w(TAG, "RemoteException in shutdown", e); 364 } 365 366 if (mBound) { 367 mContext.unbindService(mConnection); 368 mBound = false; 369 } 370 if (mValid) { 371 mContext.unregisterReceiver(mBroadcastReceiver); 372 } 373 } 374 notifySoundModelsChangedLocked()375 void notifySoundModelsChangedLocked() { 376 if (mService == null) { 377 Slog.w(TAG, "Not bound to voice interaction service " + mComponent); 378 return; 379 } 380 try { 381 mService.soundModelsChanged(); 382 } catch (RemoteException e) { 383 Slog.w(TAG, "RemoteException while calling soundModelsChanged", e); 384 } 385 } 386 387 @Override sessionConnectionGone(VoiceInteractionSessionConnection connection)388 public void sessionConnectionGone(VoiceInteractionSessionConnection connection) { 389 synchronized (mServiceStub) { 390 finishLocked(connection.mToken, false); 391 } 392 } 393 394 @Override onSessionShown(VoiceInteractionSessionConnection connection)395 public void onSessionShown(VoiceInteractionSessionConnection connection) { 396 mServiceStub.onSessionShown(); 397 } 398 399 @Override onSessionHidden(VoiceInteractionSessionConnection connection)400 public void onSessionHidden(VoiceInteractionSessionConnection connection) { 401 mServiceStub.onSessionHidden(); 402 } 403 } 404