1 /* 2 * Copyright (C) 2018 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.net.wifi.rtt; 18 19 import android.annotation.Nullable; 20 import android.location.Address; 21 import android.net.wifi.rtt.CivicLocationKeys.CivicLocationKeysType; 22 import android.os.Parcel; 23 import android.os.Parcelable; 24 import android.os.Parcelable.Creator; 25 import android.util.SparseArray; 26 27 import java.nio.charset.StandardCharsets; 28 import java.util.Arrays; 29 import java.util.Locale; 30 import java.util.Objects; 31 32 /** 33 * Decodes the Type Length Value (TLV) elements found in a Location Civic Record as defined by IEEE 34 * P802.11-REVmc/D8.0 section 9.4.2.22.13 using the format described in IETF RFC 4776. 35 * 36 * <p>The TLVs each define a key, value pair for a civic address type such as apt, street, city, 37 * county, and country. The class provides a general getter method to extract a value for an element 38 * key, returning null if not set. 39 * 40 * @hide 41 */ 42 public final class CivicLocation implements Parcelable { 43 // Address (class) line indexes 44 private static final int ADDRESS_LINE_0_ROOM_DESK_FLOOR = 0; 45 private static final int ADDRESS_LINE_1_NUMBER_ROAD_SUFFIX_APT = 1; 46 private static final int ADDRESS_LINE_2_CITY = 2; 47 private static final int ADDRESS_LINE_3_STATE_POSTAL_CODE = 3; 48 private static final int ADDRESS_LINE_4_COUNTRY = 4; 49 50 // Buffer management 51 private static final int MIN_CIVIC_BUFFER_SIZE = 3; 52 private static final int MAX_CIVIC_BUFFER_SIZE = 256; 53 private static final int COUNTRY_CODE_LENGTH = 2; 54 private static final int BYTE_MASK = 0xFF; 55 private static final int TLV_TYPE_INDEX = 0; 56 private static final int TLV_LENGTH_INDEX = 1; 57 private static final int TLV_VALUE_INDEX = 2; 58 59 private final boolean mIsValid; 60 private final String mCountryCode; // Two character country code (ISO 3166 standard). 61 private SparseArray<String> mCivicAddressElements = 62 new SparseArray<>(MIN_CIVIC_BUFFER_SIZE); 63 64 65 /** 66 * Constructor 67 * 68 * @param civicTLVs a byte buffer containing parameters in the form type, length, value 69 * @param countryCode the two letter code defined by the ISO 3166 standard 70 * 71 * @hide 72 */ CivicLocation(@ullable byte[] civicTLVs, @Nullable String countryCode)73 public CivicLocation(@Nullable byte[] civicTLVs, @Nullable String countryCode) { 74 this.mCountryCode = countryCode; 75 if (countryCode == null || countryCode.length() != COUNTRY_CODE_LENGTH) { 76 this.mIsValid = false; 77 return; 78 } 79 boolean isValid = false; 80 if (civicTLVs != null 81 && civicTLVs.length >= MIN_CIVIC_BUFFER_SIZE 82 && civicTLVs.length < MAX_CIVIC_BUFFER_SIZE) { 83 isValid = parseCivicTLVs(civicTLVs); 84 } 85 86 mIsValid = isValid; 87 } 88 CivicLocation(Parcel in)89 private CivicLocation(Parcel in) { 90 mIsValid = in.readByte() != 0; 91 mCountryCode = in.readString(); 92 mCivicAddressElements = in.readSparseArray(this.getClass().getClassLoader()); 93 } 94 95 public static final @android.annotation.NonNull Creator<CivicLocation> CREATOR = new Creator<CivicLocation>() { 96 @Override 97 public CivicLocation createFromParcel(Parcel in) { 98 return new CivicLocation(in); 99 } 100 101 @Override 102 public CivicLocation[] newArray(int size) { 103 return new CivicLocation[size]; 104 } 105 }; 106 107 @Override describeContents()108 public int describeContents() { 109 return 0; 110 } 111 112 @Override writeToParcel(Parcel parcel, int flags)113 public void writeToParcel(Parcel parcel, int flags) { 114 parcel.writeByte((byte) (mIsValid ? 1 : 0)); 115 parcel.writeString(mCountryCode); 116 parcel.writeSparseArray((android.util.SparseArray) mCivicAddressElements); 117 } 118 119 /** 120 * Check TLV format and store TLV key/value pairs in this object so they can be queried by key. 121 * 122 * @param civicTLVs the buffer of TLV elements 123 * @return a boolean indicating success of the parsing process 124 */ parseCivicTLVs(byte[] civicTLVs)125 private boolean parseCivicTLVs(byte[] civicTLVs) { 126 int bufferPtr = 0; 127 int bufferLength = civicTLVs.length; 128 129 // Iterate through the sub-elements contained in the LCI IE checking the accumulated 130 // element lengths do not overflow the total buffer length 131 while (bufferPtr < bufferLength) { 132 int civicAddressType = civicTLVs[bufferPtr + TLV_TYPE_INDEX] & BYTE_MASK; 133 int civicAddressTypeLength = civicTLVs[bufferPtr + TLV_LENGTH_INDEX]; 134 if (civicAddressTypeLength != 0) { 135 if (bufferPtr + TLV_VALUE_INDEX + civicAddressTypeLength > bufferLength) { 136 return false; 137 } 138 mCivicAddressElements.put(civicAddressType, 139 new String(civicTLVs, bufferPtr + TLV_VALUE_INDEX, 140 civicAddressTypeLength, StandardCharsets.UTF_8)); 141 } 142 bufferPtr += civicAddressTypeLength + TLV_VALUE_INDEX; 143 } 144 return true; 145 } 146 147 /** 148 * Getter for the value of a civic Address element type. 149 * 150 * @param key an integer code for the element type key 151 * @return the string value associated with that element type 152 */ 153 @Nullable getCivicElementValue(@ivicLocationKeysType int key)154 public String getCivicElementValue(@CivicLocationKeysType int key) { 155 return mCivicAddressElements.get(key); 156 } 157 158 /** 159 * Converts a CivicLocation object to a SparseArray. 160 * 161 * @return the SparseArray<string> representation of the CivicLocation 162 */ 163 @Nullable toSparseArray()164 public SparseArray<String> toSparseArray() { 165 return mCivicAddressElements; 166 } 167 168 /** 169 * Generates a comma separated string of all the defined elements. 170 * 171 * @return a compiled string representing all elements 172 */ 173 @Override toString()174 public String toString() { 175 return mCivicAddressElements.toString(); 176 } 177 178 /** 179 * Converts Civic Location to the best effort Address Object. 180 * 181 * @return the {@link Address} object based on the Civic Location data 182 */ 183 @Nullable toAddress()184 public Address toAddress() { 185 if (!mIsValid) { 186 return null; 187 } 188 Address address = new Address(Locale.US); 189 String room = formatAddressElement("Room: ", getCivicElementValue(CivicLocationKeys.ROOM)); 190 String desk = 191 formatAddressElement(" Desk: ", getCivicElementValue(CivicLocationKeys.DESK)); 192 String floor = 193 formatAddressElement(", Flr: ", getCivicElementValue(CivicLocationKeys.FLOOR)); 194 String houseNumber = formatAddressElement("", getCivicElementValue(CivicLocationKeys.HNO)); 195 String houseNumberSuffix = 196 formatAddressElement("", getCivicElementValue(CivicLocationKeys.HNS)); 197 String road = 198 formatAddressElement(" ", getCivicElementValue( 199 CivicLocationKeys.PRIMARY_ROAD_NAME)); 200 String roadSuffix = formatAddressElement(" ", getCivicElementValue(CivicLocationKeys.STS)); 201 String apt = formatAddressElement(", Apt: ", getCivicElementValue(CivicLocationKeys.APT)); 202 String city = formatAddressElement("", getCivicElementValue(CivicLocationKeys.CITY)); 203 String state = formatAddressElement("", getCivicElementValue(CivicLocationKeys.STATE)); 204 String postalCode = 205 formatAddressElement(" ", getCivicElementValue(CivicLocationKeys.POSTAL_CODE)); 206 207 // Aggregation into common address format 208 String addressLine0 = 209 new StringBuilder().append(room).append(desk).append(floor).toString(); 210 String addressLine1 = 211 new StringBuilder().append(houseNumber).append(houseNumberSuffix).append(road) 212 .append(roadSuffix).append(apt).toString(); 213 String addressLine2 = city; 214 String addressLine3 = new StringBuilder().append(state).append(postalCode).toString(); 215 String addressLine4 = mCountryCode; 216 217 // Setting Address object line fields by common convention. 218 address.setAddressLine(ADDRESS_LINE_0_ROOM_DESK_FLOOR, addressLine0); 219 address.setAddressLine(ADDRESS_LINE_1_NUMBER_ROAD_SUFFIX_APT, addressLine1); 220 address.setAddressLine(ADDRESS_LINE_2_CITY, addressLine2); 221 address.setAddressLine(ADDRESS_LINE_3_STATE_POSTAL_CODE, addressLine3); 222 address.setAddressLine(ADDRESS_LINE_4_COUNTRY, addressLine4); 223 224 // Other compatible fields between the CIVIC_ADDRESS and the Address Class. 225 address.setFeatureName(getCivicElementValue(CivicLocationKeys.NAM)); // Structure name 226 address.setSubThoroughfare(getCivicElementValue(CivicLocationKeys.HNO)); 227 address.setThoroughfare(getCivicElementValue(CivicLocationKeys.PRIMARY_ROAD_NAME)); 228 address.setSubLocality(getCivicElementValue(CivicLocationKeys.NEIGHBORHOOD)); 229 address.setSubAdminArea(getCivicElementValue(CivicLocationKeys.COUNTY)); 230 address.setAdminArea(getCivicElementValue(CivicLocationKeys.STATE)); 231 address.setPostalCode(getCivicElementValue(CivicLocationKeys.POSTAL_CODE)); 232 address.setCountryCode(mCountryCode); // Country 233 return address; 234 } 235 236 /** 237 * Prepares an address element so that it can be integrated into an address line convention. 238 * 239 * <p>If an address element is null, the return string will be empty e.g. "". 240 * 241 * @param label a string defining the type of address element 242 * @param value a string defining the elements value 243 * @return the formatted version of the value, with null values converted to empty strings 244 */ formatAddressElement(String label, String value)245 private String formatAddressElement(String label, String value) { 246 if (value != null) { 247 return label + value; 248 } else { 249 return ""; 250 } 251 } 252 253 @Override equals(Object obj)254 public boolean equals(Object obj) { 255 if (this == obj) { 256 return true; 257 } 258 if (!(obj instanceof CivicLocation)) { 259 return false; 260 } 261 CivicLocation other = (CivicLocation) obj; 262 return mIsValid == other.mIsValid 263 && Objects.equals(mCountryCode, other.mCountryCode) 264 && isSparseArrayStringEqual(mCivicAddressElements, other.mCivicAddressElements); 265 } 266 267 @Override hashCode()268 public int hashCode() { 269 int[] civicAddressKeys = getSparseArrayKeys(mCivicAddressElements); 270 String[] civicAddressValues = getSparseArrayValues(mCivicAddressElements); 271 return Objects.hash(mIsValid, mCountryCode, Arrays.hashCode(civicAddressKeys), 272 Arrays.hashCode(civicAddressValues)); 273 } 274 275 /** 276 * Tests if the Civic Location object is valid 277 * 278 * @return a boolean defining mIsValid 279 */ isValid()280 public boolean isValid() { 281 return mIsValid; 282 } 283 284 /** 285 * Tests if two sparse arrays are equal on a key for key basis 286 * 287 * @param sa1 the first sparse array 288 * @param sa2 the second sparse array 289 * @return the boolean result after comparing values key by key 290 */ isSparseArrayStringEqual(SparseArray<String> sa1, SparseArray<String> sa2)291 private boolean isSparseArrayStringEqual(SparseArray<String> sa1, SparseArray<String> sa2) { 292 int size = sa1.size(); 293 if (size != sa2.size()) { 294 return false; 295 } 296 for (int i = 0; i < size; i++) { 297 String sa1Value = sa1.valueAt(i); 298 String sa2Value = sa2.valueAt(i); 299 if (!sa1Value.equals(sa2Value)) { 300 return false; 301 } 302 } 303 return true; 304 } 305 306 /** 307 * Extract an array of all the keys in a SparseArray<String> 308 * 309 * @param sa the sparse array of Strings 310 * @return an integer array of all keys in the SparseArray<String> 311 */ getSparseArrayKeys(SparseArray<String> sa)312 private int[] getSparseArrayKeys(SparseArray<String> sa) { 313 int size = sa.size(); 314 int[] keys = new int[size]; 315 for (int i = 0; i < size; i++) { 316 keys[i] = sa.keyAt(i); 317 } 318 return keys; 319 } 320 321 /** 322 * Extract an array of all the String values in a SparseArray<String> 323 * 324 * @param sa the sparse array of Strings 325 * @return a String array of all values in the SparseArray<String> 326 */ getSparseArrayValues(SparseArray<String> sa)327 private String[] getSparseArrayValues(SparseArray<String> sa) { 328 int size = sa.size(); 329 String[] values = new String[size]; 330 for (int i = 0; i < size; i++) { 331 values[i] = sa.valueAt(i); 332 } 333 return values; 334 } 335 } 336