Merge "Fix a race condition in BT device handling"
diff --git a/src/com/android/server/telecom/Call.java b/src/com/android/server/telecom/Call.java
index e110be8..628df84 100644
--- a/src/com/android/server/telecom/Call.java
+++ b/src/com/android/server/telecom/Call.java
@@ -1251,6 +1251,23 @@
     }
 
     /**
+     * Sets whether video calling is supported by the current phone account. Since video support
+     * can change during a call, this method facilitates updating call video state.
+     * @param isVideoCallingSupported Sets whether video calling is supported.
+     */
+    public void setVideoCallingSupportedByPhoneAccount(boolean isVideoCallingSupported) {
+        if (mIsVideoCallingSupportedByPhoneAccount == isVideoCallingSupported) {
+            return;
+        }
+        Log.i(this, "setVideoCallingSupportedByPhoneAccount: isSupp=%b", isVideoCallingSupported);
+        mIsVideoCallingSupportedByPhoneAccount = isVideoCallingSupported;
+
+        // Force an update of the connection capabilities so that the dialer is informed of the new
+        // video capabilities based on the phone account's support for video.
+        setConnectionCapabilities(getConnectionCapabilities(), true /* force */);
+    }
+
+    /**
      * @return {@code true} if the {@link Call} locally supports video.
      */
     public boolean isLocallyVideoCapable() {
@@ -1363,8 +1380,8 @@
         }
         PhoneAccount phoneAccount =
                 phoneAccountRegistrar.getPhoneAccountUnchecked(mTargetPhoneAccountHandle);
-        mIsVideoCallingSupportedByPhoneAccount = phoneAccount != null && phoneAccount.hasCapabilities(
-                    PhoneAccount.CAPABILITY_VIDEO_CALLING);
+        mIsVideoCallingSupportedByPhoneAccount = phoneAccount != null &&
+                phoneAccount.hasCapabilities(PhoneAccount.CAPABILITY_VIDEO_CALLING);
 
         if (!mIsVideoCallingSupportedByPhoneAccount && VideoProfile.isVideo(getVideoState())) {
             // The PhoneAccount for the Call was set to one which does not support video calling,
@@ -1455,7 +1472,7 @@
         mConnectElapsedTimeMillis = connectElapsedTimeMillis;
     }
 
-    int getConnectionCapabilities() {
+    public int getConnectionCapabilities() {
         return mConnectionCapabilities;
     }
 
@@ -1463,7 +1480,7 @@
         return mConnectionProperties;
     }
 
-    void setConnectionCapabilities(int connectionCapabilities) {
+    public void setConnectionCapabilities(int connectionCapabilities) {
         setConnectionCapabilities(connectionCapabilities, false /* forceUpdate */);
     }
 
@@ -3178,4 +3195,18 @@
     public CallIdentification getCallIdentification() {
         return mCallIdentification;
     }
+
+    /**
+     * When upgrading a call to video via
+     * {@link VideoProviderProxy#onSendSessionModifyRequest(VideoProfile, VideoProfile)}, if the
+     * upgrade is from audio to video, potentially auto-engage the speakerphone.
+     * @param newVideoState The proposed new video state for the call.
+     */
+    public void maybeEnableSpeakerForVideoUpgrade(@VideoProfile.VideoState int newVideoState) {
+        if (mCallsManager.isSpeakerphoneAutoEnabledForVideoCalls(newVideoState)) {
+            Log.i(this, "maybeEnableSpeakerForVideoCall; callId=%s, auto-enable speaker for call"
+                            + " upgraded to video.");
+            mCallsManager.setAudioRoute(CallAudioState.ROUTE_SPEAKER, null);
+        }
+    }
 }
diff --git a/src/com/android/server/telecom/CallLogManager.java b/src/com/android/server/telecom/CallLogManager.java
index d355a65..cfb5e80 100755
--- a/src/com/android/server/telecom/CallLogManager.java
+++ b/src/com/android/server/telecom/CallLogManager.java
@@ -82,7 +82,7 @@
                 int features, PhoneAccountHandle accountHandle, long creationDate,
                 long durationInMillis, Long dataUsage, UserHandle initiatingUser, boolean isRead,
                 @Nullable LogCallCompletedListener logCallCompletedListener, int callBlockReason,
