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