1 /*
2  * Copyright (C) 2008 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 static com.android.internal.telephony.TelephonyProperties.PROPERTY_TEST_CSIM;
20 
21 import android.content.Context;
22 import android.content.res.Resources;
23 import android.os.AsyncResult;
24 import android.os.Message;
25 import android.os.SystemProperties;
26 import android.telephony.Rlog;
27 import android.telephony.ServiceState;
28 import android.telephony.SubscriptionInfo;
29 import android.telephony.SubscriptionManager;
30 import android.text.TextUtils;
31 import android.util.Log;
32 
33 import com.android.internal.telephony.CommandsInterface;
34 import com.android.internal.telephony.GsmAlphabet;
35 import com.android.internal.telephony.MccTable;
36 import com.android.internal.telephony.SubscriptionController;
37 import com.android.internal.telephony.cdma.sms.UserData;
38 import com.android.internal.telephony.uicc.IccCardApplicationStatus.AppType;
39 import com.android.internal.util.BitwiseInputStream;
40 
41 import java.io.FileDescriptor;
42 import java.io.PrintWriter;
43 import java.util.ArrayList;
44 import java.util.Arrays;
45 import java.util.Locale;
46 
47 /**
48  * {@hide}
49  */
50 public class RuimRecords extends IccRecords {
51     static final String LOG_TAG = "RuimRecords";
52 
53     private boolean  mOtaCommited=false;
54 
55     // ***** Instance Variables
56 
57     private String mMyMobileNumber;
58     private String mMin2Min1;
59 
60     private String mPrlVersion;
61     // From CSIM application
62     private byte[] mEFpl = null;
63     private byte[] mEFli = null;
64     boolean mCsimSpnDisplayCondition = false;
65     private String mMdn;
66     private String mMin;
67     private String mHomeSystemId;
68     private String mHomeNetworkId;
69     private String mNai;
70 
71     @Override
toString()72     public String toString() {
73         return "RuimRecords: " + super.toString()
74                 + " m_ota_commited" + mOtaCommited
75                 + " mMyMobileNumber=" + "xxxx"
76                 + " mMin2Min1=" + mMin2Min1
77                 + " mPrlVersion=" + mPrlVersion
78                 + " mEFpl=" + mEFpl
79                 + " mEFli=" + mEFli
80                 + " mCsimSpnDisplayCondition=" + mCsimSpnDisplayCondition
81                 + " mMdn=" + mMdn
82                 + " mMin=" + mMin
83                 + " mHomeSystemId=" + mHomeSystemId
84                 + " mHomeNetworkId=" + mHomeNetworkId;
85     }
86 
87     // ***** Event Constants
88     private static final int EVENT_GET_IMSI_DONE = 3;
89     private static final int EVENT_GET_DEVICE_IDENTITY_DONE = 4;
90     private static final int EVENT_GET_ICCID_DONE = 5;
91     private static final int EVENT_GET_CDMA_SUBSCRIPTION_DONE = 10;
92     private static final int EVENT_UPDATE_DONE = 14;
93     private static final int EVENT_GET_SST_DONE = 17;
94     private static final int EVENT_GET_ALL_SMS_DONE = 18;
95     private static final int EVENT_MARK_SMS_READ_DONE = 19;
96 
97     private static final int EVENT_SMS_ON_RUIM = 21;
98     private static final int EVENT_GET_SMS_DONE = 22;
99 
100     private static final int EVENT_APP_LOCKED = 32;
101     private static final int EVENT_APP_NETWORK_LOCKED = 33;
102 
RuimRecords(UiccCardApplication app, Context c, CommandsInterface ci)103     public RuimRecords(UiccCardApplication app, Context c, CommandsInterface ci) {
104         super(app, c, ci);
105 
106         mAdnCache = new AdnRecordCache(mFh);
107 
108         mRecordsRequested = false;  // No load request is made till SIM ready
109         mLockedRecordsReqReason = LOCKED_RECORDS_REQ_REASON_NONE;
110 
111         // recordsToLoad is set to 0 because no requests are made yet
112         mRecordsToLoad = 0;
113 
114         // NOTE the EVENT_SMS_ON_RUIM is not registered
115 
116         // Start off by setting empty state
117         resetRecords();
118 
119         mParentApp.registerForReady(this, EVENT_APP_READY, null);
120         mParentApp.registerForLocked(this, EVENT_APP_LOCKED, null);
121         mParentApp.registerForNetworkLocked(this, EVENT_APP_NETWORK_LOCKED, null);
122         if (DBG) log("RuimRecords X ctor this=" + this);
123     }
124 
125     @Override
dispose()126     public void dispose() {
127         if (DBG) log("Disposing RuimRecords " + this);
128         //Unregister for all events
129         mParentApp.unregisterForReady(this);
130         mParentApp.unregisterForLocked(this);
131         mParentApp.unregisterForNetworkLocked(this);
132         resetRecords();
133         super.dispose();
134     }
135 
136     @Override
finalize()137     protected void finalize() {
138         if(DBG) log("RuimRecords finalized");
139     }
140 
resetRecords()141     protected void resetRecords() {
142         mMncLength = UNINITIALIZED;
143         log("setting0 mMncLength" + mMncLength);
144         mIccId = null;
145         mFullIccId = null;
146 
147         mAdnCache.reset();
148 
149         // Don't clean up PROPERTY_ICC_OPERATOR_ISO_COUNTRY and
150         // PROPERTY_ICC_OPERATOR_NUMERIC here. Since not all CDMA
151         // devices have RUIM, these properties should keep the original
152         // values, e.g. build time settings, when there is no RUIM but
153         // set new values when RUIM is available and loaded.
154 
155         // recordsRequested is set to false indicating that the SIM
156         // read requests made so far are not valid. This is set to
157         // true only when fresh set of read requests are made.
158         mRecordsRequested = false;
159         mLockedRecordsReqReason = LOCKED_RECORDS_REQ_REASON_NONE;
160         mLoaded.set(false);
161     }
162 
getMdnNumber()163     public String getMdnNumber() {
164         return mMyMobileNumber;
165     }
166 
getCdmaMin()167     public String getCdmaMin() {
168          return mMin2Min1;
169     }
170 
171     /** Returns null if RUIM is not yet ready */
getPrlVersion()172     public String getPrlVersion() {
173         return mPrlVersion;
174     }
175 
176     @Override
177     /** Returns null if RUIM is not yet ready */
getNAI()178     public String getNAI() {
179         return mNai;
180     }
181 
182     @Override
setVoiceMailNumber(String alphaTag, String voiceNumber, Message onComplete)183     public void setVoiceMailNumber(String alphaTag, String voiceNumber, Message onComplete){
184         // In CDMA this is Operator/OEM dependent
185         AsyncResult.forMessage((onComplete)).exception =
186                 new IccException("setVoiceMailNumber not implemented");
187         onComplete.sendToTarget();
188         loge("method setVoiceMailNumber is not implemented");
189     }
190 
191     /**
192      * Called by CCAT Service when REFRESH is received.
193      * @param fileChanged indicates whether any files changed
194      * @param fileList if non-null, a list of EF files that changed
195      */
196     @Override
onRefresh(boolean fileChanged, int[] fileList)197     public void onRefresh(boolean fileChanged, int[] fileList) {
198         if (fileChanged) {
199             // A future optimization would be to inspect fileList and
200             // only reload those files that we care about.  For now,
201             // just re-fetch all RUIM records that we cache.
202             fetchRuimRecords();
203         }
204     }
205 
adjstMinDigits(int digits)206     private int adjstMinDigits (int digits) {
207         // Per C.S0005 section 2.3.1.
208         digits += 111;
209         digits = (digits % 10 == 0)?(digits - 10):digits;
210         digits = ((digits / 10) % 10 == 0)?(digits - 100):digits;
211         digits = ((digits / 100) % 10 == 0)?(digits - 1000):digits;
212         return digits;
213     }
214 
215     /**
216      * Returns the 5 or 6 digit MCC/MNC of the operator that
217      *  provided the RUIM card. Returns null of RUIM is not yet ready
218      */
getRUIMOperatorNumeric()219     public String getRUIMOperatorNumeric() {
220         String imsi = getIMSI();
221 
222         if (imsi == null) {
223             return null;
224         }
225 
226         if (mMncLength != UNINITIALIZED && mMncLength != UNKNOWN) {
227             // Length = length of MCC + length of MNC
228             // length of mcc = 3 (3GPP2 C.S0005 - Section 2.3)
229             return imsi.substring(0, 3 + mMncLength);
230         }
231 
232         // Guess the MNC length based on the MCC if we don't
233         // have a valid value in ef[ad]
234 
235         int mcc = Integer.parseInt(imsi.substring(0, 3));
236         return imsi.substring(0, 3 + MccTable.smallestDigitsMccForMnc(mcc));
237     }
238 
239     // Refer to ETSI TS 102.221
240     private class EfPlLoaded implements IccRecordLoaded {
241         @Override
getEfName()242         public String getEfName() {
243             return "EF_PL";
244         }
245 
246         @Override
onRecordLoaded(AsyncResult ar)247         public void onRecordLoaded(AsyncResult ar) {
248             mEFpl = (byte[]) ar.result;
249             if (DBG) log("EF_PL=" + IccUtils.bytesToHexString(mEFpl));
250         }
251     }
252 
253     // Refer to C.S0065 5.2.26
254     private class EfCsimLiLoaded implements IccRecordLoaded {
255         @Override
getEfName()256         public String getEfName() {
257             return "EF_CSIM_LI";
258         }
259 
260         @Override
onRecordLoaded(AsyncResult ar)261         public void onRecordLoaded(AsyncResult ar) {
262             mEFli = (byte[]) ar.result;
263             // convert csim efli data to iso 639 format
264             for (int i = 0; i < mEFli.length; i+=2) {
265                 switch(mEFli[i+1]) {
266                 case 0x01: mEFli[i] = 'e'; mEFli[i+1] = 'n';break;
267                 case 0x02: mEFli[i] = 'f'; mEFli[i+1] = 'r';break;
268                 case 0x03: mEFli[i] = 'e'; mEFli[i+1] = 's';break;
269                 case 0x04: mEFli[i] = 'j'; mEFli[i+1] = 'a';break;
270                 case 0x05: mEFli[i] = 'k'; mEFli[i+1] = 'o';break;
271                 case 0x06: mEFli[i] = 'z'; mEFli[i+1] = 'h';break;
272                 case 0x07: mEFli[i] = 'h'; mEFli[i+1] = 'e';break;
273                 default: mEFli[i] = ' '; mEFli[i+1] = ' ';
274                 }
275             }
276 
277             if (DBG) log("EF_LI=" + IccUtils.bytesToHexString(mEFli));
278         }
279     }
280 
281     // Refer to C.S0065 5.2.32
282     private class EfCsimSpnLoaded implements IccRecordLoaded {
283         @Override
getEfName()284         public String getEfName() {
285             return "EF_CSIM_SPN";
286         }
287 
288         @Override
onRecordLoaded(AsyncResult ar)289         public void onRecordLoaded(AsyncResult ar) {
290             byte[] data = (byte[]) ar.result;
291             if (DBG) log("CSIM_SPN=" +
292                          IccUtils.bytesToHexString(data));
293 
294             // C.S0065 for EF_SPN decoding
295             mCsimSpnDisplayCondition = ((0x01 & data[0]) != 0);
296 
297             int encoding = data[1];
298             int language = data[2];
299             byte[] spnData = new byte[32];
300             int len = ((data.length - 3) < 32) ? (data.length - 3) : 32;
301             System.arraycopy(data, 3, spnData, 0, len);
302 
303             int numBytes;
304             for (numBytes = 0; numBytes < spnData.length; numBytes++) {
305                 if ((spnData[numBytes] & 0xFF) == 0xFF) break;
306             }
307 
308             if (numBytes == 0) {
309                 setServiceProviderName("");
310                 return;
311             }
312             try {
313                 switch (encoding) {
314                 case UserData.ENCODING_OCTET:
315                 case UserData.ENCODING_LATIN:
316                     setServiceProviderName(new String(spnData, 0, numBytes, "ISO-8859-1"));
317                     break;
318                 case UserData.ENCODING_IA5:
319                 case UserData.ENCODING_GSM_7BIT_ALPHABET:
320                     setServiceProviderName(
321                             GsmAlphabet.gsm7BitPackedToString(spnData, 0, (numBytes*8)/7));
322                     break;
323                 case UserData.ENCODING_7BIT_ASCII:
324                     String spn = new String(spnData, 0, numBytes, "US-ASCII");
325                     // To address issues with incorrect encoding scheme
326                     // programmed in some commercial CSIM cards, the decoded
327                     // SPN is checked to have characters in printable ASCII
328                     // range. If not, they are decoded with
329                     // ENCODING_GSM_7BIT_ALPHABET scheme.
330                     if (TextUtils.isPrintableAsciiOnly(spn)) {
331                         setServiceProviderName(spn);
332                     } else {
333                         if (DBG) log("Some corruption in SPN decoding = " + spn);
334                         if (DBG) log("Using ENCODING_GSM_7BIT_ALPHABET scheme...");
335                         setServiceProviderName(
336                                 GsmAlphabet.gsm7BitPackedToString(spnData, 0, (numBytes * 8) / 7));
337                     }
338                 break;
339                 case UserData.ENCODING_UNICODE_16:
340                     setServiceProviderName(new String(spnData, 0, numBytes, "utf-16"));
341                     break;
342                 default:
343                     log("SPN encoding not supported");
344                 }
345             } catch(Exception e) {
346                 log("spn decode error: " + e);
347             }
348             if (DBG) log("spn=" + getServiceProviderName());
349             if (DBG) log("spnCondition=" + mCsimSpnDisplayCondition);
350             mTelephonyManager.setSimOperatorNameForPhone(
351                     mParentApp.getPhoneId(), getServiceProviderName());
352         }
353     }
354 
355     private class EfCsimMdnLoaded implements IccRecordLoaded {
356         @Override
getEfName()357         public String getEfName() {
358             return "EF_CSIM_MDN";
359         }
360 
361         @Override
onRecordLoaded(AsyncResult ar)362         public void onRecordLoaded(AsyncResult ar) {
363             byte[] data = (byte[]) ar.result;
364             if (DBG) log("CSIM_MDN=" + IccUtils.bytesToHexString(data));
365             // Refer to C.S0065 5.2.35
366             int mdnDigitsNum = 0x0F & data[0];
367             mMdn = IccUtils.cdmaBcdToString(data, 1, mdnDigitsNum);
368             if (DBG) log("CSIM MDN=" + mMdn);
369         }
370     }
371 
372     private class EfCsimImsimLoaded implements IccRecordLoaded {
373         @Override
getEfName()374         public String getEfName() {
375             return "EF_CSIM_IMSIM";
376         }
377 
378         @Override
onRecordLoaded(AsyncResult ar)379         public void onRecordLoaded(AsyncResult ar) {
380             byte[] data = (byte[]) ar.result;
381             if (VDBG) log("CSIM_IMSIM=" + IccUtils.bytesToHexString(data));
382             // C.S0065 section 5.2.2 for IMSI_M encoding
383             // C.S0005 section 2.3.1 for MIN encoding in IMSI_M.
384             boolean provisioned = ((data[7] & 0x80) == 0x80);
385 
386             if (provisioned) {
387                 int first3digits = ((0x03 & data[2]) << 8) + (0xFF & data[1]);
388                 int second3digits = (((0xFF & data[5]) << 8) | (0xFF & data[4])) >> 6;
389                 int digit7 = 0x0F & (data[4] >> 2);
390                 if (digit7 > 0x09) digit7 = 0;
391                 int last3digits = ((0x03 & data[4]) << 8) | (0xFF & data[3]);
392                 first3digits = adjstMinDigits(first3digits);
393                 second3digits = adjstMinDigits(second3digits);
394                 last3digits = adjstMinDigits(last3digits);
395 
396                 StringBuilder builder = new StringBuilder();
397                 builder.append(String.format(Locale.US, "%03d", first3digits));
398                 builder.append(String.format(Locale.US, "%03d", second3digits));
399                 builder.append(String.format(Locale.US, "%d", digit7));
400                 builder.append(String.format(Locale.US, "%03d", last3digits));
401                 mMin = builder.toString();
402                 if (DBG) log("min present=" + mMin);
403             } else {
404                 if (DBG) log("min not present");
405             }
406         }
407     }
408 
409     private class EfCsimCdmaHomeLoaded implements IccRecordLoaded {
410         @Override
getEfName()411         public String getEfName() {
412             return "EF_CSIM_CDMAHOME";
413         }
414 
415         @Override
onRecordLoaded(AsyncResult ar)416         public void onRecordLoaded(AsyncResult ar) {
417             // Per C.S0065 section 5.2.8
418             ArrayList<byte[]> dataList = (ArrayList<byte[]>) ar.result;
419             if (DBG) log("CSIM_CDMAHOME data size=" + dataList.size());
420             if (dataList.isEmpty()) {
421                 return;
422             }
423             StringBuilder sidBuf = new StringBuilder();
424             StringBuilder nidBuf = new StringBuilder();
425 
426             for (byte[] data : dataList) {
427                 if (data.length == 5) {
428                     int sid = ((data[1] & 0xFF) << 8) | (data[0] & 0xFF);
429                     int nid = ((data[3] & 0xFF) << 8) | (data[2] & 0xFF);
430                     sidBuf.append(sid).append(',');
431                     nidBuf.append(nid).append(',');
432                 }
433             }
434             // remove trailing ","
435             sidBuf.setLength(sidBuf.length()-1);
436             nidBuf.setLength(nidBuf.length()-1);
437 
438             mHomeSystemId = sidBuf.toString();
439             mHomeNetworkId = nidBuf.toString();
440         }
441     }
442 
443     private class EfCsimEprlLoaded implements IccRecordLoaded {
444         @Override
getEfName()445         public String getEfName() {
446             return "EF_CSIM_EPRL";
447         }
448         @Override
onRecordLoaded(AsyncResult ar)449         public void onRecordLoaded(AsyncResult ar) {
450             onGetCSimEprlDone(ar);
451         }
452     }
453 
onGetCSimEprlDone(AsyncResult ar)454     private void onGetCSimEprlDone(AsyncResult ar) {
455         // C.S0065 section 5.2.57 for EFeprl encoding
456         // C.S0016 section 3.5.5 for PRL format.
457         byte[] data = (byte[]) ar.result;
458         if (DBG) log("CSIM_EPRL=" + IccUtils.bytesToHexString(data));
459 
460         // Only need the first 4 bytes of record
461         if (data.length > 3) {
462             int prlId = ((data[2] & 0xFF) << 8) | (data[3] & 0xFF);
463             mPrlVersion = Integer.toString(prlId);
464         }
465         if (DBG) log("CSIM PRL version=" + mPrlVersion);
466     }
467 
468     private class EfCsimMipUppLoaded implements IccRecordLoaded {
469         @Override
getEfName()470         public String getEfName() {
471             return "EF_CSIM_MIPUPP";
472         }
473 
checkLengthLegal(int length, int expectLength)474         boolean checkLengthLegal(int length, int expectLength) {
475             if(length < expectLength) {
476                 Log.e(LOG_TAG, "CSIM MIPUPP format error, length = " + length  +
477                         "expected length at least =" + expectLength);
478                 return false;
479             } else {
480                 return true;
481             }
482         }
483 
484         @Override
onRecordLoaded(AsyncResult ar)485         public void onRecordLoaded(AsyncResult ar) {
486             // 3GPP2 C.S0065 section 5.2.24
487             byte[] data = (byte[]) ar.result;
488 
489             if(data.length < 1) {
490                 Log.e(LOG_TAG,"MIPUPP read error");
491                 return;
492             }
493 
494             BitwiseInputStream bitStream = new BitwiseInputStream(data);
495             try {
496                 int  mipUppLength = bitStream.read(8);
497                 //transfer length from byte to bit
498                 mipUppLength = (mipUppLength << 3);
499 
500                 if (!checkLengthLegal(mipUppLength, 1)) {
501                     return;
502                 }
503                 //parse the MIPUPP body 3GPP2 C.S0016-C 3.5.8.6
504                 int retryInfoInclude = bitStream.read(1);
505                 mipUppLength--;
506 
507                 if(retryInfoInclude == 1) {
508                     if (!checkLengthLegal(mipUppLength, 11)) {
509                         return;
510                     }
511                     bitStream.skip(11); //not used now
512                     //transfer length from byte to bit
513                     mipUppLength -= 11;
514                 }
515 
516                 if (!checkLengthLegal(mipUppLength, 4)) {
517                     return;
518                 }
519                 int numNai = bitStream.read(4);
520                 mipUppLength -= 4;
521 
522                 //start parse NAI body
523                 for(int index = 0; index < numNai; index++) {
524                     if (!checkLengthLegal(mipUppLength, 4)) {
525                         return;
526                     }
527                     int naiEntryIndex = bitStream.read(4);
528                     mipUppLength -= 4;
529 
530                     if (!checkLengthLegal(mipUppLength, 8)) {
531                         return;
532                     }
533                     int naiLength = bitStream.read(8);
534                     mipUppLength -= 8;
535 
536                     if(naiEntryIndex == 0) {
537                         //we find the one!
538                         if (!checkLengthLegal(mipUppLength, naiLength << 3)) {
539                             return;
540                         }
541                         char naiCharArray[] = new char[naiLength];
542                         for(int index1 = 0; index1 < naiLength; index1++) {
543                             naiCharArray[index1] = (char)(bitStream.read(8) & 0xFF);
544                         }
545                         mNai =  new String(naiCharArray);
546                         if (Log.isLoggable(LOG_TAG, Log.VERBOSE)) {
547                             Log.v(LOG_TAG,"MIPUPP Nai = " + mNai);
548                         }
549                         return; //need not parsing further
550                     } else {
551                         //ignore this NAI body
552                         if (!checkLengthLegal(mipUppLength, (naiLength << 3) + 102)) {
553                             return;
554                         }
555                         bitStream.skip((naiLength << 3) + 101);//not used
556                         int mnAaaSpiIndicator = bitStream.read(1);
557                         mipUppLength -= ((naiLength << 3) + 102);
558 
559                         if(mnAaaSpiIndicator == 1) {
560                             if (!checkLengthLegal(mipUppLength, 32)) {
561                                 return;
562                             }
563                             bitStream.skip(32); //not used
564                             mipUppLength -= 32;
565                         }
566 
567                         //MN-HA_AUTH_ALGORITHM
568                         if (!checkLengthLegal(mipUppLength, 5)) {
569                             return;
570                         }
571                         bitStream.skip(4);
572                         mipUppLength -= 4;
573                         int mnHaSpiIndicator = bitStream.read(1);
574                         mipUppLength--;
575 
576                         if(mnHaSpiIndicator == 1) {
577                             if (!checkLengthLegal(mipUppLength, 32)) {
578                                 return;
579                             }
580                             bitStream.skip(32);
581                             mipUppLength -= 32;
582                         }
583                     }
584                 }
585             } catch(Exception e) {
586               Log.e(LOG_TAG,"MIPUPP read Exception error!");
587                 return;
588             }
589         }
590     }
591 
592     @Override
handleMessage(Message msg)593     public void handleMessage(Message msg) {
594         AsyncResult ar;
595 
596         byte data[];
597 
598         boolean isRecordLoadResponse = false;
599 
600         if (mDestroyed.get()) {
601             loge("Received message " + msg +
602                     "[" + msg.what + "] while being destroyed. Ignoring.");
603             return;
604         }
605 
606         try {
607             switch (msg.what) {
608             case EVENT_APP_READY:
609                 onReady();
610                 break;
611 
612                 case EVENT_APP_LOCKED:
613                 case EVENT_APP_NETWORK_LOCKED:
614                     onLocked(msg.what);
615                     break;
616 
617             case EVENT_GET_DEVICE_IDENTITY_DONE:
618                 log("Event EVENT_GET_DEVICE_IDENTITY_DONE Received");
619             break;
620 
621             /* IO events */
622             case EVENT_GET_IMSI_DONE:
623                 isRecordLoadResponse = true;
624 
625                 ar = (AsyncResult)msg.obj;
626                 if (ar.exception != null) {
627                     loge("Exception querying IMSI, Exception:" + ar.exception);
628                     break;
629                 }
630 
631                 mImsi = (String) ar.result;
632 
633                 // IMSI (MCC+MNC+MSIN) is at least 6 digits, but not more
634                 // than 15 (and usually 15).
635                 if (mImsi != null && (mImsi.length() < 6 || mImsi.length() > 15)) {
636                     loge("invalid IMSI " + mImsi);
637                     mImsi = null;
638                 }
639 
640                 // FIXME: CSIM IMSI may not contain the MNC.
641                 if (false) {
642                     log("IMSI: " + mImsi.substring(0, 6) + "xxxxxxxxx");
643 
644                     String operatorNumeric = getRUIMOperatorNumeric();
645                     if (operatorNumeric != null) {
646                         if (operatorNumeric.length() <= 6) {
647                             log("update mccmnc=" + operatorNumeric);
648                             MccTable.updateMccMncConfiguration(mContext, operatorNumeric, false);
649                         }
650                     }
651                 } else {
652                     String operatorNumeric = getRUIMOperatorNumeric();
653                     log("NO update mccmnc=" + operatorNumeric);
654                 }
655 
656             break;
657 
658             case EVENT_GET_CDMA_SUBSCRIPTION_DONE:
659                 ar = (AsyncResult)msg.obj;
660                 String localTemp[] = (String[])ar.result;
661                 if (ar.exception != null) {
662                     break;
663                 }
664 
665                 mMyMobileNumber = localTemp[0];
666                 mMin2Min1 = localTemp[3];
667                 mPrlVersion = localTemp[4];
668 
669                 log("MDN: " + mMyMobileNumber + " MIN: " + mMin2Min1);
670 
671             break;
672 
673             case EVENT_GET_ICCID_DONE:
674                 isRecordLoadResponse = true;
675 
676                 ar = (AsyncResult)msg.obj;
677                 data = (byte[])ar.result;
678 
679                 if (ar.exception != null) {
680                     break;
681                 }
682 
683                 mIccId = IccUtils.bcdToString(data, 0, data.length);
684                 mFullIccId = IccUtils.bchToString(data, 0, data.length);
685 
686                 log("iccid: " + SubscriptionInfo.givePrintableIccid(mFullIccId));
687 
688             break;
689 
690             case EVENT_UPDATE_DONE:
691                 ar = (AsyncResult)msg.obj;
692                 if (ar.exception != null) {
693                     Rlog.i(LOG_TAG, "RuimRecords update failed", ar.exception);
694                 }
695             break;
696 
697             case EVENT_GET_ALL_SMS_DONE:
698             case EVENT_MARK_SMS_READ_DONE:
699             case EVENT_SMS_ON_RUIM:
700             case EVENT_GET_SMS_DONE:
701                 Rlog.w(LOG_TAG, "Event not supported: " + msg.what);
702                 break;
703 
704             // TODO: probably EF_CST should be read instead
705             case EVENT_GET_SST_DONE:
706                 log("Event EVENT_GET_SST_DONE Received");
707             break;
708 
709             default:
710                 super.handleMessage(msg);   // IccRecords handles generic record load responses
711 
712         }}catch (RuntimeException exc) {
713             // I don't want these exceptions to be fatal
714             Rlog.w(LOG_TAG, "Exception parsing RUIM record", exc);
715         } finally {
716             // Count up record load responses even if they are fails
717             if (isRecordLoadResponse) {
718                 onRecordLoaded();
719             }
720         }
721     }
722 
723     /**
724      * Returns an array of languages we have assets for.
725      *
726      * NOTE: This array will have duplicates. If this method will be caused
727      * frequently or in a tight loop, it can be rewritten for efficiency.
728      */
getAssetLanguages(Context ctx)729     private static String[] getAssetLanguages(Context ctx) {
730         final String[] locales = ctx.getAssets().getLocales();
731         final String[] localeLangs = new String[locales.length];
732         for (int i = 0; i < locales.length; ++i) {
733             final String localeStr = locales[i];
734             final int separator = localeStr.indexOf('-');
735             if (separator < 0) {
736                 localeLangs[i] = localeStr;
737             } else {
738                 localeLangs[i] = localeStr.substring(0, separator);
739             }
740         }
741 
742         return localeLangs;
743     }
744 
745     @Override
onRecordLoaded()746     protected void onRecordLoaded() {
747         // One record loaded successfully or failed, In either case
748         // we need to update the recordsToLoad count
749         mRecordsToLoad -= 1;
750         if (DBG) log("onRecordLoaded " + mRecordsToLoad + " requested: " + mRecordsRequested);
751 
752         if (getRecordsLoaded()) {
753             onAllRecordsLoaded();
754         } else if (getLockedRecordsLoaded() || getNetworkLockedRecordsLoaded()) {
755             onLockedAllRecordsLoaded();
756         } else if (mRecordsToLoad < 0) {
757             loge("recordsToLoad <0, programmer error suspected");
758             mRecordsToLoad = 0;
759         }
760     }
761 
onLockedAllRecordsLoaded()762     private void onLockedAllRecordsLoaded() {
763         if (mLockedRecordsReqReason == LOCKED_RECORDS_REQ_REASON_LOCKED) {
764             mLockedRecordsLoadedRegistrants.notifyRegistrants(new AsyncResult(null, null, null));
765         } else if (mLockedRecordsReqReason == LOCKED_RECORDS_REQ_REASON_NETWORK_LOCKED) {
766             mNetworkLockedRecordsLoadedRegistrants.notifyRegistrants(
767                     new AsyncResult(null, null, null));
768         } else {
769             loge("onLockedAllRecordsLoaded: unexpected mLockedRecordsReqReason "
770                     + mLockedRecordsReqReason);
771         }
772     }
773 
774     @Override
onAllRecordsLoaded()775     protected void onAllRecordsLoaded() {
776         if (DBG) log("record load complete");
777 
778         // Further records that can be inserted are Operator/OEM dependent
779 
780         // FIXME: CSIM IMSI may not contain the MNC.
781         if (false) {
782             String operator = getRUIMOperatorNumeric();
783             if (!TextUtils.isEmpty(operator)) {
784                 log("onAllRecordsLoaded set 'gsm.sim.operator.numeric' to operator='" +
785                         operator + "'");
786                 log("update icc_operator_numeric=" + operator);
787                 mTelephonyManager.setSimOperatorNumericForPhone(
788                         mParentApp.getPhoneId(), operator);
789             } else {
790                 log("onAllRecordsLoaded empty 'gsm.sim.operator.numeric' skipping");
791             }
792 
793             String imsi = getIMSI();
794 
795             if (!TextUtils.isEmpty(imsi)) {
796                 log("onAllRecordsLoaded set mcc imsi=" + (VDBG ? ("=" + imsi) : ""));
797                 mTelephonyManager.setSimCountryIsoForPhone(
798                         mParentApp.getPhoneId(),
799                         MccTable.countryCodeForMcc(
800                         Integer.parseInt(imsi.substring(0, 3))));
801             } else {
802                 log("onAllRecordsLoaded empty imsi skipping setting mcc");
803             }
804         }
805 
806         Resources resource = Resources.getSystem();
807         if (resource.getBoolean(com.android.internal.R.bool.config_use_sim_language_file)) {
808             setSimLanguage(mEFli, mEFpl);
809         }
810 
811         mLoaded.set(true);
812         mRecordsLoadedRegistrants.notifyRegistrants(new AsyncResult(null, null, null));
813 
814         // TODO: The below is hacky since the SubscriptionController may not be ready at this time.
815         if (!TextUtils.isEmpty(mMdn)) {
816             int phoneId = mParentApp.getUiccProfile().getPhoneId();
817             int subId = SubscriptionController.getInstance().getSubIdUsingPhoneId(phoneId);
818             if (SubscriptionManager.isValidSubscriptionId(subId)) {
819                 SubscriptionManager.from(mContext).setDisplayNumber(mMdn, subId);
820             } else {
821                 log("Cannot call setDisplayNumber: invalid subId");
822             }
823         }
824     }
825 
826     @Override
onReady()827     public void onReady() {
828         fetchRuimRecords();
829 
830         mCi.getCDMASubscription(obtainMessage(EVENT_GET_CDMA_SUBSCRIPTION_DONE));
831     }
832 
onLocked(int msg)833     private void onLocked(int msg) {
834         if (DBG) log("only fetch EF_ICCID in locked state");
835         mLockedRecordsReqReason = msg == EVENT_APP_LOCKED ? LOCKED_RECORDS_REQ_REASON_LOCKED :
836                 LOCKED_RECORDS_REQ_REASON_NETWORK_LOCKED;
837 
838         mFh.loadEFTransparent(EF_ICCID, obtainMessage(EVENT_GET_ICCID_DONE));
839         mRecordsToLoad++;
840     }
841 
fetchRuimRecords()842     private void fetchRuimRecords() {
843         mRecordsRequested = true;
844 
845         if (DBG) log("fetchRuimRecords " + mRecordsToLoad);
846 
847         mCi.getIMSIForApp(mParentApp.getAid(), obtainMessage(EVENT_GET_IMSI_DONE));
848         mRecordsToLoad++;
849 
850         mFh.loadEFTransparent(EF_ICCID,
851                 obtainMessage(EVENT_GET_ICCID_DONE));
852         mRecordsToLoad++;
853 
854         mFh.loadEFTransparent(EF_PL,
855                 obtainMessage(EVENT_GET_ICC_RECORD_DONE, new EfPlLoaded()));
856         mRecordsToLoad++;
857 
858         mFh.loadEFTransparent(EF_CSIM_LI,
859                 obtainMessage(EVENT_GET_ICC_RECORD_DONE, new EfCsimLiLoaded()));
860         mRecordsToLoad++;
861 
862         mFh.loadEFTransparent(EF_CSIM_SPN,
863                 obtainMessage(EVENT_GET_ICC_RECORD_DONE, new EfCsimSpnLoaded()));
864         mRecordsToLoad++;
865 
866         mFh.loadEFLinearFixed(EF_CSIM_MDN, 1,
867                 obtainMessage(EVENT_GET_ICC_RECORD_DONE, new EfCsimMdnLoaded()));
868         mRecordsToLoad++;
869 
870         mFh.loadEFTransparent(EF_CSIM_IMSIM,
871                 obtainMessage(EVENT_GET_ICC_RECORD_DONE, new EfCsimImsimLoaded()));
872         mRecordsToLoad++;
873 
874         mFh.loadEFLinearFixedAll(EF_CSIM_CDMAHOME,
875                 obtainMessage(EVENT_GET_ICC_RECORD_DONE, new EfCsimCdmaHomeLoaded()));
876         mRecordsToLoad++;
877 
878         // Entire PRL could be huge. We are only interested in
879         // the first 4 bytes of the record.
880         mFh.loadEFTransparent(EF_CSIM_EPRL, 4,
881                 obtainMessage(EVENT_GET_ICC_RECORD_DONE, new EfCsimEprlLoaded()));
882         mRecordsToLoad++;
883 
884         mFh.loadEFTransparent(EF_CSIM_MIPUPP,
885                 obtainMessage(EVENT_GET_ICC_RECORD_DONE, new EfCsimMipUppLoaded()));
886         mRecordsToLoad++;
887 
888         if (DBG) log("fetchRuimRecords " + mRecordsToLoad + " requested: " + mRecordsRequested);
889         // Further records that can be inserted are Operator/OEM dependent
890     }
891 
892     /**
893      * {@inheritDoc}
894      *
895      * No Display rule for RUIMs yet.
896      */
897     @Override
getDisplayRule(ServiceState serviceState)898     public int getDisplayRule(ServiceState serviceState) {
899         // TODO together with spn
900         return 0;
901     }
902 
903     @Override
isProvisioned()904     public boolean isProvisioned() {
905         // If UICC card has CSIM app, look for MDN and MIN field
906         // to determine if the SIM is provisioned.  Otherwise,
907         // consider the SIM is provisioned. (for case of ordinal
908         // USIM only UICC.)
909         // If PROPERTY_TEST_CSIM is defined, bypess provision check
910         // and consider the SIM is provisioned.
911         if (SystemProperties.getBoolean(PROPERTY_TEST_CSIM, false)) {
912             return true;
913         }
914 
915         if (mParentApp == null) {
916             return false;
917         }
918 
919         if (mParentApp.getType() == AppType.APPTYPE_CSIM &&
920             ((mMdn == null) || (mMin == null))) {
921             return false;
922         }
923         return true;
924     }
925 
926     @Override
setVoiceMessageWaiting(int line, int countWaiting)927     public void setVoiceMessageWaiting(int line, int countWaiting) {
928         // Will be used in future to store voice mail count in UIM
929         // C.S0023-D_v1.0 does not have a file id in UIM for MWI
930         log("RuimRecords:setVoiceMessageWaiting - NOP for CDMA");
931     }
932 
933     @Override
getVoiceMessageCount()934     public int getVoiceMessageCount() {
935         // Will be used in future to retrieve voice mail count for UIM
936         // C.S0023-D_v1.0 does not have a file id in UIM for MWI
937         log("RuimRecords:getVoiceMessageCount - NOP for CDMA");
938         return 0;
939     }
940 
941     @Override
handleFileUpdate(int efid)942     protected void handleFileUpdate(int efid) {
943         mAdnCache.reset();
944         fetchRuimRecords();
945     }
946 
getMdn()947     public String getMdn() {
948         return mMdn;
949     }
950 
getMin()951     public String getMin() {
952         return mMin;
953     }
954 
getSid()955     public String getSid() {
956         return mHomeSystemId;
957     }
958 
getNid()959     public String getNid() {
960         return mHomeNetworkId;
961     }
962 
getCsimSpnDisplayCondition()963     public boolean getCsimSpnDisplayCondition() {
964         return mCsimSpnDisplayCondition;
965     }
966     @Override
log(String s)967     protected void log(String s) {
968         Rlog.d(LOG_TAG, "[RuimRecords] " + s);
969     }
970 
971     @Override
loge(String s)972     protected void loge(String s) {
973         Rlog.e(LOG_TAG, "[RuimRecords] " + s);
974     }
975 
976     @Override
dump(FileDescriptor fd, PrintWriter pw, String[] args)977     public void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
978         pw.println("RuimRecords: " + this);
979         pw.println(" extends:");
980         super.dump(fd, pw, args);
981         pw.println(" mOtaCommited=" + mOtaCommited);
982         pw.println(" mMyMobileNumber=" + mMyMobileNumber);
983         pw.println(" mMin2Min1=" + mMin2Min1);
984         pw.println(" mPrlVersion=" + mPrlVersion);
985         pw.println(" mEFpl[]=" + Arrays.toString(mEFpl));
986         pw.println(" mEFli[]=" + Arrays.toString(mEFli));
987         pw.println(" mCsimSpnDisplayCondition=" + mCsimSpnDisplayCondition);
988         pw.println(" mMdn=" + mMdn);
989         pw.println(" mMin=" + mMin);
990         pw.println(" mHomeSystemId=" + mHomeSystemId);
991         pw.println(" mHomeNetworkId=" + mHomeNetworkId);
992         pw.flush();
993     }
994 }
995