DO NOT MERGE ANYWHERE

Handle audio route restrictions set on calls.

When a call restricts audio routes, it will move the audio route to a
supported route if available. The supported routes are determined initially
and re-evaluated when the call becomes the foreground call.

Bug: 25485578
Change-Id: I54b9bf17495eb2b0dc73b822ad16485a4673a779
diff --git a/src/com/android/server/telecom/Call.java b/src/com/android/server/telecom/Call.java
index 714e7fd..0cf2373 100644
--- a/src/com/android/server/telecom/Call.java
+++ b/src/com/android/server/telecom/Call.java
@@ -296,6 +296,8 @@
 
     private int mConnectionCapabilities;
 
+    private int mSupportedAudioRoutes;
+
     private boolean mIsConference = false;
 
     private Call mParentCall = null;
@@ -808,6 +810,16 @@
         }
     }
 
+    int getSupportedAudioRoutes() {
+        return mSupportedAudioRoutes;
+    }
+
+    void setSupportedAudioRoutes(int audioRoutes) {
+        if (mSupportedAudioRoutes != audioRoutes) {
+            mSupportedAudioRoutes = audioRoutes;
+        }
+    }
+
     Call getParentCall() {
         return mParentCall;
     }
@@ -911,6 +923,7 @@
         setCallerDisplayName(
                 connection.getCallerDisplayName(), connection.getCallerDisplayNamePresentation());
         setConnectionCapabilities(connection.getConnectionCapabilities());
+        setSupportedAudioRoutes(connection.getSupportedAudioRoutes());
         setVideoProvider(connection.getVideoProvider());
         setVideoState(connection.getVideoState());
         setRingbackRequested(connection.isRingbackRequested());
diff --git a/src/com/android/server/telecom/CallAudioManager.java b/src/com/android/server/telecom/CallAudioManager.java
index 23284e3..af6f98c 100644
--- a/src/com/android/server/telecom/CallAudioManager.java
+++ b/src/com/android/server/telecom/CallAudioManager.java
@@ -253,6 +253,12 @@
     @Override
     public void onForegroundCallChanged(Call oldForegroundCall, Call newForegroundCall) {
         onCallUpdated(newForegroundCall);
+
+        // Set the system audio state again in case the current route is not permitted by the new
+        // foreground call.
+        setSystemAudioState(mCallAudioState.isMuted(), mCallAudioState.getRoute(),
+                calculateSupportedRoutes(newForegroundCall));
+
         // Ensure that the foreground call knows about the latest audio state.
         updateAudioForForegroundCall();
     }
@@ -472,6 +478,20 @@
         }
 
         CallAudioState oldAudioState = mCallAudioState;
