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