Jean-Michel Trivi | d3c71f0 | 2015-12-07 11:59:31 -0800 | [diff] [blame] | 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 | |
Jean-Michel Trivi | 66ffacf | 2017-02-04 17:25:31 -0800 | [diff] [blame^] | 19 | import android.content.Context; |
| 20 | import android.content.pm.PackageManager; |
Jean-Michel Trivi | 33fd816 | 2016-02-22 10:52:41 -0800 | [diff] [blame] | 21 | import android.media.AudioFormat; |
Jean-Michel Trivi | d3c71f0 | 2015-12-07 11:59:31 -0800 | [diff] [blame] | 22 | import android.media.AudioManager; |
Jean-Michel Trivi | 66ffacf | 2017-02-04 17:25:31 -0800 | [diff] [blame^] | 23 | import android.media.AudioPlaybackConfiguration; |
Jean-Michel Trivi | 598c0c9 | 2016-03-07 14:13:03 -0800 | [diff] [blame] | 24 | import android.media.AudioRecordingConfiguration; |
Jean-Michel Trivi | d3c71f0 | 2015-12-07 11:59:31 -0800 | [diff] [blame] | 25 | import android.media.AudioSystem; |
| 26 | import android.media.IRecordingConfigDispatcher; |
Jean-Michel Trivi | dd2772a | 2016-02-17 12:30:52 -0800 | [diff] [blame] | 27 | import android.media.MediaRecorder; |
Jean-Michel Trivi | d3c71f0 | 2015-12-07 11:59:31 -0800 | [diff] [blame] | 28 | import android.os.IBinder; |
| 29 | import android.os.RemoteException; |
| 30 | import android.util.Log; |
| 31 | |
Jean-Michel Trivi | 66ffacf | 2017-02-04 17:25:31 -0800 | [diff] [blame^] | 32 | import java.io.PrintWriter; |
| 33 | import java.text.DateFormat; |
Jean-Michel Trivi | d3c71f0 | 2015-12-07 11:59:31 -0800 | [diff] [blame] | 34 | import java.util.ArrayList; |
Jean-Michel Trivi | 66ffacf | 2017-02-04 17:25:31 -0800 | [diff] [blame^] | 35 | import java.util.Date; |
Jean-Michel Trivi | d3c71f0 | 2015-12-07 11:59:31 -0800 | [diff] [blame] | 36 | import java.util.HashMap; |
| 37 | import java.util.Iterator; |
Jean-Michel Trivi | f04fab1 | 2016-05-19 10:42:35 -0700 | [diff] [blame] | 38 | import java.util.List; |
Jean-Michel Trivi | d3c71f0 | 2015-12-07 11:59:31 -0800 | [diff] [blame] | 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>(); |
Jean-Michel Trivi | 66ffacf | 2017-02-04 17:25:31 -0800 | [diff] [blame^] | 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; |
Jean-Michel Trivi | d3c71f0 | 2015-12-07 11:59:31 -0800 | [diff] [blame] | 52 | |
Jean-Michel Trivi | 598c0c9 | 2016-03-07 14:13:03 -0800 | [diff] [blame] | 53 | private HashMap<Integer, AudioRecordingConfiguration> mRecordConfigs = |
| 54 | new HashMap<Integer, AudioRecordingConfiguration>(); |
Jean-Michel Trivi | d3c71f0 | 2015-12-07 11:59:31 -0800 | [diff] [blame] | 55 | |
Jean-Michel Trivi | 66ffacf | 2017-02-04 17:25:31 -0800 | [diff] [blame^] | 56 | private final PackageManager mPackMan; |
| 57 | |
| 58 | RecordingActivityMonitor(Context ctxt) { |
Jean-Michel Trivi | d3c71f0 | 2015-12-07 11:59:31 -0800 | [diff] [blame] | 59 | RecMonitorClient.sMonitor = this; |
Jean-Michel Trivi | 66ffacf | 2017-02-04 17:25:31 -0800 | [diff] [blame^] | 60 | mPackMan = ctxt.getPackageManager(); |
Jean-Michel Trivi | d3c71f0 | 2015-12-07 11:59:31 -0800 | [diff] [blame] | 61 | } |
| 62 | |
| 63 | /** |
| 64 | * Implementation of android.media.AudioSystem.AudioRecordingCallback |
| 65 | */ |
Jean-Michel Trivi | 66ffacf | 2017-02-04 17:25:31 -0800 | [diff] [blame^] | 66 | public void onRecordingConfigurationChanged(int event, int uid, int session, int source, |
| 67 | int[] recordingInfo, String packName) { |
Jean-Michel Trivi | dd2772a | 2016-02-17 12:30:52 -0800 | [diff] [blame] | 68 | if (MediaRecorder.isSystemOnlyAudioSource(source)) { |
| 69 | return; |
| 70 | } |
Jean-Michel Trivi | 66ffacf | 2017-02-04 17:25:31 -0800 | [diff] [blame^] | 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>(); |
Jean-Michel Trivi | 28ff76b | 2016-03-02 09:36:30 -0800 | [diff] [blame] | 80 | final Iterator<RecMonitorClient> clientIterator = mClients.iterator(); |
Jean-Michel Trivi | d3c71f0 | 2015-12-07 11:59:31 -0800 | [diff] [blame] | 81 | while (clientIterator.hasNext()) { |
Jean-Michel Trivi | 66ffacf | 2017-02-04 17:25:31 -0800 | [diff] [blame^] | 82 | final RecMonitorClient rmc = clientIterator.next(); |
Jean-Michel Trivi | d3c71f0 | 2015-12-07 11:59:31 -0800 | [diff] [blame] | 83 | try { |
Jean-Michel Trivi | 66ffacf | 2017-02-04 17:25:31 -0800 | [diff] [blame^] | 84 | if (rmc.mIsPrivileged) { |
| 85 | rmc.mDispatcherCb.dispatchRecordingConfigChange(configsSystem); |
| 86 | } else { |
| 87 | rmc.mDispatcherCb.dispatchRecordingConfigChange(configsPublic); |
| 88 | } |
Jean-Michel Trivi | d3c71f0 | 2015-12-07 11:59:31 -0800 | [diff] [blame] | 89 | } catch (RemoteException e) { |
| 90 | Log.w(TAG, "Could not call dispatchRecordingConfigChange() on client", e); |
| 91 | } |
| 92 | } |
| 93 | } |
| 94 | } |
| 95 | } |
| 96 | |
Jean-Michel Trivi | 66ffacf | 2017-02-04 17:25:31 -0800 | [diff] [blame^] | 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 | |
Jean-Michel Trivi | d3c71f0 | 2015-12-07 11:59:31 -0800 | [diff] [blame] | 119 | void initMonitor() { |
| 120 | AudioSystem.setRecordingCallback(this); |
| 121 | } |
| 122 | |
Jean-Michel Trivi | 66ffacf | 2017-02-04 17:25:31 -0800 | [diff] [blame^] | 123 | void registerRecordingCallback(IRecordingConfigDispatcher rcdb, boolean isPrivileged) { |
Jean-Michel Trivi | d3c71f0 | 2015-12-07 11:59:31 -0800 | [diff] [blame] | 124 | if (rcdb == null) { |
| 125 | return; |
| 126 | } |
Jean-Michel Trivi | 66ffacf | 2017-02-04 17:25:31 -0800 | [diff] [blame^] | 127 | synchronized (mClients) { |
| 128 | final RecMonitorClient rmc = new RecMonitorClient(rcdb, isPrivileged); |
Jean-Michel Trivi | d3c71f0 | 2015-12-07 11:59:31 -0800 | [diff] [blame] | 129 | if (rmc.init()) { |
Jean-Michel Trivi | 66ffacf | 2017-02-04 17:25:31 -0800 | [diff] [blame^] | 130 | if (!isPrivileged) { |
| 131 | mHasPublicClients = true; |
| 132 | } |
Jean-Michel Trivi | d3c71f0 | 2015-12-07 11:59:31 -0800 | [diff] [blame] | 133 | mClients.add(rmc); |
| 134 | } |
| 135 | } |
| 136 | } |
| 137 | |
| 138 | void unregisterRecordingCallback(IRecordingConfigDispatcher rcdb) { |
| 139 | if (rcdb == null) { |
| 140 | return; |
| 141 | } |
Jean-Michel Trivi | 66ffacf | 2017-02-04 17:25:31 -0800 | [diff] [blame^] | 142 | synchronized (mClients) { |
Jean-Michel Trivi | d3c71f0 | 2015-12-07 11:59:31 -0800 | [diff] [blame] | 143 | final Iterator<RecMonitorClient> clientIterator = mClients.iterator(); |
Jean-Michel Trivi | 66ffacf | 2017-02-04 17:25:31 -0800 | [diff] [blame^] | 144 | boolean hasPublicClients = false; |
Jean-Michel Trivi | d3c71f0 | 2015-12-07 11:59:31 -0800 | [diff] [blame] | 145 | while (clientIterator.hasNext()) { |
| 146 | RecMonitorClient rmc = clientIterator.next(); |
| 147 | if (rcdb.equals(rmc.mDispatcherCb)) { |
| 148 | rmc.release(); |
| 149 | clientIterator.remove(); |
Jean-Michel Trivi | 66ffacf | 2017-02-04 17:25:31 -0800 | [diff] [blame^] | 150 | } else { |
| 151 | if (!rmc.mIsPrivileged) { |
| 152 | hasPublicClients = true; |
| 153 | } |
Jean-Michel Trivi | d3c71f0 | 2015-12-07 11:59:31 -0800 | [diff] [blame] | 154 | } |
| 155 | } |
Jean-Michel Trivi | 66ffacf | 2017-02-04 17:25:31 -0800 | [diff] [blame^] | 156 | mHasPublicClients = hasPublicClients; |
Jean-Michel Trivi | d3c71f0 | 2015-12-07 11:59:31 -0800 | [diff] [blame] | 157 | } |
| 158 | } |
| 159 | |
Jean-Michel Trivi | 66ffacf | 2017-02-04 17:25:31 -0800 | [diff] [blame^] | 160 | List<AudioRecordingConfiguration> getActiveRecordingConfigurations(boolean isPrivileged) { |
Jean-Michel Trivi | d3c71f0 | 2015-12-07 11:59:31 -0800 | [diff] [blame] | 161 | synchronized(mRecordConfigs) { |
Jean-Michel Trivi | 66ffacf | 2017-02-04 17:25:31 -0800 | [diff] [blame^] | 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 | } |
Jean-Michel Trivi | d3c71f0 | 2015-12-07 11:59:31 -0800 | [diff] [blame] | 170 | } |
| 171 | } |
| 172 | |
| 173 | /** |
| 174 | * Update the internal "view" of the active recording sessions |
| 175 | * @param event |
| 176 | * @param session |
| 177 | * @param source |
Jean-Michel Trivi | 33fd816 | 2016-02-22 10:52:41 -0800 | [diff] [blame] | 178 | * @param recordingFormat see |
| 179 | * {@link AudioSystem.AudioRecordingCallback#onRecordingConfigurationChanged(int, int, int, int[])} |
| 180 | * for the definition of the contents of the array |
Jean-Michel Trivi | f04fab1 | 2016-05-19 10:42:35 -0700 | [diff] [blame] | 181 | * @return null if the list of active recording sessions has not been modified, a list |
Jean-Michel Trivi | 28ff76b | 2016-03-02 09:36:30 -0800 | [diff] [blame] | 182 | * with the current active configurations otherwise. |
Jean-Michel Trivi | d3c71f0 | 2015-12-07 11:59:31 -0800 | [diff] [blame] | 183 | */ |
Jean-Michel Trivi | 66ffacf | 2017-02-04 17:25:31 -0800 | [diff] [blame^] | 184 | private List<AudioRecordingConfiguration> updateSnapshot(int event, int uid, int session, |
| 185 | int source, int[] recordingInfo) { |
Jean-Michel Trivi | 28ff76b | 2016-03-02 09:36:30 -0800 | [diff] [blame] | 186 | final boolean configChanged; |
Jean-Michel Trivi | f04fab1 | 2016-05-19 10:42:35 -0700 | [diff] [blame] | 187 | final ArrayList<AudioRecordingConfiguration> configs; |
Jean-Michel Trivi | d3c71f0 | 2015-12-07 11:59:31 -0800 | [diff] [blame] | 188 | synchronized(mRecordConfigs) { |
| 189 | switch (event) { |
| 190 | case AudioManager.RECORD_CONFIG_EVENT_STOP: |
| 191 | // return failure if an unknown recording session stopped |
Jean-Michel Trivi | 28ff76b | 2016-03-02 09:36:30 -0800 | [diff] [blame] | 192 | configChanged = (mRecordConfigs.remove(new Integer(session)) != null); |
| 193 | break; |
Jean-Michel Trivi | d3c71f0 | 2015-12-07 11:59:31 -0800 | [diff] [blame] | 194 | case AudioManager.RECORD_CONFIG_EVENT_START: |
Jean-Michel Trivi | 33fd816 | 2016-02-22 10:52:41 -0800 | [diff] [blame] | 195 | final AudioFormat clientFormat = new AudioFormat.Builder() |
Jean-Michel Trivi | 8ab7280 | 2016-02-25 16:31:45 -0800 | [diff] [blame] | 196 | .setEncoding(recordingInfo[0]) |
Jean-Michel Trivi | 33fd816 | 2016-02-22 10:52:41 -0800 | [diff] [blame] | 197 | // FIXME this doesn't support index-based masks |
Jean-Michel Trivi | 8ab7280 | 2016-02-25 16:31:45 -0800 | [diff] [blame] | 198 | .setChannelMask(recordingInfo[1]) |
| 199 | .setSampleRate(recordingInfo[2]) |
Jean-Michel Trivi | 33fd816 | 2016-02-22 10:52:41 -0800 | [diff] [blame] | 200 | .build(); |
| 201 | final AudioFormat deviceFormat = new AudioFormat.Builder() |
Jean-Michel Trivi | 8ab7280 | 2016-02-25 16:31:45 -0800 | [diff] [blame] | 202 | .setEncoding(recordingInfo[3]) |
Jean-Michel Trivi | 33fd816 | 2016-02-22 10:52:41 -0800 | [diff] [blame] | 203 | // FIXME this doesn't support index-based masks |
Jean-Michel Trivi | 8ab7280 | 2016-02-25 16:31:45 -0800 | [diff] [blame] | 204 | .setChannelMask(recordingInfo[4]) |
| 205 | .setSampleRate(recordingInfo[5]) |
Jean-Michel Trivi | 33fd816 | 2016-02-22 10:52:41 -0800 | [diff] [blame] | 206 | .build(); |
Jean-Michel Trivi | 8ab7280 | 2016-02-25 16:31:45 -0800 | [diff] [blame] | 207 | final int patchHandle = recordingInfo[6]; |
| 208 | final Integer sessionKey = new Integer(session); |
Jean-Michel Trivi | 66ffacf | 2017-02-04 17:25:31 -0800 | [diff] [blame^] | 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 | |
Jean-Michel Trivi | 8ab7280 | 2016-02-25 16:31:45 -0800 | [diff] [blame] | 221 | if (mRecordConfigs.containsKey(sessionKey)) { |
Jean-Michel Trivi | 8ab7280 | 2016-02-25 16:31:45 -0800 | [diff] [blame] | 222 | if (updatedConfig.equals(mRecordConfigs.get(sessionKey))) { |
Jean-Michel Trivi | 28ff76b | 2016-03-02 09:36:30 -0800 | [diff] [blame] | 223 | configChanged = false; |
Jean-Michel Trivi | 8ab7280 | 2016-02-25 16:31:45 -0800 | [diff] [blame] | 224 | } else { |
| 225 | // config exists but has been modified |
| 226 | mRecordConfigs.remove(sessionKey); |
| 227 | mRecordConfigs.put(sessionKey, updatedConfig); |
Jean-Michel Trivi | 28ff76b | 2016-03-02 09:36:30 -0800 | [diff] [blame] | 228 | configChanged = true; |
Jean-Michel Trivi | 8ab7280 | 2016-02-25 16:31:45 -0800 | [diff] [blame] | 229 | } |
| 230 | } else { |
Jean-Michel Trivi | 66ffacf | 2017-02-04 17:25:31 -0800 | [diff] [blame^] | 231 | mRecordConfigs.put(sessionKey, updatedConfig); |
Jean-Michel Trivi | 28ff76b | 2016-03-02 09:36:30 -0800 | [diff] [blame] | 232 | configChanged = true; |
Jean-Michel Trivi | d3c71f0 | 2015-12-07 11:59:31 -0800 | [diff] [blame] | 233 | } |
Jean-Michel Trivi | 28ff76b | 2016-03-02 09:36:30 -0800 | [diff] [blame] | 234 | break; |
Jean-Michel Trivi | d3c71f0 | 2015-12-07 11:59:31 -0800 | [diff] [blame] | 235 | default: |
| 236 | Log.e(TAG, String.format("Unknown event %d for session %d, source %d", |
| 237 | event, session, source)); |
Jean-Michel Trivi | 28ff76b | 2016-03-02 09:36:30 -0800 | [diff] [blame] | 238 | configChanged = false; |
| 239 | } |
| 240 | if (configChanged) { |
Jean-Michel Trivi | f04fab1 | 2016-05-19 10:42:35 -0700 | [diff] [blame] | 241 | configs = new ArrayList<AudioRecordingConfiguration>(mRecordConfigs.values()); |
Jean-Michel Trivi | 28ff76b | 2016-03-02 09:36:30 -0800 | [diff] [blame] | 242 | } else { |
| 243 | configs = null; |
Jean-Michel Trivi | d3c71f0 | 2015-12-07 11:59:31 -0800 | [diff] [blame] | 244 | } |
| 245 | } |
Jean-Michel Trivi | 28ff76b | 2016-03-02 09:36:30 -0800 | [diff] [blame] | 246 | return configs; |
Jean-Michel Trivi | d3c71f0 | 2015-12-07 11:59:31 -0800 | [diff] [blame] | 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; |
Jean-Michel Trivi | 66ffacf | 2017-02-04 17:25:31 -0800 | [diff] [blame^] | 258 | final boolean mIsPrivileged; |
Jean-Michel Trivi | d3c71f0 | 2015-12-07 11:59:31 -0800 | [diff] [blame] | 259 | |
Jean-Michel Trivi | 66ffacf | 2017-02-04 17:25:31 -0800 | [diff] [blame^] | 260 | RecMonitorClient(IRecordingConfigDispatcher rcdb, boolean isPrivileged) { |
Jean-Michel Trivi | d3c71f0 | 2015-12-07 11:59:31 -0800 | [diff] [blame] | 261 | mDispatcherCb = rcdb; |
Jean-Michel Trivi | 66ffacf | 2017-02-04 17:25:31 -0800 | [diff] [blame^] | 262 | mIsPrivileged = isPrivileged; |
Jean-Michel Trivi | d3c71f0 | 2015-12-07 11:59:31 -0800 | [diff] [blame] | 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 | } |