Update ringer to take BT device into account

When there is an HFP device connected but the phone would otherwise not
play an audible ringtone, acquire audio focus anyway so that music won't
play over top of the BT device-generated sound.

Change-Id: I898dddfb29147debf5d520583bed02d2012b6cb7
Fixes: 34084026
Test: unit tests and manual
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);
+    }
+}