-                String callScreeningAppName, String callScreeningComponentName,
+                CharSequence callScreeningAppName, String callScreeningComponentName,
                 CallIdentification callIdentification) {
             this.context = context;
             this.callerInfo = callerInfo;
@@ -125,7 +125,7 @@
         public final LogCallCompletedListener logCallCompletedListener;
 
         public final int callBockReason;
-        public final String callScreeningAppName;
+        public final CharSequence callScreeningAppName;
         public final String callScreeningComponentName;
 
         public final CallIdentification callIdentification;
@@ -359,7 +359,7 @@
             boolean isSelfManaged,
             @Nullable LogCallCompletedListener logCallCompletedListener,
             int callBlockReason,
-            String callScreeningAppName,
+            CharSequence callScreeningAppName,
             String callScreeningComponentName,
             @Nullable CallIdentification callIdentification) {
 
diff --git a/src/com/android/server/telecom/CallScreeningServiceHelper.java b/src/com/android/server/telecom/CallScreeningServiceHelper.java
index b2f76c4..cb50b86 100644
--- a/src/com/android/server/telecom/CallScreeningServiceHelper.java
+++ b/src/com/android/server/telecom/CallScreeningServiceHelper.java
@@ -51,7 +51,7 @@
      * app.
      */
     public interface AppLabelProxy {
-        String getAppLabel(String packageName);
+        CharSequence getAppLabel(String packageName);
     }
 
     /**
diff --git a/src/com/android/server/telecom/CallsManager.java b/src/com/android/server/telecom/CallsManager.java
index 7653968..4b9e63c 100644
--- a/src/com/android/server/telecom/CallsManager.java
+++ b/src/com/android/server/telecom/CallsManager.java
@@ -340,6 +340,12 @@
                                                PhoneAccountHandle handle) {
             broadcastUnregisterIntent(handle);
         }
+
+        @Override
+        public void onPhoneAccountChanged(PhoneAccountRegistrar registrar,
+                PhoneAccount phoneAccount) {
+            handlePhoneAccountChanged(registrar, phoneAccount);
+        }
     };
 
     /**
@@ -595,11 +601,11 @@
                 new TelecomServiceImpl.SettingsSecureAdapterImpl(), mCallerInfoLookupHelper,
                 new CallScreeningServiceHelper.AppLabelProxy() {
                     @Override
-                    public String getAppLabel(String packageName) {
+                    public CharSequence getAppLabel(String packageName) {
                         PackageManager pm = mContext.getPackageManager();
                         try {
                             ApplicationInfo info = pm.getApplicationInfo(packageName, 0);
-                            return (String) pm.getApplicationLabel(info);
+                            return pm.getApplicationLabel(info);
                         } catch (PackageManager.NameNotFoundException nnfe) {
                             Log.w(this, "Could not determine package name.");
                         }
@@ -925,6 +931,11 @@
     }
 
     @VisibleForTesting
+    public PhoneAccountRegistrar.Listener getPhoneAccountListener() {
+        return mPhoneAccountListener;
+    }
+
+    @VisibleForTesting
     public boolean hasEmergencyCall() {
         for (Call call : mCalls) {
             if (call.isEmergencyCall()) {
@@ -1534,12 +1545,12 @@
                 theCall,
                 new CallScreeningServiceHelper.AppLabelProxy() {
                     @Override
-                    public String getAppLabel(String packageName) {
+                    public CharSequence getAppLabel(String packageName) {
                         PackageManager pm = mContext.getPackageManager();
                         try {
                             ApplicationInfo info = pm.getApplicationInfo(
                                     packageName, 0);
-                            return (String) pm.getApplicationLabel(info);
+                            return pm.getApplicationLabel(info);
                         } catch (PackageManager.NameNotFoundException nnfe) {
                             Log.w(this, "Could not determine package name.");
                         }
@@ -4461,4 +4472,23 @@
         errorIntent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
         mContext.startActivityAsUser(errorIntent, UserHandle.CURRENT);
     }
+
+    /**
+     * Handles changes to a {@link PhoneAccount}.
+     *
+     * Checks for changes to video calling availability and updates whether calls for that phone
+     * account are video capable.
+     *
+     * @param registrar The {@link PhoneAccountRegistrar} originating the change.
+     * @param phoneAccount The {@link PhoneAccount} which changed.
+     */
+    private void handlePhoneAccountChanged(PhoneAccountRegistrar registrar,
+            PhoneAccount phoneAccount) {
+        Log.i(this, "handlePhoneAccountChanged: phoneAccount=%s", phoneAccount);
+        boolean isVideoNowSupported = phoneAccount.hasCapabilities(
+                PhoneAccount.CAPABILITY_VIDEO_CALLING);
+        mCalls.stream()
+                .filter(c -> phoneAccount.getAccountHandle().equals(c.getTargetPhoneAccount()))
+                .forEach(c -> c.setVideoCallingSupportedByPhoneAccount(isVideoNowSupported));
+    }
 }
