blob: 5c5096251e799dc6a277377752167b701ef17ec8 [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
Mikhail Naganov27c6887a2019-07-01 09:09:45 -070089 void release() {
90 if (mDeathHandler != null) {
91 mDeathHandler.release();
92 }
93 }
94
Mikhail Naganova00883d2019-04-18 12:36:27 -070095 // returns true if status of an active recording has changed
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
103 boolean setConfig(AudioRecordingConfiguration config) {
104 if (config.equals(mConfig)) return false;
105 mConfig = config;
106 return mIsActive;
107 }
108
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>();
Jean-Michel Trivid3c71f02015-12-07 11:59:31 -0800119
Jean-Michel Trivi66ffacf2017-02-04 17:25:31 -0800120 private final PackageManager mPackMan;
121
122 RecordingActivityMonitor(Context ctxt) {
Jean-Michel Trivid3c71f02015-12-07 11:59:31 -0800123 RecMonitorClient.sMonitor = this;
Mikhail Naganova00883d2019-04-18 12:36:27 -0700124 RecorderDeathHandler.sMonitor = this;
Jean-Michel Trivi66ffacf2017-02-04 17:25:31 -0800125 mPackMan = ctxt.getPackageManager();
Jean-Michel Trivid3c71f02015-12-07 11:59:31 -0800126 }
127
128 /**
129 * Implementation of android.media.AudioSystem.AudioRecordingCallback
130 */
Mikhail Naganova00883d2019-04-18 12:36:27 -0700131 public void onRecordingConfigurationChanged(int event, int riid, int uid, int session,
132 int source, int portId, boolean silenced,
133 int[] recordingInfo,
Eric Laurentc7a0cdf2018-11-30 12:16:52 -0800134 AudioEffect.Descriptor[] clientEffects,
135 AudioEffect.Descriptor[] effects,
136 int activeSource, String packName) {
Mikhail Naganova00883d2019-04-18 12:36:27 -0700137 final AudioRecordingConfiguration config = createRecordingConfiguration(
138 uid, session, source, recordingInfo,
139 portId, silenced, activeSource, clientEffects, effects);
Jean-Michel Trividd2772a2016-02-17 12:30:52 -0800140 if (MediaRecorder.isSystemOnlyAudioSource(source)) {
Mikhail Naganova00883d2019-04-18 12:36:27 -0700141 // still want to log event, it just won't appear in recording configurations;
142 sEventLogger.log(new RecordingEvent(event, riid, config).printLog(TAG));
Jean-Michel Trividd2772a2016-02-17 12:30:52 -0800143 return;
144 }
Mikhail Naganova00883d2019-04-18 12:36:27 -0700145 dispatchCallbacks(updateSnapshot(event, riid, config));
Eric Laurent07584202019-01-24 18:33:49 -0800146 }
Mikhail Naganova00883d2019-04-18 12:36:27 -0700147
148 /**
149 * Track a recorder provided by the client
150 */
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 */
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
Mikhail Naganovcfe4c262019-05-09 09:02:47 -0700185 /**
186 * Stop tracking the recorder
187 */
188 public void releaseRecorder(int riid) {
189 dispatchCallbacks(updateSnapshot(AudioManager.RECORD_CONFIG_EVENT_RELEASE, riid, null));
Mikhail Naganova00883d2019-04-18 12:36:27 -0700190 }
191
Eric Laurent07584202019-01-24 18:33:49 -0800192 private void dispatchCallbacks(List<AudioRecordingConfiguration> configs) {
Mikhail Naganova00883d2019-04-18 12:36:27 -0700193 if (configs == null) { // null means "no changes"
194 return;
195 }
Eric Laurent07584202019-01-24 18:33:49 -0800196 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>();
Mikhail Naganova00883d2019-04-18 12:36:27 -0700202 for (RecMonitorClient rmc : mClients) {
Eric Laurent07584202019-01-24 18:33:49 -0800203 try {
204 if (rmc.mIsPrivileged) {
205 rmc.mDispatcherCb.dispatchRecordingConfigChange(configs);
206 } else {
207 rmc.mDispatcherCb.dispatchRecordingConfigChange(configsPublic);
Jean-Michel Trivid3c71f02015-12-07 11:59:31 -0800208 }
Eric Laurent07584202019-01-24 18:33:49 -0800209 } catch (RemoteException e) {
210 Log.w(TAG, "Could not call dispatchRecordingConfigChange() on client", e);
Jean-Michel Trivid3c71f02015-12-07 11:59:31 -0800211 }
212 }
213 }
214 }
215
Jean-Michel Trivi66ffacf2017-02-04 17:25:31 -0800216 protected void dump(PrintWriter pw) {
Mikhail Naganova00883d2019-04-18 12:36:27 -0700217 // recorders
Jean-Michel Trivi66ffacf2017-02-04 17:25:31 -0800218 pw.println("\nRecordActivityMonitor dump time: "
219 + DateFormat.getTimeInstance().format(new Date()));
Mikhail Naganova00883d2019-04-18 12:36:27 -0700220 synchronized (mRecordStates) {
221 for (RecordingState state : mRecordStates) {
222 state.dump(pw);
Jean-Michel Trivi66ffacf2017-02-04 17:25:31 -0800223 }
224 }
Jean-Michel Trivi12a86762018-04-24 16:57:49 -0700225 pw.println("\n");
226 // log
227 sEventLogger.dump(pw);
Jean-Michel Trivi66ffacf2017-02-04 17:25:31 -0800228 }
229
Mikhail Naganova00883d2019-04-18 12:36:27 -0700230 private static ArrayList<AudioRecordingConfiguration> anonymizeForPublicConsumption(
Jean-Michel Trivi66ffacf2017-02-04 17:25:31 -0800231 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
Jean-Michel Trivid3c71f02015-12-07 11:59:31 -0800241 void initMonitor() {
242 AudioSystem.setRecordingCallback(this);
243 }
244
Mikhail Naganova00883d2019-04-18 12:36:27 -0700245 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(
Mikhail Naganovcfe4c262019-05-09 09:02:47 -0700258 AudioManager.RECORD_CONFIG_EVENT_RELEASE,
Mikhail Naganova00883d2019-04-18 12:36:27 -0700259 state.getRiid(), state.getConfig()));
260 }
261 it.remove();
262 }
263 }
264 if (configChanged) {
265 configs = getActiveRecordingConfigurations(true /*isPrivileged*/);
266 }
Eric Laurent07584202019-01-24 18:33:49 -0800267 }
Mikhail Naganova00883d2019-04-18 12:36:27 -0700268 dispatchCallbacks(configs);
Eric Laurent07584202019-01-24 18:33:49 -0800269 }
270
Jean-Michel Trivi66ffacf2017-02-04 17:25:31 -0800271 void registerRecordingCallback(IRecordingConfigDispatcher rcdb, boolean isPrivileged) {
Jean-Michel Trivid3c71f02015-12-07 11:59:31 -0800272 if (rcdb == null) {
273 return;
274 }
Jean-Michel Trivi66ffacf2017-02-04 17:25:31 -0800275 synchronized (mClients) {
276 final RecMonitorClient rmc = new RecMonitorClient(rcdb, isPrivileged);
Jean-Michel Trivid3c71f02015-12-07 11:59:31 -0800277 if (rmc.init()) {
Jean-Michel Trivi66ffacf2017-02-04 17:25:31 -0800278 if (!isPrivileged) {
279 mHasPublicClients = true;
280 }
Jean-Michel Trivid3c71f02015-12-07 11:59:31 -0800281 mClients.add(rmc);
282 }
283 }
284 }
285
286 void unregisterRecordingCallback(IRecordingConfigDispatcher rcdb) {
287 if (rcdb == null) {
288 return;
289 }
Jean-Michel Trivi66ffacf2017-02-04 17:25:31 -0800290 synchronized (mClients) {
Jean-Michel Trivid3c71f02015-12-07 11:59:31 -0800291 final Iterator<RecMonitorClient> clientIterator = mClients.iterator();
Jean-Michel Trivi66ffacf2017-02-04 17:25:31 -0800292 boolean hasPublicClients = false;
Jean-Michel Trivid3c71f02015-12-07 11:59:31 -0800293 while (clientIterator.hasNext()) {
294 RecMonitorClient rmc = clientIterator.next();
295 if (rcdb.equals(rmc.mDispatcherCb)) {
296 rmc.release();
297 clientIterator.remove();
Jean-Michel Trivi66ffacf2017-02-04 17:25:31 -0800298 } else {
299 if (!rmc.mIsPrivileged) {
300 hasPublicClients = true;
301 }
Jean-Michel Trivid3c71f02015-12-07 11:59:31 -0800302 }
303 }
Jean-Michel Trivi66ffacf2017-02-04 17:25:31 -0800304 mHasPublicClients = hasPublicClients;
Jean-Michel Trivid3c71f02015-12-07 11:59:31 -0800305 }
306 }
307
Jean-Michel Trivi66ffacf2017-02-04 17:25:31 -0800308 List<AudioRecordingConfiguration> getActiveRecordingConfigurations(boolean isPrivileged) {
Mikhail Naganova00883d2019-04-18 12:36:27 -0700309 List<AudioRecordingConfiguration> configs = new ArrayList<AudioRecordingConfiguration>();
310 synchronized (mRecordStates) {
311 for (RecordingState state : mRecordStates) {
312 if (state.isActiveConfiguration()) {
313 configs.add(state.getConfig());
314 }
Jean-Michel Trivi66ffacf2017-02-04 17:25:31 -0800315 }
Jean-Michel Trivid3c71f02015-12-07 11:59:31 -0800316 }
Mikhail Naganova00883d2019-04-18 12:36:27 -0700317 // 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;
Jean-Michel Trivid3c71f02015-12-07 11:59:31 -0800323 }
324
325 /**
Mikhail Naganova00883d2019-04-18 12:36:27 -0700326 * Create a recording configuration from the provided parameters
327 * @param uid
Jean-Michel Trivid3c71f02015-12-07 11:59:31 -0800328 * @param session
329 * @param source
Jean-Michel Trivi33fd8162016-02-22 10:52:41 -0800330 * @param recordingFormat see
Eric Laurentc7a0cdf2018-11-30 12:16:52 -0800331 * {@link AudioSystem.AudioRecordingCallback#onRecordingConfigurationChanged(int, int, int,\
332 int, int, boolean, int[], AudioEffect.Descriptor[], AudioEffect.Descriptor[], int, String)}
Jean-Michel Trivi33fd8162016-02-22 10:52:41 -0800333 * for the definition of the contents of the array
Eric Laurentc7a0cdf2018-11-30 12:16:52 -0800334 * @param portId
335 * @param silenced
336 * @param activeSource
337 * @param clientEffects
338 * @param effects
Mikhail Naganova00883d2019-04-18 12:36:27 -0700339 * @return null a configuration object.
340 */
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
Jean-Michel Trivif04fab12016-05-19 10:42:35 -0700375 * @return null if the list of active recording sessions has not been modified, a list
Jean-Michel Trivi28ff76b2016-03-02 09:36:30 -0800376 * with the current active configurations otherwise.
Jean-Michel Trivid3c71f02015-12-07 11:59:31 -0800377 */
Mikhail Naganova00883d2019-04-18 12:36:27 -0700378 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;
Jean-Michel Trivi66ffacf2017-02-04 17:25:31 -0800393 } else {
Mikhail Naganova00883d2019-04-18 12:36:27 -0700394 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));
Jean-Michel Trivi8ab72802016-02-25 16:31:45 -0800398 }
Mikhail Naganova00883d2019-04-18 12:36:27 -0700399 return configs;
Jean-Michel Trivid3c71f02015-12-07 11:59:31 -0800400 }
Mikhail Naganova00883d2019-04-18 12:36:27 -0700401 }
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);
Mikhail Naganovcfe4c262019-05-09 09:02:47 -0700408 if (config != null) {
Mikhail Naganova00883d2019-04-18 12:36:27 -0700409 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;
Mikhail Naganovcfe4c262019-05-09 09:02:47 -0700424 case AudioManager.RECORD_CONFIG_EVENT_RELEASE:
Mikhail Naganova00883d2019-04-18 12:36:27 -0700425 configChanged = state.isActiveConfiguration();
Mikhail Naganov27c6887a2019-07-01 09:09:45 -0700426 state.release();
Mikhail Naganova00883d2019-04-18 12:36:27 -0700427 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;
Jean-Michel Trivi28ff76b2016-03-02 09:36:30 -0800433 }
434 if (configChanged) {
Mikhail Naganova00883d2019-04-18 12:36:27 -0700435 sEventLogger.log(new RecordingEvent(event, riid, state.getConfig()));
436 configs = getActiveRecordingConfigurations(true /*isPrivileged*/);
Jean-Michel Trivid3c71f02015-12-07 11:59:31 -0800437 }
438 }
Jean-Michel Trivi28ff76b2016-03-02 09:36:30 -0800439 return configs;
Jean-Michel Trivid3c71f02015-12-07 11:59:31 -0800440 }
441
Mikhail Naganova00883d2019-04-18 12:36:27 -0700442 // riid is assumed to be valid
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
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
Jean-Michel Trivid3c71f02015-12-07 11:59:31 -0800467 /**
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;
Jean-Michel Trivi66ffacf2017-02-04 17:25:31 -0800476 final boolean mIsPrivileged;
Jean-Michel Trivid3c71f02015-12-07 11:59:31 -0800477
Jean-Michel Trivi66ffacf2017-02-04 17:25:31 -0800478 RecMonitorClient(IRecordingConfigDispatcher rcdb, boolean isPrivileged) {
Jean-Michel Trivid3c71f02015-12-07 11:59:31 -0800479 mDispatcherCb = rcdb;
Jean-Michel Trivi66ffacf2017-02-04 17:25:31 -0800480 mIsPrivileged = isPrivileged;
Jean-Michel Trivid3c71f02015-12-07 11:59:31 -0800481 }
482
483 public void binderDied() {
484 Log.w(TAG, "client died");
485 sMonitor.unregisterRecordingCallback(mDispatcherCb);
486 }
487
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
498 void release() {
499 mDispatcherCb.asBinder().unlinkToDeath(this, 0);
500 }
501 }
Jean-Michel Trivi12a86762018-04-24 16:57:49 -0700502
Mikhail Naganova00883d2019-04-18 12:36:27 -0700503 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
511 RecorderDeathHandler(int riid, IBinder recorderToken) {
512 mRiid = riid;
513 mRecorderToken = recorderToken;
514 }
515
516 public void binderDied() {
Mikhail Naganovcfe4c262019-05-09 09:02:47 -0700517 sMonitor.releaseRecorder(mRiid);
Mikhail Naganova00883d2019-04-18 12:36:27 -0700518 }
519
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 }
Mikhail Naganov27c6887a2019-07-01 09:09:45 -0700529
530 void release() {
531 mRecorderToken.unlinkToDeath(this, 0);
532 }
Mikhail Naganova00883d2019-04-18 12:36:27 -0700533 }
534
Jean-Michel Trivi12a86762018-04-24 16:57:49 -0700535 /**
536 * Inner class for recording event logging
537 */
538 private static final class RecordingEvent extends AudioEventLogger.Event {
539 private final int mRecEvent;
Mikhail Naganova00883d2019-04-18 12:36:27 -0700540 private final int mRIId;
Jean-Michel Trivi12a86762018-04-24 16:57:49 -0700541 private final int mClientUid;
542 private final int mSession;
543 private final int mSource;
544 private final String mPackName;
545
Mikhail Naganova00883d2019-04-18 12:36:27 -0700546 RecordingEvent(int event, int riid, AudioRecordingConfiguration config) {
Jean-Michel Trivi12a86762018-04-24 16:57:49 -0700547 mRecEvent = event;
Mikhail Naganova00883d2019-04-18 12:36:27 -0700548 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
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";
Mikhail Naganovcfe4c262019-05-09 09:02:47 -0700570 case AudioManager.RECORD_CONFIG_EVENT_RELEASE:
571 return "release";
Mikhail Naganova00883d2019-04-18 12:36:27 -0700572 default:
573 return "unknown (" + recEvent + ")";
574 }
Jean-Michel Trivi12a86762018-04-24 16:57:49 -0700575 }
576
577 @Override
578 public String eventToString() {
Mikhail Naganova00883d2019-04-18 12:36:27 -0700579 return new StringBuilder("rec ").append(recordEventToString(mRecEvent))
580 .append(" riid:").append(mRIId)
Jean-Michel Trivi12a86762018-04-24 16:57:49 -0700581 .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,
Mikhail Naganova00883d2019-04-18 12:36:27 -0700589 "recording activity received by AudioService");
Jean-Michel Trivid3c71f02015-12-07 11:59:31 -0800590}