Snap for 4585119 from dcabb4c24484f7e6244cb824299cc452893353e0 to pi-release
Change-Id: I8dc70bda244d3faf3a920c1edf37f4494a8d3d5c
diff --git a/AndroidManifest.xml b/AndroidManifest.xml
index 4f1cb8b..2e3bc37 100644
--- a/AndroidManifest.xml
+++ b/AndroidManifest.xml
@@ -36,6 +36,8 @@
<uses-permission android:name="android.permission.INTERACT_ACROSS_USERS_FULL" />
<uses-permission android:name="android.permission.MANAGE_USERS" />
<uses-permission android:name="android.permission.MODIFY_AUDIO_SETTINGS" />
+ <!-- Required to determine source of ongoing audio recordings. -->
+ <uses-permission android:name="android.permission.MODIFY_AUDIO_ROUTING" />
<uses-permission android:name="android.permission.MODIFY_PHONE_STATE" />
<uses-permission android:name="android.permission.READ_CALL_LOG" />
<uses-permission android:name="android.permission.STOP_APP_SWITCHES" />
diff --git a/res/raw/record.ogg b/res/raw/record.ogg
new file mode 100644
index 0000000..a023e6d
--- /dev/null
+++ b/res/raw/record.ogg
Binary files differ
diff --git a/src/com/android/server/telecom/Call.java b/src/com/android/server/telecom/Call.java
index c682848..420b71d 100644
--- a/src/com/android/server/telecom/Call.java
+++ b/src/com/android/server/telecom/Call.java
@@ -434,6 +434,12 @@
private boolean mIsWorkCall;
+ /**
+ * Tracks whether this {@link Call}'s {@link #getTargetPhoneAccount()} has
+ * {@link PhoneAccount#EXTRA_PLAY_CALL_RECORDING_TONE} set.
+ */
+ private boolean mUseCallRecordingTone;
+
// Set to true once the NewOutgoingCallIntentBroadcast comes back and is processed.
private boolean mIsNewOutgoingCallIntentBroadcastDone = false;
@@ -1077,7 +1083,7 @@
for (Listener l : mListeners) {
l.onTargetPhoneAccountChanged(this);
}
- configureIsWorkCall();
+ configureCallAttributes();
}
checkIfVideoCapable();
}
@@ -1134,6 +1140,10 @@
return mIsWorkCall;
}
+ public boolean isUsingCallRecordingTone() {
+ return mUseCallRecordingTone;
+ }
+
public boolean isVideoCallingSupported() {
return mIsVideoCallingSupported;
}
@@ -1201,9 +1211,10 @@
return mHandoverState;
}
- private void configureIsWorkCall() {
+ private void configureCallAttributes() {
PhoneAccountRegistrar phoneAccountRegistrar = mCallsManager.getPhoneAccountRegistrar();
boolean isWorkCall = false;
+ boolean isCallRecordingToneSupported = false;
PhoneAccount phoneAccount =
phoneAccountRegistrar.getPhoneAccountUnchecked(mTargetPhoneAccountHandle);
if (phoneAccount != null) {
@@ -1216,8 +1227,14 @@
if (userHandle != null) {
isWorkCall = UserUtil.isManagedProfile(mContext, userHandle);
}
+
+ isCallRecordingToneSupported = (phoneAccount.hasCapabilities(
+ PhoneAccount.CAPABILITY_SIM_SUBSCRIPTION) && phoneAccount.getExtras() != null
+ && phoneAccount.getExtras().getBoolean(
+ PhoneAccount.EXTRA_PLAY_CALL_RECORDING_TONE, false));
}
mIsWorkCall = isWorkCall;
+ mUseCallRecordingTone = isCallRecordingToneSupported;
}
/**
diff --git a/src/com/android/server/telecom/CallRecordingTonePlayer.java b/src/com/android/server/telecom/CallRecordingTonePlayer.java
new file mode 100644
index 0000000..ffa1b04
--- /dev/null
+++ b/src/com/android/server/telecom/CallRecordingTonePlayer.java
@@ -0,0 +1,309 @@
+/*
+ * Copyright (C) 2018 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License
+ */
+
+package com.android.server.telecom;
+
+import android.content.Context;
+import android.media.AudioDeviceInfo;
+import android.media.AudioManager;
+import android.media.AudioRecordingConfiguration;
+import android.media.MediaPlayer;
+import android.os.Handler;
+import android.os.Looper;
+import android.telecom.Log;
+
+import com.android.internal.annotations.VisibleForTesting;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+import java.util.stream.Collectors;
+
+/**
+ * Plays a periodic, repeating tone to the remote party when an app on the device is recording
+ * a call. A call recording tone is played on the called party's audio if an app begins recording.
+ * This ensures that the remote party is aware of the fact call recording is in progress.
+ */
+public class CallRecordingTonePlayer extends CallsManagerListenerBase {
+ /**
+ * Callback registered with {@link AudioManager} to track apps which are recording audio.
+ * Registered when a SIM call is added and unregistered when it ends.
+ */
+ private AudioManager.AudioRecordingCallback mAudioRecordingCallback =
+ new AudioManager.AudioRecordingCallback() {
+ @Override
+ public void onRecordingConfigChanged(List<AudioRecordingConfiguration> configs) {
+ synchronized (mLock) {
+ try {
+ Log.startSession("CRTP.oRCC");
+ handleRecordingConfigurationChange(configs);
+ maybeStartCallAudioTone();
+ maybeStopCallAudioTone();
+ } finally {
+ Log.endSession();
+ }
+ }
+ }
+ };
+
+ private final AudioManager mAudioManager;
+ private final Context mContext;
+ private final TelecomSystem.SyncRoot mLock;
+ private final Handler mMainThreadHandler = new Handler(Looper.getMainLooper());
+ private boolean mIsRecording = false;
+ private MediaPlayer mRecordingTonePlayer = null;
+ private List<Call> mCalls = new ArrayList<>();
+
+ public CallRecordingTonePlayer(Context context, AudioManager audioManager,
+ TelecomSystem.SyncRoot lock) {
+ mContext = context;
+ mAudioManager = audioManager;
+ mLock = lock;
+ }
+
+ @Override
+ public void onCallAdded(Call call) {
+ if (!shouldUseRecordingTone(call)) {
+ return; // Ignore calls which don't use the recording tone.
+ }
+
+ addCall(call);
+ }
+
+ @Override
+ public void onCallRemoved(Call call) {
+ if (!shouldUseRecordingTone(call)) {
+ return; // Ignore calls which don't use the recording tone.
+ }
+
+ removeCall(call);
+ }
+
+ @Override
+ public void onCallStateChanged(Call call, int oldState, int newState) {
+ if (!shouldUseRecordingTone(call)) {
+ return; // Ignore calls which don't use the recording tone.
+ }
+
+ if (mIsRecording) {
+ // Handle start and stop now; could be stopping if we held a call.
+ maybeStartCallAudioTone();
+ maybeStopCallAudioTone();
+ }
+ }
+
+ /**
+ * Handles addition of a new call by:
+ * 1. Registering an audio manager listener to track changes to recording state.
+ * 2. Checking if there is recording in progress.
+ * 3. Potentially starting the call recording tone.
+ *
+ * @param toAdd The call to start tracking.
+ */
+ private void addCall(Call toAdd) {
+ boolean isFirstCall = mCalls.isEmpty();
+
+ mCalls.add(toAdd);
+ if (isFirstCall) {
+ // First call, so register the recording callback. Also check for recordings which
+ // started before we registered the callback (we don't receive a callback for those).
+ handleRecordingConfigurationChange(mAudioManager.getActiveRecordingConfigurations());
+ mAudioManager.registerAudioRecordingCallback(mAudioRecordingCallback,
+ mMainThreadHandler);
+ }
+
+ maybeStartCallAudioTone();
+ }
+
+ /**
+ * Handles removal of tracked call by unregistering the audio recording callback and stopping
+ * the recording tone if this is the last call.
+ * @param toRemove The call to stop tracking.
+ */
+ private void removeCall(Call toRemove) {
+ mCalls.remove(toRemove);
+ boolean isLastCall = mCalls.isEmpty();
+
+ if (isLastCall) {
+ mAudioManager.unregisterAudioRecordingCallback(mAudioRecordingCallback);
+ maybeStopCallAudioTone();
+ }
+ }
+
+ /**
+ * Determines whether a call is applicable for call recording tone generation.
+ * Only top level sim calls are considered which have
+ * {@link android.telecom.PhoneAccount#EXTRA_PLAY_CALL_RECORDING_TONE} set on their target
+ * {@link android.telecom.PhoneAccount}.
+ * @param call The call to check.
+ * @return {@code true} if the call is should use the recording tone, {@code false} otherwise.
+ */
+ private boolean shouldUseRecordingTone(Call call) {
+ return call.getParentCall() == null && !call.isExternalCall() &&
+ !call.isEmergencyCall() && call.isUsingCallRecordingTone();
+ }
+
+ /**
+ * Starts the call recording tone if recording has started and there are calls.
+ */
+ private void maybeStartCallAudioTone() {
+ if (mIsRecording && hasActiveCall()) {
+ startCallRecordingTone(mContext);
+ }
+ }
+
+ /**
+ * Stops the call recording tone if recording has stopped or there are no longer any calls.
+ */
+ private void maybeStopCallAudioTone() {
+ if (!mIsRecording || !hasActiveCall()) {
+ stopCallRecordingTone();
+ }
+ }
+
+ /**
+ * Determines if any of the calls tracked are active.
+ * @return {@code true} if there is an active call, {@code false} otherwise.
+ */
+ private boolean hasActiveCall() {
+ return !mCalls.isEmpty() && mCalls.stream()
+ .filter(call -> call.isActive())
+ .count() > 0;
+ }
+
+ /**
+ * Handles changes to recording configuration changes.
+ * @param configs the recording configurations.
+ */
+ private void handleRecordingConfigurationChange(List<AudioRecordingConfiguration> configs) {
+ if (configs == null) {
+ configs = Collections.emptyList();
+ }
+ boolean wasRecording = mIsRecording;
+ boolean isRecording = isRecordingInProgress(configs);
+ if (wasRecording != isRecording) {
+ mIsRecording = isRecording;
+ if (isRecording) {
+ Log.i(this, "handleRecordingConfigurationChange: recording started");
+ } else {
+ Log.i(this, "handleRecordingConfigurationChange: recording stopped");
+ }
+ }
+ }
+
+ /**
+ * Determines if call recording is potentially in progress.
+ * Excludes from consideration any recordings from packages which have active calls themselves.
+ * Presumably a call with an active recording session is doing so in order to capture the audio
+ * for the purpose of making a call. In practice Telephony calls don't show up in the
+ * recording configurations, but it is reasonable to consider Connection Managers which are
+ * using an over the top voip solution for calling.
+ * @param configs the ongoing recording configurations.
+ * @return {@code true} if there are active audio recordings for which we want to generate a
+ * call recording tone, {@code false} otherwise.
+ */
+ private boolean isRecordingInProgress(List<AudioRecordingConfiguration> configs) {
+ String recordingPackages = configs.stream()
+ .map(config -> config.getClientPackageName())
+ .collect(Collectors.joining(", "));
+ Log.i(this, "isRecordingInProgress: recordingPackages=%s", recordingPackages);
+ return configs.stream()
+ .filter(config -> !hasCallForPackage(config.getClientPackageName()))
+ .count() > 0;
+ }
+
+ /**
+ * Begins playing the call recording tone to the remote end of the call.
+ * The call recording tone is played via the telephony audio output device; this means that it
+ * will only be audible to the remote end of the call, not the local side.
+ *
+ * @param context required for obtaining media player.
+ */
+ private void startCallRecordingTone(Context context) {
+ if (mRecordingTonePlayer != null) {
+ return;
+ }
+ AudioDeviceInfo telephonyDevice = getTelephonyDevice(mAudioManager);
+ if (telephonyDevice != null) {
+ Log.i(this ,"startCallRecordingTone: playing call recording tone to remote end.");
+ /**
+ TODO: uncomment this in P release; API dependencies exist which are not met in AOSP.
+ mRecordingTonePlayer = MediaPlayer.create(context, R.raw.record);
+ mRecordingTonePlayer.setLooping(true);
+ mRecordingTonePlayer.setPreferredDevice(telephonyDevice);
+ mRecordingTonePlayer.setVolume(0.1f);
+ mRecordingTonePlayer.start();
+ */
+ } else {
+ Log.w(this ,"startCallRecordingTone: can't find telephony audio device.");
+ }
+ }
+
+ /**
+ * Attempts to stop the call recording tone if it is playing.
+ */
+ private void stopCallRecordingTone() {
+ if (mRecordingTonePlayer != null) {
+ Log.i(this ,"stopCallRecordingTone: stopping call recording tone.");
+ mRecordingTonePlayer.stop();
+ mRecordingTonePlayer = null;
+ }
+ }
+
+ /**
+ * Finds the the output device of type {@link AudioDeviceInfo#TYPE_TELEPHONY}. This device is
+ * the one on which outgoing audio for SIM calls is played.
+ * @param audioManager the audio manage.
+ * @return the {@link AudioDeviceInfo} corresponding to the telephony device, or {@code null}
+ * if none can be found.
+ */
+ private AudioDeviceInfo getTelephonyDevice(AudioManager audioManager) {
+ AudioDeviceInfo[] deviceList = audioManager.getDevices(AudioManager.GET_DEVICES_OUTPUTS);
+ for (AudioDeviceInfo device: deviceList) {
+ if (device.getType() == AudioDeviceInfo.TYPE_TELEPHONY) {
+ return device;
+ }
+ }
+ return null;
+ }
+
+ /**
+ * Determines if any of the known calls belongs to a {@link android.telecom.PhoneAccount} with
+ * the specified package name.
+ * @param packageName The package name.
+ * @return {@code true} if a call exists for this package, {@code false} otherwise.
+ */
+ private boolean hasCallForPackage(String packageName) {
+ return mCalls.stream()
+ .filter(call -> (call.getTargetPhoneAccount() != null &&
+ call.getTargetPhoneAccount()
+ .getComponentName().getPackageName().equals(packageName)) ||
+ (call.getConnectionManagerPhoneAccount() != null &&
+ call.getConnectionManagerPhoneAccount()
+ .getComponentName().getPackageName().equals(packageName)))
+ .count() >= 1;
+ }
+
+ @VisibleForTesting
+ public boolean hasCalls() {
+ return mCalls.size() > 0;
+ }
+
+ @VisibleForTesting
+ public boolean isRecording() {
+ return mIsRecording;
+ }
+}
diff --git a/src/com/android/server/telecom/CallsManager.java b/src/com/android/server/telecom/CallsManager.java
index 4ce0ff9..82e3787 100644
--- a/src/com/android/server/telecom/CallsManager.java
+++ b/src/com/android/server/telecom/CallsManager.java
@@ -238,6 +238,7 @@
private final DtmfLocalTonePlayer mDtmfLocalTonePlayer;
private final InCallController mInCallController;
private final CallAudioManager mCallAudioManager;
+ private final CallRecordingTonePlayer mCallRecordingTonePlayer;
private RespondViaSmsManager mRespondViaSmsManager;
private final Ringer mRinger;
private final InCallWakeLockController mInCallWakeLockController;
@@ -386,7 +387,8 @@
emergencyCallHelper);
mRinger = new Ringer(playerFactory, context, systemSettingsUtil, asyncRingtonePlayer,
ringtoneFactory, systemVibrator, mInCallController);
-
+ mCallRecordingTonePlayer = new CallRecordingTonePlayer(mContext,
+ (AudioManager) mContext.getSystemService(Context.AUDIO_SERVICE), mLock);
mCallAudioManager = new CallAudioManager(callAudioRouteStateMachine,
this,new CallAudioModeStateMachine((AudioManager)
mContext.getSystemService(Context.AUDIO_SERVICE)),
@@ -394,7 +396,6 @@
mConnectionSvrFocusMgr = connectionServiceFocusManagerFactory.create(
mRequester, Looper.getMainLooper());
-
mHeadsetMediaButton = headsetMediaButtonFactory.create(context, this, mLock);
mTtyManager = new TtyManager(context, mWiredHeadsetManager);
mProximitySensorManager = proximitySensorManagerFactory.create(context, this);
@@ -411,6 +412,7 @@
mListeners.add(mPhoneStateBroadcaster);
mListeners.add(mInCallController);
mListeners.add(mCallAudioManager);
+ mListeners.add(mCallRecordingTonePlayer);
mListeners.add(missedCallNotifier);
mListeners.add(mHeadsetMediaButton);
mListeners.add(mProximitySensorManager);
diff --git a/tests/src/com/android/server/telecom/tests/CallRecordingTonePlayerTest.java b/tests/src/com/android/server/telecom/tests/CallRecordingTonePlayerTest.java
new file mode 100644
index 0000000..eca374b
--- /dev/null
+++ b/tests/src/com/android/server/telecom/tests/CallRecordingTonePlayerTest.java
@@ -0,0 +1,272 @@
+/*
+ * Copyright (C) 2018 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License
+ */
+
+package com.android.server.telecom.tests;
+
+import static com.android.server.telecom.tests.TelecomSystemTest.TEST_TIMEOUT;
+
+import static junit.framework.Assert.assertFalse;
+import static junit.framework.Assert.assertTrue;
+
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+import android.content.ComponentName;
+import android.media.AudioFormat;
+import android.media.AudioManager;
+import android.media.AudioRecordingConfiguration;
+import android.media.MediaRecorder;
+import android.os.Handler;
+import android.os.Looper;
+import android.telecom.PhoneAccountHandle;
+import android.test.suitebuilder.annotation.MediumTest;
+
+import com.android.server.telecom.Call;
+import com.android.server.telecom.CallRecordingTonePlayer;
+import com.android.server.telecom.TelecomSystem;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.JUnit4;
+import org.mockito.ArgumentCaptor;
+import org.mockito.Mock;
+import org.mockito.Mockito;
+import org.mockito.MockitoAnnotations;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * Unit tests for the {@link com.android.server.telecom.CallRecordingTonePlayer} class.
+ */
+@RunWith(JUnit4.class)
+public class CallRecordingTonePlayerTest extends TelecomTestCase {
+
+ private static final String PHONE_ACCOUNT_PACKAGE = "com.android.telecom.test";
+ private static final String PHONE_ACCOUNT_CLASS = "MyFancyConnectionService";
+ private static final String PHONE_ACCOUNT_ID = "1";
+ private static final String RECORDING_APP_PACKAGE = "com.recording.app";
+
+ private static final PhoneAccountHandle TEST_PHONE_ACCOUNT = new PhoneAccountHandle(
+ new ComponentName(PHONE_ACCOUNT_PACKAGE, PHONE_ACCOUNT_CLASS), PHONE_ACCOUNT_ID);
+
+ private CallRecordingTonePlayer mCallRecordingTonePlayer;
+ private TelecomSystem.SyncRoot mSyncRoot = new TelecomSystem.SyncRoot() { };
+ @Mock private AudioManager mAudioManager;
+
+ @Override
+ @Before
+ public void setUp() throws Exception {
+ super.setUp();
+ MockitoAnnotations.initMocks(this);
+ mCallRecordingTonePlayer = new CallRecordingTonePlayer(
+ mComponentContextFixture.getTestDouble().getApplicationContext(),
+ mAudioManager, mSyncRoot);
+ when(mAudioManager.getActiveRecordingConfigurations()).thenReturn(null);
+ }
+
+ /**
+ * Ensures that child calls are not tracked.
+ */
+ @MediumTest
+ @Test
+ public void testChildCall() {
+ Call childCall = Mockito.mock(Call.class);
+ Call parentcall = Mockito.mock(Call.class);
+ when(childCall.getParentCall()).thenReturn(parentcall);
+ mCallRecordingTonePlayer.onCallAdded(childCall);
+
+ assertFalse(mCallRecordingTonePlayer.hasCalls());
+ }
+
+ /**
+ * Ensures that external calls are not tracked.
+ */
+ @MediumTest
+ @Test
+ public void testAddExternalCall() {
+ Call call = Mockito.mock(Call.class);
+ when(call.getParentCall()).thenReturn(null);
+ when(call.isExternalCall()).thenReturn(true);
+ mCallRecordingTonePlayer.onCallAdded(call);
+
+ assertFalse(mCallRecordingTonePlayer.hasCalls());
+ }
+
+ /**
+ * Ensures that emergency calls are not tracked.
+ */
+ @MediumTest
+ @Test
+ public void testAddEmergencyCall() {
+ Call call = Mockito.mock(Call.class);
+ when(call.getParentCall()).thenReturn(null);
+ when(call.isExternalCall()).thenReturn(false);
+ when(call.isEmergencyCall()).thenReturn(true);
+ mCallRecordingTonePlayer.onCallAdded(call);
+
+ assertFalse(mCallRecordingTonePlayer.hasCalls());
+ }
+
+ /**
+ * Ensures that calls which don't use the recording tone are not tracked.
+ */
+ @MediumTest
+ @Test
+ public void testAddIneligibleCall() {
+ Call call = Mockito.mock(Call.class);
+ when(call.getParentCall()).thenReturn(null);
+ when(call.isExternalCall()).thenReturn(false);
+ when(call.isEmergencyCall()).thenReturn(false);
+ when(call.isUsingCallRecordingTone()).thenReturn(false);
+ mCallRecordingTonePlayer.onCallAdded(call);
+
+ assertFalse(mCallRecordingTonePlayer.hasCalls());
+ }
+
+ /**
+ * Ensures that an eligible call is tracked.
+ */
+ @MediumTest
+ @Test
+ public void testAddEligibleCall() {
+ Call call = addValidCall();
+
+ mCallRecordingTonePlayer.onCallRemoved(call);
+ assertFalse(mCallRecordingTonePlayer.hasCalls());
+ }
+
+ /**
+ * Verifies registration and unregistration of the recording callback.
+ */
+ @MediumTest
+ @Test
+ public void testRecordingCallbackRegistered() {
+ Call call = addValidCall();
+
+ // Ensure we got a request for the first set of recordings.
+ verify(mAudioManager).getActiveRecordingConfigurations();
+
+ // Ensure that we registered an audio recording callback.
+ verify(mAudioManager).registerAudioRecordingCallback(
+ any(AudioManager.AudioRecordingCallback.class), any());
+
+ mCallRecordingTonePlayer.onCallRemoved(call);
+
+ // Ensure we unregistered the audio recording callback after the last call was removed.
+ verify(mAudioManager).unregisterAudioRecordingCallback(
+ any(AudioManager.AudioRecordingCallback.class));
+ }
+
+ /**
+ * Verify that we are in a recording state when we add a call and there is a recording taking
+ * place prior to the call starting.
+ */
+ @MediumTest
+ @Test
+ public void testIsRecordingInitial() {
+ // Return an active recording configuration when we add the first call.
+ when(mAudioManager.getActiveRecordingConfigurations()).thenReturn(
+ getAudioRecordingConfig(RECORDING_APP_PACKAGE));
+
+ addValidCall();
+
+ // Ensure we got a request for the first set of recordings.
+ verify(mAudioManager).getActiveRecordingConfigurations();
+
+ assertTrue(mCallRecordingTonePlayer.isRecording());
+ }
+
+ /**
+ * Verify that we are in a recording state when we add a call and a recording start after the
+ * call starts.
+ */
+ @MediumTest
+ @Test
+ public void testIsRecordingLater() {
+ // Return no active recording configuration when we add the first call.
+ when(mAudioManager.getActiveRecordingConfigurations()).thenReturn( null);
+
+ addValidCall();
+
+ // Capture the registered callback so we can pass back test data via it.
+ ArgumentCaptor<AudioManager.AudioRecordingCallback> callbackCaptor =
+ ArgumentCaptor.forClass(AudioManager.AudioRecordingCallback.class);
+ verify(mAudioManager).registerAudioRecordingCallback(callbackCaptor.capture(), any());
+
+ // Pass back some test configuration data.
+ callbackCaptor.getValue().onRecordingConfigChanged(getAudioRecordingConfig(
+ RECORDING_APP_PACKAGE));
+ waitForHandlerAction(new Handler(Looper.getMainLooper()), TEST_TIMEOUT);
+
+ assertTrue(mCallRecordingTonePlayer.isRecording());
+ }
+
+ /**
+ * Verifies that we are not in a recording state if the PhoneAccount associated with the call is
+ * the recording app.
+ */
+ @MediumTest
+ @Test
+ public void testNotRecordingApp() {
+ // Return no active recording configuration when we add the first call.
+ when(mAudioManager.getActiveRecordingConfigurations()).thenReturn( null);
+
+ addValidCall();
+
+ // Capture the registered callback so we can pass back test data via it.
+ ArgumentCaptor<AudioManager.AudioRecordingCallback> callbackCaptor =
+ ArgumentCaptor.forClass(AudioManager.AudioRecordingCallback.class);
+ verify(mAudioManager).registerAudioRecordingCallback(callbackCaptor.capture(), any());
+
+ // Report that the recording app is the call's phone account.
+ callbackCaptor.getValue().onRecordingConfigChanged(getAudioRecordingConfig(
+ PHONE_ACCOUNT_PACKAGE));
+ waitForHandlerAction(new Handler(Looper.getMainLooper()), TEST_TIMEOUT);
+
+ // Since the app which is recording is the phone account of the call, we should not be in
+ // a recording state.
+ assertFalse(mCallRecordingTonePlayer.isRecording());
+ }
+
+ /**
+ * @return Test audio recording configuration.
+ */
+ private List<AudioRecordingConfiguration> getAudioRecordingConfig(String packageName) {
+ List<AudioRecordingConfiguration> configs = new ArrayList<>();
+ configs.add(new AudioRecordingConfiguration(0, 0, MediaRecorder.AudioSource.MIC,
+ new AudioFormat.Builder().build(), new AudioFormat.Builder().build(),
+ 0, packageName));
+ return configs;
+ }
+
+ private Call addValidCall() {
+ Call call = Mockito.mock(Call.class);
+ when(call.getParentCall()).thenReturn(null);
+ when(call.isExternalCall()).thenReturn(false);
+ when(call.isEmergencyCall()).thenReturn(false);
+ when(call.isUsingCallRecordingTone()).thenReturn(true);
+ when(call.getConnectionManagerPhoneAccount()).thenReturn(null);
+ when(call.getTargetPhoneAccount()).thenReturn(TEST_PHONE_ACCOUNT);
+ mCallRecordingTonePlayer.onCallAdded(call);
+ assertTrue(mCallRecordingTonePlayer.hasCalls());
+ return call;
+ }
+
+
+}