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

When a calling InCallService attempts to use the setCamera API on the
VideoCall, Telecom will perform a permission check to ensure that the
caller has the correct camera permission and passes the app-ops camera
check.  A failure to set the camera will result in a callback via the
call session event API.

This got a little messy as the app ops package name needs to come from the
InCallService, and handler usage in the VideoProvider API means we had to
pass around the uid/pid of the caller, obtained before we trampoline onto
the handler.

Test: Unit tests added, manual testing performed.
Bug: 32747443
Change-Id: I555a04f9c3fb45e60bb811f64ba855ccf2e3b0e2
This commit is contained in:
Tyler Gunn
2016-11-09 10:19:23 -08:00
parent baca8f50df
commit b88b311463
12 changed files with 107 additions and 25 deletions

View File

@@ -36511,6 +36511,7 @@ package android.telecom {
method public void receiveSessionModifyResponse(int, android.telecom.VideoProfile, android.telecom.VideoProfile);
method public void setCallDataUsage(long);
field public static final int SESSION_EVENT_CAMERA_FAILURE = 5; // 0x5
field public static final int SESSION_EVENT_CAMERA_PERMISSION_ERROR = 7; // 0x7
field public static final int SESSION_EVENT_CAMERA_READY = 6; // 0x6
field public static final int SESSION_EVENT_RX_PAUSE = 1; // 0x1
field public static final int SESSION_EVENT_RX_RESUME = 2; // 0x2

View File

@@ -39420,6 +39420,7 @@ package android.telecom {
method public void receiveSessionModifyResponse(int, android.telecom.VideoProfile, android.telecom.VideoProfile);
method public void setCallDataUsage(long);
field public static final int SESSION_EVENT_CAMERA_FAILURE = 5; // 0x5
field public static final int SESSION_EVENT_CAMERA_PERMISSION_ERROR = 7; // 0x7
field public static final int SESSION_EVENT_CAMERA_READY = 6; // 0x6
field public static final int SESSION_EVENT_RX_PAUSE = 1; // 0x1
field public static final int SESSION_EVENT_RX_RESUME = 2; // 0x2

View File

@@ -36600,6 +36600,7 @@ package android.telecom {
method public void receiveSessionModifyResponse(int, android.telecom.VideoProfile, android.telecom.VideoProfile);
method public void setCallDataUsage(long);
field public static final int SESSION_EVENT_CAMERA_FAILURE = 5; // 0x5
field public static final int SESSION_EVENT_CAMERA_PERMISSION_ERROR = 7; // 0x7
field public static final int SESSION_EVENT_CAMERA_READY = 6; // 0x6
field public static final int SESSION_EVENT_RX_PAUSE = 1; // 0x1
field public static final int SESSION_EVENT_RX_RESUME = 2; // 0x2

View File

@@ -843,6 +843,7 @@ public final class Call {
private String mParentId = null;
private int mState;
private List<String> mCannedTextResponses = null;
private String mCallingPackage;
private String mRemainingPostDialSequence;
private VideoCallImpl mVideoCallImpl;
private Details mDetails;
@@ -1330,19 +1331,22 @@ public final class Call {
}
/** {@hide} */
Call(Phone phone, String telecomCallId, InCallAdapter inCallAdapter) {
Call(Phone phone, String telecomCallId, InCallAdapter inCallAdapter, String callingPackage) {
mPhone = phone;
mTelecomCallId = telecomCallId;
mInCallAdapter = inCallAdapter;
mState = STATE_NEW;
mCallingPackage = callingPackage;
}
/** {@hide} */
Call(Phone phone, String telecomCallId, InCallAdapter inCallAdapter, int state) {
Call(Phone phone, String telecomCallId, InCallAdapter inCallAdapter, int state,
String callingPackage) {
mPhone = phone;
mTelecomCallId = telecomCallId;
mInCallAdapter = inCallAdapter;
mState = state;
mCallingPackage = callingPackage;
}
/** {@hide} */
@@ -1352,6 +1356,7 @@ public final class Call {
/** {@hide} */
final void internalUpdate(ParcelableCall parcelableCall, Map<String, Call> callIdMap) {
// First, we update the internal state as far as possible before firing any updates.
Details details = Details.createFromParcelableCall(parcelableCall);
boolean detailsChanged = !Objects.equals(mDetails, details);
@@ -1367,7 +1372,7 @@ public final class Call {
cannedTextResponsesChanged = true;
}
VideoCallImpl newVideoCallImpl = parcelableCall.getVideoCallImpl();
VideoCallImpl newVideoCallImpl = parcelableCall.getVideoCallImpl(mCallingPackage);
boolean videoCallChanged = parcelableCall.isVideoCallProviderChanged() &&
!Objects.equals(mVideoCallImpl, newVideoCallImpl);
if (videoCallChanged) {

View File

@@ -25,6 +25,7 @@ import android.annotation.Nullable;
import android.annotation.SystemApi;
import android.hardware.camera2.CameraManager;
import android.net.Uri;
import android.os.Binder;
import android.os.Bundle;
import android.os.Handler;
import android.os.IBinder;
@@ -785,7 +786,7 @@ public abstract class Connection extends Conferenceable {
public static final int SESSION_EVENT_TX_STOP = 4;
/**
* A camera failure has occurred for the selected camera. The {@link InCallService} can use
* A camera failure has occurred for the selected camera. The {@link VideoProvider} can use
* this as a cue to inform the user the camera is not available.
* @see #handleCallSessionEvent(int)
*/
@@ -793,12 +794,20 @@ public abstract class Connection extends Conferenceable {
/**
* Issued after {@link #SESSION_EVENT_CAMERA_FAILURE} when the camera is once again ready
* for operation. The {@link InCallService} can use this as a cue to inform the user that
* for operation. The {@link VideoProvider} can use this as a cue to inform the user that
* the camera has become available again.
* @see #handleCallSessionEvent(int)
*/
public static final int SESSION_EVENT_CAMERA_READY = 6;
/**
* Session event raised by Telecom when
* {@link android.telecom.InCallService.VideoCall#setCamera(String)} is called and the
* caller does not have the necessary {@link android.Manifest.permission#CAMERA} permission.
* @see #handleCallSessionEvent(int)
*/
public static final int SESSION_EVENT_CAMERA_PERMISSION_ERROR = 7;
/**
* Session modify request was successful.
* @see #receiveSessionModifyResponse(int, VideoProfile, VideoProfile)
@@ -848,6 +857,8 @@ public abstract class Connection extends Conferenceable {
private static final String SESSION_EVENT_TX_STOP_STR = "TX_STOP";
private static final String SESSION_EVENT_CAMERA_FAILURE_STR = "CAMERA_FAIL";
private static final String SESSION_EVENT_CAMERA_READY_STR = "CAMERA_READY";
private static final String SESSION_EVENT_CAMERA_PERMISSION_ERROR_STR =
"CAMERA_PERMISSION_ERROR";
private static final String SESSION_EVENT_UNKNOWN_STR = "UNKNOWN";
private VideoProvider.VideoProviderHandler mMessageHandler;
@@ -906,8 +917,17 @@ public abstract class Connection extends Conferenceable {
break;
}
case MSG_SET_CAMERA:
onSetCamera((String) msg.obj);
break;
{
SomeArgs args = (SomeArgs) msg.obj;
try {
onSetCamera((String) args.arg1);
onSetCamera((String) args.arg1, (String) args.arg2, args.argi1,
args.argi2);
} finally {
args.recycle();
}
}
break;
case MSG_SET_PREVIEW_SURFACE:
onSetPreviewSurface((Surface) msg.obj);
break;
@@ -962,8 +982,19 @@ public abstract class Connection extends Conferenceable {
MSG_REMOVE_VIDEO_CALLBACK, videoCallbackBinder).sendToTarget();
}
public void setCamera(String cameraId) {
mMessageHandler.obtainMessage(MSG_SET_CAMERA, cameraId).sendToTarget();
public void setCamera(String cameraId, String callingPackageName) {
SomeArgs args = SomeArgs.obtain();
args.arg1 = cameraId;
// Propagate the calling package; originally determined in
// android.telecom.InCallService.VideoCall#setCamera(String) from the calling
// process.
args.arg2 = callingPackageName;
// Pass along the uid and pid of the calling app; this gets lost when we put the
// message onto the handler. These are required for Telecom to perform a permission
// check to see if the calling app is able to use the camera.
args.argi1 = Binder.getCallingUid();
args.argi2 = Binder.getCallingPid();
mMessageHandler.obtainMessage(MSG_SET_CAMERA, args).sendToTarget();
}
public void setPreviewSurface(Surface surface) {
@@ -1047,6 +1078,29 @@ public abstract class Connection extends Conferenceable {
*/
public abstract void onSetCamera(String cameraId);
/**
* Sets the camera to be used for the outgoing video.
* <p>
* The {@link VideoProvider} should respond by communicating the capabilities of the chosen
* camera via
* {@link VideoProvider#changeCameraCapabilities(VideoProfile.CameraCapabilities)}.
* <p>
* This prototype is used internally to ensure that the calling package name, UID and PID
* are sent to Telecom so that can perform a camera permission check on the caller.
* <p>
* Sent from the {@link InCallService} via
* {@link InCallService.VideoCall#setCamera(String)}.
*
* @param cameraId The id of the camera (use ids as reported by
* {@link CameraManager#getCameraIdList()}).
* @param callingPackageName The AppOpps package name of the caller.
* @param callingUid The UID of the caller.
* @param callingPid The PID of the caller.
* @hide
*/
public void onSetCamera(String cameraId, String callingPackageName, int callingUid,
int callingPid) {}
/**
* Sets the surface to be used for displaying a preview of what the user's camera is
* currently capturing. When video transmission is enabled, this is the video signal which
@@ -1233,7 +1287,8 @@ public abstract class Connection extends Conferenceable {
* {@link VideoProvider#SESSION_EVENT_TX_START},
* {@link VideoProvider#SESSION_EVENT_TX_STOP},
* {@link VideoProvider#SESSION_EVENT_CAMERA_FAILURE},
* {@link VideoProvider#SESSION_EVENT_CAMERA_READY}.
* {@link VideoProvider#SESSION_EVENT_CAMERA_READY},
* {@link VideoProvider#SESSION_EVENT_CAMERA_FAILURE}.
*/
public void handleCallSessionEvent(int event) {
if (mVideoCallbacks != null) {
@@ -1382,6 +1437,8 @@ public abstract class Connection extends Conferenceable {
return SESSION_EVENT_TX_START_STR;
case SESSION_EVENT_TX_STOP:
return SESSION_EVENT_TX_STOP_STR;
case SESSION_EVENT_CAMERA_PERMISSION_ERROR:
return SESSION_EVENT_CAMERA_PERMISSION_ERROR_STR;
default:
return SESSION_EVENT_UNKNOWN_STR + " " + event;
}

View File

@@ -87,7 +87,8 @@ public abstract class InCallService extends Service {
switch (msg.what) {
case MSG_SET_IN_CALL_ADAPTER:
mPhone = new Phone(new InCallAdapter((IInCallAdapter) msg.obj));
String callingPackage = getApplicationContext().getOpPackageName();
mPhone = new Phone(new InCallAdapter((IInCallAdapter) msg.obj), callingPackage);
mPhone.addListener(mPhoneListener);
onPhoneCreated(mPhone);
break;
@@ -664,7 +665,8 @@ public abstract class InCallService extends Service {
* {@link Connection.VideoProvider#SESSION_EVENT_TX_START},
* {@link Connection.VideoProvider#SESSION_EVENT_TX_STOP},
* {@link Connection.VideoProvider#SESSION_EVENT_CAMERA_FAILURE},
* {@link Connection.VideoProvider#SESSION_EVENT_CAMERA_READY}.
* {@link Connection.VideoProvider#SESSION_EVENT_CAMERA_READY},
* {@link Connection.VideoProvider#SESSION_EVENT_CAMERA_PERMISSION_ERROR}.
*/
public abstract void onCallSessionEvent(int event);

View File

@@ -182,10 +182,10 @@ public final class ParcelableCall implements Parcelable {
* @return The video call.
*/
public VideoCallImpl getVideoCallImpl() {
public VideoCallImpl getVideoCallImpl(String callingPackageName) {
if (mVideoCall == null && mVideoCallProvider != null) {
try {
mVideoCall = new VideoCallImpl(mVideoCallProvider);
mVideoCall = new VideoCallImpl(mVideoCallProvider, callingPackageName);
} catch (RemoteException ignored) {
// Ignore RemoteException.
}

View File

@@ -125,13 +125,16 @@ public final class Phone {
private boolean mCanAddCall = true;
Phone(InCallAdapter adapter) {
private final String mCallingPackage;
Phone(InCallAdapter adapter, String callingPackage) {
mInCallAdapter = adapter;
mCallingPackage = callingPackage;
}
final void internalAddCall(ParcelableCall parcelableCall) {
Call call = new Call(this, parcelableCall.getId(), mInCallAdapter,
parcelableCall.getState());
parcelableCall.getState(), mCallingPackage);
mCallByTelecomCallId.put(parcelableCall.getId(), call);
mCalls.add(call);
checkCallTree(parcelableCall);

View File

@@ -408,6 +408,8 @@ public final class RemoteConnection {
private final IVideoProvider mVideoProviderBinder;
private final String mCallingPackage;
/**
* ConcurrentHashMap constructor params: 8 is initial table size, 0.9f is
* load factor before resizing, 1 means we only expect a single thread to
@@ -416,8 +418,9 @@ public final class RemoteConnection {
private final Set<Callback> mCallbacks = Collections.newSetFromMap(
new ConcurrentHashMap<Callback, Boolean>(8, 0.9f, 1));
VideoProvider(IVideoProvider videoProviderBinder) {
VideoProvider(IVideoProvider videoProviderBinder, String callingPackage) {
mVideoProviderBinder = videoProviderBinder;
mCallingPackage = callingPackage;
try {
mVideoProviderBinder.addVideoCallback(mVideoCallbackServant.getStub().asBinder());
} catch (RemoteException e) {
@@ -452,7 +455,7 @@ public final class RemoteConnection {
*/
public void setCamera(String cameraId) {
try {
mVideoProviderBinder.setCamera(cameraId);
mVideoProviderBinder.setCamera(cameraId, mCallingPackage);
} catch (RemoteException e) {
}
}
@@ -628,7 +631,7 @@ public final class RemoteConnection {
* @hide
*/
RemoteConnection(String callId, IConnectionService connectionService,
ParcelableConnection connection) {
ParcelableConnection connection, String callingPackage) {
mConnectionId = callId;
mConnectionService = connectionService;
mConnected = true;
@@ -640,7 +643,7 @@ public final class RemoteConnection {
mVideoState = connection.getVideoState();
IVideoProvider videoProvider = connection.getVideoProvider();
if (videoProvider != null) {
mVideoProvider = new RemoteConnection.VideoProvider(videoProvider);
mVideoProvider = new RemoteConnection.VideoProvider(videoProvider, callingPackage);
} else {
mVideoProvider = null;
}

View File

@@ -283,9 +283,13 @@ final class RemoteConnectionService {
@Override
public void setVideoProvider(String callId, IVideoProvider videoProvider,
Session.Info sessionInfo) {
String callingPackage = mOurConnectionServiceImpl.getApplicationContext()
.getOpPackageName();
RemoteConnection.VideoProvider remoteVideoProvider = null;
if (videoProvider != null) {
remoteVideoProvider = new RemoteConnection.VideoProvider(videoProvider);
remoteVideoProvider = new RemoteConnection.VideoProvider(videoProvider,
callingPackage);
}
findConnectionForAction(callId, "setVideoProvider")
.setVideoProvider(remoteVideoProvider);
@@ -351,8 +355,10 @@ final class RemoteConnectionService {
@Override
public void addExistingConnection(String callId, ParcelableConnection connection,
Session.Info sessionInfo) {
String callingPackage = mOurConnectionServiceImpl.getApplicationContext().
getOpPackageName();
RemoteConnection remoteConnection = new RemoteConnection(callId,
mOutgoingConnectionServiceRpc, connection);
mOutgoingConnectionServiceRpc, connection, callingPackage);
mConnectionById.put(callId, remoteConnection);
remoteConnection.registerCallback(new RemoteConnection.Callback() {
@Override

View File

@@ -43,6 +43,7 @@ public class VideoCallImpl extends VideoCall {
private VideoCall.Callback mCallback;
private int mVideoQuality = VideoProfile.QUALITY_UNKNOWN;
private int mVideoState = VideoProfile.STATE_AUDIO_ONLY;
private final String mCallingPackageName;
private IBinder.DeathRecipient mDeathRecipient = new IBinder.DeathRecipient() {
@Override
@@ -197,12 +198,13 @@ public class VideoCallImpl extends VideoCall {
private Handler mHandler;
VideoCallImpl(IVideoProvider videoProvider) throws RemoteException {
VideoCallImpl(IVideoProvider videoProvider, String callingPackageName) throws RemoteException {
mVideoProvider = videoProvider;
mVideoProvider.asBinder().linkToDeath(mDeathRecipient, 0);
mBinder = new VideoCallListenerBinder();
mVideoProvider.addVideoCallback(mBinder);
mCallingPackageName = callingPackageName;
}
public void destroy() {
@@ -240,7 +242,8 @@ public class VideoCallImpl extends VideoCall {
/** {@inheritDoc} */
public void setCamera(String cameraId) {
try {
mVideoProvider.setCamera(cameraId);
Log.w(this, "setCamera: cameraId=%s, calling=%s", cameraId, mCallingPackageName);
mVideoProvider.setCamera(cameraId, mCallingPackageName);
} catch (RemoteException e) {
}
}

View File

@@ -30,7 +30,7 @@ oneway interface IVideoProvider {
void removeVideoCallback(IBinder videoCallbackBinder);
void setCamera(String cameraId);
void setCamera(String cameraId, in String mCallingPackageName);
void setPreviewSurface(in Surface surface);