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