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

Commit e4fd3ed6 authored by Roshan Pius's avatar Roshan Pius
Browse files

UwbService: Create a UwbRanging callback wrapper

This wrapper will be used for performing permission checks before
sending the results to the corresponding app.

Bug: 183904955
Test: atest android.uwb.cts.UwbManagerTest
Change-Id: I562951d9f28f1a55391f723445c30fa5ff8aea47
parent 27e1a87a
Loading
Loading
Loading
Loading
+106 −0
Original line number Diff line number Diff line
@@ -19,17 +19,25 @@ package com.android.server.uwb;
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.eq;
import static org.mockito.Mockito.clearInvocations;
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.content.Context;
import android.os.IBinder;
import android.os.PersistableBundle;
import android.platform.test.annotations.Presubmit;
import android.test.suitebuilder.annotation.SmallTest;
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 androidx.test.runner.AndroidJUnit4;
@@ -37,6 +45,8 @@ import androidx.test.runner.AndroidJUnit4;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.ArgumentCaptor;
import org.mockito.Captor;
import org.mockito.Mock;
import org.mockito.MockitoAnnotations;

@@ -50,6 +60,9 @@ public class UwbServiceImplTest {
    @Mock private IUwbAdapter mVendorService;
    @Mock private Context mContext;
    @Mock private UwbInjector mUwbInjector;
    @Captor private ArgumentCaptor<IUwbRangingCallbacks> mRangingCbCaptor;
    @Captor private ArgumentCaptor<IBinder.DeathRecipient> mClientDeathCaptor;
    @Captor private ArgumentCaptor<IBinder.DeathRecipient> mVendorServiceDeathCaptor;

    private UwbServiceImpl mUwbServiceImpl;

@@ -153,4 +166,97 @@ public class UwbServiceImplTest {

        verify(mVendorService).closeRanging(sessionHandle);
    }

    @Test
    public void testRangingCallbacks() 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(mVendorService).openRanging(
                eq(sessionHandle), mRangingCbCaptor.capture(), eq(parameters));
        assertThat(mRangingCbCaptor.getValue()).isNotNull();

        // Invoke vendor service callbacks and ensure that the corresponding app callback is
        // invoked.
        mRangingCbCaptor.getValue().onRangingOpened(sessionHandle);
        verify(cb).onRangingOpened(sessionHandle);

        mRangingCbCaptor.getValue().onRangingOpenFailed(
                sessionHandle, RangingSession.Callback.REASON_GENERIC_ERROR, parameters);
        verify(cb).onRangingOpenFailed(
                sessionHandle, RangingSession.Callback.REASON_GENERIC_ERROR, parameters);

        mRangingCbCaptor.getValue().onRangingStarted(sessionHandle, parameters);
        verify(cb).onRangingStarted(sessionHandle, parameters);

        mRangingCbCaptor.getValue().onRangingStartFailed(
                sessionHandle, RangingSession.Callback.REASON_GENERIC_ERROR, parameters);
        verify(cb).onRangingStartFailed(
                sessionHandle, RangingSession.Callback.REASON_GENERIC_ERROR, parameters);

        mRangingCbCaptor.getValue().onRangingReconfigured(sessionHandle, parameters);
        verify(cb).onRangingReconfigured(sessionHandle, parameters);

        mRangingCbCaptor.getValue().onRangingReconfigureFailed(
                sessionHandle, RangingSession.Callback.REASON_GENERIC_ERROR, parameters);
        verify(cb).onRangingReconfigureFailed(
                sessionHandle, RangingSession.Callback.REASON_GENERIC_ERROR, parameters);

        mRangingCbCaptor.getValue().onRangingStopped(
                sessionHandle, RangingSession.Callback.REASON_GENERIC_ERROR, parameters);
        verify(cb).onRangingStopped(
                sessionHandle, RangingSession.Callback.REASON_GENERIC_ERROR, parameters);

        mRangingCbCaptor.getValue().onRangingStopFailed(
                sessionHandle, RangingSession.Callback.REASON_GENERIC_ERROR, parameters);
        verify(cb).onRangingStopFailed(
                sessionHandle, RangingSession.Callback.REASON_GENERIC_ERROR, parameters);

        final RangingReport rangingReport = new RangingReport.Builder().build();
        mRangingCbCaptor.getValue().onRangingResult(sessionHandle, rangingReport);
        verify(cb).onRangingResult(sessionHandle, rangingReport);

        mRangingCbCaptor.getValue().onRangingClosed(
                sessionHandle, RangingSession.Callback.REASON_GENERIC_ERROR, parameters);
        verify(cb).onRangingClosed(
                sessionHandle, RangingSession.Callback.REASON_GENERIC_ERROR, parameters);
    }

    @Test
    public void testHandleClientDeath() 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(mVendorService).openRanging(
                eq(sessionHandle), mRangingCbCaptor.capture(), eq(parameters));
        assertThat(mRangingCbCaptor.getValue()).isNotNull();

        verify(cbBinder).linkToDeath(mClientDeathCaptor.capture(), anyInt());
        assertThat(mClientDeathCaptor.getValue()).isNotNull();

        clearInvocations(cb);

        // Invoke cb, ensure it reaches the client.
        mRangingCbCaptor.getValue().onRangingOpened(sessionHandle);
        verify(cb).onRangingOpened(sessionHandle);

        // Trigger client death and ensure the session is stopped.
        mClientDeathCaptor.getValue().binderDied();
        verify(mVendorService).stopRanging(sessionHandle);
        verify(mVendorService).closeRanging(sessionHandle);

        // Invoke cb, it should be ignored.
        mRangingCbCaptor.getValue().onRangingStarted(sessionHandle, parameters);
        verify(cb, never()).onRangingStarted(any(), any());
    }
}
+132 −2
Original line number Diff line number Diff line
@@ -18,14 +18,21 @@ package com.android.server.uwb;

