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

Commit 26bae92d authored by Brad Ebinger's avatar Brad Ebinger Committed by android-build-merger
Browse files

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

am: 787eced1

Change-Id: I025aa2ecdf9d93a9b27e7d2bd1d781a0e62ed90e
parents f7a93647 787eced1
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.