Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit 978b74a5 authored by Tyler Gunn's avatar Tyler Gunn
Browse files

Ensure VT upgrade requests are auto-rejected when VT is not supported.

We have existing code to ensure that if the phone account doesn't support
VT that incoming VT upgrade requests will be auto-rejected.
Updating this code to account for the case where the phone account still
supports video but the call does not.

Bug: 77937629
Test: Manually verified incoming requests to upgrade to video are rejected
when video is not locally available for a call.
Test: Added unit tests for VideoProviderProxy class.

Change-Id: I9f3203f2e2a59c62781e9b4a40a99c2d9a5c46e4
parent 38a8b9c5
Loading
Loading
Loading
Loading
+21 −9
Original line number Original line Diff line number Diff line
@@ -461,7 +461,7 @@ public class Call implements CreateConnectionResponse, EventManager.Loggable,
     * Indicates whether the {@link PhoneAccount} associated with this call supports video calling.
     * Indicates whether the {@link PhoneAccount} associated with this call supports video calling.
     * {@code True} if the phone account supports video calling, {@code false} otherwise.
     * {@code True} if the phone account supports video calling, {@code false} otherwise.
     */
     */
    private boolean mIsVideoCallingSupported = false;
    private boolean mIsVideoCallingSupportedByPhoneAccount = false;


    private PhoneNumberUtilsAdapter mPhoneNumberUtilsAdapter;
    private PhoneNumberUtilsAdapter mPhoneNumberUtilsAdapter;


