• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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