diff --git a/remoteauth/service/Android.bp b/remoteauth/service/Android.bp index 3486d8c4b0..c7e5419e64 100644 --- a/remoteauth/service/Android.bp +++ b/remoteauth/service/Android.bp @@ -47,6 +47,7 @@ java_library { "modules-utils-preconditions", "modules-utils-backgroundthread", "presence-lite-protos", + "uwb_androidx_backend", ], sdk_version: "system_server_current", // This is included in service-connectivity which is 30+ diff --git a/remoteauth/service/java/com/android/server/remoteauth/ranging/RangingCapabilities.java b/remoteauth/service/java/com/android/server/remoteauth/ranging/RangingCapabilities.java index 2b5efff641..36aa5857a9 100644 --- a/remoteauth/service/java/com/android/server/remoteauth/ranging/RangingCapabilities.java +++ b/remoteauth/service/java/com/android/server/remoteauth/ranging/RangingCapabilities.java @@ -15,6 +15,9 @@ */ package com.android.server.remoteauth.ranging; +import android.annotation.NonNull; +import android.annotation.Nullable; + import androidx.annotation.IntDef; import com.google.common.collect.ImmutableList; @@ -43,6 +46,8 @@ public class RangingCapabilities { public static final int RANGING_METHOD_UWB = 0x1; private final ImmutableList mSupportedRangingMethods; + private final androidx.core.uwb.backend.impl.internal.RangingCapabilities + mUwbRangingCapabilities; /** * Gets the list of supported ranging methods of the device. @@ -53,13 +58,28 @@ public class RangingCapabilities { return mSupportedRangingMethods; } - private RangingCapabilities(List supportedRangingMethods) { + /** + * Gets the UWB ranging capabilities of the device. + * + * @return UWB ranging capabilities, null if UWB is not a supported {@link RangingMethod} in + * {@link #getSupportedRangingMethods}. + */ + @Nullable + public androidx.core.uwb.backend.impl.internal.RangingCapabilities getUwbRangingCapabilities() { + return mUwbRangingCapabilities; + } + + private RangingCapabilities( + List supportedRangingMethods, + androidx.core.uwb.backend.impl.internal.RangingCapabilities uwbRangingCapabilities) { mSupportedRangingMethods = ImmutableList.copyOf(supportedRangingMethods); + mUwbRangingCapabilities = uwbRangingCapabilities; } /** Builder class for {@link RangingCapabilities}. */ public static final class Builder { private List mSupportedRangingMethods = new ArrayList<>(); + private androidx.core.uwb.backend.impl.internal.RangingCapabilities mUwbRangingCapabilities; /** Adds a supported {@link RangingMethod} */ public Builder addSupportedRangingMethods(@RangingMethod int rangingMethod) { @@ -67,9 +87,18 @@ public class RangingCapabilities { return this; } + /** Sets the uwb ranging capabilities. */ + public Builder setUwbRangingCapabilities( + @NonNull + androidx.core.uwb.backend.impl.internal.RangingCapabilities + uwbRangingCapabilities) { + mUwbRangingCapabilities = uwbRangingCapabilities; + return this; + } + /** Builds {@link RangingCapabilities}. */ public RangingCapabilities build() { - return new RangingCapabilities(mSupportedRangingMethods); + return new RangingCapabilities(mSupportedRangingMethods, mUwbRangingCapabilities); } } } diff --git a/remoteauth/service/java/com/android/server/remoteauth/ranging/RangingManager.java b/remoteauth/service/java/com/android/server/remoteauth/ranging/RangingManager.java index 989b5edbb4..5a41aac66d 100644 --- a/remoteauth/service/java/com/android/server/remoteauth/ranging/RangingManager.java +++ b/remoteauth/service/java/com/android/server/remoteauth/ranging/RangingManager.java @@ -15,7 +15,18 @@ */ package com.android.server.remoteauth.ranging; +import static android.content.pm.PackageManager.FEATURE_UWB; + +import static com.android.server.remoteauth.ranging.RangingCapabilities.RANGING_METHOD_UWB; + +import android.annotation.NonNull; +import android.annotation.Nullable; import android.content.Context; +import android.os.Build; +import android.util.Log; + +import androidx.core.uwb.backend.impl.internal.UwbFeatureFlags; +import androidx.core.uwb.backend.impl.internal.UwbServiceImpl; /** * Manages the creation of generic device to device ranging session and obtaining device's ranging @@ -25,16 +36,46 @@ import android.content.Context; * outside of this class. */ public class RangingManager { + private static final String TAG = "RangingManager"; - public RangingManager(Context context) {} + private Context mContext; + @NonNull private RangingCapabilities mCachedRangingCapabilities; + @NonNull private UwbServiceImpl mUwbServiceImpl; + + public RangingManager(@NonNull Context context) { + mContext = context; + if (mContext.getPackageManager().hasSystemFeature(FEATURE_UWB)) { + initiateUwb(); + } + } + + /** + * Shutdown and stop all listeners and tasks. After shutdown, RangingManager shall not be used + * anymore. + */ + public void shutdown() { + if (mUwbServiceImpl != null) { + mUwbServiceImpl.shutdown(); + } + Log.i(TAG, "shutdown"); + } /** * Gets the {@link RangingCapabilities} of this device. * * @return RangingCapabilities. */ + @NonNull public RangingCapabilities getRangingCapabilities() { - return null; + if (mCachedRangingCapabilities == null) { + RangingCapabilities.Builder builder = new RangingCapabilities.Builder(); + if (mUwbServiceImpl != null) { + builder.addSupportedRangingMethods(RANGING_METHOD_UWB); + builder.setUwbRangingCapabilities(mUwbServiceImpl.getRangingCapabilities()); + } + mCachedRangingCapabilities = builder.build(); + } + return mCachedRangingCapabilities; } /** @@ -42,9 +83,23 @@ public class RangingManager { * provided based on the rangingCapabilities of the device. * * @param sessionParameters parameters used to setup the session. - * @return the created RangingSession. + * @return the created RangingSession. Null if session creation failed. */ - public RangingSession createSession(SessionParameters sessionParameters) { + @Nullable + public RangingSession createSession(@NonNull SessionParameters sessionParameters) { return null; } + + /** Initiation required for ranging with UWB. */ + private void initiateUwb() { + UwbFeatureFlags uwbFeatureFlags = + new UwbFeatureFlags.Builder() + .setSkipRangingCapabilitiesCheck( + Build.VERSION.SDK_INT <= Build.VERSION_CODES.S_V2) + .setReversedByteOrderFiraParams( + Build.VERSION.SDK_INT <= Build.VERSION_CODES.TIRAMISU) + .build(); + mUwbServiceImpl = new UwbServiceImpl(mContext, uwbFeatureFlags); + Log.i(TAG, "RangingManager initiateUwb complete"); + } } diff --git a/remoteauth/service/java/com/android/server/remoteauth/ranging/SessionInfo.java b/remoteauth/service/java/com/android/server/remoteauth/ranging/SessionInfo.java index 5e4fc486b2..0ec640cd70 100644 --- a/remoteauth/service/java/com/android/server/remoteauth/ranging/SessionInfo.java +++ b/remoteauth/service/java/com/android/server/remoteauth/ranging/SessionInfo.java @@ -41,6 +41,16 @@ public class SessionInfo { mRangingMethod = rangingMethod; } + @Override + public String toString() { + return "SessionInfo { " + + "DeviceId = " + + mDeviceId + + "RangingMethod = " + + mRangingMethod + + " }"; + } + /** Builder class for {@link SessionInfo}. */ public static final class Builder { private String mDeviceId = ""; diff --git a/remoteauth/tests/unit/Android.bp b/remoteauth/tests/unit/Android.bp index 4b92d847eb..629d3605e5 100644 --- a/remoteauth/tests/unit/Android.bp +++ b/remoteauth/tests/unit/Android.bp @@ -35,13 +35,20 @@ android_test { static_libs: [ "androidx.test.ext.junit", "androidx.test.rules", + "com.uwb.support.generic", "framework-remoteauth-static", "junit", "libprotobuf-java-lite", + "mockito-target-extended-minus-junit4", "platform-test-annotations", "service-remoteauth-pre-jarjar", "truth-prebuilt", ], + // these are needed for Extended Mockito + jni_libs: [ + "libdexmakerjvmtiagent", + "libstaticjvmtiagent", + ], test_suites: [ "general-tests", "mts-tethering", diff --git a/remoteauth/tests/unit/src/com/android/server/remoteauth/ranging/RangingCapabilitiesTest.java b/remoteauth/tests/unit/src/com/android/server/remoteauth/ranging/RangingCapabilitiesTest.java index e6b6e3b9a0..8135b4f23b 100644 --- a/remoteauth/tests/unit/src/com/android/server/remoteauth/ranging/RangingCapabilitiesTest.java +++ b/remoteauth/tests/unit/src/com/android/server/remoteauth/ranging/RangingCapabilitiesTest.java @@ -28,16 +28,25 @@ import org.junit.runner.RunWith; /** Unit test for {@link RangingCapabilities}. */ @RunWith(AndroidJUnit4.class) public class RangingCapabilitiesTest { + private static final androidx.core.uwb.backend.impl.internal.RangingCapabilities + TEST_UWB_RANGING_CAPABILITIES = + new androidx.core.uwb.backend.impl.internal.RangingCapabilities( + /* supportsDistance= */ true, + /* supportsAzimuthalAngle= */ true, + /* supportsElevationAngle= */ true); @Test public void testBuildingRangingCapabilities_success() { final RangingCapabilities rangingCapabilities = new RangingCapabilities.Builder() .addSupportedRangingMethods(RANGING_METHOD_UWB) + .setUwbRangingCapabilities(TEST_UWB_RANGING_CAPABILITIES) .build(); assertEquals(rangingCapabilities.getSupportedRangingMethods().size(), 1); assertEquals( (int) rangingCapabilities.getSupportedRangingMethods().get(0), RANGING_METHOD_UWB); + assertEquals( + rangingCapabilities.getUwbRangingCapabilities(), TEST_UWB_RANGING_CAPABILITIES); } } diff --git a/remoteauth/tests/unit/src/com/android/server/remoteauth/ranging/RangingManagerTest.java b/remoteauth/tests/unit/src/com/android/server/remoteauth/ranging/RangingManagerTest.java new file mode 100644 index 0000000000..609809ff38 --- /dev/null +++ b/remoteauth/tests/unit/src/com/android/server/remoteauth/ranging/RangingManagerTest.java @@ -0,0 +1,165 @@ +/* + * Copyright (C) 2023 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 com.android.server.remoteauth.ranging; + +import static android.content.pm.PackageManager.FEATURE_UWB; +import static android.uwb.UwbManager.AdapterStateCallback.STATE_ENABLED_INACTIVE; + +import static androidx.core.uwb.backend.impl.internal.RangingCapabilities.FIRA_DEFAULT_SUPPORTED_CONFIG_IDS; +import static androidx.core.uwb.backend.impl.internal.Utils.CONFIG_PROVISIONED_INDIVIDUAL_MULTICAST_DS_TWR; +import static androidx.core.uwb.backend.impl.internal.Utils.CONFIG_PROVISIONED_MULTICAST_DS_TWR; +import static androidx.core.uwb.backend.impl.internal.Utils.CONFIG_PROVISIONED_UNICAST_DS_TWR; +import static androidx.core.uwb.backend.impl.internal.Utils.CONFIG_PROVISIONED_UNICAST_DS_TWR_NO_AOA; + +import static com.android.server.remoteauth.ranging.RangingCapabilities.RANGING_METHOD_UWB; + +import static org.junit.Assert.assertArrayEquals; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNotNull; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.Mockito.times; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.verifyZeroInteractions; +import static org.mockito.Mockito.when; + +import android.content.Context; +import android.content.pm.PackageManager; +import android.uwb.UwbManager; + +import androidx.test.ext.junit.runners.AndroidJUnit4; + +import com.google.uwb.support.fira.FiraParams; +import com.google.uwb.support.fira.FiraSpecificationParams; +import com.google.uwb.support.generic.GenericSpecificationParams; + +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.Mock; +import org.mockito.MockitoAnnotations; + +import java.util.ArrayList; +import java.util.EnumSet; +import java.util.List; + +/** Unit test for {@link RangingManager}. */ +@RunWith(AndroidJUnit4.class) +public class RangingManagerTest { + private static final List TEST_UWB_SUPPORTED_CHANNELS = List.of(8, 9); + private static final FiraSpecificationParams TEST_FIRA_SPEC = + new FiraSpecificationParams.Builder() + .setSupportedChannels(TEST_UWB_SUPPORTED_CHANNELS) + .setStsCapabilities(EnumSet.allOf(FiraParams.StsCapabilityFlag.class)) + .build(); + private static final GenericSpecificationParams TEST_GENERIC_SPEC = + new GenericSpecificationParams.Builder() + .setFiraSpecificationParams(TEST_FIRA_SPEC) + .build(); + + @Mock private Context mContext; + @Mock private PackageManager mPackageManager; + @Mock private UwbManager mUwbManager; + + private RangingManager mRangingManager; + + @Before + public void setUp() throws Exception { + MockitoAnnotations.initMocks(this); + + when(mContext.getSystemService(UwbManager.class)).thenReturn(mUwbManager); + when(mContext.getPackageManager()).thenReturn(mPackageManager); + when(mPackageManager.hasSystemFeature(FEATURE_UWB)).thenReturn(false); + when(mUwbManager.getAdapterState()).thenReturn(STATE_ENABLED_INACTIVE); + when(mUwbManager.getSpecificationInfo()).thenReturn(TEST_GENERIC_SPEC.toBundle()); + } + + @Test + public void testConstruction() { + mRangingManager = new RangingManager(mContext); + verifyZeroInteractions(mUwbManager); + } + + @Test + public void testConstruction_withUwbEnabled() { + when(mPackageManager.hasSystemFeature(FEATURE_UWB)).thenReturn(true); + + mRangingManager = new RangingManager(mContext); + + verify(mUwbManager).getAdapterState(); + verify(mUwbManager).registerAdapterStateCallback(any(), any()); + } + + @Test + public void testShutdown_withUwbEnabled() { + when(mPackageManager.hasSystemFeature(FEATURE_UWB)).thenReturn(true); + + mRangingManager = new RangingManager(mContext); + mRangingManager.shutdown(); + + verify(mUwbManager).registerAdapterStateCallback(any(), any()); + verify(mUwbManager).unregisterAdapterStateCallback(any()); + } + + @Test + public void testGetRangingCapabilities() { + mRangingManager = new RangingManager(mContext); + RangingCapabilities capabilities = mRangingManager.getRangingCapabilities(); + + assertEquals(capabilities.getSupportedRangingMethods().size(), 0); + assertEquals(capabilities.getUwbRangingCapabilities(), null); + } + + @Test + public void testGetRangingCapabilities_withUwbEnabled() { + when(mPackageManager.hasSystemFeature(FEATURE_UWB)).thenReturn(true); + + mRangingManager = new RangingManager(mContext); + RangingCapabilities capabilities = mRangingManager.getRangingCapabilities(); + + List supportedConfigIds = new ArrayList<>(FIRA_DEFAULT_SUPPORTED_CONFIG_IDS); + supportedConfigIds.add(CONFIG_PROVISIONED_UNICAST_DS_TWR); + supportedConfigIds.add(CONFIG_PROVISIONED_MULTICAST_DS_TWR); + supportedConfigIds.add(CONFIG_PROVISIONED_UNICAST_DS_TWR_NO_AOA); + supportedConfigIds.add(CONFIG_PROVISIONED_INDIVIDUAL_MULTICAST_DS_TWR); + + verify(mUwbManager, times(1)).getSpecificationInfo(); + assertEquals(capabilities.getSupportedRangingMethods().size(), 1); + assertEquals((int) capabilities.getSupportedRangingMethods().get(0), RANGING_METHOD_UWB); + androidx.core.uwb.backend.impl.internal.RangingCapabilities uwbCapabilities = + capabilities.getUwbRangingCapabilities(); + assertNotNull(uwbCapabilities); + assertArrayEquals( + uwbCapabilities.getSupportedChannels().toArray(), + TEST_UWB_SUPPORTED_CHANNELS.toArray()); + assertArrayEquals( + uwbCapabilities.getSupportedConfigIds().toArray(), supportedConfigIds.toArray()); + } + + @Test + public void testGetRangingCapabilities_multipleCalls() { + when(mPackageManager.hasSystemFeature(FEATURE_UWB)).thenReturn(true); + + mRangingManager = new RangingManager(mContext); + RangingCapabilities capabilities1 = mRangingManager.getRangingCapabilities(); + RangingCapabilities capabilities2 = mRangingManager.getRangingCapabilities(); + RangingCapabilities capabilities3 = mRangingManager.getRangingCapabilities(); + + verify(mUwbManager, times(1)).getSpecificationInfo(); + assertEquals(capabilities1, capabilities2); + assertEquals(capabilities2, capabilities3); + } +}