/* * Copyright (C) 2022 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.location; import static android.hardware.gnss.measurement_corrections.SingleSatCorrection.ExcessPathInfo.EXCESS_PATH_INFO_HAS_ATTENUATION; import static android.hardware.gnss.measurement_corrections.SingleSatCorrection.ExcessPathInfo.EXCESS_PATH_INFO_HAS_EXCESS_PATH_LENGTH; import static android.hardware.gnss.measurement_corrections.SingleSatCorrection.ExcessPathInfo.EXCESS_PATH_INFO_HAS_EXCESS_PATH_LENGTH_UNC; import static android.hardware.gnss.measurement_corrections.SingleSatCorrection.ExcessPathInfo.EXCESS_PATH_INFO_HAS_REFLECTING_PLANE; import android.annotation.FloatRange; import android.annotation.NonNull; import android.annotation.Nullable; import android.annotation.SystemApi; import android.os.Parcel; import android.os.Parcelable; import com.android.internal.util.Preconditions; import java.util.Objects; /** * Contains the info of an excess path signal caused by reflection * * @hide */ @SystemApi public final class GnssExcessPathInfo implements Parcelable { private static final int HAS_EXCESS_PATH_LENGTH_MASK = EXCESS_PATH_INFO_HAS_EXCESS_PATH_LENGTH; private static final int HAS_EXCESS_PATH_LENGTH_UNC_MASK = EXCESS_PATH_INFO_HAS_EXCESS_PATH_LENGTH_UNC; private static final int HAS_REFLECTING_PLANE_MASK = EXCESS_PATH_INFO_HAS_REFLECTING_PLANE; private static final int HAS_ATTENUATION_MASK = EXCESS_PATH_INFO_HAS_ATTENUATION; /* A bitmask of fields present in this object (see HAS_* constants defined above) */ private final int mFlags; private final float mExcessPathLengthMeters; private final float mExcessPathLengthUncertaintyMeters; @Nullable private final GnssReflectingPlane mReflectingPlane; private final float mAttenuationDb; private GnssExcessPathInfo( int flags, float excessPathLengthMeters, float excessPathLengthUncertaintyMeters, @Nullable GnssReflectingPlane reflectingPlane, float attenuationDb) { mFlags = flags; mExcessPathLengthMeters = excessPathLengthMeters; mExcessPathLengthUncertaintyMeters = excessPathLengthUncertaintyMeters; mReflectingPlane = reflectingPlane; mAttenuationDb = attenuationDb; } /** * Gets a bitmask of fields present in this object. * *

This API exists for JNI since it is easier for JNI to get one integer flag than looking up * several has* methods. * @hide */ public int getFlags() { return mFlags; } /** Returns {@code true} if {@link #getExcessPathLengthMeters()} is valid. */ public boolean hasExcessPathLength() { return (mFlags & HAS_EXCESS_PATH_LENGTH_MASK) != 0; } /** * Returns the excess path length to be subtracted from pseudorange before using it in * calculating location. * *

{@link #hasExcessPathLength()} must be true when calling this method. Otherwise, an * {@link UnsupportedOperationException} will be thrown. */ @FloatRange(from = 0.0f) public float getExcessPathLengthMeters() { if (!hasExcessPathLength()) { throw new UnsupportedOperationException( "getExcessPathLengthMeters() is not supported when hasExcessPathLength() is " + "false"); } return mExcessPathLengthMeters; } /** Returns {@code true} if {@link #getExcessPathLengthUncertaintyMeters()} is valid. */ public boolean hasExcessPathLengthUncertainty() { return (mFlags & HAS_EXCESS_PATH_LENGTH_UNC_MASK) != 0; } /** * Returns the error estimate (1-sigma) for the excess path length estimate. * *

{@link #hasExcessPathLengthUncertainty()} must be true when calling this method. * Otherwise, an {@link UnsupportedOperationException} will be thrown. */ @FloatRange(from = 0.0f) public float getExcessPathLengthUncertaintyMeters() { if (!hasExcessPathLengthUncertainty()) { throw new UnsupportedOperationException( "getExcessPathLengthUncertaintyMeters() is not supported when " + "hasExcessPathLengthUncertainty() is false"); } return mExcessPathLengthUncertaintyMeters; } /** * Returns {@code true} if {@link #getReflectingPlane()} is valid. * *

Returns false if the satellite signal goes through multiple reflections or if reflection * plane serving is not supported. */ public boolean hasReflectingPlane() { return (mFlags & HAS_REFLECTING_PLANE_MASK) != 0; } /** * Returns the reflecting plane characteristics at which the signal has bounced. * *

{@link #hasReflectingPlane()} must be true when calling this method. Otherwise, an * {@link UnsupportedOperationException} will be thrown. */ @NonNull public GnssReflectingPlane getReflectingPlane() { if (!hasReflectingPlane()) { throw new UnsupportedOperationException( "getReflectingPlane() is not supported when hasReflectingPlane() is false"); } return mReflectingPlane; } /** Returns {@code true} if {@link #getAttenuationDb()} is valid. */ public boolean hasAttenuation() { return (mFlags & HAS_ATTENUATION_MASK) != 0; } /** * Returns the expected reduction of signal strength of this path in non-negative dB. * *

{@link #hasAttenuation()} must be true when calling this method. Otherwise, an * {@link UnsupportedOperationException} will be thrown. */ @FloatRange(from = 0.0f) public float getAttenuationDb() { if (!hasAttenuation()) { throw new UnsupportedOperationException( "getAttenuationDb() is not supported when hasAttenuation() is false"); } return mAttenuationDb; } @Override public int describeContents() { return 0; } @Override public void writeToParcel(@NonNull Parcel parcel, int parcelFlags) { parcel.writeInt(mFlags); if (hasExcessPathLength()) { parcel.writeFloat(mExcessPathLengthMeters); } if (hasExcessPathLengthUncertainty()) { parcel.writeFloat(mExcessPathLengthUncertaintyMeters); } if (hasReflectingPlane()) { mReflectingPlane.writeToParcel(parcel, parcelFlags); } if (hasAttenuation()) { parcel.writeFloat(mAttenuationDb); } } public static final @NonNull Creator CREATOR = new Creator() { @Override @NonNull public GnssExcessPathInfo createFromParcel(@NonNull Parcel parcel) { int flags = parcel.readInt(); float excessPathLengthMeters = (flags & HAS_EXCESS_PATH_LENGTH_MASK) != 0 ? parcel.readFloat() : 0; float excessPathLengthUncertaintyMeters = (flags & HAS_EXCESS_PATH_LENGTH_UNC_MASK) != 0 ? parcel.readFloat() : 0; GnssReflectingPlane reflectingPlane = (flags & HAS_REFLECTING_PLANE_MASK) != 0 ? GnssReflectingPlane.CREATOR.createFromParcel(parcel) : null; float attenuationDb = (flags & HAS_ATTENUATION_MASK) != 0 ? parcel.readFloat() : 0; return new GnssExcessPathInfo(flags, excessPathLengthMeters, excessPathLengthUncertaintyMeters, reflectingPlane, attenuationDb); } @Override public GnssExcessPathInfo[] newArray(int i) { return new GnssExcessPathInfo[i]; } }; @Override public boolean equals(Object obj) { if (obj instanceof GnssExcessPathInfo) { GnssExcessPathInfo that = (GnssExcessPathInfo) obj; return this.mFlags == that.mFlags && (!hasExcessPathLength() || Float.compare(this.mExcessPathLengthMeters, that.mExcessPathLengthMeters) == 0) && (!hasExcessPathLengthUncertainty() || Float.compare( this.mExcessPathLengthUncertaintyMeters, that.mExcessPathLengthUncertaintyMeters) == 0) && (!hasReflectingPlane() || Objects.equals(this.mReflectingPlane, that.mReflectingPlane)) && (!hasAttenuation() || Float.compare(this.mAttenuationDb, that.mAttenuationDb) == 0); } return false; } @Override public int hashCode() { return Objects.hash(mFlags, mExcessPathLengthMeters, mExcessPathLengthUncertaintyMeters, mReflectingPlane, mAttenuationDb); } @NonNull @Override public String toString() { StringBuilder builder = new StringBuilder("GnssExcessPathInfo["); if (hasExcessPathLength()) { builder.append(" ExcessPathLengthMeters=").append(mExcessPathLengthMeters); } if (hasExcessPathLengthUncertainty()) { builder.append(" ExcessPathLengthUncertaintyMeters=").append( mExcessPathLengthUncertaintyMeters); } if (hasReflectingPlane()) { builder.append(" ReflectingPlane=").append(mReflectingPlane); } if (hasAttenuation()) { builder.append(" AttenuationDb=").append(mAttenuationDb); } builder.append(']'); return builder.toString(); } /** Builder for {@link GnssExcessPathInfo}. */ public static final class Builder { private int mFlags; private float mExcessPathLengthMeters; private float mExcessPathLengthUncertaintyMeters; @Nullable private GnssReflectingPlane mReflectingPlane; private float mAttenuationDb; /** Constructor for {@link Builder}. */ public Builder() {} /** * Sets the excess path length to be subtracted from pseudorange before using it in * calculating location. */ @NonNull public Builder setExcessPathLengthMeters( @FloatRange(from = 0.0f) float excessPathLengthMeters) { Preconditions.checkArgumentInRange(excessPathLengthMeters, 0, Float.MAX_VALUE, "excessPathLengthMeters"); mExcessPathLengthMeters = excessPathLengthMeters; mFlags |= HAS_EXCESS_PATH_LENGTH_MASK; return this; } /** * Clears the excess path length. * *

This is to negate {@link #setExcessPathLengthMeters} call. */ @NonNull public Builder clearExcessPathLengthMeters() { mExcessPathLengthMeters = 0; mFlags &= ~HAS_EXCESS_PATH_LENGTH_MASK; return this; } /** Sets the error estimate (1-sigma) for the excess path length estimate */ @NonNull public Builder setExcessPathLengthUncertaintyMeters( @FloatRange(from = 0.0f) float excessPathLengthUncertaintyMeters) { Preconditions.checkArgumentInRange(excessPathLengthUncertaintyMeters, 0, Float.MAX_VALUE, "excessPathLengthUncertaintyMeters"); mExcessPathLengthUncertaintyMeters = excessPathLengthUncertaintyMeters; mFlags |= HAS_EXCESS_PATH_LENGTH_UNC_MASK; return this; } /** * Clears the error estimate (1-sigma) for the excess path length estimate * *

This is to negate {@link #setExcessPathLengthUncertaintyMeters} call. */ @NonNull public Builder clearExcessPathLengthUncertaintyMeters() { mExcessPathLengthUncertaintyMeters = 0; mFlags &= ~HAS_EXCESS_PATH_LENGTH_UNC_MASK; return this; } /** Sets the reflecting plane information */ @NonNull public Builder setReflectingPlane(@Nullable GnssReflectingPlane reflectingPlane) { mReflectingPlane = reflectingPlane; if (reflectingPlane != null) { mFlags |= HAS_REFLECTING_PLANE_MASK; } else { mFlags &= ~HAS_REFLECTING_PLANE_MASK; } return this; } /** * Sets the attenuation value in dB. */ @NonNull public Builder setAttenuationDb(@FloatRange(from = 0.0f) float attenuationDb) { Preconditions.checkArgumentInRange(attenuationDb, 0, Float.MAX_VALUE, "attenuationDb"); mAttenuationDb = attenuationDb; mFlags |= HAS_ATTENUATION_MASK; return this; } /** * Clears the attenuation value in dB. * *

This is to negate {@link #setAttenuationDb(float)} call. */ @NonNull public Builder clearAttenuationDb() { mAttenuationDb = 0; mFlags &= ~HAS_ATTENUATION_MASK; return this; } /** Builds a {@link GnssExcessPathInfo} instance as specified by this builder. */ @NonNull public GnssExcessPathInfo build() { return new GnssExcessPathInfo( mFlags, mExcessPathLengthMeters, mExcessPathLengthUncertaintyMeters, mReflectingPlane, mAttenuationDb); } } }