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);
+    }
+}