/* * Copyright (C) 2019 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.android.server.soundtrigger_middleware; import static android.Manifest.permission.SOUNDTRIGGER_DELEGATE_IDENTITY; import android.annotation.NonNull; import android.content.Context; import android.content.PermissionChecker; import android.media.permission.ClearCallingIdentityContext; import android.media.permission.Identity; import android.media.permission.PermissionUtil; import android.media.permission.SafeCloseable; import android.media.soundtrigger.ModelParameterRange; import android.media.soundtrigger.PhraseSoundModel; import android.media.soundtrigger.RecognitionConfig; import android.media.soundtrigger.SoundModel; import android.media.soundtrigger_middleware.ISoundTriggerCallback; import android.media.soundtrigger_middleware.ISoundTriggerInjection; import android.media.soundtrigger_middleware.ISoundTriggerMiddlewareService; import android.media.soundtrigger_middleware.ISoundTriggerModule; import android.media.soundtrigger_middleware.SoundTriggerModuleDescriptor; import android.os.IBinder; import android.os.RemoteException; import com.android.server.SystemService; import java.io.FileDescriptor; import java.io.PrintWriter; import java.util.Objects; /** * This is a wrapper around an {@link ISoundTriggerMiddlewareService} implementation, which exposes * it as a Binder service. *

* This is intended to facilitate a pattern of decorating the core implementation (business logic) * of the interface with every decorator implementing a different aspect of the service, such as * validation and logging. This class acts as the top-level decorator, which also adds the binder- * related functionality (hence, it extends ISoundTriggerMiddlewareService.Stub as rather than * implements ISoundTriggerMiddlewareService), and does the same thing for child interfaces * returned. *

* The inner class {@link Lifecycle} acts as both a factory, composing the various aspect-decorators * to create a full-featured implementation, as well as as an entry-point for presenting this * implementation as a system service. *

* Exposing this service as a System Service:
* Insert this line into {@link com.android.server.SystemServer}: *

 * mSystemServiceManager.startService(SoundTriggerMiddlewareService.Lifecycle.class);
 * 
* * {@hide} */ public class SoundTriggerMiddlewareService extends ISoundTriggerMiddlewareService.Stub { static private final String TAG = "SoundTriggerMiddlewareService"; private final @NonNull ISoundTriggerMiddlewareInternal mDelegate; private final @NonNull Context mContext; // Lightweight object used to delegate injection events to the fake STHAL private final @NonNull SoundTriggerInjection mInjection; /** * Constructor for internal use only. Could be exposed for testing purposes in the future. * Users should access this class via {@link Lifecycle}. */ private SoundTriggerMiddlewareService(@NonNull ISoundTriggerMiddlewareInternal delegate, @NonNull Context context, @NonNull SoundTriggerInjection injection) { mDelegate = Objects.requireNonNull(delegate); mContext = context; mInjection = injection; } @Override public SoundTriggerModuleDescriptor[] listModulesAsOriginator(Identity identity) { try (SafeCloseable ignored = establishIdentityDirect(identity)) { return mDelegate.listModules(); } } @Override public SoundTriggerModuleDescriptor[] listModulesAsMiddleman(Identity middlemanIdentity, Identity originatorIdentity) { try (SafeCloseable ignored = establishIdentityIndirect(middlemanIdentity, originatorIdentity)) { return mDelegate.listModules(); } } @Override public ISoundTriggerModule attachAsOriginator(int handle, Identity identity, ISoundTriggerCallback callback) { try (SafeCloseable ignored = establishIdentityDirect(Objects.requireNonNull(identity))) { return new ModuleService(mDelegate.attach(handle, callback, /* isTrusted= */ false)); } } @Override public ISoundTriggerModule attachAsMiddleman(int handle, Identity middlemanIdentity, Identity originatorIdentity, ISoundTriggerCallback callback, boolean isTrusted) { try (SafeCloseable ignored = establishIdentityIndirect( Objects.requireNonNull(middlemanIdentity), Objects.requireNonNull(originatorIdentity))) { return new ModuleService(mDelegate.attach(handle, callback, isTrusted)); } } @Override @android.annotation.RequiresPermission(android.Manifest.permission.MANAGE_SOUND_TRIGGER) public void attachFakeHalInjection(@NonNull ISoundTriggerInjection injection) { PermissionChecker.checkCallingOrSelfPermissionForPreflight( mContext, android.Manifest.permission.MANAGE_SOUND_TRIGGER); try (SafeCloseable ignored = ClearCallingIdentityContext.create()) { mInjection.registerClient(Objects.requireNonNull(injection)); } } @Override protected void dump(FileDescriptor fd, PrintWriter fout, String[] args) { if (mDelegate instanceof Dumpable) { ((Dumpable) mDelegate).dump(fout); } } private @NonNull SafeCloseable establishIdentityIndirect(Identity middlemanIdentity, Identity originatorIdentity) { return PermissionUtil.establishIdentityIndirect(mContext, SOUNDTRIGGER_DELEGATE_IDENTITY, middlemanIdentity, originatorIdentity); } private @NonNull SafeCloseable establishIdentityDirect(Identity originatorIdentity) { return PermissionUtil.establishIdentityDirect(originatorIdentity); } private final static class ModuleService extends ISoundTriggerModule.Stub { private final ISoundTriggerModule mDelegate; private ModuleService(ISoundTriggerModule delegate) { mDelegate = delegate; } @Override public int loadModel(SoundModel model) throws RemoteException { try (SafeCloseable ignored = ClearCallingIdentityContext.create()) { return mDelegate.loadModel(model); } } @Override public int loadPhraseModel(PhraseSoundModel model) throws RemoteException { try (SafeCloseable ignored = ClearCallingIdentityContext.create()) { return mDelegate.loadPhraseModel(model); } } @Override public void unloadModel(int modelHandle) throws RemoteException { try (SafeCloseable ignored = ClearCallingIdentityContext.create()) { mDelegate.unloadModel(modelHandle); } } @Override public IBinder startRecognition(int modelHandle, RecognitionConfig config) throws RemoteException { try (SafeCloseable ignored = ClearCallingIdentityContext.create()) { return mDelegate.startRecognition(modelHandle, config); } } @Override public void stopRecognition(int modelHandle) throws RemoteException { try (SafeCloseable ignored = ClearCallingIdentityContext.create()) { mDelegate.stopRecognition(modelHandle); } } @Override public void forceRecognitionEvent(int modelHandle) throws RemoteException { try (SafeCloseable ignored = ClearCallingIdentityContext.create()) { mDelegate.forceRecognitionEvent(modelHandle); } } @Override public void setModelParameter(int modelHandle, int modelParam, int value) throws RemoteException { try (SafeCloseable ignored = ClearCallingIdentityContext.create()) { mDelegate.setModelParameter(modelHandle, modelParam, value); } } @Override public int getModelParameter(int modelHandle, int modelParam) throws RemoteException { try (SafeCloseable ignored = ClearCallingIdentityContext.create()) { return mDelegate.getModelParameter(modelHandle, modelParam); } } @Override public ModelParameterRange queryModelParameterSupport(int modelHandle, int modelParam) throws RemoteException { try (SafeCloseable ignored = ClearCallingIdentityContext.create()) { return mDelegate.queryModelParameterSupport(modelHandle, modelParam); } } @Override public void detach() throws RemoteException { try (SafeCloseable ignored = ClearCallingIdentityContext.create()) { mDelegate.detach(); } } } /** * Entry-point to this module: exposes the module as a {@link SystemService}. */ public static final class Lifecycle extends SystemService { public Lifecycle(Context context) { super(context); } @Override public void onStart() { final SoundTriggerInjection injection = new SoundTriggerInjection(); HalFactory[] factories = new HalFactory[]{new DefaultHalFactory(), new FakeHalFactory(injection)}; publishBinderService(Context.SOUND_TRIGGER_MIDDLEWARE_SERVICE, new SoundTriggerMiddlewareService( new SoundTriggerMiddlewareLogging(getContext(), new SoundTriggerMiddlewarePermission( new SoundTriggerMiddlewareValidation( new SoundTriggerMiddlewareImpl(factories, new AudioSessionProviderImpl())), getContext())), getContext(), injection)); } } }