VoiceInteractionManagerService.java revision 256e1a62673472d685232d88ad4d067eb82deeac
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.Manifest; 20import android.app.ActivityManager; 21import android.content.ComponentName; 22import android.content.ContentResolver; 23import android.content.Context; 24import android.content.Intent; 25import android.content.pm.PackageManager; 26import android.database.ContentObserver; 27import android.hardware.soundtrigger.IRecognitionStatusCallback; 28import android.hardware.soundtrigger.SoundTrigger.KeyphraseSoundModel; 29import android.hardware.soundtrigger.SoundTrigger.ModuleProperties; 30import android.hardware.soundtrigger.SoundTrigger.RecognitionConfig; 31import android.os.Binder; 32import android.os.Bundle; 33import android.os.Handler; 34import android.os.IBinder; 35import android.os.Parcel; 36import android.os.RemoteException; 37import android.os.UserHandle; 38import android.provider.Settings; 39import android.service.voice.IVoiceInteractionService; 40import android.service.voice.IVoiceInteractionSession; 41import android.util.Slog; 42 43import com.android.internal.app.IVoiceInteractionManagerService; 44import com.android.internal.app.IVoiceInteractor; 45import com.android.internal.content.PackageMonitor; 46import com.android.internal.os.BackgroundThread; 47import com.android.server.SystemService; 48import com.android.server.UiThread; 49 50import java.io.FileDescriptor; 51import java.io.PrintWriter; 52 53/** 54 * SystemService that publishes an IVoiceInteractionManagerService. 55 */ 56public class VoiceInteractionManagerService extends SystemService { 57 58 static final String TAG = "VoiceInteractionManagerService"; 59 60 final Context mContext; 61 final ContentResolver mResolver; 62 final DatabaseHelper mDbHelper; 63 final SoundTriggerHelper mSoundTriggerHelper; 64 65 public VoiceInteractionManagerService(Context context) { 66 super(context); 67 mContext = context; 68 mResolver = context.getContentResolver(); 69 mDbHelper = new DatabaseHelper(context); 70 mSoundTriggerHelper = new SoundTriggerHelper(); 71 } 72 73 @Override 74 public void onStart() { 75 publishBinderService(Context.VOICE_INTERACTION_MANAGER_SERVICE, mServiceStub); 76 } 77 78 @Override 79 public void onBootPhase(int phase) { 80 if (phase == PHASE_THIRD_PARTY_APPS_CAN_START) { 81 mServiceStub.systemRunning(isSafeMode()); 82 } 83 } 84 85 @Override 86 public void onSwitchUser(int userHandle) { 87 mServiceStub.switchUser(userHandle); 88 } 89 90 // implementation entry point and binder service 91 private final VoiceInteractionManagerServiceStub mServiceStub 92 = new VoiceInteractionManagerServiceStub(); 93 94 class VoiceInteractionManagerServiceStub extends IVoiceInteractionManagerService.Stub { 95 96 VoiceInteractionManagerServiceImpl mImpl; 97 98 private boolean mSafeMode; 99 private int mCurUser; 100 101 @Override 102 public boolean onTransact(int code, Parcel data, Parcel reply, int flags) 103 throws RemoteException { 104 try { 105 return super.onTransact(code, data, reply, flags); 106 } catch (RuntimeException e) { 107 // The activity manager only throws security exceptions, so let's 108 // log all others. 109 if (!(e instanceof SecurityException)) { 110 Slog.wtf(TAG, "VoiceInteractionManagerService Crash", e); 111 } 112 throw e; 113 } 114 } 115 116 public void systemRunning(boolean safeMode) { 117 mSafeMode = safeMode; 118 119 mPackageMonitor.register(mContext, BackgroundThread.getHandler().getLooper(), 120 UserHandle.ALL, true); 121 new SettingsObserver(UiThread.getHandler()); 122 123 synchronized (this) { 124 mCurUser = ActivityManager.getCurrentUser(); 125 switchImplementationIfNeededLocked(false); 126 } 127 } 128 129 public void switchUser(int userHandle) { 130 synchronized (this) { 131 mCurUser = userHandle; 132 switchImplementationIfNeededLocked(false); 133 } 134 } 135 136 void switchImplementationIfNeededLocked(boolean force) { 137 if (!mSafeMode) { 138 String curService = Settings.Secure.getStringForUser( 139 mResolver, Settings.Secure.VOICE_INTERACTION_SERVICE, mCurUser); 140 ComponentName serviceComponent = null; 141 if (curService != null && !curService.isEmpty()) { 142 try { 143 serviceComponent = ComponentName.unflattenFromString(curService); 144 } catch (RuntimeException e) { 145 Slog.wtf(TAG, "Bad voice interaction service name " + curService, e); 146 serviceComponent = null; 147 } 148 } 149 if (force || mImpl == null || mImpl.mUser != mCurUser 150 || !mImpl.mComponent.equals(serviceComponent)) { 151 mSoundTriggerHelper.stopAllRecognitions(); 152 if (mImpl != null) { 153 mImpl.shutdownLocked(); 154 } 155 if (serviceComponent != null) { 156 mImpl = new VoiceInteractionManagerServiceImpl(mContext, 157 UiThread.getHandler(), this, mCurUser, serviceComponent); 158 mImpl.startLocked(); 159 } else { 160 mImpl = null; 161 } 162 } 163 } 164 } 165 166 @Override 167 public void startSession(IVoiceInteractionService service, Bundle args) { 168 synchronized (this) { 169 if (mImpl == null || mImpl.mService == null 170 || service.asBinder() != mImpl.mService.asBinder()) { 171 throw new SecurityException( 172 "Caller is not the current voice interaction service"); 173 } 174 final int callingPid = Binder.getCallingPid(); 175 final int callingUid = Binder.getCallingUid(); 176 final long caller = Binder.clearCallingIdentity(); 177 try { 178 mImpl.startSessionLocked(callingPid, callingUid, args); 179 } finally { 180 Binder.restoreCallingIdentity(caller); 181 } 182 } 183 } 184 185 @Override 186 public boolean deliverNewSession(IBinder token, IVoiceInteractionSession session, 187 IVoiceInteractor interactor) { 188 synchronized (this) { 189 if (mImpl == null) { 190 throw new SecurityException( 191 "deliverNewSession without running voice interaction service"); 192 } 193 final int callingPid = Binder.getCallingPid(); 194 final int callingUid = Binder.getCallingUid(); 195 final long caller = Binder.clearCallingIdentity(); 196 try { 197 return mImpl.deliverNewSessionLocked(callingPid, callingUid, token, session, 198 interactor); 199 } finally { 200 Binder.restoreCallingIdentity(caller); 201 } 202 } 203 } 204 205 @Override 206 public int startVoiceActivity(IBinder token, Intent intent, String resolvedType) { 207 synchronized (this) { 208 if (mImpl == null) { 209 Slog.w(TAG, "startVoiceActivity without running voice interaction service"); 210 return ActivityManager.START_CANCELED; 211 } 212 final int callingPid = Binder.getCallingPid(); 213 final int callingUid = Binder.getCallingUid(); 214 final long caller = Binder.clearCallingIdentity(); 215 try { 216 return mImpl.startVoiceActivityLocked(callingPid, callingUid, token, 217 intent, resolvedType); 218 } finally { 219 Binder.restoreCallingIdentity(caller); 220 } 221 } 222 } 223 224 @Override 225 public void finish(IBinder token) { 226 synchronized (this) { 227 if (mImpl == null) { 228 Slog.w(TAG, "finish without running voice interaction service"); 229 return; 230 } 231 final int callingPid = Binder.getCallingPid(); 232 final int callingUid = Binder.getCallingUid(); 233 final long caller = Binder.clearCallingIdentity(); 234 try { 235 mImpl.finishLocked(callingPid, callingUid, token); 236 } finally { 237 Binder.restoreCallingIdentity(caller); 238 } 239 } 240 } 241 242 //----------------- Model management APIs --------------------------------// 243 244 @Override 245 public KeyphraseSoundModel getKeyphraseSoundModel(int keyphraseId) { 246 synchronized (this) { 247 if (mContext.checkCallingPermission(Manifest.permission.MANAGE_VOICE_KEYPHRASES) 248 != PackageManager.PERMISSION_GRANTED) { 249 throw new SecurityException("Caller does not hold the permission " 250 + Manifest.permission.MANAGE_VOICE_KEYPHRASES); 251 } 252 } 253 254 final long caller = Binder.clearCallingIdentity(); 255 try { 256 return mDbHelper.getKeyphraseSoundModel(keyphraseId); 257 } finally { 258 Binder.restoreCallingIdentity(caller); 259 } 260 } 261 262 @Override 263 public int updateKeyphraseSoundModel(KeyphraseSoundModel model) { 264 synchronized (this) { 265 if (mContext.checkCallingPermission(Manifest.permission.MANAGE_VOICE_KEYPHRASES) 266 != PackageManager.PERMISSION_GRANTED) { 267 throw new SecurityException("Caller does not hold the permission " 268 + Manifest.permission.MANAGE_VOICE_KEYPHRASES); 269 } 270 if (model == null) { 271 throw new IllegalArgumentException("Model must not be null"); 272 } 273 } 274 275 final long caller = Binder.clearCallingIdentity(); 276 try { 277 if (mDbHelper.updateKeyphraseSoundModel(model)) { 278 synchronized (this) { 279 // Notify the voice interaction service of a change in sound models. 280 if (mImpl != null && mImpl.mService != null) { 281 mImpl.notifySoundModelsChangedLocked(); 282 } 283 } 284 return SoundTriggerHelper.STATUS_OK; 285 } else { 286 return SoundTriggerHelper.STATUS_ERROR; 287 } 288 } finally { 289 Binder.restoreCallingIdentity(caller); 290 } 291 } 292 293 @Override 294 public int deleteKeyphraseSoundModel(int keyphraseId) { 295 synchronized (this) { 296 if (mContext.checkCallingPermission(Manifest.permission.MANAGE_VOICE_KEYPHRASES) 297 != PackageManager.PERMISSION_GRANTED) { 298 throw new SecurityException("Caller does not hold the permission " 299 + Manifest.permission.MANAGE_VOICE_KEYPHRASES); 300 } 301 } 302 303 final long caller = Binder.clearCallingIdentity(); 304 boolean deleted = false; 305 try { 306 KeyphraseSoundModel soundModel = mDbHelper.getKeyphraseSoundModel(keyphraseId); 307 if (soundModel != null) { 308 deleted = mDbHelper.deleteKeyphraseSoundModel(soundModel.uuid); 309 } 310 return deleted ? SoundTriggerHelper.STATUS_OK : SoundTriggerHelper.STATUS_ERROR; 311 } finally { 312 if (deleted) { 313 synchronized (this) { 314 // Notify the voice interaction service of a change in sound models. 315 if (mImpl != null && mImpl.mService != null) { 316 mImpl.notifySoundModelsChangedLocked(); 317 } 318 } 319 } 320 Binder.restoreCallingIdentity(caller); 321 } 322 } 323 324 //----------------- SoundTrigger APIs --------------------------------// 325 @Override 326 public boolean isEnrolledForKeyphrase(IVoiceInteractionService service, int keyphraseId) { 327 synchronized (this) { 328 if (mImpl == null || mImpl.mService == null 329 || service.asBinder() != mImpl.mService.asBinder()) { 330 throw new SecurityException( 331 "Caller is not the current voice interaction service"); 332 } 333 } 334 335 final long caller = Binder.clearCallingIdentity(); 336 try { 337 KeyphraseSoundModel model = mDbHelper.getKeyphraseSoundModel(keyphraseId); 338 return model != null; 339 } finally { 340 Binder.restoreCallingIdentity(caller); 341 } 342 } 343 344 @Override 345 public ModuleProperties getDspModuleProperties(IVoiceInteractionService service) { 346 // Allow the call if this is the current voice interaction service. 347 synchronized (this) { 348 if (mImpl == null || mImpl.mService == null 349 || service == null || service.asBinder() != mImpl.mService.asBinder()) { 350 throw new SecurityException( 351 "Caller is not the current voice interaction service"); 352 } 353 354 final long caller = Binder.clearCallingIdentity(); 355 try { 356 return mSoundTriggerHelper.moduleProperties; 357 } finally { 358 Binder.restoreCallingIdentity(caller); 359 } 360 } 361 } 362 363 @Override 364 public int startRecognition(IVoiceInteractionService service, int keyphraseId, 365 IRecognitionStatusCallback callback, RecognitionConfig recognitionConfig) { 366 // Allow the call if this is the current voice interaction service. 367 synchronized (this) { 368 if (mImpl == null || mImpl.mService == null 369 || service == null || service.asBinder() != mImpl.mService.asBinder()) { 370 throw new SecurityException( 371 "Caller is not the current voice interaction service"); 372 } 373 374 if (callback == null || recognitionConfig == null) { 375 throw new IllegalArgumentException("Illegal argument(s) in startRecognition"); 376 } 377 } 378 379 final long caller = Binder.clearCallingIdentity(); 380 try { 381 KeyphraseSoundModel soundModel = mDbHelper.getKeyphraseSoundModel(keyphraseId); 382 if (soundModel == null 383 || soundModel.uuid == null 384 || soundModel.keyphrases == null) { 385 Slog.w(TAG, "No matching sound model found in startRecognition"); 386 return SoundTriggerHelper.STATUS_ERROR; 387 } else { 388 return mSoundTriggerHelper.startRecognition( 389 keyphraseId, soundModel, callback, recognitionConfig); 390 } 391 } finally { 392 Binder.restoreCallingIdentity(caller); 393 } 394 } 395 396 @Override 397 public int stopRecognition(IVoiceInteractionService service, int keyphraseId, 398 IRecognitionStatusCallback callback) { 399 // Allow the call if this is the current voice interaction service. 400 synchronized (this) { 401 if (mImpl == null || mImpl.mService == null 402 || service == null || service.asBinder() != mImpl.mService.asBinder()) { 403 throw new SecurityException( 404 "Caller is not the current voice interaction service"); 405 } 406 } 407 408 final long caller = Binder.clearCallingIdentity(); 409 try { 410 return mSoundTriggerHelper.stopRecognition(keyphraseId, callback); 411 } finally { 412 Binder.restoreCallingIdentity(caller); 413 } 414 } 415 416 @Override 417 public void dump(FileDescriptor fd, PrintWriter pw, String[] args) { 418 if (mContext.checkCallingOrSelfPermission(Manifest.permission.DUMP) 419 != PackageManager.PERMISSION_GRANTED) { 420 pw.println("Permission Denial: can't dump PowerManager from from pid=" 421 + Binder.getCallingPid() 422 + ", uid=" + Binder.getCallingUid()); 423 return; 424 } 425 synchronized (this) { 426 pw.println("VOICE INTERACTION MANAGER (dumpsys voiceinteraction)\n"); 427 if (mImpl == null) { 428 pw.println(" (No active implementation)"); 429 return; 430 } 431 mImpl.dumpLocked(fd, pw, args); 432 } 433 } 434 435 class SettingsObserver extends ContentObserver { 436 SettingsObserver(Handler handler) { 437 super(handler); 438 ContentResolver resolver = mContext.getContentResolver(); 439 resolver.registerContentObserver(Settings.Secure.getUriFor( 440 Settings.Secure.VOICE_INTERACTION_SERVICE), false, this); 441 } 442 443 @Override public void onChange(boolean selfChange) { 444 synchronized (VoiceInteractionManagerServiceStub.this) { 445 switchImplementationIfNeededLocked(false); 446 } 447 } 448 } 449 450 PackageMonitor mPackageMonitor = new PackageMonitor() { 451 @Override 452 public boolean onHandleForceStop(Intent intent, String[] packages, int uid, boolean doit) { 453 return super.onHandleForceStop(intent, packages, uid, doit); 454 } 455 456 @Override 457 public void onHandleUserStop(Intent intent, int userHandle) { 458 } 459 460 @Override 461 public void onPackageDisappeared(String packageName, int reason) { 462 } 463 464 @Override 465 public void onPackageAppeared(String packageName, int reason) { 466 if (mImpl != null && packageName.equals(mImpl.mComponent.getPackageName())) { 467 switchImplementationIfNeededLocked(true); 468 } 469 } 470 471 @Override 472 public void onPackageModified(String packageName) { 473 } 474 475 @Override 476 public void onSomePackagesChanged() { 477 } 478 }; 479 } 480} 481