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

Commit e0a6ed95 authored by Brad Ebinger's avatar Brad Ebinger Committed by Gerrit Code Review
Browse files

Merge "Perform camera permission and app ops check when setting camera for VT."

parents 9517c960 f1dcfdb9
Loading
Loading
Loading
Loading
+66 −3
Original line number Diff line number Diff line
@@ -16,7 +16,11 @@

package com.android.server.telecom;

import android.Manifest;
import android.app.AppOpsManager;
import android.content.Context;
import android.net.Uri;
import android.os.Binder;
import android.os.IBinder;
import android.os.Looper;
import android.os.RemoteException;
@@ -24,6 +28,7 @@ import android.telecom.Connection;
import android.telecom.InCallService;
import android.telecom.Log;
import android.telecom.VideoProfile;
import android.text.TextUtils;
import android.view.Surface;

import com.android.internal.telecom.IVideoCallback;
@@ -33,6 +38,8 @@ import java.util.Collections;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;

import static android.Manifest.permission.CALL_PHONE;

/**
 * Proxies video provider messages from {@link InCallService.VideoCall}
 * implementations to the underlying {@link Connection.VideoProvider} implementation.  Also proxies
@@ -274,19 +281,43 @@ public class VideoProviderProxy extends Connection.VideoProvider {
        }
    }

    @Override
    public void onSetCamera(String cameraId) {
        // No-op.  We implement the other prototype of onSetCamera so that we can use the calling
        // package, uid and pid to verify permission.
    }

    /**
     * Proxies a request from the {@link InCallService} to the
     * {@link #mConectionServiceVideoProvider} to change the camera.
     *
     * @param cameraId The id of the camera.
     * @param callingPackage The package calling in.
     * @param callingUid The UID of the caller.
     * @param callingPid The PID of the caller.
     */
    @Override
    public void onSetCamera(String cameraId) {
    public void onSetCamera(String cameraId, String callingPackage, int callingUid,
            int callingPid) {
        synchronized (mLock) {
            logFromInCall("setCamera: " + cameraId);
            logFromInCall("setCamera: " + cameraId + " callingPackage=" + callingPackage);

            if (!TextUtils.isEmpty(cameraId)) {
                if (!canUseCamera(mCall.getContext(), callingPackage, callingUid, callingPid)) {
                    // Calling app is not permitted to use the camera.  Ignore the request and send
                    // back a call session event indicating the error.
                    Log.i(this, "onSetCamera: camera permission denied; package=%d, uid=%d, pid=%d",
                            callingPackage, callingUid, callingPid);
                    VideoProviderProxy.this.handleCallSessionEvent(
                            Connection.VideoProvider.SESSION_EVENT_CAMERA_PERMISSION_ERROR);
                    return;
                }
            }
            try {
                mConectionServiceVideoProvider.setCamera(cameraId);
                mConectionServiceVideoProvider.setCamera(cameraId, callingPackage);
            } catch (RemoteException e) {
                VideoProviderProxy.this.handleCallSessionEvent(
                        Connection.VideoProvider.SESSION_EVENT_CAMERA_FAILURE);
            }
        }
    }
@@ -490,4 +521,36 @@ public class VideoProviderProxy extends Connection.VideoProvider {
    private void logFromVideoProvider(String toLog) {
        Log.i(this, "VP->IC (callId=" + (mCall == null ? "?" : mCall.getId()) + "): " + toLog);
    }

    /**
     * Determines if the caller has permission to use the camera.
     *
     * @param context The context.
     * @param callingPackage The package name of the caller (i.e. Dialer).
     * @param callingUid The UID of the caller.
     * @return {@code true} if the calling uid and package can use the camera, {@code false}
     *      otherwise.
     */
    private boolean canUseCamera(Context context, String callingPackage, int callingUid,
            int callingPid) {
        try {
            context.enforcePermission(Manifest.permission.CAMERA, callingPid, callingUid,
                    "Camera permission required.");
        } catch (SecurityException se) {
            return false;
        }

        AppOpsManager appOpsManager = (AppOpsManager) context.getSystemService(
                Context.APP_OPS_SERVICE);

        try {
            // Some apps that have the permission can be restricted via app ops.
            return appOpsManager != null && appOpsManager.noteOp(AppOpsManager.OP_CAMERA,
                    callingUid, callingPackage) == AppOpsManager.MODE_ALLOWED;
        } catch (SecurityException se) {
            Log.w(this, "canUserCamera got appOpps Exception " + se.toString());
            return false;
        }
    }

}
+2 −1
Original line number Diff line number Diff line
@@ -207,7 +207,8 @@ public class AnalyticsTests extends TelecomSystemTest {
        mConnectionServiceFixtureA.sendSetVideoProvider(
                mConnectionServiceFixtureA.mLatestConnectionId);
        InCallService.VideoCall videoCall =
                mInCallServiceFixtureX.getCall(callIds.mCallId).getVideoCallImpl();
                mInCallServiceFixtureX.getCall(callIds.mCallId).getVideoCallImpl(
                        mInCallServiceComponentNameX.getPackageName());
        videoCall.registerCallback(callback);
        ((VideoCallImpl) videoCall).setVideoState(VideoProfile.STATE_BIDIRECTIONAL);

+6 −0
Original line number Diff line number Diff line
@@ -308,6 +308,12 @@ public class ComponentContextFixture implements TestFixture<Context> {
            // Don't bother enforcing anything in mock.
        }

        @Override
        public void enforcePermission(
                String permission, int pid, int uid, String message) {
            // By default, don't enforce anything in mock.
        }

        @Override
        public void startActivityAsUser(Intent intent, UserHandle userHandle) {
            // For capturing
+4 −0
Original line number Diff line number Diff line
@@ -165,6 +165,10 @@ public class MockVideoProvider extends VideoProvider {
        } else if (CAMERA_BACK.equals(cameraId)) {
            super.changeCameraCapabilities(new VideoProfile.CameraCapabilities(
                    CAMERA_BACK_DIMENSIONS, CAMERA_BACK_DIMENSIONS));
        } else {
            // If the camera is nulled, we will send back a "camera ready" event so that the unit
            // test has something to wait for.
            super.handleCallSessionEvent(VideoProvider.SESSION_EVENT_CAMERA_READY);
        }
    }

+94 −1
Original line number Diff line number Diff line
@@ -23,6 +23,8 @@ import org.mockito.internal.exceptions.ExceptionIncludingMockitoWarnings;
import org.mockito.invocation.InvocationOnMock;
import org.mockito.stubbing.Answer;

import android.app.AppOpsManager;
import android.content.Context;
import android.graphics.SurfaceTexture;
import android.net.Uri;
import android.os.Handler;
@@ -46,9 +48,12 @@ import static android.test.MoreAsserts.assertEquals;
import static org.mockito.Matchers.any;
import static org.mockito.Matchers.anyInt;
import static org.mockito.Matchers.anyLong;
import static org.mockito.Matchers.anyString;
import static org.mockito.Matchers.eq;
import static org.mockito.Mockito.doAnswer;
import static org.mockito.Mockito.doNothing;
import static org.mockito.Mockito.doReturn;
import static org.mockito.Mockito.doThrow;
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.timeout;
import static org.mockito.Mockito.mock;
@@ -71,6 +76,7 @@ public class VideoProviderTest extends TelecomSystemTest {
    private VideoCallImpl mVideoCallImpl;
    private ConnectionServiceFixture.ConnectionInfo mConnectionInfo;
    private CountDownLatch mVerificationLock;
    private AppOpsManager mAppOpsManager;

    private Answer mVerification = new Answer() {
        @Override
@@ -83,6 +89,8 @@ public class VideoProviderTest extends TelecomSystemTest {
    @Override
    public void setUp() throws Exception {
        super.setUp();
        mContext = mComponentContextFixture.getTestDouble().getApplicationContext();
        mAppOpsManager = (AppOpsManager) mContext.getSystemService(Context.APP_OPS_SERVICE);

        mCallIds = startAndMakeActiveOutgoingCall(
                "650-555-1212",
@@ -96,13 +104,18 @@ public class VideoProviderTest extends TelecomSystemTest {
        // Provide a mocked VideoCall.Callback to receive callbacks via.
        mVideoCallCallback = mock(InCallService.VideoCall.Callback.class);

        mVideoCall = mInCallServiceFixtureX.getCall(mCallIds.mCallId).getVideoCallImpl();
        mVideoCall = mInCallServiceFixtureX.getCall(mCallIds.mCallId).getVideoCallImpl(
                mInCallServiceComponentNameX.getPackageName());
        mVideoCallImpl = (VideoCallImpl) mVideoCall;
        mVideoCall.registerCallback(mVideoCallCallback);

        mConnectionInfo = mConnectionServiceFixtureA.mConnectionById.get(mCallIds.mConnectionId);
        mVerificationLock = new CountDownLatch(1);
        waitForHandlerAction(new Handler(Looper.getMainLooper()), TEST_TIMEOUT);

        doNothing().when(mContext).enforcePermission(anyString(), anyInt(), anyInt(), anyString());
        doReturn(AppOpsManager.MODE_ALLOWED).when(mAppOpsManager).noteOp(anyInt(), anyInt(),
                anyString());
    }

    @Override
@@ -145,6 +158,86 @@ public class VideoProviderTest extends TelecomSystemTest {
                cameraCapabilities.get(1).getHeight());
    }

    /**
     * Tests the caller permission check in {@link VideoCall#setCamera(String)} to ensure a camera
     * change from a non-permitted caller is ignored.
     */
    @MediumTest
    public void testCameraChangePermissionFail() throws Exception {
        // Wait until the callback has been received before performing verification.
        doAnswer(mVerification).when(mVideoCallCallback).onCallSessionEvent(anyInt());

        // ensure permission check fails.
        doThrow(new SecurityException()).when(mContext)
                .enforcePermission(anyString(), anyInt(), anyInt(), anyString());

        // Make a request to change the camera
        mVideoCall.setCamera(MockVideoProvider.CAMERA_FRONT);
        mVerificationLock.await(TEST_TIMEOUT, TimeUnit.MILLISECONDS);

        // Capture the session event reported via the callback.
        ArgumentCaptor<Integer> sessionEventCaptor = ArgumentCaptor.forClass(Integer.class);
        verify(mVideoCallCallback, timeout(TEST_TIMEOUT)).onCallSessionEvent(
                sessionEventCaptor.capture());

        assertEquals(VideoProvider.SESSION_EVENT_CAMERA_PERMISSION_ERROR,
                sessionEventCaptor.getValue().intValue());
    }

    /**
     * Tests the caller app ops check in {@link VideoCall#setCamera(String)} to ensure a camera
     * change from a non-permitted caller is ignored.
     */
    @MediumTest
    public void testCameraChangeAppOpsFail() throws Exception {
        // Wait until the callback has been received before performing verification.
        doAnswer(mVerification).when(mVideoCallCallback).onCallSessionEvent(anyInt());

        // ensure app ops check fails.
        doReturn(AppOpsManager.MODE_ERRORED).when(mAppOpsManager).noteOp(anyInt(), anyInt(),
                anyString());

        // Make a request to change the camera
        mVideoCall.setCamera(MockVideoProvider.CAMERA_FRONT);
        mVerificationLock.await(TEST_TIMEOUT, TimeUnit.MILLISECONDS);

        // Capture the session event reported via the callback.
        ArgumentCaptor<Integer> sessionEventCaptor = ArgumentCaptor.forClass(Integer.class);
        verify(mVideoCallCallback, timeout(TEST_TIMEOUT)).onCallSessionEvent(
                sessionEventCaptor.capture());

        assertEquals(VideoProvider.SESSION_EVENT_CAMERA_PERMISSION_ERROR,
                sessionEventCaptor.getValue().intValue());
    }

    /**
     * Tests the caller permission check in {@link VideoCall#setCamera(String)} to ensure the
     * caller can null out the camera, even if they do not have camera permission.
     */
    @MediumTest
    public void testCameraChangeNullNoPermission() throws Exception {
        // Wait until the callback has been received before performing verification.
        doAnswer(mVerification).when(mVideoCallCallback).onCallSessionEvent(anyInt());

        // ensure permission check fails.
        doThrow(new SecurityException()).when(mContext)
                .enforcePermission(anyString(), anyInt(), anyInt(), anyString());

        // Make a request to null the camera; we expect the permission check won't happen.
        mVideoCall.setCamera(null);
        mVerificationLock.await(TEST_TIMEOUT, TimeUnit.MILLISECONDS);

        // Capture the session event reported via the callback.
        ArgumentCaptor<Integer> sessionEventCaptor = ArgumentCaptor.forClass(Integer.class);
        verify(mVideoCallCallback, timeout(TEST_TIMEOUT)).onCallSessionEvent(
                sessionEventCaptor.capture());

        // See the MockVideoProvider class; for convenience when the camera is nulled we just send
        // back a "camera ready" event.
        assertEquals(VideoProvider.SESSION_EVENT_CAMERA_READY,
                sessionEventCaptor.getValue().intValue());
    }

    /**
     * Tests the {@link VideoCall#setPreviewSurface(Surface)} and
     * {@link VideoProvider#onSetPreviewSurface(Surface)} APIs.