1 /* 2 * Copyright (C) 2023 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17 package com.android.server.soundtrigger_middleware; 18 19 import android.annotation.Nullable; 20 import android.media.soundtrigger.Phrase; 21 import android.media.soundtrigger.RecognitionConfig; 22 import android.media.soundtrigger.SoundModel; 23 import android.media.soundtrigger_middleware.IInjectGlobalEvent; 24 import android.media.soundtrigger_middleware.IInjectModelEvent; 25 import android.media.soundtrigger_middleware.IInjectRecognitionEvent; 26 import android.media.soundtrigger_middleware.ISoundTriggerInjection; 27 import android.os.IBinder; 28 import android.os.RemoteException; 29 import android.util.Slog; 30 31 import com.android.internal.annotations.GuardedBy; 32 33 import java.util.Objects; 34 35 /** 36 * Service side of the injection interface which enforces a single client. 37 * Essentially a facade that presents an ever-present, single injection client to the fake STHAL. 38 * Proxies a binder interface, but should never be called as such. 39 * @hide 40 */ 41 42 public class SoundTriggerInjection implements ISoundTriggerInjection, IBinder.DeathRecipient { 43 44 private static final String TAG = "SoundTriggerInjection"; 45 46 private final Object mClientLock = new Object(); 47 @GuardedBy("mClientLock") 48 private ISoundTriggerInjection mClient = null; 49 @GuardedBy("mClientLock") 50 private IInjectGlobalEvent mGlobalEventInjection = null; 51 52 /** 53 * Register a remote injection client. 54 * @param client - The injection client to register 55 */ registerClient(ISoundTriggerInjection client)56 public void registerClient(ISoundTriggerInjection client) { 57 synchronized (mClientLock) { 58 Objects.requireNonNull(client); 59 if (mClient != null) { 60 try { 61 mClient.onPreempted(); 62 } catch (RemoteException e) { 63 Slog.e(TAG, "RemoteException when handling preemption", e); 64 } 65 mClient.asBinder().unlinkToDeath(this, 0); 66 } 67 mClient = client; 68 // Register cached global event injection interfaces, 69 // in case our client missed them. 70 try { 71 mClient.asBinder().linkToDeath(this, 0); 72 if (mGlobalEventInjection != null) { 73 mClient.registerGlobalEventInjection(mGlobalEventInjection); 74 } 75 } catch (RemoteException e) { 76 mClient = null; 77 } 78 79 } 80 } 81 82 @Override binderDied()83 public void binderDied() { 84 Slog.wtf(TAG, "Binder died without params"); 85 } 86 87 // If the binder has died, clear out mClient. 88 @Override binderDied(IBinder who)89 public void binderDied(IBinder who) { 90 synchronized (mClientLock) { 91 if (mClient != null && who == mClient.asBinder()) { 92 mClient = null; 93 } 94 } 95 } 96 97 @Override registerGlobalEventInjection(IInjectGlobalEvent globalInjection)98 public void registerGlobalEventInjection(IInjectGlobalEvent globalInjection) { 99 synchronized (mClientLock) { 100 // Cache for late attaching clients 101 mGlobalEventInjection = globalInjection; 102 if (mClient == null) return; 103 try { 104 mClient.registerGlobalEventInjection(mGlobalEventInjection); 105 } catch (RemoteException e) { 106 mClient = null; 107 } 108 } 109 } 110 111 @Override onRestarted(IInjectGlobalEvent globalSession)112 public void onRestarted(IInjectGlobalEvent globalSession) { 113 synchronized (mClientLock) { 114 if (mClient == null) return; 115 try { 116 mClient.onRestarted(globalSession); 117 } catch (RemoteException e) { 118 mClient = null; 119 } 120 } 121 } 122 123 @Override onFrameworkDetached(IInjectGlobalEvent globalSession)124 public void onFrameworkDetached(IInjectGlobalEvent globalSession) { 125 synchronized (mClientLock) { 126 if (mClient == null) return; 127 try { 128 mClient.onFrameworkDetached(globalSession); 129 } catch (RemoteException e) { 130 mClient = null; 131 } 132 } 133 } 134 135 @Override onClientAttached(IBinder token, IInjectGlobalEvent globalSession)136 public void onClientAttached(IBinder token, IInjectGlobalEvent globalSession) { 137 synchronized (mClientLock) { 138 if (mClient == null) return; 139 try { 140 mClient.onClientAttached(token, globalSession); 141 } catch (RemoteException e) { 142 mClient = null; 143 } 144 } 145 } 146 147 @Override onClientDetached(IBinder token)148 public void onClientDetached(IBinder token) { 149 synchronized (mClientLock) { 150 if (mClient == null) return; 151 try { 152 mClient.onClientDetached(token); 153 } catch (RemoteException e) { 154 mClient = null; 155 } 156 } 157 } 158 159 @Override onSoundModelLoaded(SoundModel model, @Nullable Phrase[] phrases, IInjectModelEvent modelInjection, IInjectGlobalEvent globalSession)160 public void onSoundModelLoaded(SoundModel model, @Nullable Phrase[] phrases, 161 IInjectModelEvent modelInjection, IInjectGlobalEvent globalSession) { 162 synchronized (mClientLock) { 163 if (mClient == null) return; 164 try { 165 mClient.onSoundModelLoaded(model, phrases, modelInjection, globalSession); 166 } catch (RemoteException e) { 167 mClient = null; 168 } 169 } 170 } 171 172 @Override onParamSet( int modelParam, int value, IInjectModelEvent modelSession)173 public void onParamSet(/** ModelParameter **/ int modelParam, int value, 174 IInjectModelEvent modelSession) { 175 synchronized (mClientLock) { 176 if (mClient == null) return; 177 try { 178 mClient.onParamSet(modelParam, value, modelSession); 179 } catch (RemoteException e) { 180 mClient = null; 181 } 182 } 183 } 184 185 @Override onRecognitionStarted(int audioSessionToken, RecognitionConfig config, IInjectRecognitionEvent recognitionInjection, IInjectModelEvent modelSession)186 public void onRecognitionStarted(int audioSessionToken, RecognitionConfig config, 187 IInjectRecognitionEvent recognitionInjection, IInjectModelEvent modelSession) { 188 synchronized (mClientLock) { 189 if (mClient == null) return; 190 try { 191 mClient.onRecognitionStarted(audioSessionToken, config, 192 recognitionInjection, modelSession); 193 } catch (RemoteException e) { 194 mClient = null; 195 } 196 } 197 } 198 199 @Override onRecognitionStopped(IInjectRecognitionEvent recognitionSession)200 public void onRecognitionStopped(IInjectRecognitionEvent recognitionSession) { 201 synchronized (mClientLock) { 202 if (mClient == null) return; 203 try { 204 mClient.onRecognitionStopped(recognitionSession); 205 } catch (RemoteException e) { 206 mClient = null; 207 } 208 } 209 } 210 211 @Override onSoundModelUnloaded(IInjectModelEvent modelSession)212 public void onSoundModelUnloaded(IInjectModelEvent modelSession) { 213 synchronized (mClientLock) { 214 if (mClient == null) return; 215 try { 216 mClient.onSoundModelUnloaded(modelSession); 217 } catch (RemoteException e) { 218 mClient = null; 219 } 220 } 221 } 222 223 @Override onPreempted()224 public void onPreempted() { 225 // We are the service, so we can't be preempted. 226 Slog.wtf(TAG, "Unexpected preempted!"); 227 } 228 229 @Override asBinder()230 public IBinder asBinder() { 231 // This class is not a real binder object 232 Slog.wtf(TAG, "Unexpected asBinder!"); 233 throw new UnsupportedOperationException("Calling asBinder on a fake binder object"); 234 } 235 236 } 237