Merge "Update route when speakerphone changed externally" am: 44a7e4f147 am: 4db4bfcaaf
am: 1eedd53966

Change-Id: Ibe0913145db3dd5d3d6e89511fbd3333a6a3e5d1
diff --git a/src/com/android/server/telecom/CallAudioRouteStateMachine.java b/src/com/android/server/telecom/CallAudioRouteStateMachine.java
index 3a5d113..82d245d 100644
--- a/src/com/android/server/telecom/CallAudioRouteStateMachine.java
+++ b/src/com/android/server/telecom/CallAudioRouteStateMachine.java
@@ -123,6 +123,11 @@
     // Wired headset, earpiece, or speakerphone, in that order of precedence.
     public static final int SWITCH_BASELINE_ROUTE = 1005;
 
+    // Messages denoting that the speakerphone was turned on/off. Used to update state when we
+    // weren't the ones who turned it on/off
+    public static final int SPEAKER_ON = 1006;
+    public static final int SPEAKER_OFF = 1007;
+
     public static final int USER_SWITCH_EARPIECE = 1101;
     public static final int USER_SWITCH_BLUETOOTH = 1102;
     public static final int USER_SWITCH_HEADSET = 1103;
@@ -181,6 +186,8 @@
         put(SWITCH_HEADSET, "SWITCH_HEADSET");
         put(SWITCH_SPEAKER, "SWITCH_SPEAKER");
         put(SWITCH_BASELINE_ROUTE, "SWITCH_BASELINE_ROUTE");
+        put(SPEAKER_ON, "SPEAKER_ON");
+        put(SPEAKER_OFF, "SPEAKER_OFF");
 
         put(USER_SWITCH_EARPIECE, "USER_SWITCH_EARPIECE");
         put(USER_SWITCH_BLUETOOTH, "USER_SWITCH_BLUETOOTH");
