package com.android.server.wifi.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.server.wifi.anqp.eap.EAP; import com.android.server.wifi.hotspot2.omadm.MOTree; import com.android.server.wifi.hotspot2.omadm.OMAConstants; import com.android.server.wifi.hotspot2.omadm.OMAConstructed; 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.server.wifi.anqp.eap.NonEAPInnerAuth.NonEAPType; import static com.android.server.wifi.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 mDevInfo; private final List mDevDetail; private static final int IMEI_Length = 14; private static final String[] ExtWiFiPath = {"DevDetail", "Ext", "org.wi-fi", "Wi-Fi"}; private static final Map 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 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(Utils.hs2LogTag(getClass()), "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(Utils.hs2LogTag(getClass()), "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 = "" + "1.2" + "" + "DevDetail" + "" + "" + "urn:oma:mo:oma-dm-devdetail:1.0" + "" + "" + "" + "Ext" + "" + "org.wi-fi" + "" + "" + "" + "urn:wfa:mo-ext:hotspot2dot0-devdetail-ext :1.0" + "" + "" + "" + "" + "Wi-Fi" + "" + "EAPMethodList" + "" + "Method01" + "" + "" + "EAPType" + "21" + "" + "" + "InnerMethod" + "MS-CHAP-V2" + "" + "" + "" + "Method02" + "" + "" + "EAPType" + "13" + "" + "" + "" + "Method03" + "" + "" + "EAPType" + "18" + "" + "" + "" + "Method04" + "" + "" + "EAPType" + "23" + "" + "" + "" + "Method05" + "" + "" + "EAPType" + "50" + "" + "" + "" + "Method06" + "" + "" + "EAPType" + "21" + "" + "" + "InnerMethod" + "PAP" + "" + "" + "" + "Method07" + "" + "" + "EAPType" + "25" + "" + "" + "InnerEAPType" + "6" + "" + "" + "" + "" + "SPCertificate" + "" + "Cert01" + "" + "CertificateIssuerName" + "CN=RuckusCA" + "" + "" + "" + "" + "ManufacturingCertificate" + "FALSE" + "" + "" + "Wi-FiMACAddress" + "001d2e112233" + "" + "" + "ClientTriggerRedirectURI" + "http://127.0.0.1:12345/index.htm" + "" + "" + "Ops" + "" + "launchBrowserToURI" + "" + "" + "" + "negotiateClientCertTLS" + "" + "" + "" + "getCertificate" + "" + "" + "" + "" + "" + "" + "" + "" + "" + "" + "URI" + "" + "MaxDepth" + "32" + "" + "" + "MaxTotLen" + "2048" + "" + "" + "MaxSegLen" + "64" + "" + "" + "" + "DevType" + "Smartphone" + "" + "" + "OEM" + "ACME" + "" + "" + "FwV" + "1.2.100.5" + "" + "" + "SwV" + "9.11.130" + "" + "" + "HwV" + "1.0" + "" + "" + "LrgObj" + "TRUE" + "" + "" + ""; private static final String DevInfo = "" + "1.2" + "" + "DevInfo" + "" + "" + "urn:oma:mo:oma-dm-devinfo:1.0" + "" + "" + "" + "" + "" + "DevID" + "DevInfo" + "urn:acme:00-11-22-33-44-55" + "" + "" + "Man" + "DevInfo" + "ACME" + "" + "" + "Mod" + "DevInfo" + "HS2.0-01" + "" + "" + "DmV" + "DevInfo" + "1.2" + "" + "" + "Lang" + "DevInfo" + "en-US" + "" + ""; }