1 /* 2 * Copyright (C) 2020 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 static android.Manifest.permission.CAPTURE_AUDIO_HOTWORD; 20 import static android.Manifest.permission.RECORD_AUDIO; 21 22 import android.annotation.NonNull; 23 import android.annotation.Nullable; 24 import android.content.Context; 25 import android.content.PermissionChecker; 26 import android.media.permission.Identity; 27 import android.media.permission.IdentityContext; 28 import android.media.permission.PermissionUtil; 29 import android.media.soundtrigger.ModelParameterRange; 30 import android.media.soundtrigger.PhraseSoundModel; 31 import android.media.soundtrigger.RecognitionConfig; 32 import android.media.soundtrigger.SoundModel; 33 import android.media.soundtrigger.Status; 34 import android.media.soundtrigger_middleware.ISoundTriggerCallback; 35 import android.media.soundtrigger_middleware.ISoundTriggerMiddlewareService; 36 import android.media.soundtrigger_middleware.ISoundTriggerModule; 37 import android.media.soundtrigger_middleware.PhraseRecognitionEventSys; 38 import android.media.soundtrigger_middleware.RecognitionEventSys; 39 import android.media.soundtrigger_middleware.SoundTriggerModuleDescriptor; 40 import android.os.IBinder; 41 import android.os.RemoteException; 42 import android.os.ServiceSpecificException; 43 44 import com.android.server.LocalServices; 45 import com.android.server.pm.permission.LegacyPermissionManagerInternal; 46 47 import java.io.PrintWriter; 48 import java.util.Objects; 49 50 /** 51 * This is a decorator of an {@link ISoundTriggerMiddlewareService}, which enforces permissions. 52 * <p> 53 * Every public method in this class, overriding an interface method, must follow a similar 54 * pattern: 55 * <code><pre> 56 * @Override public T method(S arg) { 57 * // Permission check. 58 * enforcePermissions*(...); 59 * return mDelegate.method(arg); 60 * } 61 * </pre></code> 62 * 63 * {@hide} 64 */ 65 public class SoundTriggerMiddlewarePermission implements ISoundTriggerMiddlewareInternal, Dumpable { 66 private static final String TAG = "SoundTriggerMiddlewarePermission"; 67 68 private final @NonNull ISoundTriggerMiddlewareInternal mDelegate; 69 private final @NonNull Context mContext; 70 SoundTriggerMiddlewarePermission( @onNull ISoundTriggerMiddlewareInternal delegate, @NonNull Context context)71 public SoundTriggerMiddlewarePermission( 72 @NonNull ISoundTriggerMiddlewareInternal delegate, @NonNull Context context) { 73 mDelegate = delegate; 74 mContext = context; 75 } 76 77 @Override 78 public @NonNull listModules()79 SoundTriggerModuleDescriptor[] listModules() { 80 Identity identity = getIdentity(); 81 enforcePermissionForPreflight(mContext, identity, CAPTURE_AUDIO_HOTWORD); 82 return mDelegate.listModules(); 83 } 84 85 @Override 86 public @NonNull attach(int handle, @NonNull ISoundTriggerCallback callback, boolean isTrusted)87 ISoundTriggerModule attach(int handle, 88 @NonNull ISoundTriggerCallback callback, boolean isTrusted) { 89 Identity identity = getIdentity(); 90 enforcePermissionsForPreflight(identity); 91 ModuleWrapper wrapper = new ModuleWrapper(identity, callback, isTrusted); 92 return wrapper.attach(mDelegate.attach(handle, wrapper.getCallbackWrapper(), isTrusted)); 93 } 94 95 // Override toString() in order to have the delegate's ID in it. 96 @Override toString()97 public String toString() { 98 return Objects.toString(mDelegate); 99 } 100 101 /** 102 * Get the identity context, or throws an InternalServerError if it has not been established. 103 * 104 * @return The identity. 105 */ 106 private static @NonNull getIdentity()107 Identity getIdentity() { 108 return IdentityContext.getNonNull(); 109 } 110 111 /** 112 * Throws a {@link SecurityException} if originator permanently doesn't have the given 113 * permission, 114 * or a {@link ServiceSpecificException} with a {@link Status#TEMPORARY_PERMISSION_DENIED} if 115 * originator temporarily doesn't have the right permissions to use this service. 116 */ enforcePermissionsForPreflight(@onNull Identity identity)117 private void enforcePermissionsForPreflight(@NonNull Identity identity) { 118 enforcePermissionForPreflight(mContext, identity, RECORD_AUDIO); 119 enforcePermissionForPreflight(mContext, identity, CAPTURE_AUDIO_HOTWORD); 120 } 121 122 /** 123 * Throws a {@link SecurityException} iff the originator has permission to receive data. 124 */ enforcePermissionsForDataDelivery(@onNull Identity identity, @NonNull String reason)125 void enforcePermissionsForDataDelivery(@NonNull Identity identity, @NonNull String reason) { 126 enforceSoundTriggerRecordAudioPermissionForDataDelivery(identity, reason); 127 enforcePermissionForDataDelivery(mContext, identity, CAPTURE_AUDIO_HOTWORD, 128 reason); 129 } 130 131 /** 132 * Throws a {@link SecurityException} iff the given identity has given permission to receive 133 * data. 134 * 135 * @param context A {@link Context}, used for permission checks. 136 * @param identity The identity to check. 137 * @param permission The identifier of the permission we want to check. 138 * @param reason The reason why we're requesting the permission, for auditing purposes. 139 */ enforcePermissionForDataDelivery(@onNull Context context, @NonNull Identity identity, @NonNull String permission, @NonNull String reason)140 private static void enforcePermissionForDataDelivery(@NonNull Context context, 141 @NonNull Identity identity, 142 @NonNull String permission, @NonNull String reason) { 143 final int status = PermissionUtil.checkPermissionForDataDelivery(context, identity, 144 permission, reason); 145 if (status != PermissionChecker.PERMISSION_GRANTED) { 146 throw new SecurityException( 147 String.format("Failed to obtain permission %s for identity %s", permission, 148 ObjectPrinter.print(identity, 16))); 149 } 150 } 151 enforceSoundTriggerRecordAudioPermissionForDataDelivery( @onNull Identity identity, @NonNull String reason)152 private static void enforceSoundTriggerRecordAudioPermissionForDataDelivery( 153 @NonNull Identity identity, @NonNull String reason) { 154 LegacyPermissionManagerInternal lpmi = 155 LocalServices.getService(LegacyPermissionManagerInternal.class); 156 final int status = lpmi.checkSoundTriggerRecordAudioPermissionForDataDelivery(identity.uid, 157 identity.packageName, identity.attributionTag, reason); 158 if (status != PermissionChecker.PERMISSION_GRANTED) { 159 throw new SecurityException( 160 String.format("Failed to obtain permission RECORD_AUDIO for identity %s", 161 ObjectPrinter.print(identity, 16))); 162 } 163 } 164 165 /** 166 * Throws a {@link SecurityException} if originator permanently doesn't have the given 167 * permission. 168 * Soft (temporary) denials are considered OK for preflight purposes. 169 * 170 * @param context A {@link Context}, used for permission checks. 171 * @param identity The identity to check. 172 * @param permission The identifier of the permission we want to check. 173 */ enforcePermissionForPreflight(@onNull Context context, @NonNull Identity identity, @NonNull String permission)174 private static void enforcePermissionForPreflight(@NonNull Context context, 175 @NonNull Identity identity, @NonNull String permission) { 176 final int status = PermissionUtil.checkPermissionForPreflight(context, identity, 177 permission); 178 switch (status) { 179 case PermissionChecker.PERMISSION_GRANTED: 180 case PermissionChecker.PERMISSION_SOFT_DENIED: 181 return; 182 case PermissionChecker.PERMISSION_HARD_DENIED: 183 throw new SecurityException( 184 String.format("Failed to obtain permission %s for identity %s", permission, 185 ObjectPrinter.print(identity, 16))); 186 default: 187 throw new RuntimeException("Unexpected perimission check result."); 188 } 189 } 190 191 192 @Override dump(PrintWriter pw)193 public void dump(PrintWriter pw) { 194 if (mDelegate instanceof Dumpable) { 195 ((Dumpable) mDelegate).dump(pw); 196 } 197 } 198 199 /** 200 * A wrapper around an {@link ISoundTriggerModule} implementation, to address the same aspects 201 * mentioned in {@link SoundTriggerModule} above. This class follows the same conventions. 202 */ 203 private class ModuleWrapper extends ISoundTriggerModule.Stub { 204 private ISoundTriggerModule mDelegate; 205 private final @NonNull Identity mOriginatorIdentity; 206 private final @NonNull CallbackWrapper mCallbackWrapper; 207 private final boolean mIsTrusted; 208 ModuleWrapper(@onNull Identity originatorIdentity, @NonNull ISoundTriggerCallback callback, boolean isTrusted)209 ModuleWrapper(@NonNull Identity originatorIdentity, 210 @NonNull ISoundTriggerCallback callback, 211 boolean isTrusted) { 212 mOriginatorIdentity = originatorIdentity; 213 mCallbackWrapper = new CallbackWrapper(callback); 214 mIsTrusted = isTrusted; 215 } 216 attach(@onNull ISoundTriggerModule delegate)217 ModuleWrapper attach(@NonNull ISoundTriggerModule delegate) { 218 mDelegate = delegate; 219 return this; 220 } 221 getCallbackWrapper()222 ISoundTriggerCallback getCallbackWrapper() { 223 return mCallbackWrapper; 224 } 225 226 @Override loadModel(@onNull SoundModel model)227 public int loadModel(@NonNull SoundModel model) throws RemoteException { 228 enforcePermissions(); 229 return mDelegate.loadModel(model); 230 } 231 232 @Override loadPhraseModel(@onNull PhraseSoundModel model)233 public int loadPhraseModel(@NonNull PhraseSoundModel model) throws RemoteException { 234 enforcePermissions(); 235 return mDelegate.loadPhraseModel(model); 236 } 237 238 @Override unloadModel(int modelHandle)239 public void unloadModel(int modelHandle) throws RemoteException { 240 // Unloading a model does not require special permissions. Having a handle to the 241 // session is sufficient. 242 mDelegate.unloadModel(modelHandle); 243 244 } 245 246 @Override startRecognition(int modelHandle, @NonNull RecognitionConfig config)247 public IBinder startRecognition(int modelHandle, @NonNull RecognitionConfig config) 248 throws RemoteException { 249 enforcePermissions(); 250 return mDelegate.startRecognition(modelHandle, config); 251 } 252 253 @Override stopRecognition(int modelHandle)254 public void stopRecognition(int modelHandle) throws RemoteException { 255 // Stopping a model does not require special permissions. Having a handle to the 256 // session is sufficient. 257 mDelegate.stopRecognition(modelHandle); 258 } 259 260 @Override forceRecognitionEvent(int modelHandle)261 public void forceRecognitionEvent(int modelHandle) throws RemoteException { 262 enforcePermissions(); 263 mDelegate.forceRecognitionEvent(modelHandle); 264 } 265 266 @Override setModelParameter(int modelHandle, int modelParam, int value)267 public void setModelParameter(int modelHandle, int modelParam, int value) 268 throws RemoteException { 269 enforcePermissions(); 270 mDelegate.setModelParameter(modelHandle, modelParam, value); 271 } 272 273 @Override getModelParameter(int modelHandle, int modelParam)274 public int getModelParameter(int modelHandle, int modelParam) throws RemoteException { 275 enforcePermissions(); 276 return mDelegate.getModelParameter(modelHandle, modelParam); 277 } 278 279 @Override 280 @Nullable queryModelParameterSupport(int modelHandle, int modelParam)281 public ModelParameterRange queryModelParameterSupport(int modelHandle, int modelParam) 282 throws RemoteException { 283 enforcePermissions(); 284 return mDelegate.queryModelParameterSupport(modelHandle, 285 modelParam); 286 } 287 288 @Override detach()289 public void detach() throws RemoteException { 290 // Detaching does not require special permissions. Having a handle to the session is 291 // sufficient. 292 mDelegate.detach(); 293 } 294 295 // Override toString() in order to have the delegate's ID in it. 296 @Override toString()297 public String toString() { 298 return Objects.toString(mDelegate); 299 } 300 enforcePermissions()301 private void enforcePermissions() { 302 enforcePermissionsForPreflight(mOriginatorIdentity); 303 } 304 305 private class CallbackWrapper implements ISoundTriggerCallback { 306 private final ISoundTriggerCallback mDelegate; 307 CallbackWrapper(ISoundTriggerCallback delegate)308 private CallbackWrapper(ISoundTriggerCallback delegate) { 309 mDelegate = delegate; 310 } 311 312 @Override onRecognition(int modelHandle, RecognitionEventSys event, int captureSession)313 public void onRecognition(int modelHandle, RecognitionEventSys event, 314 int captureSession) throws RemoteException { 315 enforcePermissions("Sound trigger recognition."); 316 mDelegate.onRecognition(modelHandle, event, captureSession); 317 } 318 319 @Override onPhraseRecognition(int modelHandle, PhraseRecognitionEventSys event, int captureSession)320 public void onPhraseRecognition(int modelHandle, PhraseRecognitionEventSys event, 321 int captureSession) throws RemoteException { 322 enforcePermissions("Sound trigger phrase recognition."); 323 mDelegate.onPhraseRecognition(modelHandle, event, captureSession); 324 } 325 326 @Override onResourcesAvailable()327 public void onResourcesAvailable() throws RemoteException { 328 mDelegate.onResourcesAvailable(); 329 } 330 331 @Override onModelUnloaded(int modelHandle)332 public void onModelUnloaded(int modelHandle) throws RemoteException { 333 mDelegate.onModelUnloaded(modelHandle); 334 } 335 336 @Override onModuleDied()337 public void onModuleDied() throws RemoteException { 338 mDelegate.onModuleDied(); 339 } 340 341 @Override asBinder()342 public IBinder asBinder() { 343 return mDelegate.asBinder(); 344 } 345 346 // Override toString() in order to have the delegate's ID in it. 347 @Override toString()348 public String toString() { 349 return mDelegate.toString(); 350 } 351 enforcePermissions(String reason)352 private void enforcePermissions(String reason) { 353 if (mIsTrusted) { 354 enforcePermissionsForPreflight(mOriginatorIdentity); 355 } else { 356 enforcePermissionsForDataDelivery(mOriginatorIdentity, reason); 357 } 358 } 359 } 360 } 361 } 362