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