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

Commit b279293d authored by Brian Stack's avatar Brian Stack
Browse files

Implements UWB RangingManager and RangingSession

Bug: 170323306
Test: atest UwbManagerTests
Test: New RangingManagerTest and RangingSessionTest pass

Change-Id: I544cca2dc80c68c18994584247bb5e831bb734a2
parent 0070e26a
Loading
Loading
Loading
Loading
+178 −0
Original line number Diff line number Diff line
/*
 * Copyright 2020 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 android.uwb;

import android.annotation.NonNull;
import android.os.PersistableBundle;
import android.os.RemoteException;
import android.util.Log;

import java.util.Hashtable;
import java.util.concurrent.Executor;

/**
 * @hide
 */
public class RangingManager extends android.uwb.IUwbRangingCallbacks.Stub {
    private static final String TAG = "Uwb.RangingManager";

    private final IUwbAdapter mAdapter;
    private final Hashtable<SessionHandle, RangingSession> mRangingSessionTable = new Hashtable<>();

    public RangingManager(IUwbAdapter adapter) {
        mAdapter = adapter;
    }

    /**
     * Open a new ranging session
     *
     * @param params the parameters that define the ranging session
     * @param executor {@link Executor} to run callbacks
     * @param callbacks {@link RangingSession.Callback} to associate with the {@link RangingSession}
     *                  that is being opened.
     * @return a new {@link RangingSession}
     */
    public RangingSession openSession(@NonNull PersistableBundle params, @NonNull Executor executor,
            @NonNull RangingSession.Callback callbacks) {
        SessionHandle sessionHandle;
        try {
            sessionHandle = mAdapter.startRanging(this, params);
        } catch (RemoteException e) {
            throw e.rethrowFromSystemServer();
        }

        synchronized (this) {
            if (hasSession(sessionHandle)) {
                Log.w(TAG, "Newly created session unexpectedly reuses an active SessionHandle");
                executor.execute(() -> callbacks.onClosed(
                        RangingSession.Callback.CLOSE_REASON_LOCAL_GENERIC_ERROR,
                        new PersistableBundle()));
            }

            RangingSession session =
                    new RangingSession(executor, callbacks, mAdapter, sessionHandle);
            mRangingSessionTable.put(sessionHandle, session);
            return session;
        }
    }

    private boolean hasSession(SessionHandle sessionHandle) {
        return mRangingSessionTable.containsKey(sessionHandle);
    }

    @Override
    public void onRangingStarted(SessionHandle sessionHandle, PersistableBundle parameters) {
        synchronized (this) {
            if (!hasSession(sessionHandle)) {
                Log.w(TAG,
                        "onRangingStarted - received unexpected SessionHandle: " + sessionHandle);
                return;
            }

            RangingSession session = mRangingSessionTable.get(sessionHandle);
            session.onRangingStarted(parameters);
        }
    }

    @Override
    public void onRangingStartFailed(SessionHandle sessionHandle, int reason,
            PersistableBundle params) {
        synchronized (this) {
            if (!hasSession(sessionHandle)) {
                Log.w(TAG, "onRangingStartFailed - received unexpected SessionHandle: "
                        + sessionHandle);
                return;
            }

            RangingSession session = mRangingSessionTable.get(sessionHandle);
            session.onRangingClosed(convertStartFailureToCloseReason(reason), params);
            mRangingSessionTable.remove(sessionHandle);
        }
    }

    @Override
    public void onRangingClosed(SessionHandle sessionHandle, int reason, PersistableBundle params) {
        synchronized (this) {
            if (!hasSession(sessionHandle)) {
                Log.w(TAG, "onRangingClosed - received unexpected SessionHandle: " + sessionHandle);
                return;
            }

            RangingSession session = mRangingSessionTable.get(sessionHandle);
            session.onRangingClosed(convertToCloseReason(reason), params);
            mRangingSessionTable.remove(sessionHandle);
        }
    }

    @Override
    public void onRangingResult(SessionHandle sessionHandle, RangingReport result) {
        synchronized (this) {
            if (!hasSession(sessionHandle)) {
                Log.w(TAG, "onRangingResult - received unexpected SessionHandle: " + sessionHandle);
                return;
            }

            RangingSession session = mRangingSessionTable.get(sessionHandle);
            session.onRangingResult(result);
        }
    }

