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