diff options
| -rw-r--r-- | core/java/android/uwb/AngleMeasurement.java | 90 | ||||
| -rw-r--r-- | core/java/android/uwb/AngleOfArrivalMeasurement.java | 55 | ||||
| -rw-r--r-- | core/java/android/uwb/DistanceMeasurement.java | 90 | ||||
| -rw-r--r-- | core/java/android/uwb/RangingMeasurement.java | 126 | ||||
| -rw-r--r-- | core/java/android/uwb/RangingParams.java | 238 | ||||
| -rw-r--r-- | core/java/android/uwb/RangingReport.java | 59 | ||||
| -rw-r--r-- | core/java/android/uwb/RangingSession.java | 148 | ||||
| -rw-r--r-- | core/java/android/uwb/UwbManager.java | 29 |
8 files changed, 805 insertions, 30 deletions
diff --git a/core/java/android/uwb/AngleMeasurement.java b/core/java/android/uwb/AngleMeasurement.java index 7ef145cfe470..c3e2ecc9e0f3 100644 --- a/core/java/android/uwb/AngleMeasurement.java +++ b/core/java/android/uwb/AngleMeasurement.java @@ -27,6 +27,16 @@ import android.annotation.FloatRange; * @hide */ public final class AngleMeasurement { + private final double mRadians; + private final double mErrorRadians; + private final double mConfidenceLevel; + + private AngleMeasurement(double radians, double errorRadians, double confidenceLevel) { + mRadians = radians; + mErrorRadians = errorRadians; + mConfidenceLevel = confidenceLevel; + } + /** * Angle measurement in radians * @@ -34,7 +44,7 @@ public final class AngleMeasurement { */ @FloatRange(from = -Math.PI, to = +Math.PI) public double getRadians() { - throw new UnsupportedOperationException(); + return mRadians; } /** @@ -46,7 +56,7 @@ public final class AngleMeasurement { */ @FloatRange(from = 0.0, to = +Math.PI) public double getErrorRadians() { - throw new UnsupportedOperationException(); + return mErrorRadians; } /** @@ -60,6 +70,80 @@ public final class AngleMeasurement { */ @FloatRange(from = 0.0, to = 1.0) public double getConfidenceLevel() { - throw new UnsupportedOperationException(); + return mConfidenceLevel; + } + + /** + * Builder class for {@link AngleMeasurement}. + */ + public static final class Builder { + private double mRadians = Double.NaN; + private double mErrorRadians = Double.NaN; + private double mConfidenceLevel = Double.NaN; + + /** + * Set the angle in radians + * + * @param radians angle in radians + * @throws IllegalArgumentException if angle exceeds allowed limits of [-Math.PI, +Math.PI] + */ + public Builder setRadians(double radians) { + if (radians < -Math.PI || radians > Math.PI) { + throw new IllegalArgumentException("Invalid radians: " + radians); + } + mRadians = radians; + return this; + } + + /** + * Set the angle error in radians + * + * @param errorRadians error of the angle in radians + * @throws IllegalArgumentException if the error exceeds the allowed limits of [0, +Math.PI] + */ + public Builder setErrorRadians(double errorRadians) { + if (errorRadians < 0.0 || errorRadians > Math.PI) { + throw new IllegalArgumentException( + "Invalid error radians: " + errorRadians); + } + mErrorRadians = errorRadians; + return this; + } + + /** + * Set the angle confidence level + * + * @param confidenceLevel level of confidence of the angle measurement + * @throws IllegalArgumentException if the error exceeds the allowed limits of [0.0, 1.0] + */ + public Builder setConfidenceLevel(double confidenceLevel) { + if (confidenceLevel < 0.0 || confidenceLevel > 1.0) { + throw new IllegalArgumentException( + "Invalid confidence level: " + confidenceLevel); + } + mConfidenceLevel = confidenceLevel; + return this; + } + + /** + * Build the {@link AngleMeasurement} object + * + * @throws IllegalStateException if angle, error, or confidence values are missing + */ + public AngleMeasurement build() { + if (Double.isNaN(mRadians)) { + throw new IllegalStateException("Angle is not set"); + } + + if (Double.isNaN(mErrorRadians)) { + throw new IllegalStateException("Angle error is not set"); + } + + if (Double.isNaN(mConfidenceLevel)) { + throw new IllegalStateException("Angle confidence level is not set"); + } + + return new AngleMeasurement(mRadians, mErrorRadians, mConfidenceLevel); + } } } diff --git a/core/java/android/uwb/AngleOfArrivalMeasurement.java b/core/java/android/uwb/AngleOfArrivalMeasurement.java index 030d5299d53b..a7b5eae728cb 100644 --- a/core/java/android/uwb/AngleOfArrivalMeasurement.java +++ b/core/java/android/uwb/AngleOfArrivalMeasurement.java @@ -25,6 +25,15 @@ import android.annotation.Nullable; * @hide */ public final class AngleOfArrivalMeasurement { + private final AngleMeasurement mAzimuthAngleMeasurement; + private final AngleMeasurement mAltitudeAngleMeasurement; + + private AngleOfArrivalMeasurement(@NonNull AngleMeasurement azimuthAngleMeasurement, + @Nullable AngleMeasurement altitudeAngleMeasurement) { + mAzimuthAngleMeasurement = azimuthAngleMeasurement; + mAltitudeAngleMeasurement = altitudeAngleMeasurement; + } + /** * Azimuth angle measurement * <p>Azimuth {@link AngleMeasurement} of remote device in horizontal coordinate system, this is @@ -41,7 +50,7 @@ public final class AngleOfArrivalMeasurement { */ @NonNull public AngleMeasurement getAzimuth() { - throw new UnsupportedOperationException(); + return mAzimuthAngleMeasurement; } /** @@ -58,6 +67,48 @@ public final class AngleOfArrivalMeasurement { */ @Nullable public AngleMeasurement getAltitude() { - throw new UnsupportedOperationException(); + return mAltitudeAngleMeasurement; + } + + /** + * Builder class for {@link AngleOfArrivalMeasurement}. + */ + public static final class Builder { + private AngleMeasurement mAzimuthAngleMeasurement = null; + private AngleMeasurement mAltitudeAngleMeasurement = null; + + /** + * Set the azimuth angle + * + * @param azimuthAngle azimuth angle + */ + public Builder setAzimuthAngleMeasurement(@NonNull AngleMeasurement azimuthAngle) { + mAzimuthAngleMeasurement = azimuthAngle; + return this; + } + + /** + * Set the altitude angle + * + * @param altitudeAngle altitude angle + */ + public Builder setAltitudeAngleMeasurement(@NonNull AngleMeasurement altitudeAngle) { + mAltitudeAngleMeasurement = altitudeAngle; + return this; + } + + /** + * Build the {@link AngleOfArrivalMeasurement} object + * + * @throws IllegalStateException if the required azimuth angle is not provided + */ + public AngleOfArrivalMeasurement build() { + if (mAzimuthAngleMeasurement == null) { + throw new IllegalStateException("Azimuth angle measurement is not set"); + } + + return new AngleOfArrivalMeasurement(mAzimuthAngleMeasurement, + mAltitudeAngleMeasurement); + } } } diff --git a/core/java/android/uwb/DistanceMeasurement.java b/core/java/android/uwb/DistanceMeasurement.java index f4e6d3ed644b..4cd5d83f4efc 100644 --- a/core/java/android/uwb/DistanceMeasurement.java +++ b/core/java/android/uwb/DistanceMeasurement.java @@ -27,13 +27,23 @@ import android.annotation.FloatRange; * @hide */ public final class DistanceMeasurement { + private final double mMeters; + private final double mErrorMeters; + private final double mConfidenceLevel; + + private DistanceMeasurement(double meters, double errorMeters, double confidenceLevel) { + mMeters = meters; + mErrorMeters = errorMeters; + mConfidenceLevel = confidenceLevel; + } + /** * Distance measurement in meters * * @return distance in meters */ public double getMeters() { - throw new UnsupportedOperationException(); + return mMeters; } /** @@ -43,7 +53,7 @@ public final class DistanceMeasurement { * @return error of distance measurement in meters */ public double getErrorMeters() { - throw new UnsupportedOperationException(); + return mErrorMeters; } /** @@ -56,6 +66,80 @@ public final class DistanceMeasurement { */ @FloatRange(from = 0.0, to = 1.0) public double getConfidenceLevel() { - throw new UnsupportedOperationException(); + return mConfidenceLevel; + } + + /** + * Builder to get a {@link DistanceMeasurement} object. + */ + public static final class Builder { + private double mMeters = Double.NaN; + private double mErrorMeters = Double.NaN; + private double mConfidenceLevel = Double.NaN; + + /** + * Set the distance measurement in meters + * + * @param meters distance in meters + * @throws IllegalArgumentException if meters is NaN + */ + public Builder setMeters(double meters) { + if (Double.isNaN(meters)) { + throw new IllegalArgumentException("meters cannot be NaN"); + } + mMeters = meters; + return this; + } + + /** + * Set the distance error in meters + * + * @param errorMeters distance error in meters + * @throws IllegalArgumentException if error is negative or NaN + */ + public Builder setErrorMeters(double errorMeters) { + if (Double.isNaN(errorMeters) || errorMeters < 0.0) { + throw new IllegalArgumentException( + "errorMeters must be >= 0.0 and not NaN: " + errorMeters); + } + mErrorMeters = errorMeters; + return this; + } + + /** + * Set the confidence level + * + * @param confidenceLevel the confidence level in the distance measurement + * @throws IllegalArgumentException if confidence level is not in the range of [0.0, 1.0] + */ + public Builder setConfidenceLevel(double confidenceLevel) { + if (confidenceLevel < 0.0 || confidenceLevel > 1.0) { + throw new IllegalArgumentException( + "confidenceLevel must be in the range [0.0, 1.0]: " + confidenceLevel); + } + mConfidenceLevel = confidenceLevel; + return this; + } + + /** + * Builds the {@link DistanceMeasurement} object + * + * @throws IllegalStateException if meters, error, or confidence are not set + */ + public DistanceMeasurement build() { + if (Double.isNaN(mMeters)) { + throw new IllegalStateException("Meters cannot be NaN"); + } + + if (Double.isNaN(mErrorMeters)) { + throw new IllegalStateException("Error meters cannot be NaN"); + } + + if (Double.isNaN(mConfidenceLevel)) { + throw new IllegalStateException("Confidence level cannot be NaN"); + } + + return new DistanceMeasurement(mMeters, mErrorMeters, mConfidenceLevel); + } } } diff --git a/core/java/android/uwb/RangingMeasurement.java b/core/java/android/uwb/RangingMeasurement.java index a249802366e0..33a34e3954c2 100644 --- a/core/java/android/uwb/RangingMeasurement.java +++ b/core/java/android/uwb/RangingMeasurement.java @@ -31,6 +31,22 @@ import java.lang.annotation.RetentionPolicy; * @hide */ public final class RangingMeasurement { + private final UwbAddress mRemoteDeviceAddress; + private final @Status int mStatus; + private final long mElapsedRealtimeNanos; + private final DistanceMeasurement mDistanceMeasurement; + private final AngleOfArrivalMeasurement mAngleOfArrivalMeasurement; + + private RangingMeasurement(@NonNull UwbAddress remoteDeviceAddress, @Status int status, + long elapsedRealtimeNanos, @Nullable DistanceMeasurement distanceMeasurement, + @Nullable AngleOfArrivalMeasurement angleOfArrivalMeasurement) { + mRemoteDeviceAddress = remoteDeviceAddress; + mStatus = status; + mElapsedRealtimeNanos = elapsedRealtimeNanos; + mDistanceMeasurement = distanceMeasurement; + mAngleOfArrivalMeasurement = angleOfArrivalMeasurement; + } + /** * Get the remote device's {@link UwbAddress} * @@ -38,7 +54,7 @@ public final class RangingMeasurement { */ @NonNull public UwbAddress getRemoteDeviceAddress() { - throw new UnsupportedOperationException(); + return mRemoteDeviceAddress; } @Retention(RetentionPolicy.SOURCE) @@ -75,7 +91,7 @@ public final class RangingMeasurement { */ @Status public int getStatus() { - throw new UnsupportedOperationException(); + return mStatus; } /** @@ -86,7 +102,7 @@ public final class RangingMeasurement { */ @SuppressLint("MethodNameUnits") public long getElapsedRealtimeNanos() { - throw new UnsupportedOperationException(); + return mElapsedRealtimeNanos; } /** @@ -97,7 +113,7 @@ public final class RangingMeasurement { */ @Nullable public DistanceMeasurement getDistance() { - throw new UnsupportedOperationException(); + return mDistanceMeasurement; } /** @@ -108,6 +124,106 @@ public final class RangingMeasurement { */ @Nullable public AngleOfArrivalMeasurement getAngleOfArrival() { - throw new UnsupportedOperationException(); + return mAngleOfArrivalMeasurement; + } + + /** + * Builder for a {@link RangingMeasurement} object. + */ + public static final class Builder { + private UwbAddress mRemoteDeviceAddress = null; + private @Status int mStatus = RANGING_STATUS_FAILURE_UNKNOWN_ERROR; + private long mElapsedRealtimeNanos = -1L; + private DistanceMeasurement mDistanceMeasurement = null; + private AngleOfArrivalMeasurement mAngleOfArrivalMeasurement = null; + + /** + * Set the remote device address that this measurement is for + * + * @param remoteDeviceAddress remote device's address + */ + public Builder setRemoteDeviceAddress(@NonNull UwbAddress remoteDeviceAddress) { + mRemoteDeviceAddress = remoteDeviceAddress; + return this; + } + + /** + * Set the status of ranging measurement + * + * @param status the status of the ranging measurement + */ + public Builder setStatus(@Status int status) { + mStatus = status; + return this; + } + + /** + * Set the elapsed realtime in nanoseconds when the ranging measurement occurred + * + * @param elapsedRealtimeNanos time the ranging measurement occurred + */ + public Builder setElapsedRealtimeNanos(long elapsedRealtimeNanos) { + if (elapsedRealtimeNanos < 0) { + throw new IllegalArgumentException("elapsedRealtimeNanos must be >= 0"); + } + mElapsedRealtimeNanos = elapsedRealtimeNanos; + return this; + } + + /** + * Set the {@link DistanceMeasurement} + * + * @param distanceMeasurement the distance measurement for this ranging measurement + */ + public Builder setDistanceMeasurement(@NonNull DistanceMeasurement distanceMeasurement) { + mDistanceMeasurement = distanceMeasurement; + return this; + } + + /** + * Set the {@link AngleOfArrivalMeasurement} + * + * @param angleOfArrivalMeasurement the angle of arrival measurement for this ranging + * measurement + */ + public Builder setAngleOfArrivalMeasurement( + @NonNull AngleOfArrivalMeasurement angleOfArrivalMeasurement) { + mAngleOfArrivalMeasurement = angleOfArrivalMeasurement; + return this; + } + + /** + * Build the {@link RangingMeasurement} object + * + * @throws IllegalStateException if a distance or angle of arrival measurement is provided + * but the measurement was not successful, if the + * elapsedRealtimeNanos of the measurement is invalid, or + * if no remote device address is set + */ + public RangingMeasurement build() { + if (mStatus != RANGING_STATUS_SUCCESS) { + if (mDistanceMeasurement != null) { + throw new IllegalStateException( + "Distance Measurement must be null if ranging is not successful"); + } + + if (mAngleOfArrivalMeasurement != null) { + throw new IllegalStateException( + "Angle of Arrival must be null if ranging is not successful"); + } + } + + if (mRemoteDeviceAddress == null) { + throw new IllegalStateException("No remote device address was set"); + } + + if (mElapsedRealtimeNanos < 0) { + throw new IllegalStateException( + "elapsedRealtimeNanos must be >=0: " + mElapsedRealtimeNanos); + } + + return new RangingMeasurement(mRemoteDeviceAddress, mStatus, mElapsedRealtimeNanos, + mDistanceMeasurement, mAngleOfArrivalMeasurement); + } } } diff --git a/core/java/android/uwb/RangingParams.java b/core/java/android/uwb/RangingParams.java index 9727696f0391..a50de3e61425 100644 --- a/core/java/android/uwb/RangingParams.java +++ b/core/java/android/uwb/RangingParams.java @@ -24,7 +24,11 @@ import android.util.Duration; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.HashSet; import java.util.List; +import java.util.Set; /** * An object used when requesting to open a new {@link RangingSession}. @@ -33,11 +37,33 @@ import java.util.List; * @hide */ public final class RangingParams { - /** - * Standard builder interface as the class is not modifiable - */ - public static class Builder { - // TODO implement + private final boolean mIsInitiator; + private final boolean mIsController; + private final Duration mSamplePeriod; + private final UwbAddress mLocalDeviceAddress; + private final List<UwbAddress> mRemoteDeviceAddresses; + private final int mChannelNumber; + private final int mTransmitPreambleCodeIndex; + private final int mReceivePreambleCodeIndex; + private final int mStsPhyPacketType; + private final PersistableBundle mSpecificationParameters; + + private RangingParams(boolean isInitiator, boolean isController, + @NonNull Duration samplingPeriod, @NonNull UwbAddress localDeviceAddress, + @NonNull List<UwbAddress> remoteDeviceAddresses, int channelNumber, + int transmitPreambleCodeIndex, int receivePreambleCodeIndex, + @StsPhyPacketType int stsPhyPacketType, + @NonNull PersistableBundle specificationParameters) { + mIsInitiator = isInitiator; + mIsController = isController; + mSamplePeriod = samplingPeriod; + mLocalDeviceAddress = localDeviceAddress; + mRemoteDeviceAddresses = remoteDeviceAddresses; + mChannelNumber = channelNumber; + mTransmitPreambleCodeIndex = transmitPreambleCodeIndex; + mReceivePreambleCodeIndex = receivePreambleCodeIndex; + mStsPhyPacketType = stsPhyPacketType; + mSpecificationParameters = specificationParameters; } /** @@ -46,7 +72,7 @@ public final class RangingParams { * @return true if the device is the initiator */ public boolean isInitiator() { - throw new UnsupportedOperationException(); + return mIsInitiator; } /** @@ -55,7 +81,7 @@ public final class RangingParams { * @return true if the device is the controller */ public boolean isController() { - throw new UnsupportedOperationException(); + return mIsController; } /** @@ -65,7 +91,7 @@ public final class RangingParams { */ @NonNull public Duration getSamplingPeriod() { - throw new UnsupportedOperationException(); + return mSamplePeriod; } /** @@ -78,7 +104,7 @@ public final class RangingParams { */ @NonNull public UwbAddress getLocalDeviceAddress() { - throw new UnsupportedOperationException(); + return mLocalDeviceAddress; } /** @@ -88,7 +114,7 @@ public final class RangingParams { */ @NonNull public List<UwbAddress> getRemoteDeviceAddresses() { - throw new UnsupportedOperationException(); + return mRemoteDeviceAddresses; } /** @@ -99,7 +125,7 @@ public final class RangingParams { * @return the channel to use */ public int getChannelNumber() { - throw new UnsupportedOperationException(); + return mChannelNumber; } /** @@ -110,7 +136,7 @@ public final class RangingParams { * @return the preamble index to use for transmitting */ public int getTxPreambleIndex() { - throw new UnsupportedOperationException(); + return mTransmitPreambleCodeIndex; } /** @@ -121,7 +147,7 @@ public final class RangingParams { * @return the preamble index to use for receiving */ public int getRxPreambleIndex() { - throw new UnsupportedOperationException(); + return mReceivePreambleCodeIndex; } @Retention(RetentionPolicy.SOURCE) @@ -159,7 +185,7 @@ public final class RangingParams { */ @StsPhyPacketType public int getStsPhyPacketType() { - throw new UnsupportedOperationException(); + return mStsPhyPacketType; } /** @@ -167,9 +193,189 @@ public final class RangingParams { * * <p>Android reserves the '^android.*' namespace * - * @return a {@link PersistableBundle} of protocol specific parameters + * @return a {@link PersistableBundle} copy of protocol specific parameters */ public @Nullable PersistableBundle getSpecificationParameters() { - throw new UnsupportedOperationException(); + return new PersistableBundle(mSpecificationParameters); + } + + /** + * Builder class for {@link RangingParams}. + */ + public static final class Builder { + private boolean mIsInitiator = false; + private boolean mIsController = false; + private Duration mSamplePeriod = null; + private UwbAddress mLocalDeviceAddress = null; + private List<UwbAddress> mRemoteDeviceAddresses = new ArrayList<>(); + private int mChannelNumber = 0; + private int mTransmitPreambleCodeIndex = 0; + private int mReceivePreambleCodeIndex = 0; + private int mStsPhyPacketType = STS_PHY_PACKET_TYPE_SP0; + private PersistableBundle mSpecificationParameters = new PersistableBundle(); + + /** + * Set whether the device is the initiator or responder as defined by IEEE 802.15.4z + * + * @param isInitiator whether the device is the initiator (true) or responder (false) + */ + public Builder setIsInitiator(boolean isInitiator) { + mIsInitiator = isInitiator; + return this; + } + + /** + * Set whether the local device is the controller or controlee as defined by IEEE 802.15.4z + * + * @param isController whether the device is the controller (true) or controlee (false) + */ + public Builder setIsController(boolean isController) { + mIsController = isController; + return this; + } + + /** + * Set the time between ranging samples + * + * @param samplePeriod the time between ranging samples + */ + public Builder setSamplePeriod(@NonNull Duration samplePeriod) { + mSamplePeriod = samplePeriod; + return this; + } + + /** + * Set the local device address + * + * @param localDeviceAddress the local device's address for the {@link RangingSession} + */ + public Builder setLocalDeviceAddress(@NonNull UwbAddress localDeviceAddress) { + mLocalDeviceAddress = localDeviceAddress; + return this; + } + + /** + * Add a remote device's address to the ranging session + * + * @param remoteDeviceAddress a remote device's address for the {@link RangingSession} + * @throws IllegalArgumentException if {@code remoteDeviceAddress} is already present. + */ + public Builder addRemoteDeviceAddress(@NonNull UwbAddress remoteDeviceAddress) { + if (mRemoteDeviceAddresses.contains(remoteDeviceAddress)) { + throw new IllegalArgumentException( + "Remote device address already added: " + remoteDeviceAddress.toString()); + } + mRemoteDeviceAddresses.add(remoteDeviceAddress); + return this; + } + + /** + * Set the IEEE 802.15.4z channel to use for the {@link RangingSession} + * <p>Valid values are in the range [-1, 15] + * + * @param channelNumber the channel to use for the {@link RangingSession} + * @throws IllegalArgumentException if {@code channelNumber} is invalid. + */ + public Builder setChannelNumber(int channelNumber) { + if (channelNumber < -1 || channelNumber > 15) { + throw new IllegalArgumentException("Invalid channel number"); + } + mChannelNumber = channelNumber; + return this; + } + + private static final Set<Integer> VALID_TX_PREAMBLE_CODES = new HashSet<Integer>( + Arrays.asList(0, 13, 14, 15, 16, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32)); + + /** + * Set the IEEE 802.15.4z preamble code index to use when transmitting + * + * <p>Valid values are in the ranges: [0], [13-16], [21-32] + * + * @param transmitPreambleCodeIndex preamble code index to use for transmitting + * @throws IllegalArgumentException if {@code transmitPreambleCodeIndex} is invalid. + */ + public Builder setTransmitPreambleCodeIndex(int transmitPreambleCodeIndex) { + if (!VALID_TX_PREAMBLE_CODES.contains(transmitPreambleCodeIndex)) { + throw new IllegalArgumentException( + "Invalid transmit preamble: " + transmitPreambleCodeIndex); + } + mTransmitPreambleCodeIndex = transmitPreambleCodeIndex; + return this; + } + + private static final Set<Integer> VALID_RX_PREAMBLE_CODES = new HashSet<Integer>( + Arrays.asList(0, 16, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32)); + + /** + * Set the IEEE 802.15.4z preamble code index to use when receiving + * + * Valid values are in the ranges: [0], [16-32] + * + * @param receivePreambleCodeIndex preamble code index to use for receiving + * @throws IllegalArgumentException if {@code receivePreambleCodeIndex} is invalid. + */ + public Builder setReceivePreambleCodeIndex(int receivePreambleCodeIndex) { + if (!VALID_RX_PREAMBLE_CODES.contains(receivePreambleCodeIndex)) { + throw new IllegalArgumentException( + "Invalid receive preamble: " + receivePreambleCodeIndex); + } + mReceivePreambleCodeIndex = receivePreambleCodeIndex; + return this; + } + + /** + * Set the IEEE 802.15.4z PHY packet type when STS is used + * + * @param stsPhyPacketType PHY packet type when STS is used + * @throws IllegalArgumentException if {@code stsPhyPacketType} is invalid. + */ + public Builder setStsPhPacketType(@StsPhyPacketType int stsPhyPacketType) { + if (stsPhyPacketType != STS_PHY_PACKET_TYPE_SP0 + && stsPhyPacketType != STS_PHY_PACKET_TYPE_SP1 + && stsPhyPacketType != STS_PHY_PACKET_TYPE_SP2 + && stsPhyPacketType != STS_PHY_PACKET_TYPE_SP3) { + throw new IllegalArgumentException("unknown StsPhyPacketType: " + stsPhyPacketType); + } + + mStsPhyPacketType = stsPhyPacketType; + return this; + } + + /** + * Set the specification parameters + * + * <p>Creates a copy of the parameters + * + * @param parameters specification parameters built from support library + */ + public Builder setSpecificationParameters(@NonNull PersistableBundle parameters) { + mSpecificationParameters = new PersistableBundle(parameters); + return this; + } + + /** + * Build the {@link RangingParams} object. + * + * @throws IllegalStateException if required parameters are missing + */ + public RangingParams build() { + if (mSamplePeriod == null) { + throw new IllegalStateException("No sample period provided"); + } + + if (mLocalDeviceAddress == null) { + throw new IllegalStateException("Local device address not provided"); + } + + if (mRemoteDeviceAddresses.size() == 0) { + throw new IllegalStateException("No remote device address(es) provided"); + } + + return new RangingParams(mIsInitiator, mIsController, mSamplePeriod, + mLocalDeviceAddress, mRemoteDeviceAddresses, mChannelNumber, + mTransmitPreambleCodeIndex, mReceivePreambleCodeIndex, mStsPhyPacketType, + mSpecificationParameters); + } } } diff --git a/core/java/android/uwb/RangingReport.java b/core/java/android/uwb/RangingReport.java index 037bdfd5f224..5aca12aaf2cf 100644 --- a/core/java/android/uwb/RangingReport.java +++ b/core/java/android/uwb/RangingReport.java @@ -18,6 +18,7 @@ package android.uwb; import android.annotation.NonNull; +import java.util.ArrayList; import java.util.List; /** @@ -26,6 +27,12 @@ import java.util.List; * @hide */ public final class RangingReport { + private final List<RangingMeasurement> mRangingMeasurements; + + private RangingReport(@NonNull List<RangingMeasurement> rangingMeasurements) { + mRangingMeasurements = rangingMeasurements; + } + /** * Get a {@link List} of {@link RangingMeasurement} objects in the last measurement interval * <p>The underlying UWB adapter may choose to do multiple measurements in each ranging @@ -38,7 +45,57 @@ public final class RangingReport { */ @NonNull public List<RangingMeasurement> getMeasurements() { - throw new UnsupportedOperationException(); + return mRangingMeasurements; + } + + /** + * Builder for {@link RangingReport} object + */ + public static final class Builder { + List<RangingMeasurement> mMeasurements = new ArrayList<>(); + + /** + * Add a single {@link RangingMeasurement} + * + * @param rangingMeasurement a ranging measurement + */ + public Builder addMeasurement(@NonNull RangingMeasurement rangingMeasurement) { + mMeasurements.add(rangingMeasurement); + return this; + } + + /** + * Add a {@link List} of {@link RangingMeasurement}s + * + * @param rangingMeasurements {@link List} of {@link RangingMeasurement}s to add + */ + public Builder addMeasurements(@NonNull List<RangingMeasurement> rangingMeasurements) { + mMeasurements.addAll(rangingMeasurements); + return this; + } + + /** + * Build the {@link RangingReport} object + * + * @throws IllegalStateException if measurements are not in monotonically increasing order + */ + public RangingReport build() { + // Verify that all measurement timestamps are monotonically increasing + RangingMeasurement prevMeasurement = null; + for (int curIndex = 0; curIndex < mMeasurements.size(); curIndex++) { + RangingMeasurement curMeasurement = mMeasurements.get(curIndex); + if (prevMeasurement != null + && (prevMeasurement.getElapsedRealtimeNanos() + > curMeasurement.getElapsedRealtimeNanos())) { + throw new IllegalStateException( + "Timestamp (" + curMeasurement.getElapsedRealtimeNanos() + + ") at index " + curIndex + " is less than previous timestamp (" + + prevMeasurement.getElapsedRealtimeNanos() + ")"); + } + prevMeasurement = curMeasurement; + } + return new RangingReport(mMeasurements); + } } } diff --git a/core/java/android/uwb/RangingSession.java b/core/java/android/uwb/RangingSession.java new file mode 100644 index 000000000000..f4033fe0d29e --- /dev/null +++ b/core/java/android/uwb/RangingSession.java @@ -0,0 +1,148 @@ +/* + * 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.IntDef; +import android.os.PersistableBundle; + +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.util.concurrent.Executor; + +/** + * This class provides a way to control an active UWB ranging session. + * <p>It also defines the required {@link RangingSession.Callback} that must be implemented + * in order to be notified of UWB ranging results and status events related to the + * {@link RangingSession}. + * + * <p>To get an instance of {@link RangingSession}, first use + * {@link UwbManager#openRangingSession(RangingParams, Executor, Callback)} to request to open a + * session. Once the session is opened, a {@link RangingSession} object is provided through + * {@link RangingSession.Callback#onOpenSuccess(RangingSession, PersistableBundle)}. If opening a + * session fails, the failure is reported through {@link RangingSession.Callback#onClosed(int)} with + * the failure reason. + * + * @hide + */ +public final class RangingSession implements AutoCloseable { + /** + * Interface for receiving {@link RangingSession} events + */ + public interface Callback { + /** + * Invoked when {@link UwbManager#openRangingSession(RangingParams, Executor, Callback)} + * is successful + * + * @param session the newly opened {@link RangingSession} + * @param sessionInfo session specific parameters from lower layers + */ + void onOpenSuccess(RangingSession session, PersistableBundle sessionInfo); + + @Retention(RetentionPolicy.SOURCE) + @IntDef(value = { + CLOSE_REASON_UNKNOWN, + CLOSE_REASON_LOCAL_CLOSE_API, + CLOSE_REASON_LOCAL_BAD_PARAMETERS, + CLOSE_REASON_LOCAL_GENERIC_ERROR, + CLOSE_REASON_LOCAL_MAX_SESSIONS_REACHED, + CLOSE_REASON_LOCAL_SYSTEM_POLICY, + CLOSE_REASON_REMOTE_GENERIC_ERROR, + CLOSE_REASON_REMOTE_REQUEST}) + @interface CloseReason {} + + /** + * Indicates that the session was closed or failed to open due to an unknown reason + */ + int CLOSE_REASON_UNKNOWN = 0; + + /** + * Indicates that the session was closed or failed to open because + * {@link AutoCloseable#close()} or {@link RangingSession#close()} was called + */ + int CLOSE_REASON_LOCAL_CLOSE_API = 1; + + /** + * Indicates that the session failed to open due to erroneous parameters passed + * to {@link UwbManager#openRangingSession(RangingParams, Executor, Callback)} + */ + int CLOSE_REASON_LOCAL_BAD_PARAMETERS = 2; + + /** + * Indicates that the session was closed due to some local error on this device besides the + * error code already listed + */ + int CLOSE_REASON_LOCAL_GENERIC_ERROR = 3; + + /** + * Indicates that the session failed to open because the number of currently open sessions + * is equal to {@link UwbManager#getMaxSimultaneousSessions()} + */ + int CLOSE_REASON_LOCAL_MAX_SESSIONS_REACHED = 4; + + /** + * Indicates that the session was closed or failed to open due to local system policy, such + * as privacy policy, power management policy, permissions, and more. + */ + int CLOSE_REASON_LOCAL_SYSTEM_POLICY = 5; + + /** + * Indicates that the session was closed or failed to open due to an error with the remote + * device besides error codes already listed. + */ + int CLOSE_REASON_REMOTE_GENERIC_ERROR = 6; + + /** + * Indicates that the session was closed or failed to open due to an explicit request from + * the remote device. + */ + int CLOSE_REASON_REMOTE_REQUEST = 7; + + /** + * Invoked when session is either closed spontaneously, or per user request via + * {@link RangingSession#close()} or {@link AutoCloseable#close()}, or when session failed + * to open. + * + * @param reason reason for the session closure + */ + void onClosed(@CloseReason int reason); + + /** + * Called once per ranging interval even when a ranging measurement fails + * + * @param rangingReport ranging report for this interval's measurements + */ + void onReportReceived(RangingReport rangingReport); + } + + /** + * Close the ranging session + * <p>If this session is currently open, it will close and stop the session. + * <p>If the session is in the process of being opened, it will attempt to stop the session from + * being opened. + * <p>If the session is already closed, the registered {@link Callback#onClosed(int)} callback + * will still be invoked. + * + * <p>{@link Callback#onClosed(int)} will be invoked using the same callback + * object given to {@link UwbManager#openRangingSession(RangingParams, Executor, Callback)} when + * the {@link RangingSession} was opened. The callback will be invoked after each call to + * {@link #close()}, even if the {@link RangingSession} is already closed. + */ + @Override + public void close() { + throw new UnsupportedOperationException(); + } +} diff --git a/core/java/android/uwb/UwbManager.java b/core/java/android/uwb/UwbManager.java index 8097dc6dca11..d58d5bfd8de3 100644 --- a/core/java/android/uwb/UwbManager.java +++ b/core/java/android/uwb/UwbManager.java @@ -266,4 +266,33 @@ public final class UwbManager { public int getMaxRemoteDevicesPerResponderSession() { throw new UnsupportedOperationException(); } + + /** + * Open a {@link RangingSession} with the given parameters + * <p>This function is asynchronous and will return before ranging begins. The + * {@link RangingSession.Callback#onOpenSuccess(RangingSession, PersistableBundle)} function is + * called with a {@link RangingSession} object used to control ranging when the session is + * successfully opened. + * + * <p>If a session cannot be opened, then {@link RangingSession.Callback#onClosed(int)} will be + * invoked with the appropriate {@link RangingSession.Callback.CloseReason}. + * + * <p>An open {@link RangingSession} will be automatically closed if client application process + * dies. + * + * @param params {@link RangingParams} used to initialize this {@link RangingSession} + * @param executor {@link Executor} to run callbacks + * @param callbacks {@link RangingSession.Callback} to associate with the + * {@link RangingSession} that is being opened. + * + * @return an {@link AutoCloseable} that is able to be used to close or cancel the opening of a + * {@link RangingSession} that has been requested through {@link #openRangingSession} + * but has not yet been made available by + * {@link RangingSession.Callback#onOpenSuccess}. + */ + @NonNull + public AutoCloseable openRangingSession(@NonNull RangingParams params, + @NonNull Executor executor, @NonNull RangingSession.Callback callbacks) { + throw new UnsupportedOperationException(); + } } |