import android.annotation.NonNull;
import android.content.Context;
import android.os.IBinder;
import android.os.PersistableBundle;
import android.os.RemoteException;
import android.util.ArrayMap;
import android.util.Log;
import android.uwb.IUwbAdapter;
import android.uwb.IUwbAdapterStateCallbacks;
import android.uwb.IUwbRangingCallbacks;
import android.uwb.RangingReport;
import android.uwb.SessionHandle;

import com.android.internal.annotations.GuardedBy;

import java.util.Map;

/**
 * Implementation of {@link android.uwb.IUwbAdapter} binder service.
 */
@@ -34,12 +41,129 @@ public class UwbServiceImpl extends IUwbAdapter.Stub {

    private final Context mContext;
    private final UwbInjector mUwbInjector;
    /**
     * Map for storing the callbacks wrapper for each session.
     */
    @GuardedBy("mCallbacksMap")
    private final Map<SessionHandle, UwbRangingCallbacksWrapper> mCallbacksMap = new ArrayMap<>();

    /**
     * Used for caching the vendor implementation of {@link IUwbAdapter} interface.
     */
    private IUwbAdapter mVendorUwbAdapter;

    /**
     * Wrapper for callback registered with vendor service. This wrapper is needed for performing
     * permission check before sending the callback to the external app.
     */
    private class UwbRangingCallbacksWrapper extends IUwbRangingCallbacks.Stub
            implements IBinder.DeathRecipient{
        private final SessionHandle mSessionHandle;
        private final IUwbRangingCallbacks mExternalCb;

        UwbRangingCallbacksWrapper(@NonNull SessionHandle sessionHandle,
                @NonNull IUwbRangingCallbacks externalCb) {
            mSessionHandle = sessionHandle;
            mExternalCb = externalCb;

            // Link to death for external callback.
            linkToDeath();
        }

        private void linkToDeath() {
            IBinder binder = mExternalCb.asBinder();
            try {
                binder.linkToDeath(this, 0);
            } catch (RemoteException e) {
                Log.e(TAG, "Unable to link to client death event.");
            }
        }

        private void removeClientAndUnlinkToDeath() {
            // Remove from the map.
            synchronized (mCallbacksMap) {
                mCallbacksMap.remove(mSessionHandle);
            }
            IBinder binder = mExternalCb.asBinder();
            binder.unlinkToDeath(this, 0);
        }


        @Override
        public void onRangingOpened(SessionHandle sessionHandle) throws RemoteException {
            mExternalCb.onRangingOpened(sessionHandle);
        }

        @Override
        public void onRangingOpenFailed(SessionHandle sessionHandle,
                int reason, PersistableBundle parameters) throws RemoteException {
            mExternalCb.onRangingOpenFailed(sessionHandle, reason, parameters);
        }

        @Override
        public void onRangingStarted(SessionHandle sessionHandle, PersistableBundle parameters)
                throws RemoteException {
            mExternalCb.onRangingStarted(sessionHandle, parameters);
        }

        @Override
        public void onRangingStartFailed(SessionHandle sessionHandle,
                int reason, PersistableBundle parameters) throws RemoteException {
            mExternalCb.onRangingStartFailed(sessionHandle, reason, parameters);
        }

        @Override
        public void onRangingReconfigured(SessionHandle sessionHandle, PersistableBundle parameters)
                throws RemoteException {
            mExternalCb.onRangingReconfigured(sessionHandle, parameters);
        }

        @Override
        public void onRangingReconfigureFailed(SessionHandle sessionHandle,
                int reason, PersistableBundle parameters) throws RemoteException {
            mExternalCb.onRangingReconfigureFailed(sessionHandle, reason, parameters);
        }

        @Override
        public void onRangingStopped(SessionHandle sessionHandle, int reason,
                PersistableBundle parameters)
                throws RemoteException {
            mExternalCb.onRangingStopped(sessionHandle, reason, parameters);
        }

        @Override
        public void onRangingStopFailed(SessionHandle sessionHandle, int reason,
                PersistableBundle parameters) throws RemoteException {
            mExternalCb.onRangingStopFailed(sessionHandle, reason, parameters);
        }

        @Override
        public void onRangingClosed(SessionHandle sessionHandle, int reason,
                PersistableBundle parameters) throws RemoteException {
            mExternalCb.onRangingClosed(sessionHandle, reason, parameters);
            removeClientAndUnlinkToDeath();
        }

        @Override
        public void onRangingResult(SessionHandle sessionHandle, RangingReport rangingReport)
                throws RemoteException {
            // TODO: Perform permission checks and noteOp.
            mExternalCb.onRangingResult(sessionHandle, rangingReport);
        }

        @Override
        public void binderDied() {
            Log.i(TAG, "Client died: ending session: " + mSessionHandle);
            try {
                stopRanging(mSessionHandle);
                closeRanging(mSessionHandle);
            } catch (RemoteException execption) {
                Log.w(TAG, "Remote exception while handling client death");
                removeClientAndUnlinkToDeath();
            }
        }
    }

    private IUwbAdapter getVendorUwbAdapter() throws IllegalStateException {
        if (mVendorUwbAdapter != null) return mVendorUwbAdapter;
        mVendorUwbAdapter = mUwbInjector.getVendorService();
@@ -50,7 +174,7 @@ public class UwbServiceImpl extends IUwbAdapter.Stub {
        return mVendorUwbAdapter;
    }

    public UwbServiceImpl(@NonNull Context context, @NonNull UwbInjector uwbInjector) {
    UwbServiceImpl(@NonNull Context context, @NonNull UwbInjector uwbInjector) {
        mContext = context;
        mUwbInjector = uwbInjector;
    }
@@ -80,12 +204,18 @@ public class UwbServiceImpl extends IUwbAdapter.Stub {
    @Override
    public void openRanging(SessionHandle sessionHandle, IUwbRangingCallbacks rangingCallbacks,
            PersistableBundle parameters) throws RemoteException {
        getVendorUwbAdapter().openRanging(sessionHandle, rangingCallbacks, parameters);
        UwbRangingCallbacksWrapper wrapperCb =
                new UwbRangingCallbacksWrapper(sessionHandle, rangingCallbacks);
        synchronized (mCallbacksMap) {
            mCallbacksMap.put(sessionHandle, wrapperCb);
        }
        getVendorUwbAdapter().openRanging(sessionHandle, wrapperCb, parameters);
    }

    @Override
    public void startRanging(SessionHandle sessionHandle, PersistableBundle parameters)
            throws RemoteException {
        // TODO: Perform permission checks.
        getVendorUwbAdapter().startRanging(sessionHandle, parameters);
    }