1package com.android.server.wifi.hotspot2; 2 3import android.content.Context; 4import android.content.SharedPreferences; 5import android.net.wifi.WifiManager; 6import android.os.SystemProperties; 7import android.telephony.TelephonyManager; 8import android.text.TextUtils; 9import android.util.Log; 10 11import com.android.server.wifi.anqp.eap.EAP; 12import com.android.server.wifi.hotspot2.omadm.MOTree; 13import com.android.server.wifi.hotspot2.omadm.OMAConstants; 14import com.android.server.wifi.hotspot2.omadm.OMAConstructed; 15 16import java.io.IOException; 17import java.nio.charset.StandardCharsets; 18import java.util.ArrayList; 19import java.util.Arrays; 20import java.util.HashMap; 21import java.util.List; 22import java.util.Locale; 23import java.util.Map; 24import java.util.concurrent.atomic.AtomicInteger; 25 26import static com.android.server.wifi.anqp.eap.NonEAPInnerAuth.NonEAPType; 27import static com.android.server.wifi.anqp.eap.NonEAPInnerAuth.mapInnerType; 28 29public class OMADMAdapter { 30 private final Context mContext; 31 private final String mImei; 32 private final String mImsi; 33 private final String mDevID; 34 private final List<PathAccessor> mDevInfo; 35 private final List<PathAccessor> mDevDetail; 36 37 private static final int IMEI_Length = 14; 38 39 private static final String[] ExtWiFiPath = {"DevDetail", "Ext", "org.wi-fi", "Wi-Fi"}; 40 41 private static final Map<String, String> RTProps = new HashMap<>(); 42 43 private MOTree mDevInfoTree; 44 private MOTree mDevDetailTree; 45 46 private static OMADMAdapter sInstance; 47 48 static { 49 RTProps.put(ExtWiFiPath[2], "urn:wfa:mo-ext:hotspot2dot0-devdetail-ext:1.0"); 50 } 51 52 private static abstract class PathAccessor { 53 private final String[] mPath; 54 private final int mHashCode; 55 56 protected PathAccessor(Object ... path) { 57 int length = 0; 58 for (Object o : path) { 59 if (o.getClass() == String[].class) { 60 length += ((String[]) o).length; 61 } 62 else { 63 length++; 64 } 65 } 66 mPath = new String[length]; 67 int n = 0; 68 for (Object o : path) { 69 if (o.getClass() == String[].class) { 70 for (String element : (String[]) o) { 71 mPath[n++] = element; 72 } 73 } 74 else if (o.getClass() == Integer.class) { 75 mPath[n++] = "x" + o.toString(); 76 } 77 else { 78 mPath[n++] = o.toString(); 79 } 80 } 81 mHashCode = Arrays.hashCode(mPath); 82 } 83 84 @Override 85 public int hashCode() { 86 return mHashCode; 87 } 88 89 @Override 90 public boolean equals(Object thatObject) { 91 return thatObject == this || (thatObject instanceof ConstPathAccessor && 92 Arrays.equals(mPath, ((PathAccessor) thatObject).mPath)); 93 } 94 95 private String[] getPath() { 96 return mPath; 97 } 98 99 protected abstract Object getValue(); 100 } 101 102 private static class ConstPathAccessor<T> extends PathAccessor { 103 private final T mValue; 104 105 protected ConstPathAccessor(T value, Object ... path) { 106 super(path); 107 mValue = value; 108 } 109 110 protected Object getValue() { 111 return mValue; 112 } 113 } 114 115 public static OMADMAdapter getInstance(Context context) { 116 synchronized (OMADMAdapter.class) { 117 if (sInstance == null) { 118 sInstance = new OMADMAdapter(context); 119 } 120 return sInstance; 121 } 122 } 123 124 private OMADMAdapter(Context context) { 125 mContext = context; 126 127 TelephonyManager tm = (TelephonyManager) context 128 .getSystemService(Context.TELEPHONY_SERVICE); 129 String simOperator = tm.getSimOperator(); 130 mImsi = tm.getSubscriberId(); 131 mImei = tm.getImei(); 132 String strDevId; 133 134 /* Use MEID for sprint */ 135 if ("310120".equals(simOperator) || (mImsi != null && mImsi.startsWith("310120"))) { 136 /* MEID is 14 digits. If IMEI is returned as DevId, MEID can be extracted by taking 137 * first 14 characters. This is not always true but should be the case for sprint */ 138 strDevId = tm.getDeviceId().toUpperCase(Locale.US); 139 if (strDevId != null && strDevId.length() >= IMEI_Length) { 140 strDevId = strDevId.substring(0, IMEI_Length); 141 } else { 142 Log.w(Utils.hs2LogTag(getClass()), 143 "MEID cannot be extracted from DeviceId " + strDevId); 144 } 145 } else { 146 if (isPhoneTypeLTE()) { 147 strDevId = mImei; 148 } else { 149 strDevId = tm.getDeviceId(); 150 } 151 if (strDevId == null) { 152 strDevId = "unknown"; 153 } 154 strDevId = strDevId.toUpperCase(Locale.US); 155 156 if (!isPhoneTypeLTE()) { 157 strDevId = strDevId.substring(0, IMEI_Length); 158 } 159 } 160 mDevID = strDevId; 161 162 mDevInfo = new ArrayList<>(); 163 mDevInfo.add(new ConstPathAccessor<>(strDevId, "DevInfo", "DevID")); 164 mDevInfo.add(new ConstPathAccessor<>(getProperty(context, "Man", "ro.product.manufacturer", "unknown"), "DevInfo", "Man")); 165 mDevInfo.add(new ConstPathAccessor<>(getProperty(context, "Mod", "ro.product.model", "generic"), "DevInfo", "Mod")); 166 mDevInfo.add(new ConstPathAccessor<>(getLocale(context), "DevInfo", "Lang")); 167 mDevInfo.add(new ConstPathAccessor<>("1.2", "DevInfo", "DmV")); 168 169 mDevDetail = new ArrayList<>(); 170 mDevDetail.add(new ConstPathAccessor<>(getDeviceType(), "DevDetail", "DevType")); 171 mDevDetail.add(new ConstPathAccessor<>(SystemProperties.get("ro.product.brand"), "DevDetail", "OEM")); 172 mDevDetail.add(new ConstPathAccessor<>(getVersion(context, false), "DevDetail", "FwV")); 173 mDevDetail.add(new ConstPathAccessor<>(getVersion(context, true), "DevDetail", "SwV")); 174 mDevDetail.add(new ConstPathAccessor<>(getHwV(), "DevDetail", "HwV")); 175 mDevDetail.add(new ConstPathAccessor<>("TRUE", "DevDetail", "LrgObj")); 176 177 mDevDetail.add(new ConstPathAccessor<>(32, "DevDetail", "URI", "MaxDepth")); 178 mDevDetail.add(new ConstPathAccessor<>(2048, "DevDetail", "URI", "MaxTotLen")); 179 mDevDetail.add(new ConstPathAccessor<>(64, "DevDetail", "URI", "MaxSegLen")); 180 181 AtomicInteger index = new AtomicInteger(1); 182 mDevDetail.add(new ConstPathAccessor<>(EAP.EAP_TTLS, ExtWiFiPath, "EAPMethodList", index, "EAPType")); 183 mDevDetail.add(new ConstPathAccessor<>(mapInnerType(NonEAPType.MSCHAPv2), ExtWiFiPath, "EAPMethodList", index, "InnerMethod")); 184 185 index.incrementAndGet(); 186 mDevDetail.add(new ConstPathAccessor<>(EAP.EAP_TTLS, ExtWiFiPath, "EAPMethodList", index, "EAPType")); 187 mDevDetail.add(new ConstPathAccessor<>(mapInnerType(NonEAPType.PAP), ExtWiFiPath, "EAPMethodList", index, "InnerMethod")); 188 189 index.incrementAndGet(); 190 mDevDetail.add(new ConstPathAccessor<>(EAP.EAP_TTLS, ExtWiFiPath, "EAPMethodList", index, "EAPType")); 191 mDevDetail.add(new ConstPathAccessor<>(mapInnerType(NonEAPType.MSCHAP), ExtWiFiPath, "EAPMethodList", index, "InnerMethod")); 192 193 index.incrementAndGet(); 194 mDevDetail.add(new ConstPathAccessor<>(EAP.EAP_TLS, ExtWiFiPath, "EAPMethodList", index, "EAPType")); 195 index.incrementAndGet(); 196 mDevDetail.add(new ConstPathAccessor<>(EAP.EAP_AKA, ExtWiFiPath, "EAPMethodList", index, "EAPType")); 197 index.incrementAndGet(); 198 mDevDetail.add(new ConstPathAccessor<>(EAP.EAP_AKAPrim, ExtWiFiPath, "EAPMethodList", index, "EAPType")); 199 index.incrementAndGet(); 200 mDevDetail.add(new ConstPathAccessor<>(EAP.EAP_SIM, ExtWiFiPath, "EAPMethodList", index, "EAPType")); 201 202 mDevDetail.add(new ConstPathAccessor<>("FALSE", ExtWiFiPath, "ManufacturingCertificate")); 203 mDevDetail.add(new ConstPathAccessor<>(mImsi, ExtWiFiPath, "IMSI")); 204 mDevDetail.add(new ConstPathAccessor<>(mImei, ExtWiFiPath, "IMEI_MEID")); 205 mDevDetail.add(new PathAccessor(ExtWiFiPath, "Wi-FiMACAddress") { 206 @Override 207 protected String getValue() { 208 return getMAC(); 209 } 210 }); 211 } 212 213 private static void buildNode(PathAccessor pathAccessor, int depth, OMAConstructed parent) 214 throws IOException { 215 String[] path = pathAccessor.getPath(); 216 String name = path[depth]; 217 if (depth < path.length - 1) { 218 OMAConstructed node = (OMAConstructed) parent.getChild(name); 219 if (node == null) { 220 node = (OMAConstructed) parent.addChild(name, RTProps.get(name), 221 null, null); 222 } 223 buildNode(pathAccessor, depth + 1, node); 224 } 225 else if (pathAccessor.getValue() != null) { 226 parent.addChild(name, null, pathAccessor.getValue().toString(), null); 227 } 228 } 229 230 public String getMAC() { 231 WifiManager wifiManager = (WifiManager) mContext.getSystemService(Context.WIFI_SERVICE); 232 return wifiManager != null ? 233 String.format("%012x", 234 Utils.parseMac(wifiManager.getConnectionInfo().getMacAddress())) : 235 null; 236 } 237 238 public String getImei() { 239 return mImei; 240 } 241 242 public byte[] getMeid() { 243 return Arrays.copyOf(mImei.getBytes(StandardCharsets.ISO_8859_1), IMEI_Length); 244 } 245 246 public String getDevID() { 247 return mDevID; 248 } 249 250 public MOTree getMO(String urn) { 251 try { 252 switch (urn) { 253 case OMAConstants.DevInfoURN: 254 if (mDevInfoTree == null) { 255 OMAConstructed root = new OMAConstructed(null, "DevInfo", urn); 256 for (PathAccessor pathAccessor : mDevInfo) { 257 buildNode(pathAccessor, 1, root); 258 } 259 mDevInfoTree = MOTree.buildMgmtTree(OMAConstants.DevInfoURN, 260 OMAConstants.OMAVersion, root); 261 } 262 return mDevInfoTree; 263 case OMAConstants.DevDetailURN: 264 if (mDevDetailTree == null) { 265 OMAConstructed root = new OMAConstructed(null, "DevDetail", urn); 266 for (PathAccessor pathAccessor : mDevDetail) { 267 buildNode(pathAccessor, 1, root); 268 } 269 mDevDetailTree = MOTree.buildMgmtTree(OMAConstants.DevDetailURN, 270 OMAConstants.OMAVersion, root); 271 } 272 return mDevDetailTree; 273 default: 274 throw new IllegalArgumentException(urn); 275 } 276 } 277 catch (IOException ioe) { 278 Log.e(Utils.hs2LogTag(getClass()), "Caught exception building OMA Tree: " + ioe, ioe); 279 return null; 280 } 281 282 /* 283 switch (urn) { 284 case DevInfoURN: return DevInfo; 285 case DevDetailURN: return DevDetail; 286 default: throw new IllegalArgumentException(urn); 287 } 288 */ 289 } 290 291 // TODO: For now, assume the device supports LTE. 292 private static boolean isPhoneTypeLTE() { 293 return true; 294 } 295 296 private static String getHwV() { 297 try { 298 return SystemProperties.get("ro.hardware", "Unknown") 299 + "." + SystemProperties.get("ro.revision", "Unknown"); 300 } catch (RuntimeException e) { 301 return "Unknown"; 302 } 303 } 304 305 private static String getDeviceType() { 306 String devicetype = SystemProperties.get("ro.build.characteristics"); 307 if ((((TextUtils.isEmpty(devicetype)) || (!devicetype.equals("tablet"))))) { 308 devicetype = "phone"; 309 } 310 return devicetype; 311 } 312 313 private static String getVersion(Context context, boolean swv) { 314 String version; 315 try { 316 if (!isSprint(context) && swv) { 317 return "Android " + SystemProperties.get("ro.build.version.release"); 318 } else { 319 version = SystemProperties.get("ro.build.version.full"); 320 if (null == version || version.equals("")) { 321 return SystemProperties.get("ro.build.id", null) + "~" 322 + SystemProperties.get("ro.build.config.version", null) + "~" 323 + SystemProperties.get("gsm.version.baseband", null) + "~" 324 + SystemProperties.get("ro.gsm.flexversion", null); 325 } 326 } 327 } catch (RuntimeException e) { 328 return "Unknown"; 329 } 330 return version; 331 } 332 333 private static boolean isSprint(Context context) { 334 TelephonyManager tm = (TelephonyManager) context 335 .getSystemService(Context.TELEPHONY_SERVICE); 336 String simOperator = tm.getSimOperator(); 337 String imsi = tm.getSubscriberId(); 338 /* Use MEID for sprint */ 339 if ("310120".equals(simOperator) || (imsi != null && imsi.startsWith("310120"))) { 340 return true; 341 } else { 342 return false; 343 } 344 } 345 346 private static String getLocale(Context context) { 347 String strLang = readValueFromFile(context, "Lang"); 348 if (strLang == null) { 349 strLang = Locale.getDefault().toString(); 350 } 351 return strLang; 352 } 353 354 private static String getProperty(Context context, String key, String propKey, String dflt) { 355 String strMan = readValueFromFile(context, key); 356 if (strMan == null) { 357 strMan = SystemProperties.get(propKey, dflt); 358 } 359 return strMan; 360 } 361 362 private static String readValueFromFile(Context context, String propName) { 363 String ret = null; 364 // use preference instead of the system property 365 SharedPreferences prefs = context.getSharedPreferences("dmconfig", 0); 366 if (prefs.contains(propName)) { 367 ret = prefs.getString(propName, ""); 368 if (ret.length() == 0) { 369 ret = null; 370 } 371 } 372 return ret; 373 } 374 375 private static final String DevDetail = 376 "<MgmtTree>" + 377 "<VerDTD>1.2</VerDTD>" + 378 "<Node>" + 379 "<NodeName>DevDetail</NodeName>" + 380 "<RTProperties>" + 381 "<Type>" + 382 "<DDFName>urn:oma:mo:oma-dm-devdetail:1.0</DDFName>" + 383 "</Type>" + 384 "</RTProperties>" + 385 "<Node>" + 386 "<NodeName>Ext</NodeName>" + 387 "<Node>" + 388 "<NodeName>org.wi-fi</NodeName>" + 389 "<RTProperties>" + 390 "<Type>" + 391 "<DDFName>" + 392 "urn:wfa:mo-ext:hotspot2dot0-devdetail-ext :1.0" + 393 "</DDFName>" + 394 "</Type>" + 395 "</RTProperties>" + 396 "<Node>" + 397 "<NodeName>Wi-Fi</NodeName>" + 398 "<Node>" + 399 "<NodeName>EAPMethodList</NodeName>" + 400 "<Node>" + 401 "<NodeName>Method01</NodeName>" + 402 "<!-- EAP-TTLS/MS-CHAPv2 -->" + 403 "<Node>" + 404 "<NodeName>EAPType</NodeName>" + 405 "<Value>21</Value>" + 406 "</Node>" + 407 "<Node>" + 408 "<NodeName>InnerMethod</NodeName>" + 409 "<Value>MS-CHAP-V2</Value>" + 410 "</Node>" + 411 "</Node>" + 412 "<Node>" + 413 "<NodeName>Method02</NodeName>" + 414 "<!-- EAP-TLS -->" + 415 "<Node>" + 416 "<NodeName>EAPType</NodeName>" + 417 "<Value>13</Value>" + 418 "</Node>" + 419 "</Node>" + 420 "<Node>" + 421 "<NodeName>Method03</NodeName>" + 422 "<!-- EAP-SIM -->" + 423 "<Node>" + 424 "<NodeName>EAPType</NodeName>" + 425 "<Value>18</Value>" + 426 "</Node>" + 427 "</Node>" + 428 "<Node>" + 429 "<NodeName>Method04</NodeName>" + 430 "<!-- EAP-AKA -->" + 431 "<Node>" + 432 "<NodeName>EAPType</NodeName>" + 433 "<Value>23</Value>" + 434 "</Node>" + 435 "</Node>" + 436 "<Node>" + 437 "<NodeName>Method05</NodeName>" + 438 "<!-- EAP-AKA' -->" + 439 "<Node>" + 440 "<NodeName>EAPType</NodeName>" + 441 "<Value>50</Value>" + 442 "</Node>" + 443 "</Node>" + 444 "<Node>" + 445 "<NodeName>Method06</NodeName>" + 446 "<!-- Supported method (EAP-TTLS/PAP) not mandated by Hotspot2.0-->" + 447 "<Node>" + 448 "<NodeName>EAPType</NodeName>" + 449 "<Value>21</Value>" + 450 "</Node>" + 451 "<Node>" + 452 "<NodeName>InnerMethod</NodeName>" + 453 "<Value>PAP</Value>" + 454 "</Node>" + 455 "</Node>" + 456 "<Node>" + 457 "<NodeName>Method07</NodeName>" + 458 "<!-- Supported method (PEAP/EAP-GTC) not mandated by Hotspot 2.0-->" + 459 "<Node>" + 460 "<NodeName>EAPType</NodeName>" + 461 "<Value>25</Value>" + 462 "</Node>" + 463 "<Node>" + 464 "<NodeName>InnerEAPType</NodeName>" + 465 "<Value>6</Value>" + 466 "</Node>" + 467 "</Node>" + 468 "</Node>" + 469 "<Node>" + 470 "<NodeName>SPCertificate</NodeName>" + 471 "<Node>" + 472 "<NodeName>Cert01</NodeName>" + 473 "<Node>" + 474 "<NodeName>CertificateIssuerName</NodeName>" + 475 "<Value>CN=RuckusCA</Value>" + 476 "</Node>" + 477 "</Node>" + 478 "</Node>" + 479 "<Node>" + 480 "<NodeName>ManufacturingCertificate</NodeName>" + 481 "<Value>FALSE</Value>" + 482 "</Node>" + 483 "<Node>" + 484 "<NodeName>Wi-FiMACAddress</NodeName>" + 485 "<Value>001d2e112233</Value>" + 486 "</Node>" + 487 "<Node>" + 488 "<NodeName>ClientTriggerRedirectURI</NodeName>" + 489 "<Value>http://127.0.0.1:12345/index.htm</Value>" + 490 "</Node>" + 491 "<Node>" + 492 "<NodeName>Ops</NodeName>" + 493 "<Node>" + 494 "<NodeName>launchBrowserToURI</NodeName>" + 495 "<Value></Value>" + 496 "</Node>" + 497 "<Node>" + 498 "<NodeName>negotiateClientCertTLS</NodeName>" + 499 "<Value></Value>" + 500 "</Node>" + 501 "<Node>" + 502 "<NodeName>getCertificate</NodeName>" + 503 "<Value></Value>" + 504 "</Node>" + 505 "</Node>" + 506 "</Node>" + 507 "<!-- End of Wi-Fi node -->" + 508 "</Node>" + 509 "<!-- End of org.wi-fi node -->" + 510 "</Node>" + 511 "<!-- End of Ext node -->" + 512 "<Node>" + 513 "<NodeName>URI</NodeName>" + 514 "<Node>" + 515 "<NodeName>MaxDepth</NodeName>" + 516 "<Value>32</Value>" + 517 "</Node>" + 518 "<Node>" + 519 "<NodeName>MaxTotLen</NodeName>" + 520 "<Value>2048</Value>" + 521 "</Node>" + 522 "<Node>" + 523 "<NodeName>MaxSegLen</NodeName>" + 524 "<Value>64</Value>" + 525 "</Node>" + 526 "</Node>" + 527 "<Node>" + 528 "<NodeName>DevType</NodeName>" + 529 "<Value>Smartphone</Value>" + 530 "</Node>" + 531 "<Node>" + 532 "<NodeName>OEM</NodeName>" + 533 "<Value>ACME</Value>" + 534 "</Node>" + 535 "<Node>" + 536 "<NodeName>FwV</NodeName>" + 537 "<Value>1.2.100.5</Value>" + 538 "</Node>" + 539 "<Node>" + 540 "<NodeName>SwV</NodeName>" + 541 "<Value>9.11.130</Value>" + 542 "</Node>" + 543 "<Node>" + 544 "<NodeName>HwV</NodeName>" + 545 "<Value>1.0</Value>" + 546 "</Node>" + 547 "<Node>" + 548 "<NodeName>LrgObj</NodeName>" + 549 "<Value>TRUE</Value>" + 550 "</Node>" + 551 "</Node>" + 552 "</MgmtTree>"; 553 554 555 private static final String DevInfo = 556 "<MgmtTree>" + 557 "<VerDTD>1.2</VerDTD>" + 558 "<Node>" + 559 "<NodeName>DevInfo</NodeName>" + 560 "<RTProperties>" + 561 "<Type>" + 562 "<DDFName>urn:oma:mo:oma-dm-devinfo:1.0" + 563 "</DDFName>" + 564 "</Type>" + 565 "</RTProperties>" + 566 "</Node>" + 567 "<Node>" + 568 "<NodeName>DevID</NodeName>" + 569 "<Path>DevInfo</Path>" + 570 "<Value>urn:acme:00-11-22-33-44-55</Value>" + 571 "</Node>" + 572 "<Node>" + 573 "<NodeName>Man</NodeName>" + 574 "<Path>DevInfo</Path>" + 575 "<Value>ACME</Value>" + 576 "</Node>" + 577 "<Node>" + 578 "<NodeName>Mod</NodeName>" + 579 "<Path>DevInfo</Path>" + 580 "<Value>HS2.0-01</Value>" + 581 "</Node>" + 582 "<Node>" + 583 "<NodeName>DmV</NodeName>" + 584 "<Path>DevInfo</Path>" + 585 "<Value>1.2</Value>" + 586 "</Node>" + 587 "<Node>" + 588 "<NodeName>Lang</NodeName>" + 589 "<Path>DevInfo</Path>" + 590 "<Value>en-US</Value>" + 591 "</Node>" + 592 "</MgmtTree>"; 593} 594