diff options
author | Android Build Coastguard Worker <android-build-coastguard-worker@google.com> | 2023-07-13 17:29:00 +0000 |
---|---|---|
committer | Android Build Coastguard Worker <android-build-coastguard-worker@google.com> | 2023-07-13 17:29:00 +0000 |
commit | 31ea91e41f513d708c766872c724055f65152caf (patch) | |
tree | 4a86a14d14c39357949158cc7d79580171607dcb | |
parent | 4dd015fee647e4cb6666018a41e797dec74ba42f (diff) | |
parent | ee34bb56f472e0c69222d0ea7ca319df23373e38 (diff) | |
download | Uwb-aml_adb_340912530.tar.gz |
Snap for 10491609 from ee34bb56f472e0c69222d0ea7ca319df23373e38 to mainline-adbd-releaseaml_adb_340912530aml_adb_340912350aml_adb_340912200aml_adb_340912000android14-mainline-adbd-release
Change-Id: I68a02677c25ac98c1ccfb4bbcd23af2987604045
22 files changed, 904 insertions, 56 deletions
diff --git a/androidx_backend/src/androidx/core/uwb/backend/impl/internal/RangingDevice.java b/androidx_backend/src/androidx/core/uwb/backend/impl/internal/RangingDevice.java index 4ec07062..8df05baa 100644 --- a/androidx_backend/src/androidx/core/uwb/backend/impl/internal/RangingDevice.java +++ b/androidx_backend/src/androidx/core/uwb/backend/impl/internal/RangingDevice.java @@ -56,7 +56,7 @@ public abstract class RangingDevice { private static final int SESSION_ID_UNSET = 0; /** Timeout value after ranging start call */ - private static final int RANGING_START_TIMEOUT_MILLIS = 3000; + private static final int RANGING_START_TIMEOUT_MILLIS = 3100; protected final UwbManager mUwbManager; diff --git a/docs/FiRa_CRs_Android.csv b/docs/FiRa_CRs_Android.csv index 11e7469d..9475f1b0 100644 --- a/docs/FiRa_CRs_Android.csv +++ b/docs/FiRa_CRs_Android.csv @@ -29,7 +29,7 @@ CR326,YES, CR327,NA,Marked as N/A (MAC) CR328,NO, CR329,NO, -CR330,NO, +CR330,YES, CR337,NA,CR-337 seems to have been withdrawn CR338,NA,Marked as N/A (MAC) CR340,NA,Marked as N/A (MAC) @@ -85,7 +85,7 @@ CR432,NA,Marked as N/A (MAC) CR433,YES, CR435,YES, CR436,NA, -CR437,NO, +CR437,YES, CR438,YES, CR441,NO, CR442,YES, diff --git a/framework/java/android/uwb/IUwbAdapter.aidl b/framework/java/android/uwb/IUwbAdapter.aidl index 3228f5c5..a17e44d0 100644 --- a/framework/java/android/uwb/IUwbAdapter.aidl +++ b/framework/java/android/uwb/IUwbAdapter.aidl @@ -355,6 +355,29 @@ interface IUwbAdapter { int sendVendorUciMessage(int mt, int gid, int oid, in byte[] payload); + /** + * @hide + * Sets the Hybrid UWB Session Configuration + * + * @param SessionHandle Primary session handle + * @param params protocol specific parameters to initiate the hybrid session + * @return HUS configuration status code + * <p>{@link UwbUciConstants#STATUS_CODE_OK} UWBS successfully processes the command + * + * <p>{@link UwbUciConstants#STATUS_CODE_FAILED} Intended operation is failed to complete + * + * <p>{@link UwbUciConstants#STATUS_CODE_ERROR_SESSION_NOT_EXIST} Primary session or + * secondary session is not existing or not created + * + * <p>{@link UwbUciConstants#STATUS_CODE_ERROR_SESSION_NOT_CONFIGURED} Primary session or + * secondary session has not been configured (i.e. SESSION_STATE_IDLE) + * + * <p>{@link UwbUciConstants#STATUS_CODE_ERROR_SESSION_DUPLICATE} Session Handle in phase + * list is repeated + */ + int setHybridSessionConfiguration(in SessionHandle sessionHandle, + in PersistableBundle params); + void updateRangingRoundsDtTag(in SessionHandle sessionHandle, in PersistableBundle parameters); void getUwbActivityEnergyInfoAsync(in IOnUwbActivityEnergyInfoListener listener); diff --git a/framework/java/android/uwb/RangingSession.java b/framework/java/android/uwb/RangingSession.java index a031c28e..b1ab55d9 100644 --- a/framework/java/android/uwb/RangingSession.java +++ b/framework/java/android/uwb/RangingSession.java @@ -800,6 +800,41 @@ public final class RangingSession implements AutoCloseable { /** * @hide + * Sets the Hybrid UWB Session Configuration + * <p> + * Requires the {@link android.Manifest.permission#UWB_PRIVILEGED} permission + * + * @param params protocol specific parameters to initiate the hybrid session + * @return HUS configuration status code + * <p>{@link UwbUciConstants#STATUS_CODE_OK} UWBS successfully processes the command + * + * <p>{@link UwbUciConstants#STATUS_CODE_FAILED} Intended operation is failed to complete + * + * <p>{@link UwbUciConstants#STATUS_CODE_ERROR_SESSION_NOT_EXIST} Primary session or + * secondary session is not existing or not created + * + * <p>{@link UwbUciConstants#STATUS_CODE_ERROR_SESSION_NOT_CONFIGURED} Primary session or + * secondary session has not been configured (i.e. SESSION_STATE_IDLE) + * + * <p>{@link UwbUciConstants#STATUS_CODE_ERROR_SESSION_DUPLICATE} Session Handle in phase + * list is repeated + * @throws RemoteException if a remote error occurred + */ + @RequiresPermission(Manifest.permission.UWB_PRIVILEGED) + public int setHybridSessionConfiguration(@NonNull PersistableBundle params) { + if (!isOpen()) { + throw new IllegalStateException("Ranging session is not open"); + } + + try { + return mAdapter.setHybridSessionConfiguration(mSessionHandle, params); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + } + + /** + * @hide */ public void onRangingOpened() { if (mState == State.CLOSED) { diff --git a/framework/tests/src/android/uwb/RangingSessionTest.java b/framework/tests/src/android/uwb/RangingSessionTest.java index 4673721d..4234adfe 100644 --- a/framework/tests/src/android/uwb/RangingSessionTest.java +++ b/framework/tests/src/android/uwb/RangingSessionTest.java @@ -71,6 +71,7 @@ public class RangingSessionTest { private static final int HANDLE_ID = 12; private static final int PID = Process.myPid(); private static final int MAX_DATA_SIZE = 100; + public static final int STATUS_OK = 0; @Test public void testOnRangingOpened_OnOpenSuccessCalled() { @@ -533,6 +534,40 @@ public class RangingSessionTest { } @Test + public void testSetHybridSessionConfiguration() throws RemoteException { + assumeTrue(SdkLevel.isAtLeastV()); // Test should only run on V+ devices. + SessionHandle handle = new SessionHandle(HANDLE_ID, ATTRIBUTION_SOURCE, PID); + RangingSession.Callback callback = mock(RangingSession.Callback.class); + IUwbAdapter adapter = mock(IUwbAdapter.class); + RangingSession session = new RangingSession(EXECUTOR, callback, adapter, handle); + + // Confirm that setHybridSessionConfiguration() throws an IllegalStateException + // when the ranging session is not open. + assertFalse(session.isOpen()); + verifyThrowIllegalState(() -> session.setHybridSessionConfiguration(PARAMS)); + + // Confirm that setHybridSessionConfiguration() returns a value when the ranging + // session has been opened. + session.onRangingOpened(); + assertEquals(session.setHybridSessionConfiguration(PARAMS), STATUS_OK); + + // Confirm that setHybridSessionConfiguration() returns a value when the ranging + // session has been started. + session.onRangingStarted(PARAMS); + assertEquals(session.setHybridSessionConfiguration(PARAMS), STATUS_OK); + + // Confirm that setHybridSessionConfiguration() still returns a value, when the ranging + // session was stopped. + session.onRangingStopped(REASON, PARAMS); + assertEquals(session.setHybridSessionConfiguration(PARAMS), STATUS_OK); + + // Confirm that setHybridSessionConfiguration() throws an IllegalStateException when the + // ranging session has now been closed. + session.onRangingClosed(REASON, PARAMS); + verifyThrowIllegalState(() -> session.setHybridSessionConfiguration(PARAMS)); + } + + @Test public void testPoseUpdate() throws RemoteException { assumeTrue(SdkLevel.isAtLeastU()); // Test should only run on U+ devices. SessionHandle handle = new SessionHandle(HANDLE_ID, ATTRIBUTION_SOURCE, PID); diff --git a/service/java/com/android/server/uwb/UwbServiceCore.java b/service/java/com/android/server/uwb/UwbServiceCore.java index f7e852e0..9f5bb86c 100644 --- a/service/java/com/android/server/uwb/UwbServiceCore.java +++ b/service/java/com/android/server/uwb/UwbServiceCore.java @@ -199,6 +199,10 @@ public class UwbServiceCore implements INativeUwbManager.DeviceNotification, } private boolean isUwbEnabled() { + return getAdapterState() != AdapterStateCallback.STATE_DISABLED; + } + + private boolean isUwbChipEnabled() { synchronized (UwbServiceCore.this) { return getInternalAdapterState() != AdapterStateCallback.STATE_DISABLED; } @@ -640,6 +644,24 @@ public class UwbServiceCore implements INativeUwbManager.DeviceNotification, return getInternalAdapterState(); } + /** + * Configure a Hybrid session. + */ + public int setHybridSessionConfiguration(SessionHandle sessionHandle, + PersistableBundle params) { + int status = UwbUciConstants.STATUS_CODE_FAILED; + if (!isUwbEnabled()) { + throw new IllegalStateException("Uwb is not enabled"); + } + try { + status = mSessionManager.setHybridSessionConfiguration(sessionHandle, params); + return status; + } catch (RemoteException e) { + Log.e(TAG, "Failed to set Hybrid Session Configuration", e); + } + return status; + } + private /* @UwbManager.AdapterStateCallback.State */ int getInternalAdapterState() { synchronized (UwbServiceCore.this) { if (mChipIdToStateMap.isEmpty()) { @@ -663,10 +685,10 @@ public class UwbServiceCore implements INativeUwbManager.DeviceNotification, public synchronized void setEnabled(boolean enabled) { int task = enabled ? TASK_ENABLE : TASK_DISABLE; - if (enabled && isUwbEnabled()) { - Log.w(TAG, "Uwb is already enabled"); - } else if (!enabled && !isUwbEnabled()) { - Log.w(TAG, "Uwb is already disabled"); + if (enabled && isUwbChipEnabled()) { + Log.w(TAG, "Uwb chip is already enabled"); + } else if (!enabled && !isUwbChipEnabled()) { + Log.w(TAG, "Uwb chip is already disabled"); } mUwbTask.execute(task); @@ -826,8 +848,9 @@ public class UwbServiceCore implements INativeUwbManager.DeviceNotification, } private void handleEnable() { - if (isUwbEnabled()) { - Log.i(TAG, "UWB service is already enabled"); + if (isUwbChipEnabled()) { + Log.i(TAG, "UWB chip is already enabled, notify adapter state = " + + getAdapterState()); return; } try { @@ -890,8 +913,9 @@ public class UwbServiceCore implements INativeUwbManager.DeviceNotification, } private void handleDisable() { - if (!isUwbEnabled()) { - Log.i(TAG, "UWB service is already disabled"); + if (!isUwbChipEnabled()) { + Log.i(TAG, "UWB chip is already disabled, notify adapter state = " + + getAdapterState()); return; } diff --git a/service/java/com/android/server/uwb/UwbServiceImpl.java b/service/java/com/android/server/uwb/UwbServiceImpl.java index a1b16603..e8440184 100644 --- a/service/java/com/android/server/uwb/UwbServiceImpl.java +++ b/service/java/com/android/server/uwb/UwbServiceImpl.java @@ -396,6 +396,16 @@ public class UwbServiceImpl extends IUwbAdapter.Stub { } @Override + public int setHybridSessionConfiguration(SessionHandle sessionHandle, + PersistableBundle params) { + if (!SdkLevel.isAtLeastV()) { + throw new UnsupportedOperationException(); + } + enforceUwbPrivilegedPermission(); + return mUwbServiceCore.setHybridSessionConfiguration(sessionHandle, params); + } + + @Override public synchronized void setEnabled(boolean enabled) throws RemoteException { enforceUwbPrivilegedPermission(); persistUwbToggleState(enabled); diff --git a/service/java/com/android/server/uwb/UwbSessionManager.java b/service/java/com/android/server/uwb/UwbSessionManager.java index 33ecccfe..71ed4b74 100644 --- a/service/java/com/android/server/uwb/UwbSessionManager.java +++ b/service/java/com/android/server/uwb/UwbSessionManager.java @@ -92,6 +92,7 @@ import com.google.uwb.support.ccc.CccSpecificationParams; import com.google.uwb.support.ccc.CccStartRangingParams; import com.google.uwb.support.dltdoa.DlTDoARangingRoundsUpdate; import com.google.uwb.support.dltdoa.DlTDoARangingRoundsUpdateStatus; +import com.google.uwb.support.fira.FiraHybridSessionConfig; import com.google.uwb.support.fira.FiraOpenSessionParams; import com.google.uwb.support.fira.FiraParams; import com.google.uwb.support.fira.FiraPoseUpdateParams; @@ -104,6 +105,8 @@ import com.google.uwb.support.oemextension.SessionStatus; import java.io.Closeable; import java.io.FileDescriptor; import java.io.PrintWriter; +import java.nio.ByteBuffer; +import java.nio.ByteOrder; import java.util.ArrayList; import java.util.Collection; import java.util.Collections; @@ -129,6 +132,7 @@ public class UwbSessionManager implements INativeUwbManager.SessionNotification, private static final String TAG = "UwbSessionManager"; private static final byte OPERATION_TYPE_INIT_SESSION = 0; + private static final int UWB_HUS_PHASE_SIZE = 8; @VisibleForTesting public static final int SESSION_OPEN_RANGING = 1; @@ -1003,6 +1007,44 @@ public class UwbSessionManager implements INativeUwbManager.SessionNotification, mEventTask.execute(SESSION_SEND_DATA, info); } + /** + * Sets the hybrid UWB configuration + * + * @param sessionHandle : Primary session handle + * @param params : protocol specific parameters to initiate the hybrid + * session + * @return the status code of the operation + * @throws RemoteException if an error occurs during the remote call. + */ + public int setHybridSessionConfiguration(SessionHandle sessionHandle, PersistableBundle params) + throws RemoteException { + if (!isExistedSession(sessionHandle)) { + throw new IllegalStateException("Not initialized session ID"); + } + + FiraHybridSessionConfig husConfig = FiraHybridSessionConfig.fromBundle(params); + int numberOfPhases = husConfig.getNumberOfPhases(); + int sessionId = getSessionId(sessionHandle); + + Log.i(TAG, "setHybridSessionConfiguration() - sessionId: " + sessionId + + ", sessionHandle: " + sessionHandle + + ", numberOfPhases: " + numberOfPhases); + + ByteBuffer buffer = ByteBuffer.allocate(numberOfPhases * UWB_HUS_PHASE_SIZE); + buffer.order(ByteOrder.LITTLE_ENDIAN); + + for (FiraHybridSessionConfig.FiraHybridSessionPhaseList phaseList : + husConfig.getPhaseList()) { + buffer.putInt(mNativeUwbManager.getSessionToken(phaseList.getSessionHandle(), + getUwbSession(sessionId).getChipId())); + buffer.putShort(phaseList.getStartSlotIndex()); + buffer.putShort(phaseList.getEndSlotIndex()); + } + + return mNativeUwbManager.setHybridSessionConfiguration(sessionId, numberOfPhases, + husConfig.getUpdateTime(), buffer.array(), getUwbSession(sessionId).getChipId()); + } + private static final class SendDataInfo { public SessionHandle sessionHandle; public UwbAddress remoteDeviceAddress; @@ -1330,11 +1372,21 @@ public class UwbSessionManager implements INativeUwbManager.SessionNotification, () -> { int status = UwbUciConstants.STATUS_CODE_FAILED; synchronized (uwbSession.getWaitObj()) { + if (uwbSession.getNeedsQueryUwbsTimestamp()) { + // Query the UWBS timestamp and add the relative initiation time + // stored in the FiraOpenSessionParams, to get the absolute + // initiation time to be configured. + long uwbsTimestamp = + mUwbInjector.getUwbServiceCore().queryUwbsTimestampMicros(); + uwbSession.computeAbsoluteInitiationTime(uwbsTimestamp); + } + if (uwbSession.getNeedsAppConfigUpdate()) { uwbSession.resetNeedsAppConfigUpdate(); status = mConfigurationManager.setAppConfigurations( uwbSession.getSessionId(), uwbSession.getParams(), uwbSession.getChipId()); + uwbSession.resetAbsoluteInitiationTime(); if (status != UwbUciConstants.STATUS_CODE_OK) { mSessionNotificationManager.onRangingStartFailed( uwbSession, status); @@ -1809,6 +1861,7 @@ public class UwbSessionManager implements INativeUwbManager.SessionNotification, private int mStackSessionPriority; private boolean mSessionPriorityOverride = false; private boolean mNeedsAppConfigUpdate = false; + private boolean mNeedsQueryUwbsTimestamp = false; private UwbMulticastListUpdateStatus mMulticastListUpdateStatus; private final int mProfileType; private AlarmManager.OnAlarmListener mRangingResultErrorStreakTimerListener; @@ -2151,6 +2204,51 @@ public class UwbSessionManager implements INativeUwbManager.SessionNotification, mStackSessionPriority).build(); this.mNeedsAppConfigUpdate = true; } + + // When the UWBS supports Fira 2.0+ and the application has not configured an absolute + // UWB initiation time, we must fetch the UWBS timestamp (to compute the absolute time). + GenericSpecificationParams specificationParams = + mUwbInjector.getUwbServiceCore().getCachedSpecificationParams(mChipId); + if (specificationParams != null + && specificationParams.getFiraSpecificationParams() + .getMinPhyVersionSupported().getMajor() >= 2 + && firaOpenSessionParams.getAbsoluteInitiationTime() == 0) { + this.mNeedsQueryUwbsTimestamp = true; + } + } + + /** + * Compute {@code FiraOpenSessionParams.absolute_initiation_time}, by doing a sum of the + * UWBS Timestamp (in micro-seconds) and the relative + * {@code FiraOpenSessionParams.initiation_time} (in milli-seconds). This method should be + * called only for FiRa UCI ProtocolVersion >= 2.0 devices. + */ + public void computeAbsoluteInitiationTime(long uwbsTimestamp) { + if (this.mNeedsQueryUwbsTimestamp) { + FiraOpenSessionParams firaOpenSessionParams = (FiraOpenSessionParams) mParams; + this.mParams = ((FiraOpenSessionParams) mParams).toBuilder() + .setAbsoluteInitiationTime( + uwbsTimestamp + firaOpenSessionParams.getInitiationTime() * 1000) + .build(); + this.mNeedsAppConfigUpdate = true; + } + } + + /** + * Reset the computed {@code FiraOpenSessionParams.absolute_initiation_time}, only when it + * was computed and set by this class (it should not be reset when it was provided by the + * application}. + */ + public void resetAbsoluteInitiationTime() { + if (this.mNeedsQueryUwbsTimestamp) { + FiraOpenSessionParams firaOpenSessionParams = (FiraOpenSessionParams) mParams; + // Reset the absolute Initiation time, so that it's re-computed if start ranging is + // called in the future for this UWB session. + this.mParams = ((FiraOpenSessionParams) mParams).toBuilder() + .setAbsoluteInitiationTime(0) + .build(); + this.mNeedsQueryUwbsTimestamp = false; + } } public void updateFiraParamsOnReconfigure(FiraRangingReconfigureParams reconfigureParams) { @@ -2231,6 +2329,10 @@ public class UwbSessionManager implements INativeUwbManager.SessionNotification, this.mNeedsAppConfigUpdate = false; } + public boolean getNeedsQueryUwbsTimestamp() { + return this.mNeedsQueryUwbsTimestamp; + } + public Set<Long> getRemoteMacAddressList() { return mReceivedDataInfoMap.keySet(); } diff --git a/service/java/com/android/server/uwb/UwbSessionNotificationHelper.java b/service/java/com/android/server/uwb/UwbSessionNotificationHelper.java index 2562cecf..047bb3f2 100644 --- a/service/java/com/android/server/uwb/UwbSessionNotificationHelper.java +++ b/service/java/com/android/server/uwb/UwbSessionNotificationHelper.java @@ -46,6 +46,10 @@ public class UwbSessionNotificationHelper { case UwbUciConstants.REASON_ERROR_INVALID_RANGING_INTERVAL: case UwbUciConstants.REASON_ERROR_INVALID_STS_CONFIG: case UwbUciConstants.REASON_ERROR_INVALID_RFRAME_CONFIG: + case UwbUciConstants.REASON_ERROR_HUS_NOT_ENOUGH_SLOTS: + case UwbUciConstants.REASON_ERROR_HUS_CFP_PHASE_TOO_SHORT: + case UwbUciConstants.REASON_ERROR_HUS_CAP_PHASE_TOO_SHORT: + case UwbUciConstants.REASON_ERROR_HUS_OTHERS: rangingChangeReason = RangingChangeReason.BAD_PARAMETERS; break; case UwbUciConstants.REASON_REGULATION_UWB_OFF: diff --git a/service/java/com/android/server/uwb/UwbTestUtils.java b/service/java/com/android/server/uwb/UwbTestUtils.java index 7406e13e..6bd4ee03 100644 --- a/service/java/com/android/server/uwb/UwbTestUtils.java +++ b/service/java/com/android/server/uwb/UwbTestUtils.java @@ -88,7 +88,7 @@ public class UwbTestUtils { private static final long TEST_CURR_RANGING_INTERVAL = 100; private static final int TEST_RANGING_MEASURES_TYPE = RANGING_MEASUREMENT_TYPE_TWO_WAY; private static final int TEST_MAC_ADDRESS_MODE = 1; - private static final int TEST_STATUS = FiraParams.STATUS_CODE_OK; + public static final int TEST_STATUS = FiraParams.STATUS_CODE_OK; private static final int TEST_LOS = 0; private static final int TEST_DISTANCE = 101; private static final float TEST_AOA_AZIMUTH = 67; diff --git a/service/java/com/android/server/uwb/data/UwbUciConstants.java b/service/java/com/android/server/uwb/data/UwbUciConstants.java index cd2a95a8..6511c9e3 100644 --- a/service/java/com/android/server/uwb/data/UwbUciConstants.java +++ b/service/java/com/android/server/uwb/data/UwbUciConstants.java @@ -69,6 +69,10 @@ public class UwbUciConstants { public static final int REASON_ERROR_INVALID_RANGING_INTERVAL = 0x23; public static final int REASON_ERROR_INVALID_STS_CONFIG = 0x24; public static final int REASON_ERROR_INVALID_RFRAME_CONFIG = 0x25; + public static final int REASON_ERROR_HUS_NOT_ENOUGH_SLOTS = 0x26; + public static final int REASON_ERROR_HUS_CFP_PHASE_TOO_SHORT = 0x27; + public static final int REASON_ERROR_HUS_CAP_PHASE_TOO_SHORT = 0x28; + public static final int REASON_ERROR_HUS_OTHERS = 0x29; /* Vendor Specific reason codes */ public static final int REASON_REGULATION_UWB_OFF = UwbVendorReasonCodes.REASON_REGULATION_UWB_OFF; diff --git a/service/java/com/android/server/uwb/jni/NativeUwbManager.java b/service/java/com/android/server/uwb/jni/NativeUwbManager.java index b4454c7e..ddec1c90 100644 --- a/service/java/com/android/server/uwb/jni/NativeUwbManager.java +++ b/service/java/com/android/server/uwb/jni/NativeUwbManager.java @@ -464,6 +464,25 @@ public class NativeUwbManager { } } + /** + * Sets the Hybrid UWB Session Configuration + * + * @param sessionId : Primary session ID + * @param numberOfPhases : Number of secondary sessions + * @param updateTime : Absolute time in UWBS Time domain + * @param phaseList : list of secondary sessions which have been previously initialized and + * configured + * @param chipId : Identifier of UWB chip for multi-HAL devices + * @return Byte representing the status of the operation + */ + public byte setHybridSessionConfiguration(int sessionId, int numberOfPhases, byte[] updateTime, + byte[] phaseList, String chipId) { + synchronized (mNativeLock) { + return nativeSetHybridSessionConfigurations(sessionId, numberOfPhases, updateTime, + phaseList, chipId); + } + } + private native byte nativeSendData(int sessionId, byte[] address, short sequenceNum, byte[] appData, String chipId); @@ -522,4 +541,7 @@ public class NativeUwbManager { private native long nativeQueryUwbTimestamp(String chipId); private native int nativeGetSessionToken(int sessionId, String chipId); + + private native byte nativeSetHybridSessionConfigurations(int sessionId, int noOfPhases, + byte[] updateTime, byte[] phaseList, String chipId); } diff --git a/service/java/com/android/server/uwb/params/FiraEncoder.java b/service/java/com/android/server/uwb/params/FiraEncoder.java index be0c011b..f348d968 100644 --- a/service/java/com/android/server/uwb/params/FiraEncoder.java +++ b/service/java/com/android/server/uwb/params/FiraEncoder.java @@ -117,24 +117,36 @@ public class FiraEncoder extends TlvEncoder { tlvBufferBuilder.putInt(ConfigParam.RANGING_INTERVAL, params.getRangingIntervalMs()); } if (deviceRole != FiraParams.RANGING_DEVICE_DT_TAG) { - tlvBufferBuilder.putByte(ConfigParam.DEVICE_TYPE, (byte) params.getDeviceType()) - .putByte(ConfigParam.NUMBER_OF_CONTROLEES, - (byte) params.getDestAddressList().size()); + tlvBufferBuilder.putByte(ConfigParam.DEVICE_TYPE, (byte) params.getDeviceType()); + } + + if (isTimeScheduledTwrSession( + params.getScheduledMode(), params.getRangingRoundUsage())) { if (params.getDestAddressList().size() > 0) { ByteBuffer dstAddressList = ByteBuffer.allocate(1024); for (UwbAddress address : params.getDestAddressList()) { dstAddressList.put(getComputedMacAddress(address)); } - tlvBufferBuilder.putByteArray( - ConfigParam.DST_MAC_ADDRESS, dstAddressList.position(), - Arrays.copyOf(dstAddressList.array(), dstAddressList.position())); + tlvBufferBuilder + .putByte(ConfigParam.NUMBER_OF_CONTROLEES, + (byte) params.getDestAddressList().size()) + .putByteArray( + ConfigParam.DST_MAC_ADDRESS, dstAddressList.position(), + Arrays.copyOf(dstAddressList.array(), dstAddressList.position())); } } + if (params.getProtocolVersion().getMajor() >= 2) { // Initiation time Changed from 4 byte field to 8 byte field in version 2. if (deviceRole != FiraParams.RANGING_DEVICE_DT_TAG) { - tlvBufferBuilder.putLong(ConfigParam.UWB_INITIATION_TIME, - params.getInitiationTime()); + // For FiRa 2.0+ device, prefer to set the Absolute UWB Initiation time. + if (params.getAbsoluteInitiationTime() > 0) { + tlvBufferBuilder.putLong(ConfigParam.UWB_INITIATION_TIME, + params.getAbsoluteInitiationTime()); + } else { + tlvBufferBuilder.putLong(ConfigParam.UWB_INITIATION_TIME, + params.getInitiationTime()); + } } tlvBufferBuilder.putByte(ConfigParam.LINK_LAYER_MODE, (byte) params.getLinkLayerMode()) .putByte(ConfigParam.DATA_REPETITION_COUNT, @@ -145,10 +157,12 @@ public class FiraEncoder extends TlvEncoder { (byte) params.getApplicationDataEndpoint()); } else { if (deviceRole != FiraParams.RANGING_DEVICE_DT_TAG) { - tlvBufferBuilder.putInt(ConfigParam.UWB_INITIATION_TIME, - Math.toIntExact(params.getInitiationTime())); + tlvBufferBuilder + .putInt(ConfigParam.UWB_INITIATION_TIME, + Math.toIntExact(params.getInitiationTime())); } } + if ((stsConfig == FiraParams.STS_CONFIG_DYNAMIC_FOR_CONTROLEE_INDIVIDUAL_KEY) && (deviceType == FiraParams.RANGING_DEVICE_TYPE_CONTROLEE)) { tlvBufferBuilder.putInt(ConfigParam.SUB_SESSION_ID, params.getSubSessionId()); @@ -234,6 +248,18 @@ public class FiraEncoder extends TlvEncoder { return tlvBufferBuilder.build(); } + private boolean isTimeScheduledTwrSession(int scheduledMode, int rangingUsage) { + if (scheduledMode == FiraParams.TIME_SCHEDULED_RANGING) { + if (rangingUsage == FiraParams.RANGING_ROUND_USAGE_SS_TWR_DEFERRED_MODE + || rangingUsage == FiraParams.RANGING_ROUND_USAGE_DS_TWR_DEFERRED_MODE + || rangingUsage == FiraParams.RANGING_ROUND_USAGE_SS_TWR_NON_DEFERRED_MODE + || rangingUsage == FiraParams.RANGING_ROUND_USAGE_DS_TWR_NON_DEFERRED_MODE) { + return true; + } + } + return false; + } + private byte[] getUlTdoaDeviceId(int ulTdoaDeviceIdType, byte[] ulTdoaDeviceId) { if (ulTdoaDeviceIdType == FiraParams.UL_TDOA_DEVICE_ID_NONE) { // Device ID not included diff --git a/service/support_lib/src/com/google/uwb/support/fira/FiraHybridSessionConfig.java b/service/support_lib/src/com/google/uwb/support/fira/FiraHybridSessionConfig.java new file mode 100644 index 00000000..33bb0a1a --- /dev/null +++ b/service/support_lib/src/com/google/uwb/support/fira/FiraHybridSessionConfig.java @@ -0,0 +1,199 @@ +/* + * 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.google.uwb.support.fira; + +import android.os.PersistableBundle; + +import androidx.annotation.Nullable; + +import java.nio.ByteBuffer; +import java.nio.ByteOrder; +import java.util.ArrayList; +import java.util.List; + +/** + * Uwb Hybrid session configuration + */ +public class FiraHybridSessionConfig extends FiraParams { + private static final int BUNDLE_VERSION_1 = 1; + private static final int BUNDLE_VERSION_CURRENT = BUNDLE_VERSION_1; + private static final int UWB_HUS_PHASE_SIZE = 8; + + private final int mNumberOfPhases; + private final byte[] mUpdateTime; + private final List<FiraHybridSessionPhaseList> mPhaseList; + + public static final String KEY_BUNDLE_VERSION = "bundle_version"; + public static final String KEY_NUMBER_OF_PHASES = "number_of_phases"; + public static final String KEY_UPDATE_TIME = "update_time"; + public static final String KEY_PHASE_LIST = "phase_list"; + + @Override + public int getBundleVersion() { + return BUNDLE_VERSION_CURRENT; + } + + public int getNumberOfPhases() { + return mNumberOfPhases; + } + + public byte[] getUpdateTime() { + return mUpdateTime; + } + + public List<FiraHybridSessionPhaseList> getPhaseList() { + return mPhaseList; + } + + private FiraHybridSessionConfig(int numberOfPhases, byte[] updateTime, + List<FiraHybridSessionPhaseList> phaseList) { + mNumberOfPhases = numberOfPhases; + mUpdateTime = updateTime; + mPhaseList = phaseList; + } + + //TODO, move these utility methods to helper class + @Nullable + private static int[] byteArrayToIntArray(@Nullable byte[] bytes) { + if (bytes == null) { + return null; + } + + int[] values = new int[bytes.length]; + for (int i = 0; i < values.length; i++) { + values[i] = bytes[i]; + } + return values; + } + + @Nullable + private static byte[] intArrayToByteArray(@Nullable int[] values) { + if (values == null) { + return null; + } + byte[] bytes = new byte[values.length]; + for (int i = 0; i < values.length; i++) { + bytes[i] = (byte) values[i]; + } + return bytes; + } + + public PersistableBundle toBundle() { + PersistableBundle bundle = super.toBundle(); + bundle.putInt(KEY_BUNDLE_VERSION, getBundleVersion()); + bundle.putInt(KEY_NUMBER_OF_PHASES, mNumberOfPhases); + bundle.putIntArray(KEY_UPDATE_TIME, byteArrayToIntArray(mUpdateTime)); + + ByteBuffer buffer = ByteBuffer.allocate(mNumberOfPhases * UWB_HUS_PHASE_SIZE); + buffer.order(ByteOrder.LITTLE_ENDIAN); + for (FiraHybridSessionPhaseList phaseList : mPhaseList) { + buffer.putInt(phaseList.getSessionHandle()); + buffer.putShort(phaseList.getStartSlotIndex()); + buffer.putShort(phaseList.getEndSlotIndex()); + } + + bundle.putIntArray(KEY_PHASE_LIST, byteArrayToIntArray(buffer.array())); + return bundle; + } + + public static FiraHybridSessionConfig fromBundle(PersistableBundle bundle) { + switch (bundle.getInt(KEY_BUNDLE_VERSION)) { + case BUNDLE_VERSION_1: + return parseVersion1(bundle); + default: + throw new IllegalArgumentException("Invalid bundle version"); + } + } + + private static FiraHybridSessionConfig parseVersion1(PersistableBundle bundle) { + FiraHybridSessionConfig.Builder builder = new FiraHybridSessionConfig.Builder(); + + int numberOfPhases = bundle.getInt(KEY_NUMBER_OF_PHASES); + builder.setNumberOfPhases(numberOfPhases); + builder.setUpdateTime(intArrayToByteArray(bundle.getIntArray(KEY_UPDATE_TIME))); + + byte[] phaseByteArray = intArrayToByteArray(bundle.getIntArray(KEY_PHASE_LIST)); + ByteBuffer buffer = ByteBuffer.wrap(phaseByteArray); + buffer.order(ByteOrder.LITTLE_ENDIAN); + + for (int i = 0; i < numberOfPhases; i++) { + FiraHybridSessionPhaseList mFiraHybridSessionPhaseList = new FiraHybridSessionPhaseList( + buffer.getInt(), + buffer.getShort(), + buffer.getShort()); + builder.addPhaseList(mFiraHybridSessionPhaseList); + } + return builder.build(); + } + + /** Builder */ + public static class Builder { + private int mNumberOfPhases; + private byte[] mUpdateTime; + private final List<FiraHybridSessionPhaseList> mPhaseList = new ArrayList<>(); + + public FiraHybridSessionConfig.Builder setNumberOfPhases(int numberOfPhases) { + mNumberOfPhases = numberOfPhases; + return this; + } + + public FiraHybridSessionConfig.Builder setUpdateTime(byte[] updateTime) { + mUpdateTime = updateTime; + return this; + } + + public FiraHybridSessionConfig.Builder addPhaseList(FiraHybridSessionPhaseList phaseList) { + mPhaseList.add(phaseList); + return this; + } + + public FiraHybridSessionConfig build() { + if (mPhaseList.size() == 0) { + throw new IllegalStateException("No hybrid session phase list have been set"); + } + return new FiraHybridSessionConfig( + mNumberOfPhases, + mUpdateTime, + mPhaseList); + } + } + + /** Defines parameters for hybrid session's secondary phase list */ + public static class FiraHybridSessionPhaseList { + private final int mSessionHandle; + private final short mStartSlotIndex; + private final short mEndSlotIndex; + + public FiraHybridSessionPhaseList(int sessionHandle, short startSlotIndex, + short endSlotIndex) { + mSessionHandle = sessionHandle; + mStartSlotIndex = startSlotIndex; + mEndSlotIndex = endSlotIndex; + } + + public int getSessionHandle() { + return mSessionHandle; + } + + public short getStartSlotIndex() { + return mStartSlotIndex; + } + + public short getEndSlotIndex() { + return mEndSlotIndex; + } + } +} diff --git a/service/support_lib/src/com/google/uwb/support/fira/FiraOpenSessionParams.java b/service/support_lib/src/com/google/uwb/support/fira/FiraOpenSessionParams.java index c5b4fdfe..06f992a8 100644 --- a/service/support_lib/src/com/google/uwb/support/fira/FiraOpenSessionParams.java +++ b/service/support_lib/src/com/google/uwb/support/fira/FiraOpenSessionParams.java @@ -57,8 +57,10 @@ public class FiraOpenSessionParams extends FiraParams { private final List<UwbAddress> mDestAddressList; // FiRa 1.0: Relative time (in milli-seconds). - // FiRa 2.0: Absolute time in UWB time domain, as specified in CR-272 (in micro-seconds). + // FiRa 2.0: Relative time (in milli-seconds). private final long mInitiationTime; + // FiRa 2.0: Absolute time in UWB time domain, as specified in CR-272 (in micro-seconds). + private final long mAbsoluteInitiationTime; private final int mSlotDurationRstu; private final int mSlotsPerRangingRound; private final int mRangingIntervalMs; @@ -159,6 +161,7 @@ public class FiraOpenSessionParams extends FiraParams { private static final String KEY_DEVICE_ADDRESS = "device_address"; private static final String KEY_DEST_ADDRESS_LIST = "dest_address_list"; private static final String KEY_INITIATION_TIME_MS = "initiation_time_ms"; + private static final String KEY_ABSOLUTE_INITIATION_TIME_US = "absolute_initiation_time_us"; private static final String KEY_SLOT_DURATION_RSTU = "slot_duration_rstu"; private static final String KEY_SLOTS_PER_RANGING_ROUND = "slots_per_ranging_round"; private static final String KEY_RANGING_INTERVAL_MS = "ranging_interval_ms"; @@ -261,6 +264,7 @@ public class FiraOpenSessionParams extends FiraParams { UwbAddress deviceAddress, List<UwbAddress> destAddressList, long initiationTime, + long absoluteInitiationTime, int slotDurationRstu, int slotsPerRangingRound, int rangingIntervalMs, @@ -341,6 +345,7 @@ public class FiraOpenSessionParams extends FiraParams { mDeviceAddress = deviceAddress; mDestAddressList = destAddressList; mInitiationTime = initiationTime; + mAbsoluteInitiationTime = absoluteInitiationTime; mSlotDurationRstu = slotDurationRstu; mSlotsPerRangingRound = slotsPerRangingRound; mRangingIntervalMs = rangingIntervalMs; @@ -459,6 +464,10 @@ public class FiraOpenSessionParams extends FiraParams { return mInitiationTime; } + public long getAbsoluteInitiationTime() { + return mAbsoluteInitiationTime; + } + public int getSlotDurationRstu() { return mSlotDurationRstu; } @@ -812,6 +821,7 @@ public class FiraOpenSessionParams extends FiraParams { } bundle.putLong(KEY_INITIATION_TIME_MS, mInitiationTime); + bundle.putLong(KEY_ABSOLUTE_INITIATION_TIME_US, mAbsoluteInitiationTime); bundle.putInt(KEY_SLOT_DURATION_RSTU, mSlotDurationRstu); bundle.putInt(KEY_SLOTS_PER_RANGING_ROUND, mSlotsPerRangingRound); bundle.putInt(KEY_RANGING_INTERVAL_MS, mRangingIntervalMs); @@ -934,6 +944,7 @@ public class FiraOpenSessionParams extends FiraParams { // Changed from int to long. Look for int value, if long value not found to // maintain backwards compatibility. .setInitiationTime(bundle.getLong(KEY_INITIATION_TIME_MS)) + .setAbsoluteInitiationTime(bundle.getLong(KEY_ABSOLUTE_INITIATION_TIME_US)) .setSlotDurationRstu(bundle.getInt(KEY_SLOT_DURATION_RSTU)) .setSlotsPerRangingRound(bundle.getInt(KEY_SLOTS_PER_RANGING_ROUND)) .setRangingIntervalMs(bundle.getInt(KEY_RANGING_INTERVAL_MS)) @@ -1027,13 +1038,15 @@ public class FiraOpenSessionParams extends FiraParams { .setApplicationDataEndpoint(bundle.getInt( KEY_APPLICATION_DATA_ENDPOINT, APPLICATION_DATA_ENDPOINT_DEFAULT)); - if (builder.mDeviceRole.get() != RANGING_DEVICE_DT_TAG) { + if (builder.isTimeScheduledTwrSession()) { long[] destAddresses = bundle.getLongArray(KEY_DEST_ADDRESS_LIST); - List<UwbAddress> destAddressList = new ArrayList<>(); - for (long address : destAddresses) { - destAddressList.add(longToUwbAddress(address, addressByteLength)); + if (destAddresses != null) { + List<UwbAddress> destAddressList = new ArrayList<>(); + for (long address : destAddresses) { + destAddressList.add(longToUwbAddress(address, addressByteLength)); + } + builder.setDestAddressList(destAddressList); } - builder.setDestAddressList(destAddressList); } return builder.build(); } @@ -1067,6 +1080,7 @@ public class FiraOpenSessionParams extends FiraParams { /** UCI spec default: 0ms */ private long mInitiationTime = 0; + private long mAbsoluteInitiationTime = 0; /** UCI spec default: 2400 RSTU (2 ms). */ private int mSlotDurationRstu = 2400; @@ -1293,6 +1307,7 @@ public class FiraOpenSessionParams extends FiraParams { mDeviceAddress = builder.mDeviceAddress; mDestAddressList = builder.mDestAddressList; mInitiationTime = builder.mInitiationTime; + mAbsoluteInitiationTime = builder.mAbsoluteInitiationTime; mSlotDurationRstu = builder.mSlotDurationRstu; mSlotsPerRangingRound = builder.mSlotsPerRangingRound; mRangingIntervalMs = builder.mRangingIntervalMs; @@ -1377,6 +1392,7 @@ public class FiraOpenSessionParams extends FiraParams { mDeviceAddress = params.mDeviceAddress; mDestAddressList = params.mDestAddressList; mInitiationTime = params.mInitiationTime; + mAbsoluteInitiationTime = params.mAbsoluteInitiationTime; mSlotDurationRstu = params.mSlotDurationRstu; mSlotsPerRangingRound = params.mSlotsPerRangingRound; mRangingIntervalMs = params.mRangingIntervalMs; @@ -1500,14 +1516,26 @@ public class FiraOpenSessionParams extends FiraParams { /** * @param initiationTime UWB initiation time: * FiRa 1.0: Relative time (in milli-seconds). - * FiRa 2.0: Absolute time in UWB time domain, as specified in CR-272 - * (in micro-seconds). + * FiRa 2.0: Relative time (in milli-seconds). + * For a FiRa 2.0 device, the UWB Service will query the absolute UWBS timestamp + * and add the relative time (in milli-seconds) configured here, to compute the + * absolute time that will be configured in the UWB_INITIATION_TIME parameter. */ public FiraOpenSessionParams.Builder setInitiationTime(long initiationTime) { mInitiationTime = initiationTime; return this; } + /** + * @param absoluteInitiationTime Absolute UWB initiation time (in micro-seconds). This is + * applicable only for FiRa 2.0+ devices, as specified in CR-272. + */ + public FiraOpenSessionParams.Builder setAbsoluteInitiationTime( + long absoluteInitiationTime) { + mAbsoluteInitiationTime = absoluteInitiationTime; + return this; + } + public FiraOpenSessionParams.Builder setSlotDurationRstu(int slotDurationRstu) { mSlotDurationRstu = slotDurationRstu; return this; @@ -1927,7 +1955,7 @@ public class FiraOpenSessionParams extends FiraParams { // Make sure address length matches the address mode checkArgument(mDeviceAddress != null && mDeviceAddress.size() == addressByteLength); - if (mDeviceRole.get() != RANGING_DEVICE_DT_TAG) { + if (isTimeScheduledTwrSession()) { checkNotNull(mDestAddressList); for (UwbAddress destAddress : mDestAddressList) { checkArgument(destAddress != null @@ -2052,6 +2080,22 @@ public class FiraOpenSessionParams extends FiraParams { return this; } + /** + * Returns true when (RangingRoundUsage = 1, 2, 3, 4) and + * SCHEDULED_MODE == 0x01 (TIME_SCHEDULED_RANGING) + **/ + public boolean isTimeScheduledTwrSession() { + if (mScheduledMode == FiraParams.TIME_SCHEDULED_RANGING) { + if (mRangingRoundUsage == RANGING_ROUND_USAGE_SS_TWR_DEFERRED_MODE + || mRangingRoundUsage == RANGING_ROUND_USAGE_DS_TWR_DEFERRED_MODE + || mRangingRoundUsage == RANGING_ROUND_USAGE_SS_TWR_NON_DEFERRED_MODE + || mRangingRoundUsage == RANGING_ROUND_USAGE_DS_TWR_NON_DEFERRED_MODE) { + return true; + } + } + return false; + } + public FiraOpenSessionParams build() { checkAddress(); checkStsConfig(); @@ -2069,6 +2113,7 @@ public class FiraOpenSessionParams extends FiraParams { mDeviceAddress, mDestAddressList, mInitiationTime, + mAbsoluteInitiationTime, mSlotDurationRstu, mSlotsPerRangingRound, mRangingIntervalMs, diff --git a/service/support_lib/src/com/google/uwb/support/fira/FiraParams.java b/service/support_lib/src/com/google/uwb/support/fira/FiraParams.java index 55d65ede..a35d100b 100644 --- a/service/support_lib/src/com/google/uwb/support/fira/FiraParams.java +++ b/service/support_lib/src/com/google/uwb/support/fira/FiraParams.java @@ -233,6 +233,7 @@ public abstract class FiraParams extends Params { value = { CONTENTION_BASED_RANGING, TIME_SCHEDULED_RANGING, + HYBRID_SCHEDULED_RANGING, }) public @interface SchedulingMode {} @@ -240,6 +241,8 @@ public abstract class FiraParams extends Params { public static final int TIME_SCHEDULED_RANGING = 1; + public static final int HYBRID_SCHEDULED_RANGING = 2; + /** Ranging Time Struct */ @IntDef( value = { @@ -629,6 +632,10 @@ public abstract class FiraParams extends Params { STATE_CHANGE_REASON_CODE_ERROR_INVALID_RANGING_INTERVAL, STATE_CHANGE_REASON_CODE_ERROR_INVALID_STS_CONFIG, STATE_CHANGE_REASON_CODE_ERROR_INVALID_RFRAME_CONFIG, + STATE_CHANGE_REASON_CODE_ERROR_HUS_NOT_ENOUGH_SLOTS, + STATE_CHANGE_REASON_CODE_ERROR_HUS_CFP_PHASE_TOO_SHORT, + STATE_CHANGE_REASON_CODE_ERROR_HUS_CAP_PHASE_TOO_SHORT, + STATE_CHANGE_REASON_CODE_ERROR_HUS_OTHERS, }) public @interface StateChangeReasonCode {} @@ -640,6 +647,10 @@ public abstract class FiraParams extends Params { public static final int STATE_CHANGE_REASON_CODE_ERROR_INVALID_RANGING_INTERVAL = 0x23; public static final int STATE_CHANGE_REASON_CODE_ERROR_INVALID_STS_CONFIG = 0x24; public static final int STATE_CHANGE_REASON_CODE_ERROR_INVALID_RFRAME_CONFIG = 0x25; + public static final int STATE_CHANGE_REASON_CODE_ERROR_HUS_NOT_ENOUGH_SLOTS = 0x26; + public static final int STATE_CHANGE_REASON_CODE_ERROR_HUS_CFP_PHASE_TOO_SHORT = 0x27; + public static final int STATE_CHANGE_REASON_CODE_ERROR_HUS_CAP_PHASE_TOO_SHORT = 0x28; + public static final int STATE_CHANGE_REASON_CODE_ERROR_HUS_OTHERS = 0x29; /** Multicast controlee add/delete actions defined in UCI */ @IntDef( diff --git a/service/tests/src/com/android/server/uwb/UwbServiceCoreTest.java b/service/tests/src/com/android/server/uwb/UwbServiceCoreTest.java index 32c0cca6..66d1f054 100644 --- a/service/tests/src/com/android/server/uwb/UwbServiceCoreTest.java +++ b/service/tests/src/com/android/server/uwb/UwbServiceCoreTest.java @@ -482,11 +482,33 @@ public class UwbServiceCoreTest { mUwbServiceCore.setEnabled(true); mTestLooper.dispatchAll(); + // Verify that UWB adapter state is notified as DISABLED, and future calls to + // getAdapterState() also return the state as DISABLED. verify(mNativeUwbManager).doInitialize(); verify(mUwbCountryCode).setCountryCode(true); verify(cb).onAdapterStateChanged(UwbManager.AdapterStateCallback.STATE_DISABLED, StateChangeReason.SYSTEM_REGULATION); assertThat(mUwbServiceCore.getAdapterState()).isEqualTo(AdapterState.STATE_DISABLED); + + // Verify that a UWB ranging session cannot be opened or started. + SessionHandle sessionHandle = mock(SessionHandle.class); + IUwbRangingCallbacks rangingCallbacks = mock(IUwbRangingCallbacks.class); + + try { + mUwbServiceCore.openRanging( + TEST_ATTRIBUTION_SOURCE, sessionHandle, rangingCallbacks, + TEST_FIRA_OPEN_SESSION_PARAMS.build().toBundle(), TEST_DEFAULT_CHIP_ID); + fail(); + } catch (IllegalStateException e) { + // pass + } + + try { + mUwbServiceCore.startRanging(sessionHandle, new PersistableBundle()); + fail(); + } catch (IllegalStateException e) { + // pass + } } // Unit test for scenario when setting the country code (during UWB Enable) fails with a generic @@ -512,11 +534,33 @@ public class UwbServiceCoreTest { mUwbServiceCore.setEnabled(true); mTestLooper.dispatchAll(); + // Verify that UWB adapter state is notified as DISABLED, and future calls to + // getAdapterState() also return the state as DISABLED. verify(mNativeUwbManager).doInitialize(); verify(mUwbCountryCode).setCountryCode(true); verify(cb).onAdapterStateChanged(UwbManager.AdapterStateCallback.STATE_DISABLED, StateChangeReason.SYSTEM_POLICY); assertThat(mUwbServiceCore.getAdapterState()).isEqualTo(AdapterState.STATE_DISABLED); + + // Verify that a UWB ranging session cannot be opened or started. + SessionHandle sessionHandle = mock(SessionHandle.class); + IUwbRangingCallbacks rangingCallbacks = mock(IUwbRangingCallbacks.class); + + try { + mUwbServiceCore.openRanging( + TEST_ATTRIBUTION_SOURCE, sessionHandle, rangingCallbacks, + TEST_FIRA_OPEN_SESSION_PARAMS.build().toBundle(), TEST_DEFAULT_CHIP_ID); + fail(); + } catch (IllegalStateException e) { + // pass + } + + try { + mUwbServiceCore.startRanging(sessionHandle, new PersistableBundle()); + fail(); + } catch (IllegalStateException e) { + // pass + } } @Test @@ -542,7 +586,7 @@ public class UwbServiceCoreTest { // Enable again. should be ignored. enableUwb(VALID_COUNTRY_CODE); - verifyNoMoreInteractions(mNativeUwbManager, mUwbCountryCode, cb); + verifyNoMoreInteractions(mNativeUwbManager, cb); assertThat(mUwbServiceCore.getAdapterState()) .isEqualTo(AdapterState.STATE_ENABLED_INACTIVE); } @@ -805,7 +849,7 @@ public class UwbServiceCoreTest { // Disable again. should be ignored. disableUwb(); - verifyNoMoreInteractions(mNativeUwbManager, mUwbCountryCode, cb); + verifyNoMoreInteractions(mNativeUwbManager, cb); assertThat(mUwbServiceCore.getAdapterState()).isEqualTo(AdapterState.STATE_DISABLED); } @@ -1553,6 +1597,17 @@ public class UwbServiceCoreTest { verify(mUwbSessionManager).rangingRoundsUpdateDtTag(sessionHandle, bundle); } + @Test + public void testHybridSessionConfiguration() throws Exception { + enableUwbWithCountryCodeChangedCallback(); + + SessionHandle sessionHandle = mock(SessionHandle.class); + PersistableBundle bundle = new PersistableBundle(); + mUwbServiceCore.setHybridSessionConfiguration(sessionHandle, bundle); + + verify(mUwbSessionManager).setHybridSessionConfiguration(sessionHandle, bundle); + } + public CccSpecificationParams getTestCccSpecificationParams() { CccProtocolVersion[] protocolVersions = new CccProtocolVersion[] { diff --git a/service/tests/src/com/android/server/uwb/UwbServiceImplTest.java b/service/tests/src/com/android/server/uwb/UwbServiceImplTest.java index ada41ee2..7eb8d289 100644 --- a/service/tests/src/com/android/server/uwb/UwbServiceImplTest.java +++ b/service/tests/src/com/android/server/uwb/UwbServiceImplTest.java @@ -24,6 +24,7 @@ import static com.android.server.uwb.UwbServiceImpl.SETTINGS_SATELLITE_MODE_ENAB import static com.android.server.uwb.UwbServiceImpl.SETTINGS_SATELLITE_MODE_RADIOS; import static com.android.server.uwb.UwbSettingsStore.SETTINGS_TOGGLE_STATE; import static com.android.server.uwb.UwbTestUtils.MAX_DATA_SIZE; +import static com.android.server.uwb.UwbTestUtils.TEST_STATUS; import static com.google.common.truth.Truth.assertThat; import static com.google.uwb.support.fira.FiraParams.PACS_PROFILE_SERVICE_ID; @@ -819,6 +820,20 @@ public class UwbServiceImplTest { } @Test + public void testSetHybridSessionConfiguration() throws Exception { + assumeTrue(SdkLevel.isAtLeastV()); // Test should only run on V+ devices. + final SessionHandle sessionHandle = mock(SessionHandle.class); + final PersistableBundle parameters = new PersistableBundle(); + + when(mUwbServiceCore.setHybridSessionConfiguration(sessionHandle, parameters)) + .thenReturn(TEST_STATUS); + assertThat(mUwbServiceImpl.setHybridSessionConfiguration(sessionHandle, parameters)) + .isEqualTo(TEST_STATUS); + + verify(mUwbServiceCore).setHybridSessionConfiguration(sessionHandle, parameters); + } + + @Test public void testGetUwbActivityEnergyInfoAsync() throws Exception { final IOnUwbActivityEnergyInfoListener listener = mock( IOnUwbActivityEnergyInfoListener.class); diff --git a/service/tests/src/com/android/server/uwb/UwbSessionManagerTest.java b/service/tests/src/com/android/server/uwb/UwbSessionManagerTest.java index 35fc8807..7fe21ecd 100644 --- a/service/tests/src/com/android/server/uwb/UwbSessionManagerTest.java +++ b/service/tests/src/com/android/server/uwb/UwbSessionManagerTest.java @@ -122,6 +122,7 @@ import com.google.uwb.support.ccc.CccPulseShapeCombo; import com.google.uwb.support.ccc.CccSpecificationParams; import com.google.uwb.support.ccc.CccStartRangingParams; import com.google.uwb.support.dltdoa.DlTDoARangingRoundsUpdate; +import com.google.uwb.support.fira.FiraHybridSessionConfig; import com.google.uwb.support.fira.FiraOpenSessionParams; import com.google.uwb.support.fira.FiraParams; import com.google.uwb.support.fira.FiraProtocolVersion; @@ -139,6 +140,8 @@ import org.mockito.Captor; import org.mockito.Mock; import org.mockito.MockitoAnnotations; +import java.nio.ByteBuffer; +import java.nio.ByteOrder; import java.util.Arrays; import java.util.Collections; import java.util.EnumSet; @@ -177,7 +180,20 @@ public class UwbSessionManagerTest { private static final short DATA_SEQUENCE_NUM_1 = 2; private static final int DATA_TRANSMISSION_COUNT = 1; private static final int DATA_TRANSMISSION_COUNT_3 = 3; - + private static final FiraProtocolVersion FIRA_VERSION_1_0 = new FiraProtocolVersion(1, 0); + private static final FiraProtocolVersion FIRA_VERSION_1_1 = new FiraProtocolVersion(1, 1); + private static final FiraProtocolVersion FIRA_VERSION_2_0 = new FiraProtocolVersion(2, 0); + private static final FiraSpecificationParams FIRA_SPECIFICATION_PARAMS = + new FiraSpecificationParams.Builder() + .setMinPhyVersionSupported(FIRA_VERSION_1_0) + .setMaxPhyVersionSupported(FIRA_VERSION_1_1) + .setSupportedChannels(List.of(9)) + .setRangeDataNtfConfigCapabilities( + EnumSet.of( + HAS_RANGE_DATA_NTF_CONFIG_DISABLE, + HAS_RANGE_DATA_NTF_CONFIG_ENABLE)) + .build(); + private static final long UWBS_TIMESTAMP = 2000000L; private static final int HANDLE_ID = 12; private static final int MAX_RX_DATA_PACKETS_TO_STORE = 10; private static final int PID = Process.myPid(); @@ -229,14 +245,7 @@ public class UwbSessionManagerTest { }).when(mUwbInjector).runTaskOnSingleThreadExecutor(any(FutureTask.class), anyInt()); mSpecificationParamsBuilder = new GenericSpecificationParams.Builder() .setCccSpecificationParams(mCccSpecificationParams) - .setFiraSpecificationParams( - new FiraSpecificationParams.Builder() - .setSupportedChannels(List.of(9)) - .setRangeDataNtfConfigCapabilities( - EnumSet.of( - HAS_RANGE_DATA_NTF_CONFIG_DISABLE, - HAS_RANGE_DATA_NTF_CONFIG_ENABLE)) - .build()); + .setFiraSpecificationParams(FIRA_SPECIFICATION_PARAMS); when(mUwbServiceCore.getCachedSpecificationParams(any())).thenReturn( mSpecificationParamsBuilder.build()); when(mCccSpecificationParams.getMaxRangingSessionNumber()).thenReturn( @@ -1482,10 +1491,26 @@ public class UwbSessionManagerTest { } private Params setupFiraParams() { - return setupFiraParams(FiraParams.RANGING_DEVICE_ROLE_INITIATOR, Optional.empty()); + return setupFiraParams(FIRA_VERSION_2_0); + } + + private Params setupFiraParams(FiraProtocolVersion firaProtocolVersion) { + return setupFiraParams( + FiraParams.RANGING_DEVICE_ROLE_INITIATOR, + /* rangingRoundusageOptional = */ Optional.empty(), + firaProtocolVersion); + } + + private Params setupFiraParams( + int deviceRole, + Optional<Integer> rangingRoundUsageOptional) { + return setupFiraParams(deviceRole, rangingRoundUsageOptional, FIRA_VERSION_1_0); } - private Params setupFiraParams(int deviceRole, Optional<Integer> rangingRoundUsageOptional) { + private Params setupFiraParams( + int deviceRole, + Optional<Integer> rangingRoundUsageOptional, + FiraProtocolVersion firaProtocolVersion) { FiraOpenSessionParams.Builder paramsBuilder = new FiraOpenSessionParams.Builder() .setDeviceAddress(UwbAddress.fromBytes(new byte[] {(byte) 0x01, (byte) 0x02 })) .setVendorId(new byte[] { (byte) 0x00, (byte) 0x01 }) @@ -1493,7 +1518,7 @@ public class UwbSessionManagerTest { (byte) 0x04, (byte) 0x05, (byte) 0x06 }) .setDestAddressList(Arrays.asList( UWB_DEST_ADDRESS)) - .setProtocolVersion(new FiraProtocolVersion(1, 0)) + .setProtocolVersion(firaProtocolVersion) .setSessionId(10) .setSessionType(SESSION_TYPE_RANGING) .setDeviceType(FiraParams.RANGING_DEVICE_TYPE_CONTROLLER) @@ -2108,6 +2133,47 @@ public class UwbSessionManagerTest { } @Test + public void execStartRanging_absoluteInitiationTime() throws Exception { + // Setup the UWBS to return Fira version as 2.0. + FiraSpecificationParams firaSpecificationParams20 = new FiraSpecificationParams.Builder() + .setMinPhyVersionSupported(FIRA_VERSION_2_0) + .setMaxPhyVersionSupported(FIRA_VERSION_2_0) + .setSupportedChannels(FIRA_SPECIFICATION_PARAMS.getSupportedChannels()) + .setRangeDataNtfConfigCapabilities( + FIRA_SPECIFICATION_PARAMS.getRangeDataNtfConfigCapabilities()) + .build(); + GenericSpecificationParams specificationParams = new GenericSpecificationParams.Builder() + .setCccSpecificationParams(mCccSpecificationParams) + .setFiraSpecificationParams(firaSpecificationParams20) + .build(); + when(mUwbServiceCore.getCachedSpecificationParams(any())).thenReturn(specificationParams); + + Params params = setupFiraParams(new FiraProtocolVersion(2, 0)); + UwbSession uwbSession = prepareExistingUwbSession(params); + + // Setup for start ranging. + doReturn(UwbUciConstants.UWB_SESSION_STATE_IDLE, UwbUciConstants.UWB_SESSION_STATE_ACTIVE) + .when(uwbSession).getSessionState(); + when(mNativeUwbManager.startRanging(eq(TEST_SESSION_ID), anyString())) + .thenReturn((byte) UwbUciConstants.STATUS_CODE_OK); + when(mUwbServiceCore.queryUwbsTimestampMicros()).thenReturn(UWBS_TIMESTAMP); + when(mUwbConfigurationManager.setAppConfigurations(anyInt(), any(), anyString())) + .thenReturn(UwbUciConstants.STATUS_CODE_OK); + + mUwbSessionManager.startRanging(uwbSession.getSessionHandle(), params); + mTestLooper.dispatchAll(); + + // Verify that queryUwbsTimestampMicros() is called. Currently unable to verify that the + // FiraOpenSessionParams is changed and the absoluteInitiationTime field set in it, as + // equals() is not implemented. + verify(mUwbServiceCore).queryUwbsTimestampMicros(); + verify(mUwbConfigurationManager).setAppConfigurations(anyInt(), any(), any()); + verify(mUwbSessionNotificationManager).onRangingStarted(eq(uwbSession), any()); + verify(mUwbMetrics).longRangingStartEvent( + eq(uwbSession), eq(UwbUciConstants.STATUS_CODE_OK)); + } + + @Test public void execStartRanging_onRangeDataNotification() throws Exception { UwbSession uwbSession = prepareExistingUwbSession(); // set up for start ranging @@ -3275,6 +3341,56 @@ public class UwbSessionManagerTest { } @Test + public void testSetHybridSessionConfiguration() throws Exception { + UwbSession uwbSession = prepareExistingUwbSession(); + FiraHybridSessionConfig.Builder mockFiraBuilder = + mock(FiraHybridSessionConfig.Builder.class); + + SessionHandle sessionHandle = mock(SessionHandle.class); + SessionHandle sessionHandle1 = mock(SessionHandle.class); + SessionHandle sessionHandle2 = mock(SessionHandle.class); + byte[] updateTime = {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}; + int noOfPhases = 2; + short startSlotIndex1 = 0x01, endSlotIndex1 = 0x34; + short startSlotIndex2 = 0x37, endSlotIndex2 = 0x64; + FiraHybridSessionConfig params = new FiraHybridSessionConfig.Builder() + .setNumberOfPhases(noOfPhases) + .setUpdateTime(updateTime) + .addPhaseList(new FiraHybridSessionConfig.FiraHybridSessionPhaseList( + sessionHandle1.getId(), startSlotIndex1, endSlotIndex1)) + .addPhaseList(new FiraHybridSessionConfig.FiraHybridSessionPhaseList( + sessionHandle2.getId(), startSlotIndex2, endSlotIndex2)) + .build(); + + // Setup the expected byte-array for the Hybrid configuration. + ByteBuffer expectedHybridConfigBytes = ByteBuffer.allocate(noOfPhases * 8); + expectedHybridConfigBytes.order(ByteOrder.LITTLE_ENDIAN); + + expectedHybridConfigBytes.putInt(sessionHandle1.getId()); + expectedHybridConfigBytes.putShort(startSlotIndex1); + expectedHybridConfigBytes.putShort(endSlotIndex1); + expectedHybridConfigBytes.putInt(sessionHandle2.getId()); + expectedHybridConfigBytes.putShort(startSlotIndex2); + expectedHybridConfigBytes.putShort(endSlotIndex2); + + when(mNativeUwbManager.setHybridSessionConfiguration( + eq(uwbSession.getSessionId()), eq(noOfPhases), eq(updateTime), + eq(expectedHybridConfigBytes.array()), anyString())) + .thenReturn((byte) UwbUciConstants.STATUS_CODE_OK); + assertThat(mUwbSessionManager.setHybridSessionConfiguration(uwbSession.getSessionHandle(), + params.toBundle())) + .isEqualTo(UwbUciConstants.STATUS_CODE_OK); + } + + @Test + public void testSetHybridSessionConfiguration_whenUwbSessionDoesNotExist() throws Exception { + SessionHandle mockSessionHandle = mock(SessionHandle.class); + assertThrows(IllegalStateException.class, + () -> mUwbSessionManager.setHybridSessionConfiguration(mockSessionHandle, + mock(PersistableBundle.class))); + } + + @Test public void deInitSession_notExistedSession() { doReturn(false).when(mUwbSessionManager).isExistedSession(any()); diff --git a/service/tests/src/com/android/server/uwb/UwbSessionNotificationManagerTest.java b/service/tests/src/com/android/server/uwb/UwbSessionNotificationManagerTest.java index d89566e7..dabb74bb 100644 --- a/service/tests/src/com/android/server/uwb/UwbSessionNotificationManagerTest.java +++ b/service/tests/src/com/android/server/uwb/UwbSessionNotificationManagerTest.java @@ -460,7 +460,11 @@ public class UwbSessionNotificationManagerTest { UwbUciConstants.REASON_ERROR_MAC_ADDRESS_MODE_NOT_SUPPORTED, UwbUciConstants.REASON_ERROR_INVALID_RANGING_INTERVAL, UwbUciConstants.REASON_ERROR_INVALID_STS_CONFIG, - UwbUciConstants.REASON_ERROR_INVALID_RFRAME_CONFIG); + UwbUciConstants.REASON_ERROR_INVALID_RFRAME_CONFIG, + UwbUciConstants.REASON_ERROR_HUS_NOT_ENOUGH_SLOTS, + UwbUciConstants.REASON_ERROR_HUS_CFP_PHASE_TOO_SHORT, + UwbUciConstants.REASON_ERROR_HUS_CAP_PHASE_TOO_SHORT, + UwbUciConstants.REASON_ERROR_HUS_OTHERS); for (int reasonCode : reasonCodes) { clearInvocations(mIUwbRangingCallbacks); mUwbSessionNotificationManager.onRangingStoppedWithUciReasonCode(mUwbSession, diff --git a/service/tests/src/com/android/server/uwb/params/FiraEncoderTest.java b/service/tests/src/com/android/server/uwb/params/FiraEncoderTest.java index ea599d3f..e3295321 100644 --- a/service/tests/src/com/android/server/uwb/params/FiraEncoderTest.java +++ b/service/tests/src/com/android/server/uwb/params/FiraEncoderTest.java @@ -97,6 +97,11 @@ public class FiraEncoderTest { .setLinkLayerMode(1) .setApplicationDataEndpoint(1); + private static final FiraOpenSessionParams.Builder + TEST_FIRA_OPEN_SESSION_PARAMS_V_2_0_ABSOLUTE_INITIATION_TIME = + new FiraOpenSessionParams.Builder(TEST_FIRA_OPEN_SESSION_PARAMS_V_2_0) + .setAbsoluteInitiationTime(20000000L); + private static final FiraRangingReconfigureParams.Builder TEST_FIRA_RECONFIGURE_PARAMS = new FiraRangingReconfigureParams.Builder() .setBlockStrideLength(6) @@ -189,6 +194,7 @@ public class FiraEncoderTest { private static final String DST_MAC_ADDRESS_TLV = "07020406"; private static final String UWB_INITIATION_TIME_TLV = "2B0400000000"; private static final String UWB_INITIATION_TIME_2_0_TLV = "2B08E803000000000000"; + private static final String ABSOLUTE_UWB_INITIATION_TIME_2_0_TLV = "2B08002D310100000000"; private static final String VENDOR_ID_TLV = "27020578"; private static final String STATIC_STS_IV_TLV = "28061A5577477E7D"; private static final String RANGE_DATA_NTF_AOA_BOUND_TLV = "1D0807D59E4707D56022"; @@ -203,6 +209,7 @@ public class FiraEncoderTest { private byte[] mFiraOpenSessionTlvUtTag; private byte[] mFiraSessionv11TlvData; private byte[] mFiraSessionv20TlvData; + private byte[] mFiraSessionv20AbsoluteInitiationTimeTlvData; @Before public void setUp() { @@ -218,7 +225,7 @@ public class FiraEncoderTest { "01010002010003010004010906020604080260090B01000C01030D01010E01010F02" + "00001002204E11010412010313010014010A1501021601001701011A01011B01191C" + "01001F01002201012301002401002501322601002901012A0200002C01002D01002E" - + "01012F010131010032020000350101000101050101070206042B0400000000270278" + + "01012F0101310100320200003501010001012B0400000000270278" + "0528061A5577477E7D3304B004000034041E0000003803010B0A390101"); } else { mFiraSessionv11TlvData = UwbUtil.getByteArray(RANGING_ROUND_USAGE_SS_TWR_TLV @@ -241,7 +248,8 @@ public class FiraEncoderTest { + UWB_INITIATION_TIME_TLV + VENDOR_ID_TLV + STATIC_STS_IV_TLV + RANGE_DATA_NTF_AOA_BOUND_TLV); - mFiraSessionv20TlvData = UwbUtil.getByteArray(RANGING_ROUND_USAGE_SS_TWR_TLV + mFiraSessionv20TlvData = UwbUtil.getByteArray( + RANGING_ROUND_USAGE_SS_TWR_TLV + STS_CONFIG_STATIC_TLV + MULTI_NODE_MODE_UNICAST_TLV + CHANNEL_NUMBER_TLV + DEVICE_MAC_ADDRESS_TLV + SLOT_DURATION_TLV + MAC_FCS_TYPE_TLV + RANGING_ROUND_CONTROL_TLV + AOA_RESULT_REQ_TLV @@ -266,6 +274,32 @@ public class FiraEncoderTest { + VENDOR_ID_TLV + STATIC_STS_IV_TLV + RANGE_DATA_NTF_AOA_BOUND_TLV); + mFiraSessionv20AbsoluteInitiationTimeTlvData = UwbUtil.getByteArray( + RANGING_ROUND_USAGE_SS_TWR_TLV + + STS_CONFIG_STATIC_TLV + MULTI_NODE_MODE_UNICAST_TLV + CHANNEL_NUMBER_TLV + + DEVICE_MAC_ADDRESS_TLV + SLOT_DURATION_TLV + MAC_FCS_TYPE_TLV + + RANGING_ROUND_CONTROL_TLV + AOA_RESULT_REQ_TLV + + RANGE_DATA_NTF_CONFIG_AOA_LEVEL_TLV + RANGE_DATA_NTF_PROXIMITY_NEAR_TLV + + RANGE_DATA_NTF_PROXIMITY_FAR_TLV + DEVICE_ROLE_RESPONDER_TLV + + RFRAME_CONFIG_TLV + + RSSI_REPORTING_TLV + PREAMBLE_CODE_INDEX_TLV + SFD_ID_TLV + + PSDU_DATA_RATE_TLV + PREAMBLE_DURATION_TLV + RANGING_TIME_STRUCT_TLV + + SLOTS_PER_RR_TLV + TX_ADAPTIVE_PAYLOAD_POWER_TLV + + PRF_MODE_TLV + SCHEDULED_MODE_TIME_SCHEDULED_TLV + KEY_ROTATION_TLV + + KEY_ROTATION_RATE_TLV + + SESSION_PRIORITY_TLV + MAC_ADDRESS_MODE_TLV + NUMBER_OF_STS_SEGMENTS_TLV + + MAX_RR_RETRY_TLV + HOPPING_MODE_TLV + BLOCK_STRIDE_LENGTH_TLV + + RESULT_REPORT_CONFIG_TLV + IN_BAND_TERMINATION_ATTEMPT_COUNT_TLV + + BPRF_PHR_DATA_RATE_TLV + MAX_NUMBER_OF_MEASUREMENTS_TLV + STS_LENGTH_TLV + + RANGING_INTERVAL_TLV + DEVICE_TYPE_CONTROLLER_TLV + NUMBER_OF_CONTROLEES_TLV + + DST_MAC_ADDRESS_TLV + ABSOLUTE_UWB_INITIATION_TIME_2_0_TLV + + LINK_LAYER_MODE_CONNECTIONLESS_DATA_TLV + + DATA_TRANSFER_STATUS_NTF_CONFIG + + SESSION_DATA_TRANSFER_STATUS_NTF_CONFIG + + APPLICATION_DATA_ENDPOINT_SECURE_COMPONENT_TLV + + VENDOR_ID_TLV + STATIC_STS_IV_TLV + + RANGE_DATA_NTF_AOA_BOUND_TLV); + mFiraOpenSessionTlvUtTag = UwbUtil.getByteArray( RANGING_ROUND_USAGE_UL_TDOA_TLV + STS_CONFIG_STATIC_TLV + MULTI_NODE_MODE_UNICAST_TLV + CHANNEL_NUMBER_TLV @@ -283,7 +317,7 @@ public class FiraEncoderTest { + MAX_RR_RETRY_TLV + HOPPING_MODE_TLV + BLOCK_STRIDE_LENGTH_TLV + RESULT_REPORT_CONFIG_TLV + IN_BAND_TERMINATION_ATTEMPT_COUNT_TLV + BPRF_PHR_DATA_RATE_TLV + MAX_NUMBER_OF_MEASUREMENTS_TLV + STS_LENGTH_TLV - + DEVICE_TYPE_CONTROLLER_TLV + NUMBER_OF_CONTROLEES_TLV + DST_MAC_ADDRESS_TLV + + DEVICE_TYPE_CONTROLLER_TLV + UWB_INITIATION_TIME_TLV + VENDOR_ID_TLV + STATIC_STS_IV_TLV + UL_TDOA_TX_INTERVAL_TLV + UL_TDOA_RANDOM_WINDOW_TLV + UL_TDOA_DEVICE_ID_TLV + UL_TDOA_TX_TIMESTAMP_TLV); @@ -302,11 +336,20 @@ public class FiraEncoderTest { // Test FiRa v2.0 Params if (SdkLevel.isAtLeastU()) { + // Test the default Fira v2.0 OpenSessionParams. params = TEST_FIRA_OPEN_SESSION_PARAMS_V_2_0.build(); tlvs = mFiraEncoder.getTlvBuffer(params); assertThat(tlvs.getNoOfParams()).isEqualTo(49); assertThat(tlvs.getByteArray()).isEqualTo(mFiraSessionv20TlvData); + + // Test the Fira v2.0 OpenSessionParams with ABSOLUTE_INITIATION_TIME set. + params = TEST_FIRA_OPEN_SESSION_PARAMS_V_2_0_ABSOLUTE_INITIATION_TIME.build(); + tlvs = mFiraEncoder.getTlvBuffer(params); + + assertThat(tlvs.getNoOfParams()).isEqualTo(49); + assertThat(tlvs.getByteArray()).isEqualTo(mFiraSessionv20AbsoluteInitiationTimeTlvData); + } } @@ -344,7 +387,7 @@ public class FiraEncoderTest { FiraOpenSessionParams params = TEST_FIRA_UT_TAG_OPEN_SESSION_PARAM.build(); TlvBuffer tlvs = mFiraEncoder.getTlvBuffer(params); - assertThat(tlvs.getNoOfParams()).isEqualTo(47); + assertThat(tlvs.getNoOfParams()).isEqualTo(45); assertThat(tlvs.getByteArray()).isEqualTo(mFiraOpenSessionTlvUtTag); } @@ -421,7 +464,7 @@ public class FiraEncoderTest { .setDeviceRole(RANGING_DEVICE_DT_TAG) .setDeviceAddress(UwbAddress.fromBytes(new byte[]{0x4, 0x6})) .setDestAddressList(Arrays.asList(UwbAddress.fromBytes( - new byte[]{0x4, 0x6}))) + new byte[]{0x4, 0x6}))) // Should be ignored .setMultiNodeMode(MULTI_NODE_MODE_ONE_TO_MANY) .setRangingRoundUsage(RANGING_ROUND_USAGE_DL_TDOA) .setRframeConfig(RFRAME_CONFIG_SP1) @@ -463,6 +506,7 @@ public class FiraEncoderTest { .setDeviceType(RANGING_DEVICE_TYPE_CONTROLLER) .setDeviceRole(RANGING_DEVICE_ROLE_INITIATOR) .setDeviceAddress(UwbAddress.fromBytes(new byte[]{0x4, 0x6})) + // DestAddressList should be ignored and not set in the TLV params. .setDestAddressList(Arrays.asList(UwbAddress.fromBytes(new byte[]{0x4, 0x6}))) .setMultiNodeMode(MULTI_NODE_MODE_ONE_TO_MANY) .setRangingRoundUsage(RANGING_ROUND_USAGE_SS_TWR_DEFERRED_MODE) @@ -519,8 +563,6 @@ public class FiraEncoderTest { + STS_LENGTH_TLV + RANGING_INTERVAL_TLV + DEVICE_TYPE_CONTROLLER_TLV - + NUMBER_OF_CONTROLEES_TLV - + DST_MAC_ADDRESS_TLV + UWB_INITIATION_TIME_2_0_TLV + LINK_LAYER_MODE_BYPASS_TLV + DATA_TRANSFER_STATUS_NTF_CONFIG @@ -532,7 +574,7 @@ public class FiraEncoderTest { + CAP_SIZE_RANGE_DEFAULT_TLV); TlvBuffer tlvs = mFiraEncoder.getTlvBuffer(params); - assertThat(tlvs.getNoOfParams()).isEqualTo(50); + assertThat(tlvs.getNoOfParams()).isEqualTo(48); assertThat(tlvs.getByteArray()).isEqualTo(expected_data); } } diff --git a/service/uci/jni/src/uci_jni_android_new.rs b/service/uci/jni/src/uci_jni_android_new.rs index 2d42c142..fd5bf32d 100644 --- a/service/uci/jni/src/uci_jni_android_new.rs +++ b/service/uci/jni/src/uci_jni_android_new.rs @@ -35,8 +35,8 @@ use jni::JNIEnv; use log::{debug, error}; use uwb_core::error::{Error, Result}; use uwb_core::params::{ - AppConfigTlv, CountryCode, RawAppConfigTlv, RawUciMessage, - SessionUpdateDtTagRangingRoundsResponse, SetAppConfigResponse, + AppConfigTlv, CountryCode, PhaseList, RawAppConfigTlv, RawUciMessage, + SessionUpdateDtTagRangingRoundsResponse, SetAppConfigResponse, UpdateTime, }; use uwb_uci_packets::{ AppConfigTlvType, CapTlv, Controlee, Controlee_V2_0_16_Byte_Version, @@ -385,6 +385,82 @@ fn native_set_app_configurations( uci_manager.session_set_app_config(session_id as u32, tlvs) } +fn parse_hybrid_config_phase_list_vec( + number_of_phases: usize, + byte_array: &[u8], +) -> Result<Vec<PhaseList>> { + let mut parsed_phase_lists_len = 0; + let received_phase_list_len = byte_array.len(); + let mut phase_lists = Vec::with_capacity(number_of_phases); + // The PhaseList consists of session handle as u32 in 4 bytes, Start Slot Index as u16 + // in 2 byte and End Slot Index as u16 in 2 bytes + const PHASE_LIST_SIZE: usize = 8; + for chunk in byte_array.chunks_exact(PHASE_LIST_SIZE) { + let phase_list = PhaseList::parse(chunk).map_err(|_| Error::BadParameters)?; + parsed_phase_lists_len += PHASE_LIST_SIZE; + phase_lists.push(phase_list); + } + + if parsed_phase_lists_len != received_phase_list_len { + return Err(Error::BadParameters); + } + Ok(phase_lists) +} + +/// Set hybrid session configurations. Return null JObject if failed. +#[no_mangle] +pub extern "system" fn Java_com_android_server_uwb_jni_NativeUwbManager_nativeSetHybridSessionConfigurations( + env: JNIEnv, + obj: JObject, + session_id: jint, + number_of_phases: jint, + update_time: jbyteArray, + phase_list: jbyteArray, + chip_id: JString, +) -> jbyte { + debug!("{}: enter", function_name!()); + byte_result_helper( + native_set_hybrid_session_configurations( + env, + obj, + session_id, + number_of_phases, + update_time, + phase_list, + chip_id, + ), + function_name!(), + ) +} + +fn native_set_hybrid_session_configurations( + env: JNIEnv, + obj: JObject, + session_id: jint, + number_of_phases: jint, + update_time: jbyteArray, + phase_list: jbyteArray, + chip_id: JString, +) -> Result<()> { + let uci_manager = Dispatcher::get_uci_manager(env, obj, chip_id)?; + let phase_list_bytes = + env.convert_byte_array(phase_list).map_err(|_| Error::ForeignFunctionInterface)?; + let phase_list_vec = + parse_hybrid_config_phase_list_vec(number_of_phases as usize, &phase_list_bytes)?; + + let update_time_bytes = + env.convert_byte_array(update_time).map_err(|_| Error::ForeignFunctionInterface)?; + let update_time_array: [u8; 8] = + TryFrom::try_from(&update_time_bytes[..]).map_err(|_| Error::BadParameters)?; + + uci_manager.session_set_hybrid_config( + session_id as u32, + number_of_phases as u8, + UpdateTime::new(&update_time_array).unwrap(), + phase_list_vec, + ) +} + fn create_get_config_response(tlvs: Vec<AppConfigTlv>, env: JNIEnv) -> Result<jbyteArray> { let tlv_data_class = env.find_class(TLV_DATA_CLASS).map_err(|_| Error::ForeignFunctionInterface)?; |