diff --git a/src/com/android/server/telecom/PhoneAccountRegistrar.java b/src/com/android/server/telecom/PhoneAccountRegistrar.java
index afe5609..951a5ed 100644
--- a/src/com/android/server/telecom/PhoneAccountRegistrar.java
+++ b/src/com/android/server/telecom/PhoneAccountRegistrar.java
@@ -126,6 +126,8 @@
                                              PhoneAccountHandle handle) {}
         public void onPhoneAccountUnRegistered(PhoneAccountRegistrar registrar,
                                              PhoneAccountHandle handle) {}
+        public void onPhoneAccountChanged(PhoneAccountRegistrar registrar,
+                PhoneAccount phoneAccount) {}
     }
 
     /**
@@ -743,6 +745,8 @@
         fireAccountsChanged();
         if (isNewAccount) {
             fireAccountRegistered(account.getAccountHandle());
+        } else {
+            fireAccountChanged(account);
         }
     }
 
@@ -804,6 +808,12 @@
         }
     }
 
+    private void fireAccountChanged(PhoneAccount account) {
+        for (Listener l : mListeners) {
+            l.onPhoneAccountChanged(this, account);
+        }
+    }
+
     private void fireAccountUnRegistered(PhoneAccountHandle handle) {
         for (Listener l : mListeners) {
             l.onPhoneAccountUnRegistered(this, handle);
diff --git a/src/com/android/server/telecom/VideoProviderProxy.java b/src/com/android/server/telecom/VideoProviderProxy.java
index 6e1f01d..364e0f4 100644
--- a/src/com/android/server/telecom/VideoProviderProxy.java
+++ b/src/com/android/server/telecom/VideoProviderProxy.java
@@ -438,6 +438,11 @@
             logFromInCall("sendSessionModifyRequest: from=" + fromProfile + " to=" + toProfile);
             Log.addEvent(mCall, LogUtils.Events.SEND_VIDEO_REQUEST,
                     VideoProfile.videoStateToString(toProfile.getVideoState()));
+            if (!VideoProfile.isVideo(fromProfile.getVideoState())
+                    && VideoProfile.isVideo(toProfile.getVideoState())) {
+                // Upgrading to video; change to speaker potentially.
+                mCall.maybeEnableSpeakerForVideoUpgrade(toProfile.getVideoState());
+            }
             mCall.getAnalytics().addVideoEvent(
                     Analytics.SEND_LOCAL_SESSION_MODIFY_REQUEST,
                     toProfile.getVideoState());
diff --git a/src/com/android/server/telecom/callfiltering/CallFilteringResult.java b/src/com/android/server/telecom/callfiltering/CallFilteringResult.java
index 1213131..6c37fd4 100644
--- a/src/com/android/server/telecom/callfiltering/CallFilteringResult.java
+++ b/src/com/android/server/telecom/callfiltering/CallFilteringResult.java
@@ -26,7 +26,7 @@
     public boolean shouldAddToCallLog;
     public boolean shouldShowNotification;
     public int mCallBlockReason = CallLog.Calls.BLOCK_REASON_NOT_BLOCKED;
-    public String mCallScreeningAppName = null;
+    public CharSequence mCallScreeningAppName = null;
     public String mCallScreeningComponentName = null;
 
     public CallFilteringResult(boolean shouldAllowCall, boolean shouldReject, boolean
@@ -38,8 +38,8 @@
     }
 
     public CallFilteringResult(boolean shouldAllowCall, boolean shouldReject, boolean
-            shouldAddToCallLog, boolean shouldShowNotification, int callBlockReason, String
-            callScreeningAppName, String callScreeningComponentName) {
+            shouldAddToCallLog, boolean shouldShowNotification, int callBlockReason,
+            CharSequence callScreeningAppName, String callScreeningComponentName) {
         this.shouldAllowCall = shouldAllowCall;
         this.shouldReject = shouldReject;
         this.shouldAddToCallLog = shouldAddToCallLog;
@@ -104,7 +104,7 @@
     }
 
     private CallFilteringResult getCombinedCallFilteringResult(CallFilteringResult other,
-        int callBlockReason, String callScreeningAppName, String callScreeningComponentName) {
+        int callBlockReason, CharSequence callScreeningAppName, String callScreeningComponentName) {
         return new CallFilteringResult(
             shouldAllowCall && other.shouldAllowCall,
             shouldReject || other.shouldReject,
diff --git a/src/com/android/server/telecom/callfiltering/CallScreeningServiceFilter.java b/src/com/android/server/telecom/callfiltering/CallScreeningServiceFilter.java
index 3ec1569..3fcb684 100644
--- a/src/com/android/server/telecom/callfiltering/CallScreeningServiceFilter.java
+++ b/src/com/android/server/telecom/callfiltering/CallScreeningServiceFilter.java
@@ -184,7 +184,7 @@
     private ICallScreeningService mService;
     private ServiceConnection mConnection;
     private String mPackageName;
-    private String mAppName;
+    private CharSequence mAppName;
     private boolean mHasFinished = false;
 
     private CallFilteringResult mResult = new CallFilteringResult(
@@ -211,7 +211,7 @@
     public void startCallScreeningFilter(Call call,
                                          CallScreeningFilterResultCallback callback,
                                          String packageName,
-                                         String appName) {
+                                         CharSequence appName) {
         if (mHasFinished) {
             Log.w(this, "Attempting to reuse CallScreeningServiceFilter. Ignoring.");
             return;
diff --git a/tests/src/com/android/server/telecom/tests/CallScreeningServiceControllerTest.java b/tests/src/com/android/server/telecom/tests/CallScreeningServiceControllerTest.java
index 1001d23..1a01a95 100644
--- a/tests/src/com/android/server/telecom/tests/CallScreeningServiceControllerTest.java
+++ b/tests/src/com/android/server/telecom/tests/CallScreeningServiceControllerTest.java
@@ -84,7 +84,7 @@
     CallScreeningServiceHelper.AppLabelProxy mAppLabelProxy =
             new CallScreeningServiceHelper.AppLabelProxy() {
         @Override
-        public String getAppLabel(String packageName) {
+        public CharSequence getAppLabel(String packageName) {
             return APP_NAME;
         }
     };
diff --git a/tests/src/com/android/server/telecom/tests/CallsManagerTest.java b/tests/src/com/android/server/telecom/tests/CallsManagerTest.java
index d79ea33..bf8604a 100644
--- a/tests/src/com/android/server/telecom/tests/CallsManagerTest.java
+++ b/tests/src/com/android/server/telecom/tests/CallsManagerTest.java
@@ -16,6 +16,8 @@
 
 package com.android.server.telecom.tests;
 
+import static junit.framework.TestCase.fail;
+
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertFalse;
 import static org.junit.Assert.assertTrue;
@@ -102,9 +104,12 @@
 import java.util.HashMap;
 import java.util.List;
 import java.util.Map;
+import java.util.concurrent.LinkedBlockingQueue;
+import java.util.concurrent.TimeUnit;
 
 @RunWith(JUnit4.class)
 public class CallsManagerTest extends TelecomTestCase {
+    private static final int TEST_TIMEOUT = 5000;  // milliseconds
     private static final PhoneAccountHandle SIM_1_HANDLE = new PhoneAccountHandle(
             ComponentName.unflattenFromString("com.foo/.Blah"), "Sim1");
     private static final PhoneAccountHandle SIM_2_HANDLE = new PhoneAccountHandle(
@@ -960,6 +965,67 @@
         assertTrue(mCallsManager.isInEmergencyCall());
     }
 
+    /**
+     * Verifies that changes to a {@link PhoneAccount}'s
+     * {@link PhoneAccount#CAPABILITY_VIDEO_CALLING} capability will be reflected on a call.
+     * @throws Exception
+     */
+    @SmallTest
+    @Test
+    public void testPhoneAccountVideoAvailability() throws InterruptedException {
+        Call ongoingCall = addSpyCall(); // adds to SIM_2_ACCT
+        LinkedBlockingQueue<Integer> capabilitiesQueue = new LinkedBlockingQueue<>(1);
+        ongoingCall.addListener(new Call.ListenerBase() {
+            @Override
+            public void onConnectionCapabilitiesChanged(Call call) {
+                try {
+                    Log.i("TYLER", "Listener got " + call.getConnectionCapabilities());
+                    capabilitiesQueue.put(call.getConnectionCapabilities());
+                } catch (InterruptedException e) {
+                    fail();
+                }
+            }
+        });
+
+        // Lets make the phone account video capable.
+        PhoneAccount videoCapableAccount = new PhoneAccount.Builder(SIM_2_ACCOUNT)
+                .setCapabilities(SIM_2_ACCOUNT.getCapabilities()
+                        | PhoneAccount.CAPABILITY_VIDEO_CALLING)
+                .build();
+        mCallsManager.getPhoneAccountListener().onPhoneAccountChanged(mPhoneAccountRegistrar,
+                videoCapableAccount);
+        // Absorb first update; it'll be from when phone account changed initially (since we force
+        // a capabilities update.
+        int newCapabilities = capabilitiesQueue.poll(TEST_TIMEOUT, TimeUnit.MILLISECONDS);
+
+        // Lets pretend the ConnectionService made it video capable as well.
+        ongoingCall.setConnectionCapabilities(
+                Connection.CAPABILITY_SUPPORTS_VT_LOCAL_BIDIRECTIONAL);
+        newCapabilities = capabilitiesQueue.poll(TEST_TIMEOUT, TimeUnit.MILLISECONDS);
+        assertTrue(Connection.can(newCapabilities,
+                Connection.CAPABILITY_SUPPORTS_VT_LOCAL_BIDIRECTIONAL));
+        assertTrue(ongoingCall.isVideoCallingSupportedByPhoneAccount());
+
+        // Fire a changed event for the phone account making it not capable.
+        mCallsManager.getPhoneAccountListener().onPhoneAccountChanged(mPhoneAccountRegistrar,
+                SIM_2_ACCOUNT);
+        newCapabilities = capabilitiesQueue.poll(TEST_TIMEOUT, TimeUnit.MILLISECONDS);
+        assertFalse(Connection.can(newCapabilities,
+                Connection.CAPABILITY_SUPPORTS_VT_LOCAL_BIDIRECTIONAL));
+        assertFalse(ongoingCall.isVideoCallingSupportedByPhoneAccount());
+
+        // Fire a change for an unrelated phone account.
+        PhoneAccount anotherVideoCapableAcct = new PhoneAccount.Builder(SIM_1_ACCOUNT)
+                .setCapabilities(SIM_2_ACCOUNT.getCapabilities()
+                        | PhoneAccount.CAPABILITY_VIDEO_CALLING)
+                .build();
+        mCallsManager.getPhoneAccountListener().onPhoneAccountChanged(mPhoneAccountRegistrar,
+                anotherVideoCapableAcct);
+        // Call still should not be video capable
+        assertFalse(Connection.can(ongoingCall.getConnectionCapabilities(),
+                Connection.CAPABILITY_SUPPORTS_VT_LOCAL_BIDIRECTIONAL));
+    }
+
     private Call addSpyCallWithConnectionService(ConnectionServiceWrapper connSvr) {
         Call call = addSpyCall();
         doReturn(connSvr).when(call).getConnectionService();
diff --git a/tests/src/com/android/server/telecom/tests/VideoProviderProxyTest.java b/tests/src/com/android/server/telecom/tests/VideoProviderProxyTest.java
index a6eecf7..b09aa5b 100644
--- a/tests/src/com/android/server/telecom/tests/VideoProviderProxyTest.java
+++ b/tests/src/com/android/server/telecom/tests/VideoProviderProxyTest.java
@@ -19,6 +19,7 @@
 import static org.junit.Assert.assertEquals;
 import static org.mockito.ArgumentMatchers.any;
 import static org.mockito.ArgumentMatchers.anyInt;
+import static org.mockito.ArgumentMatchers.eq;
 import static org.mockito.Mockito.doNothing;
 import static org.mockito.Mockito.verify;
 import static org.mockito.Mockito.when;
@@ -62,6 +63,7 @@
         doNothing().when(mIBinder).linkToDeath(any(), anyInt());
         when(mCall.getAnalytics()).thenReturn(mCallInfo);
         doNothing().when(mCallInfo).addVideoEvent(anyInt(), anyInt());
+        doNothing().when(mCall).maybeEnableSpeakerForVideoUpgrade(anyInt());
         mVideoProviderProxy = new VideoProviderProxy(mLock, mVideoProvider, mCall,
                 mCurrentUserProxy);
         mVideoProviderProxy.addListener(mListener);
@@ -116,4 +118,17 @@
         verify(mListener).onSessionModifyRequestReceived(any(), capturedProfile.capture());
         assertEquals(VideoProfile.STATE_BIDIRECTIONAL, capturedProfile.getValue().getVideoState());
     }
+
+    /**
+     * Tests the case where dialer requests an upgrade to video; we should try to change to speaker.
+     * @throws Exception
+     */
+    @SmallTest
+    @Test
+    public void testTryToEnableSpeakerOnVideoUpgrade() throws Exception {
+        mVideoProviderProxy.onSendSessionModifyRequest(
+                new VideoProfile(VideoProfile.STATE_AUDIO_ONLY),
+                new VideoProfile(VideoProfile.STATE_BIDIRECTIONAL));
+        verify(mCall).maybeEnableSpeakerForVideoUpgrade(eq(VideoProfile.STATE_BIDIRECTIONAL));
+    }
 }