VoiceInteractionManagerService.java revision 6daae9622672e0b38fc2efed29f68061d749cacc
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;
52import java.util.List;
53
54
55/**
56 * SystemService that publishes an IVoiceInteractionManagerService.
57 */
58public class VoiceInteractionManagerService extends SystemService {
59
60    static final String TAG = "VoiceInteractionManagerService";
61
62    final Context mContext;
63    final ContentResolver mResolver;
64    final DatabaseHelper mDbHelper;
65    final SoundTriggerHelper mSoundTriggerHelper;
66
67    public VoiceInteractionManagerService(Context context) {
68        super(context);
69        mContext = context;
70        mResolver = context.getContentResolver();
71        mDbHelper = new DatabaseHelper(context);
72        mSoundTriggerHelper = new SoundTriggerHelper();
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                    if (mImpl != null) {
154                        mImpl.shutdownLocked();
155                    }
156                    if (serviceComponent != null) {
157                        mImpl = new VoiceInteractionManagerServiceImpl(mContext,
158                                UiThread.getHandler(), this, mCurUser, serviceComponent);
159                        mImpl.startLocked();
160                    } else {
161                        mImpl = null;
162                    }
163                }
164            }
165        }
166
167        @Override
168        public void startSession(IVoiceInteractionService service, Bundle args) {
169            synchronized (this) {
170                if (mImpl == null || mImpl.mService == null
171                        || service.asBinder() != mImpl.mService.asBinder()) {
172                    throw new SecurityException(
173                            "Caller is not the current voice interaction service");
174                }
175                final int callingPid = Binder.getCallingPid();
176                final int callingUid = Binder.getCallingUid();
177                final long caller = Binder.clearCallingIdentity();
178                try {
179                    mImpl.startSessionLocked(callingPid, callingUid, args);
180                } finally {
181                    Binder.restoreCallingIdentity(caller);
182                }
183            }
184        }
185
186        @Override
187        public boolean deliverNewSession(IBinder token, IVoiceInteractionSession session,
188                IVoiceInteractor interactor) {
189            synchronized (this) {
190                if (mImpl == null) {
191                    throw new SecurityException(
192                            "deliverNewSession without running voice interaction service");
193                }
194                final int callingPid = Binder.getCallingPid();
195                final int callingUid = Binder.getCallingUid();
196                final long caller = Binder.clearCallingIdentity();
197                try {
198                    return mImpl.deliverNewSessionLocked(callingPid, callingUid, token, session,
199                            interactor);
200                } finally {
201                    Binder.restoreCallingIdentity(caller);
202                }
203            }
204        }
205
206        @Override
207        public int startVoiceActivity(IBinder token, Intent intent, String resolvedType) {
208            synchronized (this) {
209                if (mImpl == null) {
210                    Slog.w(TAG, "startVoiceActivity without running voice interaction service");
211                    return ActivityManager.START_CANCELED;
212                }
213                final int callingPid = Binder.getCallingPid();
214                final int callingUid = Binder.getCallingUid();
215                final long caller = Binder.clearCallingIdentity();
216                try {
217                    return mImpl.startVoiceActivityLocked(callingPid, callingUid, token,
218                            intent, resolvedType);
219                } finally {
220                    Binder.restoreCallingIdentity(caller);
221                }
222            }
223        }
224
225        @Override
226        public void finish(IBinder token) {
227            synchronized (this) {
228                if (mImpl == null) {
229                    Slog.w(TAG, "finish without running voice interaction service");
230                    return;
231                }
232                final int callingPid = Binder.getCallingPid();
233                final int callingUid = Binder.getCallingUid();
234                final long caller = Binder.clearCallingIdentity();
235                try {
236                    mImpl.finishLocked(callingPid, callingUid, token);
237                } finally {
238                    Binder.restoreCallingIdentity(caller);
239                }
240            }
241        }
242
243        //----------------- Model management APIs --------------------------------//
244
245        @Override
246        public List<KeyphraseSoundModel> listRegisteredKeyphraseSoundModels(
247                IVoiceInteractionService service) {
248            // Allow the call if this is the current voice interaction service
249            // or the caller holds the MANAGE_VOICE_KEYPHRASES permission.
250            synchronized (this) {
251                boolean permissionGranted =
252                        mContext.checkCallingPermission(Manifest.permission.MANAGE_VOICE_KEYPHRASES)
253                        == PackageManager.PERMISSION_GRANTED;
254                boolean currentVoiceInteractionService = service != null
255                        && mImpl != null
256                        && mImpl.mService != null
257                        && service.asBinder() == mImpl.mService.asBinder();
258
259                if (!permissionGranted && !currentVoiceInteractionService) {
260                    if (!currentVoiceInteractionService) {
261                        throw new SecurityException(
262                                "Caller is not the current voice interaction service");
263                    }
264                    if (!permissionGranted) {
265                        throw new SecurityException("Caller does not hold the permission "
266                                + Manifest.permission.MANAGE_VOICE_KEYPHRASES);
267                    }
268                }
269
270                final long caller = Binder.clearCallingIdentity();
271                try {
272                    return mDbHelper.getKephraseSoundModels();
273                } finally {
274                    Binder.restoreCallingIdentity(caller);
275                }
276            }
277        }
278
279        @Override
280        public int updateKeyphraseSoundModel(KeyphraseSoundModel model) {
281            synchronized (this) {
282                if (mContext.checkCallingPermission(Manifest.permission.MANAGE_VOICE_KEYPHRASES)
283                        != PackageManager.PERMISSION_GRANTED) {
284                    throw new SecurityException("Caller does not hold the permission "
285                            + Manifest.permission.MANAGE_VOICE_KEYPHRASES);
286                }
287                if (model == null) {
288                    throw new IllegalArgumentException("Model must not be null");
289                }
290
291                final long caller = Binder.clearCallingIdentity();
292                try {
293                    boolean success = false;
294                    if (model.keyphrases == null) {
295                        // If the keyphrases are not present in the model, delete the model.
296                        success = mDbHelper.deleteKeyphraseSoundModel(model.uuid);
297                    } else {
298                        // Else update the model.
299                        success = mDbHelper.addOrUpdateKeyphraseSoundModel(model);
300                    }
301                    if (success) {
302                        // Notify the voice interaction service of a change in sound models.
303                        if (mImpl != null && mImpl.mService != null) {
304                            mImpl.notifySoundModelsChangedLocked();
305                        }
306                        return SoundTriggerHelper.STATUS_OK;
307                    } else {
308                        return SoundTriggerHelper.STATUS_ERROR;
309                    }
310                } finally {
311                    Binder.restoreCallingIdentity(caller);
312                }
313            }
314        }
315
316        //----------------- SoundTrigger APIs --------------------------------//
317        @Override
318        public ModuleProperties getDspModuleProperties(IVoiceInteractionService service) {
319            // Allow the call if this is the current voice interaction service.
320            synchronized (this) {
321                if (mImpl == null || mImpl.mService == null
322                        || service == null || service.asBinder() != mImpl.mService.asBinder()) {
323                    throw new SecurityException(
324                            "Caller is not the current voice interaction service");
325                }
326
327                final long caller = Binder.clearCallingIdentity();
328                try {
329                    return mSoundTriggerHelper.moduleProperties;
330                } finally {
331                    Binder.restoreCallingIdentity(caller);
332                }
333            }
334        }
335
336        @Override
337        public int startRecognition(IVoiceInteractionService service, int keyphraseId,
338                KeyphraseSoundModel soundModel, IRecognitionStatusCallback callback,
339                RecognitionConfig recognitionConfig) {
340            // Allow the call if this is the current voice interaction service.
341            synchronized (this) {
342                if (mImpl == null || mImpl.mService == null
343                        || service == null || service.asBinder() != mImpl.mService.asBinder()) {
344                    throw new SecurityException(
345                            "Caller is not the current voice interaction service");
346                }
347
348                final long caller = Binder.clearCallingIdentity();
349                try {
350                    return mSoundTriggerHelper.startRecognition(
351                            keyphraseId, soundModel, callback, recognitionConfig);
352                } finally {
353                    Binder.restoreCallingIdentity(caller);
354                }
355            }
356        }
357
358        @Override
359        public int stopRecognition(IVoiceInteractionService service, int keyphraseId,
360                IRecognitionStatusCallback callback) {
361            // Allow the call if this is the current voice interaction service.
362            synchronized (this) {
363                if (mImpl == null || mImpl.mService == null
364                        || service == null || service.asBinder() != mImpl.mService.asBinder()) {
365                    throw new SecurityException(
366                            "Caller is not the current voice interaction service");
367                }
368
369                final long caller = Binder.clearCallingIdentity();
370                try {
371                    return mSoundTriggerHelper.stopRecognition(keyphraseId, callback);
372                } finally {
373                    Binder.restoreCallingIdentity(caller);
374                }
375            }
376        }
377
378        @Override
379        public void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
380            if (mContext.checkCallingOrSelfPermission(Manifest.permission.DUMP)
381                    != PackageManager.PERMISSION_GRANTED) {
382                pw.println("Permission Denial: can't dump PowerManager from from pid="
383                        + Binder.getCallingPid()
384                        + ", uid=" + Binder.getCallingUid());
385                return;
386            }
387            synchronized (this) {
388                pw.println("VOICE INTERACTION MANAGER (dumpsys voiceinteraction)\n");
389                if (mImpl == null) {
390                    pw.println("  (No active implementation)");
391                    return;
392                }
393                mImpl.dumpLocked(fd, pw, args);
394            }
395        }
396
397        class SettingsObserver extends ContentObserver {
398            SettingsObserver(Handler handler) {
399                super(handler);
400                ContentResolver resolver = mContext.getContentResolver();
401                resolver.registerContentObserver(Settings.Secure.getUriFor(
402                        Settings.Secure.VOICE_INTERACTION_SERVICE), false, this);
403            }
404
405            @Override public void onChange(boolean selfChange) {
406                synchronized (VoiceInteractionManagerServiceStub.this) {
407                    switchImplementationIfNeededLocked(false);
408                }
409            }
410        }
411
412        PackageMonitor mPackageMonitor = new PackageMonitor() {
413            @Override
414            public boolean onHandleForceStop(Intent intent, String[] packages, int uid, boolean doit) {
415                return super.onHandleForceStop(intent, packages, uid, doit);
416            }
417
418            @Override
419            public void onHandleUserStop(Intent intent, int userHandle) {
420            }
421
422            @Override
423            public void onPackageDisappeared(String packageName, int reason) {
424            }
425
426            @Override
427            public void onPackageAppeared(String packageName, int reason) {
428                if (mImpl != null && packageName.equals(mImpl.mComponent.getPackageName())) {
429                    switchImplementationIfNeededLocked(true);
430                }
431            }
432
433            @Override
434            public void onPackageModified(String packageName) {
435            }
436
437            @Override
438            public void onSomePackagesChanged() {
439            }
440        };
441    }
442}
443