/* * 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.annotation.IntRange; import android.annotation.NonNull; import android.annotation.Nullable; import android.annotation.SuppressLint; import android.annotation.SystemApi; import android.os.Parcel; import android.os.Parcelable; import android.os.PersistableBundle; import android.os.SystemClock; import android.uwb.util.PersistableBundleUtils; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.util.Objects; /** * Representation of a ranging measurement between the local device and a remote device * * @hide */ @SystemApi public final class RangingMeasurement implements Parcelable { public static final int RSSI_UNKNOWN = -128; public static final int RSSI_MIN = -127; public static final int RSSI_MAX = -1; private final UwbAddress mRemoteDeviceAddress; private final @Status int mStatus; private final long mElapsedRealtimeNanos; private final DistanceMeasurement mDistanceMeasurement; private final AngleOfArrivalMeasurement mAngleOfArrivalMeasurement; private final AngleOfArrivalMeasurement mDestinationAngleOfArrivalMeasurement; private final @LineOfSight int mLineOfSight; private final @MeasurementFocus int mMeasurementFocus; private final int mRssiDbm; private final PersistableBundle mRangingMeasurementMetadata; private RangingMeasurement(@NonNull UwbAddress remoteDeviceAddress, @Status int status, long elapsedRealtimeNanos, @Nullable DistanceMeasurement distanceMeasurement, @Nullable AngleOfArrivalMeasurement angleOfArrivalMeasurement, @Nullable AngleOfArrivalMeasurement destinationAngleOfArrivalMeasurement, @LineOfSight int lineOfSight, @MeasurementFocus int measurementFocus, @IntRange(from = RSSI_UNKNOWN, to = RSSI_MAX) int rssiDbm, PersistableBundle rangingMeasurementMetadata) { mRemoteDeviceAddress = remoteDeviceAddress; mStatus = status; mElapsedRealtimeNanos = elapsedRealtimeNanos; mDistanceMeasurement = distanceMeasurement; mAngleOfArrivalMeasurement = angleOfArrivalMeasurement; mDestinationAngleOfArrivalMeasurement = destinationAngleOfArrivalMeasurement; mLineOfSight = lineOfSight; mMeasurementFocus = measurementFocus; mRssiDbm = rssiDbm; mRangingMeasurementMetadata = rangingMeasurementMetadata; } /** * Get the remote device's {@link UwbAddress} * * @return the remote device's {@link UwbAddress} */ @NonNull public UwbAddress getRemoteDeviceAddress() { return mRemoteDeviceAddress; } /** * @hide */ @Retention(RetentionPolicy.SOURCE) @IntDef(value = { RANGING_STATUS_SUCCESS, RANGING_STATUS_FAILURE_OUT_OF_RANGE, RANGING_STATUS_FAILURE_UNKNOWN_ERROR}) public @interface Status {} /** * Ranging attempt was successful for this device */ public static final int RANGING_STATUS_SUCCESS = 0; /** * Ranging failed for this device because it is out of range */ public static final int RANGING_STATUS_FAILURE_OUT_OF_RANGE = 1; /** * Ranging failed for this device because of unknown error */ public static final int RANGING_STATUS_FAILURE_UNKNOWN_ERROR = -1; /** * Get the status of this ranging measurement * *

Possible values are * {@link #RANGING_STATUS_SUCCESS}, * {@link #RANGING_STATUS_FAILURE_OUT_OF_RANGE}, * {@link #RANGING_STATUS_FAILURE_UNKNOWN_ERROR}. * * @return the status of the ranging measurement */ @Status public int getStatus() { return mStatus; } /** * Timestamp of this ranging measurement in time since boot nanos in the same namespace as * {@link SystemClock#elapsedRealtimeNanos()} * * @return timestamp of ranging measurement in nanoseconds */ @SuppressLint("MethodNameUnits") public long getElapsedRealtimeNanos() { return mElapsedRealtimeNanos; } /** * Get the distance measurement * * @return a {@link DistanceMeasurement} or null if {@link #getStatus()} != * {@link #RANGING_STATUS_SUCCESS} */ @Nullable public DistanceMeasurement getDistanceMeasurement() { return mDistanceMeasurement; } /** * Get the angle of arrival measurement * * @return an {@link AngleOfArrivalMeasurement} or null if {@link #getStatus()} != * {@link #RANGING_STATUS_SUCCESS} */ @Nullable public AngleOfArrivalMeasurement getAngleOfArrivalMeasurement() { return mAngleOfArrivalMeasurement; } /** * Get the angle of arrival measurement at the destination. * * @return an {@link AngleOfArrivalMeasurement} or null if {@link #getStatus()} != * {@link #RANGING_STATUS_SUCCESS} */ @Nullable public AngleOfArrivalMeasurement getDestinationAngleOfArrivalMeasurement() { return mDestinationAngleOfArrivalMeasurement; } /** * @hide */ @Retention(RetentionPolicy.SOURCE) @IntDef(value = { LOS, NLOS, LOS_UNDETERMINED}) public @interface LineOfSight {} /** * If measurement was in line of sight. */ public static final int LOS = 0; /** * If measurement was not in line of sight. */ public static final int NLOS = 1; /** * Unable to determine whether the measurement was in line of sight or not. */ public static final int LOS_UNDETERMINED = 0xFF; /** * Get whether the measurement was in Line of sight or non-line of sight. * * @return whether the measurement was in line of sight or not */ public @LineOfSight int getLineOfSight() { return mLineOfSight; } /** * Get the measured RSSI in dBm */ public @IntRange(from = RSSI_UNKNOWN, to = RSSI_MAX) int getRssiDbm() { return mRssiDbm; } /** * @hide */ @Retention(RetentionPolicy.SOURCE) @IntDef(value = { MEASUREMENT_FOCUS_NONE, MEASUREMENT_FOCUS_RANGE, MEASUREMENT_FOCUS_ANGLE_OF_ARRIVAL_AZIMUTH, MEASUREMENT_FOCUS_ANGLE_OF_ARRIVAL_ELEVATION}) public @interface MeasurementFocus {} /** * Ranging measurement was done with no particular focus in terms of antenna selection. */ public static final int MEASUREMENT_FOCUS_NONE = 0; /** * Ranging measurement was done with a focus on range calculation in terms of antenna * selection. */ public static final int MEASUREMENT_FOCUS_RANGE = 1; /** * Ranging measurement was done with a focus on Angle of arrival azimuth calculation in terms of * antenna selection. */ public static final int MEASUREMENT_FOCUS_ANGLE_OF_ARRIVAL_AZIMUTH = 2; /** * Ranging measurement was done with a focus on Angle of arrival elevation calculation in terms * of antenna selection. */ public static final int MEASUREMENT_FOCUS_ANGLE_OF_ARRIVAL_ELEVATION = 3; /** * Gets the measurement focus in terms of antenna used for this measurement. * * @return focus of this measurement. */ public @MeasurementFocus int getMeasurementFocus() { return mMeasurementFocus; } /** * Gets ranging measurement metadata passed by vendor * * @return vendor data for ranging measurement */ @NonNull public PersistableBundle getRangingMeasurementMetadata() { return mRangingMeasurementMetadata; } /** * @hide */ @Override public boolean equals(@Nullable Object obj) { if (this == obj) { return true; } if (obj instanceof RangingMeasurement) { RangingMeasurement other = (RangingMeasurement) obj; return Objects.equals(mRemoteDeviceAddress, other.getRemoteDeviceAddress()) && mStatus == other.getStatus() && mElapsedRealtimeNanos == other.getElapsedRealtimeNanos() && Objects.equals(mDistanceMeasurement, other.getDistanceMeasurement()) && Objects.equals( mAngleOfArrivalMeasurement, other.getAngleOfArrivalMeasurement()) && Objects.equals( mDestinationAngleOfArrivalMeasurement, other.getDestinationAngleOfArrivalMeasurement()) && mLineOfSight == other.getLineOfSight() && mMeasurementFocus == other.getMeasurementFocus() && mRssiDbm == other.getRssiDbm() && PersistableBundleUtils.isEqual(mRangingMeasurementMetadata, other.mRangingMeasurementMetadata); } return false; } /** * @hide */ @Override public int hashCode() { return Objects.hash(mRemoteDeviceAddress, mStatus, mElapsedRealtimeNanos, mDistanceMeasurement, mAngleOfArrivalMeasurement, mDestinationAngleOfArrivalMeasurement, mLineOfSight, mMeasurementFocus, mRssiDbm, PersistableBundleUtils.getHashCode(mRangingMeasurementMetadata)); } @Override public int describeContents() { return 0; } @Override public void writeToParcel(@NonNull Parcel dest, int flags) { dest.writeParcelable(mRemoteDeviceAddress, flags); dest.writeInt(mStatus); dest.writeLong(mElapsedRealtimeNanos); dest.writeParcelable(mDistanceMeasurement, flags); dest.writeParcelable(mAngleOfArrivalMeasurement, flags); dest.writeParcelable(mDestinationAngleOfArrivalMeasurement, flags); dest.writeInt(mLineOfSight); dest.writeInt(mMeasurementFocus); dest.writeInt(mRssiDbm); dest.writePersistableBundle(mRangingMeasurementMetadata); } public static final @android.annotation.NonNull Creator CREATOR = new Creator() { @Override public RangingMeasurement createFromParcel(Parcel in) { Builder builder = new Builder(); builder.setRemoteDeviceAddress( in.readParcelable(UwbAddress.class.getClassLoader())); builder.setStatus(in.readInt()); builder.setElapsedRealtimeNanos(in.readLong()); builder.setDistanceMeasurement( in.readParcelable(DistanceMeasurement.class.getClassLoader())); builder.setAngleOfArrivalMeasurement( in.readParcelable(AngleOfArrivalMeasurement.class.getClassLoader())); builder.setDestinationAngleOfArrivalMeasurement( in.readParcelable(AngleOfArrivalMeasurement.class.getClassLoader())); builder.setLineOfSight(in.readInt()); builder.setMeasurementFocus(in.readInt()); builder.setRssiDbm(in.readInt()); PersistableBundle metadata = in.readPersistableBundle(getClass().getClassLoader()); if (metadata != null) builder.setRangingMeasurementMetadata(metadata); return builder.build(); } @Override public RangingMeasurement[] newArray(int size) { return new RangingMeasurement[size]; } }; /** @hide **/ @Override public String toString() { return "RangingMeasurement[" + "remote device address:" + mRemoteDeviceAddress + ", distance measurement: " + mDistanceMeasurement + ", aoa measurement: " + mAngleOfArrivalMeasurement + ", dest aoa measurement: " + mDestinationAngleOfArrivalMeasurement + ", lineOfSight: " + mLineOfSight + ", measurementFocus: " + mMeasurementFocus + ", rssiDbm: " + mRssiDbm + ", ranging measurement metadata: " + mRangingMeasurementMetadata + ", elapsed real time nanos: " + mElapsedRealtimeNanos + ", status: " + mStatus + "]"; } /** * 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; private AngleOfArrivalMeasurement mDestinationAngleOfArrivalMeasurement = null; private @LineOfSight int mLineOfSight = LOS_UNDETERMINED; private @MeasurementFocus int mMeasurementFocus = MEASUREMENT_FOCUS_NONE; private int mRssiDbm = RSSI_UNKNOWN; private PersistableBundle mRangingMeasurementMetadata = null; /** * Set the remote device address that this measurement is for * * @param remoteDeviceAddress remote device's address */ @NonNull public Builder setRemoteDeviceAddress(@NonNull UwbAddress remoteDeviceAddress) { mRemoteDeviceAddress = remoteDeviceAddress; return this; } /** * Set the status of ranging measurement * * @param status the status of the ranging measurement */ @NonNull 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 */ @NonNull 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 */ @NonNull 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 */ @NonNull public Builder setAngleOfArrivalMeasurement( @NonNull AngleOfArrivalMeasurement angleOfArrivalMeasurement) { mAngleOfArrivalMeasurement = angleOfArrivalMeasurement; return this; } /** * Set the {@link AngleOfArrivalMeasurement} at the destination. * * @param angleOfArrivalMeasurement the angle of arrival measurement for this ranging * measurement */ @NonNull public Builder setDestinationAngleOfArrivalMeasurement( @NonNull AngleOfArrivalMeasurement angleOfArrivalMeasurement) { mDestinationAngleOfArrivalMeasurement = angleOfArrivalMeasurement; return this; } /** * Set whether the measurement was in Line of sight or non-line of sight. * * @param lineOfSight whether the measurement was in line of sight or not */ @NonNull public Builder setLineOfSight(@LineOfSight int lineOfSight) { mLineOfSight = lineOfSight; return this; } /** * Sets the measurement focus in terms of antenna used for this measurement. * * @param measurementFocus focus of this measurement. */ @NonNull public Builder setMeasurementFocus(@MeasurementFocus int measurementFocus) { mMeasurementFocus = measurementFocus; return this; } /** * Set the RSSI in dBm * * @param rssiDbm the measured RSSI in dBm */ @NonNull public Builder setRssiDbm(@IntRange(from = RSSI_UNKNOWN, to = RSSI_MAX) int rssiDbm) { if (rssiDbm != RSSI_UNKNOWN && (rssiDbm < RSSI_MIN || rssiDbm > RSSI_MAX)) { throw new IllegalArgumentException("Invalid rssiDbm: " + rssiDbm); } mRssiDbm = rssiDbm; return this; } /** * Set Ranging measurement metadata * * @param rangingMeasurementMetadata vendor data per ranging measurement * * @throws IllegalStateException if rangingMeasurementMetadata is null */ @NonNull public Builder setRangingMeasurementMetadata(@NonNull PersistableBundle rangingMeasurementMetadata) { if (rangingMeasurementMetadata == null) { throw new IllegalStateException("Expected non-null rangingMeasurementMetadata"); } mRangingMeasurementMetadata = rangingMeasurementMetadata; 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 */ @NonNull 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"); } // Destination AOA is optional according to the spec. } 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, mDestinationAngleOfArrivalMeasurement, mLineOfSight, mMeasurementFocus, mRssiDbm, mRangingMeasurementMetadata); } } }