    @RangingSession.Callback.CloseReason
    private static int convertToCloseReason(@CloseReason int reason) {
        switch (reason) {
            case CloseReason.LOCAL_API:
                return RangingSession.Callback.CLOSE_REASON_LOCAL_CLOSE_API;

            case CloseReason.MAX_SESSIONS_REACHED:
                return RangingSession.Callback.CLOSE_REASON_LOCAL_MAX_SESSIONS_REACHED;

            case CloseReason.SYSTEM_POLICY:
                return RangingSession.Callback.CLOSE_REASON_LOCAL_SYSTEM_POLICY;

            case CloseReason.REMOTE_REQUEST:
                return RangingSession.Callback.CLOSE_REASON_REMOTE_REQUEST;

            case CloseReason.PROTOCOL_SPECIFIC:
                return RangingSession.Callback.CLOSE_REASON_PROTOCOL_SPECIFIC;

            case CloseReason.UNKNOWN:
            default:
                return RangingSession.Callback.CLOSE_REASON_UNKNOWN;
        }
    }

    @RangingSession.Callback.CloseReason
    private static int convertStartFailureToCloseReason(@StartFailureReason int reason) {
        switch (reason) {
            case StartFailureReason.BAD_PARAMETERS:
                return RangingSession.Callback.CLOSE_REASON_LOCAL_BAD_PARAMETERS;

            case StartFailureReason.MAX_SESSIONS_REACHED:
                return RangingSession.Callback.CLOSE_REASON_LOCAL_MAX_SESSIONS_REACHED;

            case StartFailureReason.SYSTEM_POLICY:
                return RangingSession.Callback.CLOSE_REASON_LOCAL_SYSTEM_POLICY;

            case StartFailureReason.PROTOCOL_SPECIFIC:
                return RangingSession.Callback.CLOSE_REASON_PROTOCOL_SPECIFIC;

            case StartFailureReason.UNKNOWN:
            default:
                return RangingSession.Callback.CLOSE_REASON_UNKNOWN;
        }
    }
}
+108 −7
Original line number Diff line number Diff line
@@ -18,7 +18,10 @@ package android.uwb;

import android.annotation.IntDef;
import android.annotation.NonNull;
import android.os.Binder;
import android.os.PersistableBundle;
import android.os.RemoteException;
import android.util.Log;