@@ -374,6 +381,7 @@
             switch (msg.what) {
                 case SWITCH_EARPIECE:
                 case USER_SWITCH_EARPIECE:
+                case SPEAKER_OFF:
                     // Nothing to do here
                     return HANDLED;
                 case BT_AUDIO_CONNECTED:
@@ -405,6 +413,7 @@
                     return HANDLED;
                 case SWITCH_SPEAKER:
                 case USER_SWITCH_SPEAKER:
+                case SPEAKER_ON:
                     transitionTo(mActiveSpeakerRoute);
                     return HANDLED;
                 case SWITCH_FOCUS:
@@ -449,6 +458,7 @@
             switch (msg.what) {
                 case SWITCH_EARPIECE:
                 case USER_SWITCH_EARPIECE:
+                case SPEAKER_OFF:
                     // Nothing to do here
                     return HANDLED;
                 case BT_AUDIO_CONNECTED:
@@ -473,6 +483,7 @@
                     return HANDLED;
                 case SWITCH_SPEAKER:
                 case USER_SWITCH_SPEAKER:
+                case SPEAKER_ON:
                     transitionTo(mQuiescentSpeakerRoute);
                     return HANDLED;
                 case SWITCH_FOCUS:
@@ -594,10 +605,12 @@
                     return HANDLED;
                 case SWITCH_HEADSET:
                 case USER_SWITCH_HEADSET:
+                case SPEAKER_OFF:
                     // Nothing to do
                     return HANDLED;
                 case SWITCH_SPEAKER:
                 case USER_SWITCH_SPEAKER:
+                case SPEAKER_ON:
                     transitionTo(mActiveSpeakerRoute);
                     return HANDLED;
                 case SWITCH_FOCUS:
@@ -662,10 +675,12 @@
                     return HANDLED;
                 case SWITCH_HEADSET:
                 case USER_SWITCH_HEADSET:
+                case SPEAKER_OFF:
                     // Nothing to do
                     return HANDLED;
                 case SWITCH_SPEAKER:
                 case USER_SWITCH_SPEAKER:
+                case SPEAKER_ON:
                     transitionTo(mQuiescentSpeakerRoute);
                     return HANDLED;
                 case SWITCH_FOCUS:
@@ -835,9 +850,12 @@
                     mHasUserExplicitlyLeftBluetooth = true;
                     // fall through
                 case SWITCH_SPEAKER:
+                case SPEAKER_ON:
                     setBluetoothOff();
                     transitionTo(mActiveSpeakerRoute);
                     return HANDLED;
+                case SPEAKER_OFF:
+                    return HANDLED;
                 case SWITCH_FOCUS:
                     if (msg.arg1 == NO_FOCUS) {
                         // Only disconnect SCO audio here instead of routing away from BT entirely.
@@ -926,8 +944,11 @@
                     mHasUserExplicitlyLeftBluetooth = true;
                     // fall through
                 case SWITCH_SPEAKER:
+                case SPEAKER_ON:
                     transitionTo(mActiveSpeakerRoute);
                     return HANDLED;
+                case SPEAKER_OFF:
+                    return HANDLED;
                 case SWITCH_FOCUS:
                     if (msg.arg1 == NO_FOCUS) {
                         reinitialize();
@@ -987,6 +1008,7 @@
                     return HANDLED;
                 case SWITCH_BLUETOOTH:
                 case USER_SWITCH_BLUETOOTH:
+                case SPEAKER_OFF:
                     // Nothing to do
                     return HANDLED;
                 case SWITCH_HEADSET:
@@ -999,6 +1021,7 @@
                     return HANDLED;
                 case SWITCH_SPEAKER:
                 case USER_SWITCH_SPEAKER:
+                case SPEAKER_ON:
                     transitionTo(mQuiescentSpeakerRoute);
                     return HANDLED;
                 case SWITCH_FOCUS:
@@ -1143,6 +1166,12 @@
                 case USER_SWITCH_SPEAKER:
                     // Nothing to do
                     return HANDLED;
+                case SPEAKER_ON:
+                    // Expected, since we just transitioned here
+                    return HANDLED;
+                case SPEAKER_OFF:
+                    sendInternalMessage(SWITCH_BASELINE_ROUTE, INCLUDE_BLUETOOTH_IN_BASELINE);
+                    return HANDLED;
                 case SWITCH_FOCUS:
                     if (msg.arg1 == NO_FOCUS) {
                         reinitialize();
@@ -1215,8 +1244,12 @@
                     return HANDLED;
                 case SWITCH_SPEAKER:
                 case USER_SWITCH_SPEAKER:
+                case SPEAKER_ON:
                     // Nothing to do
                     return HANDLED;
+                case SPEAKER_OFF:
+                    sendInternalMessage(SWITCH_BASELINE_ROUTE, INCLUDE_BLUETOOTH_IN_BASELINE);
+                    return HANDLED;
                 case SWITCH_FOCUS:
                     if (msg.arg1 == ACTIVE_FOCUS || msg.arg1 == RINGING_FOCUS) {
                         transitionTo(mActiveSpeakerRoute);
@@ -1294,6 +1327,28 @@
         }
     };
 
+    private final BroadcastReceiver mSpeakerPhoneChangeReceiver = new BroadcastReceiver() {
+        @Override
+        public void onReceive(Context context, Intent intent) {
+            Log.startSession("CARSM.mSPCR");
+            try {
+                if (AudioManager.ACTION_SPEAKERPHONE_STATE_CHANGED.equals(intent.getAction())) {
+                    if (mAudioManager != null) {
+                        if (mAudioManager.isSpeakerphoneOn()) {
+                            sendInternalMessage(SPEAKER_ON);
+                        } else {
+                            sendInternalMessage(SPEAKER_OFF);
+                        }
+                    }
+                } else {
+                    Log.w(this, "Received non-speakerphone-change intent");
+                }
+            } finally {
+                Log.endSession();
+            }
+        }
+    };
+
     private final ActiveEarpieceRoute mActiveEarpieceRoute = new ActiveEarpieceRoute();
     private final ActiveHeadsetRoute mActiveHeadsetRoute = new ActiveHeadsetRoute();
     private final ActiveBluetoothRoute mActiveBluetoothRoute = new ActiveBluetoothRoute();
@@ -1446,6 +1501,8 @@
         mWasOnSpeaker = false;
         mContext.registerReceiver(mMuteChangeReceiver,
                 new IntentFilter(AudioManager.ACTION_MICROPHONE_MUTE_CHANGED));
+        mContext.registerReceiver(mSpeakerPhoneChangeReceiver,
+                new IntentFilter(AudioManager.ACTION_SPEAKERPHONE_STATE_CHANGED));
 
         mStatusBarNotifier.notifyMute(initState.isMuted());
         mStatusBarNotifier.notifySpeakerphone(initState.getRoute() == CallAudioState.ROUTE_SPEAKER);
@@ -1531,8 +1588,12 @@
     }
 
     private void setSpeakerphoneOn(boolean on) {
-        Log.i(this, "turning speaker phone %s", on);
-        mAudioManager.setSpeakerphoneOn(on);
+        if (mAudioManager.isSpeakerphoneOn() != on) {
+            Log.i(this, "turning speaker phone %s", on);
+            mAudioManager.setSpeakerphoneOn(on);
+        } else {
+            Log.i(this, "Ignoring speakerphone request -- already %s", on);
+        }
         mStatusBarNotifier.notifySpeakerphone(on);
     }
 
diff --git a/tests/src/com/android/server/telecom/tests/BasicCallTests.java b/tests/src/com/android/server/telecom/tests/BasicCallTests.java
index 2a284d2..09d765a 100644
--- a/tests/src/com/android/server/telecom/tests/BasicCallTests.java
+++ b/tests/src/com/android/server/telecom/tests/BasicCallTests.java
@@ -615,7 +615,7 @@
         waitForHandlerAction(mTelecomSystem.getCallsManager().getCallAudioManager()
                 .getCallAudioRouteStateMachine().getHandler(), TEST_TIMEOUT);
         // setSpeakerPhoneOn(false) gets called once during the call initiation phase
-        verify(audioManager, timeout(TEST_TIMEOUT).atLeast(2))
+        verify(audioManager, timeout(TEST_TIMEOUT).atLeast(1))
                 .setSpeakerphoneOn(false);
 
         mConnectionServiceFixtureA.
diff --git a/tests/src/com/android/server/telecom/tests/CallAudioRouteTransitionTests.java b/tests/src/com/android/server/telecom/tests/CallAudioRouteTransitionTests.java
index 57462c4..58f1ee7 100644
--- a/tests/src/com/android/server/telecom/tests/CallAudioRouteTransitionTests.java
+++ b/tests/src/com/android/server/telecom/tests/CallAudioRouteTransitionTests.java
@@ -219,8 +219,18 @@
             return null;
         }).when(mockBluetoothRouteManager).connectBluetoothAudio(nullable(String.class));
 
-        when(mockAudioManager.isSpeakerphoneOn()).thenReturn(
-                params.initialRoute == CallAudioState.ROUTE_SPEAKER);
+        // Set the speakerphone state depending on the message being sent. If it's one of the
+        // speakerphone override ones, set accordingly. Otherwise consult the initial route.
+        boolean speakerphoneOn;
+        if (params.action == CallAudioRouteStateMachine.SPEAKER_ON) {
+            speakerphoneOn = true;
+        } else if (params.action == CallAudioRouteStateMachine.SPEAKER_OFF) {
+            speakerphoneOn = false;
+        } else {
+            speakerphoneOn = params.initialRoute == CallAudioState.ROUTE_SPEAKER;
+        }
+        when(mockAudioManager.isSpeakerphoneOn()).thenReturn(speakerphoneOn);
+
         when(fakeCall.getSupportedAudioRoutes()).thenReturn(params.callSupportedRoutes);
     }
 
