1 /* 2 * Copyright (C) 2016 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.audio; 18 19 import android.content.Context; 20 import android.content.pm.PackageManager; 21 import android.media.AudioFormat; 22 import android.media.AudioManager; 23 import android.media.AudioRecordingConfiguration; 24 import android.media.AudioSystem; 25 import android.media.IRecordingConfigDispatcher; 26 import android.media.MediaRecorder; 27 import android.media.audiofx.AudioEffect; 28 import android.os.IBinder; 29 import android.os.RemoteException; 30 import android.util.Log; 31 32 import java.io.PrintWriter; 33 import java.text.DateFormat; 34 import java.util.ArrayList; 35 import java.util.Date; 36 import java.util.Iterator; 37 import java.util.List; 38 39 /** 40 * Class to receive and dispatch updates from AudioSystem about recording configurations. 41 */ 42 public final class RecordingActivityMonitor implements AudioSystem.AudioRecordingCallback { 43 44 public final static String TAG = "AudioService.RecordingActivityMonitor"; 45 46 private ArrayList<RecMonitorClient> mClients = new ArrayList<RecMonitorClient>(); 47 // a public client is one that needs an anonymized version of the playback configurations, we 48 // keep track of whether there is at least one to know when we need to create the list of 49 // playback configurations that do not contain uid/package name information. 50 private boolean mHasPublicClients = false; 51 52 static final class RecordingState { 53 private final int mRiid; 54 private final RecorderDeathHandler mDeathHandler; 55 private boolean mIsActive; 56 private AudioRecordingConfiguration mConfig; 57 RecordingState(int riid, RecorderDeathHandler handler)58 RecordingState(int riid, RecorderDeathHandler handler) { 59 mRiid = riid; 60 mDeathHandler = handler; 61 } 62 RecordingState(AudioRecordingConfiguration config)63 RecordingState(AudioRecordingConfiguration config) { 64 mRiid = AudioManager.RECORD_RIID_INVALID; 65 mDeathHandler = null; 66 mConfig = config; 67 } 68 getRiid()69 int getRiid() { 70 return mRiid; 71 } 72 getPortId()73 int getPortId() { 74 return mConfig != null ? mConfig.getClientPortId() : -1; 75 } 76 getConfig()77 AudioRecordingConfiguration getConfig() { 78 return mConfig; 79 } 80 hasDeathHandler()81 boolean hasDeathHandler() { 82 return mDeathHandler != null; 83 } 84 isActiveConfiguration()85 boolean isActiveConfiguration() { 86 return mIsActive && mConfig != null; 87 } 88 release()89 void release() { 90 if (mDeathHandler != null) { 91 mDeathHandler.release(); 92 } 93 } 94 95 // returns true if status of an active recording has changed setActive(boolean active)96 boolean setActive(boolean active) { 97 if (mIsActive == active) return false; 98 mIsActive = active; 99 return mConfig != null; 100 } 101 102 // returns true if an active recording has been updated setConfig(AudioRecordingConfiguration config)103 boolean setConfig(AudioRecordingConfiguration config) { 104 if (config.equals(mConfig)) return false; 105 mConfig = config; 106 return mIsActive; 107 } 108 dump(PrintWriter pw)109 void dump(PrintWriter pw) { 110 pw.println("riid " + mRiid + "; active? " + mIsActive); 111 if (mConfig != null) { 112 mConfig.dump(pw); 113 } else { 114 pw.println(" no config"); 115 } 116 } 117 } 118 private List<RecordingState> mRecordStates = new ArrayList<RecordingState>(); 119 120 private final PackageManager mPackMan; 121 RecordingActivityMonitor(Context ctxt)122 RecordingActivityMonitor(Context ctxt) { 123 RecMonitorClient.sMonitor = this; 124 RecorderDeathHandler.sMonitor = this; 125 mPackMan = ctxt.getPackageManager(); 126 } 127 128 /** 129 * Implementation of android.media.AudioSystem.AudioRecordingCallback 130 */ onRecordingConfigurationChanged(int event, int riid, int uid, int session, int source, int portId, boolean silenced, int[] recordingInfo, AudioEffect.Descriptor[] clientEffects, AudioEffect.Descriptor[] effects, int activeSource, String packName)131 public void onRecordingConfigurationChanged(int event, int riid, int uid, int session, 132 int source, int portId, boolean silenced, 133 int[] recordingInfo, 134 AudioEffect.Descriptor[] clientEffects, 135 AudioEffect.Descriptor[] effects, 136 int activeSource, String packName) { 137 final AudioRecordingConfiguration config = createRecordingConfiguration( 138 uid, session, source, recordingInfo, 139 portId, silenced, activeSource, clientEffects, effects); 140 if (MediaRecorder.isSystemOnlyAudioSource(source)) { 141 // still want to log event, it just won't appear in recording configurations; 142 sEventLogger.log(new RecordingEvent(event, riid, config).printLog(TAG)); 143 return; 144 } 145 dispatchCallbacks(updateSnapshot(event, riid, config)); 146 } 147 148 /** 149 * Track a recorder provided by the client 150 */ trackRecorder(IBinder recorder)151 public int trackRecorder(IBinder recorder) { 152 if (recorder == null) { 153 Log.e(TAG, "trackRecorder called with null token"); 154 return AudioManager.RECORD_RIID_INVALID; 155 } 156 final int newRiid = AudioSystem.newAudioRecorderId(); 157 RecorderDeathHandler handler = new RecorderDeathHandler(newRiid, recorder); 158 if (!handler.init()) { 159 // probably means that the AudioRecord has already died 160 return AudioManager.RECORD_RIID_INVALID; 161 } 162 synchronized (mRecordStates) { 163 mRecordStates.add(new RecordingState(newRiid, handler)); 164 } 165 // a newly added record is inactive, no change in active configs is possible. 166 return newRiid; 167 } 168 169 /** 170 * Receive an event from the client about a tracked recorder 171 */ recorderEvent(int riid, int event)172 public void recorderEvent(int riid, int event) { 173 int configEvent = event == AudioManager.RECORDER_STATE_STARTED 174 ? AudioManager.RECORD_CONFIG_EVENT_START : 175 event == AudioManager.RECORDER_STATE_STOPPED 176 ? AudioManager.RECORD_CONFIG_EVENT_STOP : AudioManager.RECORD_CONFIG_EVENT_NONE; 177 if (riid == AudioManager.RECORD_RIID_INVALID 178 || configEvent == AudioManager.RECORD_CONFIG_EVENT_NONE) { 179 sEventLogger.log(new RecordingEvent(event, riid, null).printLog(TAG)); 180 return; 181 } 182 dispatchCallbacks(updateSnapshot(configEvent, riid, null)); 183 } 184 185 /** 186 * Stop tracking the recorder 187 */ releaseRecorder(int riid)188 public void releaseRecorder(int riid) { 189 dispatchCallbacks(updateSnapshot(AudioManager.RECORD_CONFIG_EVENT_RELEASE, riid, null)); 190 } 191 dispatchCallbacks(List<AudioRecordingConfiguration> configs)192 private void dispatchCallbacks(List<AudioRecordingConfiguration> configs) { 193 if (configs == null) { // null means "no changes" 194 return; 195 } 196 synchronized (mClients) { 197 // list of recording configurations for "public consumption". It is only computed if 198 // there are non-system recording activity listeners. 199 final List<AudioRecordingConfiguration> configsPublic = mHasPublicClients 200 ? anonymizeForPublicConsumption(configs) : 201 new ArrayList<AudioRecordingConfiguration>(); 202 for (RecMonitorClient rmc : mClients) { 203 try { 204 if (rmc.mIsPrivileged) { 205 rmc.mDispatcherCb.dispatchRecordingConfigChange(configs); 206 } else { 207 rmc.mDispatcherCb.dispatchRecordingConfigChange(configsPublic); 208 } 209 } catch (RemoteException e) { 210 Log.w(TAG, "Could not call dispatchRecordingConfigChange() on client", e); 211 } 212 } 213 } 214 } 215 dump(PrintWriter pw)216 protected void dump(PrintWriter pw) { 217 // recorders 218 pw.println("\nRecordActivityMonitor dump time: " 219 + DateFormat.getTimeInstance().format(new Date())); 220 synchronized (mRecordStates) { 221 for (RecordingState state : mRecordStates) { 222 state.dump(pw); 223 } 224 } 225 pw.println("\n"); 226 // log 227 sEventLogger.dump(pw); 228 } 229 anonymizeForPublicConsumption( List<AudioRecordingConfiguration> sysConfigs)230 private static ArrayList<AudioRecordingConfiguration> anonymizeForPublicConsumption( 231 List<AudioRecordingConfiguration> sysConfigs) { 232 ArrayList<AudioRecordingConfiguration> publicConfigs = 233 new ArrayList<AudioRecordingConfiguration>(); 234 // only add active anonymized configurations, 235 for (AudioRecordingConfiguration config : sysConfigs) { 236 publicConfigs.add(AudioRecordingConfiguration.anonymizedCopy(config)); 237 } 238 return publicConfigs; 239 } 240 initMonitor()241 void initMonitor() { 242 AudioSystem.setRecordingCallback(this); 243 } 244 onAudioServerDied()245 void onAudioServerDied() { 246 // Remove all RecordingState entries that do not have a death handler (that means 247 // they are tracked by the Audio Server). If there were active entries among removed, 248 // dispatch active configuration changes. 249 List<AudioRecordingConfiguration> configs = null; 250 synchronized (mRecordStates) { 251 boolean configChanged = false; 252 for (Iterator<RecordingState> it = mRecordStates.iterator(); it.hasNext(); ) { 253 RecordingState state = it.next(); 254 if (!state.hasDeathHandler()) { 255 if (state.isActiveConfiguration()) { 256 configChanged = true; 257 sEventLogger.log(new RecordingEvent( 258 AudioManager.RECORD_CONFIG_EVENT_RELEASE, 259 state.getRiid(), state.getConfig())); 260 } 261 it.remove(); 262 } 263 } 264 if (configChanged) { 265 configs = getActiveRecordingConfigurations(true /*isPrivileged*/); 266 } 267 } 268 dispatchCallbacks(configs); 269 } 270 registerRecordingCallback(IRecordingConfigDispatcher rcdb, boolean isPrivileged)271 void registerRecordingCallback(IRecordingConfigDispatcher rcdb, boolean isPrivileged) { 272 if (rcdb == null) { 273 return; 274 } 275 synchronized (mClients) { 276 final RecMonitorClient rmc = new RecMonitorClient(rcdb, isPrivileged); 277 if (rmc.init()) { 278 if (!isPrivileged) { 279 mHasPublicClients = true; 280 } 281 mClients.add(rmc); 282 } 283 } 284 } 285 unregisterRecordingCallback(IRecordingConfigDispatcher rcdb)286 void unregisterRecordingCallback(IRecordingConfigDispatcher rcdb) { 287 if (rcdb == null) { 288 return; 289 } 290 synchronized (mClients) { 291 final Iterator<RecMonitorClient> clientIterator = mClients.iterator(); 292 boolean hasPublicClients = false; 293 while (clientIterator.hasNext()) { 294 RecMonitorClient rmc = clientIterator.next(); 295 if (rcdb.equals(rmc.mDispatcherCb)) { 296 rmc.release(); 297 clientIterator.remove(); 298 } else { 299 if (!rmc.mIsPrivileged) { 300 hasPublicClients = true; 301 } 302 } 303 } 304 mHasPublicClients = hasPublicClients; 305 } 306 } 307 getActiveRecordingConfigurations(boolean isPrivileged)308 List<AudioRecordingConfiguration> getActiveRecordingConfigurations(boolean isPrivileged) { 309 List<AudioRecordingConfiguration> configs = new ArrayList<AudioRecordingConfiguration>(); 310 synchronized (mRecordStates) { 311 for (RecordingState state : mRecordStates) { 312 if (state.isActiveConfiguration()) { 313 configs.add(state.getConfig()); 314 } 315 } 316 } 317 // AudioRecordingConfiguration objects never get updated. If config changes, 318 // the reference to the config is set in RecordingState. 319 if (!isPrivileged) { 320 configs = anonymizeForPublicConsumption(configs); 321 } 322 return configs; 323 } 324 325 /** 326 * Create a recording configuration from the provided parameters 327 * @param uid 328 * @param session 329 * @param source 330 * @param recordingFormat see 331 * {@link AudioSystem.AudioRecordingCallback#onRecordingConfigurationChanged(int, int, int,\ 332 int, int, boolean, int[], AudioEffect.Descriptor[], AudioEffect.Descriptor[], int, String)} 333 * for the definition of the contents of the array 334 * @param portId 335 * @param silenced 336 * @param activeSource 337 * @param clientEffects 338 * @param effects 339 * @return null a configuration object. 340 */ createRecordingConfiguration(int uid, int session, int source, int[] recordingInfo, int portId, boolean silenced, int activeSource, AudioEffect.Descriptor[] clientEffects, AudioEffect.Descriptor[] effects)341 private AudioRecordingConfiguration createRecordingConfiguration(int uid, 342 int session, int source, int[] recordingInfo, int portId, boolean silenced, 343 int activeSource, AudioEffect.Descriptor[] clientEffects, 344 AudioEffect.Descriptor[] effects) { 345 final AudioFormat clientFormat = new AudioFormat.Builder() 346 .setEncoding(recordingInfo[0]) 347 // FIXME this doesn't support index-based masks 348 .setChannelMask(recordingInfo[1]) 349 .setSampleRate(recordingInfo[2]) 350 .build(); 351 final AudioFormat deviceFormat = new AudioFormat.Builder() 352 .setEncoding(recordingInfo[3]) 353 // FIXME this doesn't support index-based masks 354 .setChannelMask(recordingInfo[4]) 355 .setSampleRate(recordingInfo[5]) 356 .build(); 357 final int patchHandle = recordingInfo[6]; 358 final String[] packages = mPackMan.getPackagesForUid(uid); 359 final String packageName; 360 if (packages != null && packages.length > 0) { 361 packageName = packages[0]; 362 } else { 363 packageName = ""; 364 } 365 return new AudioRecordingConfiguration(uid, session, source, 366 clientFormat, deviceFormat, patchHandle, packageName, 367 portId, silenced, activeSource, clientEffects, effects); 368 } 369 370 /** 371 * Update the internal "view" of the active recording sessions 372 * @param event RECORD_CONFIG_EVENT_... 373 * @param riid 374 * @param config 375 * @return null if the list of active recording sessions has not been modified, a list 376 * with the current active configurations otherwise. 377 */ updateSnapshot( int event, int riid, AudioRecordingConfiguration config)378 private List<AudioRecordingConfiguration> updateSnapshot( 379 int event, int riid, AudioRecordingConfiguration config) { 380 List<AudioRecordingConfiguration> configs = null; 381 synchronized (mRecordStates) { 382 int stateIndex = -1; 383 if (riid != AudioManager.RECORD_RIID_INVALID) { 384 stateIndex = findStateByRiid(riid); 385 } else if (config != null) { 386 stateIndex = findStateByPortId(config.getClientPortId()); 387 } 388 if (stateIndex == -1) { 389 if (event == AudioManager.RECORD_CONFIG_EVENT_START && config != null) { 390 // First time registration for a recorder tracked by AudioServer. 391 mRecordStates.add(new RecordingState(config)); 392 stateIndex = mRecordStates.size() - 1; 393 } else { 394 if (config == null) { 395 // Records tracked by clients must be registered first via trackRecorder. 396 Log.e(TAG, String.format( 397 "Unexpected event %d for riid %d", event, riid)); 398 } 399 return configs; 400 } 401 } 402 final RecordingState state = mRecordStates.get(stateIndex); 403 404 boolean configChanged; 405 switch (event) { 406 case AudioManager.RECORD_CONFIG_EVENT_START: 407 configChanged = state.setActive(true); 408 if (config != null) { 409 configChanged = state.setConfig(config) || configChanged; 410 } 411 break; 412 case AudioManager.RECORD_CONFIG_EVENT_UPDATE: 413 // For this event config != null 414 configChanged = state.setConfig(config); 415 break; 416 case AudioManager.RECORD_CONFIG_EVENT_STOP: 417 configChanged = state.setActive(false); 418 if (!state.hasDeathHandler()) { 419 // A recorder tracked by AudioServer has to be removed now so it 420 // does not leak. It will be re-registered if recording starts again. 421 mRecordStates.remove(stateIndex); 422 } 423 break; 424 case AudioManager.RECORD_CONFIG_EVENT_RELEASE: 425 configChanged = state.isActiveConfiguration(); 426 state.release(); 427 mRecordStates.remove(stateIndex); 428 break; 429 default: 430 Log.e(TAG, String.format("Unknown event %d for riid %d / portid %d", 431 event, riid, state.getPortId())); 432 configChanged = false; 433 } 434 if (configChanged) { 435 sEventLogger.log(new RecordingEvent(event, riid, state.getConfig())); 436 configs = getActiveRecordingConfigurations(true /*isPrivileged*/); 437 } 438 } 439 return configs; 440 } 441 442 // riid is assumed to be valid findStateByRiid(int riid)443 private int findStateByRiid(int riid) { 444 synchronized (mRecordStates) { 445 for (int i = 0; i < mRecordStates.size(); i++) { 446 if (mRecordStates.get(i).getRiid() == riid) { 447 return i; 448 } 449 } 450 } 451 return -1; 452 } 453 findStateByPortId(int portId)454 private int findStateByPortId(int portId) { 455 // Lookup by portId is unambiguous only for recordings managed by the Audio Server. 456 synchronized (mRecordStates) { 457 for (int i = 0; i < mRecordStates.size(); i++) { 458 if (!mRecordStates.get(i).hasDeathHandler() 459 && mRecordStates.get(i).getPortId() == portId) { 460 return i; 461 } 462 } 463 } 464 return -1; 465 } 466 467 /** 468 * Inner class to track clients that want to be notified of recording updates 469 */ 470 private final static class RecMonitorClient implements IBinder.DeathRecipient { 471 472 // can afford to be static because only one RecordingActivityMonitor ever instantiated 473 static RecordingActivityMonitor sMonitor; 474 475 final IRecordingConfigDispatcher mDispatcherCb; 476 final boolean mIsPrivileged; 477 RecMonitorClient(IRecordingConfigDispatcher rcdb, boolean isPrivileged)478 RecMonitorClient(IRecordingConfigDispatcher rcdb, boolean isPrivileged) { 479 mDispatcherCb = rcdb; 480 mIsPrivileged = isPrivileged; 481 } 482 binderDied()483 public void binderDied() { 484 Log.w(TAG, "client died"); 485 sMonitor.unregisterRecordingCallback(mDispatcherCb); 486 } 487 init()488 boolean init() { 489 try { 490 mDispatcherCb.asBinder().linkToDeath(this, 0); 491 return true; 492 } catch (RemoteException e) { 493 Log.w(TAG, "Could not link to client death", e); 494 return false; 495 } 496 } 497 release()498 void release() { 499 mDispatcherCb.asBinder().unlinkToDeath(this, 0); 500 } 501 } 502 503 private static final class RecorderDeathHandler implements IBinder.DeathRecipient { 504 505 // can afford to be static because only one RecordingActivityMonitor ever instantiated 506 static RecordingActivityMonitor sMonitor; 507 508 final int mRiid; 509 private final IBinder mRecorderToken; 510 RecorderDeathHandler(int riid, IBinder recorderToken)511 RecorderDeathHandler(int riid, IBinder recorderToken) { 512 mRiid = riid; 513 mRecorderToken = recorderToken; 514 } 515 binderDied()516 public void binderDied() { 517 sMonitor.releaseRecorder(mRiid); 518 } 519 init()520 boolean init() { 521 try { 522 mRecorderToken.linkToDeath(this, 0); 523 return true; 524 } catch (RemoteException e) { 525 Log.w(TAG, "Could not link to recorder death", e); 526 return false; 527 } 528 } 529 release()530 void release() { 531 mRecorderToken.unlinkToDeath(this, 0); 532 } 533 } 534 535 /** 536 * Inner class for recording event logging 537 */ 538 private static final class RecordingEvent extends AudioEventLogger.Event { 539 private final int mRecEvent; 540 private final int mRIId; 541 private final int mClientUid; 542 private final int mSession; 543 private final int mSource; 544 private final String mPackName; 545 RecordingEvent(int event, int riid, AudioRecordingConfiguration config)546 RecordingEvent(int event, int riid, AudioRecordingConfiguration config) { 547 mRecEvent = event; 548 mRIId = riid; 549 if (config != null) { 550 mClientUid = config.getClientUid(); 551 mSession = config.getClientAudioSessionId(); 552 mSource = config.getClientAudioSource(); 553 mPackName = config.getClientPackageName(); 554 } else { 555 mClientUid = -1; 556 mSession = -1; 557 mSource = -1; 558 mPackName = null; 559 } 560 } 561 recordEventToString(int recEvent)562 private static String recordEventToString(int recEvent) { 563 switch (recEvent) { 564 case AudioManager.RECORD_CONFIG_EVENT_START: 565 return "start"; 566 case AudioManager.RECORD_CONFIG_EVENT_UPDATE: 567 return "update"; 568 case AudioManager.RECORD_CONFIG_EVENT_STOP: 569 return "stop"; 570 case AudioManager.RECORD_CONFIG_EVENT_RELEASE: 571 return "release"; 572 default: 573 return "unknown (" + recEvent + ")"; 574 } 575 } 576 577 @Override eventToString()578 public String eventToString() { 579 return new StringBuilder("rec ").append(recordEventToString(mRecEvent)) 580 .append(" riid:").append(mRIId) 581 .append(" uid:").append(mClientUid) 582 .append(" session:").append(mSession) 583 .append(" src:").append(MediaRecorder.toLogFriendlyAudioSource(mSource)) 584 .append(mPackName == null ? "" : " pack:" + mPackName).toString(); 585 } 586 } 587 588 private static final AudioEventLogger sEventLogger = new AudioEventLogger(50, 589 "recording activity received by AudioService"); 590 } 591