summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--core/java/android/uwb/AdapterStateListener.java149
-rw-r--r--core/java/android/uwb/IUwbAdapter.aidl11
-rw-r--r--core/java/android/uwb/StateChangeReason.aidl5
-rw-r--r--core/java/android/uwb/UwbManager.java91
-rw-r--r--core/tests/uwbtests/Android.bp1
-rw-r--r--core/tests/uwbtests/src/android/uwb/AdapterStateListenerTest.java311
6 files changed, 553 insertions, 15 deletions
diff --git a/core/java/android/uwb/AdapterStateListener.java b/core/java/android/uwb/AdapterStateListener.java
new file mode 100644
index 000000000000..8875af385238
--- /dev/null
+++ b/core/java/android/uwb/AdapterStateListener.java
@@ -0,0 +1,149 @@
+/*
+ * 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.Binder;
+import android.os.RemoteException;
+import android.util.Log;
+import android.uwb.UwbManager.AdapterStateCallback;
+import android.uwb.UwbManager.AdapterStateCallback.StateChangedReason;
+
+import java.util.HashMap;
+import java.util.Map;
+import java.util.concurrent.Executor;
+
+/**
+ * @hide
+ */
+public class AdapterStateListener extends IUwbAdapterStateCallbacks.Stub {
+ private static final String TAG = "Uwb.StateListener";
+
+ private final IUwbAdapter mAdapter;
+ private boolean mIsRegistered = false;
+
+ private final Map<AdapterStateCallback, Executor> mCallbackMap = new HashMap<>();
+
+ @StateChangedReason
+ private int mAdapterStateChangeReason = AdapterStateCallback.STATE_CHANGED_REASON_ERROR_UNKNOWN;
+ private boolean mAdapterEnabledState = false;
+
+ public AdapterStateListener(@NonNull IUwbAdapter adapter) {
+ mAdapter = adapter;
+ }
+
+ /**
+ * Register an {@link AdapterStateCallback} with this {@link AdapterStateListener}
+ *
+ * @param executor an {@link Executor} to execute given callback
+ * @param callback user implementation of the {@link AdapterStateCallback}
+ */
+ public void register(@NonNull Executor executor, @NonNull AdapterStateCallback callback) {
+ synchronized (this) {
+ if (mCallbackMap.containsKey(callback)) {
+ return;
+ }
+
+ mCallbackMap.put(callback, executor);
+
+ if (!mIsRegistered) {
+ try {
+ mAdapter.registerAdapterStateCallbacks(this);
+ mIsRegistered = true;
+ } catch (RemoteException e) {
+ Log.w(TAG, "Failed to register adapter state callback");
+ executor.execute(() -> callback.onStateChanged(false,
+ AdapterStateCallback.STATE_CHANGED_REASON_ERROR_UNKNOWN));
+ }
+ } else {
+ sendCurrentState(callback);
+ }
+ }
+ }
+
+ /**
+ * Unregister the specified {@link AdapterStateCallback}
+ *
+ * @param callback user implementation of the {@link AdapterStateCallback}
+ */
+ public void unregister(@NonNull AdapterStateCallback callback) {
+ synchronized (this) {
+ if (!mCallbackMap.containsKey(callback)) {
+ return;
+ }
+
+ mCallbackMap.remove(callback);
+
+ if (mCallbackMap.isEmpty() && mIsRegistered) {
+ try {
+ mAdapter.unregisterAdapterStateCallbacks(this);
+ } catch (RemoteException e) {
+ Log.w(TAG, "Failed to unregister AdapterStateCallback with service");
+ }
+ mIsRegistered = false;
+ }
+ }
+ }
+
+ private void sendCurrentState(@NonNull AdapterStateCallback callback) {
+ synchronized (this) {
+ Executor executor = mCallbackMap.get(callback);
+
+ final long identity = Binder.clearCallingIdentity();
+ try {
+ executor.execute(() -> callback.onStateChanged(
+ mAdapterEnabledState, mAdapterStateChangeReason));
+ } finally {
+ Binder.restoreCallingIdentity(identity);
+ }
+ }
+ }
+
+ @Override
+ public void onAdapterStateChanged(boolean isEnabled, int reason) {
+ synchronized (this) {
+ @StateChangedReason int localReason =
+ convertToStateChangedReason(reason);
+ mAdapterEnabledState = isEnabled;
+ mAdapterStateChangeReason = localReason;
+ for (AdapterStateCallback cb : mCallbackMap.keySet()) {
+ sendCurrentState(cb);
+ }
+ }
+ }
+
+ private static @StateChangedReason int convertToStateChangedReason(
+ @StateChangeReason int reason) {
+ switch (reason) {
+ case StateChangeReason.ALL_SESSIONS_CLOSED:
+ return AdapterStateCallback.STATE_CHANGED_REASON_ALL_SESSIONS_CLOSED;
+
+ case StateChangeReason.SESSION_STARTED:
+ return AdapterStateCallback.STATE_CHANGED_REASON_SESSION_STARTED;
+
+ case StateChangeReason.SYSTEM_POLICY:
+ return AdapterStateCallback.STATE_CHANGED_REASON_SYSTEM_POLICY;
+
+ case StateChangeReason.SYSTEM_BOOT:
+ return AdapterStateCallback.STATE_CHANGED_REASON_SYSTEM_BOOT;
+
+ case StateChangeReason.UNKNOWN:
+ default:
+ return AdapterStateCallback.STATE_CHANGED_REASON_ERROR_UNKNOWN;
+ }
+ }
+}
diff --git a/core/java/android/uwb/IUwbAdapter.aidl b/core/java/android/uwb/IUwbAdapter.aidl
index d29ed34804f1..2c8b2e462510 100644
--- a/core/java/android/uwb/IUwbAdapter.aidl
+++ b/core/java/android/uwb/IUwbAdapter.aidl
@@ -98,11 +98,18 @@ interface IUwbAdapter {
int getMaxSimultaneousSessions();
/**
- * Get the maximum number of remote devices per session
+ * Get the maximum number of remote devices per session when local device is initiator
*
* @return the maximum number of remote devices supported in a single session
*/
- int getMaxRemoteDevicesPerSession();
+ int getMaxRemoteDevicesPerInitiatorSession();
+
+ /**
+ * Get the maximum number of remote devices per session when local device is responder
+ *
+ * @return the maximum number of remote devices supported in a single session
+ */
+ int getMaxRemoteDevicesPerResponderSession();
/**
* Provides the capabilities and features of the device
diff --git a/core/java/android/uwb/StateChangeReason.aidl b/core/java/android/uwb/StateChangeReason.aidl
index 46a6e2edfa22..28eaf9f2b24e 100644
--- a/core/java/android/uwb/StateChangeReason.aidl
+++ b/core/java/android/uwb/StateChangeReason.aidl
@@ -41,5 +41,10 @@ enum StateChangeReason {
* The adapter state changed because of a device system change.
*/
SYSTEM_POLICY,
+
+ /**
+ * Used to signal the first adapter state message after boot
+ */
+ SYSTEM_BOOT,
}
diff --git a/core/java/android/uwb/UwbManager.java b/core/java/android/uwb/UwbManager.java
index 6488490b8863..ed5cf3625525 100644
--- a/core/java/android/uwb/UwbManager.java
+++ b/core/java/android/uwb/UwbManager.java
@@ -16,6 +16,7 @@
package android.uwb;
+import android.annotation.CallbackExecutor;
import android.annotation.IntDef;
import android.annotation.NonNull;
import android.annotation.SuppressLint;
@@ -23,10 +24,13 @@ import android.annotation.SystemService;
import android.content.Context;
import android.os.IBinder;
import android.os.PersistableBundle;
+import android.os.RemoteException;
import android.os.ServiceManager;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
+import java.util.ArrayList;
+import java.util.HashSet;
import java.util.List;
import java.util.Set;
import java.util.concurrent.Executor;
@@ -45,6 +49,8 @@ public final class UwbManager {
private IUwbAdapter mUwbAdapter;
private static final String SERVICE_NAME = "uwb";
+ private AdapterStateListener mAdapterStateListener;
+
/**
* Interface for receiving UWB adapter state changes
*/
@@ -109,6 +115,7 @@ public final class UwbManager {
*/
private UwbManager(IUwbAdapter adapter) {
mUwbAdapter = adapter;
+ mAdapterStateListener = new AdapterStateListener(adapter);
}
/**
@@ -140,8 +147,9 @@ public final class UwbManager {
* @param executor an {@link Executor} to execute given callback
* @param callback user implementation of the {@link AdapterStateCallback}
*/
- public void registerAdapterStateCallback(Executor executor, AdapterStateCallback callback) {
- throw new UnsupportedOperationException();
+ public void registerAdapterStateCallback(@NonNull @CallbackExecutor Executor executor,
+ @NonNull AdapterStateCallback callback) {
+ mAdapterStateListener.register(executor, callback);
}
/**
@@ -153,8 +161,8 @@ public final class UwbManager {
*
* @param callback user implementation of the {@link AdapterStateCallback}
*/
- public void unregisterAdapterStateCallback(AdapterStateCallback callback) {
- throw new UnsupportedOperationException();
+ public void unregisterAdapterStateCallback(@NonNull AdapterStateCallback callback) {
+ mAdapterStateListener.unregister(callback);
}
/**
@@ -167,7 +175,11 @@ public final class UwbManager {
*/
@NonNull
public PersistableBundle getSpecificationInfo() {
- throw new UnsupportedOperationException();
+ try {
+ return mUwbAdapter.getSpecificationInfo();
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
}
/**
@@ -176,7 +188,11 @@ public final class UwbManager {
* @return true if ranging is supported
*/
public boolean isRangingSupported() {
- throw new UnsupportedOperationException();
+ try {
+ return mUwbAdapter.isRangingSupported();
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
}
@Retention(RetentionPolicy.SOURCE)
@@ -225,7 +241,24 @@ public final class UwbManager {
*/
@AngleOfArrivalSupportType
public int getAngleOfArrivalSupport() {
- throw new UnsupportedOperationException();
+ try {
+ switch (mUwbAdapter.getAngleOfArrivalSupport()) {
+ case AngleOfArrivalSupport.TWO_DIMENSIONAL:
+ return ANGLE_OF_ARRIVAL_SUPPORT_TYPE_2D;
+
+ case AngleOfArrivalSupport.THREE_DIMENSIONAL_HEMISPHERICAL:
+ return ANGLE_OF_ARRIVAL_SUPPORT_TYPE_3D_HEMISPHERICAL;
+
+ case AngleOfArrivalSupport.THREE_DIMENSIONAL_SPHERICAL:
+ return ANGLE_OF_ARRIVAL_SUPPORT_TYPE_3D_SPHERICAL;
+
+ case AngleOfArrivalSupport.NONE:
+ default:
+ return ANGLE_OF_ARRIVAL_SUPPORT_TYPE_NONE;
+ }
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
}
/**
@@ -239,7 +272,15 @@ public final class UwbManager {
*/
@NonNull
public List<Integer> getSupportedChannelNumbers() {
- throw new UnsupportedOperationException();
+ List<Integer> channels = new ArrayList<>();
+ try {
+ for (int channel : mUwbAdapter.getSupportedChannels()) {
+ channels.add(channel);
+ }
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ return channels;
}
/**
@@ -250,7 +291,15 @@ public final class UwbManager {
*/
@NonNull
public Set<Integer> getSupportedPreambleCodeIndices() {
- throw new UnsupportedOperationException();
+ Set<Integer> preambles = new HashSet<>();
+ try {
+ for (int preamble : mUwbAdapter.getSupportedPreambleCodes()) {
+ preambles.add(preamble);
+ }
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ return preambles;
}
/**
@@ -262,7 +311,11 @@ public final class UwbManager {
*/
@SuppressLint("MethodNameUnits")
public long elapsedRealtimeResolutionNanos() {
- throw new UnsupportedOperationException();
+ try {
+ return mUwbAdapter.getTimestampResolutionNanos();
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
}
/**
@@ -271,7 +324,11 @@ public final class UwbManager {
* @return the maximum allowed number of simultaneously open {@link RangingSession} instances.
*/
public int getMaxSimultaneousSessions() {
- throw new UnsupportedOperationException();
+ try {
+ return mUwbAdapter.getMaxSimultaneousSessions();
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
}
/**
@@ -281,7 +338,11 @@ public final class UwbManager {
* @return the maximum number of remote devices per {@link RangingSession}
*/
public int getMaxRemoteDevicesPerInitiatorSession() {
- throw new UnsupportedOperationException();
+ try {
+ return mUwbAdapter.getMaxRemoteDevicesPerInitiatorSession();
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
}
/**
@@ -291,7 +352,11 @@ public final class UwbManager {
* @return the maximum number of remote devices per {@link RangingSession}
*/
public int getMaxRemoteDevicesPerResponderSession() {
- throw new UnsupportedOperationException();
+ try {
+ return mUwbAdapter.getMaxRemoteDevicesPerResponderSession();
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
}
/**
diff --git a/core/tests/uwbtests/Android.bp b/core/tests/uwbtests/Android.bp
index c41c346b131a..8ee86f470c9e 100644
--- a/core/tests/uwbtests/Android.bp
+++ b/core/tests/uwbtests/Android.bp
@@ -17,6 +17,7 @@ android_test {
static_libs: [
"androidx.test.ext.junit",
"androidx.test.rules",
+ "mockito-target-minus-junit4",
],
libs: [
"android.test.runner",
diff --git a/core/tests/uwbtests/src/android/uwb/AdapterStateListenerTest.java b/core/tests/uwbtests/src/android/uwb/AdapterStateListenerTest.java
new file mode 100644
index 000000000000..ce67ef7868e8
--- /dev/null
+++ b/core/tests/uwbtests/src/android/uwb/AdapterStateListenerTest.java
@@ -0,0 +1,311 @@
+/*
+ * 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.anyBoolean;
+import static org.mockito.ArgumentMatchers.anyInt;
+import static org.mockito.Mockito.doAnswer;
+import static org.mockito.Mockito.doThrow;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.times;
+import static org.mockito.Mockito.verify;
+
+
+import android.os.RemoteException;
+import android.uwb.UwbManager.AdapterStateCallback;
+
+import androidx.test.ext.junit.runners.AndroidJUnit4;
+import androidx.test.filters.SmallTest;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.invocation.InvocationOnMock;
+import org.mockito.stubbing.Answer;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.concurrent.Executor;
+
+/**
+ * Test of {@link AdapterStateListener}.
+ */
+@SmallTest
+@RunWith(AndroidJUnit4.class)
+public class AdapterStateListenerTest {
+
+ IUwbAdapter mUwbAdapter = mock(IUwbAdapter.class);
+
+ Answer mRegisterSuccessAnswer = new Answer() {
+ public Object answer(InvocationOnMock invocation) {
+ Object[] args = invocation.getArguments();
+ IUwbAdapterStateCallbacks cb = (IUwbAdapterStateCallbacks) args[0];
+ try {
+ cb.onAdapterStateChanged(false, StateChangeReason.UNKNOWN);
+ } catch (RemoteException e) {
+ // Nothing to do
+ }
+ return new Object();
+ }
+ };
+
+ Throwable mThrowRemoteException = new RemoteException("RemoteException");
+
+ private static Executor getExecutor() {
+ return new Executor() {
+ @Override
+ public void execute(Runnable command) {
+ command.run();
+ }
+ };
+ }
+
+ private static void verifyCallbackStateChangedInvoked(
+ AdapterStateCallback callback, int numTimes) {
+ verify(callback, times(numTimes)).onStateChanged(anyBoolean(), anyInt());
+ }
+
+ @Test
+ public void testRegister_RegisterUnregister() throws RemoteException {
+ doAnswer(mRegisterSuccessAnswer).when(mUwbAdapter).registerAdapterStateCallbacks(any());
+
+ AdapterStateListener adapterStateListener = new AdapterStateListener(mUwbAdapter);
+ AdapterStateCallback callback1 = mock(AdapterStateCallback.class);
+ AdapterStateCallback callback2 = mock(AdapterStateCallback.class);
+
+ // Verify that the adapter state listener registered with the UWB Adapter
+ adapterStateListener.register(getExecutor(), callback1);
+ verify(mUwbAdapter, times(1)).registerAdapterStateCallbacks(any());
+ verifyCallbackStateChangedInvoked(callback1, 1);
+ verifyCallbackStateChangedInvoked(callback2, 0);
+
+ // Register a second client and no new call to UWB Adapter
+ adapterStateListener.register(getExecutor(), callback2);
+ verify(mUwbAdapter, times(1)).registerAdapterStateCallbacks(any());
+ verifyCallbackStateChangedInvoked(callback1, 1);
+ verifyCallbackStateChangedInvoked(callback2, 1);
+
+ // Unregister first callback
+ adapterStateListener.unregister(callback1);
+ verify(mUwbAdapter, times(1)).registerAdapterStateCallbacks(any());
+ verify(mUwbAdapter, times(0)).unregisterAdapterStateCallbacks(any());
+ verifyCallbackStateChangedInvoked(callback1, 1);
+ verifyCallbackStateChangedInvoked(callback2, 1);
+
+ // Unregister second callback
+ adapterStateListener.unregister(callback2);
+ verify(mUwbAdapter, times(1)).registerAdapterStateCallbacks(any());
+ verify(mUwbAdapter, times(1)).unregisterAdapterStateCallbacks(any());
+ verifyCallbackStateChangedInvoked(callback1, 1);
+ verifyCallbackStateChangedInvoked(callback2, 1);
+ }
+
+ @Test
+ public void testRegister_FirstRegisterFails() throws RemoteException {
+ AdapterStateListener adapterStateListener = new AdapterStateListener(mUwbAdapter);
+ AdapterStateCallback callback1 = mock(AdapterStateCallback.class);
+ AdapterStateCallback callback2 = mock(AdapterStateCallback.class);
+
+ // Throw a remote exception whenever first registering
+ doThrow(mThrowRemoteException).when(mUwbAdapter).registerAdapterStateCallbacks(any());
+
+ adapterStateListener.register(getExecutor(), callback1);
+ verify(mUwbAdapter, times(1)).registerAdapterStateCallbacks(any());
+
+ // No longer throw an exception, instead succeed
+ doAnswer(mRegisterSuccessAnswer).when(mUwbAdapter).registerAdapterStateCallbacks(any());
+
+ // Register a different callback
+ adapterStateListener.register(getExecutor(), callback2);
+ verify(mUwbAdapter, times(2)).registerAdapterStateCallbacks(any());
+
+ // Ensure first callback was invoked again
+ verifyCallbackStateChangedInvoked(callback1, 2);
+ verifyCallbackStateChangedInvoked(callback2, 1);
+ }
+
+ @Test
+ public void testRegister_RegisterSameCallbackTwice() throws RemoteException {
+ AdapterStateListener adapterStateListener = new AdapterStateListener(mUwbAdapter);
+ AdapterStateCallback callback = mock(AdapterStateCallback.class);
+ doAnswer(mRegisterSuccessAnswer).when(mUwbAdapter).registerAdapterStateCallbacks(any());
+
+ adapterStateListener.register(getExecutor(), callback);
+ verifyCallbackStateChangedInvoked(callback, 1);
+
+ adapterStateListener.register(getExecutor(), callback);
+ verifyCallbackStateChangedInvoked(callback, 1);
+
+ // Invoke a state change and ensure the callback is only called once
+ adapterStateListener.onAdapterStateChanged(false, StateChangeReason.UNKNOWN);
+ verifyCallbackStateChangedInvoked(callback, 2);
+ }
+
+ @Test
+ public void testCallback_RunViaExecutor_Success() throws RemoteException {
+ // Verify that the callbacks are invoked on the executor when successful
+ doAnswer(mRegisterSuccessAnswer).when(mUwbAdapter).registerAdapterStateCallbacks(any());
+ runViaExecutor();
+ }
+
+ @Test
+ public void testCallback_RunViaExecutor_Failure() throws RemoteException {
+ // Verify that the callbacks are invoked on the executor when there is a remote exception
+ doThrow(mThrowRemoteException).when(mUwbAdapter).registerAdapterStateCallbacks(any());
+ runViaExecutor();
+ }
+
+ private void runViaExecutor() {
+ AdapterStateListener adapterStateListener = new AdapterStateListener(mUwbAdapter);
+ AdapterStateCallback callback = mock(AdapterStateCallback.class);
+
+ Executor executor = mock(Executor.class);
+
+ // Do not run commands received and ensure that the callback is not invoked
+ doAnswer(new ExecutorAnswer(false)).when(executor).execute(any());
+ adapterStateListener.register(executor, callback);
+ verify(executor, times(1)).execute(any());
+ verifyCallbackStateChangedInvoked(callback, 0);
+
+ // Manually invoke the callback and ensure callback is not invoked
+ adapterStateListener.onAdapterStateChanged(false, StateChangeReason.UNKNOWN);
+ verify(executor, times(2)).execute(any());
+ verifyCallbackStateChangedInvoked(callback, 0);
+
+ // Run the command that the executor receives
+ doAnswer(new ExecutorAnswer(true)).when(executor).execute(any());
+ adapterStateListener.onAdapterStateChanged(false, StateChangeReason.UNKNOWN);
+ verify(executor, times(3)).execute(any());
+ verifyCallbackStateChangedInvoked(callback, 1);
+ }
+
+ class ExecutorAnswer implements Answer {
+
+ final boolean mShouldRun;
+ ExecutorAnswer(boolean shouldRun) {
+ mShouldRun = shouldRun;
+ }
+
+ @Override
+ public Object answer(InvocationOnMock invocation) throws Throwable {
+ if (mShouldRun) {
+ ((Runnable) invocation.getArgument(0)).run();
+ }
+ return null;
+ }
+ }
+
+ @Test
+ public void testNotify_AllCallbacksNotified() throws RemoteException {
+ doAnswer(mRegisterSuccessAnswer).when(mUwbAdapter).registerAdapterStateCallbacks(any());
+
+ AdapterStateListener adapterStateListener = new AdapterStateListener(mUwbAdapter);
+ List<AdapterStateCallback> callbacks = new ArrayList<>();
+ for (int i = 0; i < 10; i++) {
+ AdapterStateCallback callback = mock(AdapterStateCallback.class);
+ adapterStateListener.register(getExecutor(), callback);
+ callbacks.add(callback);
+ }
+
+ // Ensure every callback got the initial state
+ for (AdapterStateCallback callback : callbacks) {
+ verifyCallbackStateChangedInvoked(callback, 1);
+ }
+
+ // Invoke a state change and ensure all callbacks are invoked
+ adapterStateListener.onAdapterStateChanged(true, StateChangeReason.ALL_SESSIONS_CLOSED);
+ for (AdapterStateCallback callback : callbacks) {
+ verifyCallbackStateChangedInvoked(callback, 2);
+ }
+ }
+
+ @Test
+ public void testStateChange_CorrectValue() {
+ AdapterStateListener adapterStateListener = new AdapterStateListener(mUwbAdapter);
+
+ AdapterStateCallback callback = mock(AdapterStateCallback.class);
+
+ adapterStateListener.register(getExecutor(), callback);
+
+ runStateChangeValue(StateChangeReason.ALL_SESSIONS_CLOSED,
+ AdapterStateCallback.STATE_CHANGED_REASON_ALL_SESSIONS_CLOSED);
+
+ runStateChangeValue(StateChangeReason.SESSION_STARTED,
+ AdapterStateCallback.STATE_CHANGED_REASON_SESSION_STARTED);
+
+ runStateChangeValue(StateChangeReason.SYSTEM_BOOT,
+ AdapterStateCallback.STATE_CHANGED_REASON_SYSTEM_BOOT);
+
+ runStateChangeValue(StateChangeReason.SYSTEM_POLICY,
+ AdapterStateCallback.STATE_CHANGED_REASON_SYSTEM_POLICY);
+
+ runStateChangeValue(StateChangeReason.UNKNOWN,
+ AdapterStateCallback.STATE_CHANGED_REASON_ERROR_UNKNOWN);
+ }
+
+ private void runStateChangeValue(@StateChangeReason int reasonIn,
+ @AdapterStateCallback.StateChangedReason int reasonOut) {
+ AdapterStateListener adapterStateListener = new AdapterStateListener(mUwbAdapter);
+ AdapterStateCallback callback = mock(AdapterStateCallback.class);
+ adapterStateListener.register(getExecutor(), callback);
+
+ adapterStateListener.onAdapterStateChanged(false, reasonIn);
+ verify(callback, times(1)).onStateChanged(false, reasonOut);
+
+ adapterStateListener.onAdapterStateChanged(true, reasonIn);
+ verify(callback, times(1)).onStateChanged(true, reasonOut);
+ }
+
+ @Test
+ public void testStateChange_FirstRegisterGetsCorrectState() throws RemoteException {
+ AdapterStateListener adapterStateListener = new AdapterStateListener(mUwbAdapter);
+ AdapterStateCallback callback = mock(AdapterStateCallback.class);
+
+ Answer registerAnswer = new Answer() {
+ public Object answer(InvocationOnMock invocation) {
+ Object[] args = invocation.getArguments();
+ IUwbAdapterStateCallbacks cb = (IUwbAdapterStateCallbacks) args[0];
+ try {
+ cb.onAdapterStateChanged(true, StateChangeReason.SESSION_STARTED);
+ } catch (RemoteException e) {
+ // Nothing to do
+ }
+ return new Object();
+ }
+ };
+
+ doAnswer(registerAnswer).when(mUwbAdapter).registerAdapterStateCallbacks(any());
+
+ adapterStateListener.register(getExecutor(), callback);
+ verify(callback).onStateChanged(true,
+ AdapterStateCallback.STATE_CHANGED_REASON_SESSION_STARTED);
+ }
+
+ @Test
+ public void testStateChange_SecondRegisterGetsCorrectState() {
+ AdapterStateListener adapterStateListener = new AdapterStateListener(mUwbAdapter);
+ AdapterStateCallback callback1 = mock(AdapterStateCallback.class);
+ AdapterStateCallback callback2 = mock(AdapterStateCallback.class);
+
+ adapterStateListener.register(getExecutor(), callback1);
+ adapterStateListener.onAdapterStateChanged(true, StateChangeReason.SYSTEM_BOOT);
+
+ adapterStateListener.register(getExecutor(), callback2);
+ verify(callback2).onStateChanged(true,
+ AdapterStateCallback.STATE_CHANGED_REASON_SYSTEM_BOOT);
+ }
+}