1package com.android.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.anqp.eap.EAP;
12import com.android.hotspot2.omadm.MOTree;
13import com.android.hotspot2.omadm.OMAConstants;
14import com.android.hotspot2.omadm.OMAConstructed;
15import com.android.hotspot2.osu.OSUManager;
16
17import java.io.IOException;
18import java.nio.charset.StandardCharsets;
19import java.util.ArrayList;
20import java.util.Arrays;
21import java.util.HashMap;
22import java.util.List;
23import java.util.Locale;
24import java.util.Map;
25import java.util.concurrent.atomic.AtomicInteger;
26
27import static com.android.anqp.eap.NonEAPInnerAuth.NonEAPType;
28import static com.android.anqp.eap.NonEAPInnerAuth.mapInnerType;
29
30public 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