blob: 9de24383ba75e5686c054fd045a5552dbeaf1903 [file] [log] [blame]
/*
* 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.
* <p>
* 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.
* <p>
* 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.
* <p>
* <b>Exposing this service as a System Service:</b><br>
* Insert this line into {@link com.android.server.SystemServer}:
* <code><pre>
* mSystemServiceManager.startService(SoundTriggerMiddlewareService.Lifecycle.class);
* </pre></code>
*
* {@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));
}
}
}