Home | History | Annotate | Download | only in uicc
      1 /*
      2  * Copyright (C) 2014 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.annotation.Nullable;
     20 import android.content.Intent;
     21 import android.content.pm.PackageInfo;
     22 import android.content.pm.PackageManager;
     23 import android.content.pm.ResolveInfo;
     24 import android.content.pm.Signature;
     25 import android.os.AsyncResult;
     26 import android.os.Binder;
     27 import android.os.Handler;
     28 import android.os.Message;
     29 import android.telephony.Rlog;
     30 import android.telephony.TelephonyManager;
     31 
     32 import com.android.internal.telephony.CommandsInterface;
     33 import com.android.internal.telephony.uicc.IccUtils;
     34 
     35 import java.io.ByteArrayInputStream;
     36 import java.io.FileDescriptor;
     37 import java.io.PrintWriter;
     38 import java.lang.IllegalArgumentException;
     39 import java.lang.IndexOutOfBoundsException;
     40 import java.security.MessageDigest;
     41 import java.security.NoSuchAlgorithmException;
     42 import java.security.cert.Certificate;
     43 import java.security.cert.CertificateException;
     44 import java.security.cert.CertificateFactory;
     45 import java.security.cert.X509Certificate;
     46 import java.util.ArrayList;
     47 import java.util.Arrays;
     48 import java.util.List;
     49 import java.util.Locale;
     50 import java.util.concurrent.atomic.AtomicInteger;
     51 
     52 /**
     53  * Class that reads and stores the carrier privileged rules from the UICC.
     54  *
     55  * The rules are read when the class is created, hence it should only be created
     56  * after the UICC can be read. And it should be deleted when a UICC is changed.
     57  *
     58  * The spec for the rules:
     59  *     GP Secure Element Access Control:
     60  *     http://www.globalplatform.org/specifications/review/GPD_SE_Access_Control_v1.0.20.pdf
     61  *     Extension spec:
     62  *     https://code.google.com/p/seek-for-android/
     63  *
     64  *
     65  * TODO: Notifications.
     66  *
     67  * {@hide}
     68  */
     69 public class UiccCarrierPrivilegeRules extends Handler {
     70     private static final String LOG_TAG = "UiccCarrierPrivilegeRules";
     71 
     72     private static final String AID = "A00000015141434C00";
     73     private static final int CLA = 0x80;
     74     private static final int COMMAND = 0xCA;
     75     private static final int P1 = 0xFF;
     76     private static final int P2 = 0x40;
     77     private static final int P2_EXTENDED_DATA = 0x60;
     78     private static final int P3 = 0x00;
     79     private static final String DATA = "";
     80 
     81     /*
     82      * Rules format:
     83      *   ALL_REF_AR_DO = TAG_ALL_REF_AR_DO + len + [REF_AR_DO]*n
     84      *   REF_AR_DO = TAG_REF_AR_DO + len + REF-DO + AR-DO
     85      *
     86      *   REF_DO = TAG_REF_DO + len + DEVICE_APP_ID_REF_DO + (optional) PKG_REF_DO
     87      *   AR_DO = TAG_AR_DO + len + PERM_AR_DO
     88      *
     89      *   DEVICE_APP_ID_REF_DO = TAG_DEVICE_APP_ID_REF_DO + len + sha256 hexstring of cert
     90      *   PKG_REF_DO = TAG_PKG_REF_DO + len + package name
     91      *   PERM_AR_DO = TAG_PERM_AR_DO + len + detailed permission (8 bytes)
     92      *
     93      * Data objects hierarchy by TAG:
     94      * FF40
     95      *   E2
     96      *     E1
     97      *       C1
     98      *       CA
     99      *     E3
    100      *       DB
    101      */
    102     // Values from the data standard.
    103     private static final String TAG_ALL_REF_AR_DO = "FF40";
    104     private static final String TAG_REF_AR_DO = "E2";
    105     private static final String TAG_REF_DO = "E1";
    106     private static final String TAG_DEVICE_APP_ID_REF_DO = "C1";
    107     private static final String TAG_PKG_REF_DO = "CA";
    108     private static final String TAG_AR_DO = "E3";
    109     private static final String TAG_PERM_AR_DO = "DB";
    110 
    111     private static final int EVENT_OPEN_LOGICAL_CHANNEL_DONE = 1;
    112     private static final int EVENT_TRANSMIT_LOGICAL_CHANNEL_DONE = 2;
    113     private static final int EVENT_CLOSE_LOGICAL_CHANNEL_DONE = 3;
    114 
    115     // State of the object.
    116     private static final int STATE_LOADING  = 0;
    117     private static final int STATE_LOADED   = 1;
    118     private static final int STATE_ERROR    = 2;
    119 
    120     // Describes a single rule.
    121     private static class AccessRule {
    122         public byte[] certificateHash;
    123         public String packageName;
    124         public long accessType;   // This bit is not currently used, but reserved for future use.
    125 
    126         AccessRule(byte[] certificateHash, String packageName, long accessType) {
    127             this.certificateHash = certificateHash;
    128             this.packageName = packageName;
    129             this.accessType = accessType;
    130         }
    131 
    132         boolean matches(byte[] certHash, String packageName) {
    133           return certHash != null && Arrays.equals(this.certificateHash, certHash) &&
    134                 (this.packageName == null || this.packageName.equals(packageName));
    135         }
    136 
    137         @Override
    138         public String toString() {
    139             return "cert: " + IccUtils.bytesToHexString(certificateHash) + " pkg: " +
    140                 packageName + " access: " + accessType;
    141         }
    142     }
    143 
    144     // Used for parsing the data from the UICC.
    145     private static class TLV {
    146         private static final int SINGLE_BYTE_MAX_LENGTH = 0x80;
    147         private String tag;
    148         // Length encoding is in GPC_Specification_2.2.1: 11.1.5 APDU Message and Data Length.
    149         // Length field could be either 1 byte if length < 128, or multiple bytes with first byte
    150         // specifying how many bytes are used for length, followed by length bytes.
    151         // Bytes for the length field, in ASCII HEX string form.
    152         private String lengthBytes;
    153         // Decoded length as integer.
    154         private Integer length;
    155         private String value;
    156 
    157         public TLV(String tag) {
    158             this.tag = tag;
    159         }
    160 
    161         public String parseLength(String data) {
    162             int offset = tag.length();
    163             int firstByte = Integer.parseInt(data.substring(offset, offset + 2), 16);
    164             if (firstByte < SINGLE_BYTE_MAX_LENGTH) {
    165                 length = firstByte * 2;
    166                 lengthBytes = data.substring(offset, offset + 2);
    167             } else {
    168                 int numBytes = firstByte - SINGLE_BYTE_MAX_LENGTH;
    169                 length = Integer.parseInt(data.substring(offset + 2, offset + 2 + numBytes * 2), 16) * 2;
    170                 lengthBytes = data.substring(offset, offset + 2 + numBytes * 2);
    171             }
    172             Rlog.d(LOG_TAG, "TLV parseLength length=" + length + "lenghtBytes: " + lengthBytes);
    173             return lengthBytes;
    174         }
    175 
    176         public String parse(String data, boolean shouldConsumeAll) {
    177             Rlog.d(LOG_TAG, "Parse TLV: " + tag);
    178             if (!data.startsWith(tag)) {
    179                 throw new IllegalArgumentException("Tags don't match.");
    180             }
    181             int index = tag.length();
    182             if (index + 2 > data.length()) {
    183                 throw new IllegalArgumentException("No length.");
    184             }
    185 
    186             parseLength(data);
    187             index += lengthBytes.length();
    188 
    189             Rlog.d(LOG_TAG, "index="+index+" length="+length+"data.length="+data.length());
    190             int remainingLength = data.length() - (index + length);
    191             if (remainingLength < 0) {
    192                 throw new IllegalArgumentException("Not enough data.");
    193             }
    194             if (shouldConsumeAll && (remainingLength != 0)) {
    195                 throw new IllegalArgumentException("Did not consume all.");
    196             }
    197             value = data.substring(index, index + length);
    198 
    199             Rlog.d(LOG_TAG, "Got TLV: " + tag + "," + length + "," + value);
    200 
    201             return data.substring(index + length);
    202         }
    203     }
    204 
    205     private UiccCard mUiccCard;  // Parent
    206     private AtomicInteger mState;
    207     private List<AccessRule> mAccessRules;
    208     private String mRules;
    209     private Message mLoadedCallback;
    210     private String mStatusMessage;  // Only used for debugging.
    211     private int mChannelId; // Channel Id for communicating with UICC.
    212 
    213     public UiccCarrierPrivilegeRules(UiccCard uiccCard, Message loadedCallback) {
    214         Rlog.d(LOG_TAG, "Creating UiccCarrierPrivilegeRules");
    215         mUiccCard = uiccCard;
    216         mState = new AtomicInteger(STATE_LOADING);
    217         mStatusMessage = "Not loaded.";
    218         mLoadedCallback = loadedCallback;
    219         mRules = "";
    220 
    221         // Start loading the rules.
    222         mUiccCard.iccOpenLogicalChannel(AID,
    223             obtainMessage(EVENT_OPEN_LOGICAL_CHANNEL_DONE, null));
    224     }
    225 
    226     /**
    227      * Returns true if the carrier privilege rules have finished loading.
    228      */
    229     public boolean areCarrierPriviligeRulesLoaded() {
    230         return mState.get() != STATE_LOADING;
    231     }
    232 
    233     /**
    234      * Returns the status of the carrier privileges for the input certificate and package name.
    235      *
    236      * @param signature The signature of the certificate.
    237      * @param packageName name of the package.
    238      * @return Access status.
    239      */
    240     public int getCarrierPrivilegeStatus(Signature signature, String packageName) {
    241         Rlog.d(LOG_TAG, "hasCarrierPrivileges: " + signature + " : " + packageName);
    242         int state = mState.get();
    243         if (state == STATE_LOADING) {
    244             Rlog.d(LOG_TAG, "Rules not loaded.");
    245             return TelephonyManager.CARRIER_PRIVILEGE_STATUS_RULES_NOT_LOADED;
    246         } else if (state == STATE_ERROR) {
    247             Rlog.d(LOG_TAG, "Error loading rules.");
    248             return TelephonyManager.CARRIER_PRIVILEGE_STATUS_ERROR_LOADING_RULES;
    249         }
    250 
    251         // SHA-1 is for backward compatible support only, strongly discouraged for new use.
    252         byte[] certHash = getCertHash(signature, "SHA-1");
    253         byte[] certHash256 = getCertHash(signature, "SHA-256");
    254         Rlog.d(LOG_TAG, "Checking SHA1: " + IccUtils.bytesToHexString(certHash) + " : " + packageName);
    255         Rlog.d(LOG_TAG, "Checking SHA256: " + IccUtils.bytesToHexString(certHash256) + " : " + packageName);
    256         for (AccessRule ar : mAccessRules) {
    257             if (ar.matches(certHash, packageName) || ar.matches(certHash256, packageName)) {
    258                 Rlog.d(LOG_TAG, "Match found!");
    259                 return TelephonyManager.CARRIER_PRIVILEGE_STATUS_HAS_ACCESS;
    260             }
    261         }
    262 
    263         Rlog.d(LOG_TAG, "No matching rule found. Returning false.");
    264         return TelephonyManager.CARRIER_PRIVILEGE_STATUS_NO_ACCESS;
    265     }
    266 
    267     /**
    268      * Returns the status of the carrier privileges for the input package name.
    269      *
    270      * @param packageManager PackageManager for getting signatures.
    271      * @param packageName name of the package.
    272      * @return Access status.
    273      */
    274     public int getCarrierPrivilegeStatus(PackageManager packageManager, String packageName) {
    275         try {
    276             PackageInfo pInfo = packageManager.getPackageInfo(packageName,
    277                 PackageManager.GET_SIGNATURES);
    278             Signature[] signatures = pInfo.signatures;
    279             for (Signature sig : signatures) {
    280                 int accessStatus = getCarrierPrivilegeStatus(sig, pInfo.packageName);
    281                 if (accessStatus != TelephonyManager.CARRIER_PRIVILEGE_STATUS_NO_ACCESS) {
    282                     return accessStatus;
    283                 }
    284             }
    285         } catch (PackageManager.NameNotFoundException ex) {
    286             Rlog.e(LOG_TAG, "NameNotFoundException", ex);
    287         }
    288         return TelephonyManager.CARRIER_PRIVILEGE_STATUS_NO_ACCESS;
    289     }
    290 
    291     /**
    292      * Returns the status of the carrier privileges for the caller of the current transaction.
    293      *
    294      * @param packageManager PackageManager for getting signatures and package names.
    295      * @return Access status.
    296      */
    297     public int getCarrierPrivilegeStatusForCurrentTransaction(PackageManager packageManager) {
    298         String[] packages = packageManager.getPackagesForUid(Binder.getCallingUid());
    299 
    300         for (String pkg : packages) {
    301             int accessStatus = getCarrierPrivilegeStatus(packageManager, pkg);
    302             if (accessStatus != TelephonyManager.CARRIER_PRIVILEGE_STATUS_NO_ACCESS) {
    303                 return accessStatus;
    304             }
    305         }
    306         return TelephonyManager.CARRIER_PRIVILEGE_STATUS_NO_ACCESS;
    307     }
    308 
    309     /**
    310      * Returns the package name of the carrier app that should handle the input intent.
    311      *
    312      * @param packageManager PackageManager for getting receivers.
    313      * @param intent Intent that will be sent.
    314      * @return list of carrier app package names that can handle the intent.
    315      *         Returns null if there is an error and an empty list if there
    316      *         are no matching packages.
    317      */
    318     public List<String> getCarrierPackageNamesForIntent(
    319             PackageManager packageManager, Intent intent) {
    320         List<String> packages = new ArrayList<String>();
    321         List<ResolveInfo> receivers = new ArrayList<ResolveInfo>();
    322         receivers.addAll(packageManager.queryBroadcastReceivers(intent, 0));
    323         receivers.addAll(packageManager.queryIntentContentProviders(intent, 0));
    324         receivers.addAll(packageManager.queryIntentActivities(intent, 0));
    325         receivers.addAll(packageManager.queryIntentServices(intent, 0));
    326 
    327         for (ResolveInfo resolveInfo : receivers) {
    328             String packageName = getPackageName(resolveInfo);
    329             if (packageName == null) {
    330                 continue;
    331             }
    332 
    333             int status = getCarrierPrivilegeStatus(packageManager, packageName);
    334             if (status == TelephonyManager.CARRIER_PRIVILEGE_STATUS_HAS_ACCESS) {
    335                 packages.add(packageName);
    336             } else if (status != TelephonyManager.CARRIER_PRIVILEGE_STATUS_NO_ACCESS) {
    337                 // Any status apart from HAS_ACCESS and NO_ACCESS is considered an error.
    338                 return null;
    339             }
    340         }
    341 
    342         return packages;
    343     }
    344 
    345     @Nullable
    346     private String getPackageName(ResolveInfo resolveInfo) {
    347         if (resolveInfo.activityInfo != null) {
    348             return resolveInfo.activityInfo.packageName;
    349         } else if (resolveInfo.serviceInfo != null) {
    350             return resolveInfo.serviceInfo.packageName;
    351         } else if (resolveInfo.providerInfo != null) {
    352             return resolveInfo.providerInfo.packageName;
    353         }
    354         return null;
    355     }
    356 
    357     @Override
    358     public void handleMessage(Message msg) {
    359         AsyncResult ar;
    360 
    361         switch (msg.what) {
    362 
    363           case EVENT_OPEN_LOGICAL_CHANNEL_DONE:
    364               Rlog.d(LOG_TAG, "EVENT_OPEN_LOGICAL_CHANNEL_DONE");
    365               ar = (AsyncResult) msg.obj;
    366               if (ar.exception == null && ar.result != null) {
    367                   mChannelId = ((int[]) ar.result)[0];
    368                   mUiccCard.iccTransmitApduLogicalChannel(mChannelId, CLA, COMMAND, P1, P2, P3, DATA,
    369                       obtainMessage(EVENT_TRANSMIT_LOGICAL_CHANNEL_DONE, new Integer(mChannelId)));
    370               } else {
    371                   updateState(STATE_ERROR, "Error opening channel");
    372               }
    373               break;
    374 
    375           case EVENT_TRANSMIT_LOGICAL_CHANNEL_DONE:
    376               Rlog.d(LOG_TAG, "EVENT_TRANSMIT_LOGICAL_CHANNEL_DONE");
    377               ar = (AsyncResult) msg.obj;
    378               if (ar.exception == null && ar.result != null) {
    379                   IccIoResult response = (IccIoResult) ar.result;
    380                   if (response.sw1 == 0x90 && response.sw2 == 0x00 &&
    381                       response.payload != null && response.payload.length > 0) {
    382                       try {
    383                           mRules += IccUtils.bytesToHexString(response.payload).toUpperCase(Locale.US);
    384                           if (isDataComplete()) {
    385                               mAccessRules = parseRules(mRules);
    386                               updateState(STATE_LOADED, "Success!");
    387                           } else {
    388                               mUiccCard.iccTransmitApduLogicalChannel(mChannelId, CLA, COMMAND, P1, P2_EXTENDED_DATA, P3, DATA,
    389                                   obtainMessage(EVENT_TRANSMIT_LOGICAL_CHANNEL_DONE, new Integer(mChannelId)));
    390                               break;
    391                           }
    392                       } catch (IllegalArgumentException ex) {
    393                           updateState(STATE_ERROR, "Error parsing rules: " + ex);
    394                       } catch (IndexOutOfBoundsException ex) {
    395                           updateState(STATE_ERROR, "Error parsing rules: " + ex);
    396                       }
    397                    } else {
    398                       String errorMsg = "Invalid response: payload=" + response.payload +
    399                               " sw1=" + response.sw1 + " sw2=" + response.sw2;
    400                       updateState(STATE_ERROR, errorMsg);
    401                    }
    402               } else {
    403                   updateState(STATE_ERROR, "Error reading value from SIM.");
    404               }
    405 
    406               mUiccCard.iccCloseLogicalChannel(mChannelId, obtainMessage(
    407                       EVENT_CLOSE_LOGICAL_CHANNEL_DONE));
    408               mChannelId = -1;
    409               break;
    410 
    411           case EVENT_CLOSE_LOGICAL_CHANNEL_DONE:
    412               Rlog.d(LOG_TAG, "EVENT_CLOSE_LOGICAL_CHANNEL_DONE");
    413               break;
    414 
    415           default:
    416               Rlog.e(LOG_TAG, "Unknown event " + msg.what);
    417         }
    418     }
    419 
    420     /*
    421      * Check if all rule bytes have been read from UICC.
    422      * For long payload, we need to fetch it repeatly before start parsing it.
    423      */
    424     private boolean isDataComplete() {
    425         Rlog.d(LOG_TAG, "isDataComplete mRules:" + mRules);
    426         if (mRules.startsWith(TAG_ALL_REF_AR_DO)) {
    427             TLV allRules = new TLV(TAG_ALL_REF_AR_DO);
    428             String lengthBytes = allRules.parseLength(mRules);
    429             Rlog.d(LOG_TAG, "isDataComplete lengthBytes: " + lengthBytes);
    430             if (mRules.length() == TAG_ALL_REF_AR_DO.length() + lengthBytes.length() +
    431                                    allRules.length) {
    432                 Rlog.d(LOG_TAG, "isDataComplete yes");
    433                 return true;
    434             } else {
    435                 Rlog.d(LOG_TAG, "isDataComplete no");
    436                 return false;
    437             }
    438         } else {
    439             throw new IllegalArgumentException("Tags don't match.");
    440         }
    441     }
    442 
    443     /*
    444      * Parses the rules from the input string.
    445      */
    446     private static List<AccessRule> parseRules(String rules) {
    447         Rlog.d(LOG_TAG, "Got rules: " + rules);
    448 
    449         TLV allRefArDo = new TLV(TAG_ALL_REF_AR_DO); //FF40
    450         allRefArDo.parse(rules, true);
    451 
    452         String arDos = allRefArDo.value;
    453         List<AccessRule> accessRules = new ArrayList<AccessRule>();
    454         while (!arDos.isEmpty()) {
    455             TLV refArDo = new TLV(TAG_REF_AR_DO); //E2
    456             arDos = refArDo.parse(arDos, false);
    457             AccessRule accessRule = parseRefArdo(refArDo.value);
    458             if (accessRule != null) {
    459                 accessRules.add(accessRule);
    460             } else {
    461               Rlog.e(LOG_TAG, "Skip unrecognized rule." + refArDo.value);
    462             }
    463         }
    464         return accessRules;
    465     }
    466 
    467     /*
    468      * Parses a single rule.
    469      */
    470     private static AccessRule parseRefArdo(String rule) {
    471         Rlog.d(LOG_TAG, "Got rule: " + rule);
    472 
    473         String certificateHash = null;
    474         String packageName = null;
    475         String tmp = null;
    476         long accessType = 0;
    477 
    478         while (!rule.isEmpty()) {
    479             if (rule.startsWith(TAG_REF_DO)) {
    480                 TLV refDo = new TLV(TAG_REF_DO); //E1
    481                 rule = refDo.parse(rule, false);
    482 
    483                 // Skip unrelated rules.
    484                 if (!refDo.value.startsWith(TAG_DEVICE_APP_ID_REF_DO)) {
    485                     return null;
    486                 }
    487 
    488                 TLV deviceDo = new TLV(TAG_DEVICE_APP_ID_REF_DO); //C1
    489                 tmp = deviceDo.parse(refDo.value, false);
    490                 certificateHash = deviceDo.value;
    491 
    492                 if (!tmp.isEmpty()) {
    493                   if (!tmp.startsWith(TAG_PKG_REF_DO)) {
    494                       return null;
    495                   }
    496                   TLV pkgDo = new TLV(TAG_PKG_REF_DO); //CA
    497                   pkgDo.parse(tmp, true);
    498                   packageName = new String(IccUtils.hexStringToBytes(pkgDo.value));
    499                 } else {
    500                   packageName = null;
    501                 }
    502             } else if (rule.startsWith(TAG_AR_DO)) {
    503                 TLV arDo = new TLV(TAG_AR_DO); //E3
    504                 rule = arDo.parse(rule, false);
    505 
    506                 // Skip unrelated rules.
    507                 if (!arDo.value.startsWith(TAG_PERM_AR_DO)) {
    508                     return null;
    509                 }
    510 
    511                 TLV permDo = new TLV(TAG_PERM_AR_DO); //DB
    512                 permDo.parse(arDo.value, true);
    513                 Rlog.e(LOG_TAG, permDo.value);
    514             } else  {
    515                 // Spec requires it must be either TAG_REF_DO or TAG_AR_DO.
    516                 throw new RuntimeException("Invalid Rule type");
    517             }
    518         }
    519 
    520         Rlog.e(LOG_TAG, "Adding: " + certificateHash + " : " + packageName + " : " + accessType);
    521 
    522         AccessRule accessRule = new AccessRule(IccUtils.hexStringToBytes(certificateHash),
    523             packageName, accessType);
    524         Rlog.e(LOG_TAG, "Parsed rule: " + accessRule);
    525         return accessRule;
    526     }
    527 
    528     /*
    529      * Converts a Signature into a Certificate hash usable for comparison.
    530      */
    531     private static byte[] getCertHash(Signature signature, String algo) {
    532         try {
    533             MessageDigest md = MessageDigest.getInstance(algo);
    534             return md.digest(signature.toByteArray());
    535         } catch (NoSuchAlgorithmException ex) {
    536             Rlog.e(LOG_TAG, "NoSuchAlgorithmException: " + ex);
    537         }
    538         return null;
    539     }
    540 
    541     /*
    542      * Updates the state and notifies the UiccCard that the rules have finished loading.
    543      */
    544     private void updateState(int newState, String statusMessage) {
    545         mState.set(newState);
    546         if (mLoadedCallback != null) {
    547             mLoadedCallback.sendToTarget();
    548         }
    549 
    550         mStatusMessage = statusMessage;
    551         Rlog.e(LOG_TAG, mStatusMessage);
    552     }
    553 
    554     /**
    555      * Dumps info to Dumpsys - useful for debugging.
    556      */
    557     public void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
    558         pw.println("UiccCarrierPrivilegeRules: " + this);
    559         pw.println(" mState=" + getStateString(mState.get()));
    560         pw.println(" mStatusMessage='" + mStatusMessage + "'");
    561         if (mAccessRules != null) {
    562             pw.println(" mAccessRules: ");
    563             for (AccessRule ar : mAccessRules) {
    564                 pw.println("  rule='" + ar + "'");
    565             }
    566         } else {
    567             pw.println(" mAccessRules: null");
    568         }
    569         pw.flush();
    570     }
    571 
    572     /*
    573      * Converts state into human readable format.
    574      */
    575     private String getStateString(int state) {
    576       switch (state) {
    577         case STATE_LOADING:
    578             return "STATE_LOADING";
    579         case STATE_LOADED:
    580             return "STATE_LOADED";
    581         case STATE_ERROR:
    582             return "STATE_ERROR";
    583         default:
    584             return "UNKNOWN";
    585       }
    586     }
    587 }
    588