blob: 86dcd0f1f0f2ea5a012c325b7ea9e3f68c916233 [file] [log] [blame]
Jean-Michel Trivid3c71f02015-12-07 11:59:31 -08001/*
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
Jean-Michel Trivi33fd8162016-02-22 10:52:41 -080019import android.media.AudioFormat;
Jean-Michel Trivid3c71f02015-12-07 11:59:31 -080020import android.media.AudioManager;
21import android.media.AudioRecordConfiguration;
22import android.media.AudioSystem;
23import android.media.IRecordingConfigDispatcher;
Jean-Michel Trividd2772a2016-02-17 12:30:52 -080024import android.media.MediaRecorder;
Jean-Michel Trivid3c71f02015-12-07 11:59:31 -080025import 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 */
Jean-Michel Trivi33fd8162016-02-22 10:52:41 -080052 public void onRecordingConfigurationChanged(int event, int session, int source,
Jean-Michel Trivi8ab72802016-02-25 16:31:45 -080053 int[] recordingInfo) {
Jean-Michel Trividd2772a2016-02-17 12:30:52 -080054 if (MediaRecorder.isSystemOnlyAudioSource(source)) {
55 return;
56 }
Jean-Michel Trivi28ff76b2016-03-02 09:36:30 -080057 final AudioRecordConfiguration[] configs =
58 updateSnapshot(event, session, source, recordingInfo);
59 if (configs != null){
Jean-Michel Trivid3c71f02015-12-07 11:59:31 -080060 synchronized(mClients) {
Jean-Michel Trivi28ff76b2016-03-02 09:36:30 -080061 final Iterator<RecMonitorClient> clientIterator = mClients.iterator();
Jean-Michel Trivid3c71f02015-12-07 11:59:31 -080062 while (clientIterator.hasNext()) {
63 try {
Jean-Michel Trivi28ff76b2016-03-02 09:36:30 -080064 clientIterator.next().mDispatcherCb.dispatchRecordingConfigChange(
65 configs);
Jean-Michel Trivid3c71f02015-12-07 11:59:31 -080066 } catch (RemoteException e) {
67 Log.w(TAG, "Could not call dispatchRecordingConfigChange() on client", e);
68 }
69 }
70 }
71 }
72 }
73
74 void initMonitor() {
75 AudioSystem.setRecordingCallback(this);
76 }
77
78 void registerRecordingCallback(IRecordingConfigDispatcher rcdb) {
79 if (rcdb == null) {
80 return;
81 }
82 synchronized(mClients) {
83 final RecMonitorClient rmc = new RecMonitorClient(rcdb);
84 if (rmc.init()) {
85 mClients.add(rmc);
86 }
87 }
88 }
89
90 void unregisterRecordingCallback(IRecordingConfigDispatcher rcdb) {
91 if (rcdb == null) {
92 return;
93 }
94 synchronized(mClients) {
95 final Iterator<RecMonitorClient> clientIterator = mClients.iterator();
96 while (clientIterator.hasNext()) {
97 RecMonitorClient rmc = clientIterator.next();
98 if (rcdb.equals(rmc.mDispatcherCb)) {
99 rmc.release();
100 clientIterator.remove();
101 break;
102 }
103 }
104 }
105 }
106
107 AudioRecordConfiguration[] getActiveRecordConfigurations() {
108 synchronized(mRecordConfigs) {
109 return mRecordConfigs.values().toArray(new AudioRecordConfiguration[0]);
110 }
111 }
112
113 /**
114 * Update the internal "view" of the active recording sessions
115 * @param event
116 * @param session
117 * @param source
Jean-Michel Trivi33fd8162016-02-22 10:52:41 -0800118 * @param recordingFormat see
119 * {@link AudioSystem.AudioRecordingCallback#onRecordingConfigurationChanged(int, int, int, int[])}
120 * for the definition of the contents of the array
Jean-Michel Trivi28ff76b2016-03-02 09:36:30 -0800121 * @return null if the list of active recording sessions has not been modified, an array
122 * with the current active configurations otherwise.
Jean-Michel Trivid3c71f02015-12-07 11:59:31 -0800123 */
Jean-Michel Trivi28ff76b2016-03-02 09:36:30 -0800124 private AudioRecordConfiguration[] updateSnapshot(int event, int session, int source,
125 int[] recordingInfo) {
126 final boolean configChanged;
127 final AudioRecordConfiguration[] configs;
Jean-Michel Trivid3c71f02015-12-07 11:59:31 -0800128 synchronized(mRecordConfigs) {
129 switch (event) {
130 case AudioManager.RECORD_CONFIG_EVENT_STOP:
131 // return failure if an unknown recording session stopped
Jean-Michel Trivi28ff76b2016-03-02 09:36:30 -0800132 configChanged = (mRecordConfigs.remove(new Integer(session)) != null);
133 break;
Jean-Michel Trivid3c71f02015-12-07 11:59:31 -0800134 case AudioManager.RECORD_CONFIG_EVENT_START:
Jean-Michel Trivi33fd8162016-02-22 10:52:41 -0800135 final AudioFormat clientFormat = new AudioFormat.Builder()
Jean-Michel Trivi8ab72802016-02-25 16:31:45 -0800136 .setEncoding(recordingInfo[0])
Jean-Michel Trivi33fd8162016-02-22 10:52:41 -0800137 // FIXME this doesn't support index-based masks
Jean-Michel Trivi8ab72802016-02-25 16:31:45 -0800138 .setChannelMask(recordingInfo[1])
139 .setSampleRate(recordingInfo[2])
Jean-Michel Trivi33fd8162016-02-22 10:52:41 -0800140 .build();
141 final AudioFormat deviceFormat = new AudioFormat.Builder()
Jean-Michel Trivi8ab72802016-02-25 16:31:45 -0800142 .setEncoding(recordingInfo[3])
Jean-Michel Trivi33fd8162016-02-22 10:52:41 -0800143 // FIXME this doesn't support index-based masks
Jean-Michel Trivi8ab72802016-02-25 16:31:45 -0800144 .setChannelMask(recordingInfo[4])
145 .setSampleRate(recordingInfo[5])
Jean-Michel Trivi33fd8162016-02-22 10:52:41 -0800146 .build();
Jean-Michel Trivi8ab72802016-02-25 16:31:45 -0800147 final int patchHandle = recordingInfo[6];
148 final Integer sessionKey = new Integer(session);
149 if (mRecordConfigs.containsKey(sessionKey)) {
150 final AudioRecordConfiguration updatedConfig =
Jean-Michel Trivi33fd8162016-02-22 10:52:41 -0800151 new AudioRecordConfiguration(session, source,
Jean-Michel Trivi8ab72802016-02-25 16:31:45 -0800152 clientFormat, deviceFormat, patchHandle);
153 if (updatedConfig.equals(mRecordConfigs.get(sessionKey))) {
Jean-Michel Trivi28ff76b2016-03-02 09:36:30 -0800154 configChanged = false;
Jean-Michel Trivi8ab72802016-02-25 16:31:45 -0800155 } else {
156 // config exists but has been modified
157 mRecordConfigs.remove(sessionKey);
158 mRecordConfigs.put(sessionKey, updatedConfig);
Jean-Michel Trivi28ff76b2016-03-02 09:36:30 -0800159 configChanged = true;
Jean-Michel Trivi8ab72802016-02-25 16:31:45 -0800160 }
161 } else {
162 mRecordConfigs.put(sessionKey,
163 new AudioRecordConfiguration(session, source,
164 clientFormat, deviceFormat, patchHandle));
Jean-Michel Trivi28ff76b2016-03-02 09:36:30 -0800165 configChanged = true;
Jean-Michel Trivid3c71f02015-12-07 11:59:31 -0800166 }
Jean-Michel Trivi28ff76b2016-03-02 09:36:30 -0800167 break;
Jean-Michel Trivid3c71f02015-12-07 11:59:31 -0800168 default:
169 Log.e(TAG, String.format("Unknown event %d for session %d, source %d",
170 event, session, source));
Jean-Michel Trivi28ff76b2016-03-02 09:36:30 -0800171 configChanged = false;
172 }
173 if (configChanged) {
174 configs = mRecordConfigs.values().toArray(new AudioRecordConfiguration[0]);
175 } else {
176 configs = null;
Jean-Michel Trivid3c71f02015-12-07 11:59:31 -0800177 }
178 }
Jean-Michel Trivi28ff76b2016-03-02 09:36:30 -0800179 return configs;
Jean-Michel Trivid3c71f02015-12-07 11:59:31 -0800180 }
181
182 /**
183 * Inner class to track clients that want to be notified of recording updates
184 */
185 private final static class RecMonitorClient implements IBinder.DeathRecipient {
186
187 // can afford to be static because only one RecordingActivityMonitor ever instantiated
188 static RecordingActivityMonitor sMonitor;
189
190 final IRecordingConfigDispatcher mDispatcherCb;
191
192 RecMonitorClient(IRecordingConfigDispatcher rcdb) {
193 mDispatcherCb = rcdb;
194 }
195
196 public void binderDied() {
197 Log.w(TAG, "client died");
198 sMonitor.unregisterRecordingCallback(mDispatcherCb);
199 }
200
201 boolean init() {
202 try {
203 mDispatcherCb.asBinder().linkToDeath(this, 0);
204 return true;
205 } catch (RemoteException e) {
206 Log.w(TAG, "Could not link to client death", e);
207 return false;
208 }
209 }
210
211 void release() {
212 mDispatcherCb.asBinder().unlinkToDeath(this, 0);
213 }
214 }
215}