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