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

Commit 9b924caa authored by Treehugger Robot's avatar Treehugger Robot Committed by Gerrit Code Review
Browse files

Merge "Implements UWB RangingManager and RangingSession"

parents 94e3f4ba b279293d
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