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
17package com.android.server.audio;
18
19import android.media.AudioFormat;
20import android.media.AudioManager;
21import android.media.AudioRecordingConfiguration;
22import android.media.AudioSystem;
23import android.media.IRecordingConfigDispatcher;
24import android.media.MediaRecorder;
25import android.os.IBinder;
26import android.os.RemoteException;
27import android.util.Log;
28
29import java.util.ArrayList;
30import java.util.HashMap;
31import java.util.Iterator;
32import java.util.List;
33
34/**
35 * Class to receive and dispatch updates from AudioSystem about recording configurations.
36 */
37public 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
46    RecordingActivityMonitor() {
47        RecMonitorClient.sMonitor = this;
48    }
49
50    /**
51     * Implementation of android.media.AudioSystem.AudioRecordingCallback
52     */
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
75    void initMonitor() {
76        AudioSystem.setRecordingCallback(this);
77    }
78
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
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
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     */
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
193        RecMonitorClient(IRecordingConfigDispatcher rcdb) {
194            mDispatcherCb = rcdb;
195        }
196
197        public void binderDied() {
198            Log.w(TAG, "client died");
199            sMonitor.unregisterRecordingCallback(mDispatcherCb);
200        }
201
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
212        void release() {
213            mDispatcherCb.asBinder().unlinkToDeath(this, 0);
214        }
215    }
216}
217