import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
@@ -34,12 +37,26 @@ import java.util.concurrent.Executor;
 * {@link UwbManager#openRangingSession(PersistableBundle, Executor, Callback)} to request to open a
 * session. Once the session is opened, a {@link RangingSession} object is provided through
 * {@link RangingSession.Callback#onOpenSuccess(RangingSession, PersistableBundle)}. If opening a
 * session fails, the failure is reported through {@link RangingSession.Callback#onClosed(int)} with
 * the failure reason.
 * session fails, the failure is reported through
 * {@link RangingSession.Callback#onClosed(int, PersistableBundle)} with the failure reason.
 *
 * @hide
 */
public final class RangingSession implements AutoCloseable {
    private static final String TAG = "Uwb.RangingSession";
    private final SessionHandle mSessionHandle;
    private final IUwbAdapter mAdapter;
    private final Executor mExecutor;
    private final Callback mCallback;

    private enum State {
        INIT,
        OPEN,
        CLOSED,
    }

    private State mState;

    /**
     * Interface for receiving {@link RangingSession} events
     */
@@ -115,14 +132,21 @@ public final class RangingSession implements AutoCloseable {
         */
        int CLOSE_REASON_REMOTE_REQUEST = 7;

        /**
         * Indicates that the session was closed for a protocol specific reason. The associated
         * {@link PersistableBundle} should be consulted for additional information.
         */
        int CLOSE_REASON_PROTOCOL_SPECIFIC = 8;

        /**
         * Invoked when session is either closed spontaneously, or per user request via
         * {@link RangingSession#close()} or {@link AutoCloseable#close()}, or when session failed
         * to open.
         *
         * @param reason reason for the session closure
         * @param parameters protocol specific parameters related to the close reason
         */
        void onClosed(@CloseReason int reason);
        void onClosed(@CloseReason int reason, @NonNull PersistableBundle parameters);

        /**
         * Called once per ranging interval even when a ranging measurement fails
@@ -132,21 +156,98 @@ public final class RangingSession implements AutoCloseable {
        void onReportReceived(@NonNull RangingReport rangingReport);
    }

    /**
     * @hide
     */
    public RangingSession(Executor executor, Callback callback, IUwbAdapter adapter,
            SessionHandle sessionHandle) {
        mState = State.INIT;
        mExecutor = executor;
        mCallback = callback;
        mAdapter = adapter;
        mSessionHandle = sessionHandle;
    }

    /**
     * @hide
     */
    public boolean isOpen() {
        return mState == State.OPEN;
    }

    /**
     * Close the ranging session
     * <p>If this session is currently open, it will close and stop the session.
     * <p>If the session is in the process of being opened, it will attempt to stop the session from
     * being opened.
     * <p>If the session is already closed, the registered {@link Callback#onClosed(int)} callback
     * will still be invoked.
     * <p>If the session is already closed, the registered
     * {@link Callback#onClosed(int, PersistableBundle)} callback will still be invoked.
     *
     * <p>{@link Callback#onClosed(int)} will be invoked using the same callback
     * <p>{@link Callback#onClosed(int, PersistableBundle)} will be invoked using the same callback
     * object given to {@link UwbManager#openRangingSession(PersistableBundle, Executor, Callback)}
     * when the {@link RangingSession} was opened. The callback will be invoked after each call to
     * {@link #close()}, even if the {@link RangingSession} is already closed.
     */
    @Override
    public void close() {
        throw new UnsupportedOperationException();
        if (mState == State.CLOSED) {
            mExecutor.execute(() -> mCallback.onClosed(
                    Callback.CLOSE_REASON_LOCAL_CLOSE_API, new PersistableBundle()));
            return;
        }

        try {
            mAdapter.closeRanging(mSessionHandle);
        } catch (RemoteException e) {
            throw e.rethrowFromSystemServer();
        }
    }

    /**
     * @hide
     */
    public void onRangingStarted(@NonNull PersistableBundle parameters) {
        if (mState == State.CLOSED) {
            Log.w(TAG, "onRangingStarted invoked for a closed session");
            return;
        }

        mState = State.OPEN;
        final long identity = Binder.clearCallingIdentity();
        try {
            mExecutor.execute(() -> mCallback.onOpenSuccess(this, parameters));
        } finally {
            Binder.restoreCallingIdentity(identity);
        }
    }

    /**
     * @hide
     */
    public void onRangingClosed(@Callback.CloseReason int reason, PersistableBundle parameters) {
        mState = State.CLOSED;
        final long identity = Binder.clearCallingIdentity();
        try {
            mExecutor.execute(() -> mCallback.onClosed(reason, parameters));
        } finally {
            Binder.restoreCallingIdentity(identity);
        }
    }

    /**
     * @hide
     */
    public void onRangingResult(@NonNull RangingReport report) {
        if (!isOpen()) {
            Log.w(TAG, "onRangingResult invoked for non-open session");
            return;
        }

        final long identity = Binder.clearCallingIdentity();
        try {
            mExecutor.execute(() -> mCallback.onReportReceived(report));
        } finally {
            Binder.restoreCallingIdentity(identity);
        }
    }
}
+4 −2
Original line number Diff line number Diff line
@@ -49,7 +49,8 @@ public final class UwbManager {
    private IUwbAdapter mUwbAdapter;
    private static final String SERVICE_NAME = "uwb";

    private AdapterStateListener mAdapterStateListener;
    private final AdapterStateListener mAdapterStateListener;
    private final RangingManager mRangingManager;

    /**
     * Interface for receiving UWB adapter state changes
@@ -119,6 +120,7 @@ public final class UwbManager {
    private UwbManager(IUwbAdapter adapter) {
        mUwbAdapter = adapter;
        mAdapterStateListener = new AdapterStateListener(adapter);
        mRangingManager = new RangingManager(adapter);
    }

    /**
@@ -395,6 +397,6 @@ public final class UwbManager {
    public AutoCloseable openRangingSession(@NonNull PersistableBundle parameters,
            @NonNull @CallbackExecutor Executor executor,
            @NonNull RangingSession.Callback callbacks) {
        throw new UnsupportedOperationException();
        return mRangingManager.openSession(parameters, executor, callbacks);
    }
}
+260 −0
Original line number Diff line number Diff line
/*
 * Copyright 2020 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 android.uwb;

import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyInt;
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;

import android.os.PersistableBundle;
import android.os.RemoteException;

import androidx.test.ext.junit.runners.AndroidJUnit4;
import androidx.test.filters.SmallTest;

import org.junit.Test;
import org.junit.runner.RunWith;

import java.util.concurrent.Executor;

/**
 * Test of {@link AdapterStateListener}.
 */
@SmallTest
@RunWith(AndroidJUnit4.class)
public class RangingManagerTest {

    private static final IUwbAdapter ADAPTER = mock(IUwbAdapter.class);
    private static final Executor EXECUTOR = UwbTestUtils.getExecutor();
    private static final PersistableBundle PARAMS = new PersistableBundle();
    private static final @CloseReason int CLOSE_REASON = CloseReason.UNKNOWN;

    @Test
    public void testOpenSession_StartRangingInvoked() throws RemoteException {
        RangingManager rangingManager = new RangingManager(ADAPTER);
        RangingSession.Callback callback = mock(RangingSession.Callback.class);
        rangingManager.openSession(PARAMS, EXECUTOR, callback);
        verify(ADAPTER, times(1)).startRanging(eq(rangingManager), eq(PARAMS));
    }

    @Test
    public void testOpenSession_ErrorIfSameSessionHandleReturned() throws RemoteException {
        RangingManager rangingManager = new RangingManager(ADAPTER);
        RangingSession.Callback callback = mock(RangingSession.Callback.class);
        SessionHandle handle = new SessionHandle(1);
        when(ADAPTER.startRanging(any(), any())).thenReturn(handle);

        rangingManager.openSession(PARAMS, EXECUTOR, callback);

        // Calling openSession will cause the same session handle to be returned. The onClosed
        // callback should be invoked
        RangingSession.Callback callback2 = mock(RangingSession.Callback.class);
        rangingManager.openSession(PARAMS, EXECUTOR, callback2);
        verify(callback, times(0)).onClosed(anyInt(), any());
        verify(callback2, times(1)).onClosed(anyInt(), any());
    }

    @Test
    public void testOnRangingStarted_ValidSessionHandle() throws RemoteException {
        RangingManager rangingManager = new RangingManager(ADAPTER);
        RangingSession.Callback callback = mock(RangingSession.Callback.class);
        SessionHandle handle = new SessionHandle(1);
        when(ADAPTER.startRanging(any(), any())).thenReturn(handle);

        rangingManager.openSession(PARAMS, EXECUTOR, callback);
        rangingManager.onRangingStarted(handle, PARAMS);
        verify(callback, times(1)).onOpenSuccess(any(), any());
    }

    @Test
    public void testOnRangingStarted_InvalidSessionHandle() throws RemoteException {
        RangingManager rangingManager = new RangingManager(ADAPTER);
        RangingSession.Callback callback = mock(RangingSession.Callback.class);

        rangingManager.onRangingStarted(new SessionHandle(2), PARAMS);
        verify(callback, times(0)).onOpenSuccess(any(), any());
    }

    @Test
    public void testOnRangingStarted_MultipleSessionsRegistered() throws RemoteException {
        SessionHandle sessionHandle1 = new SessionHandle(1);
        SessionHandle sessionHandle2 = new SessionHandle(2);
        RangingSession.Callback callback1 = mock(RangingSession.Callback.class);
        RangingSession.Callback callback2 = mock(RangingSession.Callback.class);

        when(ADAPTER.startRanging(any(), any()))
                .thenReturn(sessionHandle1)
                .thenReturn(sessionHandle2);

        RangingManager rangingManager = new RangingManager(ADAPTER);
        rangingManager.openSession(PARAMS, EXECUTOR, callback1);
        rangingManager.openSession(PARAMS, EXECUTOR, callback2);

        rangingManager.onRangingStarted(sessionHandle1, PARAMS);
        verify(callback1, times(1)).onOpenSuccess(any(), any());
        verify(callback2, times(0)).onOpenSuccess(any(), any());

        rangingManager.onRangingStarted(sessionHandle2, PARAMS);
        verify(callback1, times(1)).onOpenSuccess(any(), any());
        verify(callback2, times(1)).onOpenSuccess(any(), any());
    }

    @Test
    public void testOnRangingClosed_OnRangingClosedCalled() throws RemoteException {
        RangingManager rangingManager = new RangingManager(ADAPTER);
        RangingSession.Callback callback = mock(RangingSession.Callback.class);
        SessionHandle handle = new SessionHandle(1);
        when(ADAPTER.startRanging(any(), any())).thenReturn(handle);
        rangingManager.openSession(PARAMS, EXECUTOR, callback);

        rangingManager.onRangingClosed(handle, CLOSE_REASON, PARAMS);
        verify(callback, times(1)).onClosed(anyInt(), any());
    }

    @Test
    public void testOnRangingClosed_MultipleSessionsRegistered() throws RemoteException {
        // Verify that if multiple sessions are registered, only the session that is
        // requested to close receives the associated callbacks
        SessionHandle sessionHandle1 = new SessionHandle(1);
        SessionHandle sessionHandle2 = new SessionHandle(2);
        RangingSession.Callback callback1 = mock(RangingSession.Callback.class);
        RangingSession.Callback callback2 = mock(RangingSession.Callback.class);

        when(ADAPTER.startRanging(any(), any()))
                .thenReturn(sessionHandle1)
                .thenReturn(sessionHandle2);

        RangingManager rangingManager = new RangingManager(ADAPTER);
        rangingManager.openSession(PARAMS, EXECUTOR, callback1);
        rangingManager.openSession(PARAMS, EXECUTOR, callback2);

        rangingManager.onRangingClosed(sessionHandle1, CLOSE_REASON, PARAMS);
        verify(callback1, times(1)).onClosed(anyInt(), any());
        verify(callback2, times(0)).onClosed(anyInt(), any());

        rangingManager.onRangingClosed(sessionHandle2, CLOSE_REASON, PARAMS);
        verify(callback1, times(1)).onClosed(anyInt(), any());
        verify(callback2, times(1)).onClosed(anyInt(), any());
    }

    @Test
    public void testOnRangingReport_OnReportReceived() throws RemoteException {
        RangingManager rangingManager = new RangingManager(ADAPTER);
        RangingSession.Callback callback = mock(RangingSession.Callback.class);
        SessionHandle handle = new SessionHandle(1);
        when(ADAPTER.startRanging(any(), any())).thenReturn(handle);
        rangingManager.openSession(PARAMS, EXECUTOR, callback);
        rangingManager.onRangingStarted(handle, PARAMS);

        RangingReport report = UwbTestUtils.getRangingReports(1);
        rangingManager.onRangingResult(handle, report);
        verify(callback, times(1)).onReportReceived(eq(report));
    }

    @Test
    public void testOnRangingReport_MultipleSessionsRegistered() throws RemoteException {
        SessionHandle sessionHandle1 = new SessionHandle(1);
        SessionHandle sessionHandle2 = new SessionHandle(2);
        RangingSession.Callback callback1 = mock(RangingSession.Callback.class);
        RangingSession.Callback callback2 = mock(RangingSession.Callback.class);

        when(ADAPTER.startRanging(any(), any()))
                .thenReturn(sessionHandle1)
                .thenReturn(sessionHandle2);

        RangingManager rangingManager = new RangingManager(ADAPTER);
        rangingManager.openSession(PARAMS, EXECUTOR, callback1);
        rangingManager.onRangingStarted(sessionHandle1, PARAMS);
        rangingManager.openSession(PARAMS, EXECUTOR, callback2);
        rangingManager.onRangingStarted(sessionHandle2, PARAMS);

        rangingManager.onRangingResult(sessionHandle1, UwbTestUtils.getRangingReports(1));
        verify(callback1, times(1)).onReportReceived(any());
        verify(callback2, times(0)).onReportReceived(any());

        rangingManager.onRangingResult(sessionHandle2, UwbTestUtils.getRangingReports(1));
        verify(callback1, times(1)).onReportReceived(any());
        verify(callback2, times(1)).onReportReceived(any());
    }

    @Test
    public void testOnClose_Reasons() throws RemoteException {
        runOnClose_Reason(CloseReason.LOCAL_API,
                RangingSession.Callback.CLOSE_REASON_LOCAL_CLOSE_API);

        runOnClose_Reason(CloseReason.MAX_SESSIONS_REACHED,
                RangingSession.Callback.CLOSE_REASON_LOCAL_MAX_SESSIONS_REACHED);

        runOnClose_Reason(CloseReason.PROTOCOL_SPECIFIC,
                RangingSession.Callback.CLOSE_REASON_PROTOCOL_SPECIFIC);

        runOnClose_Reason(CloseReason.REMOTE_REQUEST,
                RangingSession.Callback.CLOSE_REASON_REMOTE_REQUEST);

        runOnClose_Reason(CloseReason.SYSTEM_POLICY,
                RangingSession.Callback.CLOSE_REASON_LOCAL_SYSTEM_POLICY);

        runOnClose_Reason(CloseReason.UNKNOWN,
                RangingSession.Callback.CLOSE_REASON_UNKNOWN);
    }

    private void runOnClose_Reason(@CloseReason int reasonIn,
            @RangingSession.Callback.CloseReason int reasonOut) throws RemoteException {
        RangingManager rangingManager = new RangingManager(ADAPTER);
        RangingSession.Callback callback = mock(RangingSession.Callback.class);
        SessionHandle handle = new SessionHandle(1);
        when(ADAPTER.startRanging(any(), any())).thenReturn(handle);
        rangingManager.openSession(PARAMS, EXECUTOR, callback);

        rangingManager.onRangingClosed(handle, reasonIn, PARAMS);
        verify(callback, times(1)).onClosed(eq(reasonOut), eq(PARAMS));
    }

    @Test
    public void testStartFailureReasons() throws RemoteException {
        runOnRangingStartFailed_Reason(StartFailureReason.BAD_PARAMETERS,
                RangingSession.Callback.CLOSE_REASON_LOCAL_BAD_PARAMETERS);

        runOnRangingStartFailed_Reason(StartFailureReason.MAX_SESSIONS_REACHED,
                RangingSession.Callback.CLOSE_REASON_LOCAL_MAX_SESSIONS_REACHED);

        runOnRangingStartFailed_Reason(StartFailureReason.PROTOCOL_SPECIFIC,
                RangingSession.Callback.CLOSE_REASON_PROTOCOL_SPECIFIC);

        runOnRangingStartFailed_Reason(StartFailureReason.SYSTEM_POLICY,
                RangingSession.Callback.CLOSE_REASON_LOCAL_SYSTEM_POLICY);

        runOnRangingStartFailed_Reason(StartFailureReason.UNKNOWN,
                RangingSession.Callback.CLOSE_REASON_UNKNOWN);
    }

    private void runOnRangingStartFailed_Reason(@StartFailureReason int reasonIn,
            @RangingSession.Callback.CloseReason int reasonOut) throws RemoteException {
        RangingManager rangingManager = new RangingManager(ADAPTER);
        RangingSession.Callback callback = mock(RangingSession.Callback.class);
        SessionHandle handle = new SessionHandle(1);
        when(ADAPTER.startRanging(any(), any())).thenReturn(handle);
        rangingManager.openSession(PARAMS, EXECUTOR, callback);

        rangingManager.onRangingStartFailed(handle, reasonIn, PARAMS);
        verify(callback, times(1)).onClosed(eq(reasonOut), eq(PARAMS));
    }
}
+194 −0

File added.

Preview size limit exceeded, changes collapsed.

Loading