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