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.soundtrigger;
18import static android.hardware.soundtrigger.SoundTrigger.STATUS_ERROR;
19
20import android.content.Context;
21import android.content.pm.PackageManager;
22import android.Manifest;
23import android.hardware.soundtrigger.IRecognitionStatusCallback;
24import android.hardware.soundtrigger.SoundTrigger;
25import android.hardware.soundtrigger.SoundTrigger.GenericSoundModel;
26import android.hardware.soundtrigger.SoundTrigger.KeyphraseSoundModel;
27import android.hardware.soundtrigger.SoundTrigger.ModuleProperties;
28import android.hardware.soundtrigger.SoundTrigger.RecognitionConfig;
29import android.os.Parcel;
30import android.os.ParcelUuid;
31import android.os.RemoteException;
32import android.util.Slog;
33
34import com.android.server.SystemService;
35import com.android.internal.app.ISoundTriggerService;
36
37import java.io.FileDescriptor;
38import java.io.PrintWriter;
39import java.util.UUID;
40
41/**
42 * A single SystemService to manage all sound/voice-based sound models on the DSP.
43 * This services provides apis to manage sound trigger-based sound models via
44 * the ISoundTriggerService interface. This class also publishes a local interface encapsulating
45 * the functionality provided by {@link SoundTriggerHelper} for use by
46 * {@link VoiceInteractionManagerService}.
47 *
48 * @hide
49 */
50public class SoundTriggerService extends SystemService {
51    private static final String TAG = "SoundTriggerService";
52    private static final boolean DEBUG = true;
53
54    final Context mContext;
55    private final SoundTriggerServiceStub mServiceStub;
56    private final LocalSoundTriggerService mLocalSoundTriggerService;
57    private SoundTriggerDbHelper mDbHelper;
58    private SoundTriggerHelper mSoundTriggerHelper;
59
60    public SoundTriggerService(Context context) {
61        super(context);
62        mContext = context;
63        mServiceStub = new SoundTriggerServiceStub();
64        mLocalSoundTriggerService = new LocalSoundTriggerService(context);
65    }
66
67    @Override
68    public void onStart() {
69        publishBinderService(Context.SOUND_TRIGGER_SERVICE, mServiceStub);
70        publishLocalService(SoundTriggerInternal.class, mLocalSoundTriggerService);
71    }
72
73    @Override
74    public void onBootPhase(int phase) {
75        if (PHASE_SYSTEM_SERVICES_READY == phase) {
76            initSoundTriggerHelper();
77            mLocalSoundTriggerService.setSoundTriggerHelper(mSoundTriggerHelper);
78        } else if (PHASE_THIRD_PARTY_APPS_CAN_START == phase) {
79            mDbHelper = new SoundTriggerDbHelper(mContext);
80        }
81    }
82
83    @Override
84    public void onStartUser(int userHandle) {
85    }
86
87    @Override
88    public void onSwitchUser(int userHandle) {
89    }
90
91    private synchronized void initSoundTriggerHelper() {
92        if (mSoundTriggerHelper == null) {
93            mSoundTriggerHelper = new SoundTriggerHelper(mContext);
94        }
95    }
96
97    private synchronized boolean isInitialized() {
98        if (mSoundTriggerHelper == null ) {
99            Slog.e(TAG, "SoundTriggerHelper not initialized.");
100            return false;
101        }
102        return true;
103    }
104
105    class SoundTriggerServiceStub extends ISoundTriggerService.Stub {
106        @Override
107        public boolean onTransact(int code, Parcel data, Parcel reply, int flags)
108                throws RemoteException {
109            try {
110                return super.onTransact(code, data, reply, flags);
111            } catch (RuntimeException e) {
112                // The activity manager only throws security exceptions, so let's
113                // log all others.
114                if (!(e instanceof SecurityException)) {
115                    Slog.wtf(TAG, "SoundTriggerService Crash", e);
116                }
117                throw e;
118            }
119        }
120
121        @Override
122        public int startRecognition(ParcelUuid parcelUuid, IRecognitionStatusCallback callback,
123                RecognitionConfig config) {
124            enforceCallingPermission(Manifest.permission.MANAGE_SOUND_TRIGGER);
125            if (!isInitialized()) return STATUS_ERROR;
126            if (DEBUG) {
127                Slog.i(TAG, "startRecognition(): Uuid : " + parcelUuid);
128            }
129
130            GenericSoundModel model = getSoundModel(parcelUuid);
131            if (model == null) {
132                Slog.e(TAG, "Null model in database for id: " + parcelUuid);
133                return STATUS_ERROR;
134            }
135
136            return mSoundTriggerHelper.startGenericRecognition(parcelUuid.getUuid(), model,
137                    callback, config);
138        }
139
140        @Override
141        public int stopRecognition(ParcelUuid parcelUuid, IRecognitionStatusCallback callback) {
142            enforceCallingPermission(Manifest.permission.MANAGE_SOUND_TRIGGER);
143            if (DEBUG) {
144                Slog.i(TAG, "stopRecognition(): Uuid : " + parcelUuid);
145            }
146            if (!isInitialized()) return STATUS_ERROR;
147            return mSoundTriggerHelper.stopGenericRecognition(parcelUuid.getUuid(), callback);
148        }
149
150        @Override
151        public SoundTrigger.GenericSoundModel getSoundModel(ParcelUuid soundModelId) {
152            enforceCallingPermission(Manifest.permission.MANAGE_SOUND_TRIGGER);
153            if (DEBUG) {
154                Slog.i(TAG, "getSoundModel(): id = " + soundModelId);
155            }
156            SoundTrigger.GenericSoundModel model = mDbHelper.getGenericSoundModel(
157                    soundModelId.getUuid());
158            return model;
159        }
160
161        @Override
162        public void updateSoundModel(SoundTrigger.GenericSoundModel soundModel) {
163            enforceCallingPermission(Manifest.permission.MANAGE_SOUND_TRIGGER);
164            if (DEBUG) {
165                Slog.i(TAG, "updateSoundModel(): model = " + soundModel);
166            }
167            mDbHelper.updateGenericSoundModel(soundModel);
168        }
169
170        @Override
171        public void deleteSoundModel(ParcelUuid soundModelId) {
172            enforceCallingPermission(Manifest.permission.MANAGE_SOUND_TRIGGER);
173            if (DEBUG) {
174                Slog.i(TAG, "deleteSoundModel(): id = " + soundModelId);
175            }
176            // Unload the model if it is loaded.
177            mSoundTriggerHelper.unloadGenericSoundModel(soundModelId.getUuid());
178            mDbHelper.deleteGenericSoundModel(soundModelId.getUuid());
179        }
180    }
181
182    public final class LocalSoundTriggerService extends SoundTriggerInternal {
183        private final Context mContext;
184        private SoundTriggerHelper mSoundTriggerHelper;
185
186        LocalSoundTriggerService(Context context) {
187            mContext = context;
188        }
189
190        synchronized void setSoundTriggerHelper(SoundTriggerHelper helper) {
191            mSoundTriggerHelper = helper;
192        }
193
194        @Override
195        public int startRecognition(int keyphraseId, KeyphraseSoundModel soundModel,
196                IRecognitionStatusCallback listener, RecognitionConfig recognitionConfig) {
197            if (!isInitialized()) return STATUS_ERROR;
198            return mSoundTriggerHelper.startKeyphraseRecognition(keyphraseId, soundModel, listener,
199                    recognitionConfig);
200        }
201
202        @Override
203        public synchronized int stopRecognition(int keyphraseId, IRecognitionStatusCallback listener) {
204            if (!isInitialized()) return STATUS_ERROR;
205            return mSoundTriggerHelper.stopKeyphraseRecognition(keyphraseId, listener);
206        }
207
208        @Override
209        public ModuleProperties getModuleProperties() {
210            if (!isInitialized()) return null;
211            return mSoundTriggerHelper.getModuleProperties();
212        }
213
214        @Override
215        public int unloadKeyphraseModel(int keyphraseId) {
216            if (!isInitialized()) return STATUS_ERROR;
217            return mSoundTriggerHelper.unloadKeyphraseSoundModel(keyphraseId);
218        }
219
220        @Override
221        public void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
222            if (!isInitialized()) return;
223            mSoundTriggerHelper.dump(fd, pw, args);
224        }
225
226        private synchronized boolean isInitialized() {
227            if (mSoundTriggerHelper == null ) {
228                Slog.e(TAG, "SoundTriggerHelper not initialized.");
229                return false;
230            }
231            return true;
232        }
233    }
234
235    private void enforceCallingPermission(String permission) {
236        if (mContext.checkCallingOrSelfPermission(permission)
237                != PackageManager.PERMISSION_GRANTED) {
238            throw new SecurityException("Caller does not hold the permission " + permission);
239        }
240    }
241}
242