1 /*
2  * Copyright 2022 The Android Open Source Project
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *      http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 
17 package android.bluetooth.le;
18 
19 import android.annotation.FlaggedApi;
20 import android.annotation.FloatRange;
21 import android.annotation.IntDef;
22 import android.annotation.NonNull;
23 import android.annotation.SystemApi;
24 import android.os.Parcel;
25 import android.os.Parcelable;
26 
27 import com.android.bluetooth.flags.Flags;
28 
29 import java.lang.annotation.Retention;
30 import java.lang.annotation.RetentionPolicy;
31 
32 /**
33  * Result of distance measurement.
34  *
35  * @hide
36  */
37 @SystemApi
38 public final class DistanceMeasurementResult implements Parcelable {
39 
40     /**
41      * Normalized Attack Detector Metric. See Channel Sounding CR_PR, 3.13.24 for details.
42      *
43      * <p>Specification: https://www.bluetooth.com/specifications/specs/channel-sounding-cr-pr/
44      *
45      * @hide
46      */
47     @Retention(RetentionPolicy.SOURCE)
48     @IntDef(
49             value = {
50                 NADM_ATTACK_IS_EXTREMELY_UNLIKELY,
51                 NADM_ATTACK_IS_VERY_UNLIKELY,
52                 NADM_ATTACK_IS_UNLIKELY,
53                 NADM_ATTACK_IS_POSSIBLE,
54                 NADM_ATTACK_IS_LIKELY,
55                 NADM_ATTACK_IS_VERY_LIKELY,
56                 NADM_ATTACK_IS_EXTREMELY_LIKELY,
57                 NADM_UNKNOWN
58             })
59     @interface Nadm {}
60 
61     /**
62      * Attack is extremely unlikely.
63      *
64      * @hide
65      */
66     @FlaggedApi(Flags.FLAG_CHANNEL_SOUNDING)
67     @SystemApi
68     public static final int NADM_ATTACK_IS_EXTREMELY_UNLIKELY = 0;
69 
70     /**
71      * Attack is very unlikely.
72      *
73      * @hide
74      */
75     @FlaggedApi(Flags.FLAG_CHANNEL_SOUNDING)
76     @SystemApi
77     public static final int NADM_ATTACK_IS_VERY_UNLIKELY = 1;
78 
79     /**
80      * Attack is unlikely.
81      *
82      * @hide
83      */
84     @FlaggedApi(Flags.FLAG_CHANNEL_SOUNDING)
85     @SystemApi
86     public static final int NADM_ATTACK_IS_UNLIKELY = 2;
87 
88     /**
89      * Attack is possible.
90      *
91      * @hide
92      */
93     @FlaggedApi(Flags.FLAG_CHANNEL_SOUNDING)
94     @SystemApi
95     public static final int NADM_ATTACK_IS_POSSIBLE = 3;
96 
97     /**
98      * Attack is likely.
99      *
100      * @hide
101      */
102     @FlaggedApi(Flags.FLAG_CHANNEL_SOUNDING)
103     @SystemApi
104     public static final int NADM_ATTACK_IS_LIKELY = 4;
105 
106     /**
107      * Attack is very likely.
108      *
109      * @hide
110      */
111     @FlaggedApi(Flags.FLAG_CHANNEL_SOUNDING)
112     @SystemApi
113     public static final int NADM_ATTACK_IS_VERY_LIKELY = 5;
114 
115     /**
116      * Attack is extremely likely.
117      *
118      * @hide
119      */
120     @FlaggedApi(Flags.FLAG_CHANNEL_SOUNDING)
121     @SystemApi
122     public static final int NADM_ATTACK_IS_EXTREMELY_LIKELY = 6;
123 
124     /**
125      * Unknown NADM, if a device is unable to determine a NADM value, then it shall report this.
126      *
127      * @hide
128      */
129     @FlaggedApi(Flags.FLAG_CHANNEL_SOUNDING)
130     @SystemApi
131     public static final int NADM_UNKNOWN = 0xFF;
132 
133     private final double mMeters;
134     private final double mErrorMeters;
135     private final double mAzimuthAngle;
136     private final double mErrorAzimuthAngle;
137     private final double mAltitudeAngle;
138     private final double mErrorAltitudeAngle;
139     private final double mDelaySpreadMeters;
140     private final double mConfidenceLevel;
141     private final int mDetectedAttackLevel;
142     private final double mVelocityMetersPerSecond;
143 
DistanceMeasurementResult( double meters, double errorMeters, double azimuthAngle, double errorAzimuthAngle, double altitudeAngle, double errorAltitudeAngle, double delaySpreadMeters, double confidenceLevel, @Nadm int detectedAttackLevel, double velocityMetersPerSecond)144     private DistanceMeasurementResult(
145             double meters,
146             double errorMeters,
147             double azimuthAngle,
148             double errorAzimuthAngle,
149             double altitudeAngle,
150             double errorAltitudeAngle,
151             double delaySpreadMeters,
152             double confidenceLevel,
153             @Nadm int detectedAttackLevel,
154             double velocityMetersPerSecond) {
155         mMeters = meters;
156         mErrorMeters = errorMeters;
157         mAzimuthAngle = azimuthAngle;
158         mErrorAzimuthAngle = errorAzimuthAngle;
159         mAltitudeAngle = altitudeAngle;
160         mErrorAltitudeAngle = errorAltitudeAngle;
161         mDelaySpreadMeters = delaySpreadMeters;
162         mConfidenceLevel = confidenceLevel;
163         mDetectedAttackLevel = detectedAttackLevel;
164         mVelocityMetersPerSecond = velocityMetersPerSecond;
165     }
166 
167     /**
168      * Distance measurement in meters.
169      *
170      * @return distance in meters
171      * @hide
172      */
173     @SystemApi
getResultMeters()174     public double getResultMeters() {
175         return mMeters;
176     }
177 
178     /**
179      * Error of distance measurement in meters.
180      *
181      * <p>Must be positive.
182      *
183      * @return error of distance measurement in meters
184      * @hide
185      */
186     @SystemApi
187     @FloatRange(from = 0.0)
getErrorMeters()188     public double getErrorMeters() {
189         return mErrorMeters;
190     }
191 
192     /**
193      * Azimuth Angle measurement in degrees.
194      *
195      * <p>Azimuth of remote device in horizontal coordinate system, this measured from azimuth north
196      * and increasing eastward. When the remote device in azimuth north, this angle is 0, whe the
197      * remote device in azimuth south, this angle is 180.
198      *
199      * <p>See: <a href="https://en.wikipedia.org/wiki/Horizontal_coordinate_system">Horizontal
200      * coordinate system</a>for the details
201      *
202      * <p>On an Android device, azimuth north is defined as the angle perpendicular away from the
203      * back of the device when holding it in portrait mode upright.
204      *
205      * <p>The Azimuth north is defined as the direction in which the top edge of the device is
206      * facing when it is placed flat.
207      *
208      * @return azimuth angle in degrees or Double.NaN if not available
209      * @hide
210      */
211     @SystemApi
212     @FloatRange(from = 0.0, to = 360.0)
getAzimuthAngle()213     public double getAzimuthAngle() {
214         return mAzimuthAngle;
215     }
216 
217     /**
218      * Error of azimuth angle measurement in degrees.
219      *
220      * <p>Must be a positive value.
221      *
222      * @return azimuth angle measurement error in degrees or Double.NaN if not available
223      * @hide
224      */
225     @SystemApi
getErrorAzimuthAngle()226     public double getErrorAzimuthAngle() {
227         return mErrorAzimuthAngle;
228     }
229 
230     /**
231      * Altitude Angle measurement in degrees.
232      *
233      * <p>Altitude of remote device in horizontal coordinate system, this is the angle between the
234      * remote device and the top edge of local device. When local device is placed flat, the angle
235      * of the zenith is 90, the angle of the nadir is -90.
236      *
237      * <p>See: https://en.wikipedia.org/wiki/Horizontal_coordinate_system
238      *
239      * @return altitude angle in degrees or Double.NaN if not available
240      * @hide
241      */
242     @SystemApi
243     @FloatRange(from = -90.0, to = 90.0)
getAltitudeAngle()244     public double getAltitudeAngle() {
245         return mAltitudeAngle;
246     }
247 
248     /**
249      * Error of altitude angle measurement in degrees.
250      *
251      * <p>Must be a positive value.
252      *
253      * @return altitude angle measurement error in degrees or Double.NaN if not available
254      * @hide
255      */
256     @SystemApi
getErrorAltitudeAngle()257     public double getErrorAltitudeAngle() {
258         return mErrorAltitudeAngle;
259     }
260 
261     /**
262      * Get estimated delay spread in meters of the measured channel. This is a measure of multipath
263      * richness of the channel.
264      *
265      * @return delay spread in meters in degrees or Double.NaN if not available
266      * @hide
267      */
268     @FlaggedApi(Flags.FLAG_CHANNEL_SOUNDING)
269     @SystemApi
getDelaySpreadMeters()270     public double getDelaySpreadMeters() {
271         return mDelaySpreadMeters;
272     }
273 
274     /**
275      * Get a normalized value from 0.0 (low confidence) to 1.0 (high confidence) representing the
276      * confidence of estimated distance.
277      *
278      * @return confidence of estimated distance or Double.NaN if not available
279      * @hide
280      */
281     @FlaggedApi(Flags.FLAG_CHANNEL_SOUNDING)
282     @SystemApi
283     @FloatRange(from = 0.0, to = 1.0)
getConfidenceLevel()284     public double getConfidenceLevel() {
285         return mConfidenceLevel;
286     }
287 
288     /**
289      * Get a value that represents the chance of being attacked for the measurement.
290      *
291      * @return Nadm that represents the chance of being attacked for the measurement.
292      * @hide
293      */
294     @FlaggedApi(Flags.FLAG_CHANNEL_SOUNDING)
295     @SystemApi
296     @Nadm
getDetectedAttackLevel()297     public int getDetectedAttackLevel() {
298         return mDetectedAttackLevel;
299     }
300 
301     /**
302      * Get estimated velocity, in the direction of line between two devices, of the moving object in
303      * meters/sec.
304      *
305      * @return Estimated velocity, in the direction of line between two devices, of the moving
306      *     object in meters/sec.
307      * @hide
308      */
309     @FlaggedApi(Flags.FLAG_CHANNEL_SOUNDING)
310     @SystemApi
getVelocityMetersPerSecond()311     public double getVelocityMetersPerSecond() {
312         return mVelocityMetersPerSecond;
313     }
314 
315     /**
316      * {@inheritDoc}
317      *
318      * @hide
319      */
320     @Override
describeContents()321     public int describeContents() {
322         return 0;
323     }
324 
325     /**
326      * {@inheritDoc}
327      *
328      * @hide
329      */
330     @Override
writeToParcel(Parcel out, int flags)331     public void writeToParcel(Parcel out, int flags) {
332         out.writeDouble(mMeters);
333         out.writeDouble(mErrorMeters);
334         out.writeDouble(mAzimuthAngle);
335         out.writeDouble(mErrorAzimuthAngle);
336         out.writeDouble(mAltitudeAngle);
337         out.writeDouble(mErrorAltitudeAngle);
338         out.writeDouble(mDelaySpreadMeters);
339         out.writeDouble(mConfidenceLevel);
340         out.writeInt(mDetectedAttackLevel);
341         out.writeDouble(mVelocityMetersPerSecond);
342     }
343 
344     /**
345      * @hide *
346      */
347     @Override
toString()348     public String toString() {
349         return "DistanceMeasurement["
350                 + "meters: "
351                 + mMeters
352                 + ", errorMeters: "
353                 + mErrorMeters
354                 + ", azimuthAngle: "
355                 + mAzimuthAngle
356                 + ", errorAzimuthAngle: "
357                 + mErrorAzimuthAngle
358                 + ", altitudeAngle: "
359                 + mAltitudeAngle
360                 + ", errorAltitudeAngle: "
361                 + mErrorAltitudeAngle
362                 + ", delaySpreadMeters: "
363                 + mDelaySpreadMeters
364                 + ", confidenceLevel: "
365                 + mConfidenceLevel
366                 + ", detectedAttackLevel: "
367                 + mDetectedAttackLevel
368                 + ", velocityMetersPerSecond: "
369                 + mVelocityMetersPerSecond
370                 + "]";
371     }
372 
373     /** A {@link Parcelable.Creator} to create {@link DistanceMeasurementResult} from parcel. */
374     public static final @NonNull Parcelable.Creator<DistanceMeasurementResult> CREATOR =
375             new Parcelable.Creator<DistanceMeasurementResult>() {
376                 @Override
377                 public @NonNull DistanceMeasurementResult createFromParcel(@NonNull Parcel in) {
378                     return new Builder(in.readDouble(), in.readDouble())
379                             .setAzimuthAngle(in.readDouble())
380                             .setErrorAzimuthAngle(in.readDouble())
381                             .setAltitudeAngle(in.readDouble())
382                             .setErrorAltitudeAngle(in.readDouble())
383                             .setDelaySpreadMeters(in.readDouble())
384                             .setConfidenceLevel(in.readDouble())
385                             .setDetectedAttackLevel(in.readInt())
386                             .setVelocityMetersPerSecond(in.readDouble())
387                             .build();
388                 }
389 
390                 @Override
391                 public @NonNull DistanceMeasurementResult[] newArray(int size) {
392                     return new DistanceMeasurementResult[size];
393                 }
394             };
395 
396     /**
397      * Builder for {@link DistanceMeasurementResult}.
398      *
399      * @hide
400      */
401     @SystemApi
402     public static final class Builder {
403         private double mMeters = Double.NaN;
404         private double mErrorMeters = Double.NaN;
405         private double mAzimuthAngle = Double.NaN;
406         private double mErrorAzimuthAngle = Double.NaN;
407         private double mAltitudeAngle = Double.NaN;
408         private double mErrorAltitudeAngle = Double.NaN;
409         private double mDelaySpreadMeters = Double.NaN;
410         private double mConfidenceLevel = Double.NaN;
411         private int mDetectedAttackLevel = NADM_UNKNOWN;
412         private double mVelocityMetersPerSecond = Double.NaN;
413 
414         /**
415          * Constructor of the Builder.
416          *
417          * @param meters distance in meters
418          * @param errorMeters distance error in meters
419          * @throws IllegalArgumentException if meters is NaN or error is negative or NaN
420          */
Builder( @loatRangefrom = 0.0) double meters, @FloatRange(from = 0.0) double errorMeters)421         public Builder(
422                 @FloatRange(from = 0.0) double meters, @FloatRange(from = 0.0) double errorMeters) {
423             if (Double.isNaN(meters) || meters < 0.0) {
424                 throw new IllegalArgumentException("meters must be >= 0.0 and not NaN: " + meters);
425             }
426             if (Double.isNaN(errorMeters) || errorMeters < 0.0) {
427                 throw new IllegalArgumentException(
428                         "errorMeters must be >= 0.0 and not NaN: " + errorMeters);
429             }
430             mMeters = meters;
431             mErrorMeters = errorMeters;
432         }
433 
434         /**
435          * Set the azimuth angle measurement in degrees.
436          *
437          * @param angle azimuth angle in degrees
438          * @throws IllegalArgumentException if value is invalid
439          * @hide
440          */
441         @SystemApi
442         @NonNull
setAzimuthAngle(@loatRangefrom = 0.0, to = 360.0) double angle)443         public Builder setAzimuthAngle(@FloatRange(from = 0.0, to = 360.0) double angle) {
444             if (angle > 360.0 || angle < 0.0) {
445                 throw new IllegalArgumentException(
446                         "angle must be in the range from 0.0 to 360.0 : " + angle);
447             }
448             mAzimuthAngle = angle;
449             return this;
450         }
451 
452         /**
453          * Set the azimuth angle error in degrees.
454          *
455          * @param angle azimuth angle error in degrees
456          * @throws IllegalArgumentException if value is invalid
457          * @hide
458          */
459         @SystemApi
460         @NonNull
setErrorAzimuthAngle(@loatRangefrom = 0.0, to = 360.0) double angle)461         public Builder setErrorAzimuthAngle(@FloatRange(from = 0.0, to = 360.0) double angle) {
462             if (angle > 360.0 || angle < 0.0) {
463                 throw new IllegalArgumentException(
464                         "error angle must be in the range from 0.0 to 360.0 : " + angle);
465             }
466             mErrorAzimuthAngle = angle;
467             return this;
468         }
469 
470         /**
471          * Set the altitude angle measurement in degrees.
472          *
473          * @param angle altitude angle in degrees
474          * @throws IllegalArgumentException if value is invalid
475          * @hide
476          */
477         @SystemApi
478         @NonNull
setAltitudeAngle(@loatRangefrom = -90.0, to = 90.0) double angle)479         public Builder setAltitudeAngle(@FloatRange(from = -90.0, to = 90.0) double angle) {
480             if (angle > 90.0 || angle < -90.0) {
481                 throw new IllegalArgumentException(
482                         "angle must be in the range from -90.0 to 90.0 : " + angle);
483             }
484             mAltitudeAngle = angle;
485             return this;
486         }
487 
488         /**
489          * Set the altitude angle error in degrees.
490          *
491          * @param angle altitude angle error in degrees
492          * @throws IllegalArgumentException if value is invalid
493          * @hide
494          */
495         @SystemApi
496         @NonNull
setErrorAltitudeAngle(@loatRangefrom = 0.0, to = 180.0) double angle)497         public Builder setErrorAltitudeAngle(@FloatRange(from = 0.0, to = 180.0) double angle) {
498             if (angle > 180.0 || angle < 0.0) {
499                 throw new IllegalArgumentException(
500                         "error angle must be in the range from 0.0 to 180.0 : " + angle);
501             }
502             mErrorAltitudeAngle = angle;
503             return this;
504         }
505 
506         /**
507          * Set the estimated delay spread in meters.
508          *
509          * @param delaySpreadMeters estimated delay spread in meters
510          * @throws IllegalArgumentException if value is invalid
511          * @hide
512          */
513         @FlaggedApi(Flags.FLAG_CHANNEL_SOUNDING)
514         @SystemApi
515         @NonNull
setDelaySpreadMeters(double delaySpreadMeters)516         public Builder setDelaySpreadMeters(double delaySpreadMeters) {
517             if (delaySpreadMeters < 0.0) {
518                 throw new IllegalArgumentException("delaySpreadMeters must be > 0.0");
519             }
520             mDelaySpreadMeters = delaySpreadMeters;
521             return this;
522         }
523 
524         /**
525          * Set the confidence of estimated distance.
526          *
527          * @param confidenceLevel a normalized value from 0.0 (low confidence) to 1.0 (high
528          *     confidence) representing the confidence of estimated distance
529          * @throws IllegalArgumentException if value is invalid
530          * @hide
531          */
532         @FlaggedApi(Flags.FLAG_CHANNEL_SOUNDING)
533         @SystemApi
534         @NonNull
setConfidenceLevel( @loatRangefrom = 0.0, to = 1.0) double confidenceLevel)535         public Builder setConfidenceLevel(
536                 @FloatRange(from = 0.0, to = 1.0) double confidenceLevel) {
537             if (confidenceLevel > 1.0 || confidenceLevel < 0.0) {
538                 throw new IllegalArgumentException(
539                         "error confidenceLevel must be in the range from 0.0 to 100.0 : "
540                                 + confidenceLevel);
541             }
542             mConfidenceLevel = confidenceLevel;
543             return this;
544         }
545 
546         /**
547          * Set the value that represents the chance of being attacked for the measurement.
548          *
549          * @param detectedAttackLevel a value that represents the chance of being attacked for the
550          *     measurement.
551          * @throws IllegalArgumentException if value is invalid
552          * @hide
553          */
554         @FlaggedApi(Flags.FLAG_CHANNEL_SOUNDING)
555         @SystemApi
556         @NonNull
setDetectedAttackLevel(@adm int detectedAttackLevel)557         public Builder setDetectedAttackLevel(@Nadm int detectedAttackLevel) {
558             switch (detectedAttackLevel) {
559                 case NADM_ATTACK_IS_EXTREMELY_UNLIKELY:
560                 case NADM_ATTACK_IS_VERY_UNLIKELY:
561                 case NADM_ATTACK_IS_UNLIKELY:
562                 case NADM_ATTACK_IS_POSSIBLE:
563                 case NADM_ATTACK_IS_LIKELY:
564                 case NADM_ATTACK_IS_VERY_LIKELY:
565                 case NADM_ATTACK_IS_EXTREMELY_LIKELY:
566                 case NADM_UNKNOWN:
567                     mDetectedAttackLevel = detectedAttackLevel;
568                     break;
569                 default:
570                     throw new IllegalArgumentException("Invalid value " + detectedAttackLevel);
571             }
572             return this;
573         }
574 
575         /**
576          * Set estimated velocity, in the direction of line between two devices, of the moving
577          * object in meters/sec.
578          *
579          * @param velocityMetersPerSecond estimated velocity in meters/sec.
580          * @hide
581          */
582         @FlaggedApi(Flags.FLAG_CHANNEL_SOUNDING)
583         @SystemApi
584         @NonNull
setVelocityMetersPerSecond(double velocityMetersPerSecond)585         public Builder setVelocityMetersPerSecond(double velocityMetersPerSecond) {
586             mVelocityMetersPerSecond = velocityMetersPerSecond;
587             return this;
588         }
589 
590         /**
591          * Builds the {@link DistanceMeasurementResult} object.
592          *
593          * @throws IllegalStateException if meters, error, or confidence are not set
594          * @hide
595          */
596         @SystemApi
597         @NonNull
build()598         public DistanceMeasurementResult build() {
599             return new DistanceMeasurementResult(
600                     mMeters,
601                     mErrorMeters,
602                     mAzimuthAngle,
603                     mErrorAzimuthAngle,
604                     mAltitudeAngle,
605                     mErrorAltitudeAngle,
606                     mDelaySpreadMeters,
607                     mConfidenceLevel,
608                     mDetectedAttackLevel,
609                     mVelocityMetersPerSecond);
610         }
611     }
612 }
613