1 /*
2  * Copyright (C) 2018 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 android.media;
18 
19 import android.annotation.CallbackExecutor;
20 import android.annotation.NonNull;
21 import android.annotation.Nullable;
22 import android.content.Context;
23 import android.os.Binder;
24 import android.os.Handler;
25 import android.os.HandlerThread;
26 import android.os.IBinder;
27 import android.os.Looper;
28 import android.os.Message;
29 import android.os.RemoteException;
30 import android.os.ServiceManager;
31 import android.util.Log;
32 
33 import com.android.internal.annotations.GuardedBy;
34 
35 import java.util.ArrayList;
36 import java.util.LinkedList;
37 import java.util.List;
38 import java.util.concurrent.Executor;
39 
40 /**
41  * Implementation of AudioRecordingMonitor interface.
42  * @hide
43  */
44 public class AudioRecordingMonitorImpl implements AudioRecordingMonitor {
45 
46     private static final String TAG = "android.media.AudioRecordingMonitor";
47 
48     private static IAudioService sService; //lazy initialization, use getService()
49 
50     private final AudioRecordingMonitorClient mClient;
51 
AudioRecordingMonitorImpl(@onNull AudioRecordingMonitorClient client)52     AudioRecordingMonitorImpl(@NonNull AudioRecordingMonitorClient client) {
53         mClient = client;
54     }
55 
56     /**
57      * Register a callback to be notified of audio capture changes via a
58      * {@link AudioManager.AudioRecordingCallback}. A callback is received when the capture path
59      * configuration changes (pre-processing, format, sampling rate...) or capture is
60      * silenced/unsilenced by the system.
61      * @param executor {@link Executor} to handle the callbacks.
62      * @param cb non-null callback to register
63      */
registerAudioRecordingCallback(@onNull @allbackExecutor Executor executor, @NonNull AudioManager.AudioRecordingCallback cb)64     public void registerAudioRecordingCallback(@NonNull @CallbackExecutor Executor executor,
65             @NonNull AudioManager.AudioRecordingCallback cb) {
66         if (cb == null) {
67             throw new IllegalArgumentException("Illegal null AudioRecordingCallback");
68         }
69         if (executor == null) {
70             throw new IllegalArgumentException("Illegal null Executor");
71         }
72         synchronized (mRecordCallbackLock) {
73             // check if eventCallback already in list
74             for (AudioRecordingCallbackInfo arci : mRecordCallbackList) {
75                 if (arci.mCb == cb) {
76                     throw new IllegalArgumentException(
77                             "AudioRecordingCallback already registered");
78                 }
79             }
80             beginRecordingCallbackHandling();
81             mRecordCallbackList.add(new AudioRecordingCallbackInfo(executor, cb));
82         }
83     }
84 
85     /**
86      * Unregister an audio recording callback previously registered with
87      * {@link #registerAudioRecordingCallback(Executor, AudioManager.AudioRecordingCallback)}.
88      * @param cb non-null callback to unregister
89      */
unregisterAudioRecordingCallback(@onNull AudioManager.AudioRecordingCallback cb)90     public void unregisterAudioRecordingCallback(@NonNull AudioManager.AudioRecordingCallback cb) {
91         if (cb == null) {
92             throw new IllegalArgumentException("Illegal null AudioRecordingCallback argument");
93         }
94 
95         synchronized (mRecordCallbackLock) {
96             for (AudioRecordingCallbackInfo arci : mRecordCallbackList) {
97                 if (arci.mCb == cb) {
98                     // ok to remove while iterating over list as we exit iteration
99                     mRecordCallbackList.remove(arci);
100                     if (mRecordCallbackList.size() == 0) {
101                         endRecordingCallbackHandling();
102                     }
103                     return;
104                 }
105             }
106             throw new IllegalArgumentException("AudioRecordingCallback was not registered");
107         }
108     }
109 
110     /**
111      * Returns the current active audio recording for this audio recorder.
112      * @return a valid {@link AudioRecordingConfiguration} if this recorder is active
113      * or null otherwise.
114      * @see AudioRecordingConfiguration
115      */
getActiveRecordingConfiguration()116     public @Nullable AudioRecordingConfiguration getActiveRecordingConfiguration() {
117         final IAudioService service = getService();
118         try {
119             List<AudioRecordingConfiguration> configs = service.getActiveRecordingConfigurations();
120             return getMyConfig(configs);
121         } catch (RemoteException e) {
122             throw e.rethrowFromSystemServer();
123         }
124     }
125 
126     private static class AudioRecordingCallbackInfo {
127         final AudioManager.AudioRecordingCallback mCb;
128         final Executor mExecutor;
AudioRecordingCallbackInfo(Executor e, AudioManager.AudioRecordingCallback cb)129         AudioRecordingCallbackInfo(Executor e, AudioManager.AudioRecordingCallback cb) {
130             mExecutor = e;
131             mCb = cb;
132         }
133     }
134 
135     private static final int MSG_RECORDING_CONFIG_CHANGE = 1;
136 
137     private final Object mRecordCallbackLock = new Object();
138     @GuardedBy("mRecordCallbackLock")
139     @NonNull private LinkedList<AudioRecordingCallbackInfo> mRecordCallbackList =
140             new LinkedList<AudioRecordingCallbackInfo>();
141     @GuardedBy("mRecordCallbackLock")
142     private @Nullable HandlerThread mRecordingCallbackHandlerThread;
143     @GuardedBy("mRecordCallbackLock")
144     private @Nullable volatile Handler mRecordingCallbackHandler;
145 
146     @GuardedBy("mRecordCallbackLock")
147     private final IRecordingConfigDispatcher mRecordingCallback =
148             new IRecordingConfigDispatcher.Stub() {
149         @Override
150         public void dispatchRecordingConfigChange(List<AudioRecordingConfiguration> configs) {
151             AudioRecordingConfiguration config = getMyConfig(configs);
152             if (config != null) {
153                 synchronized (mRecordCallbackLock) {
154                     if (mRecordingCallbackHandler != null) {
155                         final Message m = mRecordingCallbackHandler.obtainMessage(
156                                               MSG_RECORDING_CONFIG_CHANGE/*what*/, config /*obj*/);
157                         mRecordingCallbackHandler.sendMessage(m);
158                     }
159                 }
160             }
161         }
162     };
163 
164     @GuardedBy("mRecordCallbackLock")
beginRecordingCallbackHandling()165     private void beginRecordingCallbackHandling() {
166         if (mRecordingCallbackHandlerThread == null) {
167             mRecordingCallbackHandlerThread = new HandlerThread(TAG + ".RecordingCallback");
168             mRecordingCallbackHandlerThread.start();
169             final Looper looper = mRecordingCallbackHandlerThread.getLooper();
170             if (looper != null) {
171                 mRecordingCallbackHandler = new Handler(looper) {
172                     @Override
173                     public void handleMessage(Message msg) {
174                         switch (msg.what) {
175                             case MSG_RECORDING_CONFIG_CHANGE: {
176                                 if (msg.obj == null) {
177                                     return;
178                                 }
179                                 ArrayList<AudioRecordingConfiguration> configs =
180                                         new ArrayList<AudioRecordingConfiguration>();
181                                 configs.add((AudioRecordingConfiguration) msg.obj);
182 
183                                 final LinkedList<AudioRecordingCallbackInfo> cbInfoList;
184                                 synchronized (mRecordCallbackLock) {
185                                     if (mRecordCallbackList.size() == 0) {
186                                         return;
187                                     }
188                                     cbInfoList = new LinkedList<AudioRecordingCallbackInfo>(
189                                         mRecordCallbackList);
190                                 }
191 
192                                 final long identity = Binder.clearCallingIdentity();
193                                 try {
194                                     for (AudioRecordingCallbackInfo cbi : cbInfoList) {
195                                         cbi.mExecutor.execute(() ->
196                                                 cbi.mCb.onRecordingConfigChanged(configs));
197                                     }
198                                 } finally {
199                                     Binder.restoreCallingIdentity(identity);
200                                 }
201                             } break;
202                             default:
203                                 Log.e(TAG, "Unknown event " + msg.what);
204                                 break;
205                         }
206                     }
207                 };
208                 final IAudioService service = getService();
209                 try {
210                     service.registerRecordingCallback(mRecordingCallback);
211                 } catch (RemoteException e) {
212                     throw e.rethrowFromSystemServer();
213                 }
214             }
215         }
216     }
217 
218     @GuardedBy("mRecordCallbackLock")
endRecordingCallbackHandling()219     private void endRecordingCallbackHandling() {
220         if (mRecordingCallbackHandlerThread != null) {
221             final IAudioService service = getService();
222             try {
223                 service.unregisterRecordingCallback(mRecordingCallback);
224             } catch (RemoteException e) {
225                 throw e.rethrowFromSystemServer();
226             }
227             mRecordingCallbackHandlerThread.quit();
228             mRecordingCallbackHandlerThread = null;
229         }
230     }
231 
getMyConfig(List<AudioRecordingConfiguration> configs)232     AudioRecordingConfiguration getMyConfig(List<AudioRecordingConfiguration> configs) {
233         int portId = mClient.getPortId();
234         for (AudioRecordingConfiguration config : configs) {
235             if (config.getClientPortId() == portId) {
236                 return config;
237             }
238         }
239         return null;
240     }
241 
getService()242     private static IAudioService getService() {
243         if (sService != null) {
244             return sService;
245         }
246         IBinder b = ServiceManager.getService(Context.AUDIO_SERVICE);
247         sService = IAudioService.Stub.asInterface(b);
248         return sService;
249     }
250 }
251