blob: 78e22dde6f5b74b35eec1579bc89c5e811d67cdd [file] [log] [blame]
/*
* Copyright (C) 2023 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.car.occupantconnection;
import static android.car.Car.CAR_INTENT_ACTION_RECEIVER_SERVICE;
import static android.car.CarOccupantZoneManager.OCCUPANT_TYPE_DRIVER;
import static android.car.CarOccupantZoneManager.OCCUPANT_TYPE_FRONT_PASSENGER;
import static android.car.VehicleAreaSeat.SEAT_ROW_1_LEFT;
import static android.car.VehicleAreaSeat.SEAT_ROW_1_RIGHT;
import static android.car.occupantconnection.CarOccupantConnectionManager.CONNECTION_ERROR_NOT_READY;
import static android.car.occupantconnection.CarOccupantConnectionManager.CONNECTION_ERROR_PEER_APP_NOT_INSTALLED;
import static android.car.occupantconnection.CarOccupantConnectionManager.CONNECTION_ERROR_UNKNOWN;
import static com.google.common.truth.Truth.assertThat;
import static org.junit.Assert.assertThrows;
import static org.mockito.ArgumentMatchers.anyInt;
import static org.mockito.Mockito.any;
import static org.mockito.Mockito.anyString;
import static org.mockito.Mockito.doAnswer;
import static org.mockito.Mockito.eq;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.never;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
import android.car.Car;
import android.car.CarOccupantZoneManager.OccupantZoneInfo;
import android.car.occupantconnection.IBackendReceiver;
import android.car.occupantconnection.IConnectionRequestCallback;
import android.car.occupantconnection.IPayloadCallback;
import android.car.occupantconnection.Payload;
import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
import android.content.ServiceConnection;
import android.content.pm.PackageInfo;
import android.content.pm.PackageManager;
import android.os.Binder;
import android.os.IBinder;
import android.os.RemoteException;
import android.os.UserHandle;
import android.util.ArrayMap;
import android.util.ArraySet;
import com.android.car.CarLocalServices;
import com.android.car.CarOccupantZoneService;
import com.android.car.internal.util.BinderKeyValueContainer;
import org.junit.After;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.Mock;
import org.mockito.junit.MockitoJUnitRunner;
@RunWith(MockitoJUnitRunner.class)
public final class CarOccupantConnectionServiceTest {
private static final String PACKAGE_NAME = "my_package_name";
private static final String FAKE_PACKAGE_NAME = "fake_package_name";
private static final String RECEIVER_ENDPOINT_ID = "test_receiver_endpoint";
private static final int USER_ID = 123;
private static final int RECEIVER_USER_ID = 456;
@Mock
private Context mContext;
@Mock
private CarOccupantZoneService mOccupantZoneService;
@Mock
private CarRemoteDeviceService mRemoteDeviceService;
@Mock
private IPayloadCallback mPayloadCallback;
@Mock
private IBinder mPayloadCallbackBinder;
@Mock
private IConnectionRequestCallback mConnectionRequestCallback;
@Mock
private IBinder mConnectionRequestCallbackBinder;
// TODO(b/272196149): make zone IDs constant.
private final OccupantZoneInfo mReceiverZone =
new OccupantZoneInfo(/* zoneId= */ 0, OCCUPANT_TYPE_DRIVER, SEAT_ROW_1_LEFT);
private final OccupantZoneInfo mSenderZone =
new OccupantZoneInfo(/* zoneId= */ 1, OCCUPANT_TYPE_FRONT_PASSENGER, SEAT_ROW_1_RIGHT);
private final ArraySet<ClientId> mConnectingReceiverServices = new ArraySet<>();
private final BinderKeyValueContainer<ClientId, IBackendReceiver>
mConnectedReceiverServiceMap = new BinderKeyValueContainer<>();
private final ArrayMap<ClientId, ServiceConnection> mReceiverServiceConnectionMap =
new ArrayMap<>();
private final BinderKeyValueContainer<ReceiverEndpointId, IPayloadCallback>
mPreregisteredReceiverEndpointMap = new BinderKeyValueContainer<>();
private final BinderKeyValueContainer<ReceiverEndpointId, IPayloadCallback>
mRegisteredReceiverEndpointMap = new BinderKeyValueContainer<>();
private final BinderKeyValueContainer<ConnectionId, IConnectionRequestCallback>
mPendingConnectionRequestMap = new BinderKeyValueContainer<>();
private final BinderKeyValueContainer<ConnectionId, IConnectionRequestCallback>
mAcceptedConnectionRequestMap = new BinderKeyValueContainer<>();
private final ArraySet<ConnectionRecord> mEstablishedConnections = new ArraySet<>();
private CarOccupantConnectionService mService;
@Before
public void setUp() throws PackageManager.NameNotFoundException {
// Stored as static: Other tests can leave things behind and fail this test in add call.
// So just remove as safety guard.
CarLocalServices.removeServiceForTest(CarOccupantZoneService.class);
CarLocalServices.addService(CarOccupantZoneService.class, mOccupantZoneService);
CarLocalServices.removeServiceForTest(CarRemoteDeviceService.class);
CarLocalServices.addService(CarRemoteDeviceService.class, mRemoteDeviceService);
mService = new CarOccupantConnectionService(mContext,
mOccupantZoneService,
mRemoteDeviceService,
mConnectingReceiverServices,
mConnectedReceiverServiceMap,
mReceiverServiceConnectionMap,
mPreregisteredReceiverEndpointMap,
mRegisteredReceiverEndpointMap,
mPendingConnectionRequestMap,
mAcceptedConnectionRequestMap,
mEstablishedConnections);
mService.init();
when(mPayloadCallback.asBinder()).thenReturn(mPayloadCallbackBinder);
when(mConnectionRequestCallback.asBinder()).thenReturn(mConnectionRequestCallbackBinder);
mockPackageName();
}
@After
public void tearDown() {
CarLocalServices.removeServiceForTest(CarRemoteDeviceService.class);
CarLocalServices.removeServiceForTest(CarOccupantZoneService.class);
}
@Test
public void testRegisterReceiverWithoutPermission_throwsException() {
when(mContext.checkCallingOrSelfPermission(eq(Car.PERMISSION_MANAGE_OCCUPANT_CONNECTION)))
.thenReturn(PackageManager.PERMISSION_DENIED);
assertThrows(SecurityException.class,
() -> mService.registerReceiver(PACKAGE_NAME, RECEIVER_ENDPOINT_ID,
mPayloadCallback));
}
@Test
public void testRegisterReceiverWithFakePackageName_throwsException() {
assertThrows(SecurityException.class,
() -> mService.registerReceiver(FAKE_PACKAGE_NAME, RECEIVER_ENDPOINT_ID,
mPayloadCallback));
}
@Test
public void testRegisterReceiverWithDuplicateId_throwsException() {
UserHandle receiverUserHandle = Binder.getCallingUserHandle();
when(mOccupantZoneService.getOccupantZoneForUser(receiverUserHandle))
.thenReturn(mReceiverZone);
mService.registerReceiver(PACKAGE_NAME, RECEIVER_ENDPOINT_ID, mPayloadCallback);
IPayloadCallback payloadCallback2 = mock(IPayloadCallback.class);
IBinder payloadCallbackBinder2 = mock(IBinder.class);
when(payloadCallback2.asBinder()).thenReturn(payloadCallbackBinder2);
assertThrows(IllegalStateException.class,
() -> mService.registerReceiver(PACKAGE_NAME, RECEIVER_ENDPOINT_ID,
payloadCallback2));
}
@Test
public void testRegisterTwoReceiversWithoutReceiverServiceConnected() {
UserHandle receiverUserHandle = Binder.getCallingUserHandle();
when(mOccupantZoneService.getOccupantZoneForUser(receiverUserHandle))
.thenReturn(mReceiverZone);
mService.registerReceiver(PACKAGE_NAME, RECEIVER_ENDPOINT_ID, mPayloadCallback);
// It should have a receiver service that is not connected yet, and a receiver endpoint
// pending registration.
assertThat(mConnectingReceiverServices.size()).isEqualTo(1);
assertThat(mConnectedReceiverServiceMap.size()).isEqualTo(0);
assertThat(mPreregisteredReceiverEndpointMap.size()).isEqualTo(1);
assertThat(mRegisteredReceiverEndpointMap.size()).isEqualTo(0);
ClientId clientId = mConnectingReceiverServices.valueAt(0);
assertThat(clientId.packageName).isEqualTo(PACKAGE_NAME);
ReceiverEndpointId receiverEndpointId =
new ReceiverEndpointId(clientId, RECEIVER_ENDPOINT_ID);
assertThat(mPreregisteredReceiverEndpointMap.get(receiverEndpointId))
.isEqualTo(mPayloadCallback);
// One more receiver endpoint pending registration.
String endpointId2 = "ID2";
IPayloadCallback payloadCallback2 = mock(IPayloadCallback.class);
IBinder payloadCallbackBinder2 = mock(IBinder.class);
when(payloadCallback2.asBinder()).thenReturn(payloadCallbackBinder2);
mService.registerReceiver(PACKAGE_NAME, endpointId2, payloadCallback2);
assertThat(mConnectingReceiverServices.size()).isEqualTo(1);
assertThat(mConnectedReceiverServiceMap.size()).isEqualTo(0);
assertThat(mPreregisteredReceiverEndpointMap.size()).isEqualTo(2);
assertThat(mRegisteredReceiverEndpointMap.size()).isEqualTo(0);
ReceiverEndpointId receiverEndpointId2 =
new ReceiverEndpointId(clientId, endpointId2);
assertThat(mPreregisteredReceiverEndpointMap.get(receiverEndpointId2))
.isEqualTo(payloadCallback2);
}
@Test
public void testRegisterReceiverThenConnectReceiverService() {
ServiceConnection[] connection = new ServiceConnection[1];
doAnswer((invocation) -> {
Object[] args = invocation.getArguments();
Intent intent = (Intent) args[0];
assertThat(intent.getAction()).isEqualTo(CAR_INTENT_ACTION_RECEIVER_SERVICE);
connection[0] = (ServiceConnection) args[1];
return null;
}).when(mContext).bindServiceAsUser(any(Intent.class), any(ServiceConnection.class),
anyInt(), any(UserHandle.class));
// Since the receiver service is not connected, the receiver endpoint is
// pending registration.
UserHandle receiverUserHandle = Binder.getCallingUserHandle();
when(mOccupantZoneService.getOccupantZoneForUser(receiverUserHandle))
.thenReturn(mReceiverZone);
mService.registerReceiver(PACKAGE_NAME, RECEIVER_ENDPOINT_ID, mPayloadCallback);
// The receiver service is connected.
ComponentName componentName = mock(ComponentName.class);
IBinder binder = mock(IBinder.class);
IBackendReceiver receiverService = mock(IBackendReceiver.class);
when(binder.queryLocalInterface(anyString())).thenReturn(receiverService);
when(receiverService.asBinder()).thenReturn(binder);
connection[0].onServiceConnected(componentName, binder);
// The receiver endpoint should be registered.
assertThat(mConnectingReceiverServices.size()).isEqualTo(0);
assertThat(mConnectedReceiverServiceMap.size()).isEqualTo(1);
assertThat(mPreregisteredReceiverEndpointMap.size()).isEqualTo(0);
assertThat(mRegisteredReceiverEndpointMap.size()).isEqualTo(1);
ClientId clientId = mConnectedReceiverServiceMap.keyAt(0);
assertThat(mConnectedReceiverServiceMap.get(clientId))
.isEqualTo(receiverService);
ReceiverEndpointId receiverEndpointId =
new ReceiverEndpointId(clientId, RECEIVER_ENDPOINT_ID);
assertThat(mRegisteredReceiverEndpointMap.get(receiverEndpointId))
.isEqualTo(mPayloadCallback);
}
@Test
public void testRegisterReceiverWithReceiverServiceConnected() {
ServiceConnection[] connection = new ServiceConnection[1];
doAnswer((invocation) -> {
Object[] args = invocation.getArguments();
Intent intent = (Intent) args[0];
assertThat(intent.getAction()).isEqualTo(CAR_INTENT_ACTION_RECEIVER_SERVICE);
connection[0] = (ServiceConnection) args[1];
return null;
}).when(mContext).bindServiceAsUser(any(Intent.class), any(ServiceConnection.class),
anyInt(), any(UserHandle.class));
// Register the first receiver endpoint.
UserHandle receiverUserHandle = Binder.getCallingUserHandle();
when(mOccupantZoneService.getOccupantZoneForUser(receiverUserHandle))
.thenReturn(mReceiverZone);
mService.registerReceiver(PACKAGE_NAME, RECEIVER_ENDPOINT_ID, mPayloadCallback);
// The receiver service is connected.
ComponentName componentName = mock(ComponentName.class);
IBinder binder = mock(IBinder.class);
IBackendReceiver receiverService = mock(IBackendReceiver.class);
when(binder.queryLocalInterface(anyString())).thenReturn(receiverService);
when(receiverService.asBinder()).thenReturn(binder);
connection[0].onServiceConnected(componentName, binder);
// Register the second receiver endpoint.
String endpointId2 = "ID2";
IPayloadCallback payloadCallback2 = mock(IPayloadCallback.class);
IBinder payloadCallbackBinder2 = mock(IBinder.class);
when(payloadCallback2.asBinder()).thenReturn(payloadCallbackBinder2);
mService.registerReceiver(PACKAGE_NAME, endpointId2, payloadCallback2);
assertThat(mConnectingReceiverServices.size()).isEqualTo(0);
assertThat(mConnectedReceiverServiceMap.size()).isEqualTo(1);
assertThat(mPreregisteredReceiverEndpointMap.size()).isEqualTo(0);
assertThat(mRegisteredReceiverEndpointMap.size()).isEqualTo(2);
ClientId clientId = mConnectedReceiverServiceMap.keyAt(0);
ReceiverEndpointId receiverEndpointId2 =
new ReceiverEndpointId(clientId, endpointId2);
assertThat(mRegisteredReceiverEndpointMap.get(receiverEndpointId2))
.isEqualTo(payloadCallback2);
// The receiver service is disconnected.
connection[0].onServiceDisconnected(componentName);
assertThat(mConnectingReceiverServices.size()).isEqualTo(0);
assertThat(mConnectedReceiverServiceMap.size()).isEqualTo(0);
}
@Test
public void testUnregisterReceiverWithoutPermission_throwsException() {
when(mContext.checkCallingOrSelfPermission(eq(Car.PERMISSION_MANAGE_OCCUPANT_CONNECTION)))
.thenReturn(PackageManager.PERMISSION_DENIED);
assertThrows(SecurityException.class,
() -> mService.unregisterReceiver(PACKAGE_NAME, RECEIVER_ENDPOINT_ID));
}
@Test
public void testUnregisterReceiverWithFakePackageName_throwsException() {
assertThrows(SecurityException.class,
() -> mService.unregisterReceiver(FAKE_PACKAGE_NAME, RECEIVER_ENDPOINT_ID));
}
@Test
public void testUnregisterNonexistentReceiver_throwsException() {
UserHandle receiverUserHandle = Binder.getCallingUserHandle();
when(mOccupantZoneService.getOccupantZoneForUser(receiverUserHandle))
.thenReturn(mReceiverZone);
assertThrows(IllegalStateException.class,
() -> mService.unregisterReceiver(PACKAGE_NAME, RECEIVER_ENDPOINT_ID));
}
@Test
public void testUnregisterReceiverWithReceiverServiceBound() throws RemoteException {
UserHandle receiverUserHandle = Binder.getCallingUserHandle();
when(mOccupantZoneService.getOccupantZoneForUser(receiverUserHandle))
.thenReturn(mReceiverZone);
ClientId receiverClient = mService.getCallingClientId(PACKAGE_NAME);
IBinder binder = mock(IBinder.class);
IBackendReceiver receiverService = mock(IBackendReceiver.class);
when(receiverService.asBinder()).thenReturn(binder);
// The receiver service is connected already.
mConnectedReceiverServiceMap.put(receiverClient, receiverService);
ServiceConnection serviceConnection = mock(ServiceConnection.class);
mReceiverServiceConnectionMap.put(receiverClient, serviceConnection);
mService.registerReceiver(PACKAGE_NAME, RECEIVER_ENDPOINT_ID, mPayloadCallback);
ReceiverEndpointId receiverEndpoint =
new ReceiverEndpointId(receiverClient, RECEIVER_ENDPOINT_ID);
assertThat(mRegisteredReceiverEndpointMap.get(receiverEndpoint))
.isEqualTo(mPayloadCallback);
assertThat(mPreregisteredReceiverEndpointMap.size()).isEqualTo(0);
mService.unregisterReceiver(PACKAGE_NAME, RECEIVER_ENDPOINT_ID);
// The receiver endpoint should be unregistered.
assertThat(mRegisteredReceiverEndpointMap.size()).isEqualTo(0);
assertThat(mPreregisteredReceiverEndpointMap.size()).isEqualTo(0);
// The receiver service should be unbound since there is no receiver endpoint, no
// established connection, and no pending connection request.
// TODO(b/272196149): utilize assertWithMessage to incorporate the scenarios
// described in the comments.
assertThat(mConnectedReceiverServiceMap.size()).isEqualTo(0);
assertThat(mReceiverServiceConnectionMap.size()).isEqualTo(0);
verify(receiverService).unregisterReceiver(eq(RECEIVER_ENDPOINT_ID));
verify(mContext).unbindService(serviceConnection);
}
@Test
public void testUnregisterReceiverWithoutReceiverServiceBound() {
UserHandle receiverUserHandle = Binder.getCallingUserHandle();
when(mOccupantZoneService.getOccupantZoneForUser(receiverUserHandle))
.thenReturn(mReceiverZone);
mService.registerReceiver(PACKAGE_NAME, RECEIVER_ENDPOINT_ID, mPayloadCallback);
assertThat(mPreregisteredReceiverEndpointMap.size()).isEqualTo(1);
assertThat(mConnectingReceiverServices.size()).isEqualTo(1);
ClientId receiverClient = mService.getCallingClientId(PACKAGE_NAME);
ServiceConnection serviceConnection = mReceiverServiceConnectionMap.get(receiverClient);
assertThat(serviceConnection).isNotNull();
mService.unregisterReceiver(PACKAGE_NAME, RECEIVER_ENDPOINT_ID);
// The receiver endpoint should be unregistered.
assertThat(mPreregisteredReceiverEndpointMap.size()).isEqualTo(0);
// The receiver service should be unbound since there is no receiver endpoint, no
// established connection, and no pending connection request.
assertThat(mConnectingReceiverServices.size()).isEqualTo(0);
assertThat(mReceiverServiceConnectionMap.size()).isEqualTo(0);
verify(mContext).unbindService(serviceConnection);
}
@Test
public void testUnregisterReceiverWithOtherReceiversLeft() {
UserHandle receiverUserHandle = Binder.getCallingUserHandle();
when(mOccupantZoneService.getOccupantZoneForUser(receiverUserHandle))
.thenReturn(mReceiverZone);
// Register the first receiver endpoint.
mService.registerReceiver(PACKAGE_NAME, RECEIVER_ENDPOINT_ID, mPayloadCallback);
// Register the second receiver endpoint.
String receiverEndpointId2 = "another_endpoint";
IPayloadCallback payloadCallback2 = mock(IPayloadCallback.class);
IBinder binder2 = mock(IBinder.class);
when(payloadCallback2.asBinder()).thenReturn(binder2);
mService.registerReceiver(PACKAGE_NAME, receiverEndpointId2, payloadCallback2);
assertThat(mPreregisteredReceiverEndpointMap.size()).isEqualTo(2);
// Unregister the first receiver endpoint.
mService.unregisterReceiver(PACKAGE_NAME, RECEIVER_ENDPOINT_ID);
// The first receiver endpoint should be unregistered.
assertThat(mPreregisteredReceiverEndpointMap.size()).isEqualTo(1);
ClientId receiverClient = mService.getCallingClientId(PACKAGE_NAME);
ReceiverEndpointId receiverEndpoint2 =
new ReceiverEndpointId(receiverClient, receiverEndpointId2);
assertThat(mPreregisteredReceiverEndpointMap.get(receiverEndpoint2))
.isEqualTo(payloadCallback2);
// The receiver service should not be unbound since there is another receiver endpoint
// registered.
assertThat(mConnectingReceiverServices.size()).isEqualTo(1);
assertThat(mReceiverServiceConnectionMap.size()).isEqualTo(1);
verify(mContext, never()).unbindService(any());
}
@Test
public void testRequestConnectionWithoutPermission_throwsException() {
when(mContext.checkCallingOrSelfPermission(eq(Car.PERMISSION_MANAGE_OCCUPANT_CONNECTION)))
.thenReturn(PackageManager.PERMISSION_DENIED);
assertThrows(SecurityException.class,
() -> mService.requestConnection(PACKAGE_NAME, mReceiverZone,
mConnectionRequestCallback));
}
@Test
public void testRequestConnectionWithFakePackageName_throwsException() {
assertThrows(SecurityException.class,
() -> mService.requestConnection(FAKE_PACKAGE_NAME, mReceiverZone,
mConnectionRequestCallback));
}
@Test
public void testRequestConnectionWithoutReceiverServiceBoundBefore() throws RemoteException {
// The receiver service is not bound yet before the sender requests a connection.
UserHandle senderUserHandle = Binder.getCallingUserHandle();
mockAppInstalled(mSenderZone, senderUserHandle);
UserHandle receiverUserHandle = UserHandle.of(RECEIVER_USER_ID);
mockAppInstalled(mReceiverZone, receiverUserHandle);
mService.requestConnection(PACKAGE_NAME, mReceiverZone, mConnectionRequestCallback);
// The sender endpoint should be saved in the cache.
assertThat(mPendingConnectionRequestMap.size()).isEqualTo(1);
ConnectionId connectionId = mPendingConnectionRequestMap.keyAt(0);
assertThat(connectionId.senderClient.packageName).isEqualTo(PACKAGE_NAME);
// It should start binding the receiver service automatically.
assertThat(mReceiverServiceConnectionMap.size()).isEqualTo(1);
assertThat(mConnectingReceiverServices.size()).isEqualTo(1);
ClientId receiverClient = mReceiverServiceConnectionMap.keyAt(0);
assertThat(receiverClient.packageName).isEqualTo(PACKAGE_NAME);
assertThat(mConnectingReceiverServices.valueAt(0)).isEqualTo(receiverClient);
// The receiver service is connected.
ServiceConnection connection = mReceiverServiceConnectionMap.valueAt(0);
ComponentName componentName = mock(ComponentName.class);
IBinder binder = mock(IBinder.class);
IBackendReceiver receiverService = mock(IBackendReceiver.class);
when(binder.queryLocalInterface(anyString())).thenReturn(receiverService);
when(receiverService.asBinder()).thenReturn(binder);
connection.onServiceConnected(componentName, binder);
assertThat(mConnectingReceiverServices.size()).isEqualTo(0);
assertThat(mConnectedReceiverServiceMap.size()).isEqualTo(1);
assertThat(mConnectedReceiverServiceMap.get(receiverClient)).isEqualTo(receiverService);
assertThat(mPendingConnectionRequestMap.size()).isEqualTo(1);
// The receiver service should be notified for the connection request.
verify(receiverService).onConnectionInitiated(mSenderZone, /* senderVersion= */
0, /* senderSigningInfo= */ null);
}
@Test
public void testRequestConnectionWithReceiverServiceBoundAlready() throws RemoteException {
// The receiver service is bound already before the sender requests a connection.
UserHandle senderUserHandle = Binder.getCallingUserHandle();
mockAppInstalled(mSenderZone, senderUserHandle);
UserHandle receiverUserHandle = UserHandle.of(RECEIVER_USER_ID);
mockAppInstalled(mReceiverZone, receiverUserHandle);
ClientId receiverClient = new ClientId(mReceiverZone, RECEIVER_USER_ID, PACKAGE_NAME);
IBinder binder = mock(IBinder.class);
IBackendReceiver receiverService = mock(IBackendReceiver.class);
when(receiverService.asBinder()).thenReturn(binder);
// Pretend that the receiver service is bound already.
mConnectedReceiverServiceMap.put(receiverClient, receiverService);
ServiceConnection connection = mock(ServiceConnection.class);
mReceiverServiceConnectionMap.put(receiverClient, connection);
mService.requestConnection(PACKAGE_NAME, mReceiverZone, mConnectionRequestCallback);
assertThat(mPendingConnectionRequestMap.size()).isEqualTo(1);
ConnectionId connectionId = mPendingConnectionRequestMap.keyAt(0);
assertThat(connectionId.senderClient.packageName).isEqualTo(PACKAGE_NAME);
// The receiver service should be notified for the connection request.
verify(receiverService).onConnectionInitiated(mSenderZone, /* senderVersion= */
0, /* senderSigningInfo= */ null);
}
@Test
public void testRequestConnectionAlreadyConnected() {
// The sender client is already connected to the receiver client before requesting a
// connection.
UserHandle senderUserHandle = Binder.getCallingUserHandle();
mockAppInstalled(mSenderZone, senderUserHandle);
UserHandle receiverUserHandle = UserHandle.of(RECEIVER_USER_ID);
mockAppInstalled(mReceiverZone, receiverUserHandle);
ClientId senderClient =
new ClientId(mSenderZone, senderUserHandle.getIdentifier(), PACKAGE_NAME);
ClientId receiverClient = new ClientId(mReceiverZone, RECEIVER_USER_ID, PACKAGE_NAME);
ConnectionId connectionId = new ConnectionId(senderClient, receiverClient);
IConnectionRequestCallback callback = mock(IConnectionRequestCallback.class);
IBinder callbackBinder = mock(IBinder.class);
when(callback.asBinder()).thenReturn(callbackBinder);
mAcceptedConnectionRequestMap.put(connectionId, callback);
assertThrows(IllegalStateException.class,
() -> mService.requestConnection(PACKAGE_NAME, mReceiverZone,
mConnectionRequestCallback));
}
@Test
public void testRequestConnectionWithPendingConnection() {
// The sender client is already connected to the receiver client before requesting a
// connection.
UserHandle senderUserHandle = Binder.getCallingUserHandle();
mockAppInstalled(mSenderZone, senderUserHandle);
UserHandle receiverUserHandle = UserHandle.of(RECEIVER_USER_ID);
mockAppInstalled(mReceiverZone, receiverUserHandle);
ClientId senderClient =
new ClientId(mSenderZone, senderUserHandle.getIdentifier(), PACKAGE_NAME);
ClientId receiverClient = new ClientId(mReceiverZone, RECEIVER_USER_ID, PACKAGE_NAME);
ConnectionId connectionId = new ConnectionId(senderClient, receiverClient);
IConnectionRequestCallback callback = mock(IConnectionRequestCallback.class);
IBinder callbackBinder = mock(IBinder.class);
when(callback.asBinder()).thenReturn(callbackBinder);
mPendingConnectionRequestMap.put(connectionId, callback);
assertThrows(IllegalStateException.class,
() -> mService.requestConnection(PACKAGE_NAME, mReceiverZone,
mConnectionRequestCallback));
}
@Test
public void testRequestConnectionReceiverZoneNotReady() throws RemoteException {
UserHandle senderUserHandle = Binder.getCallingUserHandle();
mockAppInstalled(mSenderZone, senderUserHandle);
when(mRemoteDeviceService.isConnectionReady(mReceiverZone)).thenReturn(false);
mService.requestConnection(PACKAGE_NAME, mReceiverZone, mConnectionRequestCallback);
verify(mConnectionRequestCallback).onFailed(mReceiverZone, CONNECTION_ERROR_NOT_READY);
}
@Test
public void testRequestConnectionReceiverAppNotInstalled() throws RemoteException {
UserHandle senderUserHandle = Binder.getCallingUserHandle();
mockAppInstalled(mSenderZone, senderUserHandle);
when(mRemoteDeviceService.isConnectionReady(mReceiverZone)).thenReturn(true);
mService.requestConnection(PACKAGE_NAME, mReceiverZone, mConnectionRequestCallback);
verify(mConnectionRequestCallback)
.onFailed(mReceiverZone, CONNECTION_ERROR_PEER_APP_NOT_INSTALLED);
}
@Test
public void testCancelConnectionWithoutPermission_throwsException() {
when(mContext.checkCallingOrSelfPermission(eq(Car.PERMISSION_MANAGE_OCCUPANT_CONNECTION)))
.thenReturn(PackageManager.PERMISSION_DENIED);
assertThrows(SecurityException.class,
() -> mService.cancelConnection(PACKAGE_NAME, any(OccupantZoneInfo.class)));
}
@Test
public void testCancelConnectionWithFakePackageName_throwsException() {
assertThrows(SecurityException.class,
() -> mService.cancelConnection(FAKE_PACKAGE_NAME, any(OccupantZoneInfo.class)));
}
@Test
public void testCancelConnection() throws RemoteException {
when(mOccupantZoneService.getUserForOccupant(mReceiverZone.zoneId))
.thenReturn(RECEIVER_USER_ID);
ClientId receiverClient = new ClientId(mReceiverZone, RECEIVER_USER_ID, PACKAGE_NAME);
UserHandle senderUserHandle = Binder.getCallingUserHandle();
when(mOccupantZoneService.getOccupantZoneForUser(senderUserHandle))
.thenReturn(mSenderZone);
ClientId senderClient = mService.getCallingClientId(PACKAGE_NAME);
ConnectionId connectionId = new ConnectionId(senderClient, receiverClient);
mPendingConnectionRequestMap.put(connectionId, mConnectionRequestCallback);
IConnectionRequestCallback connectionRequestCallback2 =
mock(IConnectionRequestCallback.class);
IBinder connectionRequestCallbackBinder2 = mock(IBinder.class);
when(connectionRequestCallback2.asBinder()).thenReturn(connectionRequestCallbackBinder2);
mPendingConnectionRequestMap.put(connectionId, connectionRequestCallback2);
ServiceConnection serviceConnection = mock(ServiceConnection.class);
mReceiverServiceConnectionMap.put(receiverClient, serviceConnection);
IBinder binder = mock(IBinder.class);
IBackendReceiver receiverService = mock(IBackendReceiver.class);
when(receiverService.asBinder()).thenReturn(binder);
mConnectedReceiverServiceMap.put(receiverClient, receiverService);
mService.cancelConnection(PACKAGE_NAME, mReceiverZone);
// All pending connection requests should be canceled.
assertThat(mPendingConnectionRequestMap.size()).isEqualTo(0);
// The receiver service should be notified of the cancellation, and should be unbound.
verify(receiverService).onConnectionCanceled(mSenderZone);
verify(mContext).unbindService(serviceConnection);
}
@Test
public void testCancelConnectionWithoutPendingConnectionRequest_throwsException() {
UserHandle senderUserHandle = Binder.getCallingUserHandle();
when(mOccupantZoneService.getOccupantZoneForUser(senderUserHandle))
.thenReturn(mSenderZone);
// There is no pending connection request to cancel, so it should throw an
// IllegalStateException.
assertThrows(IllegalStateException.class,
() -> mService.cancelConnection(PACKAGE_NAME, mReceiverZone));
}
@Test
public void testCancelConnectionWithEstablishedConnection_throwsException() {
UserHandle senderUserHandle = Binder.getCallingUserHandle();
when(mOccupantZoneService.getOccupantZoneForUser(senderUserHandle))
.thenReturn(mSenderZone);
ConnectionRecord connectionRecord =
new ConnectionRecord(PACKAGE_NAME, mSenderZone.zoneId, mReceiverZone.zoneId);
mEstablishedConnections.add(connectionRecord);
// The connection is established already, so canceling it should throw an
// IllegalStateException.
assertThrows(IllegalStateException.class,
() -> mService.cancelConnection(PACKAGE_NAME, mReceiverZone));
}
@Test
public void testReceiverServiceDisconnected() throws RemoteException {
ServiceConnection[] connection = new ServiceConnection[1];
doAnswer((invocation) -> {
Object[] args = invocation.getArguments();
connection[0] = (ServiceConnection) args[1];
return null;
}).when(mContext).bindServiceAsUser(any(Intent.class), any(ServiceConnection.class),
anyInt(), any(UserHandle.class));
UserHandle receiverUserHandle = Binder.getCallingUserHandle();
mockAppInstalled(mReceiverZone, receiverUserHandle);
mService.registerReceiver(PACKAGE_NAME, RECEIVER_ENDPOINT_ID, mPayloadCallback);
ClientId receiverClient = mService.getCallingClientId(PACKAGE_NAME);
ReceiverEndpointId receiverEndpoint =
new ReceiverEndpointId(receiverClient, RECEIVER_ENDPOINT_ID);
ClientId senderClient = new ClientId(mSenderZone, USER_ID, PACKAGE_NAME);
ConnectionId connectionId = new ConnectionId(senderClient, receiverClient);
ConnectionRecord connectionRecord =
new ConnectionRecord(PACKAGE_NAME, mSenderZone.zoneId, mReceiverZone.zoneId);
mConnectingReceiverServices.add(receiverClient);
IBinder binder = mock(IBinder.class);
IBackendReceiver receiverService = mock(IBackendReceiver.class);
when(receiverService.asBinder()).thenReturn(binder);
mConnectedReceiverServiceMap.put(receiverClient, receiverService);
mReceiverServiceConnectionMap.put(receiverClient, connection[0]);
mPreregisteredReceiverEndpointMap.put(receiverEndpoint, mPayloadCallback);
mRegisteredReceiverEndpointMap.put(receiverEndpoint, mPayloadCallback);
mPendingConnectionRequestMap.put(connectionId, mConnectionRequestCallback);
IConnectionRequestCallback callback2 = mock(IConnectionRequestCallback.class);
Binder binder2 = mock(Binder.class);
when(callback2.asBinder()).thenReturn(binder2);
mAcceptedConnectionRequestMap.put(connectionId, callback2);
mEstablishedConnections.add(connectionRecord);
connection[0].onServiceDisconnected(mock(ComponentName.class));
assertThat(mConnectingReceiverServices.isEmpty()).isTrue();
assertThat(mConnectedReceiverServiceMap.size()).isEqualTo(0);
assertThat(mReceiverServiceConnectionMap.isEmpty()).isTrue();
assertThat(mPreregisteredReceiverEndpointMap.size()).isEqualTo(0);
assertThat(mRegisteredReceiverEndpointMap.size()).isEqualTo(0);
assertThat(mPendingConnectionRequestMap.size()).isEqualTo(0);
assertThat(mAcceptedConnectionRequestMap.size()).isEqualTo(0);
assertThat(mEstablishedConnections.isEmpty()).isTrue();
verify(mConnectionRequestCallback).onFailed(receiverClient.occupantZone,
CONNECTION_ERROR_UNKNOWN);
verify(callback2).onDisconnected(receiverClient.occupantZone);
}
@Test
public void testSendPayloadWithoutPermission_throwsException() {
when(mContext.checkCallingOrSelfPermission(eq(Car.PERMISSION_MANAGE_OCCUPANT_CONNECTION)))
.thenReturn(PackageManager.PERMISSION_DENIED);
assertThrows(SecurityException.class,
() -> mService.sendPayload(PACKAGE_NAME, mReceiverZone, any(Payload.class)));
}
@Test
public void testSendPayloadWithFakePackageName_throwsException() {
assertThrows(SecurityException.class,
() -> mService.sendPayload(FAKE_PACKAGE_NAME, mReceiverZone, any(Payload.class)));
}
@Test
public void testSendPayloadWithoutConnection_throwsException() {
UserHandle senderUserHandle = Binder.getCallingUserHandle();
when(mOccupantZoneService.getOccupantZoneForUser(senderUserHandle)).thenReturn(mSenderZone);
assertThrows(IllegalStateException.class,
() -> mService.sendPayload(PACKAGE_NAME, mReceiverZone, any(Payload.class)));
}
@Test
public void testSendPayloadSucceed() throws RemoteException {
UserHandle senderUserHandle = Binder.getCallingUserHandle();
when(mOccupantZoneService.getOccupantZoneForUser(senderUserHandle)).thenReturn(mSenderZone);
ConnectionRecord connectionRecord =
new ConnectionRecord(PACKAGE_NAME, mSenderZone.zoneId, mReceiverZone.zoneId);
// It is connected.
mEstablishedConnections.add(connectionRecord);
when(mOccupantZoneService.getUserForOccupant(mReceiverZone.zoneId))
.thenReturn(RECEIVER_USER_ID);
ClientId receiverClient = new ClientId(mReceiverZone, RECEIVER_USER_ID, PACKAGE_NAME);
IBinder binder = mock(IBinder.class);
IBackendReceiver receiverService = mock(IBackendReceiver.class);
when(receiverService.asBinder()).thenReturn(binder);
// And the receiver service is bound already.
mConnectedReceiverServiceMap.put(receiverClient, receiverService);
Payload payload = mock(Payload.class);
mService.sendPayload(PACKAGE_NAME, mReceiverZone, payload);
// The receiver service should be notified for the payload.
verify(receiverService).onPayloadReceived(mSenderZone, payload);
}
@Test
public void testIsConnectedWithoutPermission_throwsException() {
when(mContext.checkCallingOrSelfPermission(eq(Car.PERMISSION_MANAGE_OCCUPANT_CONNECTION)))
.thenReturn(PackageManager.PERMISSION_DENIED);
assertThrows(SecurityException.class,
() -> mService.isConnected(PACKAGE_NAME, any(OccupantZoneInfo.class)));
}
@Test
public void testIsConnectedWithFakePackageName_throwsException() {
assertThrows(SecurityException.class,
() -> mService.isConnected(FAKE_PACKAGE_NAME, any(OccupantZoneInfo.class)));
}
@Test
public void testIsConnected() {
UserHandle senderUserHandle = Binder.getCallingUserHandle();
when(mOccupantZoneService.getOccupantZoneForUser(senderUserHandle)).thenReturn(mSenderZone);
assertThat(mService.isConnected(PACKAGE_NAME, mReceiverZone)).isFalse();
ConnectionRecord connectionRecord =
new ConnectionRecord(PACKAGE_NAME, mSenderZone.zoneId, mReceiverZone.zoneId);
mEstablishedConnections.add(connectionRecord);
assertThat(mService.isConnected(PACKAGE_NAME, mReceiverZone)).isTrue();
}
@Test
public void testDisconnectWithoutPermission_throwsException() {
when(mContext.checkCallingOrSelfPermission(eq(Car.PERMISSION_MANAGE_OCCUPANT_CONNECTION)))
.thenReturn(PackageManager.PERMISSION_DENIED);
assertThrows(SecurityException.class,
() -> mService.disconnect(PACKAGE_NAME, mReceiverZone));
}
@Test
public void testDisconnectWithFakePackageName_throwsException() {
assertThrows(SecurityException.class,
() -> mService.disconnect(FAKE_PACKAGE_NAME, mReceiverZone));
}
@Test
public void testDisconnect() throws RemoteException {
UserHandle senderUserHandle = Binder.getCallingUserHandle();
when(mOccupantZoneService.getOccupantZoneForUser(senderUserHandle)).thenReturn(mSenderZone);
ConnectionRecord connectionRecord =
new ConnectionRecord(PACKAGE_NAME, mSenderZone.zoneId, mReceiverZone.zoneId);
mEstablishedConnections.add(connectionRecord);
ClientId senderClient = mService.getCallingClientId(PACKAGE_NAME);
when(mOccupantZoneService.getUserForOccupant(mReceiverZone.zoneId))
.thenReturn(RECEIVER_USER_ID);
ClientId receiverClient = new ClientId(mReceiverZone, RECEIVER_USER_ID, PACKAGE_NAME);
ConnectionId connectionId = new ConnectionId(senderClient, receiverClient);
mAcceptedConnectionRequestMap.put(connectionId, mConnectionRequestCallback);
IBinder binder = mock(IBinder.class);
IBackendReceiver receiverService = mock(IBackendReceiver.class);
when(receiverService.asBinder()).thenReturn(binder);
mConnectedReceiverServiceMap.put(receiverClient, receiverService);
ServiceConnection serviceConnection = mock(ServiceConnection.class);
mReceiverServiceConnectionMap.put(receiverClient, serviceConnection);
mService.disconnect(PACKAGE_NAME, mReceiverZone);
assertThat(mEstablishedConnections.size()).isEqualTo(0);
assertThat(mAcceptedConnectionRequestMap.size()).isEqualTo(0);
assertThat(mConnectedReceiverServiceMap.size()).isEqualTo(0);
assertThat(mReceiverServiceConnectionMap.size()).isEqualTo(0);
verify(receiverService).onDisconnected(mSenderZone);
verify(mContext).unbindService(serviceConnection);
}
@Test
public void testDisconnectWithoutEstablishedConnection_throwsException() {
UserHandle senderUserHandle = Binder.getCallingUserHandle();
when(mOccupantZoneService.getOccupantZoneForUser(senderUserHandle)).thenReturn(mSenderZone);
// The connection is not established yet, so disconnecting should throw an
// IllegalStateException.
assertThrows(IllegalStateException.class,
() -> mService.disconnect(PACKAGE_NAME, mReceiverZone));
}
@Test
public void testConnectedSenderDied() throws RemoteException {
UserHandle senderUserHandle = Binder.getCallingUserHandle();
when(mOccupantZoneService.getOccupantZoneForUser(senderUserHandle)).thenReturn(mSenderZone);
ConnectionRecord connectionRecord =
new ConnectionRecord(PACKAGE_NAME, mSenderZone.zoneId, mReceiverZone.zoneId);
mEstablishedConnections.add(connectionRecord);
ClientId senderClient = mService.getCallingClientId(PACKAGE_NAME);
int receiverUserId = 456;
when(mOccupantZoneService.getUserForOccupant(mReceiverZone.zoneId))
.thenReturn(receiverUserId);
ClientId receiverClient = new ClientId(mReceiverZone, receiverUserId, PACKAGE_NAME);
ConnectionId connectionId = new ConnectionId(senderClient, receiverClient);
TestConnectionRequestCallback connectionRequestCallback =
new TestConnectionRequestCallback();
mAcceptedConnectionRequestMap.put(connectionId, connectionRequestCallback);
IBinder binder = mock(IBinder.class);
IBackendReceiver receiverService = mock(IBackendReceiver.class);
when(receiverService.asBinder()).thenReturn(binder);
mConnectedReceiverServiceMap.put(receiverClient, receiverService);
ServiceConnection serviceConnection = mock(ServiceConnection.class);
mReceiverServiceConnectionMap.put(receiverClient, serviceConnection);
// The sender dies.
connectionRequestCallback.die();
assertThat(mEstablishedConnections.size()).isEqualTo(0);
assertThat(mAcceptedConnectionRequestMap.size()).isEqualTo(0);
assertThat(mConnectedReceiverServiceMap.size()).isEqualTo(0);
assertThat(mReceiverServiceConnectionMap.size()).isEqualTo(0);
verify(receiverService).onDisconnected(mSenderZone);
verify(mContext).unbindService(serviceConnection);
}
@Test
public void testConnectingSenderDiedWithoutReceiverServiceBound() throws RemoteException {
UserHandle senderUserHandle = Binder.getCallingUserHandle();
when(mOccupantZoneService.getOccupantZoneForUser(senderUserHandle)).thenReturn(mSenderZone);
ClientId senderClient = mService.getCallingClientId(PACKAGE_NAME);
int receiverUserId = 456;
when(mOccupantZoneService.getUserForOccupant(mReceiverZone.zoneId))
.thenReturn(receiverUserId);
ClientId receiverClient = new ClientId(mReceiverZone, receiverUserId, PACKAGE_NAME);
ConnectionId connectionId = new ConnectionId(senderClient, receiverClient);
TestConnectionRequestCallback connectionRequestCallback =
new TestConnectionRequestCallback();
mPendingConnectionRequestMap.put(connectionId, connectionRequestCallback);
mConnectingReceiverServices.add(receiverClient);
ServiceConnection serviceConnection = mock(ServiceConnection.class);
mReceiverServiceConnectionMap.put(receiverClient, serviceConnection);
// The sender dies.
connectionRequestCallback.die();
assertThat(mPendingConnectionRequestMap.size()).isEqualTo(0);
assertThat(mConnectingReceiverServices.size()).isEqualTo(0);
assertThat(mReceiverServiceConnectionMap.size()).isEqualTo(0);
verify(mContext).unbindService(serviceConnection);
}
@Test
public void testConnectingSenderDiedWithReceiverServiceBound() throws RemoteException {
UserHandle senderUserHandle = Binder.getCallingUserHandle();
when(mOccupantZoneService.getOccupantZoneForUser(senderUserHandle)).thenReturn(mSenderZone);
ClientId senderClient = mService.getCallingClientId(PACKAGE_NAME);
int receiverUserId = 456;
when(mOccupantZoneService.getUserForOccupant(mReceiverZone.zoneId))
.thenReturn(receiverUserId);
ClientId receiverClient = new ClientId(mReceiverZone, receiverUserId, PACKAGE_NAME);
ConnectionId connectionId = new ConnectionId(senderClient, receiverClient);
TestConnectionRequestCallback connectionRequestCallback =
new TestConnectionRequestCallback();
mPendingConnectionRequestMap.put(connectionId, connectionRequestCallback);
IBinder binder = mock(IBinder.class);
IBackendReceiver receiverService = mock(IBackendReceiver.class);
when(receiverService.asBinder()).thenReturn(binder);
mConnectedReceiverServiceMap.put(receiverClient, receiverService);
ServiceConnection serviceConnection = mock(ServiceConnection.class);
mReceiverServiceConnectionMap.put(receiverClient, serviceConnection);
// The sender dies.
connectionRequestCallback.die();
assertThat(mPendingConnectionRequestMap.size()).isEqualTo(0);
assertThat(mConnectedReceiverServiceMap.size()).isEqualTo(0);
assertThat(mReceiverServiceConnectionMap.size()).isEqualTo(0);
verify(receiverService).onConnectionCanceled(mSenderZone);
verify(mContext).unbindService(serviceConnection);
}
private void mockPackageName() throws PackageManager.NameNotFoundException {
PackageManager pm = mock(PackageManager.class);
when(mContext.getPackageManager()).thenReturn(pm);
when(pm.getPackageUidAsUser(eq(PACKAGE_NAME), anyInt())).thenReturn(Binder.getCallingUid());
}
private void mockAppInstalled(OccupantZoneInfo occupantZone, UserHandle userHandle) {
when(mRemoteDeviceService.isConnectionReady(occupantZone)).thenReturn(true);
PackageInfo packageInfo = mock(PackageInfo.class);
when(mRemoteDeviceService.getEndpointPackageInfo(occupantZone.zoneId, PACKAGE_NAME))
.thenReturn(packageInfo);
when(mOccupantZoneService.getOccupantZoneForUser(userHandle))
.thenReturn(occupantZone);
when(mOccupantZoneService.getUserForOccupant(occupantZone.zoneId))
.thenReturn(userHandle.getIdentifier());
when(mRemoteDeviceService.getPackageInfoAsUser(PACKAGE_NAME, userHandle.getIdentifier()))
.thenReturn(packageInfo);
}
private static final class TestConnectionRequestCallback extends android.os.Binder implements
IConnectionRequestCallback {
private DeathRecipient mRecipient;
@Override
public void linkToDeath(DeathRecipient recipient, int flags) {
// In any situation, a single binder object should only have at most one death
// recipient.
assertThat(mRecipient).isNull();
mRecipient = recipient;
}
@Override
public boolean unlinkToDeath(DeathRecipient recipient, int flags) {
assertThat(mRecipient).isSameInstanceAs(recipient);
mRecipient = null;
return true;
}
@Override
public IBinder asBinder() {
return this;
}
@Override
public void onConnected(OccupantZoneInfo receiverZone) {
}
@Override
public void onFailed(OccupantZoneInfo receiverZone, int connectionError) {
}
@Override
public void onDisconnected(OccupantZoneInfo receiverZone) {
}
private void die() {
if (mRecipient != null) {
mRecipient.binderDied(this);
}
mRecipient = null;
}
}
}