/* * 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}:
*
*
* {@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));
}
}
}
* mSystemServiceManager.startService(SoundTriggerMiddlewareService.Lifecycle.class);
*