UwbService: Handle vendor service death

Needs to trigger session close callbacks for all the clients and
clear the client map.

Bug: 183904955
Test: atest android.uwb.cts.UwbManagerTest
Change-Id: Ib6da490ba535e692daba418c3d10abf6c0ad3ab8
This commit is contained in:
Roshan Pius
2021-04-08 12:55:11 -07:00
parent e4fd3ed60b
commit 5681a4818d
2 changed files with 106 additions and 18 deletions

View File

@@ -21,6 +21,7 @@ import static com.google.common.truth.Truth.assertThat;
import static org.junit.Assert.fail;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyInt;
import static org.mockito.ArgumentMatchers.argThat;
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.clearInvocations;
import static org.mockito.Mockito.mock;
@@ -58,6 +59,7 @@ import org.mockito.MockitoAnnotations;
@Presubmit
public class UwbServiceImplTest {
@Mock private IUwbAdapter mVendorService;
@Mock private IBinder mVendorServiceBinder;
@Mock private Context mContext;
@Mock private UwbInjector mUwbInjector;
@Captor private ArgumentCaptor<IUwbRangingCallbacks> mRangingCbCaptor;
@@ -70,6 +72,7 @@ public class UwbServiceImplTest {
public void setUp() throws Exception {
MockitoAnnotations.initMocks(this);
when(mUwbInjector.getVendorService()).thenReturn(mVendorService);
when(mVendorService.asBinder()).thenReturn(mVendorServiceBinder);
mUwbServiceImpl = new UwbServiceImpl(mContext, mUwbInjector);
}
@@ -123,10 +126,14 @@ public class UwbServiceImplTest {
final SessionHandle sessionHandle = new SessionHandle(5);
final IUwbRangingCallbacks cb = mock(IUwbRangingCallbacks.class);
final PersistableBundle parameters = new PersistableBundle();
final IBinder cbBinder = mock(IBinder.class);
when(cb.asBinder()).thenReturn(cbBinder);
mUwbServiceImpl.openRanging(sessionHandle, cb, parameters);
verify(mVendorService).openRanging(sessionHandle, cb, parameters);
verify(mVendorService).openRanging(
eq(sessionHandle), mRangingCbCaptor.capture(), eq(parameters));
assertThat(mRangingCbCaptor.getValue()).isNotNull();
}
@Test
@@ -259,4 +266,34 @@ public class UwbServiceImplTest {
mRangingCbCaptor.getValue().onRangingStarted(sessionHandle, parameters);
verify(cb, never()).onRangingStarted(any(), any());
}
@Test
public void testHandleVendorServiceDeath() throws Exception {
final SessionHandle sessionHandle = new SessionHandle(5);
final IUwbRangingCallbacks cb = mock(IUwbRangingCallbacks.class);
final PersistableBundle parameters = new PersistableBundle();
final IBinder cbBinder = mock(IBinder.class);
when(cb.asBinder()).thenReturn(cbBinder);
mUwbServiceImpl.openRanging(sessionHandle, cb, parameters);
verify(mVendorServiceBinder).linkToDeath(mVendorServiceDeathCaptor.capture(), anyInt());
assertThat(mVendorServiceDeathCaptor.getValue()).isNotNull();
verify(mVendorService).openRanging(
eq(sessionHandle), mRangingCbCaptor.capture(), eq(parameters));
assertThat(mRangingCbCaptor.getValue()).isNotNull();
clearInvocations(cb);
// Invoke cb, ensure it reaches the client.
mRangingCbCaptor.getValue().onRangingOpened(sessionHandle);
verify(cb).onRangingOpened(sessionHandle);
// Trigger vendor service death and ensure that the client is informed of session end.
mVendorServiceDeathCaptor.getValue().binderDied();
verify(cb).onRangingClosed(
eq(sessionHandle), eq(RangingSession.Callback.REASON_UNKNOWN),
argThat((p) -> p.isEmpty()));
}
}

View File

@@ -27,6 +27,7 @@ import android.uwb.IUwbAdapter;
import android.uwb.IUwbAdapterStateCallbacks;
import android.uwb.IUwbRangingCallbacks;
import android.uwb.RangingReport;
import android.uwb.RangingSession;
import android.uwb.SessionHandle;
import com.android.internal.annotations.GuardedBy;
@@ -36,7 +37,7 @@ import java.util.Map;
/**
* Implementation of {@link android.uwb.IUwbAdapter} binder service.
*/
public class UwbServiceImpl extends IUwbAdapter.Stub {
public class UwbServiceImpl extends IUwbAdapter.Stub implements IBinder.DeathRecipient{
private static final String TAG = "UwbServiceImpl";
private final Context mContext;
@@ -55,16 +56,20 @@ public class UwbServiceImpl extends IUwbAdapter.Stub {
/**
* Wrapper for callback registered with vendor service. This wrapper is needed for performing
* permission check before sending the callback to the external app.
*
* Access to these callbacks are synchronized.
*/
private class UwbRangingCallbacksWrapper extends IUwbRangingCallbacks.Stub
implements IBinder.DeathRecipient{
private final SessionHandle mSessionHandle;
private final IUwbRangingCallbacks mExternalCb;
private boolean mIsValid;
UwbRangingCallbacksWrapper(@NonNull SessionHandle sessionHandle,
@NonNull IUwbRangingCallbacks externalCb) {
mSessionHandle = sessionHandle;
mExternalCb = externalCb;
mIsValid = true;
// Link to death for external callback.
linkToDeath();
@@ -75,7 +80,7 @@ public class UwbServiceImpl extends IUwbAdapter.Stub {
try {
binder.linkToDeath(this, 0);
} catch (RemoteException e) {
Log.e(TAG, "Unable to link to client death event.");
Log.e(TAG, "Unable to link to client death event.", e);
}
}
@@ -86,91 +91,136 @@ public class UwbServiceImpl extends IUwbAdapter.Stub {
}
IBinder binder = mExternalCb.asBinder();
binder.unlinkToDeath(this, 0);
mIsValid = false;
}
@Override
public void onRangingOpened(SessionHandle sessionHandle) throws RemoteException {
public synchronized void onRangingOpened(SessionHandle sessionHandle)
throws RemoteException {
if (!mIsValid) return;
mExternalCb.onRangingOpened(sessionHandle);
}
@Override
public void onRangingOpenFailed(SessionHandle sessionHandle,
public synchronized void onRangingOpenFailed(SessionHandle sessionHandle,
int reason, PersistableBundle parameters) throws RemoteException {
if (!mIsValid) return;
mExternalCb.onRangingOpenFailed(sessionHandle, reason, parameters);
}
@Override
public void onRangingStarted(SessionHandle sessionHandle, PersistableBundle parameters)
public synchronized void onRangingStarted(SessionHandle sessionHandle,
PersistableBundle parameters)
throws RemoteException {
if (!mIsValid) return;
mExternalCb.onRangingStarted(sessionHandle, parameters);
}
@Override
public void onRangingStartFailed(SessionHandle sessionHandle,
public synchronized void onRangingStartFailed(SessionHandle sessionHandle,
int reason, PersistableBundle parameters) throws RemoteException {
if (!mIsValid) return;
mExternalCb.onRangingStartFailed(sessionHandle, reason, parameters);
}
@Override
public void onRangingReconfigured(SessionHandle sessionHandle, PersistableBundle parameters)
public synchronized void onRangingReconfigured(SessionHandle sessionHandle,
PersistableBundle parameters)
throws RemoteException {
if (!mIsValid) return;
mExternalCb.onRangingReconfigured(sessionHandle, parameters);
}
@Override
public void onRangingReconfigureFailed(SessionHandle sessionHandle,
public synchronized void onRangingReconfigureFailed(SessionHandle sessionHandle,
int reason, PersistableBundle parameters) throws RemoteException {
if (!mIsValid) return;
mExternalCb.onRangingReconfigureFailed(sessionHandle, reason, parameters);
}
@Override
public void onRangingStopped(SessionHandle sessionHandle, int reason,
public synchronized void onRangingStopped(SessionHandle sessionHandle, int reason,
PersistableBundle parameters)
throws RemoteException {
if (!mIsValid) return;
mExternalCb.onRangingStopped(sessionHandle, reason, parameters);
}
@Override
public void onRangingStopFailed(SessionHandle sessionHandle, int reason,
public synchronized void onRangingStopFailed(SessionHandle sessionHandle, int reason,
PersistableBundle parameters) throws RemoteException {
if (!mIsValid) return;
mExternalCb.onRangingStopFailed(sessionHandle, reason, parameters);
}
@Override
public void onRangingClosed(SessionHandle sessionHandle, int reason,
public synchronized void onRangingClosed(SessionHandle sessionHandle, int reason,
PersistableBundle parameters) throws RemoteException {
if (!mIsValid) return;
mExternalCb.onRangingClosed(sessionHandle, reason, parameters);
removeClientAndUnlinkToDeath();
}
@Override
public void onRangingResult(SessionHandle sessionHandle, RangingReport rangingReport)
public synchronized void onRangingResult(SessionHandle sessionHandle,
RangingReport rangingReport)
throws RemoteException {
if (!mIsValid) return;
// TODO: Perform permission checks and noteOp.
mExternalCb.onRangingResult(sessionHandle, rangingReport);
}
@Override
public void binderDied() {
public synchronized void binderDied() {
if (!mIsValid) return;
Log.i(TAG, "Client died: ending session: " + mSessionHandle);
try {
removeClientAndUnlinkToDeath();
stopRanging(mSessionHandle);
closeRanging(mSessionHandle);
} catch (RemoteException execption) {
Log.w(TAG, "Remote exception while handling client death");
removeClientAndUnlinkToDeath();
} catch (RemoteException e) {
Log.e(TAG, "Remote exception while handling client death", e);
}
}
}
private IUwbAdapter getVendorUwbAdapter() throws IllegalStateException {
private void linkToVendorServiceDeath() {
IBinder binder = mVendorUwbAdapter.asBinder();
try {
binder.linkToDeath(this, 0);
} catch (RemoteException e) {
Log.e(TAG, "Unable to link to vendor service death event.", e);
}
}
@Override
public void binderDied() {
Log.i(TAG, "Vendor service died: sending session close callbacks");
synchronized (mCallbacksMap) {
for (Map.Entry<SessionHandle, UwbRangingCallbacksWrapper> e : mCallbacksMap.entrySet()) {
try {
e.getValue().mExternalCb.onRangingClosed(
e.getKey(), RangingSession.Callback.REASON_UNKNOWN,
new PersistableBundle());
} catch (RemoteException ex) {
Log.e(TAG, "Failed to send session close callback " + e.getKey(), ex);
}
}
// Clear all sessions.
mCallbacksMap.clear();
}
mVendorUwbAdapter = null;
}
private synchronized IUwbAdapter getVendorUwbAdapter() throws IllegalStateException {
if (mVendorUwbAdapter != null) return mVendorUwbAdapter;
mVendorUwbAdapter = mUwbInjector.getVendorService();
if (mVendorUwbAdapter == null) {
throw new IllegalStateException("No vendor service found!");
}
Log.i(TAG, "Retrieved vendor service");
linkToVendorServiceDeath();
return mVendorUwbAdapter;
}
@@ -234,4 +284,5 @@ public class UwbServiceImpl extends IUwbAdapter.Stub {
public void closeRanging(SessionHandle sessionHandle) throws RemoteException {
getVendorUwbAdapter().closeRanging(sessionHandle);
}
}