@@ -757,6 +767,58 @@
                 CallAudioRouteStateMachine.EARPIECE_FORCE_ENABLED // earpieceControl
         ));
 
+        params.add(new RoutingTestParameters(
+                "Speakerphone turned on during earpiece", // name
+                CallAudioState.ROUTE_EARPIECE, // initialRoute
+                CallAudioState.ROUTE_EARPIECE | CallAudioState.ROUTE_BLUETOOTH, // availableRoutes
+                NONE, // speakerInteraction
+                NONE, // bluetoothInteraction
+                CallAudioRouteStateMachine.SPEAKER_ON, // action
+                CallAudioState.ROUTE_SPEAKER, // expectedRoute
+                CallAudioState.ROUTE_EARPIECE | CallAudioState.ROUTE_BLUETOOTH, // expectedAvailabl
+                CallAudioRouteStateMachine.EARPIECE_FORCE_ENABLED // earpieceControl
+        ));
+
+        params.add(new RoutingTestParameters(
+                "Speakerphone turned on during wired headset", // name
+                CallAudioState.ROUTE_WIRED_HEADSET, // initialRoute
+                CallAudioState.ROUTE_EARPIECE
+                        | CallAudioState.ROUTE_BLUETOOTH
+                        | CallAudioState.ROUTE_WIRED_HEADSET, // availableRoutes
+                NONE, // speakerInteraction
+                NONE, // bluetoothInteraction
+                CallAudioRouteStateMachine.SPEAKER_ON, // action
+                CallAudioState.ROUTE_SPEAKER, // expectedRoute
+                CallAudioState.ROUTE_EARPIECE
+                        | CallAudioState.ROUTE_BLUETOOTH
+                        | CallAudioState.ROUTE_WIRED_HEADSET, // availableRoutes
+                CallAudioRouteStateMachine.EARPIECE_FORCE_ENABLED // earpieceControl
+        ));
+
+        params.add(new RoutingTestParameters(
+                "Speakerphone turned on during bluetooth", // name
+                CallAudioState.ROUTE_BLUETOOTH, // initialRoute
+                CallAudioState.ROUTE_EARPIECE | CallAudioState.ROUTE_BLUETOOTH, // availableRoutes
+                NONE, // speakerInteraction
+                OFF, // bluetoothInteraction
+                CallAudioRouteStateMachine.SPEAKER_ON, // action
+                CallAudioState.ROUTE_SPEAKER, // expectedRoute
+                CallAudioState.ROUTE_EARPIECE | CallAudioState.ROUTE_BLUETOOTH, // expectedAvailabl
+                CallAudioRouteStateMachine.EARPIECE_FORCE_ENABLED // earpieceControl
+        ));
+
+        params.add(new RoutingTestParameters(
+                "Speakerphone turned off externally during speaker", // name
+                CallAudioState.ROUTE_SPEAKER, // initialRoute
+                CallAudioState.ROUTE_EARPIECE | CallAudioState.ROUTE_BLUETOOTH, // availableRoutes
+                NONE, // speakerInteraction
+                ON, // bluetoothInteraction
+                CallAudioRouteStateMachine.SPEAKER_OFF, // action
+                CallAudioState.ROUTE_BLUETOOTH, // expectedRoute
+                CallAudioState.ROUTE_EARPIECE | CallAudioState.ROUTE_BLUETOOTH, // expectedAvailabl
+                CallAudioRouteStateMachine.EARPIECE_FORCE_ENABLED // earpieceControl
+        ));
+
         return params;
     }