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