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