1/*
2 * Copyright (C) 2011 The Android Open Source Project
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 *      http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16
17package com.android.internal.telephony.uicc;
18
19import static com.android.internal.telephony.uicc.IccConstants.EF_DOMAIN;
20import static com.android.internal.telephony.uicc.IccConstants.EF_IMPI;
21import static com.android.internal.telephony.uicc.IccConstants.EF_IMPU;
22import static com.android.internal.telephony.uicc.IccConstants.EF_IST;
23import static com.android.internal.telephony.uicc.IccConstants.EF_PCSCF;
24
25import android.content.Context;
26import android.content.Intent;
27import android.os.AsyncResult;
28import android.os.Message;
29import android.telephony.Rlog;
30import android.text.TextUtils;
31
32import com.android.internal.telephony.CommandsInterface;
33import com.android.internal.telephony.gsm.SimTlv;
34//import com.android.internal.telephony.gsm.VoiceMailConstants;
35
36import java.io.FileDescriptor;
37import java.io.PrintWriter;
38import java.nio.charset.Charset;
39import java.util.ArrayList;
40import java.util.Arrays;
41
42/**
43 * {@hide}
44 */
45public class IsimUiccRecords extends IccRecords implements IsimRecords {
46    protected static final String LOG_TAG = "IsimUiccRecords";
47
48    private static final boolean DBG = true;
49    private static final boolean VDBG = false; // STOPSHIP if true
50    private static final boolean DUMP_RECORDS = false;  // Note: PII is logged when this is true
51                                                        // STOPSHIP if true
52    public static final String INTENT_ISIM_REFRESH = "com.android.intent.isim_refresh";
53
54    private static final int EVENT_APP_READY = 1;
55    private static final int EVENT_ISIM_REFRESH = 31;
56    private static final int EVENT_ISIM_AUTHENTICATE_DONE          = 91;
57
58    // ISIM EF records (see 3GPP TS 31.103)
59    private String mIsimImpi;               // IMS private user identity
60    private String mIsimDomain;             // IMS home network domain name
61    private String[] mIsimImpu;             // IMS public user identity(s)
62    private String mIsimIst;                // IMS Service Table
63    private String[] mIsimPcscf;            // IMS Proxy Call Session Control Function
64    private String auth_rsp;
65
66    private final Object mLock = new Object();
67
68    private static final int TAG_ISIM_VALUE = 0x80;     // From 3GPP TS 31.103
69
70    @Override
71    public String toString() {
72        return "IsimUiccRecords: " + super.toString()
73                + (DUMP_RECORDS ? (" mIsimImpi=" + mIsimImpi
74                + " mIsimDomain=" + mIsimDomain
75                + " mIsimImpu=" + mIsimImpu
76                + " mIsimIst=" + mIsimIst
77                + " mIsimPcscf=" + mIsimPcscf) : "");
78    }
79
80    public IsimUiccRecords(UiccCardApplication app, Context c, CommandsInterface ci) {
81        super(app, c, ci);
82
83        mRecordsRequested = false;  // No load request is made till SIM ready
84
85        // recordsToLoad is set to 0 because no requests are made yet
86        mRecordsToLoad = 0;
87        // Start off by setting empty state
88        resetRecords();
89        mCi.registerForIccRefresh(this, EVENT_ISIM_REFRESH, null);
90
91        mParentApp.registerForReady(this, EVENT_APP_READY, null);
92        if (DBG) log("IsimUiccRecords X ctor this=" + this);
93    }
94
95    @Override
96    public void dispose() {
97        log("Disposing " + this);
98        //Unregister for all events
99        mCi.unregisterForIccRefresh(this);
100        mParentApp.unregisterForReady(this);
101        resetRecords();
102        super.dispose();
103    }
104
105    // ***** Overridden from Handler
106    public void handleMessage(Message msg) {
107        AsyncResult ar;
108
109        if (mDestroyed.get()) {
110            Rlog.e(LOG_TAG, "Received message " + msg +
111                    "[" + msg.what + "] while being destroyed. Ignoring.");
112            return;
113        }
114        loge("IsimUiccRecords: handleMessage " + msg + "[" + msg.what + "] ");
115
116        try {
117            switch (msg.what) {
118                case EVENT_APP_READY:
119                    onReady();
120                    break;
121
122                case EVENT_ISIM_REFRESH:
123                    ar = (AsyncResult)msg.obj;
124                    loge("ISim REFRESH(EVENT_ISIM_REFRESH) with exception: " + ar.exception);
125                    if (ar.exception == null) {
126                        Intent intent = new Intent(INTENT_ISIM_REFRESH);
127                        loge("send ISim REFRESH: " + INTENT_ISIM_REFRESH);
128                        mContext.sendBroadcast(intent);
129                        handleIsimRefresh((IccRefreshResponse)ar.result);
130                    }
131                    break;
132
133                case EVENT_ISIM_AUTHENTICATE_DONE:
134                    ar = (AsyncResult)msg.obj;
135                    log("EVENT_ISIM_AUTHENTICATE_DONE");
136                    if (ar.exception != null) {
137                        log("Exception ISIM AKA: " + ar.exception);
138                    } else {
139                        try {
140                            auth_rsp = (String)ar.result;
141                            log("ISIM AKA: auth_rsp = " + auth_rsp);
142                        } catch (Exception e) {
143                            log("Failed to parse ISIM AKA contents: " + e);
144                        }
145                    }
146                    synchronized (mLock) {
147                        mLock.notifyAll();
148                    }
149
150                    break;
151
152                default:
153                    super.handleMessage(msg);   // IccRecords handles generic record load responses
154
155            }
156        } catch (RuntimeException exc) {
157            // I don't want these exceptions to be fatal
158            Rlog.w(LOG_TAG, "Exception parsing SIM record", exc);
159        }
160    }
161
162    protected void fetchIsimRecords() {
163        mRecordsRequested = true;
164
165        mFh.loadEFTransparent(EF_IMPI, obtainMessage(
166                IccRecords.EVENT_GET_ICC_RECORD_DONE, new EfIsimImpiLoaded()));
167        mRecordsToLoad++;
168
169        mFh.loadEFLinearFixedAll(EF_IMPU, obtainMessage(
170                IccRecords.EVENT_GET_ICC_RECORD_DONE, new EfIsimImpuLoaded()));
171        mRecordsToLoad++;
172
173        mFh.loadEFTransparent(EF_DOMAIN, obtainMessage(
174                IccRecords.EVENT_GET_ICC_RECORD_DONE, new EfIsimDomainLoaded()));
175        mRecordsToLoad++;
176        mFh.loadEFTransparent(EF_IST, obtainMessage(
177                    IccRecords.EVENT_GET_ICC_RECORD_DONE, new EfIsimIstLoaded()));
178        mRecordsToLoad++;
179        mFh.loadEFLinearFixedAll(EF_PCSCF, obtainMessage(
180                    IccRecords.EVENT_GET_ICC_RECORD_DONE, new EfIsimPcscfLoaded()));
181        mRecordsToLoad++;
182
183        if (DBG) log("fetchIsimRecords " + mRecordsToLoad + " requested: " + mRecordsRequested);
184    }
185
186    protected void resetRecords() {
187        // recordsRequested is set to false indicating that the SIM
188        // read requests made so far are not valid. This is set to
189        // true only when fresh set of read requests are made.
190        mIsimImpi = null;
191        mIsimDomain = null;
192        mIsimImpu = null;
193        mIsimIst = null;
194        mIsimPcscf = null;
195        auth_rsp = null;
196
197        mRecordsRequested = false;
198    }
199
200    private class EfIsimImpiLoaded implements IccRecords.IccRecordLoaded {
201        public String getEfName() {
202            return "EF_ISIM_IMPI";
203        }
204        public void onRecordLoaded(AsyncResult ar) {
205            byte[] data = (byte[]) ar.result;
206            mIsimImpi = isimTlvToString(data);
207            if (DUMP_RECORDS) log("EF_IMPI=" + mIsimImpi);
208        }
209    }
210
211    private class EfIsimImpuLoaded implements IccRecords.IccRecordLoaded {
212        public String getEfName() {
213            return "EF_ISIM_IMPU";
214        }
215        public void onRecordLoaded(AsyncResult ar) {
216            ArrayList<byte[]> impuList = (ArrayList<byte[]>) ar.result;
217            if (DBG) log("EF_IMPU record count: " + impuList.size());
218            mIsimImpu = new String[impuList.size()];
219            int i = 0;
220            for (byte[] identity : impuList) {
221                String impu = isimTlvToString(identity);
222                if (DUMP_RECORDS) log("EF_IMPU[" + i + "]=" + impu);
223                mIsimImpu[i++] = impu;
224            }
225        }
226    }
227
228    private class EfIsimDomainLoaded implements IccRecords.IccRecordLoaded {
229        public String getEfName() {
230            return "EF_ISIM_DOMAIN";
231        }
232        public void onRecordLoaded(AsyncResult ar) {
233            byte[] data = (byte[]) ar.result;
234            mIsimDomain = isimTlvToString(data);
235            if (DUMP_RECORDS) log("EF_DOMAIN=" + mIsimDomain);
236        }
237    }
238
239    private class EfIsimIstLoaded implements IccRecords.IccRecordLoaded {
240        public String getEfName() {
241            return "EF_ISIM_IST";
242        }
243        public void onRecordLoaded(AsyncResult ar) {
244            byte[] data = (byte[]) ar.result;
245            mIsimIst = IccUtils.bytesToHexString(data);
246            if (DUMP_RECORDS) log("EF_IST=" + mIsimIst);
247        }
248    }
249    private class EfIsimPcscfLoaded implements IccRecords.IccRecordLoaded {
250        public String getEfName() {
251            return "EF_ISIM_PCSCF";
252        }
253        public void onRecordLoaded(AsyncResult ar) {
254            ArrayList<byte[]> pcscflist = (ArrayList<byte[]>) ar.result;
255            if (DBG) log("EF_PCSCF record count: " + pcscflist.size());
256            mIsimPcscf = new String[pcscflist.size()];
257            int i = 0;
258            for (byte[] identity : pcscflist) {
259                String pcscf = isimTlvToString(identity);
260                if (DUMP_RECORDS) log("EF_PCSCF[" + i + "]=" + pcscf);
261                mIsimPcscf[i++] = pcscf;
262            }
263        }
264    }
265
266    /**
267     * ISIM records for IMS are stored inside a Tag-Length-Value record as a UTF-8 string
268     * with tag value 0x80.
269     * @param record the byte array containing the IMS data string
270     * @return the decoded String value, or null if the record can't be decoded
271     */
272    private static String isimTlvToString(byte[] record) {
273        SimTlv tlv = new SimTlv(record, 0, record.length);
274        do {
275            if (tlv.getTag() == TAG_ISIM_VALUE) {
276                return new String(tlv.getData(), Charset.forName("UTF-8"));
277            }
278        } while (tlv.nextObject());
279
280        if (VDBG) {
281            Rlog.d(LOG_TAG, "[ISIM] can't find TLV. record = " + IccUtils.bytesToHexString(record));
282        }
283        return null;
284    }
285
286    @Override
287    protected void onRecordLoaded() {
288        // One record loaded successfully or failed, In either case
289        // we need to update the recordsToLoad count
290        mRecordsToLoad -= 1;
291        if (DBG) log("onRecordLoaded " + mRecordsToLoad + " requested: " + mRecordsRequested);
292
293        if (mRecordsToLoad == 0 && mRecordsRequested == true) {
294            onAllRecordsLoaded();
295        } else if (mRecordsToLoad < 0) {
296            loge("recordsToLoad <0, programmer error suspected");
297            mRecordsToLoad = 0;
298        }
299    }
300
301    @Override
302    protected void onAllRecordsLoaded() {
303       if (DBG) log("record load complete");
304        mRecordsLoadedRegistrants.notifyRegistrants(
305                new AsyncResult(null, null, null));
306    }
307
308    private void handleFileUpdate(int efid) {
309        switch (efid) {
310            case EF_IMPI:
311                mFh.loadEFTransparent(EF_IMPI, obtainMessage(
312                            IccRecords.EVENT_GET_ICC_RECORD_DONE, new EfIsimImpiLoaded()));
313                mRecordsToLoad++;
314                break;
315
316            case EF_IMPU:
317                mFh.loadEFLinearFixedAll(EF_IMPU, obtainMessage(
318                            IccRecords.EVENT_GET_ICC_RECORD_DONE, new EfIsimImpuLoaded()));
319                mRecordsToLoad++;
320            break;
321
322            case EF_DOMAIN:
323                mFh.loadEFTransparent(EF_DOMAIN, obtainMessage(
324                            IccRecords.EVENT_GET_ICC_RECORD_DONE, new EfIsimDomainLoaded()));
325                mRecordsToLoad++;
326            break;
327
328            case EF_IST:
329                mFh.loadEFTransparent(EF_IST, obtainMessage(
330                            IccRecords.EVENT_GET_ICC_RECORD_DONE, new EfIsimIstLoaded()));
331                mRecordsToLoad++;
332            break;
333
334            case EF_PCSCF:
335                mFh.loadEFLinearFixedAll(EF_PCSCF, obtainMessage(
336                            IccRecords.EVENT_GET_ICC_RECORD_DONE, new EfIsimPcscfLoaded()));
337                mRecordsToLoad++;
338
339            default:
340                fetchIsimRecords();
341                break;
342        }
343    }
344
345    private void handleIsimRefresh(IccRefreshResponse refreshResponse) {
346        if (refreshResponse == null) {
347            if (DBG) log("handleIsimRefresh received without input");
348            return;
349        }
350
351        if (!TextUtils.isEmpty(refreshResponse.aid)
352                && !refreshResponse.aid.equals(mParentApp.getAid())) {
353            // This is for different app. Ignore.
354            if (DBG) log("handleIsimRefresh received different app");
355            return;
356        }
357
358        switch (refreshResponse.refreshResult) {
359            case IccRefreshResponse.REFRESH_RESULT_FILE_UPDATE:
360                if (DBG) log("handleIsimRefresh with REFRESH_RESULT_FILE_UPDATE");
361                handleFileUpdate(refreshResponse.efId);
362                break;
363
364            case IccRefreshResponse.REFRESH_RESULT_INIT:
365                if (DBG) log("handleIsimRefresh with REFRESH_RESULT_INIT");
366                // need to reload all files (that we care about)
367                // onIccRefreshInit();
368                fetchIsimRecords();
369                break;
370
371            case IccRefreshResponse.REFRESH_RESULT_RESET:
372                // Refresh reset is handled by the UiccCard object.
373                if (DBG) log("handleIsimRefresh with REFRESH_RESULT_RESET");
374                break;
375
376            default:
377                // unknown refresh operation
378                if (DBG) log("handleIsimRefresh with unknown operation");
379                break;
380        }
381    }
382
383    /**
384     * Return the IMS private user identity (IMPI).
385     * Returns null if the IMPI hasn't been loaded or isn't present on the ISIM.
386     * @return the IMS private user identity string, or null if not available
387     */
388    @Override
389    public String getIsimImpi() {
390        return mIsimImpi;
391    }
392
393    /**
394     * Return the IMS home network domain name.
395     * Returns null if the IMS domain hasn't been loaded or isn't present on the ISIM.
396     * @return the IMS home network domain name, or null if not available
397     */
398    @Override
399    public String getIsimDomain() {
400        return mIsimDomain;
401    }
402
403    /**
404     * Return an array of IMS public user identities (IMPU).
405     * Returns null if the IMPU hasn't been loaded or isn't present on the ISIM.
406     * @return an array of IMS public user identity strings, or null if not available
407     */
408    @Override
409    public String[] getIsimImpu() {
410        return (mIsimImpu != null) ? mIsimImpu.clone() : null;
411    }
412
413    /**
414     * Returns the IMS Service Table (IST) that was loaded from the ISIM.
415     * @return IMS Service Table or null if not present or not loaded
416     */
417    @Override
418    public String getIsimIst() {
419        return mIsimIst;
420    }
421
422    /**
423     * Returns the IMS Proxy Call Session Control Function(PCSCF) that were loaded from the ISIM.
424     * @return an array of  PCSCF strings with one PCSCF per string, or null if
425     *      not present or not loaded
426     */
427    @Override
428    public String[] getIsimPcscf() {
429        return (mIsimPcscf != null) ? mIsimPcscf.clone() : null;
430    }
431
432    /**
433     * Returns the response of ISIM Authetification through RIL.
434     * Returns null if the Authentification hasn't been successed or isn't present iphonesubinfo.
435     * @return the response of ISIM Authetification, or null if not available
436     */
437    @Override
438    public String getIsimChallengeResponse(String nonce){
439        if (DBG) log("getIsimChallengeResponse-nonce:"+nonce);
440        try {
441            synchronized(mLock) {
442                mCi.requestIsimAuthentication(nonce,obtainMessage(EVENT_ISIM_AUTHENTICATE_DONE));
443                try {
444                    mLock.wait();
445                } catch (InterruptedException e) {
446                    log("interrupted while trying to request Isim Auth");
447                }
448            }
449        } catch(Exception e) {
450            if (DBG) log( "Fail while trying to request Isim Auth");
451            return null;
452        }
453
454        if (DBG) log("getIsimChallengeResponse-auth_rsp"+auth_rsp);
455
456        return auth_rsp;
457    }
458
459    @Override
460    public int getDisplayRule(String plmn) {
461        // Not applicable to Isim
462        return 0;
463    }
464
465    @Override
466    public void onReady() {
467        fetchIsimRecords();
468    }
469
470    @Override
471    public void onRefresh(boolean fileChanged, int[] fileList) {
472        if (fileChanged) {
473            // A future optimization would be to inspect fileList and
474            // only reload those files that we care about.  For now,
475            // just re-fetch all SIM records that we cache.
476            fetchIsimRecords();
477        }
478    }
479
480    @Override
481    public void setVoiceMailNumber(String alphaTag, String voiceNumber,
482            Message onComplete) {
483        // Not applicable to Isim
484    }
485
486    @Override
487    public void setVoiceMessageWaiting(int line, int countWaiting) {
488        // Not applicable to Isim
489    }
490
491    @Override
492    protected void log(String s) {
493        if (DBG) Rlog.d(LOG_TAG, "[ISIM] " + s);
494    }
495
496    @Override
497    protected void loge(String s) {
498        if (DBG) Rlog.e(LOG_TAG, "[ISIM] " + s);
499    }
500
501    @Override
502    public void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
503        pw.println("IsimRecords: " + this);
504        pw.println(" extends:");
505        super.dump(fd, pw, args);
506        if (DUMP_RECORDS) {
507            pw.println(" mIsimImpi=" + mIsimImpi);
508            pw.println(" mIsimDomain=" + mIsimDomain);
509            pw.println(" mIsimImpu[]=" + Arrays.toString(mIsimImpu));
510            pw.println(" mIsimIst" + mIsimIst);
511            pw.println(" mIsimPcscf" + mIsimPcscf);
512        }
513        pw.flush();
514    }
515
516    @Override
517    public int getVoiceMessageCount() {
518        return 0; // Not applicable to Isim
519    }
520
521}
522