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