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.content.Context;
20 import android.content.pm.PackageManager;
21 import android.media.AudioDeviceInfo;
22 import android.media.AudioFormat;
23 import android.media.AudioManager;
24 import android.media.AudioRecordingConfiguration;
25 import android.media.AudioSystem;
26 import android.media.IRecordingConfigDispatcher;
27 import android.media.MediaRecorder;
28 import android.media.audiofx.AudioEffect;
29 import android.os.IBinder;
30 import android.os.RemoteException;
31 import android.util.Log;
32 
33 import com.android.server.utils.EventLogger;
34 
35 import java.io.PrintWriter;
36 import java.text.DateFormat;
37 import java.util.ArrayList;
38 import java.util.Date;
39 import java.util.Iterator;
40 import java.util.List;
41 import java.util.concurrent.atomic.AtomicBoolean;
42 import java.util.concurrent.atomic.AtomicInteger;
43 
44 /**
45  * Class to receive and dispatch updates from AudioSystem about recording configurations.
46  */
47 public final class RecordingActivityMonitor implements AudioSystem.AudioRecordingCallback {
48 
49     public final static String TAG = "AudioService.RecordingActivityMonitor";
50 
51     private ArrayList<RecMonitorClient> mClients = new ArrayList<RecMonitorClient>();
52     // a public client is one that needs an anonymized version of the playback configurations, we
53     // keep track of whether there is at least one to know when we need to create the list of
54     // playback configurations that do not contain uid/package name information.
55     private boolean mHasPublicClients = false;
56 
57 
58     // When legacy remote submix device is active, remote submix device should not be fixed and
59     // full volume device. When legacy remote submix device is active, there will be a recording
60     // activity using device with type as {@link AudioSystem.DEVICE_OUT_REMOTE_SUBMIX} and address
61     // as {@link AudioSystem.LEGACY_REMOTE_SUBMIX_ADDRESS}. Cache riid of legacy remote submix
62     // since remote submix state is not cached in mRecordStates.
63     private AtomicInteger mLegacyRemoteSubmixRiid =
64             new AtomicInteger(AudioManager.RECORD_RIID_INVALID);
65     private AtomicBoolean mLegacyRemoteSubmixActive = new AtomicBoolean(false);
66 
67     static final class RecordingState {
68         private final int mRiid;
69         private final RecorderDeathHandler mDeathHandler;
70         private boolean mIsActive;
71         private AudioRecordingConfiguration mConfig;
72 
RecordingState(int riid, RecorderDeathHandler handler)73         RecordingState(int riid, RecorderDeathHandler handler) {
74             mRiid = riid;
75             mDeathHandler = handler;
76         }
77 
RecordingState(AudioRecordingConfiguration config)78         RecordingState(AudioRecordingConfiguration config) {
79             mRiid = AudioManager.RECORD_RIID_INVALID;
80             mDeathHandler = null;
81             mConfig = config;
82         }
83 
getRiid()84         int getRiid() {
85             return mRiid;
86         }
87 
getPortId()88         int getPortId() {
89             return mConfig != null ? mConfig.getClientPortId() : -1;
90         }
91 
getConfig()92         AudioRecordingConfiguration getConfig() {
93             return mConfig;
94         }
95 
hasDeathHandler()96         boolean hasDeathHandler() {
97             return mDeathHandler != null;
98         }
99 
isActiveConfiguration()100         boolean isActiveConfiguration() {
101             return mIsActive && mConfig != null;
102         }
103 
release()104         void release() {
105             if (mDeathHandler != null) {
106                 mDeathHandler.release();
107             }
108         }
109 
110         // returns true if status of an active recording has changed
setActive(boolean active)111         boolean setActive(boolean active) {
112             if (mIsActive == active) return false;
113             mIsActive = active;
114             return mConfig != null;
115         }
116 
117         // returns true if an active recording has been updated
setConfig(AudioRecordingConfiguration config)118         boolean setConfig(AudioRecordingConfiguration config) {
119             if (config.equals(mConfig)) return false;
120             mConfig = config;
121             return mIsActive;
122         }
123 
dump(PrintWriter pw)124         void dump(PrintWriter pw) {
125             pw.println("riid " + mRiid + "; active? " + mIsActive);
126             if (mConfig != null) {
127                 mConfig.dump(pw);
128             } else {
129                 pw.println("  no config");
130             }
131         }
132     }
133     private List<RecordingState> mRecordStates = new ArrayList<RecordingState>();
134 
135     private final PackageManager mPackMan;
136 
RecordingActivityMonitor(Context ctxt)137     RecordingActivityMonitor(Context ctxt) {
138         RecMonitorClient.sMonitor = this;
139         RecorderDeathHandler.sMonitor = this;
140         mPackMan = ctxt.getPackageManager();
141     }
142 
143     /**
144      * Implementation of android.media.AudioSystem.AudioRecordingCallback
145      */
onRecordingConfigurationChanged(int event, int riid, int uid, int session, int source, int portId, boolean silenced, int[] recordingInfo, AudioEffect.Descriptor[] clientEffects, AudioEffect.Descriptor[] effects, int activeSource, String packName)146     public void onRecordingConfigurationChanged(int event, int riid, int uid, int session,
147                                                 int source, int portId, boolean silenced,
148                                                 int[] recordingInfo,
149                                                 AudioEffect.Descriptor[] clientEffects,
150                                                 AudioEffect.Descriptor[] effects,
151                                                 int activeSource, String packName) {
152         final AudioRecordingConfiguration config = createRecordingConfiguration(
153                 uid, session, source, recordingInfo,
154                 portId, silenced, activeSource, clientEffects, effects);
155         if (source == MediaRecorder.AudioSource.REMOTE_SUBMIX
156                 && (event == AudioManager.RECORD_CONFIG_EVENT_START
157                         || event == AudioManager.RECORD_CONFIG_EVENT_UPDATE)) {
158             final AudioDeviceInfo device = config.getAudioDevice();
159             if (device != null
160                     && AudioSystem.LEGACY_REMOTE_SUBMIX_ADDRESS.equals(device.getAddress())) {
161                 mLegacyRemoteSubmixRiid.set(riid);
162                 mLegacyRemoteSubmixActive.set(true);
163             }
164         }
165         if (MediaRecorder.isSystemOnlyAudioSource(source)) {
166             // still want to log event, it just won't appear in recording configurations;
167             sEventLogger.enqueue(new RecordingEvent(event, riid, config).printLog(TAG));
168             return;
169         }
170         dispatchCallbacks(updateSnapshot(event, riid, config));
171     }
172 
173     /**
174      * Track a recorder provided by the client
175      */
trackRecorder(IBinder recorder)176     public int trackRecorder(IBinder recorder) {
177         if (recorder == null) {
178             Log.e(TAG, "trackRecorder called with null token");
179             return AudioManager.RECORD_RIID_INVALID;
180         }
181         final int newRiid = AudioSystem.newAudioRecorderId();
182         RecorderDeathHandler handler = new RecorderDeathHandler(newRiid, recorder);
183         if (!handler.init()) {
184             // probably means that the AudioRecord has already died
185             return AudioManager.RECORD_RIID_INVALID;
186         }
187         synchronized (mRecordStates) {
188             mRecordStates.add(new RecordingState(newRiid, handler));
189         }
190         // a newly added record is inactive, no change in active configs is possible.
191         return newRiid;
192     }
193 
194     /**
195      * Receive an event from the client about a tracked recorder
196      */
recorderEvent(int riid, int event)197     public void recorderEvent(int riid, int event) {
198         if (mLegacyRemoteSubmixRiid.get() == riid) {
199             mLegacyRemoteSubmixActive.set(event == AudioManager.RECORDER_STATE_STARTED);
200         }
201         int configEvent = event == AudioManager.RECORDER_STATE_STARTED
202                 ? AudioManager.RECORD_CONFIG_EVENT_START :
203                 event == AudioManager.RECORDER_STATE_STOPPED
204                 ? AudioManager.RECORD_CONFIG_EVENT_STOP : AudioManager.RECORD_CONFIG_EVENT_NONE;
205         if (riid == AudioManager.RECORD_RIID_INVALID
206                 || configEvent == AudioManager.RECORD_CONFIG_EVENT_NONE) {
207             sEventLogger.enqueue(new RecordingEvent(event, riid, null).printLog(TAG));
208             return;
209         }
210         dispatchCallbacks(updateSnapshot(configEvent, riid, null));
211     }
212 
213     /**
214      * Stop tracking the recorder
215      */
releaseRecorder(int riid)216     public void releaseRecorder(int riid) {
217         dispatchCallbacks(updateSnapshot(AudioManager.RECORD_CONFIG_EVENT_RELEASE, riid, null));
218     }
219 
220     /**
221      * Returns true if a recorder belonging to the app with given uid is active.
222      *
223      * @param uid the app uid
224      * @return true if a recorder is active, false otherwise
225      */
isRecordingActiveForUid(int uid)226     public boolean isRecordingActiveForUid(int uid) {
227         synchronized (mRecordStates) {
228             for (RecordingState state : mRecordStates) {
229                 // Note: isActiveConfiguration() == true => state.getConfig() != null
230                 if (state.isActiveConfiguration() && state.getConfig().getClientUid() == uid
231                         && !state.getConfig().isClientSilenced()) {
232                     return true;
233                 }
234             }
235         }
236         return false;
237     }
238 
dispatchCallbacks(List<AudioRecordingConfiguration> configs)239     private void dispatchCallbacks(List<AudioRecordingConfiguration> configs) {
240         if (configs == null) { // null means "no changes"
241             return;
242         }
243         synchronized (mClients) {
244             // list of recording configurations for "public consumption". It is only computed if
245             // there are non-system recording activity listeners.
246             final List<AudioRecordingConfiguration> configsPublic = mHasPublicClients
247                     ? anonymizeForPublicConsumption(configs) :
248                       new ArrayList<AudioRecordingConfiguration>();
249             for (RecMonitorClient rmc : mClients) {
250                 try {
251                     if (rmc.mIsPrivileged) {
252                         rmc.mDispatcherCb.dispatchRecordingConfigChange(configs);
253                     } else {
254                         rmc.mDispatcherCb.dispatchRecordingConfigChange(configsPublic);
255                     }
256                 } catch (RemoteException e) {
257                     Log.w(TAG, "Could not call dispatchRecordingConfigChange() on client", e);
258                 }
259             }
260         }
261     }
262 
dump(PrintWriter pw)263     protected void dump(PrintWriter pw) {
264         // recorders
265         pw.println("\nRecordActivityMonitor dump time: "
266                 + DateFormat.getTimeInstance().format(new Date()));
267         synchronized (mRecordStates) {
268             for (RecordingState state : mRecordStates) {
269                 state.dump(pw);
270             }
271         }
272         pw.println("\n");
273         // log
274         sEventLogger.dump(pw);
275     }
276 
anonymizeForPublicConsumption( List<AudioRecordingConfiguration> sysConfigs)277     private static ArrayList<AudioRecordingConfiguration> anonymizeForPublicConsumption(
278             List<AudioRecordingConfiguration> sysConfigs) {
279         ArrayList<AudioRecordingConfiguration> publicConfigs =
280                 new ArrayList<AudioRecordingConfiguration>();
281         // only add active anonymized configurations,
282         for (AudioRecordingConfiguration config : sysConfigs) {
283             publicConfigs.add(AudioRecordingConfiguration.anonymizedCopy(config));
284         }
285         return publicConfigs;
286     }
287 
initMonitor()288     void initMonitor() {
289         AudioSystem.setRecordingCallback(this);
290     }
291 
onAudioServerDied()292     void onAudioServerDied() {
293         // Remove all RecordingState entries that do not have a death handler (that means
294         // they are tracked by the Audio Server). If there were active entries among removed,
295         // dispatch active configuration changes.
296         List<AudioRecordingConfiguration> configs = null;
297         synchronized (mRecordStates) {
298             boolean configChanged = false;
299             for (Iterator<RecordingState> it = mRecordStates.iterator(); it.hasNext(); ) {
300                 RecordingState state = it.next();
301                 if (!state.hasDeathHandler()) {
302                     if (state.isActiveConfiguration()) {
303                         configChanged = true;
304                         sEventLogger.enqueue(new RecordingEvent(
305                                         AudioManager.RECORD_CONFIG_EVENT_RELEASE,
306                                         state.getRiid(), state.getConfig()));
307                     }
308                     it.remove();
309                 }
310             }
311             if (configChanged) {
312                 configs = getActiveRecordingConfigurations(true /*isPrivileged*/);
313             }
314         }
315         dispatchCallbacks(configs);
316     }
317 
registerRecordingCallback(IRecordingConfigDispatcher rcdb, boolean isPrivileged)318     void registerRecordingCallback(IRecordingConfigDispatcher rcdb, boolean isPrivileged) {
319         if (rcdb == null) {
320             return;
321         }
322         synchronized (mClients) {
323             final RecMonitorClient rmc = new RecMonitorClient(rcdb, isPrivileged);
324             if (rmc.init()) {
325                 if (!isPrivileged) {
326                     mHasPublicClients = true;
327                 }
328                 mClients.add(rmc);
329             }
330         }
331     }
332 
unregisterRecordingCallback(IRecordingConfigDispatcher rcdb)333     void unregisterRecordingCallback(IRecordingConfigDispatcher rcdb) {
334         if (rcdb == null) {
335             return;
336         }
337         synchronized (mClients) {
338             final Iterator<RecMonitorClient> clientIterator = mClients.iterator();
339             boolean hasPublicClients = false;
340             while (clientIterator.hasNext()) {
341                 RecMonitorClient rmc = clientIterator.next();
342                 if (rcdb.asBinder().equals(rmc.mDispatcherCb.asBinder())) {
343                     rmc.release();
344                     clientIterator.remove();
345                 } else {
346                     if (!rmc.mIsPrivileged) {
347                         hasPublicClients = true;
348                     }
349                 }
350             }
351             mHasPublicClients = hasPublicClients;
352         }
353     }
354 
getActiveRecordingConfigurations(boolean isPrivileged)355     List<AudioRecordingConfiguration> getActiveRecordingConfigurations(boolean isPrivileged) {
356         List<AudioRecordingConfiguration> configs = new ArrayList<AudioRecordingConfiguration>();
357         synchronized (mRecordStates) {
358             for (RecordingState state : mRecordStates) {
359                 if (state.isActiveConfiguration()) {
360                     configs.add(state.getConfig());
361                 }
362             }
363         }
364         // AudioRecordingConfiguration objects never get updated. If config changes,
365         // the reference to the config is set in RecordingState.
366         if (!isPrivileged) {
367             configs = anonymizeForPublicConsumption(configs);
368         }
369         return configs;
370     }
371 
372     /**
373      * Return true if legacy remote submix device is active. Otherwise, return false.
374      */
isLegacyRemoteSubmixActive()375     boolean isLegacyRemoteSubmixActive() {
376         return mLegacyRemoteSubmixActive.get();
377     }
378 
379     /**
380      * Create a recording configuration from the provided parameters
381      * @param uid
382      * @param session
383      * @param source
384      * @param recordingFormat see
385      *     {@link AudioSystem.AudioRecordingCallback#onRecordingConfigurationChanged(int, int, int,\
386      int, int, boolean, int[], AudioEffect.Descriptor[], AudioEffect.Descriptor[], int, String)}
387      *     for the definition of the contents of the array
388      * @param portId
389      * @param silenced
390      * @param activeSource
391      * @param clientEffects
392      * @param effects
393      * @return null a configuration object.
394      */
createRecordingConfiguration(int uid, int session, int source, int[] recordingInfo, int portId, boolean silenced, int activeSource, AudioEffect.Descriptor[] clientEffects, AudioEffect.Descriptor[] effects)395     private AudioRecordingConfiguration createRecordingConfiguration(int uid,
396             int session, int source, int[] recordingInfo, int portId, boolean silenced,
397             int activeSource, AudioEffect.Descriptor[] clientEffects,
398             AudioEffect.Descriptor[] effects) {
399         final AudioFormat clientFormat = new AudioFormat.Builder()
400                 .setEncoding(recordingInfo[0])
401                 // FIXME this doesn't support index-based masks
402                 .setChannelMask(recordingInfo[1])
403                 .setSampleRate(recordingInfo[2])
404                 .build();
405         final AudioFormat deviceFormat = new AudioFormat.Builder()
406                 .setEncoding(recordingInfo[3])
407                 // FIXME this doesn't support index-based masks
408                 .setChannelMask(recordingInfo[4])
409                 .setSampleRate(recordingInfo[5])
410                 .build();
411         final int patchHandle = recordingInfo[6];
412         final String[] packages = mPackMan.getPackagesForUid(uid);
413         final String packageName;
414         if (packages != null && packages.length > 0) {
415             packageName = packages[0];
416         } else {
417             packageName = "";
418         }
419         return new AudioRecordingConfiguration(uid, session, source,
420                 clientFormat, deviceFormat, patchHandle, packageName,
421                 portId, silenced, activeSource, clientEffects, effects);
422     }
423 
424     /**
425      * Update the internal "view" of the active recording sessions
426      * @param event RECORD_CONFIG_EVENT_...
427      * @param riid
428      * @param config
429      * @return null if the list of active recording sessions has not been modified, a list
430      *     with the current active configurations otherwise.
431      */
updateSnapshot( int event, int riid, AudioRecordingConfiguration config)432     private List<AudioRecordingConfiguration> updateSnapshot(
433             int event, int riid, AudioRecordingConfiguration config) {
434         List<AudioRecordingConfiguration> configs = null;
435         synchronized (mRecordStates) {
436             int stateIndex = -1;
437             if (riid != AudioManager.RECORD_RIID_INVALID) {
438                 stateIndex = findStateByRiid(riid);
439             } else if (config != null) {
440                 stateIndex = findStateByPortId(config.getClientPortId());
441             }
442             if (stateIndex == -1) {
443                 if (event == AudioManager.RECORD_CONFIG_EVENT_START && config != null) {
444                     // First time registration for a recorder tracked by AudioServer.
445                     mRecordStates.add(new RecordingState(config));
446                     stateIndex = mRecordStates.size() - 1;
447                 } else {
448                     if (config == null) {
449                         // Records tracked by clients must be registered first via trackRecorder.
450                         Log.e(TAG, String.format(
451                                         "Unexpected event %d for riid %d", event, riid));
452                     }
453                     return configs;
454                 }
455             }
456             final RecordingState state = mRecordStates.get(stateIndex);
457 
458             boolean configChanged;
459             switch (event) {
460                 case AudioManager.RECORD_CONFIG_EVENT_START:
461                     configChanged = state.setActive(true);
462                     if (config != null) {
463                         configChanged = state.setConfig(config) || configChanged;
464                     }
465                     break;
466                 case AudioManager.RECORD_CONFIG_EVENT_UPDATE:
467                     // For this event config != null
468                     configChanged = state.setConfig(config);
469                     break;
470                 case AudioManager.RECORD_CONFIG_EVENT_STOP:
471                     configChanged = state.setActive(false);
472                     if (!state.hasDeathHandler()) {
473                         // A recorder tracked by AudioServer has to be removed now so it
474                         // does not leak. It will be re-registered if recording starts again.
475                         mRecordStates.remove(stateIndex);
476                     }
477                     break;
478                 case AudioManager.RECORD_CONFIG_EVENT_RELEASE:
479                     configChanged = state.isActiveConfiguration();
480                     state.release();
481                     mRecordStates.remove(stateIndex);
482                     break;
483                 default:
484                     Log.e(TAG, String.format("Unknown event %d for riid %d / portid %d",
485                                     event, riid, state.getPortId()));
486                     configChanged = false;
487             }
488             if (configChanged) {
489                 sEventLogger.enqueue(new RecordingEvent(event, riid, state.getConfig()));
490                 configs = getActiveRecordingConfigurations(true /*isPrivileged*/);
491             }
492         }
493         return configs;
494     }
495 
496     // riid is assumed to be valid
findStateByRiid(int riid)497     private int findStateByRiid(int riid) {
498         synchronized (mRecordStates) {
499             for (int i = 0; i < mRecordStates.size(); i++) {
500                 if (mRecordStates.get(i).getRiid() == riid) {
501                     return i;
502                 }
503             }
504         }
505         return -1;
506     }
507 
findStateByPortId(int portId)508     private int findStateByPortId(int portId) {
509         // Lookup by portId is unambiguous only for recordings managed by the Audio Server.
510         synchronized (mRecordStates) {
511             for (int i = 0; i < mRecordStates.size(); i++) {
512                 if (!mRecordStates.get(i).hasDeathHandler()
513                         && mRecordStates.get(i).getPortId() == portId) {
514                     return i;
515                 }
516             }
517         }
518         return -1;
519     }
520 
521     /**
522      * Inner class to track clients that want to be notified of recording updates
523      */
524     private final static class RecMonitorClient implements IBinder.DeathRecipient {
525 
526         // can afford to be static because only one RecordingActivityMonitor ever instantiated
527         static RecordingActivityMonitor sMonitor;
528 
529         final IRecordingConfigDispatcher mDispatcherCb;
530         final boolean mIsPrivileged;
531 
RecMonitorClient(IRecordingConfigDispatcher rcdb, boolean isPrivileged)532         RecMonitorClient(IRecordingConfigDispatcher rcdb, boolean isPrivileged) {
533             mDispatcherCb = rcdb;
534             mIsPrivileged = isPrivileged;
535         }
536 
binderDied()537         public void binderDied() {
538             Log.w(TAG, "client died");
539             sMonitor.unregisterRecordingCallback(mDispatcherCb);
540         }
541 
init()542         boolean init() {
543             try {
544                 mDispatcherCb.asBinder().linkToDeath(this, 0);
545                 return true;
546             } catch (RemoteException e) {
547                 Log.w(TAG, "Could not link to client death", e);
548                 return false;
549             }
550         }
551 
release()552         void release() {
553             mDispatcherCb.asBinder().unlinkToDeath(this, 0);
554         }
555     }
556 
557     private static final class RecorderDeathHandler implements IBinder.DeathRecipient {
558 
559         // can afford to be static because only one RecordingActivityMonitor ever instantiated
560         static RecordingActivityMonitor sMonitor;
561 
562         final int mRiid;
563         private final IBinder mRecorderToken;
564 
RecorderDeathHandler(int riid, IBinder recorderToken)565         RecorderDeathHandler(int riid, IBinder recorderToken) {
566             mRiid = riid;
567             mRecorderToken = recorderToken;
568         }
569 
binderDied()570         public void binderDied() {
571             sMonitor.releaseRecorder(mRiid);
572         }
573 
init()574         boolean init() {
575             try {
576                 mRecorderToken.linkToDeath(this, 0);
577                 return true;
578             } catch (RemoteException e) {
579                 Log.w(TAG, "Could not link to recorder death", e);
580                 return false;
581             }
582         }
583 
release()584         void release() {
585             mRecorderToken.unlinkToDeath(this, 0);
586         }
587     }
588 
589     /**
590      * Inner class for recording event logging
591      */
592     private static final class RecordingEvent extends EventLogger.Event {
593         private final int mRecEvent;
594         private final int mRIId;
595         private final int mClientUid;
596         private final int mSession;
597         private final int mSource;
598         private final String mPackName;
599         private final boolean mSilenced;
600 
RecordingEvent(int event, int riid, AudioRecordingConfiguration config)601         RecordingEvent(int event, int riid, AudioRecordingConfiguration config) {
602             mRecEvent = event;
603             mRIId = riid;
604             if (config != null) {
605                 mClientUid = config.getClientUid();
606                 mSession = config.getClientAudioSessionId();
607                 mSource = config.getClientAudioSource();
608                 mPackName = config.getClientPackageName();
609                 mSilenced = config.isClientSilenced();
610             } else {
611                 mClientUid = -1;
612                 mSession = -1;
613                 mSource = -1;
614                 mPackName = null;
615                 mSilenced = false;
616             }
617         }
618 
recordEventToString(int recEvent)619         private static String recordEventToString(int recEvent) {
620             switch (recEvent) {
621                 case AudioManager.RECORD_CONFIG_EVENT_START:
622                     return "start";
623                 case AudioManager.RECORD_CONFIG_EVENT_UPDATE:
624                     return "update";
625                 case AudioManager.RECORD_CONFIG_EVENT_STOP:
626                     return "stop";
627                 case AudioManager.RECORD_CONFIG_EVENT_RELEASE:
628                     return "release";
629                 default:
630                     return "unknown (" + recEvent + ")";
631             }
632         }
633 
634         @Override
eventToString()635         public String eventToString() {
636             return new StringBuilder("rec ").append(recordEventToString(mRecEvent))
637                     .append(" riid:").append(mRIId)
638                     .append(" uid:").append(mClientUid)
639                     .append(" session:").append(mSession)
640                     .append(" src:").append(MediaRecorder.toLogFriendlyAudioSource(mSource))
641                     .append(mSilenced ? " silenced" : " not silenced")
642                     .append(mPackName == null ? "" : " pack:" + mPackName).toString();
643         }
644     }
645 
646     private static final EventLogger
647             sEventLogger = new EventLogger(50,
648             "recording activity received by AudioService");
649 }
650