| /* |
| * 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 android.annotation.NonNull; |
| import android.annotation.Nullable; |
| import android.hardware.soundtrigger.V2_0.ISoundTriggerHw; |
| import android.media.soundtrigger.ModelParameterRange; |
| import android.media.soundtrigger.PhraseSoundModel; |
| import android.media.soundtrigger.Properties; |
| import android.media.soundtrigger.RecognitionConfig; |
| import android.media.soundtrigger.SoundModel; |
| import android.media.soundtrigger.Status; |
| import android.media.soundtrigger_middleware.PhraseRecognitionEventSys; |
| import android.media.soundtrigger_middleware.RecognitionEventSys; |
| import android.os.IBinder; |
| import android.os.IHwBinder; |
| import android.os.RemoteException; |
| import android.os.SystemClock; |
| import android.system.OsConstants; |
| import android.util.Log; |
| |
| import java.io.IOException; |
| import java.util.HashMap; |
| import java.util.Map; |
| import java.util.Objects; |
| import java.util.concurrent.ConcurrentHashMap; |
| import java.util.concurrent.ConcurrentMap; |
| import java.util.concurrent.atomic.AtomicInteger; |
| import java.util.concurrent.atomic.AtomicReference; |
| |
| /** |
| * An implementation of {@link ISoundTriggerHal}, on top of any |
| * android.hardware.soundtrigger.V2_x.ISoundTriggerHw implementation. This class hides away some of |
| * the details involved with retaining backward compatibility and adapts to the more pleasant syntax |
| * exposed by {@link ISoundTriggerHal}, compared to the bare driver interface. |
| * <p> |
| * Exception handling: |
| * <ul> |
| * <li>All {@link RemoteException}s get rethrown as {@link RuntimeException}. |
| * <li>All HAL malfunctions get thrown as {@link HalException}. |
| * <li>All unsupported operations get thrown as {@link RecoverableException} with a |
| * {@link android.media.soundtrigger.Status#OPERATION_NOT_SUPPORTED} |
| * code. |
| * </ul> |
| */ |
| final class SoundTriggerHw2Compat implements ISoundTriggerHal { |
| private static final String TAG = "SoundTriggerHw2Compat"; |
| |
| private final @NonNull Runnable mRebootRunnable; |
| private final @NonNull IHwBinder mBinder; |
| private @NonNull android.hardware.soundtrigger.V2_0.ISoundTriggerHw mUnderlying_2_0; |
| private @Nullable android.hardware.soundtrigger.V2_1.ISoundTriggerHw mUnderlying_2_1; |
| private @Nullable android.hardware.soundtrigger.V2_2.ISoundTriggerHw mUnderlying_2_2; |
| private @Nullable android.hardware.soundtrigger.V2_3.ISoundTriggerHw mUnderlying_2_3; |
| |
| // HAL <=2.1 requires us to pass a callback argument to startRecognition. We will store the one |
| // passed on load and then pass it on start. We don't bother storing the callback on newer |
| // versions. |
| private final @NonNull ConcurrentMap<Integer, ModelCallback> mModelCallbacks = |
| new ConcurrentHashMap<>(); |
| |
| // A map from IBinder.DeathRecipient to IHwBinder.DeathRecipient for doing the mapping upon |
| // unlinking. |
| private final @NonNull Map<IBinder.DeathRecipient, IHwBinder.DeathRecipient> |
| mDeathRecipientMap = new HashMap<>(); |
| |
| // The properties are read at construction time and cached, since we need to use some of them |
| // to enforce constraints. |
| private final @NonNull Properties mProperties; |
| |
| static ISoundTriggerHal create( |
| @NonNull ISoundTriggerHw underlying, |
| @NonNull Runnable rebootRunnable, |
| ICaptureStateNotifier notifier) { |
| return create(underlying.asBinder(), rebootRunnable, notifier); |
| } |
| |
| static ISoundTriggerHal create(@NonNull IHwBinder binder, |
| @NonNull Runnable rebootRunnable, |
| ICaptureStateNotifier notifier) { |
| SoundTriggerHw2Compat compat = new SoundTriggerHw2Compat(binder, rebootRunnable); |
| ISoundTriggerHal result = compat; |
| // Add max model limiter for versions. |
| result = new SoundTriggerHalMaxModelLimiter(result, compat.mProperties.maxSoundModels); |
| // Add concurrent capture handler for HALs which do not support concurrent capture. |
| if (!compat.mProperties.concurrentCapture) { |
| result = new SoundTriggerHalConcurrentCaptureHandler(result, notifier); |
| } |
| return result; |
| } |
| |
| private SoundTriggerHw2Compat(@NonNull IHwBinder binder, @NonNull Runnable rebootRunnable) { |
| mRebootRunnable = Objects.requireNonNull(rebootRunnable); |
| mBinder = Objects.requireNonNull(binder); |
| initUnderlying(binder); |
| mProperties = Objects.requireNonNull(getPropertiesInternal()); |
| } |
| |
| private void initUnderlying(IHwBinder binder) { |
| // We want to share the proxy instances rather than create a separate proxy for every |
| // version, so we go down the versions in descending order to find the latest one supported, |
| // and then simply up-cast it to obtain all the versions that are earlier. |
| |
| // Attempt 2.3 |
| android.hardware.soundtrigger.V2_3.ISoundTriggerHw as2_3 = |
| android.hardware.soundtrigger.V2_3.ISoundTriggerHw.asInterface(binder); |
| if (as2_3 != null) { |
| mUnderlying_2_0 = mUnderlying_2_1 = mUnderlying_2_2 = mUnderlying_2_3 = as2_3; |
| return; |
| } |
| |
| // Attempt 2.2 |
| android.hardware.soundtrigger.V2_2.ISoundTriggerHw as2_2 = |
| android.hardware.soundtrigger.V2_2.ISoundTriggerHw.asInterface(binder); |
| if (as2_2 != null) { |
| mUnderlying_2_0 = mUnderlying_2_1 = mUnderlying_2_2 = as2_2; |
| mUnderlying_2_3 = null; |
| return; |
| } |
| |
| // Attempt 2.1 |
| android.hardware.soundtrigger.V2_1.ISoundTriggerHw as2_1 = |
| android.hardware.soundtrigger.V2_1.ISoundTriggerHw.asInterface(binder); |
| if (as2_1 != null) { |
| mUnderlying_2_0 = mUnderlying_2_1 = as2_1; |
| mUnderlying_2_2 = mUnderlying_2_3 = null; |
| return; |
| } |
| |
| // Attempt 2.0 |
| android.hardware.soundtrigger.V2_0.ISoundTriggerHw as2_0 = |
| android.hardware.soundtrigger.V2_0.ISoundTriggerHw.asInterface(binder); |
| if (as2_0 != null) { |
| mUnderlying_2_0 = as2_0; |
| mUnderlying_2_1 = mUnderlying_2_2 = mUnderlying_2_3 = null; |
| return; |
| } |
| |
| throw new RuntimeException("Binder doesn't support ISoundTriggerHw@2.0"); |
| } |
| |
| private static void handleHalStatus(int status, String methodName) { |
| if (status != 0) { |
| throw new HalException(status, methodName); |
| } |
| } |
| |
| private static void handleHalStatusAllowBusy(int status, String methodName) { |
| if (status == -OsConstants.EBUSY) { |
| throw new RecoverableException(Status.RESOURCE_CONTENTION); |
| } |
| handleHalStatus(status, methodName); |
| } |
| |
| @Override |
| public void reboot() { |
| mRebootRunnable.run(); |
| } |
| |
| @Override |
| public void detach() { |
| // No-op. |
| } |
| |
| @Override |
| public Properties getProperties() { |
| return mProperties; |
| } |
| |
| private Properties getPropertiesInternal() { |
| try { |
| AtomicInteger retval = new AtomicInteger(-1); |
| AtomicReference<android.hardware.soundtrigger.V2_3.Properties> properties = |
| new AtomicReference<>(); |
| try { |
| as2_3().getProperties_2_3( |
| (r, p) -> { |
| retval.set(r); |
| properties.set(p); |
| }); |
| } catch (NotSupported e) { |
| // Fall-back to the 2.0 version: |
| return getProperties_2_0(); |
| } |
| handleHalStatus(retval.get(), "getProperties_2_3"); |
| return ConversionUtil.hidl2aidlProperties(properties.get()); |
| } catch (RemoteException e) { |
| throw e.rethrowAsRuntimeException(); |
| } |
| } |
| |
| @Override |
| public void registerCallback(GlobalCallback callback) { |
| // In versions 2.x the events represented by this callback don't exist, we can |
| // safely ignore this. |
| } |
| |
| @Override |
| public int loadSoundModel(SoundModel soundModel, ModelCallback callback) { |
| android.hardware.soundtrigger.V2_3.ISoundTriggerHw.SoundModel hidlModel = |
| ConversionUtil.aidl2hidlSoundModel(soundModel); |
| try { |
| AtomicInteger retval = new AtomicInteger(-1); |
| AtomicInteger handle = new AtomicInteger(0); |
| try { |
| as2_1().loadSoundModel_2_1(hidlModel, new ModelCallbackWrapper(callback), |
| 0, |
| (r, h) -> { |
| retval.set(r); |
| handle.set(h); |
| }); |
| handleHalStatus(retval.get(), "loadSoundModel_2_1"); |
| mModelCallbacks.put(handle.get(), callback); |
| } catch (NotSupported ee) { |
| // Fall-back to the 2.0 version: |
| return loadSoundModel_2_0(hidlModel, callback); |
| } |
| return handle.get(); |
| } catch (RemoteException e) { |
| throw e.rethrowAsRuntimeException(); |
| } finally { |
| // TODO(b/219825762): We should be able to use the entire object in a try-with-resources |
| // clause, instead of having to explicitly close internal fields. |
| if (hidlModel.data != null) { |
| try { |
| hidlModel.data.close(); |
| } catch (IOException e) { |
| Log.e(TAG, "Failed to close file", e); |
| } |
| } |
| } |
| } |
| |
| @Override |
| public int loadPhraseSoundModel(PhraseSoundModel soundModel, ModelCallback callback) { |
| android.hardware.soundtrigger.V2_1.ISoundTriggerHw.PhraseSoundModel hidlModel = |
| ConversionUtil.aidl2hidlPhraseSoundModel(soundModel); |
| try { |
| AtomicInteger retval = new AtomicInteger(-1); |
| AtomicInteger handle = new AtomicInteger(0); |
| try { |
| as2_1().loadPhraseSoundModel_2_1(hidlModel, new ModelCallbackWrapper(callback), |
| 0, |
| (r, h) -> { |
| retval.set(r); |
| handle.set(h); |
| }); |
| handleHalStatus(retval.get(), "loadPhraseSoundModel_2_1"); |
| mModelCallbacks.put(handle.get(), callback); |
| } catch (NotSupported ee) { |
| // Fall-back to the 2.0 version: |
| return loadPhraseSoundModel_2_0(hidlModel, callback); |
| } |
| return handle.get(); |
| } catch (RemoteException e) { |
| throw e.rethrowAsRuntimeException(); |
| } finally { |
| // TODO(b/219825762): We should be able to use the entire object in a try-with-resources |
| // clause, instead of having to explicitly close internal fields. |
| if (hidlModel.common.data != null) { |
| try { |
| hidlModel.common.data.close(); |
| } catch (IOException e) { |
| Log.e(TAG, "Failed to close file", e); |
| } |
| } |
| } |
| } |
| |
| @Override |
| public void unloadSoundModel(int modelHandle) { |
| try { |
| // Safe if key doesn't exist. |
| mModelCallbacks.remove(modelHandle); |
| int retval = as2_0().unloadSoundModel(modelHandle); |
| handleHalStatus(retval, "unloadSoundModel"); |
| } catch (RemoteException e) { |
| throw e.rethrowAsRuntimeException(); |
| } |
| } |
| |
| @Override |
| public void stopRecognition(int modelHandle) { |
| try { |
| int retval = as2_0().stopRecognition(modelHandle); |
| handleHalStatus(retval, "stopRecognition"); |
| } catch (RemoteException e) { |
| throw e.rethrowAsRuntimeException(); |
| } |
| |
| } |
| |
| @Override |
| public void startRecognition(int modelHandle, int deviceHandle, int ioHandle, |
| RecognitionConfig config) { |
| android.hardware.soundtrigger.V2_3.RecognitionConfig hidlConfig = |
| ConversionUtil.aidl2hidlRecognitionConfig(config, deviceHandle, ioHandle); |
| try { |
| try { |
| int retval = as2_3().startRecognition_2_3(modelHandle, hidlConfig); |
| handleHalStatus(retval, "startRecognition_2_3"); |
| } catch (NotSupported ee) { |
| // Fall-back to the 2.0 version: |
| startRecognition_2_1(modelHandle, hidlConfig); |
| } |
| } catch (RemoteException e) { |
| throw e.rethrowAsRuntimeException(); |
| } |
| } |
| |
| @Override |
| public void forceRecognitionEvent(int modelHandle) { |
| try { |
| int retval = as2_2().getModelState(modelHandle); |
| handleHalStatus(retval, "getModelState"); |
| } catch (RemoteException e) { |
| throw e.rethrowAsRuntimeException(); |
| } catch (NotSupported e) { |
| throw e.throwAsRecoverableException(); |
| } |
| } |
| |
| @Override |
| public int getModelParameter(int modelHandle, int param) { |
| AtomicInteger status = new AtomicInteger(-1); |
| AtomicInteger value = new AtomicInteger(0); |
| try { |
| as2_3().getParameter(modelHandle, param, |
| (s, v) -> { |
| status.set(s); |
| value.set(v); |
| }); |
| } catch (RemoteException e) { |
| throw e.rethrowAsRuntimeException(); |
| } catch (NotSupported e) { |
| throw e.throwAsRecoverableException(); |
| } |
| handleHalStatus(status.get(), "getParameter"); |
| return value.get(); |
| } |
| |
| @Override |
| public void setModelParameter(int modelHandle, int param, int value) { |
| try { |
| int retval = as2_3().setParameter(modelHandle, param, value); |
| handleHalStatus(retval, "setParameter"); |
| } catch (RemoteException e) { |
| throw e.rethrowAsRuntimeException(); |
| } catch (NotSupported e) { |
| throw e.throwAsRecoverableException(); |
| } |
| } |
| |
| @Override |
| public ModelParameterRange queryParameter(int modelHandle, int param) { |
| AtomicInteger status = new AtomicInteger(-1); |
| AtomicReference<android.hardware.soundtrigger.V2_3.OptionalModelParameterRange> |
| optionalRange = |
| new AtomicReference<>(); |
| try { |
| as2_3().queryParameter(modelHandle, param, |
| (s, r) -> { |
| status.set(s); |
| optionalRange.set(r); |
| }); |
| } catch (NotSupported e) { |
| // For older drivers, we consider no model parameter to be supported. |
| return null; |
| } catch (RemoteException e) { |
| throw e.rethrowAsRuntimeException(); |
| } |
| handleHalStatus(status.get(), "queryParameter"); |
| return (optionalRange.get().getDiscriminator() |
| == android.hardware.soundtrigger.V2_3.OptionalModelParameterRange.hidl_discriminator.range) |
| ? |
| ConversionUtil.hidl2aidlModelParameterRange(optionalRange.get().range()) : null; |
| } |
| |
| @Override |
| public void linkToDeath(IBinder.DeathRecipient recipient) { |
| IHwBinder.DeathRecipient wrapper = cookie -> recipient.binderDied(); |
| mDeathRecipientMap.put(recipient, wrapper); |
| mBinder.linkToDeath(wrapper, 0); |
| } |
| |
| @Override |
| public void unlinkToDeath(IBinder.DeathRecipient recipient) { |
| mBinder.unlinkToDeath(mDeathRecipientMap.remove(recipient)); |
| } |
| |
| @Override |
| public String interfaceDescriptor() { |
| try { |
| return as2_0().interfaceDescriptor(); |
| } catch (RemoteException e) { |
| throw e.rethrowAsRuntimeException(); |
| } |
| } |
| |
| @Override |
| public void flushCallbacks() { |
| // This is a no-op. Only implemented for decorators. |
| } |
| |
| @Override |
| public void clientAttached(IBinder binder) { |
| // This is a no-op. Only implemented for decorators. |
| } |
| |
| @Override |
| public void clientDetached(IBinder binder) { |
| // This is a no-op. Only implemented for decorators. |
| } |
| |
| private Properties getProperties_2_0() |
| throws RemoteException { |
| AtomicInteger retval = new AtomicInteger(-1); |
| AtomicReference<android.hardware.soundtrigger.V2_0.ISoundTriggerHw.Properties> |
| properties = |
| new AtomicReference<>(); |
| as2_0().getProperties( |
| (r, p) -> { |
| retval.set(r); |
| properties.set(p); |
| }); |
| handleHalStatus(retval.get(), "getProperties"); |
| return ConversionUtil.hidl2aidlProperties( |
| Hw2CompatUtil.convertProperties_2_0_to_2_3(properties.get())); |
| } |
| |
| private int loadSoundModel_2_0( |
| android.hardware.soundtrigger.V2_1.ISoundTriggerHw.SoundModel soundModel, |
| ModelCallback callback) |
| throws RemoteException { |
| // Convert the soundModel to V2.0. |
| android.hardware.soundtrigger.V2_0.ISoundTriggerHw.SoundModel model_2_0 = |
| Hw2CompatUtil.convertSoundModel_2_1_to_2_0(soundModel); |
| |
| AtomicInteger retval = new AtomicInteger(-1); |
| AtomicInteger handle = new AtomicInteger(0); |
| as2_0().loadSoundModel(model_2_0, new ModelCallbackWrapper(callback), 0, (r, h) -> { |
| retval.set(r); |
| handle.set(h); |
| }); |
| handleHalStatus(retval.get(), "loadSoundModel"); |
| mModelCallbacks.put(handle.get(), callback); |
| return handle.get(); |
| } |
| |
| private int loadPhraseSoundModel_2_0( |
| android.hardware.soundtrigger.V2_1.ISoundTriggerHw.PhraseSoundModel soundModel, |
| ModelCallback callback) |
| throws RemoteException { |
| // Convert the soundModel to V2.0. |
| android.hardware.soundtrigger.V2_0.ISoundTriggerHw.PhraseSoundModel model_2_0 = |
| Hw2CompatUtil.convertPhraseSoundModel_2_1_to_2_0(soundModel); |
| |
| AtomicInteger retval = new AtomicInteger(-1); |
| AtomicInteger handle = new AtomicInteger(0); |
| as2_0().loadPhraseSoundModel(model_2_0, new ModelCallbackWrapper(callback), 0, |
| (r, h) -> { |
| retval.set(r); |
| handle.set(h); |
| }); |
| handleHalStatus(retval.get(), "loadSoundModel"); |
| mModelCallbacks.put(handle.get(), callback); |
| return handle.get(); |
| } |
| |
| private void startRecognition_2_1(int modelHandle, |
| android.hardware.soundtrigger.V2_3.RecognitionConfig config) { |
| try { |
| try { |
| android.hardware.soundtrigger.V2_1.ISoundTriggerHw.RecognitionConfig config_2_1 = |
| Hw2CompatUtil.convertRecognitionConfig_2_3_to_2_1(config); |
| int retval = as2_1().startRecognition_2_1(modelHandle, config_2_1, |
| new ModelCallbackWrapper(mModelCallbacks.get(modelHandle)), 0); |
| handleHalStatus(retval, "startRecognition_2_1"); |
| } catch (NotSupported e) { |
| // Fall-back to the 2.0 version: |
| startRecognition_2_0(modelHandle, config); |
| } |
| } catch (RemoteException e) { |
| throw e.rethrowAsRuntimeException(); |
| } |
| } |
| |
| private void startRecognition_2_0(int modelHandle, |
| android.hardware.soundtrigger.V2_3.RecognitionConfig config) |
| throws RemoteException { |
| android.hardware.soundtrigger.V2_0.ISoundTriggerHw.RecognitionConfig config_2_0 = |
| Hw2CompatUtil.convertRecognitionConfig_2_3_to_2_0(config); |
| int retval = as2_0().startRecognition(modelHandle, config_2_0, |
| new ModelCallbackWrapper(mModelCallbacks.get(modelHandle)), 0); |
| handleHalStatus(retval, "startRecognition"); |
| } |
| |
| private @NonNull |
| android.hardware.soundtrigger.V2_0.ISoundTriggerHw as2_0() { |
| return mUnderlying_2_0; |
| } |
| |
| private @NonNull |
| android.hardware.soundtrigger.V2_1.ISoundTriggerHw as2_1() throws NotSupported { |
| if (mUnderlying_2_1 == null) { |
| throw new NotSupported("Underlying driver version < 2.1"); |
| } |
| return mUnderlying_2_1; |
| } |
| |
| private @NonNull |
| android.hardware.soundtrigger.V2_2.ISoundTriggerHw as2_2() throws NotSupported { |
| if (mUnderlying_2_2 == null) { |
| throw new NotSupported("Underlying driver version < 2.2"); |
| } |
| return mUnderlying_2_2; |
| } |
| |
| private @NonNull |
| android.hardware.soundtrigger.V2_3.ISoundTriggerHw as2_3() throws NotSupported { |
| if (mUnderlying_2_3 == null) { |
| throw new NotSupported("Underlying driver version < 2.3"); |
| } |
| return mUnderlying_2_3; |
| } |
| |
| /** |
| * A checked exception representing the requested interface version not being supported. |
| * At the public interface layer, use {@link #throwAsRecoverableException()} to propagate it to |
| * the caller if the request cannot be fulfilled. |
| */ |
| private static class NotSupported extends Exception { |
| NotSupported(String message) { |
| super(message); |
| } |
| |
| /** |
| * Throw this as a recoverable exception. |
| * |
| * @return Never actually returns anything. Always throws. Used so that caller can write |
| * throw e.throwAsRecoverableException(). |
| */ |
| RecoverableException throwAsRecoverableException() { |
| throw new RecoverableException(Status.OPERATION_NOT_SUPPORTED, getMessage()); |
| } |
| } |
| |
| private static class ModelCallbackWrapper extends |
| android.hardware.soundtrigger.V2_1.ISoundTriggerHwCallback.Stub { |
| private final @NonNull ModelCallback mDelegate; |
| |
| private ModelCallbackWrapper( |
| @NonNull ModelCallback delegate) { |
| mDelegate = Objects.requireNonNull(delegate); |
| } |
| |
| @Override |
| public void recognitionCallback_2_1( |
| android.hardware.soundtrigger.V2_1.ISoundTriggerHwCallback.RecognitionEvent event, |
| int cookie) { |
| RecognitionEventSys eventSys = new RecognitionEventSys(); |
| eventSys.recognitionEvent = ConversionUtil.hidl2aidlRecognitionEvent(event); |
| eventSys.halEventReceivedMillis = SystemClock.elapsedRealtime(); |
| mDelegate.recognitionCallback(event.header.model, eventSys); |
| } |
| |
| @Override |
| public void phraseRecognitionCallback_2_1( |
| android.hardware.soundtrigger.V2_1.ISoundTriggerHwCallback.PhraseRecognitionEvent event, |
| int cookie) { |
| PhraseRecognitionEventSys eventSys = new PhraseRecognitionEventSys(); |
| eventSys.phraseRecognitionEvent = ConversionUtil.hidl2aidlPhraseRecognitionEvent(event); |
| eventSys.halEventReceivedMillis = SystemClock.elapsedRealtime(); |
| mDelegate.phraseRecognitionCallback(event.common.header.model, eventSys); |
| } |
| |
| @Override |
| public void soundModelCallback_2_1( |
| android.hardware.soundtrigger.V2_1.ISoundTriggerHwCallback.ModelEvent event, |
| int cookie) { |
| // Nobody cares. |
| } |
| |
| @Override |
| public void recognitionCallback( |
| android.hardware.soundtrigger.V2_0.ISoundTriggerHwCallback.RecognitionEvent event, |
| int cookie) { |
| android.hardware.soundtrigger.V2_1.ISoundTriggerHwCallback.RecognitionEvent event_2_1 = |
| Hw2CompatUtil.convertRecognitionEvent_2_0_to_2_1(event); |
| recognitionCallback_2_1(event_2_1, cookie); |
| } |
| |
| @Override |
| public void phraseRecognitionCallback( |
| android.hardware.soundtrigger.V2_0.ISoundTriggerHwCallback.PhraseRecognitionEvent event, |
| int cookie) { |
| android.hardware.soundtrigger.V2_1.ISoundTriggerHwCallback.PhraseRecognitionEvent |
| event_2_1 = Hw2CompatUtil.convertPhraseRecognitionEvent_2_0_to_2_1(event); |
| phraseRecognitionCallback_2_1(event_2_1, cookie); |
| } |
| |
| @Override |
| public void soundModelCallback( |
| android.hardware.soundtrigger.V2_0.ISoundTriggerHwCallback.ModelEvent event, |
| int cookie) { |
| // Nobody cares. |
| } |
| } |
| } |