diff options
-rw-r--r-- | core/java/android/uwb/AdapterStateListener.java | 149 | ||||
-rw-r--r-- | core/java/android/uwb/IUwbAdapter.aidl | 11 | ||||
-rw-r--r-- | core/java/android/uwb/StateChangeReason.aidl | 5 | ||||
-rw-r--r-- | core/java/android/uwb/UwbManager.java | 91 | ||||
-rw-r--r-- | core/tests/uwbtests/Android.bp | 1 | ||||
-rw-r--r-- | core/tests/uwbtests/src/android/uwb/AdapterStateListenerTest.java | 311 |
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); + } +} |