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