blob: 5d31dbe93cf9a1dc6cf42d0ab29fa5e9e158ba50 [file] [log] [blame]
Jean-Michel Trivid3c71f02015-12-07 11:59:31 -08001/*
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
17package com.android.server.audio;
18
Jean-Michel Trivi66ffacf2017-02-04 17:25:31 -080019import android.content.Context;
20import android.content.pm.PackageManager;
Jean-Michel Trivi33fd8162016-02-22 10:52:41 -080021import android.media.AudioFormat;
Jean-Michel Trivid3c71f02015-12-07 11:59:31 -080022import android.media.AudioManager;
Jean-Michel Trivi598c0c92016-03-07 14:13:03 -080023import android.media.AudioRecordingConfiguration;
Jean-Michel Trivid3c71f02015-12-07 11:59:31 -080024import android.media.AudioSystem;
25import android.media.IRecordingConfigDispatcher;
Jean-Michel Trividd2772a2016-02-17 12:30:52 -080026import android.media.MediaRecorder;
Eric Laurentc7a0cdf2018-11-30 12:16:52 -080027import android.media.audiofx.AudioEffect;
Jean-Michel Trivid3c71f02015-12-07 11:59:31 -080028import android.os.IBinder;
29import android.os.RemoteException;
30import android.util.Log;
31
Jean-Michel Trivi66ffacf2017-02-04 17:25:31 -080032import java.io.PrintWriter;
33import java.text.DateFormat;
Jean-Michel Trivid3c71f02015-12-07 11:59:31 -080034import java.util.ArrayList;
Jean-Michel Trivi66ffacf2017-02-04 17:25:31 -080035import java.util.Date;
Jean-Michel Trivid3c71f02015-12-07 11:59:31 -080036import java.util.Iterator;
Jean-Michel Trivif04fab12016-05-19 10:42:35 -070037import java.util.List;
Jean-Michel Trivid3c71f02015-12-07 11:59:31 -080038
39/**
40 * Class to receive and dispatch updates from AudioSystem about recording configurations.
41 */
42public final class RecordingActivityMonitor implements AudioSystem.AudioRecordingCallback {
43
44 public final static String TAG = "AudioService.RecordingActivityMonitor";
45
46 private ArrayList<RecMonitorClient> mClients = new ArrayList<RecMonitorClient>();
Jean-Michel Trivi66ffacf2017-02-04 17:25:31 -080047 // 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;
Jean-Michel Trivid3c71f02015-12-07 11:59:31 -080051
Mikhail Naganova00883d2019-04-18 12:36:27 -070052 static final class RecordingState {
53 private final int mRiid;
54 private final RecorderDeathHandler mDeathHandler;
55 private boolean mIsActive;
56 private AudioRecordingConfiguration mConfig;
57
58 RecordingState(int riid, RecorderDeathHandler handler) {
59 mRiid = riid;
60 mDeathHandler = handler;
61 }
62
63 RecordingState(AudioRecordingConfiguration config) {
64 mRiid = AudioManager.RECORD_RIID_INVALID;
65 mDeathHandler = null;
66 mConfig = config;
67 }
68
69 int getRiid() {
70 return mRiid;
71 }
72
73 int getPortId() {
74 return mConfig != null ? mConfig.getClientPortId() : -1;
75 }
76
77 AudioRecordingConfiguration getConfig() {
78 return mConfig;
79 }
80
81 boolean hasDeathHandler() {
82 return mDeathHandler != null;
83 }
84
85 boolean isActiveConfiguration() {
86 return mIsActive && mConfig != null;
87 }
88
89 // returns true if status of an active recording has changed
90 boolean setActive(boolean active) {
91 if (mIsActive == active) return false;
92 mIsActive = active;
93 return mConfig != null;
94 }
95
96 // returns true if an active recording has been updated
97 boolean setConfig(AudioRecordingConfiguration config) {
98 if (config.equals(mConfig)) return false;
99 mConfig = config;
100 return mIsActive;
101 }
102
103 void dump(PrintWriter pw) {
104 pw.println("riid " + mRiid + "; active? " + mIsActive);
105 if (mConfig != null) {
106 mConfig.dump(pw);
107 } else {
108 pw.println(" no config");
109 }
110 }
111 }
112 private List<RecordingState> mRecordStates = new ArrayList<RecordingState>();
Jean-Michel Trivid3c71f02015-12-07 11:59:31 -0800113
Jean-Michel Trivi66ffacf2017-02-04 17:25:31 -0800114 private final PackageManager mPackMan;
115
116 RecordingActivityMonitor(Context ctxt) {
Jean-Michel Trivid3c71f02015-12-07 11:59:31 -0800117 RecMonitorClient.sMonitor = this;
Mikhail Naganova00883d2019-04-18 12:36:27 -0700118 RecorderDeathHandler.sMonitor = this;
Jean-Michel Trivi66ffacf2017-02-04 17:25:31 -0800119 mPackMan = ctxt.getPackageManager();
Jean-Michel Trivid3c71f02015-12-07 11:59:31 -0800120 }
121
122 /**
123 * Implementation of android.media.AudioSystem.AudioRecordingCallback
124 */
Mikhail Naganova00883d2019-04-18 12:36:27 -0700125 public void onRecordingConfigurationChanged(int event, int riid, int uid, int session,
126 int source, int portId, boolean silenced,
127 int[] recordingInfo,
Eric Laurentc7a0cdf2018-11-30 12:16:52 -0800128 AudioEffect.Descriptor[] clientEffects,
129 AudioEffect.Descriptor[] effects,
130 int activeSource, String packName) {
Mikhail Naganova00883d2019-04-18 12:36:27 -0700131 final AudioRecordingConfiguration config = createRecordingConfiguration(
132 uid, session, source, recordingInfo,
133 portId, silenced, activeSource, clientEffects, effects);
Jean-Michel Trividd2772a2016-02-17 12:30:52 -0800134 if (MediaRecorder.isSystemOnlyAudioSource(source)) {
Mikhail Naganova00883d2019-04-18 12:36:27 -0700135 // still want to log event, it just won't appear in recording configurations;
136 sEventLogger.log(new RecordingEvent(event, riid, config).printLog(TAG));
Jean-Michel Trividd2772a2016-02-17 12:30:52 -0800137 return;
138 }
Mikhail Naganova00883d2019-04-18 12:36:27 -0700139 dispatchCallbacks(updateSnapshot(event, riid, config));
Eric Laurent07584202019-01-24 18:33:49 -0800140 }
Mikhail Naganova00883d2019-04-18 12:36:27 -0700141
142 /**
143 * Track a recorder provided by the client
144 */
145 public int trackRecorder(IBinder recorder) {
146 if (recorder == null) {
147 Log.e(TAG, "trackRecorder called with null token");
148 return AudioManager.RECORD_RIID_INVALID;
149 }
150 final int newRiid = AudioSystem.newAudioRecorderId();
151 RecorderDeathHandler handler = new RecorderDeathHandler(newRiid, recorder);
152 if (!handler.init()) {
153 // probably means that the AudioRecord has already died
154 return AudioManager.RECORD_RIID_INVALID;
155 }
156 synchronized (mRecordStates) {
157 mRecordStates.add(new RecordingState(newRiid, handler));
158 }
159 // a newly added record is inactive, no change in active configs is possible.
160 return newRiid;
161 }
162
163 /**
164 * Receive an event from the client about a tracked recorder
165 */
166 public void recorderEvent(int riid, int event) {
167 int configEvent = event == AudioManager.RECORDER_STATE_STARTED
168 ? AudioManager.RECORD_CONFIG_EVENT_START :
169 event == AudioManager.RECORDER_STATE_STOPPED
170 ? AudioManager.RECORD_CONFIG_EVENT_STOP : AudioManager.RECORD_CONFIG_EVENT_NONE;
171 if (riid == AudioManager.RECORD_RIID_INVALID
172 || configEvent == AudioManager.RECORD_CONFIG_EVENT_NONE) {
173 sEventLogger.log(new RecordingEvent(event, riid, null).printLog(TAG));
174 return;
175 }
176 dispatchCallbacks(updateSnapshot(configEvent, riid, null));
177 }
178
Mikhail Naganovcfe4c262019-05-09 09:02:47 -0700179 /**
180 * Stop tracking the recorder
181 */
182 public void releaseRecorder(int riid) {
183 dispatchCallbacks(updateSnapshot(AudioManager.RECORD_CONFIG_EVENT_RELEASE, riid, null));
Mikhail Naganova00883d2019-04-18 12:36:27 -0700184 }
185
Eric Laurent07584202019-01-24 18:33:49 -0800186 private void dispatchCallbacks(List<AudioRecordingConfiguration> configs) {
Mikhail Naganova00883d2019-04-18 12:36:27 -0700187 if (configs == null) { // null means "no changes"
188 return;
189 }
Eric Laurent07584202019-01-24 18:33:49 -0800190 synchronized (mClients) {
191 // list of recording configurations for "public consumption". It is only computed if
192 // there are non-system recording activity listeners.
193 final List<AudioRecordingConfiguration> configsPublic = mHasPublicClients
194 ? anonymizeForPublicConsumption(configs) :
195 new ArrayList<AudioRecordingConfiguration>();
Mikhail Naganova00883d2019-04-18 12:36:27 -0700196 for (RecMonitorClient rmc : mClients) {
Eric Laurent07584202019-01-24 18:33:49 -0800197 try {
198 if (rmc.mIsPrivileged) {
199 rmc.mDispatcherCb.dispatchRecordingConfigChange(configs);
200 } else {
201 rmc.mDispatcherCb.dispatchRecordingConfigChange(configsPublic);
Jean-Michel Trivid3c71f02015-12-07 11:59:31 -0800202 }
Eric Laurent07584202019-01-24 18:33:49 -0800203 } catch (RemoteException e) {
204 Log.w(TAG, "Could not call dispatchRecordingConfigChange() on client", e);
Jean-Michel Trivid3c71f02015-12-07 11:59:31 -0800205 }
206 }
207 }
208 }
209
Jean-Michel Trivi66ffacf2017-02-04 17:25:31 -0800210 protected void dump(PrintWriter pw) {
Mikhail Naganova00883d2019-04-18 12:36:27 -0700211 // recorders
Jean-Michel Trivi66ffacf2017-02-04 17:25:31 -0800212 pw.println("\nRecordActivityMonitor dump time: "
213 + DateFormat.getTimeInstance().format(new Date()));
Mikhail Naganova00883d2019-04-18 12:36:27 -0700214 synchronized (mRecordStates) {
215 for (RecordingState state : mRecordStates) {
216 state.dump(pw);
Jean-Michel Trivi66ffacf2017-02-04 17:25:31 -0800217 }
218 }
Jean-Michel Trivi12a86762018-04-24 16:57:49 -0700219 pw.println("\n");
220 // log
221 sEventLogger.dump(pw);
Jean-Michel Trivi66ffacf2017-02-04 17:25:31 -0800222 }
223
Mikhail Naganova00883d2019-04-18 12:36:27 -0700224 private static ArrayList<AudioRecordingConfiguration> anonymizeForPublicConsumption(
Jean-Michel Trivi66ffacf2017-02-04 17:25:31 -0800225 List<AudioRecordingConfiguration> sysConfigs) {
226 ArrayList<AudioRecordingConfiguration> publicConfigs =
227 new ArrayList<AudioRecordingConfiguration>();
228 // only add active anonymized configurations,
229 for (AudioRecordingConfiguration config : sysConfigs) {
230 publicConfigs.add(AudioRecordingConfiguration.anonymizedCopy(config));
231 }
232 return publicConfigs;
233 }
234
Jean-Michel Trivid3c71f02015-12-07 11:59:31 -0800235 void initMonitor() {
236 AudioSystem.setRecordingCallback(this);
237 }
238
Mikhail Naganova00883d2019-04-18 12:36:27 -0700239 void onAudioServerDied() {
240 // Remove all RecordingState entries that do not have a death handler (that means
241 // they are tracked by the Audio Server). If there were active entries among removed,
242 // dispatch active configuration changes.
243 List<AudioRecordingConfiguration> configs = null;
244 synchronized (mRecordStates) {
245 boolean configChanged = false;
246 for (Iterator<RecordingState> it = mRecordStates.iterator(); it.hasNext(); ) {
247 RecordingState state = it.next();
248 if (!state.hasDeathHandler()) {
249 if (state.isActiveConfiguration()) {
250 configChanged = true;
251 sEventLogger.log(new RecordingEvent(
Mikhail Naganovcfe4c262019-05-09 09:02:47 -0700252 AudioManager.RECORD_CONFIG_EVENT_RELEASE,
Mikhail Naganova00883d2019-04-18 12:36:27 -0700253 state.getRiid(), state.getConfig()));
254 }
255 it.remove();
256 }
257 }
258 if (configChanged) {
259 configs = getActiveRecordingConfigurations(true /*isPrivileged*/);
260 }
Eric Laurent07584202019-01-24 18:33:49 -0800261 }
Mikhail Naganova00883d2019-04-18 12:36:27 -0700262 dispatchCallbacks(configs);
Eric Laurent07584202019-01-24 18:33:49 -0800263 }
264
Jean-Michel Trivi66ffacf2017-02-04 17:25:31 -0800265 void registerRecordingCallback(IRecordingConfigDispatcher rcdb, boolean isPrivileged) {
Jean-Michel Trivid3c71f02015-12-07 11:59:31 -0800266 if (rcdb == null) {
267 return;
268 }
Jean-Michel Trivi66ffacf2017-02-04 17:25:31 -0800269 synchronized (mClients) {
270 final RecMonitorClient rmc = new RecMonitorClient(rcdb, isPrivileged);
Jean-Michel Trivid3c71f02015-12-07 11:59:31 -0800271 if (rmc.init()) {
Jean-Michel Trivi66ffacf2017-02-04 17:25:31 -0800272 if (!isPrivileged) {
273 mHasPublicClients = true;
274 }
Jean-Michel Trivid3c71f02015-12-07 11:59:31 -0800275 mClients.add(rmc);
276 }
277 }
278 }
279
280 void unregisterRecordingCallback(IRecordingConfigDispatcher rcdb) {
281 if (rcdb == null) {
282 return;
283 }
Jean-Michel Trivi66ffacf2017-02-04 17:25:31 -0800284 synchronized (mClients) {
Jean-Michel Trivid3c71f02015-12-07 11:59:31 -0800285 final Iterator<RecMonitorClient> clientIterator = mClients.iterator();
Jean-Michel Trivi66ffacf2017-02-04 17:25:31 -0800286 boolean hasPublicClients = false;
Jean-Michel Trivid3c71f02015-12-07 11:59:31 -0800287 while (clientIterator.hasNext()) {
288 RecMonitorClient rmc = clientIterator.next();
289 if (rcdb.equals(rmc.mDispatcherCb)) {
290 rmc.release();
291 clientIterator.remove();
Jean-Michel Trivi66ffacf2017-02-04 17:25:31 -0800292 } else {
293 if (!rmc.mIsPrivileged) {
294 hasPublicClients = true;
295 }
Jean-Michel Trivid3c71f02015-12-07 11:59:31 -0800296 }
297 }
Jean-Michel Trivi66ffacf2017-02-04 17:25:31 -0800298 mHasPublicClients = hasPublicClients;
Jean-Michel Trivid3c71f02015-12-07 11:59:31 -0800299 }
300 }
301
Jean-Michel Trivi66ffacf2017-02-04 17:25:31 -0800302 List<AudioRecordingConfiguration> getActiveRecordingConfigurations(boolean isPrivileged) {
Mikhail Naganova00883d2019-04-18 12:36:27 -0700303 List<AudioRecordingConfiguration> configs = new ArrayList<AudioRecordingConfiguration>();
304 synchronized (mRecordStates) {
305 for (RecordingState state : mRecordStates) {
306 if (state.isActiveConfiguration()) {
307 configs.add(state.getConfig());
308 }
Jean-Michel Trivi66ffacf2017-02-04 17:25:31 -0800309 }
Jean-Michel Trivid3c71f02015-12-07 11:59:31 -0800310 }
Mikhail Naganova00883d2019-04-18 12:36:27 -0700311 // AudioRecordingConfiguration objects never get updated. If config changes,
312 // the reference to the config is set in RecordingState.
313 if (!isPrivileged) {
314 configs = anonymizeForPublicConsumption(configs);
315 }
316 return configs;
Jean-Michel Trivid3c71f02015-12-07 11:59:31 -0800317 }
318
319 /**
Mikhail Naganova00883d2019-04-18 12:36:27 -0700320 * Create a recording configuration from the provided parameters
321 * @param uid
Jean-Michel Trivid3c71f02015-12-07 11:59:31 -0800322 * @param session
323 * @param source
Jean-Michel Trivi33fd8162016-02-22 10:52:41 -0800324 * @param recordingFormat see
Eric Laurentc7a0cdf2018-11-30 12:16:52 -0800325 * {@link AudioSystem.AudioRecordingCallback#onRecordingConfigurationChanged(int, int, int,\
326 int, int, boolean, int[], AudioEffect.Descriptor[], AudioEffect.Descriptor[], int, String)}
Jean-Michel Trivi33fd8162016-02-22 10:52:41 -0800327 * for the definition of the contents of the array
Eric Laurentc7a0cdf2018-11-30 12:16:52 -0800328 * @param portId
329 * @param silenced
330 * @param activeSource
331 * @param clientEffects
332 * @param effects
Mikhail Naganova00883d2019-04-18 12:36:27 -0700333 * @return null a configuration object.
334 */
335 private AudioRecordingConfiguration createRecordingConfiguration(int uid,
336 int session, int source, int[] recordingInfo, int portId, boolean silenced,
337 int activeSource, AudioEffect.Descriptor[] clientEffects,
338 AudioEffect.Descriptor[] effects) {
339 final AudioFormat clientFormat = new AudioFormat.Builder()
340 .setEncoding(recordingInfo[0])
341 // FIXME this doesn't support index-based masks
342 .setChannelMask(recordingInfo[1])
343 .setSampleRate(recordingInfo[2])
344 .build();
345 final AudioFormat deviceFormat = new AudioFormat.Builder()
346 .setEncoding(recordingInfo[3])
347 // FIXME this doesn't support index-based masks
348 .setChannelMask(recordingInfo[4])
349 .setSampleRate(recordingInfo[5])
350 .build();
351 final int patchHandle = recordingInfo[6];
352 final String[] packages = mPackMan.getPackagesForUid(uid);
353 final String packageName;
354 if (packages != null && packages.length > 0) {
355 packageName = packages[0];
356 } else {
357 packageName = "";
358 }
359 return new AudioRecordingConfiguration(uid, session, source,
360 clientFormat, deviceFormat, patchHandle, packageName,
361 portId, silenced, activeSource, clientEffects, effects);
362 }
363
364 /**
365 * Update the internal "view" of the active recording sessions
366 * @param event RECORD_CONFIG_EVENT_...
367 * @param riid
368 * @param config
Jean-Michel Trivif04fab12016-05-19 10:42:35 -0700369 * @return null if the list of active recording sessions has not been modified, a list
Jean-Michel Trivi28ff76b2016-03-02 09:36:30 -0800370 * with the current active configurations otherwise.
Jean-Michel Trivid3c71f02015-12-07 11:59:31 -0800371 */
Mikhail Naganova00883d2019-04-18 12:36:27 -0700372 private List<AudioRecordingConfiguration> updateSnapshot(
373 int event, int riid, AudioRecordingConfiguration config) {
374 List<AudioRecordingConfiguration> configs = null;
375 synchronized (mRecordStates) {
376 int stateIndex = -1;
377 if (riid != AudioManager.RECORD_RIID_INVALID) {
378 stateIndex = findStateByRiid(riid);
379 } else if (config != null) {
380 stateIndex = findStateByPortId(config.getClientPortId());
381 }
382 if (stateIndex == -1) {
383 if (event == AudioManager.RECORD_CONFIG_EVENT_START && config != null) {
384 // First time registration for a recorder tracked by AudioServer.
385 mRecordStates.add(new RecordingState(config));
386 stateIndex = mRecordStates.size() - 1;
Jean-Michel Trivi66ffacf2017-02-04 17:25:31 -0800387 } else {
Mikhail Naganova00883d2019-04-18 12:36:27 -0700388 if (config == null) {
389 // Records tracked by clients must be registered first via trackRecorder.
390 Log.e(TAG, String.format(
391 "Unexpected event %d for riid %d", event, riid));
Jean-Michel Trivi8ab72802016-02-25 16:31:45 -0800392 }
Mikhail Naganova00883d2019-04-18 12:36:27 -0700393 return configs;
Jean-Michel Trivid3c71f02015-12-07 11:59:31 -0800394 }
Mikhail Naganova00883d2019-04-18 12:36:27 -0700395 }
396 final RecordingState state = mRecordStates.get(stateIndex);
397
398 boolean configChanged;
399 switch (event) {
400 case AudioManager.RECORD_CONFIG_EVENT_START:
401 configChanged = state.setActive(true);
Mikhail Naganovcfe4c262019-05-09 09:02:47 -0700402 if (config != null) {
Mikhail Naganova00883d2019-04-18 12:36:27 -0700403 configChanged = state.setConfig(config) || configChanged;
404 }
405 break;
406 case AudioManager.RECORD_CONFIG_EVENT_UPDATE:
407 // For this event config != null
408 configChanged = state.setConfig(config);
409 break;
410 case AudioManager.RECORD_CONFIG_EVENT_STOP:
411 configChanged = state.setActive(false);
412 if (!state.hasDeathHandler()) {
413 // A recorder tracked by AudioServer has to be removed now so it
414 // does not leak. It will be re-registered if recording starts again.
415 mRecordStates.remove(stateIndex);
416 }
417 break;
Mikhail Naganovcfe4c262019-05-09 09:02:47 -0700418 case AudioManager.RECORD_CONFIG_EVENT_RELEASE:
Mikhail Naganova00883d2019-04-18 12:36:27 -0700419 configChanged = state.isActiveConfiguration();
420 mRecordStates.remove(stateIndex);
421 break;
422 default:
423 Log.e(TAG, String.format("Unknown event %d for riid %d / portid %d",
424 event, riid, state.getPortId()));
425 configChanged = false;
Jean-Michel Trivi28ff76b2016-03-02 09:36:30 -0800426 }
427 if (configChanged) {
Mikhail Naganova00883d2019-04-18 12:36:27 -0700428 sEventLogger.log(new RecordingEvent(event, riid, state.getConfig()));
429 configs = getActiveRecordingConfigurations(true /*isPrivileged*/);
Jean-Michel Trivid3c71f02015-12-07 11:59:31 -0800430 }
431 }
Jean-Michel Trivi28ff76b2016-03-02 09:36:30 -0800432 return configs;
Jean-Michel Trivid3c71f02015-12-07 11:59:31 -0800433 }
434
Mikhail Naganova00883d2019-04-18 12:36:27 -0700435 // riid is assumed to be valid
436 private int findStateByRiid(int riid) {
437 synchronized (mRecordStates) {
438 for (int i = 0; i < mRecordStates.size(); i++) {
439 if (mRecordStates.get(i).getRiid() == riid) {
440 return i;
441 }
442 }
443 }
444 return -1;
445 }
446
447 private int findStateByPortId(int portId) {
448 // Lookup by portId is unambiguous only for recordings managed by the Audio Server.
449 synchronized (mRecordStates) {
450 for (int i = 0; i < mRecordStates.size(); i++) {
451 if (!mRecordStates.get(i).hasDeathHandler()
452 && mRecordStates.get(i).getPortId() == portId) {
453 return i;
454 }
455 }
456 }
457 return -1;
458 }
459
Jean-Michel Trivid3c71f02015-12-07 11:59:31 -0800460 /**
461 * Inner class to track clients that want to be notified of recording updates
462 */
463 private final static class RecMonitorClient implements IBinder.DeathRecipient {
464
465 // can afford to be static because only one RecordingActivityMonitor ever instantiated
466 static RecordingActivityMonitor sMonitor;
467
468 final IRecordingConfigDispatcher mDispatcherCb;
Jean-Michel Trivi66ffacf2017-02-04 17:25:31 -0800469 final boolean mIsPrivileged;
Jean-Michel Trivid3c71f02015-12-07 11:59:31 -0800470
Jean-Michel Trivi66ffacf2017-02-04 17:25:31 -0800471 RecMonitorClient(IRecordingConfigDispatcher rcdb, boolean isPrivileged) {
Jean-Michel Trivid3c71f02015-12-07 11:59:31 -0800472 mDispatcherCb = rcdb;
Jean-Michel Trivi66ffacf2017-02-04 17:25:31 -0800473 mIsPrivileged = isPrivileged;
Jean-Michel Trivid3c71f02015-12-07 11:59:31 -0800474 }
475
476 public void binderDied() {
477 Log.w(TAG, "client died");
478 sMonitor.unregisterRecordingCallback(mDispatcherCb);
479 }
480
481 boolean init() {
482 try {
483 mDispatcherCb.asBinder().linkToDeath(this, 0);
484 return true;
485 } catch (RemoteException e) {
486 Log.w(TAG, "Could not link to client death", e);
487 return false;
488 }
489 }
490
491 void release() {
492 mDispatcherCb.asBinder().unlinkToDeath(this, 0);
493 }
494 }
Jean-Michel Trivi12a86762018-04-24 16:57:49 -0700495
Mikhail Naganova00883d2019-04-18 12:36:27 -0700496 private static final class RecorderDeathHandler implements IBinder.DeathRecipient {
497
498 // can afford to be static because only one RecordingActivityMonitor ever instantiated
499 static RecordingActivityMonitor sMonitor;
500
501 final int mRiid;
502 private final IBinder mRecorderToken;
503
504 RecorderDeathHandler(int riid, IBinder recorderToken) {
505 mRiid = riid;
506 mRecorderToken = recorderToken;
507 }
508
509 public void binderDied() {
Mikhail Naganovcfe4c262019-05-09 09:02:47 -0700510 sMonitor.releaseRecorder(mRiid);
Mikhail Naganova00883d2019-04-18 12:36:27 -0700511 }
512
513 boolean init() {
514 try {
515 mRecorderToken.linkToDeath(this, 0);
516 return true;
517 } catch (RemoteException e) {
518 Log.w(TAG, "Could not link to recorder death", e);
519 return false;
520 }
521 }
522 }
523
Jean-Michel Trivi12a86762018-04-24 16:57:49 -0700524 /**
525 * Inner class for recording event logging
526 */
527 private static final class RecordingEvent extends AudioEventLogger.Event {
528 private final int mRecEvent;
Mikhail Naganova00883d2019-04-18 12:36:27 -0700529 private final int mRIId;
Jean-Michel Trivi12a86762018-04-24 16:57:49 -0700530 private final int mClientUid;
531 private final int mSession;
532 private final int mSource;
533 private final String mPackName;
534
Mikhail Naganova00883d2019-04-18 12:36:27 -0700535 RecordingEvent(int event, int riid, AudioRecordingConfiguration config) {
Jean-Michel Trivi12a86762018-04-24 16:57:49 -0700536 mRecEvent = event;
Mikhail Naganova00883d2019-04-18 12:36:27 -0700537 mRIId = riid;
538 if (config != null) {
539 mClientUid = config.getClientUid();
540 mSession = config.getClientAudioSessionId();
541 mSource = config.getClientAudioSource();
542 mPackName = config.getClientPackageName();
543 } else {
544 mClientUid = -1;
545 mSession = -1;
546 mSource = -1;
547 mPackName = null;
548 }
549 }
550
551 private static String recordEventToString(int recEvent) {
552 switch (recEvent) {
553 case AudioManager.RECORD_CONFIG_EVENT_START:
554 return "start";
555 case AudioManager.RECORD_CONFIG_EVENT_UPDATE:
556 return "update";
557 case AudioManager.RECORD_CONFIG_EVENT_STOP:
558 return "stop";
Mikhail Naganovcfe4c262019-05-09 09:02:47 -0700559 case AudioManager.RECORD_CONFIG_EVENT_RELEASE:
560 return "release";
Mikhail Naganova00883d2019-04-18 12:36:27 -0700561 default:
562 return "unknown (" + recEvent + ")";
563 }
Jean-Michel Trivi12a86762018-04-24 16:57:49 -0700564 }
565
566 @Override
567 public String eventToString() {
Mikhail Naganova00883d2019-04-18 12:36:27 -0700568 return new StringBuilder("rec ").append(recordEventToString(mRecEvent))
569 .append(" riid:").append(mRIId)
Jean-Michel Trivi12a86762018-04-24 16:57:49 -0700570 .append(" uid:").append(mClientUid)
571 .append(" session:").append(mSession)
572 .append(" src:").append(MediaRecorder.toLogFriendlyAudioSource(mSource))
573 .append(mPackName == null ? "" : " pack:" + mPackName).toString();
574 }
575 }
576
577 private static final AudioEventLogger sEventLogger = new AudioEventLogger(50,
Mikhail Naganova00883d2019-04-18 12:36:27 -0700578 "recording activity received by AudioService");
Jean-Michel Trivid3c71f02015-12-07 11:59:31 -0800579}