Home | History | Annotate | Download | only in audio
      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.AudioFormat;
     22 import android.media.AudioManager;
     23 import android.media.AudioPlaybackConfiguration;
     24 import android.media.AudioRecordingConfiguration;
     25 import android.media.AudioSystem;
     26 import android.media.IRecordingConfigDispatcher;
     27 import android.media.MediaRecorder;
     28 import android.os.IBinder;
     29 import android.os.RemoteException;
     30 import android.util.Log;
     31 
     32 import java.io.PrintWriter;
     33 import java.text.DateFormat;
     34 import java.util.ArrayList;
     35 import java.util.Date;
     36 import java.util.HashMap;
     37 import java.util.Iterator;
     38 import java.util.List;
     39 
     40 /**
     41  * Class to receive and dispatch updates from AudioSystem about recording configurations.
     42  */
     43 public final class RecordingActivityMonitor implements AudioSystem.AudioRecordingCallback {
     44 
     45     public final static String TAG = "AudioService.RecordingActivityMonitor";
     46 
     47     private ArrayList<RecMonitorClient> mClients = new ArrayList<RecMonitorClient>();
     48     // a public client is one that needs an anonymized version of the playback configurations, we
     49     // keep track of whether there is at least one to know when we need to create the list of
     50     // playback configurations that do not contain uid/package name information.
     51     private boolean mHasPublicClients = false;
     52 
     53     private HashMap<Integer, AudioRecordingConfiguration> mRecordConfigs =
     54             new HashMap<Integer, AudioRecordingConfiguration>();
     55 
     56     private final PackageManager mPackMan;
     57 
     58     RecordingActivityMonitor(Context ctxt) {
     59         RecMonitorClient.sMonitor = this;
     60         mPackMan = ctxt.getPackageManager();
     61     }
     62 
     63     /**
     64      * Implementation of android.media.AudioSystem.AudioRecordingCallback
     65      */
     66     public void onRecordingConfigurationChanged(int event, int uid, int session, int source,
     67             int[] recordingInfo, String packName) {
     68         if (MediaRecorder.isSystemOnlyAudioSource(source)) {
     69             return;
     70         }
     71         final List<AudioRecordingConfiguration> configsSystem =
     72                 updateSnapshot(event, uid, session, source, recordingInfo);
     73         if (configsSystem != null){
     74             synchronized (mClients) {
     75                 // list of recording configurations for "public consumption". It is only computed if
     76                 // there are non-system recording activity listeners.
     77                 final List<AudioRecordingConfiguration> configsPublic = mHasPublicClients ?
     78                         anonymizeForPublicConsumption(configsSystem) :
     79                             new ArrayList<AudioRecordingConfiguration>();
     80                 final Iterator<RecMonitorClient> clientIterator = mClients.iterator();
     81                 while (clientIterator.hasNext()) {
     82                     final RecMonitorClient rmc = clientIterator.next();
     83                     try {
     84                         if (rmc.mIsPrivileged) {
     85                             rmc.mDispatcherCb.dispatchRecordingConfigChange(configsSystem);
     86                         } else {
     87                             rmc.mDispatcherCb.dispatchRecordingConfigChange(configsPublic);
     88                         }
     89                     } catch (RemoteException e) {
     90                         Log.w(TAG, "Could not call dispatchRecordingConfigChange() on client", e);
     91                     }
     92                 }
     93             }
     94         }
     95     }
     96 
     97     protected void dump(PrintWriter pw) {
     98         // players
     99         pw.println("\nRecordActivityMonitor dump time: "
    100                 + DateFormat.getTimeInstance().format(new Date()));
    101         synchronized(mRecordConfigs) {
    102             for (AudioRecordingConfiguration conf : mRecordConfigs.values()) {
    103                 conf.dump(pw);
    104             }
    105         }
    106     }
    107 
    108     private ArrayList<AudioRecordingConfiguration> anonymizeForPublicConsumption(
    109             List<AudioRecordingConfiguration> sysConfigs) {
    110         ArrayList<AudioRecordingConfiguration> publicConfigs =
    111                 new ArrayList<AudioRecordingConfiguration>();
    112         // only add active anonymized configurations,
    113         for (AudioRecordingConfiguration config : sysConfigs) {
    114             publicConfigs.add(AudioRecordingConfiguration.anonymizedCopy(config));
    115         }
    116         return publicConfigs;
    117     }
    118 
    119     void initMonitor() {
    120         AudioSystem.setRecordingCallback(this);
    121     }
    122 
    123     void registerRecordingCallback(IRecordingConfigDispatcher rcdb, boolean isPrivileged) {
    124         if (rcdb == null) {
    125             return;
    126         }
    127         synchronized (mClients) {
    128             final RecMonitorClient rmc = new RecMonitorClient(rcdb, isPrivileged);
    129             if (rmc.init()) {
    130                 if (!isPrivileged) {
    131                     mHasPublicClients = true;
    132                 }
    133                 mClients.add(rmc);
    134             }
    135         }
    136     }
    137 
    138     void unregisterRecordingCallback(IRecordingConfigDispatcher rcdb) {
    139         if (rcdb == null) {
    140             return;
    141         }
    142         synchronized (mClients) {
    143             final Iterator<RecMonitorClient> clientIterator = mClients.iterator();
    144             boolean hasPublicClients = false;
    145             while (clientIterator.hasNext()) {
    146                 RecMonitorClient rmc = clientIterator.next();
    147                 if (rcdb.equals(rmc.mDispatcherCb)) {
    148                     rmc.release();
    149                     clientIterator.remove();
    150                 } else {
    151                     if (!rmc.mIsPrivileged) {
    152                         hasPublicClients = true;
    153                     }
    154                 }
    155             }
    156             mHasPublicClients = hasPublicClients;
    157         }
    158     }
    159 
    160     List<AudioRecordingConfiguration> getActiveRecordingConfigurations(boolean isPrivileged) {
    161         synchronized(mRecordConfigs) {
    162             if (isPrivileged) {
    163                 return new ArrayList<AudioRecordingConfiguration>(mRecordConfigs.values());
    164             } else {
    165                 final List<AudioRecordingConfiguration> configsPublic =
    166                         anonymizeForPublicConsumption(
    167                             new ArrayList<AudioRecordingConfiguration>(mRecordConfigs.values()));
    168                 return configsPublic;
    169             }
    170         }
    171     }
    172 
    173     /**
    174      * Update the internal "view" of the active recording sessions
    175      * @param event
    176      * @param session
    177      * @param source
    178      * @param recordingFormat see
    179      *     {@link AudioSystem.AudioRecordingCallback#onRecordingConfigurationChanged(int, int, int, int[])}
    180      *     for the definition of the contents of the array
    181      * @return null if the list of active recording sessions has not been modified, a list
    182      *     with the current active configurations otherwise.
    183      */
    184     private List<AudioRecordingConfiguration> updateSnapshot(int event, int uid, int session,
    185             int source, int[] recordingInfo) {
    186         final boolean configChanged;
    187         final ArrayList<AudioRecordingConfiguration> configs;
    188         synchronized(mRecordConfigs) {
    189             switch (event) {
    190             case AudioManager.RECORD_CONFIG_EVENT_STOP:
    191                 // return failure if an unknown recording session stopped
    192                 configChanged = (mRecordConfigs.remove(new Integer(session)) != null);
    193                 break;
    194             case AudioManager.RECORD_CONFIG_EVENT_START:
    195                 final AudioFormat clientFormat = new AudioFormat.Builder()
    196                         .setEncoding(recordingInfo[0])
    197                         // FIXME this doesn't support index-based masks
    198                         .setChannelMask(recordingInfo[1])
    199                         .setSampleRate(recordingInfo[2])
    200                         .build();
    201                 final AudioFormat deviceFormat = new AudioFormat.Builder()
    202                         .setEncoding(recordingInfo[3])
    203                         // FIXME this doesn't support index-based masks
    204                         .setChannelMask(recordingInfo[4])
    205                         .setSampleRate(recordingInfo[5])
    206                         .build();
    207                 final int patchHandle = recordingInfo[6];
    208                 final Integer sessionKey = new Integer(session);
    209 
    210                 final String[] packages = mPackMan.getPackagesForUid(uid);
    211                 final String packageName;
    212                 if (packages != null && packages.length > 0) {
    213                     packageName = packages[0];
    214                 } else {
    215                     packageName = "";
    216                 }
    217                 final AudioRecordingConfiguration updatedConfig =
    218                         new AudioRecordingConfiguration(uid, session, source,
    219                                 clientFormat, deviceFormat, patchHandle, packageName);
    220 
    221                 if (mRecordConfigs.containsKey(sessionKey)) {
    222                     if (updatedConfig.equals(mRecordConfigs.get(sessionKey))) {
    223                         configChanged = false;
    224                     } else {
    225                         // config exists but has been modified
    226                         mRecordConfigs.remove(sessionKey);
    227                         mRecordConfigs.put(sessionKey, updatedConfig);
    228                         configChanged = true;
    229                     }
    230                 } else {
    231                     mRecordConfigs.put(sessionKey, updatedConfig);
    232                     configChanged = true;
    233                 }
    234                 break;
    235             default:
    236                 Log.e(TAG, String.format("Unknown event %d for session %d, source %d",
    237                         event, session, source));
    238                 configChanged = false;
    239             }
    240             if (configChanged) {
    241                 configs = new ArrayList<AudioRecordingConfiguration>(mRecordConfigs.values());
    242             } else {
    243                 configs = null;
    244             }
    245         }
    246         return configs;
    247     }
    248 
    249     /**
    250      * Inner class to track clients that want to be notified of recording updates
    251      */
    252     private final static class RecMonitorClient implements IBinder.DeathRecipient {
    253 
    254         // can afford to be static because only one RecordingActivityMonitor ever instantiated
    255         static RecordingActivityMonitor sMonitor;
    256 
    257         final IRecordingConfigDispatcher mDispatcherCb;
    258         final boolean mIsPrivileged;
    259 
    260         RecMonitorClient(IRecordingConfigDispatcher rcdb, boolean isPrivileged) {
    261             mDispatcherCb = rcdb;
    262             mIsPrivileged = isPrivileged;
    263         }
    264 
    265         public void binderDied() {
    266             Log.w(TAG, "client died");
    267             sMonitor.unregisterRecordingCallback(mDispatcherCb);
    268         }
    269 
    270         boolean init() {
    271             try {
    272                 mDispatcherCb.asBinder().linkToDeath(this, 0);
    273                 return true;
    274             } catch (RemoteException e) {
    275                 Log.w(TAG, "Could not link to client death", e);
    276                 return false;
    277             }
    278         }
    279 
    280         void release() {
    281             mDispatcherCb.asBinder().unlinkToDeath(this, 0);
    282         }
    283     }
    284 }
    285