Add ability to generate remote call recording tone.

Add Telecom support to generate a call recording tone in the outgoing
telephony audio stream when a recording app is enabled on the device.

The new CallRecordingTonePlayer will play a call recording warning tone to
the remote user via outgoing telephony audio stream.  This functionality
if gated on device support and carrier config in the telephony stack.

Test: Added new unit tests.  Manually changed carrier config to enable
tones on local deice, installed audio recording app and verified while it
is recording the tone is audible on remote end.
Bug: 64138141
Change-Id: Ifcf5a49704af5a1352527e705e7d2a3bc16d7fdd
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;
+    }
+
+
+}