Merge "Don't complete in-call binding future for nonui" into rvc-qpr-dev
diff --git a/Android.bp b/Android.bp
index 94a6f9d..0d89b00 100644
--- a/Android.bp
+++ b/Android.bp
@@ -41,7 +41,7 @@
static_libs: [
"android-ex-camera2",
"guava",
- "mockito-target-inline",
+ "mockito-target-extended",
"androidx.test.rules",
"platform-test-annotations",
"androidx.legacy_legacy-support-core-ui",
@@ -70,7 +70,10 @@
"android.test.runner",
],
- jni_libs: ["libdexmakerjvmtiagent"],
+ jni_libs: [
+ "libdexmakerjvmtiagent",
+ "libstaticjvmtiagent",
+ ],
aaptflags: [
"--auto-add-overlay",
diff --git a/res/raw/record.ogg b/res/raw/record.ogg
index a023e6d..732b42f 100644
--- a/res/raw/record.ogg
+++ b/res/raw/record.ogg
Binary files differ
diff --git a/res/values/strings.xml b/res/values/strings.xml
index 07d6cc9..df08d7c 100644
--- a/res/values/strings.xml
+++ b/res/values/strings.xml
@@ -75,13 +75,14 @@
background. This app may be accessing and playing audio over the call.
</string>
- <!-- Crashed in call service notification label, used when the in call service has cranshed and
+ <!-- Crashed in call service notification label, used when the in call service has crashed and
the system fall back to use system dialer. [CHAR LIMIT=NONE] -->
- <string name="notification_crashedInCallService_title">Crashed phone app</string>
+ <string name="notification_incallservice_not_responding_title">
+ <xliff:g id="in_call_service_app_name">%s</xliff:g> stopped responding
+ </string>
<!-- Body of the notification presented when an in call service crashed. [CHAR LIMIT=NONE] -->
- <string name="notification_crashedInCallService_body">
- Your phone app <xliff:g id="in_call_service_app_name">%s</xliff:g> has crashed.
- You call was continued using the phone app that came with your device.
+ <string name="notification_incallservice_not_responding_body">
+ Your call used the phone app that came with your device
</string>
<!-- Content description of the call muted notification icon for
diff --git a/src/com/android/server/telecom/Call.java b/src/com/android/server/telecom/Call.java
index 491aed3..df6322c 100755
--- a/src/com/android/server/telecom/Call.java
+++ b/src/com/android/server/telecom/Call.java
@@ -2569,7 +2569,8 @@
}
}
- boolean isActive() {
+ @VisibleForTesting
+ public boolean isActive() {
return mState == CallState.ACTIVE;
}
diff --git a/src/com/android/server/telecom/CallAudioModeStateMachine.java b/src/com/android/server/telecom/CallAudioModeStateMachine.java
index 2c435d7..32598be 100644
--- a/src/com/android/server/telecom/CallAudioModeStateMachine.java
+++ b/src/com/android/server/telecom/CallAudioModeStateMachine.java
@@ -353,7 +353,17 @@
}
private class RingingFocusState extends BaseState {
+ // Keeps track of whether we're ringing with audio focus or if we've just entered the state
+ // without acquiring focus because of a silent ringtone or something.
+ private boolean mHasFocus = false;
+
private void tryStartRinging() {
+ if (mHasFocus) {
+ Log.i(LOG_TAG, "RingingFocusState#tryStartRinging -- audio focus previously"
+ + " acquired and ringtone already playing -- skipping.");
+ return;
+ }
+
if (mCallAudioManager.startRinging()) {
mAudioManager.requestAudioFocusForCall(AudioManager.STREAM_RING,
AudioManager.AUDIOFOCUS_GAIN_TRANSIENT);
@@ -364,6 +374,7 @@
}
mCallAudioManager.setCallAudioRouteFocusState(
CallAudioRouteStateMachine.RINGING_FOCUS);
+ mHasFocus = true;
} else {
Log.i(LOG_TAG, "RINGING state, try start ringing but not acquiring audio focus");
}
@@ -380,6 +391,7 @@
public void exit() {
// Audio mode and audio stream will be set by the next state.
mCallAudioManager.stopRinging();
+ mHasFocus = false;
}
@Override
diff --git a/src/com/android/server/telecom/CallRecordingTonePlayer.java b/src/com/android/server/telecom/CallRecordingTonePlayer.java
index 999148c..1b522bc 100644
--- a/src/com/android/server/telecom/CallRecordingTonePlayer.java
+++ b/src/com/android/server/telecom/CallRecordingTonePlayer.java
@@ -24,6 +24,7 @@
import android.media.MediaPlayer;
import android.os.Handler;
import android.os.Looper;
+import android.os.Message;
import android.provider.MediaStore;
import android.telecom.Log;
@@ -61,19 +62,71 @@
}
};
+ private class LoopingTonePlayer extends Handler {
+ private Runnable mPlayToneRunnable = new Runnable() {
+ @Override
+ public void run() {
+ if (mRecordingTonePlayer != null) {
+ mRecordingTonePlayer.start();
+ postDelayed(this, mRepeatInterval);
+ }
+ }
+ };
+ private MediaPlayer mRecordingTonePlayer = null;
+
+ LoopingTonePlayer() {
+ // We're using the main looper here to avoid creating more threads and risking a thread
+ // leak. The actual playing of the tone doesn't take up much time on the calling
+ // thread, so it's okay to use the main thread for this.
+ super(Looper.getMainLooper());
+ }
+
+ private boolean start() {
+ if (mRecordingTonePlayer != null) {
+ Log.w(CallRecordingTonePlayer.this, "Can't start looping tone player more than"
+ + " once");
+ return false;
+ }
+ AudioDeviceInfo telephonyDevice = getTelephonyDevice(mAudioManager);
+ if (telephonyDevice != null) {
+ mRecordingTonePlayer = MediaPlayer.create(mContext, R.raw.record);
+ mRecordingTonePlayer.setPreferredDevice(telephonyDevice);
+ mRecordingTonePlayer.setVolume(0.1f);
+ AudioAttributes audioAttributes = new AudioAttributes.Builder()
+ .setUsage(AudioAttributes.USAGE_VOICE_COMMUNICATION).build();
+ mRecordingTonePlayer.setAudioAttributes(audioAttributes);
+
+ post(mPlayToneRunnable);
+ return true;
+ } else {
+ Log.w(this ,"startCallRecordingTone: can't find telephony audio device.");
+ return false;
+ }
+ }
+
+ private void stop() {
+ mRecordingTonePlayer.release();
+ mRecordingTonePlayer = null;
+ }
+ }
+
private final AudioManager mAudioManager;
private final Context mContext;
private final TelecomSystem.SyncRoot mLock;
private final Handler mMainThreadHandler = new Handler(Looper.getMainLooper());
+ private final long mRepeatInterval;
private boolean mIsRecording = false;
- private MediaPlayer mRecordingTonePlayer = null;
+ private LoopingTonePlayer mLoopingTonePlayer;
private List<Call> mCalls = new ArrayList<>();
public CallRecordingTonePlayer(Context context, AudioManager audioManager,
+ Timeouts.Adapter timeouts,
TelecomSystem.SyncRoot lock) {
mContext = context;
mAudioManager = audioManager;
mLock = lock;
+ mRepeatInterval = timeouts.getCallRecordingToneRepeatIntervalMillis(
+ context.getContentResolver());
}
@Override
@@ -163,7 +216,7 @@
*/
private void maybeStartCallAudioTone() {
if (mIsRecording && hasActiveCall()) {
- startCallRecordingTone(mContext);
+ startCallRecordingTone();
}
}
@@ -231,26 +284,15 @@
* 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) {
+ private void startCallRecordingTone() {
+ if (mLoopingTonePlayer != null) {
+ Log.w(this, "Tone is already playing");
return;
}
- AudioDeviceInfo telephonyDevice = getTelephonyDevice(mAudioManager);
- if (telephonyDevice != null) {
- Log.i(this ,"startCallRecordingTone: playing call recording tone to remote end.");
- mRecordingTonePlayer = MediaPlayer.create(context, R.raw.record);
- mRecordingTonePlayer.setLooping(true);
- mRecordingTonePlayer.setPreferredDevice(telephonyDevice);
- mRecordingTonePlayer.setVolume(0.1f);
- AudioAttributes audioAttributes = new AudioAttributes.Builder()
- .setUsage(AudioAttributes.USAGE_VOICE_COMMUNICATION).build();
- mRecordingTonePlayer.setAudioAttributes(audioAttributes);
- mRecordingTonePlayer.start();
- } else {
- Log.w(this ,"startCallRecordingTone: can't find telephony audio device.");
+ mLoopingTonePlayer = new LoopingTonePlayer();
+ if (!mLoopingTonePlayer.start()) {
+ mLoopingTonePlayer = null;
}
}
@@ -258,10 +300,10 @@
* 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;
+ if (mLoopingTonePlayer != null) {
+ Log.i(this, "stopCallRecordingTone: stopping call recording tone.");
+ mLoopingTonePlayer.stop();
+ mLoopingTonePlayer = null;
}
}
diff --git a/src/com/android/server/telecom/CallsManager.java b/src/com/android/server/telecom/CallsManager.java
index 0bfe27a..1c46209 100755
--- a/src/com/android/server/telecom/CallsManager.java
+++ b/src/com/android/server/telecom/CallsManager.java
@@ -543,7 +543,8 @@
mRinger = new Ringer(playerFactory, context, systemSettingsUtil, asyncRingtonePlayer,
ringtoneFactory, systemVibrator,
new Ringer.VibrationEffectProxy(), mInCallController);
- mCallRecordingTonePlayer = new CallRecordingTonePlayer(mContext, audioManager, mLock);
+ mCallRecordingTonePlayer = new CallRecordingTonePlayer(mContext, audioManager,
+ mTimeoutsAdapter, mLock);
mCallAudioManager = new CallAudioManager(callAudioRouteStateMachine,
this, callAudioModeStateMachineFactory.create(systemStateHelper,
(AudioManager) mContext.getSystemService(Context.AUDIO_SERVICE)),
@@ -2958,6 +2959,9 @@
call.setState(CallState.AUDIO_PROCESSING, "active set explicitly and adding");
addCall(call);
}
+ // Clear mPendingAudioProcessingCall so that future attempts to mark the call as
+ // active (e.g. coming off of hold) don't put the call into audio processing instead
+ mPendingAudioProcessingCall = null;
return;
}
setCallState(call, CallState.ACTIVE, "active set explicitly");
diff --git a/src/com/android/server/telecom/InCallController.java b/src/com/android/server/telecom/InCallController.java
index 296d0b3..341c02e 100644
--- a/src/com/android/server/telecom/InCallController.java
+++ b/src/com/android/server/telecom/InCallController.java
@@ -1324,10 +1324,12 @@
(systemPackageName != null && systemPackageName.equals(packageName))
? getInCallServiceComponent(packageName, IN_CALL_SERVICE_TYPE_SYSTEM_UI)
: getInCallServiceComponent(packageName, IN_CALL_SERVICE_TYPE_DIALER_UI);
- if (packageName != null && defaultDialerComponent == null) {
- // The in call service of default phone app is disabled, send notification.
- sendCrashedInCallServiceNotification(packageName);
- }
+ /* TODO: in Android 12 re-enable this an InCallService is required by the dialer role.
+ if (packageName != null && defaultDialerComponent == null) {
+ // The in call service of default phone app is disabled, send notification.
+ sendCrashedInCallServiceNotification(packageName);
+ }
+ */
return defaultDialerComponent;
}
@@ -1829,12 +1831,11 @@
builder.setSmallIcon(R.drawable.ic_phone)
.setColor(mContext.getResources().getColor(R.color.theme_color))
.setContentTitle(
- mContext.getText(
- R.string.notification_crashedInCallService_title))
+ mContext.getString(
+ R.string.notification_incallservice_not_responding_title, appName))
.setStyle(new Notification.BigTextStyle()
- .bigText(mContext.getString(
- R.string.notification_crashedInCallService_body,
- appName)));
+ .bigText(mContext.getText(
+ R.string.notification_incallservice_not_responding_body)));
notificationManager.notify(NOTIFICATION_TAG, IN_CALL_SERVICE_NOTIFICATION_ID,
builder.build());
}
diff --git a/src/com/android/server/telecom/Timeouts.java b/src/com/android/server/telecom/Timeouts.java
index 37f9363..a701b88 100644
--- a/src/com/android/server/telecom/Timeouts.java
+++ b/src/com/android/server/telecom/Timeouts.java
@@ -29,7 +29,8 @@
*/
public final class Timeouts {
public static class Adapter {
- public Adapter() { }
+ public Adapter() {
+ }
public long getCallScreeningTimeoutMillis(ContentResolver cr) {
return Timeouts.getCallScreeningTimeoutMillis(cr);
@@ -62,20 +63,25 @@
public long getPhoneAccountSuggestionServiceTimeout(ContentResolver cr) {
return Timeouts.getPhoneAccountSuggestionServiceTimeout(cr);
}
+
+ public long getCallRecordingToneRepeatIntervalMillis(ContentResolver cr) {
+ return Timeouts.getCallRecordingToneRepeatIntervalMillis(cr);
+ }
}
/** A prefix to use for all keys so to not clobber the global namespace. */
private static final String PREFIX = "telecom.";
- private Timeouts() {}
+ private Timeouts() {
+ }
/**
* Returns the timeout value from Settings or the default value if it hasn't been changed. This
* method is safe to call from any thread, including the UI thread.
*
* @param contentResolver The content resolved.
- * @param key Settings key to retrieve.
- * @param defaultValue Default value, in milliseconds.
+ * @param key Settings key to retrieve.
+ * @param defaultValue Default value, in milliseconds.
* @return The timeout value from Settings or the default value if it hasn't been changed.
*/
private static long get(ContentResolver contentResolver, String key, long defaultValue) {
@@ -176,8 +182,8 @@
* as potential emergency callbacks.
*/
public static long getEmergencyCallbackWindowMillis(ContentResolver contentResolver) {
- return get(contentResolver, "emergency_callback_window_millis",
- TimeUnit.MILLISECONDS.convert(5, TimeUnit.MINUTES));
+ return get(contentResolver, "emergency_callback_window_millis",
+ TimeUnit.MILLISECONDS.convert(5, TimeUnit.MINUTES));
}
/**
@@ -187,7 +193,7 @@
*/
public static long getUserDefinedCallRedirectionTimeoutMillis(ContentResolver contentResolver) {
return get(contentResolver, "user_defined_call_redirection_timeout",
- 5000L /* 5 seconds */);
+ 5000L /* 5 seconds */);
}
/**
@@ -198,4 +204,11 @@
public static long getCarrierCallRedirectionTimeoutMillis(ContentResolver contentResolver) {
return get(contentResolver, "carrier_call_redirection_timeout", 5000L /* 5 seconds */);
}
+
+ /**
+ * Returns the number of milliseconds between two plays of the call recording tone.
+ */
+ public static long getCallRecordingToneRepeatIntervalMillis(ContentResolver contentResolver) {
+ return get(contentResolver, "call_recording_tone_repeat_interval", 15000L /* 15 seconds */);
+ }
}
diff --git a/src/com/android/server/telecom/callfiltering/CallScreeningServiceFilter.java b/src/com/android/server/telecom/callfiltering/CallScreeningServiceFilter.java
index 51608a0..1e52c5a 100644
--- a/src/com/android/server/telecom/callfiltering/CallScreeningServiceFilter.java
+++ b/src/com/android/server/telecom/callfiltering/CallScreeningServiceFilter.java
@@ -34,6 +34,7 @@
import com.android.server.telecom.Call;
import com.android.server.telecom.CallScreeningServiceHelper;
import com.android.server.telecom.CallsManager;
+import com.android.server.telecom.LogUtils;
import com.android.server.telecom.ParcelableCallUtils;
import java.util.concurrent.CompletableFuture;
@@ -70,6 +71,7 @@
if (mCall == null || (!mCall.getId().equals(callId))) {
Log.w(this, "allowCall, unknown call id: %s", callId);
}
+ Log.addEvent(mCall, LogUtils.Events.SCREENING_COMPLETED, mPriorStageResult);
mResultFuture.complete(mPriorStageResult);
} finally {
unbindCallScreeningService();
@@ -86,7 +88,7 @@
Log.startSession("NCSSF.dC");
try {
if (mCall != null && mCall.getId().equals(callId)) {
- mResultFuture.complete(new CallFilteringResult.Builder()
+ CallFilteringResult result = new CallFilteringResult.Builder()
.setShouldAllowCall(false)
.setShouldReject(shouldReject)
.setShouldSilence(false)
@@ -97,7 +99,9 @@
.setCallScreeningAppName(mAppName)
.setCallScreeningComponentName(componentName.flattenToString())
.setContactExists(mPriorStageResult.contactExists)
- .build());
+ .build();
+ Log.addEvent(mCall, LogUtils.Events.SCREENING_COMPLETED, result);
+ mResultFuture.complete(result);
} else {
Log.w(this, "disallowCall, unknown call id: %s", callId);
mResultFuture.complete(mPriorStageResult);
@@ -115,14 +119,16 @@
Log.startSession("NCSSF.sC");
try {
if (mCall != null && mCall.getId().equals(callId)) {
- mResultFuture.complete(new CallFilteringResult.Builder()
+ CallFilteringResult result = new CallFilteringResult.Builder()
.setShouldAllowCall(true)
.setShouldReject(false)
.setShouldSilence(true)
.setShouldAddToCallLog(true)
.setShouldShowNotification(true)
.setContactExists(mPriorStageResult.contactExists)
- .build());
+ .build();
+ Log.addEvent(mCall, LogUtils.Events.SCREENING_COMPLETED, result);
+ mResultFuture.complete(result);
} else {
Log.w(this, "silenceCall, unknown call id: %s", callId);
mResultFuture.complete(mPriorStageResult);
@@ -146,14 +152,16 @@
try {
if (mCall != null && mCall.getId().equals(callId)) {
- mResultFuture.complete(new CallFilteringResult.Builder()
+ CallFilteringResult result = new CallFilteringResult.Builder()
.setShouldAllowCall(true)
.setShouldReject(false)
.setShouldSilence(false)
.setShouldScreenViaAudio(true)
.setCallScreeningAppName(mAppName)
.setContactExists(mPriorStageResult.contactExists)
- .build());
+ .build();
+ Log.addEvent(mCall, LogUtils.Events.SCREENING_COMPLETED, result);
+ mResultFuture.complete(result);
} else {
Log.w(this, "screenCallFurther, unknown call id: %s", callId);
mResultFuture.complete(mPriorStageResult);
@@ -185,6 +193,7 @@
Log.e(this, e, "Failed to set the call screening adapter");
mResultFuture.complete(mPriorStageResult);
}
+ Log.addEvent(mCall, LogUtils.Events.SCREENING_BOUND, componentName);
Log.i(this, "Binding completed.");
}
diff --git a/tests/src/com/android/server/telecom/tests/CallAudioModeStateMachineTest.java b/tests/src/com/android/server/telecom/tests/CallAudioModeStateMachineTest.java
index ca84c4c..4fc4358 100644
--- a/tests/src/com/android/server/telecom/tests/CallAudioModeStateMachineTest.java
+++ b/tests/src/com/android/server/telecom/tests/CallAudioModeStateMachineTest.java
@@ -180,6 +180,42 @@
CallAudioRouteStateMachine.RINGING_FOCUS);
}
+ @SmallTest
+ @Test
+ public void testDoNotRingTwiceWhenHfpConnected() {
+ CallAudioModeStateMachine sm = new CallAudioModeStateMachine(mSystemStateHelper,
+ mAudioManager, mTestThread.getLooper());
+ sm.setCallAudioManager(mCallAudioManager);
+ sm.sendMessage(CallAudioModeStateMachine.ABANDON_FOCUS_FOR_TESTING);
+ waitForHandlerAction(sm.getHandler(), TEST_TIMEOUT);
+
+ resetMocks();
+ when(mCallAudioManager.startRinging()).thenReturn(true);
+
+ sm.sendMessage(CallAudioModeStateMachine.NEW_RINGING_CALL, new Builder()
+ .setHasActiveOrDialingCalls(false)
+ .setHasRingingCalls(true)
+ .setHasHoldingCalls(false)
+ .setIsTonePlaying(false)
+ .setForegroundCallIsVoip(false)
+ .setSession(null)
+ .build());
+ waitForHandlerAction(sm.getHandler(), TEST_TIMEOUT);
+
+ assertEquals(CallAudioModeStateMachine.RING_STATE_NAME, sm.getCurrentStateName());
+
+ verify(mAudioManager).requestAudioFocusForCall(AudioManager.STREAM_RING,
+ AudioManager.AUDIOFOCUS_GAIN_TRANSIENT);
+ verify(mAudioManager).setMode(AudioManager.MODE_RINGTONE);
+ verify(mCallAudioManager).setCallAudioRouteFocusState(
+ CallAudioRouteStateMachine.RINGING_FOCUS);
+
+ sm.sendMessage(CallAudioModeStateMachine.RINGER_MODE_CHANGE);
+ waitForHandlerAction(sm.getHandler(), TEST_TIMEOUT);
+
+ // Make sure we don't try and start ringing again.
+ verify(mCallAudioManager, times(1)).startRinging();
+ }
private void resetMocks() {
clearInvocations(mCallAudioManager, mAudioManager);
diff --git a/tests/src/com/android/server/telecom/tests/CallRecordingTonePlayerTest.java b/tests/src/com/android/server/telecom/tests/CallRecordingTonePlayerTest.java
index 5151d4c..b5c6468 100644
--- a/tests/src/com/android/server/telecom/tests/CallRecordingTonePlayerTest.java
+++ b/tests/src/com/android/server/telecom/tests/CallRecordingTonePlayerTest.java
@@ -22,22 +22,37 @@
import static junit.framework.Assert.assertTrue;
import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.anyInt;
+import static org.mockito.ArgumentMatchers.nullable;
+import static org.mockito.Mockito.atLeast;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.reset;
+import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
import android.content.ComponentName;
+import android.content.ContentResolver;
+import android.content.Context;
+import android.media.AudioDeviceInfo;
import android.media.AudioFormat;
import android.media.AudioManager;
import android.media.AudioRecordingConfiguration;
+import android.media.MediaPlayer;
import android.media.MediaRecorder;
+import android.net.Uri;
import android.os.Handler;
import android.os.Looper;
import android.telecom.PhoneAccountHandle;
import android.test.suitebuilder.annotation.MediumTest;
+import com.android.dx.mockito.inline.extended.ExtendedMockito;
import com.android.server.telecom.Call;
import com.android.server.telecom.CallRecordingTonePlayer;
+import com.android.server.telecom.CallState;
import com.android.server.telecom.TelecomSystem;
+import com.android.server.telecom.Timeouts;
import org.junit.After;
import org.junit.Before;
@@ -48,6 +63,7 @@
import org.mockito.Mock;
import org.mockito.Mockito;
import org.mockito.MockitoAnnotations;
+import org.mockito.MockitoSession;
import java.util.ArrayList;
import java.util.List;
@@ -62,22 +78,29 @@
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 long TEST_RECORDING_TONE_INTERVAL = 300L;
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;
+ private TelecomSystem.SyncRoot mSyncRoot = new TelecomSystem.SyncRoot() {
+ };
+ @Mock
+ private AudioManager mAudioManager;
+ @Mock
+ private Timeouts.Adapter mTimeouts;
@Override
@Before
public void setUp() throws Exception {
super.setUp();
MockitoAnnotations.initMocks(this);
+ when(mTimeouts.getCallRecordingToneRepeatIntervalMillis(nullable(ContentResolver.class)))
+ .thenReturn(500L);
mCallRecordingTonePlayer = new CallRecordingTonePlayer(
mComponentContextFixture.getTestDouble().getApplicationContext(),
- mAudioManager, mSyncRoot);
+ mAudioManager, mTimeouts, mSyncRoot);
when(mAudioManager.getActiveRecordingConfigurations()).thenReturn(null);
}
@@ -87,6 +110,45 @@
super.tearDown();
}
+ @MediumTest
+ @Test
+ public void testToneLooping() throws Exception {
+ MediaPlayer mockMediaPlayer = mock(MediaPlayer.class);
+ MockitoSession session = ExtendedMockito.mockitoSession().mockStatic(MediaPlayer.class)
+ .startMocking();
+ ExtendedMockito.doReturn(mockMediaPlayer).when(() ->
+ MediaPlayer.create(nullable(Context.class), anyInt()));
+
+ when(mAudioManager.getActiveRecordingConfigurations()).thenReturn(
+ getAudioRecordingConfig(RECORDING_APP_PACKAGE));
+
+ AudioDeviceInfo mockAudioDeviceInfo = mock(AudioDeviceInfo.class);
+ when(mockAudioDeviceInfo.getType()).thenReturn(AudioDeviceInfo.TYPE_TELEPHONY);
+ when(mAudioManager.getDevices(AudioManager.GET_DEVICES_OUTPUTS))
+ .thenReturn(new AudioDeviceInfo[] { mockAudioDeviceInfo });
+
+ Call call = addValidCall();
+ when(call.isActive()).thenReturn(true);
+ mCallRecordingTonePlayer.onCallStateChanged(call, CallState.NEW, CallState.ACTIVE);
+
+ waitForHandlerAction(Handler.getMain(), TEST_TIMEOUT);
+ verify(mockMediaPlayer).start();
+
+ // Sleep for 4x the interval, then make sure it played more. No exact count,
+ // since timing can be tricky in tests.
+ Thread.sleep(TEST_RECORDING_TONE_INTERVAL * 4);
+ verify(mockMediaPlayer, atLeast(2)).start();
+ reset(mockMediaPlayer);
+
+ // Remove the call and verify that we're not starting the tone anymore.
+ mCallRecordingTonePlayer.onCallRemoved(call);
+ Thread.sleep(TEST_RECORDING_TONE_INTERVAL * 3 + 50);
+ verify(mockMediaPlayer, never()).start();
+ verify(mockMediaPlayer).release();
+
+ session.finishMocking();
+ }
+
/**
* Ensures that child calls are not tracked.
*/