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.AudioPlaybackConfiguration;
24 import android.media.AudioRecordingConfiguration;
25 import android.media.AudioSystem;
26 import android.media.IRecordingConfigDispatcher;
27 import android.media.MediaRecorder;
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.HashMap;
37 import java.util.Iterator;
38 import java.util.List;
39 
40 /**
41  * Class to receive and dispatch updates from AudioSystem about recording configurations.
42  */
43 public final class RecordingActivityMonitor implements AudioSystem.AudioRecordingCallback {
44 
45     public final static String TAG = "AudioService.RecordingActivityMonitor";
46 
47     private ArrayList<RecMonitorClient> mClients = new ArrayList<RecMonitorClient>();
48     // a public client is one that needs an anonymized version of the playback configurations, we
49     // keep track of whether there is at least one to know when we need to create the list of
50     // playback configurations that do not contain uid/package name information.
51     private boolean mHasPublicClients = false;
52 
53     private HashMap<Integer, AudioRecordingConfiguration> mRecordConfigs =
54             new HashMap<Integer, AudioRecordingConfiguration>();
55 
56     private final PackageManager mPackMan;
57 
RecordingActivityMonitor(Context ctxt)58     RecordingActivityMonitor(Context ctxt) {
59         RecMonitorClient.sMonitor = this;
60         mPackMan = ctxt.getPackageManager();
61     }
62 
63     /**
64      * Implementation of android.media.AudioSystem.AudioRecordingCallback
65      */
onRecordingConfigurationChanged(int event, int uid, int session, int source, int[] recordingInfo, String packName)66     public void onRecordingConfigurationChanged(int event, int uid, int session, int source,
67             int[] recordingInfo, String packName) {
68         if (MediaRecorder.isSystemOnlyAudioSource(source)) {
69             return;
70         }
71         final List<AudioRecordingConfiguration> configsSystem =
72                 updateSnapshot(event, uid, session, source, recordingInfo);
73         if (configsSystem != null){
74             synchronized (mClients) {
75                 // list of recording configurations for "public consumption". It is only computed if
76                 // there are non-system recording activity listeners.
77                 final List<AudioRecordingConfiguration> configsPublic = mHasPublicClients ?
78                         anonymizeForPublicConsumption(configsSystem) :
79                             new ArrayList<AudioRecordingConfiguration>();
80                 final Iterator<RecMonitorClient> clientIterator = mClients.iterator();
81                 while (clientIterator.hasNext()) {
82                     final RecMonitorClient rmc = clientIterator.next();
83                     try {
84                         if (rmc.mIsPrivileged) {
85                             rmc.mDispatcherCb.dispatchRecordingConfigChange(configsSystem);
86                         } else {
87                             rmc.mDispatcherCb.dispatchRecordingConfigChange(configsPublic);
88                         }
89                     } catch (RemoteException e) {
90                         Log.w(TAG, "Could not call dispatchRecordingConfigChange() on client", e);
91                     }
92                 }
93             }
94         }
95     }
96 
dump(PrintWriter pw)97     protected void dump(PrintWriter pw) {
98         // players
99         pw.println("\nRecordActivityMonitor dump time: "
100                 + DateFormat.getTimeInstance().format(new Date()));
101         synchronized(mRecordConfigs) {
102             for (AudioRecordingConfiguration conf : mRecordConfigs.values()) {
103                 conf.dump(pw);
104             }
105         }
106         pw.println("\n");
107         // log
108         sEventLogger.dump(pw);
109     }
110 
anonymizeForPublicConsumption( List<AudioRecordingConfiguration> sysConfigs)111     private ArrayList<AudioRecordingConfiguration> anonymizeForPublicConsumption(
112             List<AudioRecordingConfiguration> sysConfigs) {
113         ArrayList<AudioRecordingConfiguration> publicConfigs =
114                 new ArrayList<AudioRecordingConfiguration>();
115         // only add active anonymized configurations,
116         for (AudioRecordingConfiguration config : sysConfigs) {
117             publicConfigs.add(AudioRecordingConfiguration.anonymizedCopy(config));
118         }
119         return publicConfigs;
120     }
121 
initMonitor()122     void initMonitor() {
123         AudioSystem.setRecordingCallback(this);
124     }
125 
registerRecordingCallback(IRecordingConfigDispatcher rcdb, boolean isPrivileged)126     void registerRecordingCallback(IRecordingConfigDispatcher rcdb, boolean isPrivileged) {
127         if (rcdb == null) {
128             return;
129         }
130         synchronized (mClients) {
131             final RecMonitorClient rmc = new RecMonitorClient(rcdb, isPrivileged);
132             if (rmc.init()) {
133                 if (!isPrivileged) {
134                     mHasPublicClients = true;
135                 }
136                 mClients.add(rmc);
137             }
138         }
139     }
140 
unregisterRecordingCallback(IRecordingConfigDispatcher rcdb)141     void unregisterRecordingCallback(IRecordingConfigDispatcher rcdb) {
142         if (rcdb == null) {
143             return;
144         }
145         synchronized (mClients) {
146             final Iterator<RecMonitorClient> clientIterator = mClients.iterator();
147             boolean hasPublicClients = false;
148             while (clientIterator.hasNext()) {
149                 RecMonitorClient rmc = clientIterator.next();
150                 if (rcdb.equals(rmc.mDispatcherCb)) {
151                     rmc.release();
152                     clientIterator.remove();
153                 } else {
154                     if (!rmc.mIsPrivileged) {
155                         hasPublicClients = true;
156                     }
157                 }
158             }
159             mHasPublicClients = hasPublicClients;
160         }
161     }
162 
getActiveRecordingConfigurations(boolean isPrivileged)163     List<AudioRecordingConfiguration> getActiveRecordingConfigurations(boolean isPrivileged) {
164         synchronized(mRecordConfigs) {
165             if (isPrivileged) {
166                 return new ArrayList<AudioRecordingConfiguration>(mRecordConfigs.values());
167             } else {
168                 final List<AudioRecordingConfiguration> configsPublic =
169                         anonymizeForPublicConsumption(
170                             new ArrayList<AudioRecordingConfiguration>(mRecordConfigs.values()));
171                 return configsPublic;
172             }
173         }
174     }
175 
176     /**
177      * Update the internal "view" of the active recording sessions
178      * @param event
179      * @param session
180      * @param source
181      * @param recordingFormat see
182      *     {@link AudioSystem.AudioRecordingCallback#onRecordingConfigurationChanged(int, int, int, int[])}
183      *     for the definition of the contents of the array
184      * @return null if the list of active recording sessions has not been modified, a list
185      *     with the current active configurations otherwise.
186      */
updateSnapshot(int event, int uid, int session, int source, int[] recordingInfo)187     private List<AudioRecordingConfiguration> updateSnapshot(int event, int uid, int session,
188             int source, int[] recordingInfo) {
189         final boolean configChanged;
190         final ArrayList<AudioRecordingConfiguration> configs;
191         synchronized(mRecordConfigs) {
192             switch (event) {
193             case AudioManager.RECORD_CONFIG_EVENT_STOP:
194                 // return failure if an unknown recording session stopped
195                 configChanged = (mRecordConfigs.remove(new Integer(session)) != null);
196                 if (configChanged) {
197                     sEventLogger.log(new RecordingEvent(event, uid, session, source, null));
198                 }
199                 break;
200             case AudioManager.RECORD_CONFIG_EVENT_START:
201                 final AudioFormat clientFormat = new AudioFormat.Builder()
202                         .setEncoding(recordingInfo[0])
203                         // FIXME this doesn't support index-based masks
204                         .setChannelMask(recordingInfo[1])
205                         .setSampleRate(recordingInfo[2])
206                         .build();
207                 final AudioFormat deviceFormat = new AudioFormat.Builder()
208                         .setEncoding(recordingInfo[3])
209                         // FIXME this doesn't support index-based masks
210                         .setChannelMask(recordingInfo[4])
211                         .setSampleRate(recordingInfo[5])
212                         .build();
213                 final int patchHandle = recordingInfo[6];
214                 final Integer sessionKey = new Integer(session);
215 
216                 final String[] packages = mPackMan.getPackagesForUid(uid);
217                 final String packageName;
218                 if (packages != null && packages.length > 0) {
219                     packageName = packages[0];
220                 } else {
221                     packageName = "";
222                 }
223                 final AudioRecordingConfiguration updatedConfig =
224                         new AudioRecordingConfiguration(uid, session, source,
225                                 clientFormat, deviceFormat, patchHandle, packageName);
226 
227                 if (mRecordConfigs.containsKey(sessionKey)) {
228                     if (updatedConfig.equals(mRecordConfigs.get(sessionKey))) {
229                         configChanged = false;
230                     } else {
231                         // config exists but has been modified
232                         mRecordConfigs.remove(sessionKey);
233                         mRecordConfigs.put(sessionKey, updatedConfig);
234                         configChanged = true;
235                     }
236                 } else {
237                     mRecordConfigs.put(sessionKey, updatedConfig);
238                     configChanged = true;
239                 }
240                 if (configChanged) {
241                     sEventLogger.log(new RecordingEvent(event, uid, session, source, packageName));
242                 }
243                 break;
244             default:
245                 Log.e(TAG, String.format("Unknown event %d for session %d, source %d",
246                         event, session, source));
247                 configChanged = false;
248             }
249             if (configChanged) {
250                 configs = new ArrayList<AudioRecordingConfiguration>(mRecordConfigs.values());
251             } else {
252                 configs = null;
253             }
254         }
255         return configs;
256     }
257 
258     /**
259      * Inner class to track clients that want to be notified of recording updates
260      */
261     private final static class RecMonitorClient implements IBinder.DeathRecipient {
262 
263         // can afford to be static because only one RecordingActivityMonitor ever instantiated
264         static RecordingActivityMonitor sMonitor;
265 
266         final IRecordingConfigDispatcher mDispatcherCb;
267         final boolean mIsPrivileged;
268 
RecMonitorClient(IRecordingConfigDispatcher rcdb, boolean isPrivileged)269         RecMonitorClient(IRecordingConfigDispatcher rcdb, boolean isPrivileged) {
270             mDispatcherCb = rcdb;
271             mIsPrivileged = isPrivileged;
272         }
273 
binderDied()274         public void binderDied() {
275             Log.w(TAG, "client died");
276             sMonitor.unregisterRecordingCallback(mDispatcherCb);
277         }
278 
init()279         boolean init() {
280             try {
281                 mDispatcherCb.asBinder().linkToDeath(this, 0);
282                 return true;
283             } catch (RemoteException e) {
284                 Log.w(TAG, "Could not link to client death", e);
285                 return false;
286             }
287         }
288 
release()289         void release() {
290             mDispatcherCb.asBinder().unlinkToDeath(this, 0);
291         }
292     }
293 
294     /**
295      * Inner class for recording event logging
296      */
297     private static final class RecordingEvent extends AudioEventLogger.Event {
298         private final int mRecEvent;
299         private final int mClientUid;
300         private final int mSession;
301         private final int mSource;
302         private final String mPackName;
303 
RecordingEvent(int event, int uid, int session, int source, String packName)304         RecordingEvent(int event, int uid, int session, int source, String packName) {
305             mRecEvent = event;
306             mClientUid = uid;
307             mSession = session;
308             mSource = source;
309             mPackName = packName;
310         }
311 
312         @Override
eventToString()313         public String eventToString() {
314             return new StringBuilder("rec ").append(
315                         mRecEvent == AudioManager.RECORD_CONFIG_EVENT_START ? "start" : "stop ")
316                     .append(" uid:").append(mClientUid)
317                     .append(" session:").append(mSession)
318                     .append(" src:").append(MediaRecorder.toLogFriendlyAudioSource(mSource))
319                     .append(mPackName == null ? "" : " pack:" + mPackName).toString();
320         }
321     }
322 
323     private static final AudioEventLogger sEventLogger = new AudioEventLogger(50,
324             "recording activity as reported through AudioSystem.AudioRecordingCallback");
325 }
326