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