1 /*
2  * Copyright (C) 2019 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 
17 package com.android.server.soundtrigger_middleware;
18 
19 import static android.Manifest.permission.SOUNDTRIGGER_DELEGATE_IDENTITY;
20 
21 import android.annotation.NonNull;
22 import android.content.Context;
23 import android.content.PermissionChecker;
24 import android.media.permission.ClearCallingIdentityContext;
25 import android.media.permission.Identity;
26 import android.media.permission.PermissionUtil;
27 import android.media.permission.SafeCloseable;
28 import android.media.soundtrigger.ModelParameterRange;
29 import android.media.soundtrigger.PhraseSoundModel;
30 import android.media.soundtrigger.RecognitionConfig;
31 import android.media.soundtrigger.SoundModel;
32 import android.media.soundtrigger_middleware.ISoundTriggerCallback;
33 import android.media.soundtrigger_middleware.ISoundTriggerInjection;
34 import android.media.soundtrigger_middleware.ISoundTriggerMiddlewareService;
35 import android.media.soundtrigger_middleware.ISoundTriggerModule;
36 import android.media.soundtrigger_middleware.SoundTriggerModuleDescriptor;
37 import android.os.IBinder;
38 import android.os.RemoteException;
39 
40 import com.android.server.SystemService;
41 
42 import java.io.FileDescriptor;
43 import java.io.PrintWriter;
44 import java.util.Objects;
45 
46 /**
47  * This is a wrapper around an {@link ISoundTriggerMiddlewareService} implementation, which exposes
48  * it as a Binder service.
49  * <p>
50  * This is intended to facilitate a pattern of decorating the core implementation (business logic)
51  * of the interface with every decorator implementing a different aspect of the service, such as
52  * validation and logging. This class acts as the top-level decorator, which also adds the binder-
53  * related functionality (hence, it extends ISoundTriggerMiddlewareService.Stub as rather than
54  * implements ISoundTriggerMiddlewareService), and does the same thing for child interfaces
55  * returned.
56  * <p>
57  * The inner class {@link Lifecycle} acts as both a factory, composing the various aspect-decorators
58  * to create a full-featured implementation, as well as as an entry-point for presenting this
59  * implementation as a system service.
60  * <p>
61  * <b>Exposing this service as a System Service:</b><br>
62  * Insert this line into {@link com.android.server.SystemServer}:
63  * <code><pre>
64  * mSystemServiceManager.startService(SoundTriggerMiddlewareService.Lifecycle.class);
65  * </pre></code>
66  *
67  * {@hide}
68  */
69 public class SoundTriggerMiddlewareService extends ISoundTriggerMiddlewareService.Stub {
70     static private final String TAG = "SoundTriggerMiddlewareService";
71 
72     private final @NonNull ISoundTriggerMiddlewareInternal mDelegate;
73     private final @NonNull Context mContext;
74     // Lightweight object used to delegate injection events to the fake STHAL
75     private final @NonNull SoundTriggerInjection mInjection;
76 
77     /**
78      * Constructor for internal use only. Could be exposed for testing purposes in the future.
79      * Users should access this class via {@link Lifecycle}.
80      */
SoundTriggerMiddlewareService(@onNull ISoundTriggerMiddlewareInternal delegate, @NonNull Context context, @NonNull SoundTriggerInjection injection)81     private SoundTriggerMiddlewareService(@NonNull ISoundTriggerMiddlewareInternal delegate,
82             @NonNull Context context, @NonNull SoundTriggerInjection injection) {
83         mDelegate = Objects.requireNonNull(delegate);
84         mContext = context;
85         mInjection = injection;
86     }
87 
88     @Override
listModulesAsOriginator(Identity identity)89     public SoundTriggerModuleDescriptor[] listModulesAsOriginator(Identity identity) {
90         try (SafeCloseable ignored = establishIdentityDirect(identity)) {
91             return mDelegate.listModules();
92         }
93     }
94 
95     @Override
listModulesAsMiddleman(Identity middlemanIdentity, Identity originatorIdentity)96     public SoundTriggerModuleDescriptor[] listModulesAsMiddleman(Identity middlemanIdentity,
97             Identity originatorIdentity) {
98         try (SafeCloseable ignored = establishIdentityIndirect(middlemanIdentity,
99                 originatorIdentity)) {
100             return mDelegate.listModules();
101         }
102     }
103 
104     @Override
attachAsOriginator(int handle, Identity identity, ISoundTriggerCallback callback)105     public ISoundTriggerModule attachAsOriginator(int handle, Identity identity,
106             ISoundTriggerCallback callback) {
107         try (SafeCloseable ignored = establishIdentityDirect(Objects.requireNonNull(identity))) {
108             return new ModuleService(mDelegate.attach(handle, callback, /* isTrusted= */ false));
109         }
110     }
111 
112     @Override
attachAsMiddleman(int handle, Identity middlemanIdentity, Identity originatorIdentity, ISoundTriggerCallback callback, boolean isTrusted)113     public ISoundTriggerModule attachAsMiddleman(int handle, Identity middlemanIdentity,
114             Identity originatorIdentity, ISoundTriggerCallback callback, boolean isTrusted) {
115         try (SafeCloseable ignored = establishIdentityIndirect(
116                 Objects.requireNonNull(middlemanIdentity),
117                 Objects.requireNonNull(originatorIdentity))) {
118             return new ModuleService(mDelegate.attach(handle, callback, isTrusted));
119         }
120     }
121 
122     @Override
123     @android.annotation.RequiresPermission(android.Manifest.permission.MANAGE_SOUND_TRIGGER)
attachFakeHalInjection(@onNull ISoundTriggerInjection injection)124     public void attachFakeHalInjection(@NonNull ISoundTriggerInjection injection) {
125         PermissionChecker.checkCallingOrSelfPermissionForPreflight(
126                 mContext, android.Manifest.permission.MANAGE_SOUND_TRIGGER);
127         try (SafeCloseable ignored = ClearCallingIdentityContext.create()) {
128             mInjection.registerClient(Objects.requireNonNull(injection));
129         }
130     }
131 
132     @Override
dump(FileDescriptor fd, PrintWriter fout, String[] args)133     protected void dump(FileDescriptor fd, PrintWriter fout, String[] args) {
134         if (mDelegate instanceof Dumpable) {
135             ((Dumpable) mDelegate).dump(fout);
136         }
137     }
138 
139     private @NonNull
establishIdentityIndirect(Identity middlemanIdentity, Identity originatorIdentity)140     SafeCloseable establishIdentityIndirect(Identity middlemanIdentity,
141             Identity originatorIdentity) {
142         return PermissionUtil.establishIdentityIndirect(mContext, SOUNDTRIGGER_DELEGATE_IDENTITY,
143                 middlemanIdentity, originatorIdentity);
144     }
145 
146     private @NonNull
establishIdentityDirect(Identity originatorIdentity)147     SafeCloseable establishIdentityDirect(Identity originatorIdentity) {
148         return PermissionUtil.establishIdentityDirect(originatorIdentity);
149     }
150 
151     private final static class ModuleService extends ISoundTriggerModule.Stub {
152         private final ISoundTriggerModule mDelegate;
153 
ModuleService(ISoundTriggerModule delegate)154         private ModuleService(ISoundTriggerModule delegate) {
155             mDelegate = delegate;
156         }
157 
158         @Override
loadModel(SoundModel model)159         public int loadModel(SoundModel model) throws RemoteException {
160             try (SafeCloseable ignored = ClearCallingIdentityContext.create()) {
161                 return mDelegate.loadModel(model);
162             }
163         }
164 
165         @Override
loadPhraseModel(PhraseSoundModel model)166         public int loadPhraseModel(PhraseSoundModel model) throws RemoteException {
167             try (SafeCloseable ignored = ClearCallingIdentityContext.create()) {
168                 return mDelegate.loadPhraseModel(model);
169             }
170         }
171 
172         @Override
unloadModel(int modelHandle)173         public void unloadModel(int modelHandle) throws RemoteException {
174             try (SafeCloseable ignored = ClearCallingIdentityContext.create()) {
175                 mDelegate.unloadModel(modelHandle);
176             }
177         }
178 
179         @Override
startRecognition(int modelHandle, RecognitionConfig config)180         public IBinder startRecognition(int modelHandle, RecognitionConfig config)
181                 throws RemoteException {
182             try (SafeCloseable ignored = ClearCallingIdentityContext.create()) {
183                 return mDelegate.startRecognition(modelHandle, config);
184             }
185         }
186 
187         @Override
stopRecognition(int modelHandle)188         public void stopRecognition(int modelHandle) throws RemoteException {
189             try (SafeCloseable ignored = ClearCallingIdentityContext.create()) {
190                 mDelegate.stopRecognition(modelHandle);
191             }
192         }
193 
194         @Override
forceRecognitionEvent(int modelHandle)195         public void forceRecognitionEvent(int modelHandle) throws RemoteException {
196             try (SafeCloseable ignored = ClearCallingIdentityContext.create()) {
197                 mDelegate.forceRecognitionEvent(modelHandle);
198             }
199         }
200 
201         @Override
setModelParameter(int modelHandle, int modelParam, int value)202         public void setModelParameter(int modelHandle, int modelParam, int value)
203                 throws RemoteException {
204             try (SafeCloseable ignored = ClearCallingIdentityContext.create()) {
205                 mDelegate.setModelParameter(modelHandle, modelParam, value);
206             }
207         }
208 
209         @Override
getModelParameter(int modelHandle, int modelParam)210         public int getModelParameter(int modelHandle, int modelParam) throws RemoteException {
211             try (SafeCloseable ignored = ClearCallingIdentityContext.create()) {
212                 return mDelegate.getModelParameter(modelHandle, modelParam);
213             }
214         }
215 
216         @Override
queryModelParameterSupport(int modelHandle, int modelParam)217         public ModelParameterRange queryModelParameterSupport(int modelHandle, int modelParam)
218                 throws RemoteException {
219             try (SafeCloseable ignored = ClearCallingIdentityContext.create()) {
220                 return mDelegate.queryModelParameterSupport(modelHandle, modelParam);
221             }
222         }
223 
224         @Override
detach()225         public void detach() throws RemoteException {
226             try (SafeCloseable ignored = ClearCallingIdentityContext.create()) {
227                 mDelegate.detach();
228             }
229         }
230     }
231 
232     /**
233      * Entry-point to this module: exposes the module as a {@link SystemService}.
234      */
235     public static final class Lifecycle extends SystemService {
Lifecycle(Context context)236         public Lifecycle(Context context) {
237             super(context);
238         }
239 
240         @Override
onStart()241         public void onStart() {
242             final SoundTriggerInjection injection = new SoundTriggerInjection();
243             HalFactory[] factories = new HalFactory[]{new DefaultHalFactory(),
244                     new FakeHalFactory(injection)};
245 
246             publishBinderService(Context.SOUND_TRIGGER_MIDDLEWARE_SERVICE,
247                     new SoundTriggerMiddlewareService(
248                             new SoundTriggerMiddlewareLogging(getContext(),
249                                 new SoundTriggerMiddlewarePermission(
250                                         new SoundTriggerMiddlewareValidation(
251                                                 new SoundTriggerMiddlewareImpl(factories,
252                                                         new AudioSessionProviderImpl())),
253                                         getContext())), getContext(),
254                                         injection));
255         }
256     }
257 }
258