Merge changes I9f0a440d,I0b734cc7,I510ee307,Ia257021d into rvc-dev
* changes: Pretty-print parcelables when logging Add sysdump to SoundTriggerMiddlewareService SoundTriggerMiddlewareService logging decorator Refactor SoundTriggerMiddlewareService
This commit is contained in:
committed by
Android (Google) Code Review
commit
d050812a30
@@ -0,0 +1,32 @@
|
||||
/*
|
||||
* Copyright (C) 2020 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 android.annotation.NonNull;
|
||||
|
||||
import java.io.PrintWriter;
|
||||
|
||||
/**
|
||||
* An interface of an object that can generate a dump.
|
||||
*/
|
||||
interface Dumpable {
|
||||
/**
|
||||
* Generate a human-readable dump into the given writer.
|
||||
* @param pw The writer.
|
||||
*/
|
||||
void dump(@NonNull PrintWriter pw);
|
||||
}
|
||||
@@ -0,0 +1,249 @@
|
||||
/*
|
||||
* Copyright (C) 2020 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 android.annotation.NonNull;
|
||||
import android.annotation.Nullable;
|
||||
|
||||
import java.lang.reflect.Array;
|
||||
import java.lang.reflect.Field;
|
||||
import java.lang.reflect.Modifier;
|
||||
import java.util.Collection;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* A collection of pretty-print utilities for data objects.
|
||||
*/
|
||||
class ObjectPrinter {
|
||||
/** Default maximum elements to print in a collection. */
|
||||
static public final int kDefaultMaxCollectionLength = 16;
|
||||
|
||||
/**
|
||||
* Simple version of {@link #print(Object, boolean, int)} that prints an object, without
|
||||
* recursing into sub-objects.
|
||||
*
|
||||
* @param obj The object to print.
|
||||
* @return A string representing the object.
|
||||
*/
|
||||
static String print(@Nullable Object obj) {
|
||||
return print(obj, false, kDefaultMaxCollectionLength);
|
||||
}
|
||||
|
||||
/**
|
||||
* Pretty-prints an object.
|
||||
*
|
||||
* @param obj The object to print.
|
||||
* @param deep Whether to pretty-print sub-objects (if false, just prints them
|
||||
* with {@link Object#toString()}).
|
||||
* @param maxCollectionLength Whenever encountering collections, maximum number of elements to
|
||||
* print.
|
||||
* @return A string representing the object.
|
||||
*/
|
||||
static String print(@Nullable Object obj, boolean deep, int maxCollectionLength) {
|
||||
StringBuilder builder = new StringBuilder();
|
||||
print(builder, obj, deep, maxCollectionLength);
|
||||
return builder.toString();
|
||||
}
|
||||
|
||||
/**
|
||||
* This version is suitable for use inside a toString() override of an object, e.g.:
|
||||
* <pre><code>
|
||||
* class MyObject {
|
||||
* ...
|
||||
* @Override
|
||||
* String toString() {
|
||||
* return ObjectPrinter.printPublicFields(this, ...);
|
||||
* }
|
||||
* }
|
||||
* </code></pre>
|
||||
*
|
||||
* @param obj The object to print.
|
||||
* @param deep Whether to pretty-print sub-objects (if false, just prints them
|
||||
* with {@link Object#toString()}).
|
||||
* @param maxCollectionLength Whenever encountering collections, maximum number of elements to
|
||||
* print.
|
||||
*/
|
||||
static String printPublicFields(@Nullable Object obj, boolean deep, int maxCollectionLength) {
|
||||
StringBuilder builder = new StringBuilder();
|
||||
printPublicFields(builder, obj, deep, maxCollectionLength);
|
||||
return builder.toString();
|
||||
}
|
||||
|
||||
/**
|
||||
* A version of {@link #print(Object, boolean, int)} that uses a {@link StringBuilder}.
|
||||
*
|
||||
* @param builder StringBuilder to print into.
|
||||
* @param obj The object to print.
|
||||
* @param deep Whether to pretty-print sub-objects (if false, just prints them
|
||||
* with {@link Object#toString()}).
|
||||
* @param maxCollectionLength Whenever encountering collections, maximum number of elements to
|
||||
* print.
|
||||
*/
|
||||
static void print(@NonNull StringBuilder builder, @Nullable Object obj, boolean deep,
|
||||
int maxCollectionLength) {
|
||||
try {
|
||||
if (obj == null) {
|
||||
builder.append("null");
|
||||
return;
|
||||
}
|
||||
if (obj instanceof Boolean) {
|
||||
builder.append(obj.toString());
|
||||
return;
|
||||
}
|
||||
if (obj instanceof Number) {
|
||||
builder.append(obj.toString());
|
||||
return;
|
||||
}
|
||||
if (obj instanceof Character) {
|
||||
builder.append('\'');
|
||||
builder.append(obj.toString());
|
||||
builder.append('\'');
|
||||
return;
|
||||
}
|
||||
if (obj instanceof String) {
|
||||
builder.append('"');
|
||||
builder.append(obj.toString());
|
||||
builder.append('"');
|
||||
return;
|
||||
}
|
||||
|
||||
Class cls = obj.getClass();
|
||||
|
||||
if (Collection.class.isAssignableFrom(cls)) {
|
||||
Collection collection = (Collection) obj;
|
||||
builder.append("[ ");
|
||||
int length = collection.size();
|
||||
boolean isLong = false;
|
||||
int i = 0;
|
||||
for (Object child : collection) {
|
||||
if (i > 0) {
|
||||
builder.append(", ");
|
||||
}
|
||||
if (i >= maxCollectionLength) {
|
||||
isLong = true;
|
||||
break;
|
||||
}
|
||||
print(builder, child, deep, maxCollectionLength);
|
||||
++i;
|
||||
}
|
||||
if (isLong) {
|
||||
builder.append("... (+");
|
||||
builder.append(length - maxCollectionLength);
|
||||
builder.append(" entries)");
|
||||
}
|
||||
builder.append(" ]");
|
||||
return;
|
||||
}
|
||||
|
||||
if (Map.class.isAssignableFrom(cls)) {
|
||||
Map<?, ?> map = (Map<?, ?>) obj;
|
||||
builder.append("< ");
|
||||
int length = map.size();
|
||||
boolean isLong = false;
|
||||
int i = 0;
|
||||
for (Map.Entry<?, ?> child : map.entrySet()) {
|
||||
if (i > 0) {
|
||||
builder.append(", ");
|
||||
}
|
||||
if (i >= maxCollectionLength) {
|
||||
isLong = true;
|
||||
break;
|
||||
}
|
||||
print(builder, child.getKey(), deep, maxCollectionLength);
|
||||
builder.append(": ");
|
||||
print(builder, child.getValue(), deep, maxCollectionLength);
|
||||
++i;
|
||||
}
|
||||
if (isLong) {
|
||||
builder.append("... (+");
|
||||
builder.append(length - maxCollectionLength);
|
||||
builder.append(" entries)");
|
||||
}
|
||||
builder.append(" >");
|
||||
return;
|
||||
}
|
||||
|
||||
if (cls.isArray()) {
|
||||
builder.append("[ ");
|
||||
int length = Array.getLength(obj);
|
||||
boolean isLong = false;
|
||||
for (int i = 0; i < length; ++i) {
|
||||
if (i > 0) {
|
||||
builder.append(", ");
|
||||
}
|
||||
if (i >= maxCollectionLength) {
|
||||
isLong = true;
|
||||
break;
|
||||
}
|
||||
print(builder, Array.get(obj, i), deep, maxCollectionLength);
|
||||
}
|
||||
if (isLong) {
|
||||
builder.append("... (+");
|
||||
builder.append(length - maxCollectionLength);
|
||||
builder.append(" entries)");
|
||||
}
|
||||
builder.append(" ]");
|
||||
return;
|
||||
}
|
||||
|
||||
if (!deep) {
|
||||
builder.append(obj.toString());
|
||||
return;
|
||||
}
|
||||
printPublicFields(builder, obj, deep, maxCollectionLength);
|
||||
} catch (Exception e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* A version of {@link #printPublicFields(Object, boolean, int)} that uses a {@link
|
||||
* StringBuilder}.
|
||||
*
|
||||
* @param obj The object to print.
|
||||
* @param deep Whether to pretty-print sub-objects (if false, just prints them
|
||||
* with {@link Object#toString()}).
|
||||
* @param maxCollectionLength Whenever encountering collections, maximum number of elements to
|
||||
* print.
|
||||
*/
|
||||
static void printPublicFields(@NonNull StringBuilder builder, @Nullable Object obj,
|
||||
boolean deep,
|
||||
int maxCollectionLength) {
|
||||
try {
|
||||
Class cls = obj.getClass();
|
||||
builder.append("{ ");
|
||||
|
||||
boolean first = true;
|
||||
for (Field fld : cls.getDeclaredFields()) {
|
||||
int mod = fld.getModifiers();
|
||||
if ((mod & Modifier.PUBLIC) != 0 && (mod & Modifier.STATIC) == 0) {
|
||||
if (first) {
|
||||
first = false;
|
||||
} else {
|
||||
builder.append(", ");
|
||||
}
|
||||
builder.append(fld.getName());
|
||||
builder.append(": ");
|
||||
print(builder, fld.get(obj), deep, maxCollectionLength);
|
||||
}
|
||||
}
|
||||
builder.append(" }");
|
||||
} catch (Exception e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,454 @@
|
||||
/*
|
||||
* Copyright (C) 2020 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 android.annotation.NonNull;
|
||||
import android.annotation.Nullable;
|
||||
import android.media.soundtrigger_middleware.ISoundTriggerCallback;
|
||||
import android.media.soundtrigger_middleware.ISoundTriggerMiddlewareService;
|
||||
import android.media.soundtrigger_middleware.ISoundTriggerModule;
|
||||
import android.media.soundtrigger_middleware.ModelParameterRange;
|
||||
import android.media.soundtrigger_middleware.PhraseRecognitionEvent;
|
||||
import android.media.soundtrigger_middleware.PhraseSoundModel;
|
||||
import android.media.soundtrigger_middleware.RecognitionConfig;
|
||||
import android.media.soundtrigger_middleware.RecognitionEvent;
|
||||
import android.media.soundtrigger_middleware.SoundModel;
|
||||
import android.media.soundtrigger_middleware.SoundTriggerModuleDescriptor;
|
||||
import android.os.Binder;
|
||||
import android.os.IBinder;
|
||||
import android.os.Parcelable;
|
||||
import android.os.RemoteException;
|
||||
import android.util.Log;
|
||||
|
||||
import java.io.PrintWriter;
|
||||
import java.text.SimpleDateFormat;
|
||||
import java.util.Arrays;
|
||||
import java.util.Date;
|
||||
import java.util.LinkedList;
|
||||
|
||||
/**
|
||||
* An ISoundTriggerMiddlewareService decorator, which adds logging of all API calls (and
|
||||
* callbacks).
|
||||
*
|
||||
* All API methods should follow this structure:
|
||||
* <pre><code>
|
||||
* @Override
|
||||
* public @NonNull ReturnType someMethod(ArgType1 arg1, ArgType2 arg2) throws ExceptionType {
|
||||
* try {
|
||||
* ReturnType result = mDelegate.someMethod(arg1, arg2);
|
||||
* logReturn("someMethod", result, arg1, arg2);
|
||||
* return result;
|
||||
* } catch (Exception e) {
|
||||
* logException("someMethod", e, arg1, arg2);
|
||||
* throw e;
|
||||
* }
|
||||
* }
|
||||
* </code></pre>
|
||||
* The actual handling of these events is then done inside of {@link #logReturnWithObject(Object,
|
||||
* String, Object, Object[])}, {@link #logVoidReturnWithObject(Object, String, Object[])} and {@link
|
||||
* #logExceptionWithObject(Object, String, Exception, Object[])}.
|
||||
*/
|
||||
public class SoundTriggerMiddlewareLogging implements ISoundTriggerMiddlewareService, Dumpable {
|
||||
private static final String TAG = "SoundTriggerMiddlewareLogging";
|
||||
private final @NonNull ISoundTriggerMiddlewareService mDelegate;
|
||||
|
||||
public SoundTriggerMiddlewareLogging(@NonNull ISoundTriggerMiddlewareService delegate) {
|
||||
mDelegate = delegate;
|
||||
}
|
||||
|
||||
@Override
|
||||
public @NonNull SoundTriggerModuleDescriptor[] listModules() throws RemoteException {
|
||||
try {
|
||||
SoundTriggerModuleDescriptor[] result = mDelegate.listModules();
|
||||
logReturn("listModules", result);
|
||||
return result;
|
||||
} catch (Exception e) {
|
||||
logException("listModules", e);
|
||||
throw e;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public @NonNull ISoundTriggerModule attach(int handle, ISoundTriggerCallback callback)
|
||||
throws RemoteException {
|
||||
try {
|
||||
ISoundTriggerModule result = mDelegate.attach(handle, new CallbackLogging(callback));
|
||||
logReturn("attach", result, handle, callback);
|
||||
return new ModuleLogging(result);
|
||||
} catch (Exception e) {
|
||||
logException("attach", e, handle, callback);
|
||||
throw e;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setExternalCaptureState(boolean active) throws RemoteException {
|
||||
try {
|
||||
mDelegate.setExternalCaptureState(active);
|
||||
logVoidReturn("setExternalCaptureState", active);
|
||||
} catch (Exception e) {
|
||||
logException("setExternalCaptureState", e, active);
|
||||
throw e;
|
||||
}
|
||||
}
|
||||
|
||||
@Override public IBinder asBinder() {
|
||||
throw new UnsupportedOperationException(
|
||||
"This implementation is not inteded to be used directly with Binder.");
|
||||
}
|
||||
|
||||
// Override toString() in order to have the delegate's ID in it.
|
||||
@Override
|
||||
public String toString() {
|
||||
return mDelegate.toString();
|
||||
}
|
||||
|
||||
private void logException(String methodName, Exception ex, Object... args) {
|
||||
logExceptionWithObject(this, methodName, ex, args);
|
||||
}
|
||||
|
||||
private void logReturn(String methodName, Object retVal, Object... args) {
|
||||
logReturnWithObject(this, methodName, retVal, args);
|
||||
}
|
||||
|
||||
private void logVoidReturn(String methodName, Object... args) {
|
||||
logVoidReturnWithObject(this, methodName, args);
|
||||
}
|
||||
|
||||
private class CallbackLogging implements ISoundTriggerCallback {
|
||||
private final ISoundTriggerCallback mDelegate;
|
||||
|
||||
private CallbackLogging(ISoundTriggerCallback delegate) {
|
||||
mDelegate = delegate;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onRecognition(int modelHandle, RecognitionEvent event) throws RemoteException {
|
||||
try {
|
||||
mDelegate.onRecognition(modelHandle, event);
|
||||
logVoidReturn("onRecognition", modelHandle, event);
|
||||
} catch (Exception e) {
|
||||
logException("onRecognition", e, modelHandle, event);
|
||||
throw e;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onPhraseRecognition(int modelHandle, PhraseRecognitionEvent event)
|
||||
throws RemoteException {
|
||||
try {
|
||||
mDelegate.onPhraseRecognition(modelHandle, event);
|
||||
logVoidReturn("onPhraseRecognition", modelHandle, event);
|
||||
} catch (Exception e) {
|
||||
logException("onPhraseRecognition", e, modelHandle, event);
|
||||
throw e;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onRecognitionAvailabilityChange(boolean available) throws RemoteException {
|
||||
try {
|
||||
mDelegate.onRecognitionAvailabilityChange(available);
|
||||
logVoidReturn("onRecognitionAvailabilityChange", available);
|
||||
} catch (Exception e) {
|
||||
logException("onRecognitionAvailabilityChange", e, available);
|
||||
throw e;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onModuleDied() throws RemoteException {
|
||||
try {
|
||||
mDelegate.onModuleDied();
|
||||
logVoidReturn("onModuleDied");
|
||||
} catch (Exception e) {
|
||||
logException("onModuleDied", e);
|
||||
throw e;
|
||||
}
|
||||
}
|
||||
|
||||
private void logException(String methodName, Exception ex, Object... args) {
|
||||
logExceptionWithObject(this, methodName, ex, args);
|
||||
}
|
||||
|
||||
private void logVoidReturn(String methodName, Object... args) {
|
||||
logVoidReturnWithObject(this, methodName, args);
|
||||
}
|
||||
|
||||
@Override
|
||||
public IBinder asBinder() {
|
||||
return mDelegate.asBinder();
|
||||
}
|
||||
|
||||
// Override toString() in order to have the delegate's ID in it.
|
||||
@Override
|
||||
public String toString() {
|
||||
return mDelegate.toString();
|
||||
}
|
||||
}
|
||||
|
||||
private class ModuleLogging implements ISoundTriggerModule {
|
||||
private final ISoundTriggerModule mDelegate;
|
||||
|
||||
private ModuleLogging(ISoundTriggerModule delegate) {
|
||||
mDelegate = delegate;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int loadModel(SoundModel model) throws RemoteException {
|
||||
try {
|
||||
int result = mDelegate.loadModel(model);
|
||||
logReturn("loadModel", result, model);
|
||||
return result;
|
||||
} catch (Exception e) {
|
||||
logException("loadModel", e, model);
|
||||
throw e;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public int loadPhraseModel(PhraseSoundModel model) throws RemoteException {
|
||||
try {
|
||||
int result = mDelegate.loadPhraseModel(model);
|
||||
logReturn("loadPhraseModel", result, model);
|
||||
return result;
|
||||
} catch (Exception e) {
|
||||
logException("loadPhraseModel", e, model);
|
||||
throw e;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void unloadModel(int modelHandle) throws RemoteException {
|
||||
try {
|
||||
mDelegate.unloadModel(modelHandle);
|
||||
logVoidReturn("unloadModel", modelHandle);
|
||||
} catch (Exception e) {
|
||||
logException("unloadModel", e, modelHandle);
|
||||
throw e;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void startRecognition(int modelHandle, RecognitionConfig config)
|
||||
throws RemoteException {
|
||||
try {
|
||||
mDelegate.startRecognition(modelHandle, config);
|
||||
logVoidReturn("startRecognition", modelHandle, config);
|
||||
} catch (Exception e) {
|
||||
logException("startRecognition", e, modelHandle, config);
|
||||
throw e;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void stopRecognition(int modelHandle) throws RemoteException {
|
||||
try {
|
||||
mDelegate.stopRecognition(modelHandle);
|
||||
logVoidReturn("stopRecognition", modelHandle);
|
||||
} catch (Exception e) {
|
||||
logException("stopRecognition", e, modelHandle);
|
||||
throw e;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void forceRecognitionEvent(int modelHandle) throws RemoteException {
|
||||
try {
|
||||
mDelegate.forceRecognitionEvent(modelHandle);
|
||||
logVoidReturn("forceRecognitionEvent", modelHandle);
|
||||
} catch (Exception e) {
|
||||
logException("forceRecognitionEvent", e, modelHandle);
|
||||
throw e;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setModelParameter(int modelHandle, int modelParam, int value)
|
||||
throws RemoteException {
|
||||
try {
|
||||
mDelegate.setModelParameter(modelHandle, modelParam, value);
|
||||
logVoidReturn("setModelParameter", modelHandle, modelParam, value);
|
||||
} catch (Exception e) {
|
||||
logException("setModelParameter", e, modelHandle, modelParam, value);
|
||||
throw e;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getModelParameter(int modelHandle, int modelParam) throws RemoteException {
|
||||
try {
|
||||
int result = mDelegate.getModelParameter(modelHandle, modelParam);
|
||||
logReturn("getModelParameter", result, modelHandle, modelParam);
|
||||
return result;
|
||||
} catch (Exception e) {
|
||||
logException("getModelParameter", e, modelHandle, modelParam);
|
||||
throw e;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public ModelParameterRange queryModelParameterSupport(int modelHandle, int modelParam)
|
||||
throws RemoteException {
|
||||
try {
|
||||
ModelParameterRange result = mDelegate.queryModelParameterSupport(modelHandle,
|
||||
modelParam);
|
||||
logReturn("queryModelParameterSupport", result, modelHandle, modelParam);
|
||||
return result;
|
||||
} catch (Exception e) {
|
||||
logException("queryModelParameterSupport", e, modelHandle, modelParam);
|
||||
throw e;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void detach() throws RemoteException {
|
||||
try {
|
||||
mDelegate.detach();
|
||||
logVoidReturn("detach");
|
||||
} catch (Exception e) {
|
||||
logException("detach", e);
|
||||
throw e;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public IBinder asBinder() {
|
||||
return mDelegate.asBinder();
|
||||
}
|
||||
|
||||
// Override toString() in order to have the delegate's ID in it.
|
||||
@Override
|
||||
public String toString() {
|
||||
return mDelegate.toString();
|
||||
}
|
||||
|
||||
private void logException(String methodName, Exception ex, Object... args) {
|
||||
logExceptionWithObject(this, methodName, ex, args);
|
||||
}
|
||||
|
||||
private void logReturn(String methodName, Object retVal, Object... args) {
|
||||
logReturnWithObject(this, methodName, retVal, args);
|
||||
}
|
||||
|
||||
private void logVoidReturn(String methodName, Object... args) {
|
||||
logVoidReturnWithObject(this, methodName, args);
|
||||
}
|
||||
}
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
// Actual logging logic below.
|
||||
private static final int NUM_EVENTS_TO_DUMP = 64;
|
||||
private final static SimpleDateFormat DATE_FORMAT = new SimpleDateFormat("MM-dd HH:mm:ss:SSS");
|
||||
private final @NonNull LinkedList<Event> mLastEvents = new LinkedList<>();
|
||||
|
||||
static private class Event {
|
||||
public final long timestamp = System.currentTimeMillis();
|
||||
public final String message;
|
||||
|
||||
private Event(String message) {
|
||||
this.message = message;
|
||||
}
|
||||
}
|
||||
|
||||
private static String printArgs(@NonNull Object[] args) {
|
||||
StringBuilder result = new StringBuilder();
|
||||
for (int i = 0; i < args.length; ++i) {
|
||||
if (i > 0) {
|
||||
result.append(", ");
|
||||
}
|
||||
printObject(result, args[i]);
|
||||
}
|
||||
return result.toString();
|
||||
}
|
||||
|
||||
private static void printObject(@NonNull StringBuilder builder, @Nullable Object obj) {
|
||||
if (obj instanceof Parcelable) {
|
||||
ObjectPrinter.print(builder, obj, true, 16);
|
||||
} else {
|
||||
builder.append(obj.toString());
|
||||
}
|
||||
}
|
||||
|
||||
private static String printObject(@Nullable Object obj) {
|
||||
StringBuilder builder = new StringBuilder();
|
||||
printObject(builder, obj);
|
||||
return builder.toString();
|
||||
}
|
||||
|
||||
private void logReturnWithObject(@NonNull Object object, String methodName,
|
||||
@Nullable Object retVal,
|
||||
@NonNull Object[] args) {
|
||||
final String message = String.format("%s[this=%s, caller=%d/%d](%s) -> %s", methodName,
|
||||
object,
|
||||
Binder.getCallingUid(), Binder.getCallingPid(),
|
||||
printArgs(args),
|
||||
printObject(retVal));
|
||||
Log.i(TAG, message);
|
||||
appendMessage(message);
|
||||
}
|
||||
|
||||
private void logVoidReturnWithObject(@NonNull Object object, @NonNull String methodName,
|
||||
@NonNull Object[] args) {
|
||||
final String message = String.format("%s[this=%s, caller=%d/%d](%s)", methodName,
|
||||
object,
|
||||
Binder.getCallingUid(), Binder.getCallingPid(),
|
||||
printArgs(args));
|
||||
Log.i(TAG, message);
|
||||
appendMessage(message);
|
||||
}
|
||||
|
||||
private void logExceptionWithObject(@NonNull Object object, @NonNull String methodName,
|
||||
@NonNull Exception ex,
|
||||
Object[] args) {
|
||||
final String message = String.format("%s[this=%s, caller=%d/%d](%s) threw", methodName,
|
||||
object,
|
||||
Binder.getCallingUid(), Binder.getCallingPid(),
|
||||
printArgs(args));
|
||||
Log.e(TAG, message, ex);
|
||||
appendMessage(message + " " + ex.toString());
|
||||
}
|
||||
|
||||
private void appendMessage(@NonNull String message) {
|
||||
Event event = new Event(message);
|
||||
synchronized (mLastEvents) {
|
||||
if (mLastEvents.size() > NUM_EVENTS_TO_DUMP) {
|
||||
mLastEvents.remove();
|
||||
}
|
||||
mLastEvents.add(event);
|
||||
}
|
||||
}
|
||||
|
||||
@Override public void dump(PrintWriter pw) {
|
||||
pw.println();
|
||||
pw.println("=========================================");
|
||||
pw.println("Last events");
|
||||
pw.println("=========================================");
|
||||
synchronized (mLastEvents) {
|
||||
for (Event event : mLastEvents) {
|
||||
pw.print(DATE_FORMAT.format(new Date(event.timestamp)));
|
||||
pw.print('\t');
|
||||
pw.println(event.message);
|
||||
}
|
||||
}
|
||||
pw.println();
|
||||
|
||||
if (mDelegate instanceof Dumpable) {
|
||||
((Dumpable) mDelegate).dump(pw);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -16,91 +16,40 @@
|
||||
|
||||
package com.android.server.soundtrigger_middleware;
|
||||
|
||||
import android.Manifest;
|
||||
import android.annotation.NonNull;
|
||||
import android.annotation.Nullable;
|
||||
import android.content.Context;
|
||||
import android.content.PermissionChecker;
|
||||
import android.hardware.soundtrigger.V2_0.ISoundTriggerHw;
|
||||
import android.media.soundtrigger_middleware.ISoundTriggerCallback;
|
||||
import android.media.soundtrigger_middleware.ISoundTriggerMiddlewareService;
|
||||
import android.media.soundtrigger_middleware.ISoundTriggerModule;
|
||||
import android.media.soundtrigger_middleware.ModelParameterRange;
|
||||
import android.media.soundtrigger_middleware.PhraseRecognitionEvent;
|
||||
import android.media.soundtrigger_middleware.PhraseSoundModel;
|
||||
import android.media.soundtrigger_middleware.RecognitionConfig;
|
||||
import android.media.soundtrigger_middleware.RecognitionEvent;
|
||||
import android.media.soundtrigger_middleware.RecognitionStatus;
|
||||
import android.media.soundtrigger_middleware.SoundModel;
|
||||
import android.media.soundtrigger_middleware.SoundTriggerModuleDescriptor;
|
||||
import android.media.soundtrigger_middleware.Status;
|
||||
import android.os.RemoteException;
|
||||
import android.os.ServiceSpecificException;
|
||||
import android.util.Log;
|
||||
|
||||
import com.android.internal.util.Preconditions;
|
||||
import com.android.server.SystemService;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.HashSet;
|
||||
import java.util.Map;
|
||||
import java.io.FileDescriptor;
|
||||
import java.io.PrintWriter;
|
||||
import java.util.Objects;
|
||||
import java.util.Set;
|
||||
|
||||
/**
|
||||
* This is a wrapper around an {@link ISoundTriggerMiddlewareService} implementation, which exposes
|
||||
* it as a Binder service and enforces permissions and correct usage by the client, as well as makes
|
||||
* sure that exceptions representing a server malfunction do not get sent to the client.
|
||||
* it as a Binder service.
|
||||
* <p>
|
||||
* This is intended to extract the non-business logic out of the underlying implementation and thus
|
||||
* make it easier to maintain each one of those separate aspects. A design trade-off is being made
|
||||
* here, in that this class would need to essentially eavesdrop on all the client-server
|
||||
* communication and retain all state known to the client, while the client doesn't necessarily care
|
||||
* about all of it, and while the server has its own representation of this information. However,
|
||||
* in this case, this is a small amount of data, and the benefits in code elegance seem worth it.
|
||||
* There is also some additional cost in employing a simplistic locking mechanism here, but
|
||||
* following the same line of reasoning, the benefits in code simplicity outweigh it.
|
||||
* 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>
|
||||
* Every public method in this class, overriding an interface method, must follow the following
|
||||
* pattern:
|
||||
* <code><pre>
|
||||
* @Override public T method(S arg) {
|
||||
* // Permission check.
|
||||
* checkPermissions();
|
||||
* // Input validation.
|
||||
* ValidationUtil.validateS(arg);
|
||||
* synchronized (this) {
|
||||
* // State validation.
|
||||
* if (...state is not valid for this call...) {
|
||||
* throw new IllegalStateException("State is invalid because...");
|
||||
* }
|
||||
* // From here on, every exception isn't client's fault.
|
||||
* try {
|
||||
* T result = mDelegate.method(arg);
|
||||
* // Update state.;
|
||||
* ...
|
||||
* return result;
|
||||
* } catch (Exception e) {
|
||||
* throw handleException(e);
|
||||
* }
|
||||
* }
|
||||
* }
|
||||
* </pre></code>
|
||||
* Following this patterns ensures a consistent and rigorous handling of all aspects associated
|
||||
* with client-server separation.
|
||||
* <p>
|
||||
* <b>Exception handling approach:</b><br>
|
||||
* We make sure all client faults (permissions, argument and state validation) happen first, and
|
||||
* would throw {@link SecurityException}, {@link IllegalArgumentException}/
|
||||
* {@link NullPointerException} or {@link
|
||||
* IllegalStateException}, respectively. All those exceptions are treated specially by Binder and
|
||||
* will get sent back to the client.<br>
|
||||
* Once this is done, any subsequent fault is considered a server fault. Only {@link
|
||||
* RecoverableException}s thrown by the implementation are special-cased: they would get sent back
|
||||
* to the caller as a {@link ServiceSpecificException}, which is the behavior of Binder. Any other
|
||||
* exception gets wrapped with a {@link InternalServerError}, which is specifically chosen as a type
|
||||
* that <b>does NOT</b> get forwarded by binder. Those exceptions would be handled by a high-level
|
||||
* exception handler on the server side, typically resulting in rebooting the server.
|
||||
* 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}:
|
||||
@@ -113,224 +62,100 @@ import java.util.Set;
|
||||
public class SoundTriggerMiddlewareService extends ISoundTriggerMiddlewareService.Stub {
|
||||
static private final String TAG = "SoundTriggerMiddlewareService";
|
||||
|
||||
@NonNull
|
||||
private final ISoundTriggerMiddlewareService mDelegate;
|
||||
private final Context mContext;
|
||||
private Set<Integer> mModuleHandles;
|
||||
|
||||
/**
|
||||
* 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 ISoundTriggerMiddlewareService delegate, @NonNull Context context) {
|
||||
mDelegate = delegate;
|
||||
mContext = context;
|
||||
}
|
||||
|
||||
/**
|
||||
* Generic exception handling for exceptions thrown by the underlying implementation.
|
||||
*
|
||||
* Would throw any {@link RecoverableException} as a {@link ServiceSpecificException} (passed
|
||||
* by Binder to the caller) and <i>any other</i> exception as {@link InternalServerError}
|
||||
* (<b>not</b> passed by Binder to the caller).
|
||||
* <p>
|
||||
* Typical usage:
|
||||
* <code><pre>
|
||||
* try {
|
||||
* ... Do server operations ...
|
||||
* } catch (Exception e) {
|
||||
* throw handleException(e);
|
||||
* }
|
||||
* </pre></code>
|
||||
*/
|
||||
private static @NonNull
|
||||
RuntimeException handleException(@NonNull Exception e) {
|
||||
if (e instanceof RecoverableException) {
|
||||
throw new ServiceSpecificException(((RecoverableException) e).errorCode,
|
||||
e.getMessage());
|
||||
}
|
||||
throw new InternalServerError(e);
|
||||
private SoundTriggerMiddlewareService(@NonNull ISoundTriggerMiddlewareService delegate) {
|
||||
mDelegate = Objects.requireNonNull(delegate);
|
||||
}
|
||||
|
||||
@Override
|
||||
public @NonNull
|
||||
SoundTriggerModuleDescriptor[] listModules() {
|
||||
// Permission check.
|
||||
checkPermissions();
|
||||
// Input validation (always valid).
|
||||
|
||||
synchronized (this) {
|
||||
// State validation (always valid).
|
||||
|
||||
// From here on, every exception isn't client's fault.
|
||||
try {
|
||||
SoundTriggerModuleDescriptor[] result = mDelegate.listModules();
|
||||
mModuleHandles = new HashSet<>(result.length);
|
||||
for (SoundTriggerModuleDescriptor desc : result) {
|
||||
mModuleHandles.add(desc.handle);
|
||||
}
|
||||
return result;
|
||||
} catch (Exception e) {
|
||||
throw handleException(e);
|
||||
}
|
||||
}
|
||||
SoundTriggerModuleDescriptor[] listModules() throws RemoteException {
|
||||
return mDelegate.listModules();
|
||||
}
|
||||
|
||||
@Override
|
||||
public @NonNull
|
||||
ISoundTriggerModule attach(int handle, @NonNull ISoundTriggerCallback callback) {
|
||||
// Permission check.
|
||||
checkPermissions();
|
||||
// Input validation.
|
||||
Objects.requireNonNull(callback);
|
||||
Objects.requireNonNull(callback.asBinder());
|
||||
|
||||
synchronized (this) {
|
||||
// State validation.
|
||||
if (mModuleHandles == null) {
|
||||
throw new IllegalStateException(
|
||||
"Client must call listModules() prior to attaching.");
|
||||
}
|
||||
if (!mModuleHandles.contains(handle)) {
|
||||
throw new IllegalArgumentException("Invalid handle: " + handle);
|
||||
}
|
||||
|
||||
// From here on, every exception isn't client's fault.
|
||||
try {
|
||||
ModuleService moduleService = new ModuleService(callback);
|
||||
moduleService.attach(mDelegate.attach(handle, moduleService));
|
||||
return moduleService;
|
||||
} catch (Exception e) {
|
||||
throw handleException(e);
|
||||
}
|
||||
}
|
||||
ISoundTriggerModule attach(int handle, @NonNull ISoundTriggerCallback callback)
|
||||
throws RemoteException {
|
||||
return new ModuleService(mDelegate.attach(handle, callback));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setExternalCaptureState(boolean active) {
|
||||
// Permission check.
|
||||
checkPreemptPermissions();
|
||||
// Input validation (always valid).
|
||||
public void setExternalCaptureState(boolean active) throws RemoteException {
|
||||
mDelegate.setExternalCaptureState(active);
|
||||
}
|
||||
|
||||
synchronized (this) {
|
||||
// State validation (always valid).
|
||||
|
||||
// From here on, every exception isn't client's fault.
|
||||
try {
|
||||
mDelegate.setExternalCaptureState(active);
|
||||
} catch (Exception e) {
|
||||
throw handleException(e);
|
||||
}
|
||||
@Override protected void dump(FileDescriptor fd, PrintWriter fout, String[] args) {
|
||||
if (mDelegate instanceof Dumpable) {
|
||||
((Dumpable) mDelegate).dump(fout);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Throws a {@link SecurityException} if caller permanently doesn't have the given permission,
|
||||
* or a {@link ServiceSpecificException} with a {@link Status#TEMPORARY_PERMISSION_DENIED} if
|
||||
* caller temporarily doesn't have the right permissions to use this service.
|
||||
*/
|
||||
private void checkPermissions() {
|
||||
enforcePermission(Manifest.permission.RECORD_AUDIO);
|
||||
enforcePermission(Manifest.permission.CAPTURE_AUDIO_HOTWORD);
|
||||
}
|
||||
private final static class ModuleService extends ISoundTriggerModule.Stub {
|
||||
private final ISoundTriggerModule mDelegate;
|
||||
|
||||
/**
|
||||
* Throws a {@link SecurityException} if caller permanently doesn't have the given permission,
|
||||
* or a {@link ServiceSpecificException} with a {@link Status#TEMPORARY_PERMISSION_DENIED} if
|
||||
* caller temporarily doesn't have the right permissions to preempt active sound trigger
|
||||
* sessions.
|
||||
*/
|
||||
private void checkPreemptPermissions() {
|
||||
enforcePermission(Manifest.permission.PREEMPT_SOUND_TRIGGER);
|
||||
}
|
||||
|
||||
/**
|
||||
* Throws a {@link SecurityException} if caller permanently doesn't have the given permission,
|
||||
* or a {@link ServiceSpecificException} with a {@link Status#TEMPORARY_PERMISSION_DENIED} if
|
||||
* caller temporarily doesn't have the given permission.
|
||||
*
|
||||
* @param permission The permission to check.
|
||||
*/
|
||||
private void enforcePermission(String permission) {
|
||||
final int status = PermissionChecker.checkCallingOrSelfPermissionForPreflight(mContext,
|
||||
permission);
|
||||
switch (status) {
|
||||
case PermissionChecker.PERMISSION_GRANTED:
|
||||
return;
|
||||
case PermissionChecker.PERMISSION_HARD_DENIED:
|
||||
throw new SecurityException(
|
||||
String.format("Caller must have the %s permission.", permission));
|
||||
case PermissionChecker.PERMISSION_SOFT_DENIED:
|
||||
throw new ServiceSpecificException(Status.TEMPORARY_PERMISSION_DENIED,
|
||||
String.format("Caller must have the %s permission.", permission));
|
||||
default:
|
||||
throw new InternalServerError(
|
||||
new RuntimeException("Unexpected perimission check result."));
|
||||
}
|
||||
}
|
||||
|
||||
/** State of a sound model. */
|
||||
static class ModelState {
|
||||
/** Activity state of a sound model. */
|
||||
enum Activity {
|
||||
/** Model is loaded, recognition is inactive. */
|
||||
LOADED,
|
||||
/** Model is loaded, recognition is active. */
|
||||
ACTIVE
|
||||
private ModuleService(ISoundTriggerModule delegate) {
|
||||
mDelegate = delegate;
|
||||
}
|
||||
|
||||
/** Activity state. */
|
||||
Activity activityState = Activity.LOADED;
|
||||
|
||||
/**
|
||||
* A map of known parameter support. A missing key means we don't know yet whether the
|
||||
* parameter is supported. A null value means it is known to not be supported. A non-null
|
||||
* value indicates the valid value range.
|
||||
*/
|
||||
private Map<Integer, ModelParameterRange> parameterSupport = new HashMap<>();
|
||||
|
||||
/**
|
||||
* Check that the given parameter is known to be supported for this model.
|
||||
*
|
||||
* @param modelParam The parameter key.
|
||||
*/
|
||||
void checkSupported(int modelParam) {
|
||||
if (!parameterSupport.containsKey(modelParam)) {
|
||||
throw new IllegalStateException("Parameter has not been checked for support.");
|
||||
}
|
||||
ModelParameterRange range = parameterSupport.get(modelParam);
|
||||
if (range == null) {
|
||||
throw new IllegalArgumentException("Paramater is not supported.");
|
||||
}
|
||||
@Override
|
||||
public int loadModel(SoundModel model) throws RemoteException {
|
||||
return mDelegate.loadModel(model);
|
||||
}
|
||||
|
||||
/**
|
||||
* Check that the given parameter is known to be supported for this model and that the given
|
||||
* value is a valid value for it.
|
||||
*
|
||||
* @param modelParam The parameter key.
|
||||
* @param value The value.
|
||||
*/
|
||||
void checkSupported(int modelParam, int value) {
|
||||
if (!parameterSupport.containsKey(modelParam)) {
|
||||
throw new IllegalStateException("Parameter has not been checked for support.");
|
||||
}
|
||||
ModelParameterRange range = parameterSupport.get(modelParam);
|
||||
if (range == null) {
|
||||
throw new IllegalArgumentException("Paramater is not supported.");
|
||||
}
|
||||
Preconditions.checkArgumentInRange(value, range.minInclusive, range.maxInclusive,
|
||||
"value");
|
||||
@Override
|
||||
public int loadPhraseModel(PhraseSoundModel model) throws RemoteException {
|
||||
return mDelegate.loadPhraseModel(model);
|
||||
}
|
||||
|
||||
/**
|
||||
* Update support state for the given parameter for this model.
|
||||
*
|
||||
* @param modelParam The parameter key.
|
||||
* @param range The parameter value range, or null if not supported.
|
||||
*/
|
||||
void updateParameterSupport(int modelParam, @Nullable ModelParameterRange range) {
|
||||
parameterSupport.put(modelParam, range);
|
||||
@Override
|
||||
public void unloadModel(int modelHandle) throws RemoteException {
|
||||
mDelegate.unloadModel(modelHandle);
|
||||
;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void startRecognition(int modelHandle, RecognitionConfig config)
|
||||
throws RemoteException {
|
||||
mDelegate.startRecognition(modelHandle, config);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void stopRecognition(int modelHandle) throws RemoteException {
|
||||
mDelegate.stopRecognition(modelHandle);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void forceRecognitionEvent(int modelHandle) throws RemoteException {
|
||||
mDelegate.forceRecognitionEvent(modelHandle);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setModelParameter(int modelHandle, int modelParam, int value)
|
||||
throws RemoteException {
|
||||
mDelegate.setModelParameter(modelHandle, modelParam, value);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getModelParameter(int modelHandle, int modelParam) throws RemoteException {
|
||||
return mDelegate.getModelParameter(modelHandle, modelParam);
|
||||
}
|
||||
|
||||
@Override
|
||||
public ModelParameterRange queryModelParameterSupport(int modelHandle, int modelParam)
|
||||
throws RemoteException {
|
||||
return mDelegate.queryModelParameterSupport(modelHandle, modelParam);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void detach() throws RemoteException {
|
||||
mDelegate.detach();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -355,395 +180,11 @@ public class SoundTriggerMiddlewareService extends ISoundTriggerMiddlewareServic
|
||||
|
||||
publishBinderService(Context.SOUND_TRIGGER_MIDDLEWARE_SERVICE,
|
||||
new SoundTriggerMiddlewareService(
|
||||
new SoundTriggerMiddlewareImpl(factories,
|
||||
new AudioSessionProviderImpl()),
|
||||
getContext()));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* A wrapper around an {@link ISoundTriggerModule} implementation, to address the same aspects
|
||||
* mentioned in {@link SoundTriggerModule} above. This class follows the same conventions.
|
||||
*/
|
||||
private class ModuleService extends ISoundTriggerModule.Stub implements ISoundTriggerCallback,
|
||||
DeathRecipient {
|
||||
private final ISoundTriggerCallback mCallback;
|
||||
private ISoundTriggerModule mDelegate;
|
||||
private @NonNull Map<Integer, ModelState> mLoadedModels = new HashMap<>();
|
||||
|
||||
ModuleService(@NonNull ISoundTriggerCallback callback) {
|
||||
mCallback = callback;
|
||||
try {
|
||||
mCallback.asBinder().linkToDeath(this, 0);
|
||||
} catch (RemoteException e) {
|
||||
throw e.rethrowAsRuntimeException();
|
||||
}
|
||||
}
|
||||
|
||||
void attach(@NonNull ISoundTriggerModule delegate) {
|
||||
mDelegate = delegate;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int loadModel(@NonNull SoundModel model) {
|
||||
// Permission check.
|
||||
checkPermissions();
|
||||
// Input validation.
|
||||
ValidationUtil.validateGenericModel(model);
|
||||
|
||||
synchronized (this) {
|
||||
// State validation.
|
||||
if (mDelegate == null) {
|
||||
throw new IllegalStateException("Module has been detached.");
|
||||
}
|
||||
|
||||
// From here on, every exception isn't client's fault.
|
||||
try {
|
||||
int handle = mDelegate.loadModel(model);
|
||||
mLoadedModels.put(handle, new ModelState());
|
||||
return handle;
|
||||
} catch (Exception e) {
|
||||
throw handleException(e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public int loadPhraseModel(@NonNull PhraseSoundModel model) {
|
||||
// Permission check.
|
||||
checkPermissions();
|
||||
// Input validation.
|
||||
ValidationUtil.validatePhraseModel(model);
|
||||
|
||||
synchronized (this) {
|
||||
// State validation.
|
||||
if (mDelegate == null) {
|
||||
throw new IllegalStateException("Module has been detached.");
|
||||
}
|
||||
|
||||
// From here on, every exception isn't client's fault.
|
||||
try {
|
||||
int handle = mDelegate.loadPhraseModel(model);
|
||||
mLoadedModels.put(handle, new ModelState());
|
||||
return handle;
|
||||
} catch (Exception e) {
|
||||
throw handleException(e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void unloadModel(int modelHandle) {
|
||||
// Permission check.
|
||||
checkPermissions();
|
||||
// Input validation (always valid).
|
||||
|
||||
synchronized (this) {
|
||||
// State validation.
|
||||
if (mDelegate == null) {
|
||||
throw new IllegalStateException("Module has been detached.");
|
||||
}
|
||||
ModelState modelState = mLoadedModels.get(modelHandle);
|
||||
if (modelState == null) {
|
||||
throw new IllegalStateException("Invalid handle: " + modelHandle);
|
||||
}
|
||||
if (modelState.activityState != ModelState.Activity.LOADED) {
|
||||
throw new IllegalStateException("Model with handle: " + modelHandle
|
||||
+ " has invalid state for unloading: " + modelState.activityState);
|
||||
}
|
||||
|
||||
// From here on, every exception isn't client's fault.
|
||||
try {
|
||||
mDelegate.unloadModel(modelHandle);
|
||||
mLoadedModels.remove(modelHandle);
|
||||
} catch (Exception e) {
|
||||
throw handleException(e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void startRecognition(int modelHandle, @NonNull RecognitionConfig config) {
|
||||
// Permission check.
|
||||
checkPermissions();
|
||||
// Input validation.
|
||||
ValidationUtil.validateRecognitionConfig(config);
|
||||
|
||||
synchronized (this) {
|
||||
// State validation.
|
||||
if (mDelegate == null) {
|
||||
throw new IllegalStateException("Module has been detached.");
|
||||
}
|
||||
ModelState modelState = mLoadedModels.get(modelHandle);
|
||||
if (modelState == null) {
|
||||
throw new IllegalStateException("Invalid handle: " + modelHandle);
|
||||
}
|
||||
if (modelState.activityState != ModelState.Activity.LOADED) {
|
||||
throw new IllegalStateException("Model with handle: " + modelHandle
|
||||
+ " has invalid state for starting recognition: "
|
||||
+ modelState.activityState);
|
||||
}
|
||||
|
||||
// From here on, every exception isn't client's fault.
|
||||
try {
|
||||
mDelegate.startRecognition(modelHandle, config);
|
||||
modelState.activityState = ModelState.Activity.ACTIVE;
|
||||
} catch (Exception e) {
|
||||
throw handleException(e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void stopRecognition(int modelHandle) {
|
||||
// Permission check.
|
||||
checkPermissions();
|
||||
// Input validation (always valid).
|
||||
|
||||
synchronized (this) {
|
||||
// State validation.
|
||||
if (mDelegate == null) {
|
||||
throw new IllegalStateException("Module has been detached.");
|
||||
}
|
||||
ModelState modelState = mLoadedModels.get(modelHandle);
|
||||
if (modelState == null) {
|
||||
throw new IllegalStateException("Invalid handle: " + modelHandle);
|
||||
}
|
||||
// stopRecognition is idempotent - no need to check model state.
|
||||
|
||||
// From here on, every exception isn't client's fault.
|
||||
try {
|
||||
mDelegate.stopRecognition(modelHandle);
|
||||
modelState.activityState = ModelState.Activity.LOADED;
|
||||
} catch (Exception e) {
|
||||
throw handleException(e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void forceRecognitionEvent(int modelHandle) {
|
||||
// Permission check.
|
||||
checkPermissions();
|
||||
// Input validation (always valid).
|
||||
|
||||
synchronized (this) {
|
||||
// State validation.
|
||||
if (mDelegate == null) {
|
||||
throw new IllegalStateException("Module has been detached.");
|
||||
}
|
||||
ModelState modelState = mLoadedModels.get(modelHandle);
|
||||
if (modelState == null) {
|
||||
throw new IllegalStateException("Invalid handle: " + modelHandle);
|
||||
}
|
||||
// forceRecognitionEvent is idempotent - no need to check model state.
|
||||
|
||||
// From here on, every exception isn't client's fault.
|
||||
try {
|
||||
mDelegate.forceRecognitionEvent(modelHandle);
|
||||
} catch (Exception e) {
|
||||
throw handleException(e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setModelParameter(int modelHandle, int modelParam, int value) {
|
||||
// Permission check.
|
||||
checkPermissions();
|
||||
// Input validation.
|
||||
ValidationUtil.validateModelParameter(modelParam);
|
||||
|
||||
synchronized (this) {
|
||||
// State validation.
|
||||
if (mDelegate == null) {
|
||||
throw new IllegalStateException("Module has been detached.");
|
||||
}
|
||||
ModelState modelState = mLoadedModels.get(modelHandle);
|
||||
if (modelState == null) {
|
||||
throw new IllegalStateException("Invalid handle: " + modelHandle);
|
||||
}
|
||||
modelState.checkSupported(modelParam, value);
|
||||
|
||||
// From here on, every exception isn't client's fault.
|
||||
try {
|
||||
mDelegate.setModelParameter(modelHandle, modelParam, value);
|
||||
} catch (Exception e) {
|
||||
throw handleException(e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getModelParameter(int modelHandle, int modelParam) {
|
||||
// Permission check.
|
||||
checkPermissions();
|
||||
// Input validation.
|
||||
ValidationUtil.validateModelParameter(modelParam);
|
||||
|
||||
synchronized (this) {
|
||||
// State validation.
|
||||
if (mDelegate == null) {
|
||||
throw new IllegalStateException("Module has been detached.");
|
||||
}
|
||||
ModelState modelState = mLoadedModels.get(modelHandle);
|
||||
if (modelState == null) {
|
||||
throw new IllegalStateException("Invalid handle: " + modelHandle);
|
||||
}
|
||||
modelState.checkSupported(modelParam);
|
||||
|
||||
// From here on, every exception isn't client's fault.
|
||||
try {
|
||||
return mDelegate.getModelParameter(modelHandle, modelParam);
|
||||
} catch (Exception e) {
|
||||
throw handleException(e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
@Nullable
|
||||
public ModelParameterRange queryModelParameterSupport(int modelHandle, int modelParam) {
|
||||
// Permission check.
|
||||
checkPermissions();
|
||||
// Input validation.
|
||||
ValidationUtil.validateModelParameter(modelParam);
|
||||
|
||||
synchronized (this) {
|
||||
// State validation.
|
||||
if (mDelegate == null) {
|
||||
throw new IllegalStateException("Module has been detached.");
|
||||
}
|
||||
ModelState modelState = mLoadedModels.get(modelHandle);
|
||||
if (modelState == null) {
|
||||
throw new IllegalStateException("Invalid handle: " + modelHandle);
|
||||
}
|
||||
|
||||
// From here on, every exception isn't client's fault.
|
||||
try {
|
||||
ModelParameterRange result = mDelegate.queryModelParameterSupport(modelHandle,
|
||||
modelParam);
|
||||
modelState.updateParameterSupport(modelParam, result);
|
||||
return result;
|
||||
} catch (Exception e) {
|
||||
throw handleException(e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void detach() {
|
||||
// Permission check.
|
||||
checkPermissions();
|
||||
// Input validation (always valid).
|
||||
|
||||
synchronized (this) {
|
||||
// State validation.
|
||||
if (mDelegate == null) {
|
||||
throw new IllegalStateException("Module has already been detached.");
|
||||
}
|
||||
if (!mLoadedModels.isEmpty()) {
|
||||
throw new IllegalStateException("Cannot detach while models are loaded.");
|
||||
}
|
||||
|
||||
// From here on, every exception isn't client's fault.
|
||||
try {
|
||||
detachInternal();
|
||||
} catch (Exception e) {
|
||||
throw handleException(e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void detachInternal() {
|
||||
try {
|
||||
mDelegate.detach();
|
||||
mDelegate = null;
|
||||
mCallback.asBinder().unlinkToDeath(this, 0);
|
||||
} catch (RemoteException e) {
|
||||
throw e.rethrowAsRuntimeException();
|
||||
}
|
||||
}
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////////////////
|
||||
// Callbacks
|
||||
|
||||
@Override
|
||||
public void onRecognition(int modelHandle, @NonNull RecognitionEvent event) {
|
||||
synchronized (this) {
|
||||
if (event.status != RecognitionStatus.FORCED) {
|
||||
mLoadedModels.get(modelHandle).activityState = ModelState.Activity.LOADED;
|
||||
}
|
||||
try {
|
||||
mCallback.onRecognition(modelHandle, event);
|
||||
} catch (RemoteException e) {
|
||||
// Dead client will be handled by binderDied() - no need to handle here.
|
||||
// In any case, client callbacks are considered best effort.
|
||||
Log.e(TAG, "Client callback exception.", e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onPhraseRecognition(int modelHandle, @NonNull PhraseRecognitionEvent event) {
|
||||
synchronized (this) {
|
||||
if (event.common.status != RecognitionStatus.FORCED) {
|
||||
mLoadedModels.get(modelHandle).activityState = ModelState.Activity.LOADED;
|
||||
}
|
||||
try {
|
||||
mCallback.onPhraseRecognition(modelHandle, event);
|
||||
} catch (RemoteException e) {
|
||||
// Dead client will be handled by binderDied() - no need to handle here.
|
||||
// In any case, client callbacks are considered best effort.
|
||||
Log.e(TAG, "Client callback exception.", e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onRecognitionAvailabilityChange(boolean available) {
|
||||
synchronized (this) {
|
||||
try {
|
||||
mCallback.onRecognitionAvailabilityChange(available);
|
||||
} catch (RemoteException e) {
|
||||
// Dead client will be handled by binderDied() - no need to handle here.
|
||||
// In any case, client callbacks are considered best effort.
|
||||
Log.e(TAG, "Client callback exception.", e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onModuleDied() {
|
||||
synchronized (this) {
|
||||
try {
|
||||
mCallback.onModuleDied();
|
||||
} catch (RemoteException e) {
|
||||
// Dead client will be handled by binderDied() - no need to handle here.
|
||||
// In any case, client callbacks are considered best effort.
|
||||
Log.e(TAG, "Client callback exception.", e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void binderDied() {
|
||||
// This is called whenever our client process dies.
|
||||
synchronized (this) {
|
||||
try {
|
||||
// Gracefully stop all active recognitions and unload the models.
|
||||
for (Map.Entry<Integer, ModelState> entry : mLoadedModels.entrySet()) {
|
||||
if (entry.getValue().activityState == ModelState.Activity.ACTIVE) {
|
||||
mDelegate.stopRecognition(entry.getKey());
|
||||
}
|
||||
mDelegate.unloadModel(entry.getKey());
|
||||
}
|
||||
// Detach.
|
||||
detachInternal();
|
||||
} catch (Exception e) {
|
||||
throw handleException(e);
|
||||
}
|
||||
}
|
||||
new SoundTriggerMiddlewareLogging(
|
||||
new SoundTriggerMiddlewareValidation(
|
||||
new SoundTriggerMiddlewareImpl(factories,
|
||||
new AudioSessionProviderImpl()),
|
||||
getContext()))));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,784 @@
|
||||
/*
|
||||
* Copyright (C) 2020 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 android.Manifest;
|
||||
import android.annotation.NonNull;
|
||||
import android.annotation.Nullable;
|
||||
import android.content.Context;
|
||||
import android.content.PermissionChecker;
|
||||
import android.media.soundtrigger_middleware.ISoundTriggerCallback;
|
||||
import android.media.soundtrigger_middleware.ISoundTriggerMiddlewareService;
|
||||
import android.media.soundtrigger_middleware.ISoundTriggerModule;
|
||||
import android.media.soundtrigger_middleware.ModelParameterRange;
|
||||
import android.media.soundtrigger_middleware.PhraseRecognitionEvent;
|
||||
import android.media.soundtrigger_middleware.PhraseSoundModel;
|
||||
import android.media.soundtrigger_middleware.RecognitionConfig;
|
||||
import android.media.soundtrigger_middleware.RecognitionEvent;
|
||||
import android.media.soundtrigger_middleware.RecognitionStatus;
|
||||
import android.media.soundtrigger_middleware.SoundModel;
|
||||
import android.media.soundtrigger_middleware.SoundTriggerModuleDescriptor;
|
||||
import android.media.soundtrigger_middleware.Status;
|
||||
import android.os.IBinder;
|
||||
import android.os.RemoteException;
|
||||
import android.os.ServiceSpecificException;
|
||||
import android.util.Log;
|
||||
|
||||
import com.android.internal.util.Preconditions;
|
||||
|
||||
import java.io.PrintWriter;
|
||||
import java.util.HashMap;
|
||||
import java.util.HashSet;
|
||||
import java.util.Map;
|
||||
import java.util.Objects;
|
||||
import java.util.Set;
|
||||
|
||||
/**
|
||||
* This is a decorator of an {@link ISoundTriggerMiddlewareService}, which enforces permissions and
|
||||
* correct usage by the client, as well as makes sure that exceptions representing a server
|
||||
* malfunction do not get sent to the client.
|
||||
* <p>
|
||||
* This is intended to extract the non-business logic out of the underlying implementation and thus
|
||||
* make it easier to maintain each one of those separate aspects. A design trade-off is being made
|
||||
* here, in that this class would need to essentially eavesdrop on all the client-server
|
||||
* communication and retain all state known to the client, while the client doesn't necessarily care
|
||||
* about all of it, and while the server has its own representation of this information. However,
|
||||
* in this case, this is a small amount of data, and the benefits in code elegance seem worth it.
|
||||
* There is also some additional cost in employing a simplistic locking mechanism here, but
|
||||
* following the same line of reasoning, the benefits in code simplicity outweigh it.
|
||||
* <p>
|
||||
* Every public method in this class, overriding an interface method, must follow the following
|
||||
* pattern:
|
||||
* <code><pre>
|
||||
* @Override public T method(S arg) {
|
||||
* // Permission check.
|
||||
* checkPermissions();
|
||||
* // Input validation.
|
||||
* ValidationUtil.validateS(arg);
|
||||
* synchronized (this) {
|
||||
* // State validation.
|
||||
* if (...state is not valid for this call...) {
|
||||
* throw new IllegalStateException("State is invalid because...");
|
||||
* }
|
||||
* // From here on, every exception isn't client's fault.
|
||||
* try {
|
||||
* T result = mDelegate.method(arg);
|
||||
* // Update state.;
|
||||
* ...
|
||||
* return result;
|
||||
* } catch (Exception e) {
|
||||
* throw handleException(e);
|
||||
* }
|
||||
* }
|
||||
* }
|
||||
* </pre></code>
|
||||
* Following this patterns ensures a consistent and rigorous handling of all aspects associated
|
||||
* with client-server separation.
|
||||
* <p>
|
||||
* <b>Exception handling approach:</b><br>
|
||||
* We make sure all client faults (permissions, argument and state validation) happen first, and
|
||||
* would throw {@link SecurityException}, {@link IllegalArgumentException}/
|
||||
* {@link NullPointerException} or {@link
|
||||
* IllegalStateException}, respectively. All those exceptions are treated specially by Binder and
|
||||
* will get sent back to the client.<br>
|
||||
* Once this is done, any subsequent fault is considered a server fault. Only {@link
|
||||
* RecoverableException}s thrown by the implementation are special-cased: they would get sent back
|
||||
* to the caller as a {@link ServiceSpecificException}, which is the behavior of Binder. Any other
|
||||
* exception gets wrapped with a {@link InternalServerError}, which is specifically chosen as a type
|
||||
* that <b>does NOT</b> get forwarded by binder. Those exceptions would be handled by a high-level
|
||||
* exception handler on the server side, typically resulting in rebooting the server.
|
||||
*
|
||||
* {@hide}
|
||||
*/
|
||||
public class SoundTriggerMiddlewareValidation implements ISoundTriggerMiddlewareService, Dumpable {
|
||||
private static final String TAG = "SoundTriggerMiddlewareValidation";
|
||||
|
||||
private final @NonNull ISoundTriggerMiddlewareService mDelegate;
|
||||
private final @NonNull Context mContext;
|
||||
private Map<Integer, Set<ModuleService>> mModules;
|
||||
|
||||
public SoundTriggerMiddlewareValidation(
|
||||
@NonNull ISoundTriggerMiddlewareService delegate, @NonNull Context context) {
|
||||
mDelegate = delegate;
|
||||
mContext = context;
|
||||
}
|
||||
|
||||
/**
|
||||
* Generic exception handling for exceptions thrown by the underlying implementation.
|
||||
*
|
||||
* Would throw any {@link RecoverableException} as a {@link ServiceSpecificException} (passed
|
||||
* by Binder to the caller) and <i>any other</i> exception as {@link InternalServerError}
|
||||
* (<b>not</b> passed by Binder to the caller).
|
||||
* <p>
|
||||
* Typical usage:
|
||||
* <code><pre>
|
||||
* try {
|
||||
* ... Do server operations ...
|
||||
* } catch (Exception e) {
|
||||
* throw handleException(e);
|
||||
* }
|
||||
* </pre></code>
|
||||
*/
|
||||
static @NonNull
|
||||
RuntimeException handleException(@NonNull Exception e) {
|
||||
if (e instanceof RecoverableException) {
|
||||
throw new ServiceSpecificException(((RecoverableException) e).errorCode,
|
||||
e.getMessage());
|
||||
}
|
||||
throw new InternalServerError(e);
|
||||
}
|
||||
|
||||
@Override
|
||||
public @NonNull
|
||||
SoundTriggerModuleDescriptor[] listModules() {
|
||||
// Permission check.
|
||||
checkPermissions();
|
||||
// Input validation (always valid).
|
||||
|
||||
synchronized (this) {
|
||||
// State validation (always valid).
|
||||
|
||||
// From here on, every exception isn't client's fault.
|
||||
try {
|
||||
SoundTriggerModuleDescriptor[] result = mDelegate.listModules();
|
||||
mModules = new HashMap<>(result.length);
|
||||
for (SoundTriggerModuleDescriptor desc : result) {
|
||||
mModules.put(desc.handle, new HashSet<>());
|
||||
}
|
||||
return result;
|
||||
} catch (Exception e) {
|
||||
throw handleException(e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public @NonNull ISoundTriggerModule attach(int handle,
|
||||
@NonNull ISoundTriggerCallback callback) {
|
||||
// Permission check.
|
||||
checkPermissions();
|
||||
// Input validation.
|
||||
Objects.requireNonNull(callback);
|
||||
Objects.requireNonNull(callback.asBinder());
|
||||
|
||||
synchronized (this) {
|
||||
// State validation.
|
||||
if (mModules == null) {
|
||||
throw new IllegalStateException(
|
||||
"Client must call listModules() prior to attaching.");
|
||||
}
|
||||
if (!mModules.containsKey(handle)) {
|
||||
throw new IllegalArgumentException("Invalid handle: " + handle);
|
||||
}
|
||||
|
||||
// From here on, every exception isn't client's fault.
|
||||
try {
|
||||
ModuleService moduleService =
|
||||
new ModuleService(handle, callback);
|
||||
moduleService.attach(mDelegate.attach(handle, moduleService));
|
||||
return moduleService;
|
||||
} catch (Exception e) {
|
||||
throw handleException(e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setExternalCaptureState(boolean active) {
|
||||
// Permission check.
|
||||
checkPreemptPermissions();
|
||||
// Input validation (always valid).
|
||||
|
||||
synchronized (this) {
|
||||
// State validation (always valid).
|
||||
|
||||
// From here on, every exception isn't client's fault.
|
||||
try {
|
||||
mDelegate.setExternalCaptureState(active);
|
||||
} catch (Exception e) {
|
||||
throw handleException(e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Override toString() in order to have the delegate's ID in it.
|
||||
@Override
|
||||
public String toString() {
|
||||
return mDelegate.toString();
|
||||
}
|
||||
|
||||
/**
|
||||
* Throws a {@link SecurityException} if caller permanently doesn't have the given permission,
|
||||
* or a {@link ServiceSpecificException} with a {@link Status#TEMPORARY_PERMISSION_DENIED} if
|
||||
* caller temporarily doesn't have the right permissions to use this service.
|
||||
*/
|
||||
void checkPermissions() {
|
||||
enforcePermission(Manifest.permission.RECORD_AUDIO);
|
||||
enforcePermission(Manifest.permission.CAPTURE_AUDIO_HOTWORD);
|
||||
}
|
||||
|
||||
/**
|
||||
* Throws a {@link SecurityException} if caller permanently doesn't have the given permission,
|
||||
* or a {@link ServiceSpecificException} with a {@link Status#TEMPORARY_PERMISSION_DENIED} if
|
||||
* caller temporarily doesn't have the right permissions to preempt active sound trigger
|
||||
* sessions.
|
||||
*/
|
||||
void checkPreemptPermissions() {
|
||||
enforcePermission(Manifest.permission.PREEMPT_SOUND_TRIGGER);
|
||||
}
|
||||
|
||||
/**
|
||||
* Throws a {@link SecurityException} if caller permanently doesn't have the given permission,
|
||||
* or a {@link ServiceSpecificException} with a {@link Status#TEMPORARY_PERMISSION_DENIED} if
|
||||
* caller temporarily doesn't have the given permission.
|
||||
*
|
||||
* @param permission The permission to check.
|
||||
*/
|
||||
void enforcePermission(String permission) {
|
||||
final int status = PermissionChecker.checkCallingOrSelfPermissionForPreflight(mContext,
|
||||
permission);
|
||||
switch (status) {
|
||||
case PermissionChecker.PERMISSION_GRANTED:
|
||||
return;
|
||||
case PermissionChecker.PERMISSION_HARD_DENIED:
|
||||
throw new SecurityException(
|
||||
String.format("Caller must have the %s permission.", permission));
|
||||
case PermissionChecker.PERMISSION_SOFT_DENIED:
|
||||
throw new ServiceSpecificException(Status.TEMPORARY_PERMISSION_DENIED,
|
||||
String.format("Caller must have the %s permission.", permission));
|
||||
default:
|
||||
throw new InternalServerError(
|
||||
new RuntimeException("Unexpected perimission check result."));
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public IBinder asBinder() {
|
||||
throw new UnsupportedOperationException(
|
||||
"This implementation is not inteded to be used directly with Binder.");
|
||||
}
|
||||
|
||||
@Override public void dump(PrintWriter pw) {
|
||||
synchronized (this) {
|
||||
if (mModules != null) {
|
||||
for (int handle : mModules.keySet()) {
|
||||
pw.println("=========================================");
|
||||
pw.printf("Active sessions for module %d", handle);
|
||||
pw.println();
|
||||
pw.println("=========================================");
|
||||
for (ModuleService session : mModules.get(handle)) {
|
||||
session.dump(pw);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
pw.println();
|
||||
|
||||
if (mDelegate instanceof Dumpable) {
|
||||
((Dumpable) mDelegate).dump(pw);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/** State of a sound model. */
|
||||
static class ModelState {
|
||||
/** Activity state of a sound model. */
|
||||
enum Activity {
|
||||
/** Model is loaded, recognition is inactive. */
|
||||
LOADED,
|
||||
/** Model is loaded, recognition is active. */
|
||||
ACTIVE
|
||||
}
|
||||
|
||||
/** Activity state. */
|
||||
Activity activityState = Activity.LOADED;
|
||||
|
||||
/**
|
||||
* A map of known parameter support. A missing key means we don't know yet whether the
|
||||
* parameter is supported. A null value means it is known to not be supported. A non-null
|
||||
* value indicates the valid value range.
|
||||
*/
|
||||
private Map<Integer, ModelParameterRange> parameterSupport = new HashMap<>();
|
||||
|
||||
/**
|
||||
* Check that the given parameter is known to be supported for this model.
|
||||
*
|
||||
* @param modelParam The parameter key.
|
||||
*/
|
||||
void checkSupported(int modelParam) {
|
||||
if (!parameterSupport.containsKey(modelParam)) {
|
||||
throw new IllegalStateException("Parameter has not been checked for support.");
|
||||
}
|
||||
ModelParameterRange range = parameterSupport.get(modelParam);
|
||||
if (range == null) {
|
||||
throw new IllegalArgumentException("Paramater is not supported.");
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Check that the given parameter is known to be supported for this model and that the given
|
||||
* value is a valid value for it.
|
||||
*
|
||||
* @param modelParam The parameter key.
|
||||
* @param value The value.
|
||||
*/
|
||||
void checkSupported(int modelParam, int value) {
|
||||
if (!parameterSupport.containsKey(modelParam)) {
|
||||
throw new IllegalStateException("Parameter has not been checked for support.");
|
||||
}
|
||||
ModelParameterRange range = parameterSupport.get(modelParam);
|
||||
if (range == null) {
|
||||
throw new IllegalArgumentException("Paramater is not supported.");
|
||||
}
|
||||
Preconditions.checkArgumentInRange(value, range.minInclusive, range.maxInclusive,
|
||||
"value");
|
||||
}
|
||||
|
||||
/**
|
||||
* Update support state for the given parameter for this model.
|
||||
*
|
||||
* @param modelParam The parameter key.
|
||||
* @param range The parameter value range, or null if not supported.
|
||||
*/
|
||||
void updateParameterSupport(int modelParam, @Nullable ModelParameterRange range) {
|
||||
parameterSupport.put(modelParam, range);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* A wrapper around an {@link ISoundTriggerModule} implementation, to address the same aspects
|
||||
* mentioned in {@link SoundTriggerModule} above. This class follows the same conventions.
|
||||
*/
|
||||
private class ModuleService extends ISoundTriggerModule.Stub implements ISoundTriggerCallback,
|
||||
IBinder.DeathRecipient {
|
||||
private final ISoundTriggerCallback mCallback;
|
||||
private ISoundTriggerModule mDelegate;
|
||||
private @NonNull Map<Integer, ModelState> mLoadedModels = new HashMap<>();
|
||||
private final int mHandle;
|
||||
|
||||
ModuleService(int handle, @NonNull ISoundTriggerCallback callback) {
|
||||
mCallback = callback;
|
||||
mHandle = handle;
|
||||
try {
|
||||
mCallback.asBinder().linkToDeath(null, 0);
|
||||
} catch (RemoteException e) {
|
||||
throw e.rethrowAsRuntimeException();
|
||||
}
|
||||
}
|
||||
|
||||
void attach(@NonNull ISoundTriggerModule delegate) {
|
||||
mDelegate = delegate;
|
||||
mModules.get(mHandle).add(this);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int loadModel(@NonNull SoundModel model) {
|
||||
// Permission check.
|
||||
checkPermissions();
|
||||
// Input validation.
|
||||
ValidationUtil.validateGenericModel(model);
|
||||
|
||||
synchronized (SoundTriggerMiddlewareValidation.this) {
|
||||
// State validation.
|
||||
if (mDelegate == null) {
|
||||
throw new IllegalStateException("Module has been detached.");
|
||||
}
|
||||
|
||||
// From here on, every exception isn't client's fault.
|
||||
try {
|
||||
int handle = mDelegate.loadModel(model);
|
||||
mLoadedModels.put(handle, new ModelState());
|
||||
return handle;
|
||||
} catch (Exception e) {
|
||||
throw handleException(e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public int loadPhraseModel(@NonNull PhraseSoundModel model) {
|
||||
// Permission check.
|
||||
checkPermissions();
|
||||
// Input validation.
|
||||
ValidationUtil.validatePhraseModel(model);
|
||||
|
||||
synchronized (SoundTriggerMiddlewareValidation.this) {
|
||||
// State validation.
|
||||
if (mDelegate == null) {
|
||||
throw new IllegalStateException("Module has been detached.");
|
||||
}
|
||||
|
||||
// From here on, every exception isn't client's fault.
|
||||
try {
|
||||
int handle = mDelegate.loadPhraseModel(model);
|
||||
mLoadedModels.put(handle, new ModelState());
|
||||
return handle;
|
||||
} catch (Exception e) {
|
||||
throw handleException(e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void unloadModel(int modelHandle) {
|
||||
// Permission check.
|
||||
checkPermissions();
|
||||
// Input validation (always valid).
|
||||
|
||||
synchronized (SoundTriggerMiddlewareValidation.this) {
|
||||
// State validation.
|
||||
if (mDelegate == null) {
|
||||
throw new IllegalStateException("Module has been detached.");
|
||||
}
|
||||
ModelState modelState = mLoadedModels.get(
|
||||
modelHandle);
|
||||
if (modelState == null) {
|
||||
throw new IllegalStateException("Invalid handle: " + modelHandle);
|
||||
}
|
||||
if (modelState.activityState
|
||||
!= ModelState.Activity.LOADED) {
|
||||
throw new IllegalStateException("Model with handle: " + modelHandle
|
||||
+ " has invalid state for unloading: " + modelState.activityState);
|
||||
}
|
||||
|
||||
// From here on, every exception isn't client's fault.
|
||||
try {
|
||||
mDelegate.unloadModel(modelHandle);
|
||||
mLoadedModels.remove(modelHandle);
|
||||
} catch (Exception e) {
|
||||
throw handleException(e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void startRecognition(int modelHandle, @NonNull RecognitionConfig config) {
|
||||
// Permission check.
|
||||
checkPermissions();
|
||||
// Input validation.
|
||||
ValidationUtil.validateRecognitionConfig(config);
|
||||
|
||||
synchronized (SoundTriggerMiddlewareValidation.this) {
|
||||
// State validation.
|
||||
if (mDelegate == null) {
|
||||
throw new IllegalStateException("Module has been detached.");
|
||||
}
|
||||
ModelState modelState = mLoadedModels.get(
|
||||
modelHandle);
|
||||
if (modelState == null) {
|
||||
throw new IllegalStateException("Invalid handle: " + modelHandle);
|
||||
}
|
||||
if (modelState.activityState
|
||||
!= ModelState.Activity.LOADED) {
|
||||
throw new IllegalStateException("Model with handle: " + modelHandle
|
||||
+ " has invalid state for starting recognition: "
|
||||
+ modelState.activityState);
|
||||
}
|
||||
|
||||
// From here on, every exception isn't client's fault.
|
||||
try {
|
||||
mDelegate.startRecognition(modelHandle, config);
|
||||
modelState.activityState =
|
||||
ModelState.Activity.ACTIVE;
|
||||
} catch (Exception e) {
|
||||
throw handleException(e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void stopRecognition(int modelHandle) {
|
||||
// Permission check.
|
||||
checkPermissions();
|
||||
// Input validation (always valid).
|
||||
|
||||
synchronized (SoundTriggerMiddlewareValidation.this) {
|
||||
// State validation.
|
||||
if (mDelegate == null) {
|
||||
throw new IllegalStateException("Module has been detached.");
|
||||
}
|
||||
ModelState modelState = mLoadedModels.get(
|
||||
modelHandle);
|
||||
if (modelState == null) {
|
||||
throw new IllegalStateException("Invalid handle: " + modelHandle);
|
||||
}
|
||||
// stopRecognition is idempotent - no need to check model state.
|
||||
|
||||
// From here on, every exception isn't client's fault.
|
||||
try {
|
||||
mDelegate.stopRecognition(modelHandle);
|
||||
modelState.activityState =
|
||||
ModelState.Activity.LOADED;
|
||||
} catch (Exception e) {
|
||||
throw handleException(e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void forceRecognitionEvent(int modelHandle) {
|
||||
// Permission check.
|
||||
checkPermissions();
|
||||
// Input validation (always valid).
|
||||
|
||||
synchronized (SoundTriggerMiddlewareValidation.this) {
|
||||
// State validation.
|
||||
if (mDelegate == null) {
|
||||
throw new IllegalStateException("Module has been detached.");
|
||||
}
|
||||
ModelState modelState = mLoadedModels.get(
|
||||
modelHandle);
|
||||
if (modelState == null) {
|
||||
throw new IllegalStateException("Invalid handle: " + modelHandle);
|
||||
}
|
||||
// forceRecognitionEvent is idempotent - no need to check model state.
|
||||
|
||||
// From here on, every exception isn't client's fault.
|
||||
try {
|
||||
mDelegate.forceRecognitionEvent(modelHandle);
|
||||
} catch (Exception e) {
|
||||
throw handleException(e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setModelParameter(int modelHandle, int modelParam, int value) {
|
||||
// Permission check.
|
||||
checkPermissions();
|
||||
// Input validation.
|
||||
ValidationUtil.validateModelParameter(modelParam);
|
||||
|
||||
synchronized (SoundTriggerMiddlewareValidation.this) {
|
||||
// State validation.
|
||||
if (mDelegate == null) {
|
||||
throw new IllegalStateException("Module has been detached.");
|
||||
}
|
||||
ModelState modelState = mLoadedModels.get(
|
||||
modelHandle);
|
||||
if (modelState == null) {
|
||||
throw new IllegalStateException("Invalid handle: " + modelHandle);
|
||||
}
|
||||
modelState.checkSupported(modelParam, value);
|
||||
|
||||
// From here on, every exception isn't client's fault.
|
||||
try {
|
||||
mDelegate.setModelParameter(modelHandle, modelParam, value);
|
||||
} catch (Exception e) {
|
||||
throw handleException(e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getModelParameter(int modelHandle, int modelParam) {
|
||||
// Permission check.
|
||||
checkPermissions();
|
||||
// Input validation.
|
||||
ValidationUtil.validateModelParameter(modelParam);
|
||||
|
||||
synchronized (SoundTriggerMiddlewareValidation.this) {
|
||||
// State validation.
|
||||
if (mDelegate == null) {
|
||||
throw new IllegalStateException("Module has been detached.");
|
||||
}
|
||||
ModelState modelState = mLoadedModels.get(
|
||||
modelHandle);
|
||||
if (modelState == null) {
|
||||
throw new IllegalStateException("Invalid handle: " + modelHandle);
|
||||
}
|
||||
modelState.checkSupported(modelParam);
|
||||
|
||||
// From here on, every exception isn't client's fault.
|
||||
try {
|
||||
return mDelegate.getModelParameter(modelHandle, modelParam);
|
||||
} catch (Exception e) {
|
||||
throw handleException(e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
@Nullable
|
||||
public ModelParameterRange queryModelParameterSupport(int modelHandle, int modelParam) {
|
||||
// Permission check.
|
||||
checkPermissions();
|
||||
// Input validation.
|
||||
ValidationUtil.validateModelParameter(modelParam);
|
||||
|
||||
synchronized (SoundTriggerMiddlewareValidation.this) {
|
||||
// State validation.
|
||||
if (mDelegate == null) {
|
||||
throw new IllegalStateException("Module has been detached.");
|
||||
}
|
||||
ModelState modelState = mLoadedModels.get(
|
||||
modelHandle);
|
||||
if (modelState == null) {
|
||||
throw new IllegalStateException("Invalid handle: " + modelHandle);
|
||||
}
|
||||
|
||||
// From here on, every exception isn't client's fault.
|
||||
try {
|
||||
ModelParameterRange result = mDelegate.queryModelParameterSupport(modelHandle,
|
||||
modelParam);
|
||||
modelState.updateParameterSupport(modelParam, result);
|
||||
return result;
|
||||
} catch (Exception e) {
|
||||
throw handleException(e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void detach() {
|
||||
// Permission check.
|
||||
checkPermissions();
|
||||
// Input validation (always valid).
|
||||
|
||||
synchronized (SoundTriggerMiddlewareValidation.this) {
|
||||
// State validation.
|
||||
if (mDelegate == null) {
|
||||
throw new IllegalStateException("Module has already been detached.");
|
||||
}
|
||||
if (!mLoadedModels.isEmpty()) {
|
||||
throw new IllegalStateException("Cannot detach while models are loaded.");
|
||||
}
|
||||
|
||||
// From here on, every exception isn't client's fault.
|
||||
try {
|
||||
detachInternal();
|
||||
} catch (Exception e) {
|
||||
throw handleException(e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Override toString() in order to have the delegate's ID in it.
|
||||
@Override
|
||||
public String toString() {
|
||||
return mDelegate.toString();
|
||||
}
|
||||
|
||||
private void detachInternal() {
|
||||
try {
|
||||
mDelegate.detach();
|
||||
mDelegate = null;
|
||||
mCallback.asBinder().unlinkToDeath(null, 0);
|
||||
mModules.get(mHandle).remove(this);
|
||||
} catch (RemoteException e) {
|
||||
throw e.rethrowAsRuntimeException();
|
||||
}
|
||||
}
|
||||
|
||||
void dump(PrintWriter pw) {
|
||||
pw.printf("Loaded models for session %s (handle, active)", toString());
|
||||
pw.println();
|
||||
pw.println("-------------------------------");
|
||||
for (Map.Entry<Integer, ModelState> entry : mLoadedModels.entrySet()) {
|
||||
pw.print(entry.getKey());
|
||||
pw.print('\t');
|
||||
pw.print(entry.getValue().activityState.name());
|
||||
pw.println();
|
||||
}
|
||||
}
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////////////////
|
||||
// Callbacks
|
||||
|
||||
@Override
|
||||
public void onRecognition(int modelHandle, @NonNull RecognitionEvent event) {
|
||||
synchronized (SoundTriggerMiddlewareValidation.this) {
|
||||
if (event.status != RecognitionStatus.FORCED) {
|
||||
mLoadedModels.get(modelHandle).activityState =
|
||||
ModelState.Activity.LOADED;
|
||||
}
|
||||
try {
|
||||
mCallback.onRecognition(modelHandle, event);
|
||||
} catch (RemoteException e) {
|
||||
// Dead client will be handled by binderDied() - no need to handle here.
|
||||
// In any case, client callbacks are considered best effort.
|
||||
Log.e(TAG, "Client callback exception.", e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onPhraseRecognition(int modelHandle, @NonNull PhraseRecognitionEvent event) {
|
||||
synchronized (SoundTriggerMiddlewareValidation.this) {
|
||||
if (event.common.status != RecognitionStatus.FORCED) {
|
||||
mLoadedModels.get(modelHandle).activityState =
|
||||
ModelState.Activity.LOADED;
|
||||
}
|
||||
try {
|
||||
mCallback.onPhraseRecognition(modelHandle, event);
|
||||
} catch (RemoteException e) {
|
||||
// Dead client will be handled by binderDied() - no need to handle here.
|
||||
// In any case, client callbacks are considered best effort.
|
||||
Log.e(TAG, "Client callback exception.", e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onRecognitionAvailabilityChange(boolean available) {
|
||||
synchronized (SoundTriggerMiddlewareValidation.this) {
|
||||
try {
|
||||
mCallback.onRecognitionAvailabilityChange(available);
|
||||
} catch (RemoteException e) {
|
||||
// Dead client will be handled by binderDied() - no need to handle here.
|
||||
// In any case, client callbacks are considered best effort.
|
||||
Log.e(TAG, "Client callback exception.", e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onModuleDied() {
|
||||
synchronized (SoundTriggerMiddlewareValidation.this) {
|
||||
try {
|
||||
mCallback.onModuleDied();
|
||||
} catch (RemoteException e) {
|
||||
// Dead client will be handled by binderDied() - no need to handle here.
|
||||
// In any case, client callbacks are considered best effort.
|
||||
Log.e(TAG, "Client callback exception.", e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void binderDied() {
|
||||
// This is called whenever our client process dies.
|
||||
synchronized (SoundTriggerMiddlewareValidation.this) {
|
||||
try {
|
||||
// Gracefully stop all active recognitions and unload the models.
|
||||
for (Map.Entry<Integer, ModelState> entry :
|
||||
mLoadedModels.entrySet()) {
|
||||
if (entry.getValue().activityState
|
||||
== ModelState.Activity.ACTIVE) {
|
||||
mDelegate.stopRecognition(entry.getKey());
|
||||
}
|
||||
mDelegate.unloadModel(entry.getKey());
|
||||
}
|
||||
// Detach.
|
||||
detachInternal();
|
||||
} catch (Exception e) {
|
||||
throw handleException(e);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -123,7 +123,6 @@ class SoundTriggerModule implements IHwBinder.DeathRecipient {
|
||||
*/
|
||||
synchronized @NonNull
|
||||
ISoundTriggerModule attach(@NonNull ISoundTriggerCallback callback) {
|
||||
Log.d(TAG, "attach()");
|
||||
Session session = new Session(callback);
|
||||
mActiveSessions.add(session);
|
||||
return session;
|
||||
@@ -149,8 +148,6 @@ class SoundTriggerModule implements IHwBinder.DeathRecipient {
|
||||
* @param active true iff external capture is active.
|
||||
*/
|
||||
synchronized void setExternalCaptureState(boolean active) {
|
||||
Log.d(TAG, String.format("setExternalCaptureState(active=%b)", active));
|
||||
|
||||
if (mProperties.concurrentCapture) {
|
||||
// If we support concurrent capture, we don't care about any of this.
|
||||
return;
|
||||
@@ -235,7 +232,6 @@ class SoundTriggerModule implements IHwBinder.DeathRecipient {
|
||||
|
||||
@Override
|
||||
public void detach() {
|
||||
Log.d(TAG, "detach()");
|
||||
synchronized (SoundTriggerModule.this) {
|
||||
if (mCallback == null) {
|
||||
return;
|
||||
@@ -247,8 +243,6 @@ class SoundTriggerModule implements IHwBinder.DeathRecipient {
|
||||
|
||||
@Override
|
||||
public int loadModel(@NonNull SoundModel model) {
|
||||
Log.d(TAG, String.format("loadModel(model=%s)", model));
|
||||
|
||||
// We must do this outside the lock, to avoid possible deadlocks with the remote process
|
||||
// that provides the audio sessions, which may also be calling into us.
|
||||
SoundTriggerMiddlewareImpl.AudioSessionProvider.AudioSession audioSession =
|
||||
@@ -276,8 +270,6 @@ class SoundTriggerModule implements IHwBinder.DeathRecipient {
|
||||
|
||||
@Override
|
||||
public int loadPhraseModel(@NonNull PhraseSoundModel model) {
|
||||
Log.d(TAG, String.format("loadPhraseModel(model=%s)", model));
|
||||
|
||||
// We must do this outside the lock, to avoid possible deadlocks with the remote process
|
||||
// that provides the audio sessions, which may also be calling into us.
|
||||
SoundTriggerMiddlewareImpl.AudioSessionProvider.AudioSession audioSession =
|
||||
@@ -306,10 +298,7 @@ class SoundTriggerModule implements IHwBinder.DeathRecipient {
|
||||
|
||||
@Override
|
||||
public void unloadModel(int modelHandle) {
|
||||
Log.d(TAG, String.format("unloadModel(handle=%d)", modelHandle));
|
||||
|
||||
int sessionId;
|
||||
|
||||
synchronized (SoundTriggerModule.this) {
|
||||
checkValid();
|
||||
sessionId = mLoadedModels.get(modelHandle).unload();
|
||||
@@ -323,8 +312,6 @@ class SoundTriggerModule implements IHwBinder.DeathRecipient {
|
||||
|
||||
@Override
|
||||
public void startRecognition(int modelHandle, @NonNull RecognitionConfig config) {
|
||||
Log.d(TAG,
|
||||
String.format("startRecognition(handle=%d, config=%s)", modelHandle, config));
|
||||
synchronized (SoundTriggerModule.this) {
|
||||
checkValid();
|
||||
mLoadedModels.get(modelHandle).startRecognition(config);
|
||||
@@ -333,7 +320,6 @@ class SoundTriggerModule implements IHwBinder.DeathRecipient {
|
||||
|
||||
@Override
|
||||
public void stopRecognition(int modelHandle) {
|
||||
Log.d(TAG, String.format("stopRecognition(handle=%d)", modelHandle));
|
||||
synchronized (SoundTriggerModule.this) {
|
||||
mLoadedModels.get(modelHandle).stopRecognition();
|
||||
}
|
||||
@@ -341,7 +327,6 @@ class SoundTriggerModule implements IHwBinder.DeathRecipient {
|
||||
|
||||
@Override
|
||||
public void forceRecognitionEvent(int modelHandle) {
|
||||
Log.d(TAG, String.format("forceRecognitionEvent(handle=%d)", modelHandle));
|
||||
synchronized (SoundTriggerModule.this) {
|
||||
checkValid();
|
||||
mLoadedModels.get(modelHandle).forceRecognitionEvent();
|
||||
@@ -350,9 +335,6 @@ class SoundTriggerModule implements IHwBinder.DeathRecipient {
|
||||
|
||||
@Override
|
||||
public void setModelParameter(int modelHandle, int modelParam, int value) {
|
||||
Log.d(TAG,
|
||||
String.format("setModelParameter(handle=%d, param=%d, value=%d)", modelHandle,
|
||||
modelParam, value));
|
||||
synchronized (SoundTriggerModule.this) {
|
||||
checkValid();
|
||||
mLoadedModels.get(modelHandle).setParameter(modelParam, value);
|
||||
@@ -361,8 +343,6 @@ class SoundTriggerModule implements IHwBinder.DeathRecipient {
|
||||
|
||||
@Override
|
||||
public int getModelParameter(int modelHandle, int modelParam) {
|
||||
Log.d(TAG, String.format("getModelParameter(handle=%d, param=%d)", modelHandle,
|
||||
modelParam));
|
||||
synchronized (SoundTriggerModule.this) {
|
||||
checkValid();
|
||||
return mLoadedModels.get(modelHandle).getParameter(modelParam);
|
||||
@@ -372,8 +352,6 @@ class SoundTriggerModule implements IHwBinder.DeathRecipient {
|
||||
@Override
|
||||
@Nullable
|
||||
public ModelParameterRange queryModelParameterSupport(int modelHandle, int modelParam) {
|
||||
Log.d(TAG, String.format("queryModelParameterSupport(handle=%d, param=%d)", modelHandle,
|
||||
modelParam));
|
||||
synchronized (SoundTriggerModule.this) {
|
||||
checkValid();
|
||||
return mLoadedModels.get(modelHandle).queryModelParameterSupport(modelParam);
|
||||
@@ -584,8 +562,6 @@ class SoundTriggerModule implements IHwBinder.DeathRecipient {
|
||||
public void recognitionCallback(
|
||||
@NonNull ISoundTriggerHwCallback.RecognitionEvent recognitionEvent,
|
||||
int cookie) {
|
||||
Log.d(TAG, String.format("recognitionCallback_2_1(event=%s, cookie=%d)",
|
||||
recognitionEvent, cookie));
|
||||
synchronized (SoundTriggerModule.this) {
|
||||
android.media.soundtrigger_middleware.RecognitionEvent aidlEvent =
|
||||
ConversionUtil.hidl2aidlRecognitionEvent(recognitionEvent);
|
||||
@@ -608,8 +584,6 @@ class SoundTriggerModule implements IHwBinder.DeathRecipient {
|
||||
public void phraseRecognitionCallback(
|
||||
@NonNull ISoundTriggerHwCallback.PhraseRecognitionEvent phraseRecognitionEvent,
|
||||
int cookie) {
|
||||
Log.d(TAG, String.format("phraseRecognitionCallback_2_1(event=%s, cookie=%d)",
|
||||
phraseRecognitionEvent, cookie));
|
||||
synchronized (SoundTriggerModule.this) {
|
||||
android.media.soundtrigger_middleware.PhraseRecognitionEvent aidlEvent =
|
||||
ConversionUtil.hidl2aidlPhraseRecognitionEvent(phraseRecognitionEvent);
|
||||
|
||||
Reference in New Issue
Block a user