| /* |
| * Copyright (C) 2018 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.net.wifi.rtt; |
| |
| import static java.lang.annotation.RetentionPolicy.SOURCE; |
| |
| import android.annotation.IntDef; |
| import android.annotation.NonNull; |
| import android.annotation.Nullable; |
| import android.annotation.SystemApi; |
| import android.location.Address; |
| import android.location.Location; |
| import android.net.MacAddress; |
| import android.net.Uri; |
| import android.net.wifi.rtt.CivicLocationKeys.CivicLocationKeysType; |
| import android.os.Parcel; |
| import android.os.Parcelable; |
| import android.text.TextUtils; |
| import android.util.SparseArray; |
| import android.webkit.MimeTypeMap; |
| |
| import java.lang.annotation.Retention; |
| import java.nio.charset.StandardCharsets; |
| import java.util.ArrayList; |
| import java.util.Arrays; |
| import java.util.Collections; |
| import java.util.List; |
| import java.util.Objects; |
| |
| /** |
| * ResponderLocation is both a Location Configuration Information (LCI) decoder and a Location Civic |
| * Report (LCR) decoder for information received from a Wi-Fi Access Point (AP) during Wi-Fi RTT |
| * ranging process. |
| * |
| * <p>This is based on the IEEE P802.11-REVmc/D8.0 spec section 9.4.2.22, under Measurement Report |
| * Element. Subelement location data-fields parsed from separate input LCI and LCR Information |
| * Elements are unified in this class.</p> |
| * |
| * <p>Note: The information provided by this class is broadcast by a responder (usually an Access |
| * Point), and passed on as-is. There is no guarantee this information is accurate or correct, and |
| * as a result developers should carefully consider how this information should be used and provide |
| * corresponding advice to users.</p> |
| */ |
| public final class ResponderLocation implements Parcelable { |
| private static final int BYTE_MASK = 0xFF; |
| private static final int LSB_IN_BYTE = 0x01; |
| private static final int MSB_IN_BYTE = 0x80; |
| private static final int MIN_BUFFER_SIZE = 3; // length of LEAD_LCI_ELEMENT_BYTES |
| private static final int MAX_BUFFER_SIZE = 256; |
| |
| // Information Element (IE) fields |
| private static final byte MEASUREMENT_TOKEN_AUTONOMOUS = 0x01; |
| private static final byte MEASUREMENT_REPORT_MODE = 0x00; |
| private static final byte MEASUREMENT_TYPE_LCI = 0x08; |
| private static final byte MEASUREMENT_TYPE_LCR = 0x0b; |
| |
| // LCI Subelement IDs |
| private static final byte SUBELEMENT_LCI = 0x00; |
| private static final byte SUBELEMENT_Z = 0x04; |
| private static final byte SUBELEMENT_USAGE = 0x06; |
| private static final byte SUBELEMENT_BSSID_LIST = 0x07; |
| |
| // LCI Subelement Lengths |
| private static final int SUBELEMENT_LCI_LENGTH = 16; |
| private static final int SUBELEMENT_Z_LENGTH = 6; |
| private static final int SUBELEMENT_USAGE_LENGTH1 = 1; |
| private static final int SUBELEMENT_USAGE_LENGTH3 = 3; |
| private static final int SUBELEMENT_BSSID_LIST_MIN_BUFFER_LENGTH = 1; |
| |
| private static final byte[] LEAD_LCI_ELEMENT_BYTES = { |
| MEASUREMENT_TOKEN_AUTONOMOUS, MEASUREMENT_REPORT_MODE, MEASUREMENT_TYPE_LCI |
| }; |
| |
| // Subelement LCI constants |
| |
| /* The LCI subelement bit-field lengths are described in Figure 9-214 of the REVmc spec and |
| represented here as a an array of integers */ |
| private static final int[] SUBELEMENT_LCI_BIT_FIELD_LENGTHS = { |
| 6, 34, 6, 34, 4, 6, 30, 3, 1, 1, 1, 2 |
| }; |
| private static final int LATLNG_FRACTION_BITS = 25; |
| private static final int LATLNG_UNCERTAINTY_BASE = 8; |
| private static final int ALTITUDE_FRACTION_BITS = 8; |
| private static final int ALTITUDE_UNCERTAINTY_BASE = 21; |
| private static final double LAT_ABS_LIMIT = 90.0; |
| private static final double LNG_ABS_LIMIT = 180.0; |
| private static final int UNCERTAINTY_UNDEFINED = 0; |
| |
| // Subelement LCI fields indices |
| private static final int SUBELEMENT_LCI_LAT_UNCERTAINTY_INDEX = 0; |
| private static final int SUBELEMENT_LCI_LAT_INDEX = 1; |
| private static final int SUBELEMENT_LCI_LNG_UNCERTAINTY_INDEX = 2; |
| private static final int SUBELEMENT_LCI_LNG_INDEX = 3; |
| private static final int SUBELEMENT_LCI_ALT_TYPE_INDEX = 4; |
| private static final int SUBELEMENT_LCI_ALT_UNCERTAINTY_INDEX = 5; |
| private static final int SUBELEMENT_LCI_ALT_INDEX = 6; |
| private static final int SUBELEMENT_LCI_DATUM_INDEX = 7; |
| private static final int SUBELEMENT_LCI_REGLOC_AGREEMENT_INDEX = 8; |
| private static final int SUBELEMENT_LCI_REGLOC_DSE_INDEX = 9; |
| private static final int SUBELEMENT_LCI_DEPENDENT_STA_INDEX = 10; |
| private static final int SUBELEMENT_LCI_VERSION_INDEX = 11; |
| |
| /** |
| * The Altitude value is interpreted based on the Altitude Type, and the selected mDatum. |
| * |
| * @hide |
| */ |
| @Retention(SOURCE) |
| @IntDef({ALTITUDE_UNDEFINED, ALTITUDE_METERS, ALTITUDE_FLOORS}) |
| public @interface AltitudeType { |
| } |
| |
| /** |
| * Altitude is not defined for the Responder. |
| * The altitude and altitude uncertainty should not be used: see section 2.4 of IETF RFC 6225. |
| */ |
| public static final int ALTITUDE_UNDEFINED = 0; |
| /** Responder Altitude is measured in meters. */ |
| public static final int ALTITUDE_METERS = 1; |
| /** Responder Altitude is measured in floors. */ |
| public static final int ALTITUDE_FLOORS = 2; |
| |
| /** |
| * The Datum value determines how coordinates are organized in relation to the real world. |
| * |
| * @hide |
| */ |
| @Retention(SOURCE) |
| @IntDef({DATUM_UNDEFINED, DATUM_WGS84, DATUM_NAD83_NAV88, DATUM_NAD83_MLLW}) |
| public @interface DatumType { |
| } |
| |
| /** Datum is not defined. */ |
| public static final int DATUM_UNDEFINED = 0; |
| /** Datum used is WGS84. */ |
| public static final int DATUM_WGS84 = 1; |
| /** Datum used is NAD83 NAV88. */ |
| public static final int DATUM_NAD83_NAV88 = 2; |
| /** Datum used is NAD83 MLLW. */ |
| public static final int DATUM_NAD83_MLLW = 3; |
| |
| |
| /** Version of the LCI protocol is 1.0, the only defined protocol at this time. */ |
| public static final int LCI_VERSION_1 = 1; |
| |
| /** Provider/Source of the location */ |
| private static final String LOCATION_PROVIDER = "WiFi Access Point"; |
| |
| // LCI Subelement Z constants |
| private static final int[] SUBELEMENT_Z_BIT_FIELD_LENGTHS = {2, 14, 24, 8}; |
| private static final int Z_FLOOR_NUMBER_FRACTION_BITS = 4; |
| private static final int Z_FLOOR_HEIGHT_FRACTION_BITS = 12; |
| private static final int Z_MAX_HEIGHT_UNCERTAINTY_FACTOR = 25; |
| |
| // LCI Subelement Z fields indices |
| private static final int SUBELEMENT_Z_LAT_EXPECTED_TO_MOVE_INDEX = 0; |
| private static final int SUBELEMENT_Z_FLOOR_NUMBER_INDEX = 1; |
| private static final int SUBELEMENT_Z_HEIGHT_ABOVE_FLOOR_INDEX = 2; |
| private static final int SUBELEMENT_Z_HEIGHT_ABOVE_FLOOR_UNCERTAINTY_INDEX = 3; |
| |
| // LCI Subelement Usage Rules constants |
| private static final int SUBELEMENT_USAGE_MASK_RETRANSMIT = 0x01; |
| private static final int SUBELEMENT_USAGE_MASK_RETENTION_EXPIRES = 0x02; |
| private static final int SUBELEMENT_USAGE_MASK_STA_LOCATION_POLICY = 0x04; |
| |
| // LCI Subelement Usage Rules field indices |
| private static final int SUBELEMENT_USAGE_PARAMS_INDEX = 0; // 8 bits |
| |
| // LCI Subelement BSSID List |
| private static final int SUBELEMENT_BSSID_MAX_INDICATOR_INDEX = 0; |
| private static final int SUBELEMENT_BSSID_LIST_INDEX = 1; |
| private static final int BYTES_IN_A_BSSID = 6; |
| |
| /** |
| * The Expected-To-Move value determines how mobile we expect the STA to be. |
| * |
| * @hide |
| */ |
| @Retention(SOURCE) |
| @IntDef({LOCATION_FIXED, LOCATION_VARIABLE, LOCATION_MOVEMENT_UNKNOWN, LOCATION_RESERVED}) |
| public @interface ExpectedToMoveType { |
| } |
| |
| /** Location of responder is fixed (does not move) */ |
| public static final int LOCATION_FIXED = 0; |
| /** Location of the responder is variable, and may move */ |
| public static final int LOCATION_VARIABLE = 1; |
| /** Location of the responder is not known to be either fixed or variable. */ |
| public static final int LOCATION_MOVEMENT_UNKNOWN = 2; |
| /** Location of the responder status is a reserved value */ |
| public static final int LOCATION_RESERVED = 3; |
| |
| // LCR Subelement IDs |
| private static final byte SUBELEMENT_LOCATION_CIVIC = 0x00; |
| private static final byte SUBELEMENT_MAP_IMAGE = 0x05; |
| |
| // LCR Subelement Lengths |
| private static final int SUBELEMENT_LOCATION_CIVIC_MIN_LENGTH = 2; |
| private static final int SUBELEMENT_LOCATION_CIVIC_MAX_LENGTH = 256; |
| private static final int SUBELEMENT_MAP_IMAGE_URL_MAX_LENGTH = 256; |
| |
| private static final byte[] LEAD_LCR_ELEMENT_BYTES = { |
| MEASUREMENT_TOKEN_AUTONOMOUS, MEASUREMENT_REPORT_MODE, MEASUREMENT_TYPE_LCR |
| }; |
| |
| // LCR Location Civic Subelement |
| private static final int CIVIC_COUNTRY_CODE_INDEX = 0; |
| private static final int CIVIC_TLV_LIST_INDEX = 2; |
| |
| // LCR Map Image Subelement field indexes. |
| private static final int SUBELEMENT_IMAGE_MAP_TYPE_INDEX = 0; |
| private static final int MAP_TYPE_URL_DEFINED = 0; |
| private static final String[] SUPPORTED_IMAGE_FILE_EXTENSIONS = { |
| "", |
| "png", |
| "gif", |
| "jpg", |
| "svg", |
| "dxf", |
| "dwg", |
| "dwf", |
| "cad", |
| "tif", |
| "gml", |
| "kml", |
| "bmp", |
| "pgm", |
| "ppm", |
| "xbm", |
| "xpm", |
| "ico" |
| }; |
| |
| // General LCI and LCR state |
| private final boolean mIsValid; |
| |
| /* |
| These members are not final because we are not sure if the corresponding subelement will be |
| present until after the parsing process. However, the default value should be set as listed. |
| */ |
| private boolean mIsLciValid = false; |
| private boolean mIsZValid = false; |
| private boolean mIsUsageValid = true; // By default this is assumed true |
| private boolean mIsBssidListValid = false; |
| private boolean mIsLocationCivicValid = false; |
| private boolean mIsMapImageValid = false; |
| |
| // LCI Subelement LCI state |
| private double mLatitudeUncertainty; |
| private double mLatitude; |
| private double mLongitudeUncertainty; |
| private double mLongitude; |
| private int mAltitudeType; |
| private double mAltitudeUncertainty; |
| private double mAltitude; |
| private int mDatum; |
| private boolean mLciRegisteredLocationAgreement; |
| private boolean mLciRegisteredLocationDse; |
| private boolean mLciDependentStation; |
| private int mLciVersion; |
| |
| // LCI Subelement Z state |
| private int mExpectedToMove; |
| private double mFloorNumber; |
| private double mHeightAboveFloorMeters; |
| private double mHeightAboveFloorUncertaintyMeters; |
| |
| // LCI Subelement Usage Rights state |
| private boolean mUsageRetransmit; |
| private boolean mUsageRetentionExpires; |
| private boolean mUsageExtraInfoOnAssociation; |
| |
| // LCI Subelement BSSID List state |
| private ArrayList<MacAddress> mBssidList; |
| |
| // LCR Subelement Location Civic state |
| private String mCivicLocationCountryCode; |
| private String mCivicLocationString; |
| private CivicLocation mCivicLocation; |
| |
| // LCR Subelement Map Image state |
| private int mMapImageType; |
| private Uri mMapImageUri; |
| |
| /** |
| * Constructor |
| * |
| * @param lciBuffer the bytes received in the LCI Measurement Report Information Element |
| * @param lcrBuffer the bytes received in the LCR Measurement Report Information Element |
| * |
| * @hide |
| */ |
| public ResponderLocation(byte[] lciBuffer, byte[] lcrBuffer) { |
| boolean isLciIeValid = false; |
| boolean isLcrIeValid = false; |
| setLciSubelementDefaults(); |
| setZaxisSubelementDefaults(); |
| setUsageSubelementDefaults(); |
| setBssidListSubelementDefaults(); |
| setCivicLocationSubelementDefaults(); |
| setMapImageSubelementDefaults(); |
| if (lciBuffer != null && lciBuffer.length > LEAD_LCI_ELEMENT_BYTES.length) { |
| isLciIeValid = parseInformationElementBuffer( |
| MEASUREMENT_TYPE_LCI, lciBuffer, LEAD_LCI_ELEMENT_BYTES); |
| } |
| if (lcrBuffer != null && lcrBuffer.length > LEAD_LCR_ELEMENT_BYTES.length) { |
| isLcrIeValid = parseInformationElementBuffer( |
| MEASUREMENT_TYPE_LCR, lcrBuffer, LEAD_LCR_ELEMENT_BYTES); |
| } |
| |
| boolean isLciValid = isLciIeValid && mIsUsageValid |
| && (mIsLciValid || mIsZValid || mIsBssidListValid); |
| |
| boolean isLcrValid = isLcrIeValid && mIsUsageValid |
| && (mIsLocationCivicValid || mIsMapImageValid); |
| |
| mIsValid = isLciValid || isLcrValid; |
| |
| if (!mIsValid) { |
| setLciSubelementDefaults(); |
| setZaxisSubelementDefaults(); |
| setCivicLocationSubelementDefaults(); |
| setMapImageSubelementDefaults(); |
| } |
| } |
| |
| private ResponderLocation(Parcel in) { |
| // Object Validation |
| mIsValid = in.readByte() != 0; |
| mIsLciValid = in.readByte() != 0; |
| mIsZValid = in.readByte() != 0; |
| mIsUsageValid = in.readByte() != 0; |
| mIsBssidListValid = in.readByte() != 0; |
| mIsLocationCivicValid = in.readByte() != 0; |
| mIsMapImageValid = in.readByte() != 0; |
| |
| // LCI Subelement LCI state |
| mLatitudeUncertainty = in.readDouble(); |
| mLatitude = in.readDouble(); |
| mLongitudeUncertainty = in.readDouble(); |
| mLongitude = in.readDouble(); |
| mAltitudeType = in.readInt(); |
| mAltitudeUncertainty = in.readDouble(); |
| mAltitude = in.readDouble(); |
| mDatum = in.readInt(); |
| mLciRegisteredLocationAgreement = in.readByte() != 0; |
| mLciRegisteredLocationDse = in.readByte() != 0; |
| mLciDependentStation = in.readByte() != 0; |
| mLciVersion = in.readInt(); |
| |
| // LCI Subelement Z state |
| mExpectedToMove = in.readInt(); |
| mFloorNumber = in.readDouble(); |
| mHeightAboveFloorMeters = in.readDouble(); |
| mHeightAboveFloorUncertaintyMeters = in.readDouble(); |
| |
| // LCI Usage Rights |
| mUsageRetransmit = in.readByte() != 0; |
| mUsageRetentionExpires = in.readByte() != 0; |
| mUsageExtraInfoOnAssociation = in.readByte() != 0; |
| |
| // LCI Subelement BSSID List |
| mBssidList = in.readArrayList(MacAddress.class.getClassLoader()); |
| |
| // LCR Subelement Location Civic |
| mCivicLocationCountryCode = in.readString(); |
| mCivicLocationString = in.readString(); |
| mCivicLocation = in.readParcelable(this.getClass().getClassLoader()); |
| |
| // LCR Subelement Map Image |
| mMapImageType = in.readInt(); |
| String urlString = in.readString(); |
| if (TextUtils.isEmpty(urlString)) { |
| mMapImageUri = null; |
| } else { |
| mMapImageUri = Uri.parse(urlString); |
| } |
| } |
| |
| public static final @android.annotation.NonNull Creator<ResponderLocation> CREATOR = new Creator<ResponderLocation>() { |
| @Override |
| public ResponderLocation createFromParcel(Parcel in) { |
| return new ResponderLocation(in); |
| } |
| |
| @Override |
| public ResponderLocation[] newArray(int size) { |
| return new ResponderLocation[size]; |
| } |
| }; |
| |
| @Override |
| public int describeContents() { |
| return 0; |
| } |
| |
| @Override |
| public void writeToParcel(Parcel parcel, int flags) { |
| // Object |
| parcel.writeByte((byte) (mIsValid ? 1 : 0)); |
| parcel.writeByte((byte) (mIsLciValid ? 1 : 0)); |
| parcel.writeByte((byte) (mIsZValid ? 1 : 0)); |
| parcel.writeByte((byte) (mIsUsageValid ? 1 : 0)); |
| parcel.writeByte((byte) (mIsBssidListValid ? 1 : 0)); |
| parcel.writeByte((byte) (mIsLocationCivicValid ? 1 : 0)); |
| parcel.writeByte((byte) (mIsMapImageValid ? 1 : 0)); |
| |
| // LCI Subelement LCI state |
| parcel.writeDouble(mLatitudeUncertainty); |
| parcel.writeDouble(mLatitude); |
| parcel.writeDouble(mLongitudeUncertainty); |
| parcel.writeDouble(mLongitude); |
| parcel.writeInt(mAltitudeType); |
| parcel.writeDouble(mAltitudeUncertainty); |
| parcel.writeDouble(mAltitude); |
| parcel.writeInt(mDatum); |
| parcel.writeByte((byte) (mLciRegisteredLocationAgreement ? 1 : 0)); |
| parcel.writeByte((byte) (mLciRegisteredLocationDse ? 1 : 0)); |
| parcel.writeByte((byte) (mLciDependentStation ? 1 : 0)); |
| parcel.writeInt(mLciVersion); |
| |
| // LCI Subelement Z state |
| parcel.writeInt(mExpectedToMove); |
| parcel.writeDouble(mFloorNumber); |
| parcel.writeDouble(mHeightAboveFloorMeters); |
| parcel.writeDouble(mHeightAboveFloorUncertaintyMeters); |
| |
| // LCI Usage Rights |
| parcel.writeByte((byte) (mUsageRetransmit ? 1 : 0)); |
| parcel.writeByte((byte) (mUsageRetentionExpires ? 1 : 0)); |
| parcel.writeByte((byte) (mUsageExtraInfoOnAssociation ? 1 : 0)); |
| |
| // LCI Subelement BSSID List |
| parcel.writeList(mBssidList); |
| |
| // LCR Subelement Location Civic |
| parcel.writeString(mCivicLocationCountryCode); |
| parcel.writeString(mCivicLocationString); |
| parcel.writeParcelable(mCivicLocation, flags); |
| |
| // LCR Subelement Map Image |
| parcel.writeInt(mMapImageType); |
| if (mMapImageUri != null) { |
| parcel.writeString(mMapImageUri.toString()); |
| } else { |
| parcel.writeString(""); |
| } |
| } |
| |
| /** |
| * Test if the Information Element (IE) is in the correct format, and then parse its Subelements |
| * based on their type, and setting state in this object when present. |
| * |
| * @return a boolean indicating the success of the parsing function |
| */ |
| private boolean parseInformationElementBuffer( |
| int ieType, byte[] buffer, byte[] expectedLeadBytes) { |
| int bufferPtr = 0; |
| int bufferLength = buffer.length; |
| |
| // Ensure the buffer size is within expected limits |
| if (bufferLength < MIN_BUFFER_SIZE || bufferLength > MAX_BUFFER_SIZE) { |
| return false; |
| } |
| |
| // Ensure the IE contains the correct leading bytes |
| byte[] leadBufferBytes = Arrays.copyOfRange(buffer, bufferPtr, expectedLeadBytes.length); |
| if (!Arrays.equals(leadBufferBytes, expectedLeadBytes)) { |
| return false; |
| } |
| |
| // Iterate through the sub-elements contained in the Information Element (IE) |
| bufferPtr += expectedLeadBytes.length; |
| // Loop over the buffer ensuring there are 2-bytes available for each new subelement tested. |
| while (bufferPtr + 1 < bufferLength) { |
| byte subelement = buffer[bufferPtr++]; |
| int subelementLength = buffer[bufferPtr++]; |
| // Check there is enough data for the next subelement |
| if ((bufferPtr + subelementLength) > bufferLength || subelementLength <= 0) { |
| return false; |
| } |
| |
| byte[] subelementData = |
| Arrays.copyOfRange(buffer, bufferPtr, bufferPtr + subelementLength); |
| |
| if (ieType == MEASUREMENT_TYPE_LCI) { |
| switch (subelement) { |
| case SUBELEMENT_LCI: |
| mIsLciValid = parseSubelementLci(subelementData); |
| if (!mIsLciValid || mLciVersion != LCI_VERSION_1) { |
| setLciSubelementDefaults(); |
| } |
| break; |
| case SUBELEMENT_Z: |
| mIsZValid = parseSubelementZ(subelementData); |
| if (!mIsZValid) { |
| setZaxisSubelementDefaults(); |
| } |
| break; |
| case SUBELEMENT_USAGE: |
| mIsUsageValid = parseSubelementUsage(subelementData); |
| // Note: if the Usage Subelement is not valid, don't reset the state, as |
| // it is now indicating the whole ResponderLocation is invalid. |
| break; |
| case SUBELEMENT_BSSID_LIST: |
| mIsBssidListValid = parseSubelementBssidList(subelementData); |
| if (!mIsBssidListValid) { |
| setBssidListSubelementDefaults(); |
| } |
| break; |
| default: |
| break; // skip over unused or vendor specific subelements |
| } |
| } else if (ieType == MEASUREMENT_TYPE_LCR) { |
| switch (subelement) { |
| case SUBELEMENT_LOCATION_CIVIC: |
| mIsLocationCivicValid = parseSubelementLocationCivic(subelementData); |
| if (!mIsLocationCivicValid) { |
| setCivicLocationSubelementDefaults(); |
| } |
| break; |
| case SUBELEMENT_MAP_IMAGE: |
| mIsMapImageValid = parseSubelementMapImage(subelementData); |
| if (!mIsMapImageValid) { |
| setMapImageSubelementDefaults(); |
| } |
| break; |
| default: |
| break; // skip over unused or other vendor specific subelements |
| } |
| } |
| |
| bufferPtr += subelementLength; |
| } |
| return true; |
| } |
| |
| /** |
| * Parse the LCI Sub-Element in the LCI Information Element (IE). |
| * |
| * @param buffer a buffer containing the subelement |
| * @return boolean true indicates success |
| */ |
| private boolean parseSubelementLci(byte[] buffer) { |
| if (buffer.length > SUBELEMENT_LCI_LENGTH) { |
| return false; |
| } |
| swapEndianByteByByte(buffer); |
| long[] subelementLciFields = getFieldData(buffer, SUBELEMENT_LCI_BIT_FIELD_LENGTHS); |
| if (subelementLciFields == null) { |
| return false; |
| } |
| // Set member state based on parsed buffer data |
| mLatitudeUncertainty = decodeLciLatLngUncertainty( |
| subelementLciFields[SUBELEMENT_LCI_LAT_UNCERTAINTY_INDEX]); |
| mLatitude = decodeLciLatLng(subelementLciFields, SUBELEMENT_LCI_BIT_FIELD_LENGTHS, |
| SUBELEMENT_LCI_LAT_INDEX, LAT_ABS_LIMIT); |
| mLongitudeUncertainty = decodeLciLatLngUncertainty( |
| subelementLciFields[SUBELEMENT_LCI_LNG_UNCERTAINTY_INDEX]); |
| mLongitude = |
| decodeLciLatLng(subelementLciFields, SUBELEMENT_LCI_BIT_FIELD_LENGTHS, |
| SUBELEMENT_LCI_LNG_INDEX, LNG_ABS_LIMIT); |
| mAltitudeType = (int) subelementLciFields[SUBELEMENT_LCI_ALT_TYPE_INDEX] & BYTE_MASK; |
| mAltitudeUncertainty = |
| decodeLciAltUncertainty(subelementLciFields[SUBELEMENT_LCI_ALT_UNCERTAINTY_INDEX]); |
| mAltitude = |
| Math.scalb(subelementLciFields[SUBELEMENT_LCI_ALT_INDEX], -ALTITUDE_FRACTION_BITS); |
| mDatum = (int) subelementLciFields[SUBELEMENT_LCI_DATUM_INDEX] & BYTE_MASK; |
| mLciRegisteredLocationAgreement = |
| (subelementLciFields[SUBELEMENT_LCI_REGLOC_AGREEMENT_INDEX] == 1); |
| mLciRegisteredLocationDse = |
| (subelementLciFields[SUBELEMENT_LCI_REGLOC_DSE_INDEX] == 1); |
| mLciDependentStation = |
| (subelementLciFields[SUBELEMENT_LCI_DEPENDENT_STA_INDEX] == 1); |
| mLciVersion = (int) subelementLciFields[SUBELEMENT_LCI_VERSION_INDEX]; |
| return true; |
| } |
| |
| /** |
| * Decode the floating point value of an encoded lat or lng in the LCI subelement field. |
| * |
| * @param fields the array of field data represented as longs |
| * @param bitFieldSizes the lengths of each field |
| * @param offset the offset of the field being decoded |
| * @param limit the maximum absolute value (note: different for lat vs lng) |
| * @return the floating point value of the lat or lng |
| */ |
| private double decodeLciLatLng(long[] fields, int[] bitFieldSizes, int offset, double limit) { |
| double angle; |
| if ((fields[offset] & (long) Math.pow(2, bitFieldSizes[offset] - 1)) != 0) { |
| // Negative 2's complement value |
| // Note: The Math.pow(...) method cannot return a NaN value because the bitFieldSize |
| // for Lat or Lng is limited to exactly 34 bits. |
| angle = Math.scalb(fields[offset] - Math.pow(2, bitFieldSizes[offset]), |
| -LATLNG_FRACTION_BITS); |
| } else { |
| // Positive 2's complement value |
| angle = Math.scalb(fields[offset], -LATLNG_FRACTION_BITS); |
| } |
| if (angle > limit) { |
| angle = limit; |
| } else if (angle < -limit) { |
| angle = -limit; |
| } |
| return angle; |
| } |
| |
| /** |
| * Coverts an encoded Lat/Lng uncertainty into a number of degrees. |
| * |
| * @param encodedValue the encoded uncertainty |
| * @return the value in degrees |
| */ |
| private double decodeLciLatLngUncertainty(long encodedValue) { |
| return Math.pow(2, LATLNG_UNCERTAINTY_BASE - encodedValue); |
| } |
| |
| /** |
| * Converts an encoded Alt uncertainty into a number of degrees. |
| * |
| * @param encodedValue the encoded uncertainty |
| * @return the value in degrees |
| */ |
| private double decodeLciAltUncertainty(long encodedValue) { |
| return Math.pow(2, ALTITUDE_UNCERTAINTY_BASE - encodedValue); |
| } |
| |
| /** |
| * Parse the Z subelement of the LCI IE. |
| * |
| * @param buffer a buffer containing the subelement |
| * @return boolean true indicates success |
| */ |
| private boolean parseSubelementZ(byte[] buffer) { |
| if (buffer.length != SUBELEMENT_Z_LENGTH) { |
| return false; |
| } |
| swapEndianByteByByte(buffer); |
| long[] subelementZFields = getFieldData(buffer, SUBELEMENT_Z_BIT_FIELD_LENGTHS); |
| if (subelementZFields == null) { |
| return false; |
| } |
| |
| mExpectedToMove = |
| (int) subelementZFields[SUBELEMENT_Z_LAT_EXPECTED_TO_MOVE_INDEX] & BYTE_MASK; |
| mFloorNumber = decodeZUnsignedToSignedValue(subelementZFields, |
| SUBELEMENT_Z_BIT_FIELD_LENGTHS, SUBELEMENT_Z_FLOOR_NUMBER_INDEX, |
| Z_FLOOR_NUMBER_FRACTION_BITS); |
| |
| mHeightAboveFloorMeters = decodeZUnsignedToSignedValue(subelementZFields, |
| SUBELEMENT_Z_BIT_FIELD_LENGTHS, SUBELEMENT_Z_HEIGHT_ABOVE_FLOOR_INDEX, |
| Z_FLOOR_HEIGHT_FRACTION_BITS); |
| |
| long zHeightUncertainty = |
| subelementZFields[SUBELEMENT_Z_HEIGHT_ABOVE_FLOOR_UNCERTAINTY_INDEX]; |
| if (zHeightUncertainty > 0 && zHeightUncertainty < Z_MAX_HEIGHT_UNCERTAINTY_FACTOR) { |
| mHeightAboveFloorUncertaintyMeters = |
| Math.pow(2, Z_FLOOR_HEIGHT_FRACTION_BITS - zHeightUncertainty - 1); |
| } else { |
| return false; |
| } |
| return true; |
| } |
| |
| /** |
| * Decode a two's complement encoded value, to a signed double based on the field length. |
| * |
| * @param fieldValues the array of field values reprented as longs |
| * @param fieldLengths the array of field lengths |
| * @param index the index of the field being decoded |
| * @param fraction the number of fractional bits in the value |
| * @return the signed value represented as a double |
| */ |
| private double decodeZUnsignedToSignedValue(long[] fieldValues, int[] fieldLengths, int index, |
| int fraction) { |
| int value = (int) fieldValues[index]; |
| int maxPositiveValue = (int) Math.pow(2, fieldLengths[index] - 1) - 1; |
| if (value > maxPositiveValue) { |
| value -= Math.pow(2, fieldLengths[index]); |
| } |
| return Math.scalb(value, -fraction); |
| } |
| |
| /** |
| * Parse Subelement Usage Rights |
| */ |
| private boolean parseSubelementUsage(byte[] buffer) { |
| if (buffer.length != SUBELEMENT_USAGE_LENGTH1 |
| && buffer.length != SUBELEMENT_USAGE_LENGTH3) { |
| return false; |
| } |
| mUsageRetransmit = |
| (buffer[SUBELEMENT_USAGE_PARAMS_INDEX] & SUBELEMENT_USAGE_MASK_RETRANSMIT) != 0; |
| mUsageRetentionExpires = |
| (buffer[SUBELEMENT_USAGE_PARAMS_INDEX] & SUBELEMENT_USAGE_MASK_RETENTION_EXPIRES) |
| != 0; |
| mUsageExtraInfoOnAssociation = |
| (buffer[SUBELEMENT_USAGE_PARAMS_INDEX] & SUBELEMENT_USAGE_MASK_STA_LOCATION_POLICY) |
| != 0; |
| // Note: the Retransmit flag must be true, and RetentionExpires, false for the |
| // ResponderLocation object to be usable by public applications. |
| return (mUsageRetransmit && !mUsageRetentionExpires); |
| } |
| |
| /** |
| * Parse the BSSID List Subelement of the LCI IE. |
| * |
| * @param buffer a buffer containing the subelement |
| * @return boolean true indicates success |
| */ |
| private boolean parseSubelementBssidList(byte[] buffer) { |
| if (buffer.length < SUBELEMENT_BSSID_LIST_MIN_BUFFER_LENGTH) { |
| return false; |
| } |
| if ((buffer.length - 1) % BYTES_IN_A_BSSID != 0) { |
| return false; |
| } |
| |
| int maxBssidIndicator = (int) buffer[SUBELEMENT_BSSID_MAX_INDICATOR_INDEX] & BYTE_MASK; |
| int bssidListLength = (buffer.length - 1) / BYTES_IN_A_BSSID; |
| // Check the max number of BSSIDs agrees with the list length. |
| if (maxBssidIndicator != bssidListLength) { |
| return false; |
| } |
| |
| int bssidOffset = SUBELEMENT_BSSID_LIST_INDEX; |
| for (int i = 0; i < bssidListLength; i++) { |
| byte[] bssid = Arrays.copyOfRange(buffer, bssidOffset, bssidOffset + BYTES_IN_A_BSSID); |
| MacAddress macAddress = MacAddress.fromBytes(bssid); |
| mBssidList.add(macAddress); |
| bssidOffset += BYTES_IN_A_BSSID; |
| } |
| return true; |
| } |
| |
| /** |
| * Parse the Location Civic subelement in the LCR IE. |
| * |
| * @param buffer a buffer containing the subelement |
| * @return boolean true indicates success |
| */ |
| private boolean parseSubelementLocationCivic(byte[] buffer) { |
| if (buffer.length < SUBELEMENT_LOCATION_CIVIC_MIN_LENGTH |
| || buffer.length > SUBELEMENT_LOCATION_CIVIC_MAX_LENGTH) { |
| return false; |
| } |
| mCivicLocationCountryCode = |
| new String(Arrays.copyOfRange(buffer, CIVIC_COUNTRY_CODE_INDEX, |
| CIVIC_TLV_LIST_INDEX)).toUpperCase(); |
| CivicLocation civicLocation = |
| new CivicLocation( |
| Arrays.copyOfRange(buffer, CIVIC_TLV_LIST_INDEX, buffer.length), |
| mCivicLocationCountryCode); |
| if (!civicLocation.isValid()) { |
| return false; |
| } |
| this.mCivicLocation = civicLocation; |
| mCivicLocationString = civicLocation.toString(); |
| return true; |
| } |
| |
| /** |
| * Parse the Map Image subelement in the LCR IE. |
| * |
| * @param buffer a buffer containing the subelement |
| * @return boolean true indicates success |
| */ |
| private boolean parseSubelementMapImage(byte[] buffer) { |
| if (buffer.length > SUBELEMENT_MAP_IMAGE_URL_MAX_LENGTH) { |
| return false; |
| } |
| int mapImageType = buffer[SUBELEMENT_IMAGE_MAP_TYPE_INDEX]; |
| int supportedTypesMax = SUPPORTED_IMAGE_FILE_EXTENSIONS.length - 1; |
| if (mapImageType < MAP_TYPE_URL_DEFINED || mapImageType > supportedTypesMax) { |
| return false; |
| } |
| this.mMapImageType = mapImageType; |
| byte[] urlBytes = Arrays.copyOfRange(buffer, 1, buffer.length); |
| mMapImageUri = Uri.parse(new String(urlBytes, StandardCharsets.UTF_8)); |
| return true; |
| } |
| |
| /** |
| * Convert an image type code to a Mime type |
| * |
| * @param imageTypeCode encoded as an integer |
| * @return the mime type of the image file |
| */ |
| private String imageTypeToMime(int imageTypeCode, String imageUrl) { |
| int supportedExtensionsMax = SUPPORTED_IMAGE_FILE_EXTENSIONS.length - 1; |
| if ((imageTypeCode == 0 && imageUrl == null) || imageTypeCode > supportedExtensionsMax) { |
| return null; |
| } |
| MimeTypeMap mimeTypeMap = MimeTypeMap.getSingleton(); |
| if (imageTypeCode == 0) { |
| return mimeTypeMap.getMimeTypeFromExtension( |
| MimeTypeMap.getFileExtensionFromUrl(imageUrl)); |
| } else { |
| return mimeTypeMap.getMimeTypeFromExtension( |
| SUPPORTED_IMAGE_FILE_EXTENSIONS[imageTypeCode]); |
| } |
| } |
| |
| /** |
| * Converts a byte array containing fields of variable size, into an array of longs where the |
| * field boundaries are defined in a constant int array passed as an argument. |
| * |
| * @param buffer the byte array containing all the fields |
| * @param bitFieldSizes the int array defining the size of each field |
| */ |
| private long[] getFieldData(byte[] buffer, int[] bitFieldSizes) { |
| int bufferLengthBits = buffer.length * Byte.SIZE; |
| int sumBitFieldSizes = 0; |
| for (int i : bitFieldSizes) { |
| if (i > Long.SIZE) { |
| return null; |
| } |
| sumBitFieldSizes += i; |
| } |
| if (bufferLengthBits != sumBitFieldSizes) { |
| return null; |
| } |
| long[] fieldData = new long[bitFieldSizes.length]; |
| int bufferBitPos = 0; |
| for (int fieldIndex = 0; fieldIndex < bitFieldSizes.length; fieldIndex++) { |
| int bitFieldSize = bitFieldSizes[fieldIndex]; |
| long field = 0; |
| for (int n = 0; n < bitFieldSize; n++) { |
| field |= ((long) getBitAtBitOffsetInByteArray(buffer, bufferBitPos + n) << n); |
| } |
| fieldData[fieldIndex] = field; |
| bufferBitPos += bitFieldSize; |
| } |
| return fieldData; |
| } |
| |
| /** |
| * Retrieves state of a bit at the bit-offset in a byte array, where the offset represents the |
| * bytes in contiguous data with each value in big endian order. |
| * |
| * @param buffer the data buffer of bytes containing all the fields |
| * @param bufferBitOffset the bit offset into the entire buffer |
| * @return a zero or one value, representing the state of that bit. |
| */ |
| private int getBitAtBitOffsetInByteArray(byte[] buffer, int bufferBitOffset) { |
| int bufferIndex = bufferBitOffset / Byte.SIZE; // The byte index that contains the bit |
| int bitOffsetInByte = bufferBitOffset % Byte.SIZE; // The bit offset within that byte |
| int result = (buffer[bufferIndex] & (MSB_IN_BYTE >> bitOffsetInByte)) == 0 ? 0 : 1; |
| return result; |
| } |
| |
| /** |
| * Reverses the order of the bits in each byte of a byte array. |
| * |
| * @param buffer the array containing each byte that will be reversed |
| */ |
| private void swapEndianByteByByte(byte[] buffer) { |
| for (int n = 0; n < buffer.length; n++) { |
| byte currentByte = buffer[n]; |
| byte reversedByte = 0; // Cleared value |
| byte bitSelectorMask = LSB_IN_BYTE; |
| for (int i = 0; i < Byte.SIZE; i++) { |
| reversedByte = (byte) (reversedByte << 1); |
| if ((currentByte & bitSelectorMask) != 0) { |
| reversedByte = (byte) (reversedByte | LSB_IN_BYTE); |
| } |
| bitSelectorMask = (byte) (bitSelectorMask << 1); |
| } |
| buffer[n] = reversedByte; |
| } |
| } |
| |
| /** |
| * Sets the LCI subelement fields to the default undefined values. |
| */ |
| private void setLciSubelementDefaults() { |
| mIsLciValid = false; |
| mLatitudeUncertainty = UNCERTAINTY_UNDEFINED; |
| mLatitude = 0; |
| mLongitudeUncertainty = UNCERTAINTY_UNDEFINED; |
| mLongitude = 0; |
| mAltitudeType = ALTITUDE_UNDEFINED; |
| mAltitudeUncertainty = UNCERTAINTY_UNDEFINED; |
| mAltitude = 0; |
| mDatum = DATUM_UNDEFINED; |
| mLciRegisteredLocationAgreement = false; |
| mLciRegisteredLocationDse = false; |
| mLciDependentStation = false; |
| mLciVersion = 0; |
| } |
| |
| /** |
| * Sets the Z subelement fields to the default values when undefined. |
| */ |
| private void setZaxisSubelementDefaults() { |
| mIsZValid = false; |
| mExpectedToMove = 0; |
| mFloorNumber = 0; |
| mHeightAboveFloorMeters = 0; |
| mHeightAboveFloorUncertaintyMeters = 0; |
| } |
| |
| /** |
| * Sets the Usage Policy subelement fields to the default undefined values. |
| */ |
| private void setUsageSubelementDefaults() { |
| mUsageRetransmit = true; |
| mUsageRetentionExpires = false; |
| mUsageExtraInfoOnAssociation = false; |
| } |
| |
| /** |
| * Sets the BSSID List subelement fields to the default values when undefined. |
| */ |
| private void setBssidListSubelementDefaults() { |
| mIsBssidListValid = false; |
| mBssidList = new ArrayList<>(); |
| } |
| |
| /** |
| * Sets the LCR Civic Location subelement field to the default undefined value. |
| * |
| * @hide |
| */ |
| public void setCivicLocationSubelementDefaults() { |
| mIsLocationCivicValid = false; |
| mCivicLocationCountryCode = ""; |
| mCivicLocationString = ""; |
| mCivicLocation = null; |
| } |
| |
| /** |
| * Sets the LCR Map Image subelement field to the default values when undefined. |
| */ |
| private void setMapImageSubelementDefaults() { |
| mIsMapImageValid = false; |
| mMapImageType = MAP_TYPE_URL_DEFINED; |
| mMapImageUri = null; |
| } |
| |
| @Override |
| public boolean equals(Object obj) { |
| if (this == obj) { |
| return true; |
| } |
| if (obj == null || getClass() != obj.getClass()) { |
| return false; |
| } |
| ResponderLocation other = (ResponderLocation) obj; |
| return mIsValid == other.mIsValid |
| && mIsLciValid == other.mIsLciValid |
| && mIsZValid == other.mIsZValid |
| && mIsUsageValid == other.mIsUsageValid |
| && mIsBssidListValid == other.mIsBssidListValid |
| && mIsLocationCivicValid == other.mIsLocationCivicValid |
| && mIsMapImageValid == other.mIsMapImageValid |
| && mLatitudeUncertainty == other.mLatitudeUncertainty |
| && mLatitude == other.mLatitude |
| && mLongitudeUncertainty == other.mLongitudeUncertainty |
| && mLongitude == other.mLongitude |
| && mAltitudeType == other.mAltitudeType |
| && mAltitudeUncertainty == other.mAltitudeUncertainty |
| && mAltitude == other.mAltitude |
| && mDatum == other.mDatum |
| && mLciRegisteredLocationAgreement == other.mLciRegisteredLocationAgreement |
| && mLciRegisteredLocationDse == other.mLciRegisteredLocationDse |
| && mLciDependentStation == other.mLciDependentStation |
| && mLciVersion == other.mLciVersion |
| && mExpectedToMove == other.mExpectedToMove |
| && mFloorNumber == other.mFloorNumber |
| && mHeightAboveFloorMeters == other.mHeightAboveFloorMeters |
| && mHeightAboveFloorUncertaintyMeters |
| == other.mHeightAboveFloorUncertaintyMeters |
| && mUsageRetransmit == other.mUsageRetransmit |
| && mUsageRetentionExpires == other.mUsageRetentionExpires |
| && mUsageExtraInfoOnAssociation == other.mUsageExtraInfoOnAssociation |
| && mBssidList.equals(other.mBssidList) |
| && mCivicLocationCountryCode.equals(other.mCivicLocationCountryCode) |
| && mCivicLocationString.equals(other.mCivicLocationString) |
| && Objects.equals(mCivicLocation, other.mCivicLocation) |
| && mMapImageType == other.mMapImageType |
| && Objects.equals(mMapImageUri, other.mMapImageUri); |
| } |
| |
| @Override |
| public int hashCode() { |
| return Objects.hash(mIsValid, mIsLciValid, mIsZValid, mIsUsageValid, mIsBssidListValid, |
| mIsLocationCivicValid, mIsMapImageValid, mLatitudeUncertainty, mLatitude, |
| mLongitudeUncertainty, mLongitude, mAltitudeType, mAltitudeUncertainty, mAltitude, |
| mDatum, mLciRegisteredLocationAgreement, |
| mLciRegisteredLocationDse, mLciDependentStation, mLciVersion, |
| mExpectedToMove, mFloorNumber, mHeightAboveFloorMeters, |
| mHeightAboveFloorUncertaintyMeters, mUsageRetransmit, mUsageRetentionExpires, |
| mUsageExtraInfoOnAssociation, mBssidList, mCivicLocationCountryCode, |
| mCivicLocationString, mCivicLocation, mMapImageType, mMapImageUri); |
| } |
| |
| /** |
| * @return true if the ResponderLocation object is valid and contains useful information |
| * relevant to the location of the Responder. If this is ever false, this object will not be |
| * available to developers, and have a null value. |
| * |
| * @hide |
| */ |
| public boolean isValid() { |
| return mIsValid; |
| } |
| |
| /** |
| * @return true if the LCI subelement (containing Latitude, Longitude and Altitude) is valid. |
| * |
| * <p> This method tells us if the responder has provided its Location Configuration |
| * Information (LCI) directly, and is useful when an external database of responder locations |
| * is not available</p> |
| * |
| * <p>If isLciSubelementValid() returns true, all the LCI values provided by the corresponding |
| * getter methods will have been set as described by the responder, or else if false, they |
| * should not be used and will throw an IllegalStateException.</p> |
| */ |
| public boolean isLciSubelementValid() { |
| return mIsLciValid; |
| } |
| |
| /** |
| * @return the latitude uncertainty in degrees. |
| * <p> |
| * Only valid if {@link #isLciSubelementValid()} returns true, or will throw an exception. |
| * </p> |
| * <p> An unknown uncertainty is indicated by 0.</p> |
| */ |
| public double getLatitudeUncertainty() { |
| if (!mIsLciValid) { |
| throw new IllegalStateException( |
| "getLatitudeUncertainty(): invoked on an invalid result: mIsLciValid = false."); |
| } |
| return mLatitudeUncertainty; |
| } |
| |
| /** |
| * @return the latitude in degrees |
| * <p> |
| * Only valid if {@link #isLciSubelementValid()} returns true, or will throw an exception. |
| */ |
| public double getLatitude() { |
| if (!mIsLciValid) { |
| throw new IllegalStateException( |
| "getLatitude(): invoked on an invalid result: mIsLciValid = false."); |
| } |
| return mLatitude; |
| } |
| |
| /** |
| * @return the Longitude uncertainty in degrees. |
| * <p> |
| * Only valid if {@link #isLciSubelementValid()} returns true, or will throw an exception. |
| * </p> |
| * <p> An unknown uncertainty is indicated by 0.</p> |
| */ |
| public double getLongitudeUncertainty() { |
| if (!mIsLciValid) { |
| throw new IllegalStateException( |
| "getLongitudeUncertainty(): invoked on an invalid result: mIsLciValid = false."); |
| } |
| return mLongitudeUncertainty; |
| } |
| |
| /** |
| * @return the Longitude in degrees.. |
| * <p> |
| * Only valid if {@link #isLciSubelementValid()} returns true, or will throw an exception. |
| */ |
| public double getLongitude() { |
| if (!mIsLciValid) { |
| throw new IllegalStateException( |
| "getLatitudeUncertainty(): invoked on an invalid result: mIsLciValid = false."); |
| } |
| return mLongitude; |
| } |
| |
| /** |
| * @return the Altitude type. |
| * <p> |
| * Only valid if {@link #isLciSubelementValid()} returns true, or will throw an exception. |
| */ |
| @AltitudeType |
| public int getAltitudeType() { |
| if (!mIsLciValid) { |
| throw new IllegalStateException( |
| "getLatitudeUncertainty(): invoked on an invalid result: mIsLciValid = false."); |
| } |
| return mAltitudeType; |
| } |
| |
| /** |
| * @return the Altitude uncertainty in meters. |
| * <p> |
| * Only valid if {@link #isLciSubelementValid()} returns true, or will throw an exception. |
| * </p> |
| * <p>An unknown uncertainty is indicated by 0.</p> |
| */ |
| public double getAltitudeUncertainty() { |
| if (!mIsLciValid) { |
| throw new IllegalStateException( |
| "getLatitudeUncertainty(): invoked on an invalid result: mIsLciValid = false."); |
| } |
| return mAltitudeUncertainty; |
| } |
| |
| /** |
| * @return the Altitude in units defined by the altitude type. |
| * <p> |
| * Only valid if {@link #isLciSubelementValid()} returns true, or will throw an exception. |
| */ |
| public double getAltitude() { |
| if (!mIsLciValid) { |
| throw new IllegalStateException( |
| "getAltitude(): invoked on an invalid result: mIsLciValid = false."); |
| } |
| return mAltitude; |
| } |
| |
| /** |
| * @return the Datum used for the LCI positioning information. |
| * <p> |
| * Only valid if {@link #isLciSubelementValid()} returns true, or will throw an exception. |
| */ |
| @DatumType |
| public int getDatum() { |
| if (!mIsLciValid) { |
| throw new IllegalStateException( |
| "getDatum(): invoked on an invalid result: mIsLciValid = false."); |
| } |
| return mDatum; |
| } |
| |
| /** |
| * @return true if the station is operating within a national policy area or an international |
| * agreement area near a national border, otherwise false |
| * (see 802.11REVmc Section 11.12.3 - Registered STA Operation). |
| * <p> |
| * Only valid if {@link #isLciSubelementValid()} returns true, or will throw an exception. |
| */ |
| public boolean getRegisteredLocationAgreementIndication() { |
| if (!mIsLciValid) { |
| throw new IllegalStateException( |
| "getRegisteredLocationAgreementIndication(): " |
| + "invoked on an invalid result: mIsLciValid = false."); |
| } |
| return mLciRegisteredLocationAgreement; |
| } |
| |
| /** |
| * @return true indicating this is an enabling station, enabling the operation of nearby STAs |
| * with Dynamic Station Enablement (DSE), otherwise false. |
| * (see 802.11REVmc Section 11.12.3 - Registered STA Operation). |
| * <p> |
| * Only valid if {@link #isLciSubelementValid()} returns true, or will throw an exception. |
| */ |
| public boolean getRegisteredLocationDseIndication() { |
| if (!mIsLciValid) { |
| throw new IllegalStateException( |
| "getRegisteredLocationDseIndication(): " |
| + "invoked on an invalid result: mIsLciValid = false."); |
| } |
| return mLciRegisteredLocationDse; |
| } |
| |
| /** |
| * @return true indicating this is a dependent station that is operating with the enablement of |
| * an enabling station whose LCI is being reported, otherwise false. |
| * (see 802.11REVmc Section 11.12.3 - Registered STA Operation). |
| * <p> |
| * Only valid if {@link #isLciSubelementValid()} returns true, or will throw an exception. |
| */ |
| public boolean getDependentStationIndication() { |
| if (!mIsLciValid) { |
| throw new IllegalStateException( |
| "getDependentStationIndication(): " |
| + "invoked on an invalid result: mIsLciValid = false."); |
| } |
| return mLciDependentStation; |
| } |
| |
| /** |
| * @return a value greater or equal to 1, indicating the current version number |
| * of the LCI protocol. |
| * <p> |
| * Only valid if {@link #isLciSubelementValid()} returns true, or will throw an exception. |
| */ |
| public int getLciVersion() { |
| if (!mIsLciValid) { |
| throw new IllegalStateException( |
| "getLciVersion(): " |
| + "invoked on an invalid result: mIsLciValid = false."); |
| } |
| return mLciVersion; |
| } |
| |
| /** |
| * @return the LCI location represented as a {@link Location} object (best effort). |
| * <p> |
| * Only valid if {@link #isLciSubelementValid()} returns true, or will throw an exception. |
| */ |
| @NonNull |
| public Location toLocation() { |
| if (!mIsLciValid) { |
| throw new IllegalStateException( |
| "toLocation(): " |
| + "invoked on an invalid result: mIsLciValid = false."); |
| } |
| Location location = new Location(LOCATION_PROVIDER); |
| location.setLatitude(mLatitude); |
| location.setLongitude(mLongitude); |
| location.setAccuracy((float) (mLatitudeUncertainty + mLongitudeUncertainty) / 2); |
| location.setAltitude(mAltitude); |
| location.setVerticalAccuracyMeters((float) mAltitudeUncertainty); |
| location.setTime(System.currentTimeMillis()); |
| return location; |
| } |
| |
| /** |
| * @return if the Z subelement (containing mobility, Floor, Height above floor) is valid. |
| */ |
| public boolean isZaxisSubelementValid() { |
| return mIsZValid; |
| } |
| |
| /** |
| * @return an integer representing the mobility of the responder. |
| * <p> |
| * Only valid if {@link #isZaxisSubelementValid()} returns true, or will throw an exception. |
| */ |
| @ExpectedToMoveType |
| public int getExpectedToMove() { |
| if (!mIsZValid) { |
| throw new IllegalStateException( |
| "getExpectedToMove(): invoked on an invalid result: mIsZValid = false."); |
| } |
| return mExpectedToMove; |
| } |
| |
| /** |
| * @return the Z sub element Floor Number. |
| * <p> |
| * Only valid if {@link #isZaxisSubelementValid()} returns true, or will throw an exception. |
| * </p> |
| * <p>Note: this number can be positive or negative, with value increments of +/- 1/16 of a |
| * floor.</p>. |
| */ |
| public double getFloorNumber() { |
| if (!mIsZValid) { |
| throw new IllegalStateException( |
| "getFloorNumber(): invoked on an invalid result: mIsZValid = false)"); |
| } |
| return mFloorNumber; |
| } |
| |
| /** |
| * @return the Z subelement Height above the floor in meters. |
| * <p> |
| * Only valid if {@link #isZaxisSubelementValid()} returns true, or will throw an exception. |
| * </p> |
| * <p>This value can be positive or negative. </p> |
| */ |
| public double getHeightAboveFloorMeters() { |
| if (!mIsZValid) { |
| throw new IllegalStateException( |
| "getHeightAboveFloorMeters(): invoked on an invalid result: mIsZValid = false)"); |
| } |
| return mHeightAboveFloorMeters; |
| } |
| |
| /** |
| * @return the Z subelement Height above the floor uncertainty in meters. |
| * <p> |
| * Only valid if {@link #isZaxisSubelementValid()} returns true, or will throw an exception. |
| * </p> |
| * <p>An unknown uncertainty is indicated by 0.</p> |
| */ |
| public double getHeightAboveFloorUncertaintyMeters() { |
| if (!mIsZValid) { |
| throw new IllegalStateException( |
| "getHeightAboveFloorUncertaintyMeters():" |
| + "invoked on an invalid result: mIsZValid = false)"); |
| } |
| return mHeightAboveFloorUncertaintyMeters; |
| } |
| |
| /** |
| * @return true if the location information received from the responder can be |
| * retransmitted to another device, physically separate from the one that received it. |
| * |
| * @hide |
| */ |
| public boolean getRetransmitPolicyIndication() { |
| return mUsageRetransmit; |
| } |
| |
| /** |
| * @return true if location-data received should expire (and be deleted) |
| * by the time provided in the getRelativeExpirationTimeHours() method. |
| * |
| * @hide |
| */ |
| public boolean getRetentionExpiresIndication() { |
| return mUsageRetentionExpires; |
| } |
| |
| /** |
| * @return true if there is extra location info available on association. |
| * |
| * @hide |
| */ |
| @SystemApi |
| public boolean getExtraInfoOnAssociationIndication() { |
| return mUsageExtraInfoOnAssociation; |
| } |
| |
| /** |
| * @return the Immutable list of colocated BSSIDs at the responder. |
| * |
| * <p> Will return an empty list when there are no bssids listed. |
| */ |
| public List<MacAddress> getColocatedBssids() { |
| return Collections.unmodifiableList(mBssidList); |
| } |
| |
| /** |
| * @return the civic location represented as an {@link Address} object (best effort). |
| * |
| * <p> Will return a {@code null} when there is no Civic Location defined. |
| */ |
| @Nullable |
| public Address toCivicLocationAddress() { |
| if (mCivicLocation != null && mCivicLocation.isValid()) { |
| return mCivicLocation.toAddress(); |
| } else { |
| return null; |
| } |
| } |
| |
| /** |
| * @return the civic location represented as a {@link SparseArray} |
| * <p> |
| * Valid keys to access the SparseArray can be found in {@code CivicLocationKeys}. |
| * </p> |
| * <p> Will return a {@code null} when there is no Civic Location defined. |
| * |
| */ |
| @Nullable |
| public SparseArray toCivicLocationSparseArray() { |
| if (mCivicLocation != null && mCivicLocation.isValid()) { |
| return mCivicLocation.toSparseArray(); |
| } else { |
| return null; |
| } |
| } |
| |
| /** |
| * @return the civic location two upper-case ASCII character country code defined in ISO 3166. |
| * |
| * <p> Will return a {@code null} when there is no country code defined. |
| * |
| * @hide |
| */ |
| @Nullable |
| public String getCivicLocationCountryCode() { |
| return mCivicLocationCountryCode; |
| } |
| |
| /** |
| * @return the value of the Civic Location String associated with a key. |
| * |
| * <p> Will return a {@code null} when there is no value associated with the key provided. |
| * |
| * @param key used to find a corresponding value in the Civic Location Tuple list |
| * |
| * @hide |
| */ |
| @Nullable |
| public String getCivicLocationElementValue(@CivicLocationKeysType int key) { |
| return mCivicLocation.getCivicElementValue(key); |
| } |
| |
| /** |
| * @return the Map Image file Mime type, referred to by getMapImageUrl(). |
| */ |
| @Nullable |
| public String getMapImageMimeType() { |
| if (mMapImageUri == null) { |
| return null; |
| } else { |
| return imageTypeToMime(mMapImageType, mMapImageUri.toString()); |
| } |
| } |
| |
| /** |
| * @return a URI referencing a map-file showing the local floor plan. |
| * |
| * <p> Will return a {@code null} when there is no URI defined. |
| */ |
| @Nullable |
| public Uri getMapImageUri() { |
| return mMapImageUri; |
| } |
| } |