+
+        // If the currently selected route is not supported, switch to another supported route
+        if ((route & supportedRouteMask) == 0) {
+            if ((CallAudioState.ROUTE_EARPIECE & supportedRouteMask) != 0) {
+                route = CallAudioState.ROUTE_EARPIECE;
+            } else if ((CallAudioState.ROUTE_WIRED_HEADSET & supportedRouteMask) != 0) {
+                route = CallAudioState.ROUTE_WIRED_HEADSET;
+            } else if ((CallAudioState.ROUTE_SPEAKER & supportedRouteMask) != 0) {
+                route = CallAudioState.ROUTE_SPEAKER;
+            } else if ((CallAudioState.ROUTE_BLUETOOTH & supportedRouteMask) != 0) {
+                route = CallAudioState.ROUTE_BLUETOOTH;
+            }
+        }
+
         saveAudioState(new CallAudioState(isMuted, route, supportedRouteMask));
         if (!force && Objects.equals(oldAudioState, mCallAudioState)) {
             return;
@@ -645,6 +665,10 @@
     }
 
     private int calculateSupportedRoutes() {
+        return calculateSupportedRoutes(getForegroundCall());
+    }
+
+    private int calculateSupportedRoutes(Call call) {
         int routeMask = CallAudioState.ROUTE_SPEAKER;
 
         if (mWiredHeadsetManager.isPluggedIn()) {
@@ -657,11 +681,11 @@
             routeMask |=  CallAudioState.ROUTE_BLUETOOTH;
         }
 
-        return routeMask;
+        return call != null ? routeMask & call.getSupportedAudioRoutes() : routeMask;
     }
 
     private CallAudioState getInitialAudioState(Call call) {
-        int supportedRouteMask = calculateSupportedRoutes();
+        int supportedRouteMask = calculateSupportedRoutes(call);
         int route = selectWiredOrEarpiece(
                 CallAudioState.ROUTE_WIRED_OR_EARPIECE, supportedRouteMask);
 
diff --git a/src/com/android/server/telecom/InCallController.java b/src/com/android/server/telecom/InCallController.java
index 9562ea7..1011910 100644
--- a/src/com/android/server/telecom/InCallController.java
+++ b/src/com/android/server/telecom/InCallController.java
@@ -590,6 +590,8 @@
         int state = getParcelableState(call);
         int capabilities = convertConnectionToCallCapabilities(call.getConnectionCapabilities());
         int properties = convertConnectionToCallProperties(call.getConnectionCapabilities());
+        int supportedAudioRoutes = call.getSupportedAudioRoutes();
+
         if (call.isConference()) {
             properties |= android.telecom.Call.Details.PROPERTY_CONFERENCE;
         }
@@ -659,6 +661,7 @@
                 call.getCannedSmsResponses(),
                 capabilities,
                 properties,
+                supportedAudioRoutes,
                 connectTimeMillis,
                 handle,
                 call.getHandlePresentation(),
diff --git a/src/com/android/server/telecom/PhoneAccountRegistrar.java b/src/com/android/server/telecom/PhoneAccountRegistrar.java
index aab193e..a4b8992 100644
--- a/src/com/android/server/telecom/PhoneAccountRegistrar.java
+++ b/src/com/android/server/telecom/PhoneAccountRegistrar.java
@@ -35,6 +35,7 @@
 import android.os.UserHandle;
 import android.os.UserManager;
 import android.provider.Settings;
+import android.telecom.CallAudioState;
 import android.telecom.ConnectionService;
 import android.telecom.DefaultDialerManager;
 import android.telecom.PhoneAccount;
@@ -121,7 +122,7 @@
 
     private static final String FILE_NAME = "phone-account-registrar-state.xml";
     @VisibleForTesting
-    public static final int EXPECTED_STATE_VERSION = 8;
+    public static final int EXPECTED_STATE_VERSION = 9;
 
     /** Keep in sync with the same in SipSettings.java */
     private static final String SIP_SHARED_PREFERENCES = "SIP_PREFERENCES";
@@ -1147,6 +1148,7 @@
         private static final String ADDRESS = "handle";
         private static final String SUBSCRIPTION_ADDRESS = "subscription_number";
         private static final String CAPABILITIES = "capabilities";
+        private static final String SUPPORTED_AUDIO_ROUTES = "supported_audio_routes";
         private static final String ICON_RES_ID = "icon_res_id";
         private static final String ICON_PACKAGE_NAME = "icon_package_name";
         private static final String ICON_BITMAP = "icon_bitmap";
@@ -1179,7 +1181,9 @@
                 writeTextIfNonNull(LABEL, o.getLabel(), serializer);
                 writeTextIfNonNull(SHORT_DESCRIPTION, o.getShortDescription(), serializer);
                 writeStringList(SUPPORTED_URI_SCHEMES, o.getSupportedUriSchemes(), serializer);
-                writeTextIfNonNull(ENABLED, o.isEnabled() ? "true" : "false" , serializer);
+                writeTextIfNonNull(ENABLED, o.isEnabled() ? "true" : "false", serializer);
+                writeTextIfNonNull(SUPPORTED_AUDIO_ROUTES, Integer.toString(
+                                o.getSupportedAudioRoutes()), serializer);
 
                 serializer.endTag(null, CLASS_PHONE_ACCOUNT);
             }
@@ -1193,6 +1197,7 @@
                 Uri address = null;
                 Uri subscriptionAddress = null;
                 int capabilities = 0;
+                int supportedAudioRoutes = 0;
                 int iconResId = PhoneAccount.NO_RESOURCE_ID;
                 String iconPackageName = null;
                 Bitmap iconBitmap = null;
@@ -1248,6 +1253,9 @@
                     } else if (parser.getName().equals(ENABLED)) {
                         parser.next();
                         enabled = "true".equalsIgnoreCase(parser.getText());
+                    } else if (parser.getName().equals(SUPPORTED_AUDIO_ROUTES)) {
+                        parser.next();
+                        supportedAudioRoutes = Integer.parseInt(parser.getText());
                     }
                 }
 
@@ -1306,10 +1314,16 @@
                     }
                 }
 
+                if (version < 9) {
+                    // Set supported audio routes to all by default
+                    supportedAudioRoutes = CallAudioState.ROUTE_ALL;
+                }
+
                 PhoneAccount.Builder builder = PhoneAccount.builder(accountHandle, label)
                         .setAddress(address)
                         .setSubscriptionAddress(subscriptionAddress)
                         .setCapabilities(capabilities)
+                        .setSupportedAudioRoutes(supportedAudioRoutes)
                         .setShortDescription(shortDescription)
                         .setSupportedUriSchemes(supportedUriSchemes)
                         .setHighlightColor(highlightColor)
diff --git a/tests/src/com/android/server/telecom/tests/ConnectionServiceFixture.java b/tests/src/com/android/server/telecom/tests/ConnectionServiceFixture.java
index bdcfb5b..c108d21 100644
--- a/tests/src/com/android/server/telecom/tests/ConnectionServiceFixture.java
+++ b/tests/src/com/android/server/telecom/tests/ConnectionServiceFixture.java
@@ -171,6 +171,7 @@
         int state;
         int addressPresentation;
         int capabilities;
+        int supportedAudioRoutes;
         StatusHints statusHints;
         DisconnectCause disconnectCause;
         String conferenceId;
@@ -395,6 +396,7 @@
                 c.request.getAccountHandle(),
                 c.state,
                 c.capabilities,
+                c.supportedAudioRoutes,
                 c.request.getAddress(),
                 c.addressPresentation,
                 c.callerDisplayName,