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