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