• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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 
89         // returns true if status of an active recording has changed
setActive(boolean active)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
setConfig(AudioRecordingConfiguration config)97         boolean setConfig(AudioRecordingConfiguration config) {
98             if (config.equals(mConfig)) return false;
99             mConfig = config;
100             return mIsActive;
101         }
102 
dump(PrintWriter pw)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>();
113 
114     private final PackageManager mPackMan;
115 
RecordingActivityMonitor(Context ctxt)116     RecordingActivityMonitor(Context ctxt) {
117         RecMonitorClient.sMonitor = this;
118         RecorderDeathHandler.sMonitor = this;
119         mPackMan = ctxt.getPackageManager();
120     }
121 
122     /**
123      * Implementation of android.media.AudioSystem.AudioRecordingCallback
124      */
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)125     public void onRecordingConfigurationChanged(int event, int riid, int uid, int session,
126                                                 int source, int portId, boolean silenced,
127                                                 int[] recordingInfo,
128                                                 AudioEffect.Descriptor[] clientEffects,
129                                                 AudioEffect.Descriptor[] effects,
130                                                 int activeSource, String packName) {
131         final AudioRecordingConfiguration config = createRecordingConfiguration(
132                 uid, session, source, recordingInfo,
133                 portId, silenced, activeSource, clientEffects, effects);
134         if (MediaRecorder.isSystemOnlyAudioSource(source)) {
135             // still want to log event, it just won't appear in recording configurations;
136             sEventLogger.log(new RecordingEvent(event, riid, config).printLog(TAG));
137             return;
138         }
139         dispatchCallbacks(updateSnapshot(event, riid, config));
140     }
141 
142     /**
143      * Track a recorder provided by the client
144      */
trackRecorder(IBinder recorder)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      */
recorderEvent(int riid, int event)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 
179     /**
180      * Stop tracking the recorder
181      */
releaseRecorder(int riid)182     public void releaseRecorder(int riid) {
183         dispatchCallbacks(updateSnapshot(AudioManager.RECORD_CONFIG_EVENT_RELEASE, riid, null));
184     }
185 
dispatchCallbacks(List<AudioRecordingConfiguration> configs)186     private void dispatchCallbacks(List<AudioRecordingConfiguration> configs) {
187         if (configs == null) { // null means "no changes"
188             return;
189         }
190         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>();
196             for (RecMonitorClient rmc : mClients) {
197                 try {
198                     if (rmc.mIsPrivileged) {
199                         rmc.mDispatcherCb.dispatchRecordingConfigChange(configs);
200                     } else {
201                         rmc.mDispatcherCb.dispatchRecordingConfigChange(configsPublic);
202                     }
203                 } catch (RemoteException e) {
204                     Log.w(TAG, "Could not call dispatchRecordingConfigChange() on client", e);
205                 }
206             }
207         }
208     }
209 
dump(PrintWriter pw)210     protected void dump(PrintWriter pw) {
211         // recorders
212         pw.println("\nRecordActivityMonitor dump time: "
213                 + DateFormat.getTimeInstance().format(new Date()));
214         synchronized (mRecordStates) {
215             for (RecordingState state : mRecordStates) {
216                 state.dump(pw);
217             }
218         }
219         pw.println("\n");
220         // log
221         sEventLogger.dump(pw);
222     }
223 
anonymizeForPublicConsumption( List<AudioRecordingConfiguration> sysConfigs)224     private static ArrayList<AudioRecordingConfiguration> anonymizeForPublicConsumption(
225             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 
initMonitor()235     void initMonitor() {
236         AudioSystem.setRecordingCallback(this);
237     }
238 
onAudioServerDied()239     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(
252                                         AudioManager.RECORD_CONFIG_EVENT_RELEASE,
253                                         state.getRiid(), state.getConfig()));
254                     }
255                     it.remove();
256                 }
257             }
258             if (configChanged) {
259                 configs = getActiveRecordingConfigurations(true /*isPrivileged*/);
260             }
261         }
262         dispatchCallbacks(configs);
263     }
264 
registerRecordingCallback(IRecordingConfigDispatcher rcdb, boolean isPrivileged)265     void registerRecordingCallback(IRecordingConfigDispatcher rcdb, boolean isPrivileged) {
266         if (rcdb == null) {
267             return;
268         }
269         synchronized (mClients) {
270             final RecMonitorClient rmc = new RecMonitorClient(rcdb, isPrivileged);
271             if (rmc.init()) {
272                 if (!isPrivileged) {
273                     mHasPublicClients = true;
274                 }
275                 mClients.add(rmc);
276             }
277         }
278     }
279 
unregisterRecordingCallback(IRecordingConfigDispatcher rcdb)280     void unregisterRecordingCallback(IRecordingConfigDispatcher rcdb) {
281         if (rcdb == null) {
282             return;
283         }
284         synchronized (mClients) {
285             final Iterator<RecMonitorClient> clientIterator = mClients.iterator();
286             boolean hasPublicClients = false;
287             while (clientIterator.hasNext()) {
288                 RecMonitorClient rmc = clientIterator.next();
289                 if (rcdb.equals(rmc.mDispatcherCb)) {
290                     rmc.release();
291                     clientIterator.remove();
292                 } else {
293                     if (!rmc.mIsPrivileged) {
294                         hasPublicClients = true;
295                     }
296                 }
297             }
298             mHasPublicClients = hasPublicClients;
299         }
300     }
301 
getActiveRecordingConfigurations(boolean isPrivileged)302     List<AudioRecordingConfiguration> getActiveRecordingConfigurations(boolean isPrivileged) {
303         List<AudioRecordingConfiguration> configs = new ArrayList<AudioRecordingConfiguration>();
304         synchronized (mRecordStates) {
305             for (RecordingState state : mRecordStates) {
306                 if (state.isActiveConfiguration()) {
307                     configs.add(state.getConfig());
308                 }
309             }
310         }
311         // 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;
317     }
318 
319     /**
320      * Create a recording configuration from the provided parameters
321      * @param uid
322      * @param session
323      * @param source
324      * @param recordingFormat see
325      *     {@link AudioSystem.AudioRecordingCallback#onRecordingConfigurationChanged(int, int, int,\
326      int, int, boolean, int[], AudioEffect.Descriptor[], AudioEffect.Descriptor[], int, String)}
327      *     for the definition of the contents of the array
328      * @param portId
329      * @param silenced
330      * @param activeSource
331      * @param clientEffects
332      * @param effects
333      * @return null a configuration object.
334      */
createRecordingConfiguration(int uid, int session, int source, int[] recordingInfo, int portId, boolean silenced, int activeSource, AudioEffect.Descriptor[] clientEffects, AudioEffect.Descriptor[] effects)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
369      * @return null if the list of active recording sessions has not been modified, a list
370      *     with the current active configurations otherwise.
371      */
updateSnapshot( int event, int riid, AudioRecordingConfiguration config)372     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;
387                 } else {
388                     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));
392                     }
393                     return configs;
394                 }
395             }
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);
402                     if (config != null) {
403                         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;
418                 case AudioManager.RECORD_CONFIG_EVENT_RELEASE:
419                     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;
426             }
427             if (configChanged) {
428                 sEventLogger.log(new RecordingEvent(event, riid, state.getConfig()));
429                 configs = getActiveRecordingConfigurations(true /*isPrivileged*/);
430             }
431         }
432         return configs;
433     }
434 
435     // riid is assumed to be valid
findStateByRiid(int riid)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 
findStateByPortId(int portId)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 
460     /**
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;
469         final boolean mIsPrivileged;
470 
RecMonitorClient(IRecordingConfigDispatcher rcdb, boolean isPrivileged)471         RecMonitorClient(IRecordingConfigDispatcher rcdb, boolean isPrivileged) {
472             mDispatcherCb = rcdb;
473             mIsPrivileged = isPrivileged;
474         }
475 
binderDied()476         public void binderDied() {
477             Log.w(TAG, "client died");
478             sMonitor.unregisterRecordingCallback(mDispatcherCb);
479         }
480 
init()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 
release()491         void release() {
492             mDispatcherCb.asBinder().unlinkToDeath(this, 0);
493         }
494     }
495 
496     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 
RecorderDeathHandler(int riid, IBinder recorderToken)504         RecorderDeathHandler(int riid, IBinder recorderToken) {
505             mRiid = riid;
506             mRecorderToken = recorderToken;
507         }
508 
binderDied()509         public void binderDied() {
510             sMonitor.releaseRecorder(mRiid);
511         }
512 
init()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 
524     /**
525      * Inner class for recording event logging
526      */
527     private static final class RecordingEvent extends AudioEventLogger.Event {
528         private final int mRecEvent;
529         private final int mRIId;
530         private final int mClientUid;
531         private final int mSession;
532         private final int mSource;
533         private final String mPackName;
534 
RecordingEvent(int event, int riid, AudioRecordingConfiguration config)535         RecordingEvent(int event, int riid, AudioRecordingConfiguration config) {
536             mRecEvent = event;
537             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 
recordEventToString(int recEvent)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";
559                 case AudioManager.RECORD_CONFIG_EVENT_RELEASE:
560                     return "release";
561                 default:
562                     return "unknown (" + recEvent + ")";
563             }
564         }
565 
566         @Override
eventToString()567         public String eventToString() {
568             return new StringBuilder("rec ").append(recordEventToString(mRecEvent))
569                     .append(" riid:").append(mRIId)
570                     .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,
578             "recording activity received by AudioService");
579 }
580