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.os.AsyncResult;
     21 import android.os.Handler;
     22 import android.os.Message;
     23 import android.telephony.Rlog;
     24 import android.content.Intent;
     25 
     26 
     27 import com.android.internal.telephony.CommandsInterface;
     28 import com.android.internal.telephony.gsm.SimTlv;
     29 //import com.android.internal.telephony.gsm.VoiceMailConstants;
     30 
     31 import java.io.FileDescriptor;
     32 import java.io.PrintWriter;
     33 import java.nio.charset.Charset;
     34 import java.util.ArrayList;
     35 import java.util.Arrays;
     36 
     37 import static com.android.internal.telephony.uicc.IccConstants.EF_DOMAIN;
     38 import static com.android.internal.telephony.uicc.IccConstants.EF_IMPI;
     39 import static com.android.internal.telephony.uicc.IccConstants.EF_IMPU;
     40 import static com.android.internal.telephony.uicc.IccConstants.EF_IST;
     41 import static com.android.internal.telephony.uicc.IccConstants.EF_PCSCF;
     42 
     43 /**
     44  * {@hide}
     45  */
     46 public 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