VoiceInteractionManagerService.java revision 5e33fb057c20b84418d96574abe861e9d05956eb
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            try {
305                if (mDbHelper.deleteKeyphraseSoundModel(keyphraseId)) {
306                    synchronized (this) {
307                        // Notify the voice interaction service of a change in sound models.
308                        if (mImpl != null && mImpl.mService != null) {
309                            mImpl.notifySoundModelsChangedLocked();
310                        }
311                    }
312                    return SoundTriggerHelper.STATUS_OK;
313                } else {
314                    return SoundTriggerHelper.STATUS_ERROR;
315                }
316            } finally {
317                Binder.restoreCallingIdentity(caller);
318            }
319        }
320
321        //----------------- SoundTrigger APIs --------------------------------//
322        @Override
323        public boolean isEnrolledForKeyphrase(IVoiceInteractionService service, int keyphraseId) {
324            synchronized (this) {
325                if (mImpl == null || mImpl.mService == null
326                        || service.asBinder() != mImpl.mService.asBinder()) {
327                    throw new SecurityException(
328                            "Caller is not the current voice interaction service");
329                }
330            }
331
332            final long caller = Binder.clearCallingIdentity();
333            try {
334                KeyphraseSoundModel model = mDbHelper.getKeyphraseSoundModel(keyphraseId);
335                return model != null;
336            } finally {
337                Binder.restoreCallingIdentity(caller);
338            }
339        }
340
341        @Override
342        public ModuleProperties getDspModuleProperties(IVoiceInteractionService service) {
343            // Allow the call if this is the current voice interaction service.
344            synchronized (this) {
345                if (mImpl == null || mImpl.mService == null
346                        || service == null || service.asBinder() != mImpl.mService.asBinder()) {
347                    throw new SecurityException(
348                            "Caller is not the current voice interaction service");
349                }
350
351                final long caller = Binder.clearCallingIdentity();
352                try {
353                    return mSoundTriggerHelper.moduleProperties;
354                } finally {
355                    Binder.restoreCallingIdentity(caller);
356                }
357            }
358        }
359
360        @Override
361        public int startRecognition(IVoiceInteractionService service, int keyphraseId,
362                IRecognitionStatusCallback callback, RecognitionConfig recognitionConfig) {
363            // Allow the call if this is the current voice interaction service.
364            synchronized (this) {
365                if (mImpl == null || mImpl.mService == null
366                        || service == null || service.asBinder() != mImpl.mService.asBinder()) {
367                    throw new SecurityException(
368                            "Caller is not the current voice interaction service");
369                }
370
371                if (callback == null || recognitionConfig == null) {
372                    throw new IllegalArgumentException("Illegal argument(s) in startRecognition");
373                }
374            }
375
376            final long caller = Binder.clearCallingIdentity();
377            try {
378                KeyphraseSoundModel soundModel = mDbHelper.getKeyphraseSoundModel(keyphraseId);
379                if (soundModel == null
380                        || soundModel.uuid == null
381                        || soundModel.keyphrases == null) {
382                    Slog.w(TAG, "No matching sound model found in startRecognition");
383                    return SoundTriggerHelper.STATUS_ERROR;
384                } else {
385                    return mSoundTriggerHelper.startRecognition(
386                            keyphraseId, soundModel, callback, recognitionConfig);
387                }
388            } finally {
389                Binder.restoreCallingIdentity(caller);
390            }
391        }
392
393        @Override
394        public int stopRecognition(IVoiceInteractionService service, int keyphraseId,
395                IRecognitionStatusCallback callback) {
396            // Allow the call if this is the current voice interaction service.
397            synchronized (this) {
398                if (mImpl == null || mImpl.mService == null
399                        || service == null || service.asBinder() != mImpl.mService.asBinder()) {
400                    throw new SecurityException(
401                            "Caller is not the current voice interaction service");
402                }
403            }
404
405            final long caller = Binder.clearCallingIdentity();
406            try {
407                return mSoundTriggerHelper.stopRecognition(keyphraseId, callback);
408            } finally {
409                Binder.restoreCallingIdentity(caller);
410            }
411        }
412
413        @Override
414        public void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
415            if (mContext.checkCallingOrSelfPermission(Manifest.permission.DUMP)
416                    != PackageManager.PERMISSION_GRANTED) {
417                pw.println("Permission Denial: can't dump PowerManager from from pid="
418                        + Binder.getCallingPid()
419                        + ", uid=" + Binder.getCallingUid());
420                return;
421            }
422            synchronized (this) {
423                pw.println("VOICE INTERACTION MANAGER (dumpsys voiceinteraction)\n");
424                if (mImpl == null) {
425                    pw.println("  (No active implementation)");
426                    return;
427                }
428                mImpl.dumpLocked(fd, pw, args);
429            }
430        }
431
432        class SettingsObserver extends ContentObserver {
433            SettingsObserver(Handler handler) {
434                super(handler);
435                ContentResolver resolver = mContext.getContentResolver();
436                resolver.registerContentObserver(Settings.Secure.getUriFor(
437                        Settings.Secure.VOICE_INTERACTION_SERVICE), false, this);
438            }
439
440            @Override public void onChange(boolean selfChange) {
441                synchronized (VoiceInteractionManagerServiceStub.this) {
442                    switchImplementationIfNeededLocked(false);
443                }
444            }
445        }
446
447        PackageMonitor mPackageMonitor = new PackageMonitor() {
448            @Override
449            public boolean onHandleForceStop(Intent intent, String[] packages, int uid, boolean doit) {
450                return super.onHandleForceStop(intent, packages, uid, doit);
451            }
452
453            @Override
454            public void onHandleUserStop(Intent intent, int userHandle) {
455            }
456
457            @Override
458            public void onPackageDisappeared(String packageName, int reason) {
459            }
460
461            @Override
462            public void onPackageAppeared(String packageName, int reason) {
463                if (mImpl != null && packageName.equals(mImpl.mComponent.getPackageName())) {
464                    switchImplementationIfNeededLocked(true);
465                }
466            }
467
468            @Override
469            public void onPackageModified(String packageName) {
470            }
471
472            @Override
473            public void onSomePackagesChanged() {
474            }
475        };
476    }
477}
478