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.media.AudioFormat;
20 import android.media.AudioManager;
21 import android.media.AudioRecordingConfiguration;
22 import android.media.AudioSystem;
23 import android.media.IRecordingConfigDispatcher;
24 import android.media.MediaRecorder;
25 import android.os.IBinder;
26 import android.os.RemoteException;
27 import android.util.Log;
28 
29 import java.util.ArrayList;
30 import java.util.HashMap;
31 import java.util.Iterator;
32 import java.util.List;
33 
34 /**
35  * Class to receive and dispatch updates from AudioSystem about recording configurations.
36  */
37 public final class RecordingActivityMonitor implements AudioSystem.AudioRecordingCallback {
38 
39     public final static String TAG = "AudioService.RecordingActivityMonitor";
40 
41     private ArrayList<RecMonitorClient> mClients = new ArrayList<RecMonitorClient>();
42 
43     private HashMap<Integer, AudioRecordingConfiguration> mRecordConfigs =
44             new HashMap<Integer, AudioRecordingConfiguration>();
45 
RecordingActivityMonitor()46     RecordingActivityMonitor() {
47         RecMonitorClient.sMonitor = this;
48     }
49 
50     /**
51      * Implementation of android.media.AudioSystem.AudioRecordingCallback
52      */
onRecordingConfigurationChanged(int event, int session, int source, int[] recordingInfo)53     public void onRecordingConfigurationChanged(int event, int session, int source,
54             int[] recordingInfo) {
55         if (MediaRecorder.isSystemOnlyAudioSource(source)) {
56             return;
57         }
58         final List<AudioRecordingConfiguration> configs =
59                 updateSnapshot(event, session, source, recordingInfo);
60         if (configs != null){
61             synchronized(mClients) {
62                 final Iterator<RecMonitorClient> clientIterator = mClients.iterator();
63                 while (clientIterator.hasNext()) {
64                     try {
65                         clientIterator.next().mDispatcherCb.dispatchRecordingConfigChange(
66                                 configs);
67                     } catch (RemoteException e) {
68                         Log.w(TAG, "Could not call dispatchRecordingConfigChange() on client", e);
69                     }
70                 }
71             }
72         }
73     }
74 
initMonitor()75     void initMonitor() {
76         AudioSystem.setRecordingCallback(this);
77     }
78 
registerRecordingCallback(IRecordingConfigDispatcher rcdb)79     void registerRecordingCallback(IRecordingConfigDispatcher rcdb) {
80         if (rcdb == null) {
81             return;
82         }
83         synchronized(mClients) {
84             final RecMonitorClient rmc = new RecMonitorClient(rcdb);
85             if (rmc.init()) {
86                 mClients.add(rmc);
87             }
88         }
89     }
90 
unregisterRecordingCallback(IRecordingConfigDispatcher rcdb)91     void unregisterRecordingCallback(IRecordingConfigDispatcher rcdb) {
92         if (rcdb == null) {
93             return;
94         }
95         synchronized(mClients) {
96             final Iterator<RecMonitorClient> clientIterator = mClients.iterator();
97             while (clientIterator.hasNext()) {
98                 RecMonitorClient rmc = clientIterator.next();
99                 if (rcdb.equals(rmc.mDispatcherCb)) {
100                     rmc.release();
101                     clientIterator.remove();
102                     break;
103                 }
104             }
105         }
106     }
107 
getActiveRecordingConfigurations()108     List<AudioRecordingConfiguration> getActiveRecordingConfigurations() {
109         synchronized(mRecordConfigs) {
110             return new ArrayList<AudioRecordingConfiguration>(mRecordConfigs.values());
111         }
112     }
113 
114     /**
115      * Update the internal "view" of the active recording sessions
116      * @param event
117      * @param session
118      * @param source
119      * @param recordingFormat see
120      *     {@link AudioSystem.AudioRecordingCallback#onRecordingConfigurationChanged(int, int, int, int[])}
121      *     for the definition of the contents of the array
122      * @return null if the list of active recording sessions has not been modified, a list
123      *     with the current active configurations otherwise.
124      */
updateSnapshot(int event, int session, int source, int[] recordingInfo)125     private List<AudioRecordingConfiguration> updateSnapshot(int event, int session, int source,
126             int[] recordingInfo) {
127         final boolean configChanged;
128         final ArrayList<AudioRecordingConfiguration> configs;
129         synchronized(mRecordConfigs) {
130             switch (event) {
131             case AudioManager.RECORD_CONFIG_EVENT_STOP:
132                 // return failure if an unknown recording session stopped
133                 configChanged = (mRecordConfigs.remove(new Integer(session)) != null);
134                 break;
135             case AudioManager.RECORD_CONFIG_EVENT_START:
136                 final AudioFormat clientFormat = new AudioFormat.Builder()
137                         .setEncoding(recordingInfo[0])
138                         // FIXME this doesn't support index-based masks
139                         .setChannelMask(recordingInfo[1])
140                         .setSampleRate(recordingInfo[2])
141                         .build();
142                 final AudioFormat deviceFormat = new AudioFormat.Builder()
143                         .setEncoding(recordingInfo[3])
144                         // FIXME this doesn't support index-based masks
145                         .setChannelMask(recordingInfo[4])
146                         .setSampleRate(recordingInfo[5])
147                         .build();
148                 final int patchHandle = recordingInfo[6];
149                 final Integer sessionKey = new Integer(session);
150                 if (mRecordConfigs.containsKey(sessionKey)) {
151                     final AudioRecordingConfiguration updatedConfig =
152                             new AudioRecordingConfiguration(session, source,
153                                     clientFormat, deviceFormat, patchHandle);
154                     if (updatedConfig.equals(mRecordConfigs.get(sessionKey))) {
155                         configChanged = false;
156                     } else {
157                         // config exists but has been modified
158                         mRecordConfigs.remove(sessionKey);
159                         mRecordConfigs.put(sessionKey, updatedConfig);
160                         configChanged = true;
161                     }
162                 } else {
163                     mRecordConfigs.put(sessionKey,
164                             new AudioRecordingConfiguration(session, source,
165                                     clientFormat, deviceFormat, patchHandle));
166                     configChanged = true;
167                 }
168                 break;
169             default:
170                 Log.e(TAG, String.format("Unknown event %d for session %d, source %d",
171                         event, session, source));
172                 configChanged = false;
173             }
174             if (configChanged) {
175                 configs = new ArrayList<AudioRecordingConfiguration>(mRecordConfigs.values());
176             } else {
177                 configs = null;
178             }
179         }
180         return configs;
181     }
182 
183     /**
184      * Inner class to track clients that want to be notified of recording updates
185      */
186     private final static class RecMonitorClient implements IBinder.DeathRecipient {
187 
188         // can afford to be static because only one RecordingActivityMonitor ever instantiated
189         static RecordingActivityMonitor sMonitor;
190 
191         final IRecordingConfigDispatcher mDispatcherCb;
192 
RecMonitorClient(IRecordingConfigDispatcher rcdb)193         RecMonitorClient(IRecordingConfigDispatcher rcdb) {
194             mDispatcherCb = rcdb;
195         }
196 
binderDied()197         public void binderDied() {
198             Log.w(TAG, "client died");
199             sMonitor.unregisterRecordingCallback(mDispatcherCb);
200         }
201 
init()202         boolean init() {
203             try {
204                 mDispatcherCb.asBinder().linkToDeath(this, 0);
205                 return true;
206             } catch (RemoteException e) {
207                 Log.w(TAG, "Could not link to client death", e);
208                 return false;
209             }
210         }
211 
release()212         void release() {
213             mDispatcherCb.asBinder().unlinkToDeath(this, 0);
214         }
215     }
216 }
217