1 /* 2 * Copyright (C) 2018 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.telecom; 18 19 import android.content.Context; 20 import android.media.AudioAttributes; 21 import android.media.AudioDeviceInfo; 22 import android.media.AudioManager; 23 import android.media.AudioRecordingConfiguration; 24 import android.media.MediaPlayer; 25 import android.os.Handler; 26 import android.os.Looper; 27 import android.provider.MediaStore; 28 import android.telecom.Log; 29 30 import com.android.internal.annotations.VisibleForTesting; 31 32 import java.util.ArrayList; 33 import java.util.Collections; 34 import java.util.List; 35 import java.util.stream.Collectors; 36 37 /** 38 * Plays a periodic, repeating tone to the remote party when an app on the device is recording 39 * a call. A call recording tone is played on the called party's audio if an app begins recording. 40 * This ensures that the remote party is aware of the fact call recording is in progress. 41 */ 42 public class CallRecordingTonePlayer extends CallsManagerListenerBase { 43 /** 44 * Callback registered with {@link AudioManager} to track apps which are recording audio. 45 * Registered when a SIM call is added and unregistered when it ends. 46 */ 47 private AudioManager.AudioRecordingCallback mAudioRecordingCallback = 48 new AudioManager.AudioRecordingCallback() { 49 @Override 50 public void onRecordingConfigChanged(List<AudioRecordingConfiguration> configs) { 51 synchronized (mLock) { 52 try { 53 Log.startSession("CRTP.oRCC"); 54 handleRecordingConfigurationChange(configs); 55 maybeStartCallAudioTone(); 56 maybeStopCallAudioTone(); 57 } finally { 58 Log.endSession(); 59 } 60 } 61 } 62 }; 63 64 private final AudioManager mAudioManager; 65 private final Context mContext; 66 private final TelecomSystem.SyncRoot mLock; 67 private final Handler mMainThreadHandler = new Handler(Looper.getMainLooper()); 68 private boolean mIsRecording = false; 69 private MediaPlayer mRecordingTonePlayer = null; 70 private List<Call> mCalls = new ArrayList<>(); 71 CallRecordingTonePlayer(Context context, AudioManager audioManager, TelecomSystem.SyncRoot lock)72 public CallRecordingTonePlayer(Context context, AudioManager audioManager, 73 TelecomSystem.SyncRoot lock) { 74 mContext = context; 75 mAudioManager = audioManager; 76 mLock = lock; 77 } 78 79 @Override onCallAdded(Call call)80 public void onCallAdded(Call call) { 81 if (!shouldUseRecordingTone(call)) { 82 return; // Ignore calls which don't use the recording tone. 83 } 84 85 addCall(call); 86 } 87 88 @Override onCallRemoved(Call call)89 public void onCallRemoved(Call call) { 90 if (!shouldUseRecordingTone(call)) { 91 return; // Ignore calls which don't use the recording tone. 92 } 93 94 removeCall(call); 95 } 96 97 @Override onCallStateChanged(Call call, int oldState, int newState)98 public void onCallStateChanged(Call call, int oldState, int newState) { 99 if (!shouldUseRecordingTone(call)) { 100 return; // Ignore calls which don't use the recording tone. 101 } 102 103 if (mIsRecording) { 104 // Handle start and stop now; could be stopping if we held a call. 105 maybeStartCallAudioTone(); 106 maybeStopCallAudioTone(); 107 } 108 } 109 110 /** 111 * Handles addition of a new call by: 112 * 1. Registering an audio manager listener to track changes to recording state. 113 * 2. Checking if there is recording in progress. 114 * 3. Potentially starting the call recording tone. 115 * 116 * @param toAdd The call to start tracking. 117 */ addCall(Call toAdd)118 private void addCall(Call toAdd) { 119 boolean isFirstCall = mCalls.isEmpty(); 120 121 mCalls.add(toAdd); 122 if (isFirstCall) { 123 // First call, so register the recording callback. Also check for recordings which 124 // started before we registered the callback (we don't receive a callback for those). 125 handleRecordingConfigurationChange(mAudioManager.getActiveRecordingConfigurations()); 126 mAudioManager.registerAudioRecordingCallback(mAudioRecordingCallback, 127 mMainThreadHandler); 128 } 129 130 maybeStartCallAudioTone(); 131 } 132 133 /** 134 * Handles removal of tracked call by unregistering the audio recording callback and stopping 135 * the recording tone if this is the last call. 136 * @param toRemove The call to stop tracking. 137 */ removeCall(Call toRemove)138 private void removeCall(Call toRemove) { 139 mCalls.remove(toRemove); 140 boolean isLastCall = mCalls.isEmpty(); 141 142 if (isLastCall) { 143 mAudioManager.unregisterAudioRecordingCallback(mAudioRecordingCallback); 144 maybeStopCallAudioTone(); 145 } 146 } 147 148 /** 149 * Determines whether a call is applicable for call recording tone generation. 150 * Only top level sim calls are considered which have 151 * {@link android.telecom.PhoneAccount#EXTRA_PLAY_CALL_RECORDING_TONE} set on their target 152 * {@link android.telecom.PhoneAccount}. 153 * @param call The call to check. 154 * @return {@code true} if the call is should use the recording tone, {@code false} otherwise. 155 */ shouldUseRecordingTone(Call call)156 private boolean shouldUseRecordingTone(Call call) { 157 return call.getParentCall() == null && !call.isExternalCall() && 158 !call.isEmergencyCall() && call.isUsingCallRecordingTone(); 159 } 160 161 /** 162 * Starts the call recording tone if recording has started and there are calls. 163 */ maybeStartCallAudioTone()164 private void maybeStartCallAudioTone() { 165 if (mIsRecording && hasActiveCall()) { 166 startCallRecordingTone(mContext); 167 } 168 } 169 170 /** 171 * Stops the call recording tone if recording has stopped or there are no longer any calls. 172 */ maybeStopCallAudioTone()173 private void maybeStopCallAudioTone() { 174 if (!mIsRecording || !hasActiveCall()) { 175 stopCallRecordingTone(); 176 } 177 } 178 179 /** 180 * Determines if any of the calls tracked are active. 181 * @return {@code true} if there is an active call, {@code false} otherwise. 182 */ hasActiveCall()183 private boolean hasActiveCall() { 184 return !mCalls.isEmpty() && mCalls.stream() 185 .filter(call -> call.isActive()) 186 .count() > 0; 187 } 188 189 /** 190 * Handles changes to recording configuration changes. 191 * @param configs the recording configurations. 192 */ handleRecordingConfigurationChange(List<AudioRecordingConfiguration> configs)193 private void handleRecordingConfigurationChange(List<AudioRecordingConfiguration> configs) { 194 if (configs == null) { 195 configs = Collections.emptyList(); 196 } 197 boolean wasRecording = mIsRecording; 198 boolean isRecording = isRecordingInProgress(configs); 199 if (wasRecording != isRecording) { 200 mIsRecording = isRecording; 201 if (isRecording) { 202 Log.i(this, "handleRecordingConfigurationChange: recording started"); 203 } else { 204 Log.i(this, "handleRecordingConfigurationChange: recording stopped"); 205 } 206 } 207 } 208 209 /** 210 * Determines if call recording is potentially in progress. 211 * Excludes from consideration any recordings from packages which have active calls themselves. 212 * Presumably a call with an active recording session is doing so in order to capture the audio 213 * for the purpose of making a call. In practice Telephony calls don't show up in the 214 * recording configurations, but it is reasonable to consider Connection Managers which are 215 * using an over the top voip solution for calling. 216 * @param configs the ongoing recording configurations. 217 * @return {@code true} if there are active audio recordings for which we want to generate a 218 * call recording tone, {@code false} otherwise. 219 */ isRecordingInProgress(List<AudioRecordingConfiguration> configs)220 private boolean isRecordingInProgress(List<AudioRecordingConfiguration> configs) { 221 String recordingPackages = configs.stream() 222 .map(config -> config.getClientPackageName()) 223 .collect(Collectors.joining(", ")); 224 Log.i(this, "isRecordingInProgress: recordingPackages=%s", recordingPackages); 225 return configs.stream() 226 .filter(config -> !hasCallForPackage(config.getClientPackageName())) 227 .count() > 0; 228 } 229 230 /** 231 * Begins playing the call recording tone to the remote end of the call. 232 * The call recording tone is played via the telephony audio output device; this means that it 233 * will only be audible to the remote end of the call, not the local side. 234 * 235 * @param context required for obtaining media player. 236 */ startCallRecordingTone(Context context)237 private void startCallRecordingTone(Context context) { 238 if (mRecordingTonePlayer != null) { 239 return; 240 } 241 AudioDeviceInfo telephonyDevice = getTelephonyDevice(mAudioManager); 242 if (telephonyDevice != null) { 243 Log.i(this ,"startCallRecordingTone: playing call recording tone to remote end."); 244 mRecordingTonePlayer = MediaPlayer.create(context, R.raw.record); 245 mRecordingTonePlayer.setLooping(true); 246 mRecordingTonePlayer.setPreferredDevice(telephonyDevice); 247 mRecordingTonePlayer.setVolume(0.1f); 248 AudioAttributes audioAttributes = new AudioAttributes.Builder() 249 .setUsage(AudioAttributes.USAGE_VOICE_COMMUNICATION).build(); 250 mRecordingTonePlayer.setAudioAttributes(audioAttributes); 251 mRecordingTonePlayer.start(); 252 } else { 253 Log.w(this ,"startCallRecordingTone: can't find telephony audio device."); 254 } 255 } 256 257 /** 258 * Attempts to stop the call recording tone if it is playing. 259 */ stopCallRecordingTone()260 private void stopCallRecordingTone() { 261 if (mRecordingTonePlayer != null) { 262 Log.i(this ,"stopCallRecordingTone: stopping call recording tone."); 263 mRecordingTonePlayer.stop(); 264 mRecordingTonePlayer = null; 265 } 266 } 267 268 /** 269 * Finds the the output device of type {@link AudioDeviceInfo#TYPE_TELEPHONY}. This device is 270 * the one on which outgoing audio for SIM calls is played. 271 * @param audioManager the audio manage. 272 * @return the {@link AudioDeviceInfo} corresponding to the telephony device, or {@code null} 273 * if none can be found. 274 */ getTelephonyDevice(AudioManager audioManager)275 private AudioDeviceInfo getTelephonyDevice(AudioManager audioManager) { 276 AudioDeviceInfo[] deviceList = audioManager.getDevices(AudioManager.GET_DEVICES_OUTPUTS); 277 for (AudioDeviceInfo device: deviceList) { 278 if (device.getType() == AudioDeviceInfo.TYPE_TELEPHONY) { 279 return device; 280 } 281 } 282 return null; 283 } 284 285 /** 286 * Determines if any of the known calls belongs to a {@link android.telecom.PhoneAccount} with 287 * the specified package name. 288 * @param packageName The package name. 289 * @return {@code true} if a call exists for this package, {@code false} otherwise. 290 */ hasCallForPackage(String packageName)291 private boolean hasCallForPackage(String packageName) { 292 return mCalls.stream() 293 .filter(call -> (call.getTargetPhoneAccount() != null && 294 call.getTargetPhoneAccount() 295 .getComponentName().getPackageName().equals(packageName)) || 296 (call.getConnectionManagerPhoneAccount() != null && 297 call.getConnectionManagerPhoneAccount() 298 .getComponentName().getPackageName().equals(packageName))) 299 .count() >= 1; 300 } 301 302 @VisibleForTesting hasCalls()303 public boolean hasCalls() { 304 return mCalls.size() > 0; 305 } 306 307 @VisibleForTesting isRecording()308 public boolean isRecording() { 309 return mIsRecording; 310 } 311 } 312