| package com.android.hotspot2; |
| |
| import android.content.Context; |
| import android.content.SharedPreferences; |
| import android.net.wifi.WifiManager; |
| import android.os.SystemProperties; |
| import android.telephony.TelephonyManager; |
| import android.text.TextUtils; |
| import android.util.Log; |
| |
| import com.android.anqp.eap.EAP; |
| import com.android.hotspot2.omadm.MOTree; |
| import com.android.hotspot2.omadm.OMAConstants; |
| import com.android.hotspot2.omadm.OMAConstructed; |
| import com.android.hotspot2.osu.OSUManager; |
| |
| import java.io.IOException; |
| import java.nio.charset.StandardCharsets; |
| import java.util.ArrayList; |
| import java.util.Arrays; |
| import java.util.HashMap; |
| import java.util.List; |
| import java.util.Locale; |
| import java.util.Map; |
| import java.util.concurrent.atomic.AtomicInteger; |
| |
| import static com.android.anqp.eap.NonEAPInnerAuth.NonEAPType; |
| import static com.android.anqp.eap.NonEAPInnerAuth.mapInnerType; |
| |
| public class OMADMAdapter { |
| private final Context mContext; |
| private final String mImei; |
| private final String mImsi; |
| private final String mDevID; |
| private final List<PathAccessor> mDevInfo; |
| private final List<PathAccessor> mDevDetail; |
| |
| private static final int IMEI_Length = 14; |
| |
| private static final String[] ExtWiFiPath = {"DevDetail", "Ext", "org.wi-fi", "Wi-Fi"}; |
| |
| private static final Map<String, String> RTProps = new HashMap<>(); |
| |
| private MOTree mDevInfoTree; |
| private MOTree mDevDetailTree; |
| |
| private static OMADMAdapter sInstance; |
| |
| static { |
| RTProps.put(ExtWiFiPath[2], "urn:wfa:mo-ext:hotspot2dot0-devdetail-ext:1.0"); |
| } |
| |
| private static abstract class PathAccessor { |
| private final String[] mPath; |
| private final int mHashCode; |
| |
| protected PathAccessor(Object... path) { |
| int length = 0; |
| for (Object o : path) { |
| if (o.getClass() == String[].class) { |
| length += ((String[]) o).length; |
| } else { |
| length++; |
| } |
| } |
| mPath = new String[length]; |
| int n = 0; |
| for (Object o : path) { |
| if (o.getClass() == String[].class) { |
| for (String element : (String[]) o) { |
| mPath[n++] = element; |
| } |
| } else if (o.getClass() == Integer.class) { |
| mPath[n++] = "x" + o.toString(); |
| } else { |
| mPath[n++] = o.toString(); |
| } |
| } |
| mHashCode = Arrays.hashCode(mPath); |
| } |
| |
| @Override |
| public int hashCode() { |
| return mHashCode; |
| } |
| |
| @Override |
| public boolean equals(Object thatObject) { |
| return thatObject == this || (thatObject instanceof ConstPathAccessor && |
| Arrays.equals(mPath, ((PathAccessor) thatObject).mPath)); |
| } |
| |
| private String[] getPath() { |
| return mPath; |
| } |
| |
| protected abstract Object getValue(); |
| } |
| |
| private static class ConstPathAccessor<T> extends PathAccessor { |
| private final T mValue; |
| |
| protected ConstPathAccessor(T value, Object... path) { |
| super(path); |
| mValue = value; |
| } |
| |
| protected Object getValue() { |
| return mValue; |
| } |
| } |
| |
| public static OMADMAdapter getInstance(Context context) { |
| synchronized (OMADMAdapter.class) { |
| if (sInstance == null) { |
| sInstance = new OMADMAdapter(context); |
| } |
| return sInstance; |
| } |
| } |
| |
| private OMADMAdapter(Context context) { |
| mContext = context; |
| |
| TelephonyManager tm = (TelephonyManager) context |
| .getSystemService(Context.TELEPHONY_SERVICE); |
| String simOperator = tm.getSimOperator(); |
| mImsi = tm.getSubscriberId(); |
| mImei = tm.getImei(); |
| String strDevId; |
| |
| /* Use MEID for sprint */ |
| if ("310120".equals(simOperator) || (mImsi != null && mImsi.startsWith("310120"))) { |
| /* MEID is 14 digits. If IMEI is returned as DevId, MEID can be extracted by taking |
| * first 14 characters. This is not always true but should be the case for sprint */ |
| strDevId = tm.getDeviceId().toUpperCase(Locale.US); |
| if (strDevId != null && strDevId.length() >= IMEI_Length) { |
| strDevId = strDevId.substring(0, IMEI_Length); |
| } else { |
| Log.w(OSUManager.TAG, "MEID cannot be extracted from DeviceId " + strDevId); |
| } |
| } else { |
| if (isPhoneTypeLTE()) { |
| strDevId = mImei; |
| } else { |
| strDevId = tm.getDeviceId(); |
| } |
| if (strDevId == null) { |
| strDevId = "unknown"; |
| } |
| strDevId = strDevId.toUpperCase(Locale.US); |
| |
| if (!isPhoneTypeLTE()) { |
| strDevId = strDevId.substring(0, IMEI_Length); |
| } |
| } |
| mDevID = strDevId; |
| |
| mDevInfo = new ArrayList<>(); |
| mDevInfo.add(new ConstPathAccessor<>(strDevId, "DevInfo", "DevID")); |
| mDevInfo.add(new ConstPathAccessor<>(getProperty(context, |
| "Man", "ro.product.manufacturer", "unknown"), "DevInfo", "Man")); |
| mDevInfo.add(new ConstPathAccessor<>(getProperty(context, |
| "Mod", "ro.product.model", "generic"), "DevInfo", "Mod")); |
| mDevInfo.add(new ConstPathAccessor<>(getLocale(context), "DevInfo", "Lang")); |
| mDevInfo.add(new ConstPathAccessor<>("1.2", "DevInfo", "DmV")); |
| |
| mDevDetail = new ArrayList<>(); |
| mDevDetail.add(new ConstPathAccessor<>(getDeviceType(), "DevDetail", "DevType")); |
| mDevDetail.add(new ConstPathAccessor<>(SystemProperties.get("ro.product.brand"), |
| "DevDetail", "OEM")); |
| mDevDetail.add(new ConstPathAccessor<>(getVersion(context, false), "DevDetail", "FwV")); |
| mDevDetail.add(new ConstPathAccessor<>(getVersion(context, true), "DevDetail", "SwV")); |
| mDevDetail.add(new ConstPathAccessor<>(getHwV(), "DevDetail", "HwV")); |
| mDevDetail.add(new ConstPathAccessor<>("TRUE", "DevDetail", "LrgObj")); |
| |
| mDevDetail.add(new ConstPathAccessor<>(32, "DevDetail", "URI", "MaxDepth")); |
| mDevDetail.add(new ConstPathAccessor<>(2048, "DevDetail", "URI", "MaxTotLen")); |
| mDevDetail.add(new ConstPathAccessor<>(64, "DevDetail", "URI", "MaxSegLen")); |
| |
| AtomicInteger index = new AtomicInteger(1); |
| mDevDetail.add(new ConstPathAccessor<>(EAP.EAP_TTLS, ExtWiFiPath, |
| "EAPMethodList", index, "EAPType")); |
| mDevDetail.add(new ConstPathAccessor<>(mapInnerType(NonEAPType.MSCHAPv2), ExtWiFiPath, |
| "EAPMethodList", index, "InnerMethod")); |
| |
| index.incrementAndGet(); |
| mDevDetail.add(new ConstPathAccessor<>(EAP.EAP_TTLS, ExtWiFiPath, |
| "EAPMethodList", index, "EAPType")); |
| mDevDetail.add(new ConstPathAccessor<>(mapInnerType(NonEAPType.PAP), ExtWiFiPath, |
| "EAPMethodList", index, "InnerMethod")); |
| |
| index.incrementAndGet(); |
| mDevDetail.add(new ConstPathAccessor<>(EAP.EAP_TTLS, ExtWiFiPath, |
| "EAPMethodList", index, "EAPType")); |
| mDevDetail.add(new ConstPathAccessor<>(mapInnerType(NonEAPType.MSCHAP), ExtWiFiPath, |
| "EAPMethodList", index, "InnerMethod")); |
| |
| index.incrementAndGet(); |
| mDevDetail.add(new ConstPathAccessor<>(EAP.EAP_TLS, ExtWiFiPath, |
| "EAPMethodList", index, "EAPType")); |
| index.incrementAndGet(); |
| mDevDetail.add(new ConstPathAccessor<>(EAP.EAP_AKA, ExtWiFiPath, |
| "EAPMethodList", index, "EAPType")); |
| index.incrementAndGet(); |
| mDevDetail.add(new ConstPathAccessor<>(EAP.EAP_AKAPrim, ExtWiFiPath, |
| "EAPMethodList", index, "EAPType")); |
| index.incrementAndGet(); |
| mDevDetail.add(new ConstPathAccessor<>(EAP.EAP_SIM, ExtWiFiPath, |
| "EAPMethodList", index, "EAPType")); |
| |
| mDevDetail.add(new ConstPathAccessor<>("FALSE", ExtWiFiPath, "ManufacturingCertificate")); |
| mDevDetail.add(new ConstPathAccessor<>(mImsi, ExtWiFiPath, "IMSI")); |
| mDevDetail.add(new ConstPathAccessor<>(mImei, ExtWiFiPath, "IMEI_MEID")); |
| mDevDetail.add(new PathAccessor(ExtWiFiPath, "Wi-FiMACAddress") { |
| @Override |
| protected String getValue() { |
| return getMAC(); |
| } |
| }); |
| } |
| |
| private static void buildNode(PathAccessor pathAccessor, int depth, OMAConstructed parent) |
| throws IOException { |
| String[] path = pathAccessor.getPath(); |
| String name = path[depth]; |
| if (depth < path.length - 1) { |
| OMAConstructed node = (OMAConstructed) parent.getChild(name); |
| if (node == null) { |
| node = (OMAConstructed) parent.addChild(name, RTProps.get(name), |
| null, null); |
| } |
| buildNode(pathAccessor, depth + 1, node); |
| } else if (pathAccessor.getValue() != null) { |
| parent.addChild(name, null, pathAccessor.getValue().toString(), null); |
| } |
| } |
| |
| public String getMAC() { |
| WifiManager wifiManager = (WifiManager) mContext.getSystemService(Context.WIFI_SERVICE); |
| return wifiManager != null ? |
| String.format("%012x", |
| Utils.parseMac(wifiManager.getConnectionInfo().getMacAddress())) : |
| null; |
| } |
| |
| public String getImei() { |
| return mImei; |
| } |
| |
| public byte[] getMeid() { |
| return Arrays.copyOf(mImei.getBytes(StandardCharsets.ISO_8859_1), IMEI_Length); |
| } |
| |
| public String getDevID() { |
| return mDevID; |
| } |
| |
| public MOTree getMO(String urn) { |
| try { |
| switch (urn) { |
| case OMAConstants.DevInfoURN: |
| if (mDevInfoTree == null) { |
| OMAConstructed root = new OMAConstructed(null, "DevInfo", urn); |
| for (PathAccessor pathAccessor : mDevInfo) { |
| buildNode(pathAccessor, 1, root); |
| } |
| mDevInfoTree = MOTree.buildMgmtTree(OMAConstants.DevInfoURN, |
| OMAConstants.OMAVersion, root); |
| } |
| return mDevInfoTree; |
| case OMAConstants.DevDetailURN: |
| if (mDevDetailTree == null) { |
| OMAConstructed root = new OMAConstructed(null, "DevDetail", urn); |
| for (PathAccessor pathAccessor : mDevDetail) { |
| buildNode(pathAccessor, 1, root); |
| } |
| mDevDetailTree = MOTree.buildMgmtTree(OMAConstants.DevDetailURN, |
| OMAConstants.OMAVersion, root); |
| } |
| return mDevDetailTree; |
| default: |
| throw new IllegalArgumentException(urn); |
| } |
| } catch (IOException ioe) { |
| Log.e(OSUManager.TAG, "Caught exception building OMA Tree: " + ioe, ioe); |
| return null; |
| } |
| |
| /* |
| switch (urn) { |
| case DevInfoURN: return DevInfo; |
| case DevDetailURN: return DevDetail; |
| default: throw new IllegalArgumentException(urn); |
| } |
| */ |
| } |
| |
| // TODO: For now, assume the device supports LTE. |
| private static boolean isPhoneTypeLTE() { |
| return true; |
| } |
| |
| private static String getHwV() { |
| try { |
| return SystemProperties.get("ro.hardware", "Unknown") |
| + "." + SystemProperties.get("ro.revision", "Unknown"); |
| } catch (RuntimeException e) { |
| return "Unknown"; |
| } |
| } |
| |
| private static String getDeviceType() { |
| String devicetype = SystemProperties.get("ro.build.characteristics"); |
| if ((((TextUtils.isEmpty(devicetype)) || (!devicetype.equals("tablet"))))) { |
| devicetype = "phone"; |
| } |
| return devicetype; |
| } |
| |
| private static String getVersion(Context context, boolean swv) { |
| String version; |
| try { |
| if (!isSprint(context) && swv) { |
| return "Android " + SystemProperties.get("ro.build.version.release"); |
| } else { |
| version = SystemProperties.get("ro.build.version.full"); |
| if (null == version || version.equals("")) { |
| return SystemProperties.get("ro.build.id", null) + "~" |
| + SystemProperties.get("ro.build.config.version", null) + "~" |
| + SystemProperties.get("gsm.version.baseband", null) + "~" |
| + SystemProperties.get("ro.gsm.flexversion", null); |
| } |
| } |
| } catch (RuntimeException e) { |
| return "Unknown"; |
| } |
| return version; |
| } |
| |
| private static boolean isSprint(Context context) { |
| TelephonyManager tm = (TelephonyManager) context |
| .getSystemService(Context.TELEPHONY_SERVICE); |
| String simOperator = tm.getSimOperator(); |
| String imsi = tm.getSubscriberId(); |
| /* Use MEID for sprint */ |
| if ("310120".equals(simOperator) || (imsi != null && imsi.startsWith("310120"))) { |
| return true; |
| } else { |
| return false; |
| } |
| } |
| |
| private static String getLocale(Context context) { |
| String strLang = readValueFromFile(context, "Lang"); |
| if (strLang == null) { |
| strLang = Locale.getDefault().toString(); |
| } |
| return strLang; |
| } |
| |
| private static String getProperty(Context context, String key, String propKey, String dflt) { |
| String strMan = readValueFromFile(context, key); |
| if (strMan == null) { |
| strMan = SystemProperties.get(propKey, dflt); |
| } |
| return strMan; |
| } |
| |
| private static String readValueFromFile(Context context, String propName) { |
| String ret = null; |
| // use preference instead of the system property |
| SharedPreferences prefs = context.getSharedPreferences("dmconfig", 0); |
| if (prefs.contains(propName)) { |
| ret = prefs.getString(propName, ""); |
| if (ret.length() == 0) { |
| ret = null; |
| } |
| } |
| return ret; |
| } |
| |
| private static final String DevDetail = |
| "<MgmtTree>" + |
| "<VerDTD>1.2</VerDTD>" + |
| "<Node>" + |
| "<NodeName>DevDetail</NodeName>" + |
| "<RTProperties>" + |
| "<Type>" + |
| "<DDFName>urn:oma:mo:oma-dm-devdetail:1.0</DDFName>" + |
| "</Type>" + |
| "</RTProperties>" + |
| "<Node>" + |
| "<NodeName>Ext</NodeName>" + |
| "<Node>" + |
| "<NodeName>org.wi-fi</NodeName>" + |
| "<RTProperties>" + |
| "<Type>" + |
| "<DDFName>" + |
| "urn:wfa:mo-ext:hotspot2dot0-devdetail-ext :1.0" + |
| "</DDFName>" + |
| "</Type>" + |
| "</RTProperties>" + |
| "<Node>" + |
| "<NodeName>Wi-Fi</NodeName>" + |
| "<Node>" + |
| "<NodeName>EAPMethodList</NodeName>" + |
| "<Node>" + |
| "<NodeName>Method01</NodeName>" + |
| "<!-- EAP-TTLS/MS-CHAPv2 -->" + |
| "<Node>" + |
| "<NodeName>EAPType</NodeName>" + |
| "<Value>21</Value>" + |
| "</Node>" + |
| "<Node>" + |
| "<NodeName>InnerMethod</NodeName>" + |
| "<Value>MS-CHAP-V2</Value>" + |
| "</Node>" + |
| "</Node>" + |
| "<Node>" + |
| "<NodeName>Method02</NodeName>" + |
| "<!-- EAP-TLS -->" + |
| "<Node>" + |
| "<NodeName>EAPType</NodeName>" + |
| "<Value>13</Value>" + |
| "</Node>" + |
| "</Node>" + |
| "<Node>" + |
| "<NodeName>Method03</NodeName>" + |
| "<!-- EAP-SIM -->" + |
| "<Node>" + |
| "<NodeName>EAPType</NodeName>" + |
| "<Value>18</Value>" + |
| "</Node>" + |
| "</Node>" + |
| "<Node>" + |
| "<NodeName>Method04</NodeName>" + |
| "<!-- EAP-AKA -->" + |
| "<Node>" + |
| "<NodeName>EAPType</NodeName>" + |
| "<Value>23</Value>" + |
| "</Node>" + |
| "</Node>" + |
| "<Node>" + |
| "<NodeName>Method05</NodeName>" + |
| "<!-- EAP-AKA' -->" + |
| "<Node>" + |
| "<NodeName>EAPType</NodeName>" + |
| "<Value>50</Value>" + |
| "</Node>" + |
| "</Node>" + |
| "<Node>" + |
| "<NodeName>Method06</NodeName>" + |
| "<!-- Supported method (EAP-TTLS/PAP) not mandated by Hotspot2.0-->" + |
| "<Node>" + |
| "<NodeName>EAPType</NodeName>" + |
| "<Value>21</Value>" + |
| "</Node>" + |
| "<Node>" + |
| "<NodeName>InnerMethod</NodeName>" + |
| "<Value>PAP</Value>" + |
| "</Node>" + |
| "</Node>" + |
| "<Node>" + |
| "<NodeName>Method07</NodeName>" + |
| "<!-- Supported method (PEAP/EAP-GTC) not mandated by Hotspot 2.0-->" + |
| "<Node>" + |
| "<NodeName>EAPType</NodeName>" + |
| "<Value>25</Value>" + |
| "</Node>" + |
| "<Node>" + |
| "<NodeName>InnerEAPType</NodeName>" + |
| "<Value>6</Value>" + |
| "</Node>" + |
| "</Node>" + |
| "</Node>" + |
| "<Node>" + |
| "<NodeName>SPCertificate</NodeName>" + |
| "<Node>" + |
| "<NodeName>Cert01</NodeName>" + |
| "<Node>" + |
| "<NodeName>CertificateIssuerName</NodeName>" + |
| "<Value>CN=RuckusCA</Value>" + |
| "</Node>" + |
| "</Node>" + |
| "</Node>" + |
| "<Node>" + |
| "<NodeName>ManufacturingCertificate</NodeName>" + |
| "<Value>FALSE</Value>" + |
| "</Node>" + |
| "<Node>" + |
| "<NodeName>Wi-FiMACAddress</NodeName>" + |
| "<Value>001d2e112233</Value>" + |
| "</Node>" + |
| "<Node>" + |
| "<NodeName>ClientTriggerRedirectURI</NodeName>" + |
| "<Value>http://127.0.0.1:12345/index.htm</Value>" + |
| "</Node>" + |
| "<Node>" + |
| "<NodeName>Ops</NodeName>" + |
| "<Node>" + |
| "<NodeName>launchBrowserToURI</NodeName>" + |
| "<Value></Value>" + |
| "</Node>" + |
| "<Node>" + |
| "<NodeName>negotiateClientCertTLS</NodeName>" + |
| "<Value></Value>" + |
| "</Node>" + |
| "<Node>" + |
| "<NodeName>getCertificate</NodeName>" + |
| "<Value></Value>" + |
| "</Node>" + |
| "</Node>" + |
| "</Node>" + |
| "<!-- End of Wi-Fi node -->" + |
| "</Node>" + |
| "<!-- End of org.wi-fi node -->" + |
| "</Node>" + |
| "<!-- End of Ext node -->" + |
| "<Node>" + |
| "<NodeName>URI</NodeName>" + |
| "<Node>" + |
| "<NodeName>MaxDepth</NodeName>" + |
| "<Value>32</Value>" + |
| "</Node>" + |
| "<Node>" + |
| "<NodeName>MaxTotLen</NodeName>" + |
| "<Value>2048</Value>" + |
| "</Node>" + |
| "<Node>" + |
| "<NodeName>MaxSegLen</NodeName>" + |
| "<Value>64</Value>" + |
| "</Node>" + |
| "</Node>" + |
| "<Node>" + |
| "<NodeName>DevType</NodeName>" + |
| "<Value>Smartphone</Value>" + |
| "</Node>" + |
| "<Node>" + |
| "<NodeName>OEM</NodeName>" + |
| "<Value>ACME</Value>" + |
| "</Node>" + |
| "<Node>" + |
| "<NodeName>FwV</NodeName>" + |
| "<Value>1.2.100.5</Value>" + |
| "</Node>" + |
| "<Node>" + |
| "<NodeName>SwV</NodeName>" + |
| "<Value>9.11.130</Value>" + |
| "</Node>" + |
| "<Node>" + |
| "<NodeName>HwV</NodeName>" + |
| "<Value>1.0</Value>" + |
| "</Node>" + |
| "<Node>" + |
| "<NodeName>LrgObj</NodeName>" + |
| "<Value>TRUE</Value>" + |
| "</Node>" + |
| "</Node>" + |
| "</MgmtTree>"; |
| |
| |
| private static final String DevInfo = |
| "<MgmtTree>" + |
| "<VerDTD>1.2</VerDTD>" + |
| "<Node>" + |
| "<NodeName>DevInfo</NodeName>" + |
| "<RTProperties>" + |
| "<Type>" + |
| "<DDFName>urn:oma:mo:oma-dm-devinfo:1.0" + |
| "</DDFName>" + |
| "</Type>" + |
| "</RTProperties>" + |
| "</Node>" + |
| "<Node>" + |
| "<NodeName>DevID</NodeName>" + |
| "<Path>DevInfo</Path>" + |
| "<Value>urn:acme:00-11-22-33-44-55</Value>" + |
| "</Node>" + |
| "<Node>" + |
| "<NodeName>Man</NodeName>" + |
| "<Path>DevInfo</Path>" + |
| "<Value>ACME</Value>" + |
| "</Node>" + |
| "<Node>" + |
| "<NodeName>Mod</NodeName>" + |
| "<Path>DevInfo</Path>" + |
| "<Value>HS2.0-01</Value>" + |
| "</Node>" + |
| "<Node>" + |
| "<NodeName>DmV</NodeName>" + |
| "<Path>DevInfo</Path>" + |
| "<Value>1.2</Value>" + |
| "</Node>" + |
| "<Node>" + |
| "<NodeName>Lang</NodeName>" + |
| "<Path>DevInfo</Path>" + |
| "<Value>en-US</Value>" + |
| "</Node>" + |
| "</MgmtTree>"; |
| } |