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