@@ -1229,8 +1229,19 @@ public class Call implements CreateConnectionResponse, EventManager.Loggable,
        return mUseCallRecordingTone;
        return mUseCallRecordingTone;
    }
    }


    public boolean isVideoCallingSupported() {
    /**
        return mIsVideoCallingSupported;
     * @return {@code true} if the {@link Call}'s {@link #getTargetPhoneAccount()} supports video.
     */
    public boolean isVideoCallingSupportedByPhoneAccount() {
        return mIsVideoCallingSupportedByPhoneAccount;
    }

    /**
     * @return {@code true} if the {@link Call} locally supports video.
     */
    public boolean isLocallyVideoCapable() {
        return (getConnectionCapabilities() & Connection.CAPABILITY_SUPPORTS_VT_LOCAL_BIDIRECTIONAL)
                == Connection.CAPABILITY_SUPPORTS_VT_LOCAL_BIDIRECTIONAL;
    }
    }


    public boolean isSelfManaged() {
    public boolean isSelfManaged() {
@@ -1332,16 +1343,16 @@ public class Call implements CreateConnectionResponse, EventManager.Loggable,
        if (mTargetPhoneAccountHandle == null) {
        if (mTargetPhoneAccountHandle == null) {
            // If no target phone account handle is specified, assume we can potentially perform a
            // If no target phone account handle is specified, assume we can potentially perform a
            // video call; once the phone account is set, we can confirm that it is video capable.
            // video call; once the phone account is set, we can confirm that it is video capable.
            mIsVideoCallingSupported = true;
            mIsVideoCallingSupportedByPhoneAccount = true;
            Log.d(this, "checkIfVideoCapable: no phone account selected; assume video capable.");
            Log.d(this, "checkIfVideoCapable: no phone account selected; assume video capable.");
            return;
            return;
        }
        }
        PhoneAccount phoneAccount =
        PhoneAccount phoneAccount =
                phoneAccountRegistrar.getPhoneAccountUnchecked(mTargetPhoneAccountHandle);
                phoneAccountRegistrar.getPhoneAccountUnchecked(mTargetPhoneAccountHandle);
        mIsVideoCallingSupported = phoneAccount != null && phoneAccount.hasCapabilities(
        mIsVideoCallingSupportedByPhoneAccount = phoneAccount != null && phoneAccount.hasCapabilities(
                    PhoneAccount.CAPABILITY_VIDEO_CALLING);
                    PhoneAccount.CAPABILITY_VIDEO_CALLING);


        if (!mIsVideoCallingSupported && VideoProfile.isVideo(getVideoState())) {
        if (!mIsVideoCallingSupportedByPhoneAccount && VideoProfile.isVideo(getVideoState())) {
            // The PhoneAccount for the Call was set to one which does not support video calling,
            // The PhoneAccount for the Call was set to one which does not support video calling,
            // and the current call is configured to be a video call; downgrade to audio-only.
            // and the current call is configured to be a video call; downgrade to audio-only.
            setVideoState(VideoProfile.STATE_AUDIO_ONLY);
            setVideoState(VideoProfile.STATE_AUDIO_ONLY);
@@ -1444,7 +1455,8 @@ public class Call implements CreateConnectionResponse, EventManager.Loggable,
        if (forceUpdate || mConnectionCapabilities != connectionCapabilities) {
        if (forceUpdate || mConnectionCapabilities != connectionCapabilities) {
            // If the phone account does not support video calling, and the connection capabilities
            // If the phone account does not support video calling, and the connection capabilities
            // passed in indicate that the call supports video, remove those video capabilities.
            // passed in indicate that the call supports video, remove those video capabilities.
            if (!isVideoCallingSupported() && doesCallSupportVideo(connectionCapabilities)) {
            if (!isVideoCallingSupportedByPhoneAccount()
                    && doesCallSupportVideo(connectionCapabilities)) {
                Log.w(this, "setConnectionCapabilities: attempt to set connection as video " +
                Log.w(this, "setConnectionCapabilities: attempt to set connection as video " +
                        "capable when not supported by the phone account.");
                        "capable when not supported by the phone account.");
                connectionCapabilities = removeVideoCapabilities(connectionCapabilities);
                connectionCapabilities = removeVideoCapabilities(connectionCapabilities);
@@ -1880,7 +1892,7 @@ public class Call implements CreateConnectionResponse, EventManager.Loggable,
        // Check to verify that the call is still in the ringing state. A call can change states
        // Check to verify that the call is still in the ringing state. A call can change states
        // between the time the user hits 'answer' and Telecom receives the command.
        // between the time the user hits 'answer' and Telecom receives the command.
        if (isRinging("answer")) {
        if (isRinging("answer")) {
            if (!isVideoCallingSupported() && VideoProfile.isVideo(videoState)) {
            if (!isVideoCallingSupportedByPhoneAccount() && VideoProfile.isVideo(videoState)) {
                // Video calling is not supported, yet the InCallService is attempting to answer as
                // Video calling is not supported, yet the InCallService is attempting to answer as
                // video.  We will simply answer as audio-only.
                // video.  We will simply answer as audio-only.
                videoState = VideoProfile.STATE_AUDIO_ONLY;
                videoState = VideoProfile.STATE_AUDIO_ONLY;
@@ -2784,7 +2796,7 @@ public class Call implements CreateConnectionResponse, EventManager.Loggable,
    public void setVideoState(int videoState) {
    public void setVideoState(int videoState) {
        // If the phone account associated with this call does not support video calling, then we
        // If the phone account associated with this call does not support video calling, then we
        // will automatically set the video state to audio-only.
        // will automatically set the video state to audio-only.
        if (!isVideoCallingSupported()) {
        if (!isVideoCallingSupportedByPhoneAccount()) {
            Log.d(this, "setVideoState: videoState=%s defaulted to audio (video not supported)",
            Log.d(this, "setVideoState: videoState=%s defaulted to audio (video not supported)",
                    VideoProfile.videoStateToString(videoState));
                    VideoProfile.videoStateToString(videoState));
            videoState = VideoProfile.STATE_AUDIO_ONLY;
            videoState = VideoProfile.STATE_AUDIO_ONLY;
+17 −13
Original line number Original line Diff line number Diff line
@@ -20,7 +20,6 @@ import android.Manifest;
import android.app.AppOpsManager;
import android.app.AppOpsManager;
import android.content.Context;
import android.content.Context;
import android.net.Uri;
import android.net.Uri;
import android.os.Binder;
import android.os.Build;
import android.os.Build;
import android.os.IBinder;
import android.os.IBinder;
import android.os.Looper;
import android.os.Looper;
@@ -33,6 +32,7 @@ import android.telecom.VideoProfile;
import android.text.TextUtils;
import android.text.TextUtils;
import android.view.Surface;
import android.view.Surface;


import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.telecom.IVideoCallback;
import com.android.internal.telecom.IVideoCallback;
import com.android.internal.telecom.IVideoProvider;
import com.android.internal.telecom.IVideoProvider;


@@ -40,8 +40,6 @@ import java.util.Collections;
import java.util.Set;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentHashMap;


import static android.Manifest.permission.CALL_PHONE;

/**
/**
 * Proxies video provider messages from {@link InCallService.VideoCall}
 * Proxies video provider messages from {@link InCallService.VideoCall}
 * implementations to the underlying {@link Connection.VideoProvider} implementation.  Also proxies
 * implementations to the underlying {@link Connection.VideoProvider} implementation.  Also proxies
@@ -55,7 +53,7 @@ public class VideoProviderProxy extends Connection.VideoProvider {
    /**
    /**
     * Listener for Telecom components interested in callbacks from the video provider.
     * Listener for Telecom components interested in callbacks from the video provider.
     */
     */
    interface Listener {
    public interface Listener {
        void onSessionModifyRequestReceived(Call call, VideoProfile videoProfile);
        void onSessionModifyRequestReceived(Call call, VideoProfile videoProfile);
    }
    }


@@ -112,7 +110,7 @@ public class VideoProviderProxy extends Connection.VideoProvider {
     * @param call The current call.
     * @param call The current call.
     * @throws RemoteException Remote exception.
     * @throws RemoteException Remote exception.
     */
     */
    VideoProviderProxy(TelecomSystem.SyncRoot lock,
    public VideoProviderProxy(TelecomSystem.SyncRoot lock,
            IVideoProvider videoProvider, Call call, CurrentUserProxy currentUserProxy)
            IVideoProvider videoProvider, Call call, CurrentUserProxy currentUserProxy)
            throws RemoteException {
            throws RemoteException {


@@ -136,11 +134,16 @@ public class VideoProviderProxy extends Connection.VideoProvider {
        }
        }
    }
    }


    @VisibleForTesting
    public VideoCallListenerBinder getVideoCallListenerBinder() {
        return mVideoCallListenerBinder;
    }

    /**
    /**
     * IVideoCallback stub implementation.  An instance of this class receives callbacks from the
     * IVideoCallback stub implementation.  An instance of this class receives callbacks from the
     * {@code ConnectionService}'s video provider.
     * {@code ConnectionService}'s video provider.
     */
     */
    private final class VideoCallListenerBinder extends IVideoCallback.Stub {
    public final class VideoCallListenerBinder extends IVideoCallback.Stub {
        /**
        /**
         * Proxies a request from the {@link #mConectionServiceVideoProvider} to the
         * Proxies a request from the {@link #mConectionServiceVideoProvider} to the
         * {@link InCallService} when a session modification request is received.
         * {@link InCallService} when a session modification request is received.
@@ -160,13 +163,14 @@ public class VideoProviderProxy extends Connection.VideoProvider {
                            Analytics.RECEIVE_REMOTE_SESSION_MODIFY_REQUEST,
                            Analytics.RECEIVE_REMOTE_SESSION_MODIFY_REQUEST,
                            videoProfile.getVideoState());
                            videoProfile.getVideoState());


                    if (!mCall.isVideoCallingSupported() &&
                    if ((!mCall.isVideoCallingSupportedByPhoneAccount()
                            VideoProfile.isVideo(videoProfile.getVideoState())) {
                            || !mCall.isLocallyVideoCapable())
                        // If video calling is not supported by the phone account, and we receive
                            && VideoProfile.isVideo(videoProfile.getVideoState())) {
                        // a request to upgrade to video, automatically reject it without informing
                        // If video calling is not supported by the phone account, or is not
                        // the InCallService.
                        // locally video capable and we receive a request to upgrade to video,

                        // automatically reject it without informing the InCallService.
                        Log.addEvent(mCall, LogUtils.Events.SEND_VIDEO_RESPONSE, "video not supported");
                        Log.addEvent(mCall, LogUtils.Events.SEND_VIDEO_RESPONSE,
                                "video not supported");
                        VideoProfile responseProfile = new VideoProfile(
                        VideoProfile responseProfile = new VideoProfile(
                                VideoProfile.STATE_AUDIO_ONLY);
                                VideoProfile.STATE_AUDIO_ONLY);
                        try {
                        try {
+4 −5
Original line number Original line Diff line number Diff line
@@ -26,7 +26,6 @@ import static org.mockito.Matchers.anyInt;
import static org.mockito.Matchers.anyString;
import static org.mockito.Matchers.anyString;
import static org.mockito.Matchers.eq;
import static org.mockito.Matchers.eq;
import static org.mockito.Matchers.isNull;
import static org.mockito.Matchers.isNull;
import static org.mockito.Mockito.atLeastOnce;
import static org.mockito.Mockito.never;
import static org.mockito.Mockito.never;
import static org.mockito.Mockito.timeout;
import static org.mockito.Mockito.timeout;
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.times;
@@ -921,12 +920,12 @@ public class BasicCallTests extends TelecomSystemTest {
                Process.myUserHandle(), VideoProfile.STATE_BIDIRECTIONAL);
                Process.myUserHandle(), VideoProfile.STATE_BIDIRECTIONAL);
        com.android.server.telecom.Call call = mTelecomSystem.getCallsManager().getCalls()
        com.android.server.telecom.Call call = mTelecomSystem.getCallsManager().getCalls()
                .iterator().next();
                .iterator().next();
        assert(call.isVideoCallingSupported());
        assert(call.isVideoCallingSupportedByPhoneAccount());
        assertEquals(VideoProfile.STATE_BIDIRECTIONAL, call.getVideoState());
        assertEquals(VideoProfile.STATE_BIDIRECTIONAL, call.getVideoState());


        // Change the phone account to one which supports video calling.
        // Change the phone account to one which supports video calling.
        call.setTargetPhoneAccount(mPhoneAccountA1.getAccountHandle());
        call.setTargetPhoneAccount(mPhoneAccountA1.getAccountHandle());
        assert(call.isVideoCallingSupported());
        assert(call.isVideoCallingSupportedByPhoneAccount());
        assertEquals(VideoProfile.STATE_BIDIRECTIONAL, call.getVideoState());
        assertEquals(VideoProfile.STATE_BIDIRECTIONAL, call.getVideoState());
    }
    }


@@ -944,12 +943,12 @@ public class BasicCallTests extends TelecomSystemTest {
                Process.myUserHandle(), VideoProfile.STATE_BIDIRECTIONAL);
                Process.myUserHandle(), VideoProfile.STATE_BIDIRECTIONAL);
        com.android.server.telecom.Call call = mTelecomSystem.getCallsManager().getCalls()
        com.android.server.telecom.Call call = mTelecomSystem.getCallsManager().getCalls()
                .iterator().next();
                .iterator().next();
        assert(call.isVideoCallingSupported());
        assert(call.isVideoCallingSupportedByPhoneAccount());
        assertEquals(VideoProfile.STATE_BIDIRECTIONAL, call.getVideoState());
        assertEquals(VideoProfile.STATE_BIDIRECTIONAL, call.getVideoState());


        // Change the phone account to one which does not support video calling.
        // Change the phone account to one which does not support video calling.
        call.setTargetPhoneAccount(mPhoneAccountA2.getAccountHandle());
        call.setTargetPhoneAccount(mPhoneAccountA2.getAccountHandle());
        assert(!call.isVideoCallingSupported());
        assert(!call.isVideoCallingSupportedByPhoneAccount());
        assertEquals(VideoProfile.STATE_AUDIO_ONLY, call.getVideoState());
        assertEquals(VideoProfile.STATE_AUDIO_ONLY, call.getVideoState());
    }
    }


+119 −0
Original line number Original line Diff line number Diff line
/*
 * Copyright (C) 2018 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 static org.junit.Assert.assertEquals;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyInt;
import static org.mockito.Mockito.doNothing;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;

import android.os.IBinder;
import android.telecom.VideoProfile;
import android.test.suitebuilder.annotation.SmallTest;

import com.android.internal.telecom.IVideoProvider;
import com.android.server.telecom.Analytics;
import com.android.server.telecom.Call;
import com.android.server.telecom.CurrentUserProxy;
import com.android.server.telecom.TelecomSystem;
import com.android.server.telecom.VideoProviderProxy;

import org.junit.Before;
import org.junit.Test;
import org.mockito.ArgumentCaptor;
import org.mockito.Mock;
import org.mockito.MockitoAnnotations;

public class VideoProviderProxyTest extends TelecomTestCase {

    private TelecomSystem.SyncRoot mLock;
    private VideoProviderProxy mVideoProviderProxy;
    @Mock private IVideoProvider mVideoProvider;
    @Mock private IBinder mIBinder;
    @Mock private Call mCall;
    @Mock private Analytics.CallInfo mCallInfo;
    @Mock private CurrentUserProxy mCurrentUserProxy;
    @Mock private VideoProviderProxy.Listener mListener;

    @Override
    @Before
    public void setUp() throws Exception {
        super.setUp();
        MockitoAnnotations.initMocks(this);
        mLock = new TelecomSystem.SyncRoot() { };

        when(mVideoProvider.asBinder()).thenReturn(mIBinder);
        doNothing().when(mIBinder).linkToDeath(any(), anyInt());
        when(mCall.getAnalytics()).thenReturn(mCallInfo);
        doNothing().when(mCallInfo).addVideoEvent(anyInt(), anyInt());
        mVideoProviderProxy = new VideoProviderProxy(mLock, mVideoProvider, mCall,
                mCurrentUserProxy);
        mVideoProviderProxy.addListener(mListener);
    }

    /**
     * Tests the case where we receive a request to upgrade to video, except:
     * 1. Phone account says we support video.
     * 2. Call says we don't support video.
     *
     * Ensures that we send back a response immediately to indicate the call should remain as
     * audio-only.
     * @throws Exception
     */
    @SmallTest
    @Test
    public void testReceiveUpgradeRequestWhenLocalDoesntSupportVideo() throws Exception {
        // Given a call which supports video at the phone account level, but is not currently
        // marked as supporting video locally.
        when(mCall.isLocallyVideoCapable()).thenReturn(false);
        when(mCall.isVideoCallingSupportedByPhoneAccount()).thenReturn(true);

        // Simulate receiving a request to upgrade to video.
        mVideoProviderProxy.getVideoCallListenerBinder().receiveSessionModifyRequest(
                new VideoProfile(VideoProfile.STATE_BIDIRECTIONAL));

        // Make sure that we send back a response rejecting the request.
        ArgumentCaptor<VideoProfile> capturedProfile = ArgumentCaptor.forClass(VideoProfile.class);
        verify(mVideoProvider).sendSessionModifyResponse(capturedProfile.capture());
        assertEquals(VideoProfile.STATE_AUDIO_ONLY, capturedProfile.getValue().getVideoState());
    }

    /**
     * Tests the case where we receive a request to upgrade to video and video is supported.
     * @throws Exception
     */
    @SmallTest
    @Test
    public void testReceiveUpgradeRequestWhenVideoIsSupported() throws Exception {
        // Given a call which supports video at the phone account level, and is currently marked as
        // supporting video locally.
        when(mCall.isLocallyVideoCapable()).thenReturn(true);
        when(mCall.isVideoCallingSupportedByPhoneAccount()).thenReturn(true);

        // Simulate receiving a request to upgrade to video.
        mVideoProviderProxy.getVideoCallListenerBinder().receiveSessionModifyRequest(
                new VideoProfile(VideoProfile.STATE_BIDIRECTIONAL));

        // Ensure it gets proxied back to the caller.

        ArgumentCaptor<VideoProfile> capturedProfile = ArgumentCaptor.forClass(VideoProfile.class);
        verify(mListener).onSessionModifyRequestReceived(any(), capturedProfile.capture());
        assertEquals(VideoProfile.STATE_BIDIRECTIONAL, capturedProfile.getValue().getVideoState());
    }
}