Merge "Update ringer to take BT device into account"
diff --git a/src/com/android/server/telecom/CallAudioManager.java b/src/com/android/server/telecom/CallAudioManager.java
index 9eb8aea..227d5f5 100644
--- a/src/com/android/server/telecom/CallAudioManager.java
+++ b/src/com/android/server/telecom/CallAudioManager.java
@@ -424,7 +424,8 @@
@VisibleForTesting
public boolean startRinging() {
- return mRinger.startRinging(mForegroundCall);
+ return mRinger.startRinging(mForegroundCall,
+ mCallAudioRouteStateMachine.isHfpDeviceAvailable());
}
@VisibleForTesting
diff --git a/src/com/android/server/telecom/CallAudioRouteStateMachine.java b/src/com/android/server/telecom/CallAudioRouteStateMachine.java
index 7dfd78c..365ef4d 100644
--- a/src/com/android/server/telecom/CallAudioRouteStateMachine.java
+++ b/src/com/android/server/telecom/CallAudioRouteStateMachine.java
@@ -1323,6 +1323,10 @@
getHandler().getLooper().dump(pw::println, "");
}
+ public boolean isHfpDeviceAvailable() {
+ return mBluetoothRouteManager.isBluetoothAvailable();
+ }
+
/**
* Sets whether notifications should be suppressed or not. Used when in a call to ensure the
* device will not vibrate due to notifications.
diff --git a/src/com/android/server/telecom/InCallController.java b/src/com/android/server/telecom/InCallController.java
index 1c75c50..f74dc42 100644
--- a/src/com/android/server/telecom/InCallController.java
+++ b/src/com/android/server/telecom/InCallController.java
@@ -61,7 +61,7 @@
* can send updates to the in-call app. This class is created and owned by CallsManager and retains
* a binding to the {@link IInCallService} (implemented by the in-call app).
*/
-public final class InCallController extends CallsManagerListenerBase {
+public class InCallController extends CallsManagerListenerBase {
public class InCallServiceConnection {
/**
diff --git a/src/com/android/server/telecom/Ringer.java b/src/com/android/server/telecom/Ringer.java
index c5817e7..d955227 100644
--- a/src/com/android/server/telecom/Ringer.java
+++ b/src/com/android/server/telecom/Ringer.java
@@ -97,41 +97,44 @@
mInCallController = inCallController;
}
- public boolean startRinging(Call foregroundCall) {
- AudioManager audioManager =
- (AudioManager) mContext.getSystemService(Context.AUDIO_SERVICE);
- boolean isRingerAudible = audioManager.getStreamVolume(AudioManager.STREAM_RING) > 0;
-
- if (mSystemSettingsUtil.isTheaterModeOn(mContext)) {
- return false;
- }
-
+ public boolean startRinging(Call foregroundCall, boolean isHfpDeviceAttached) {
if (foregroundCall == null) {
Log.wtf(this, "startRinging called with null foreground call.");
return false;
}
- if (mInCallController.doesConnectedDialerSupportRinging()) {
- Log.addEvent(foregroundCall, LogUtils.Events.SKIP_RINGING);
- return isRingerAudible;
- }
+ AudioManager audioManager =
+ (AudioManager) mContext.getSystemService(Context.AUDIO_SERVICE);
+ boolean isVolumeOverZero = audioManager.getStreamVolume(AudioManager.STREAM_RING) > 0;
+ boolean shouldRingForContact = shouldRingForContact(foregroundCall.getContactUri());
+ boolean isRingtonePresent = !(mRingtoneFactory.getRingtone(foregroundCall) == null);
+ boolean isSelfManaged = foregroundCall.isSelfManaged();
- if (foregroundCall.isSelfManaged()) {
- Log.addEvent(foregroundCall, LogUtils.Events.SKIP_RINGING, "Self-managed");
- return false;
+ boolean isRingerAudible = isVolumeOverZero && shouldRingForContact && isRingtonePresent;
+ // Acquire audio focus under any of the following conditions:
+ // 1. Should ring for contact and there's an HFP device attached
+ // 2. Volume is over zero, we should ring for the contact, and there's a audible ringtone
+ // present.
+ // 3. The call is self-managed.
+ boolean shouldAcquireAudioFocus =
+ isRingerAudible || (isHfpDeviceAttached && shouldRingForContact) || isSelfManaged;
+
+ // Don't do call waiting operations or vibration unless these are false.
+ boolean isTheaterModeOn = mSystemSettingsUtil.isTheaterModeOn(mContext);
+ boolean letDialerHandleRinging = mInCallController.doesConnectedDialerSupportRinging();
+ boolean endEarly = isTheaterModeOn || letDialerHandleRinging || isSelfManaged;
+
+ if (endEarly) {
+ if (letDialerHandleRinging) {
+ Log.addEvent(foregroundCall, LogUtils.Events.SKIP_RINGING);
+ }
+ Log.i(this, "Ending early -- isTheaterModeOn=%s, letDialerHandleRinging=%s, " +
+ "isSelfManaged=%s", isTheaterModeOn, letDialerHandleRinging, isSelfManaged);
+ return shouldAcquireAudioFocus;
}
stopCallWaiting();
- if (!shouldRingForContact(foregroundCall.getContactUri())) {
- return false;
- }
-
- // Don't ring/acquire focus if there is no ringtone
- if (mRingtoneFactory.getRingtone(foregroundCall) == null) {
- isRingerAudible = false;
- }
-
if (isRingerAudible) {
mRingingCall = foregroundCall;
Log.addEvent(foregroundCall, LogUtils.Events.START_RINGER);
@@ -141,10 +144,12 @@
// request the custom ringtone from the call and expect it to be current.
mRingtonePlayer.play(mRingtoneFactory, foregroundCall);
} else {
- Log.i(this, "startRingingOrCallWaiting, skipping because volume is 0");
+ Log.i(this, "startRinging: skipping because ringer would not be audible. " +
+ "isVolumeOverZero=%s, shouldRingForContact=%s, isRingtonePresent=%s",
+ isVolumeOverZero, shouldRingForContact, isRingtonePresent);
}
- if (shouldVibrate(mContext, foregroundCall) && !mIsVibrating) {
+ if (shouldVibrate(mContext, foregroundCall) && !mIsVibrating && shouldRingForContact) {
mVibratingCall = foregroundCall;
mVibrator.vibrate(VIBRATION_PATTERN, VIBRATION_PATTERN_REPEAT,
VIBRATION_ATTRIBUTES);
@@ -153,7 +158,7 @@
Log.addEvent(foregroundCall, LogUtils.Events.SKIP_VIBRATION, "already vibrating");
}
- return isRingerAudible;
+ return shouldAcquireAudioFocus;
}
public void startCallWaiting(Call call) {
diff --git a/tests/src/com/android/server/telecom/tests/RingerTest.java b/tests/src/com/android/server/telecom/tests/RingerTest.java
new file mode 100644
index 0000000..8b269a9
--- /dev/null
+++ b/tests/src/com/android/server/telecom/tests/RingerTest.java
@@ -0,0 +1,224 @@
+/*
+ * Copyright (C) 2017 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 android.app.NotificationManager;
+import android.content.Context;
+import android.media.AudioAttributes;
+import android.media.AudioManager;
+import android.media.Ringtone;
+import android.os.Bundle;
+import android.os.Vibrator;
+import android.test.suitebuilder.annotation.SmallTest;
+
+import com.android.server.telecom.AsyncRingtonePlayer;
+import com.android.server.telecom.Call;
+import com.android.server.telecom.InCallController;
+import com.android.server.telecom.InCallTonePlayer;
+import com.android.server.telecom.Ringer;
+import com.android.server.telecom.RingtoneFactory;
+import com.android.server.telecom.SystemSettingsUtil;
+
+import org.mockito.Mock;
+
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.anyInt;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+public class RingerTest extends TelecomTestCase {
+ @Mock InCallTonePlayer.Factory mockPlayerFactory;
+ @Mock SystemSettingsUtil mockSystemSettingsUtil;
+ @Mock AsyncRingtonePlayer mockRingtonePlayer;
+ @Mock RingtoneFactory mockRingtoneFactory;
+ @Mock Vibrator mockVibrator;
+ @Mock InCallController mockInCallController;
+
+ @Mock InCallTonePlayer mockTonePlayer;
+ @Mock Call mockCall1;
+ @Mock Call mockCall2;
+
+ Ringer mRingerUnderTest;
+ AudioManager mockAudioManager;
+ public void setUp() throws Exception {
+ super.setUp();
+ mContext = mComponentContextFixture.getTestDouble().getApplicationContext();
+ mRingerUnderTest = new Ringer(mockPlayerFactory, mContext, mockSystemSettingsUtil,
+ mockRingtonePlayer, mockRingtoneFactory, mockVibrator, mockInCallController);
+ when(mockPlayerFactory.createPlayer(anyInt())).thenReturn(mockTonePlayer);
+ mockAudioManager =
+ (AudioManager) mContext.getSystemService(Context.AUDIO_SERVICE);
+ when(mockAudioManager.getRingerModeInternal()).thenReturn(AudioManager.RINGER_MODE_NORMAL);
+ NotificationManager notificationManager =
+ (NotificationManager) mContext.getSystemService(Context.NOTIFICATION_SERVICE);
+ when(notificationManager.matchesCallFilter(any(Bundle.class))).thenReturn(true);
+ }
+
+ @SmallTest
+ public void testNoActionInTheaterMode() {
+ // Start call waiting to make sure that it doesn't stop when we start ringing
+ mRingerUnderTest.startCallWaiting(mockCall1);
+ when(mockSystemSettingsUtil.isTheaterModeOn(any(Context.class))).thenReturn(true);
+ assertFalse(mRingerUnderTest.startRinging(mockCall2, false));
+ verify(mockTonePlayer, never()).stopTone();
+ verify(mockRingtonePlayer, never()).play(any(RingtoneFactory.class), any(Call.class));
+ verify(mockVibrator, never()).vibrate(
+ any(long[].class), anyInt(), any(AudioAttributes.class));
+ }
+
+ @SmallTest
+ public void testNoActionWhenDialerRings() {
+ // Start call waiting to make sure that it doesn't stop when we start ringing
+ mRingerUnderTest.startCallWaiting(mockCall1);
+ when(mockInCallController.doesConnectedDialerSupportRinging()).thenReturn(true);
+ assertFalse(mRingerUnderTest.startRinging(mockCall2, false));
+ verify(mockTonePlayer, never()).stopTone();
+ verify(mockRingtonePlayer, never()).play(any(RingtoneFactory.class), any(Call.class));
+ verify(mockVibrator, never()).vibrate(
+ any(long[].class), anyInt(), any(AudioAttributes.class));
+ }
+
+ @SmallTest
+ public void testAudioFocusStillAcquiredWhenDialerRings() {
+ // Start call waiting to make sure that it doesn't stop when we start ringing
+ mRingerUnderTest.startCallWaiting(mockCall1);
+ when(mockInCallController.doesConnectedDialerSupportRinging()).thenReturn(true);
+ ensureRingerIsAudible();
+ assertTrue(mRingerUnderTest.startRinging(mockCall2, false));
+ verify(mockTonePlayer, never()).stopTone();
+ verify(mockRingtonePlayer, never()).play(any(RingtoneFactory.class), any(Call.class));
+ verify(mockVibrator, never()).vibrate(
+ any(long[].class), anyInt(), any(AudioAttributes.class));
+ }
+
+ @SmallTest
+ public void testNoActionWhenCallIsSelfManaged() {
+ // Start call waiting to make sure that it doesn't stop when we start ringing
+ mRingerUnderTest.startCallWaiting(mockCall1);
+ when(mockCall2.isSelfManaged()).thenReturn(true);
+ // We do want to acquire audio focus when self-managed
+ assertTrue(mRingerUnderTest.startRinging(mockCall2, true));
+ verify(mockTonePlayer, never()).stopTone();
+ verify(mockRingtonePlayer, never()).play(any(RingtoneFactory.class), any(Call.class));
+ verify(mockVibrator, never()).vibrate(
+ any(long[].class), anyInt(), any(AudioAttributes.class));
+ }
+
+ @SmallTest
+ public void testCallWaitingButNoRingForSpecificContacts() {
+ NotificationManager notificationManager =
+ (NotificationManager) mContext.getSystemService(Context.NOTIFICATION_SERVICE);
+ when(notificationManager.matchesCallFilter(any(Bundle.class))).thenReturn(false);
+ // Start call waiting to make sure that it does stop when we start ringing
+ mRingerUnderTest.startCallWaiting(mockCall1);
+ verify(mockTonePlayer).startTone();
+
+ assertFalse(mRingerUnderTest.startRinging(mockCall2, false));
+ verify(mockTonePlayer).stopTone();
+ verify(mockRingtonePlayer, never()).play(any(RingtoneFactory.class), any(Call.class));
+ verify(mockVibrator, never()).vibrate(
+ any(long[].class), anyInt(), any(AudioAttributes.class));
+ }
+
+ @SmallTest
+ public void testVibrateButNoRingForNullRingtone() {
+ mRingerUnderTest.startCallWaiting(mockCall1);
+ when(mockRingtoneFactory.getRingtone(any(Call.class))).thenReturn(null);
+ when(mockAudioManager.getRingerModeInternal()).thenReturn(AudioManager.RINGER_MODE_NORMAL);
+ enableVibrationWhenRinging();
+ assertFalse(mRingerUnderTest.startRinging(mockCall2, false));
+ verify(mockTonePlayer).stopTone();
+ verify(mockRingtonePlayer, never()).play(any(RingtoneFactory.class), any(Call.class));
+ verify(mockVibrator).vibrate(
+ any(long[].class), anyInt(), any(AudioAttributes.class));
+ }
+
+ @SmallTest
+ public void testVibrateButNoRingForSilentRingtone() {
+ mRingerUnderTest.startCallWaiting(mockCall1);
+ Ringtone mockRingtone = mock(Ringtone.class);
+ when(mockRingtoneFactory.getRingtone(any(Call.class))).thenReturn(mockRingtone);
+ when(mockAudioManager.getRingerModeInternal()).thenReturn(AudioManager.RINGER_MODE_NORMAL);
+ when(mockAudioManager.getStreamVolume(AudioManager.STREAM_RING)).thenReturn(0);
+ enableVibrationWhenRinging();
+ assertFalse(mRingerUnderTest.startRinging(mockCall2, false));
+ verify(mockTonePlayer).stopTone();
+ verify(mockRingtonePlayer, never()).play(any(RingtoneFactory.class), any(Call.class));
+ verify(mockVibrator).vibrate(
+ any(long[].class), anyInt(), any(AudioAttributes.class));
+ }
+
+ @SmallTest
+ public void testRingAndNoVibrate() {
+ mRingerUnderTest.startCallWaiting(mockCall1);
+ ensureRingerIsAudible();
+ enableVibrationOnlyWhenNotRinging();
+ assertTrue(mRingerUnderTest.startRinging(mockCall2, false));
+ verify(mockTonePlayer).stopTone();
+ verify(mockRingtonePlayer).play(any(RingtoneFactory.class), any(Call.class));
+ verify(mockVibrator, never()).vibrate(
+ any(long[].class), anyInt(), any(AudioAttributes.class));
+ }
+
+ @SmallTest
+ public void testSilentRingWithHfpStillAcquiresFocus1() {
+ mRingerUnderTest.startCallWaiting(mockCall1);
+ Ringtone mockRingtone = mock(Ringtone.class);
+ when(mockRingtoneFactory.getRingtone(any(Call.class))).thenReturn(mockRingtone);
+ when(mockAudioManager.getRingerModeInternal()).thenReturn(AudioManager.RINGER_MODE_NORMAL);
+ when(mockAudioManager.getStreamVolume(AudioManager.STREAM_RING)).thenReturn(0);
+ enableVibrationOnlyWhenNotRinging();
+ assertTrue(mRingerUnderTest.startRinging(mockCall2, true));
+ verify(mockTonePlayer).stopTone();
+ verify(mockRingtonePlayer, never()).play(any(RingtoneFactory.class), any(Call.class));
+ verify(mockVibrator, never()).vibrate(
+ any(long[].class), anyInt(), any(AudioAttributes.class));
+ }
+
+ @SmallTest
+ public void testSilentRingWithHfpStillAcquiresFocus2() {
+ mRingerUnderTest.startCallWaiting(mockCall1);
+ when(mockRingtoneFactory.getRingtone(any(Call.class))).thenReturn(null);
+ when(mockAudioManager.getRingerModeInternal()).thenReturn(AudioManager.RINGER_MODE_NORMAL);
+ when(mockAudioManager.getStreamVolume(AudioManager.STREAM_RING)).thenReturn(0);
+ enableVibrationOnlyWhenNotRinging();
+ assertTrue(mRingerUnderTest.startRinging(mockCall2, true));
+ verify(mockTonePlayer).stopTone();
+ verify(mockRingtonePlayer, never()).play(any(RingtoneFactory.class), any(Call.class));
+ verify(mockVibrator, never()).vibrate(
+ any(long[].class), anyInt(), any(AudioAttributes.class));
+ }
+
+ private void ensureRingerIsAudible() {
+ Ringtone mockRingtone = mock(Ringtone.class);
+ when(mockRingtoneFactory.getRingtone(any(Call.class))).thenReturn(mockRingtone);
+ when(mockAudioManager.getRingerModeInternal()).thenReturn(AudioManager.RINGER_MODE_NORMAL);
+ when(mockAudioManager.getStreamVolume(AudioManager.STREAM_RING)).thenReturn(100);
+ }
+
+ private void enableVibrationWhenRinging() {
+ when(mockVibrator.hasVibrator()).thenReturn(true);
+ when(mockSystemSettingsUtil.canVibrateWhenRinging(any(Context.class))).thenReturn(true);
+ }
+
+ private void enableVibrationOnlyWhenNotRinging() {
+ when(mockVibrator.hasVibrator()).thenReturn(true);
+ when(mockSystemSettingsUtil.canVibrateWhenRinging(any(Context.class))).thenReturn(false);
+ }
+}