| /* |
| * Copyright (C) 2009 The Android Open Source Project |
| * |
| * Licensed under the Apache License, Version 2.0 (the "License"); |
| * you may not use this file except in compliance with the License. |
| * You may obtain a copy of the License at |
| * |
| * http://www.apache.org/licenses/LICENSE-2.0 |
| * |
| * Unless required by applicable law or agreed to in writing, software |
| * distributed under the License is distributed on an "AS IS" BASIS, |
| * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| * See the License for the specific language governing permissions and |
| * limitations under the License. |
| */ |
| |
| package com.android.internal.telephony.gsm; |
| |
| import android.os.AsyncResult; |
| import android.os.Handler; |
| import android.os.Message; |
| import android.telephony.Rlog; |
| import android.util.SparseArray; |
| import android.util.SparseIntArray; |
| |
| import com.android.internal.telephony.uicc.AdnRecord; |
| import com.android.internal.telephony.uicc.AdnRecordCache; |
| import com.android.internal.telephony.uicc.IccConstants; |
| import com.android.internal.telephony.uicc.IccFileHandler; |
| import com.android.internal.telephony.uicc.IccUtils; |
| import java.util.ArrayList; |
| |
| /** |
| * This class implements reading and parsing USIM records. |
| * Refer to Spec 3GPP TS 31.102 for more details. |
| * |
| * {@hide} |
| */ |
| public class UsimPhoneBookManager extends Handler implements IccConstants { |
| private static final String LOG_TAG = "UsimPhoneBookManager"; |
| private static final boolean DBG = true; |
| private ArrayList<PbrRecord> mPbrRecords; |
| private Boolean mIsPbrPresent; |
| private IccFileHandler mFh; |
| private AdnRecordCache mAdnCache; |
| private Object mLock = new Object(); |
| private ArrayList<AdnRecord> mPhoneBookRecords; |
| private ArrayList<byte[]> mIapFileRecord; |
| private ArrayList<byte[]> mEmailFileRecord; |
| |
| // email list for each ADN record. The key would be |
| // ADN's efid << 8 + record # |
| private SparseArray<ArrayList<String>> mEmailsForAdnRec; |
| |
| // SFI to ADN Efid mapping table |
| private SparseIntArray mSfiEfidTable; |
| |
| private boolean mRefreshCache = false; |
| |
| |
| private static final int EVENT_PBR_LOAD_DONE = 1; |
| private static final int EVENT_USIM_ADN_LOAD_DONE = 2; |
| private static final int EVENT_IAP_LOAD_DONE = 3; |
| private static final int EVENT_EMAIL_LOAD_DONE = 4; |
| |
| private static final int USIM_TYPE1_TAG = 0xA8; |
| private static final int USIM_TYPE2_TAG = 0xA9; |
| private static final int USIM_TYPE3_TAG = 0xAA; |
| private static final int USIM_EFADN_TAG = 0xC0; |
| private static final int USIM_EFIAP_TAG = 0xC1; |
| private static final int USIM_EFEXT1_TAG = 0xC2; |
| private static final int USIM_EFSNE_TAG = 0xC3; |
| private static final int USIM_EFANR_TAG = 0xC4; |
| private static final int USIM_EFPBC_TAG = 0xC5; |
| private static final int USIM_EFGRP_TAG = 0xC6; |
| private static final int USIM_EFAAS_TAG = 0xC7; |
| private static final int USIM_EFGSD_TAG = 0xC8; |
| private static final int USIM_EFUID_TAG = 0xC9; |
| private static final int USIM_EFEMAIL_TAG = 0xCA; |
| private static final int USIM_EFCCP1_TAG = 0xCB; |
| |
| private static final int INVALID_SFI = -1; |
| private static final byte INVALID_BYTE = -1; |
| |
| // class File represent a PBR record TLV object which points to the rest of the phonebook EFs |
| private class File { |
| // Phonebook reference file constructed tag defined in 3GPP TS 31.102 |
| // section 4.4.2.1 table 4.1 |
| private final int mParentTag; |
| // EFID of the file |
| private final int mEfid; |
| // SFI (Short File Identification) of the file. 0xFF indicates invalid SFI. |
| private final int mSfi; |
| // The order of this tag showing in the PBR record. |
| private final int mIndex; |
| |
| File(int parentTag, int efid, int sfi, int index) { |
| mParentTag = parentTag; |
| mEfid = efid; |
| mSfi = sfi; |
| mIndex = index; |
| } |
| |
| public int getParentTag() { return mParentTag; } |
| public int getEfid() { return mEfid; } |
| public int getSfi() { return mSfi; } |
| public int getIndex() { return mIndex; } |
| } |
| |
| public UsimPhoneBookManager(IccFileHandler fh, AdnRecordCache cache) { |
| mFh = fh; |
| mPhoneBookRecords = new ArrayList<AdnRecord>(); |
| mPbrRecords = null; |
| // We assume its present, after the first read this is updated. |
| // So we don't have to read from UICC if its not present on subsequent reads. |
| mIsPbrPresent = true; |
| mAdnCache = cache; |
| mEmailsForAdnRec = new SparseArray<ArrayList<String>>(); |
| mSfiEfidTable = new SparseIntArray(); |
| } |
| |
| public void reset() { |
| mPhoneBookRecords.clear(); |
| mIapFileRecord = null; |
| mEmailFileRecord = null; |
| mPbrRecords = null; |
| mIsPbrPresent = true; |
| mRefreshCache = false; |
| mEmailsForAdnRec.clear(); |
| mSfiEfidTable.clear(); |
| } |
| |
| // Load all phonebook related EFs from the SIM. |
| public ArrayList<AdnRecord> loadEfFilesFromUsim() { |
| synchronized (mLock) { |
| if (!mPhoneBookRecords.isEmpty()) { |
| if (mRefreshCache) { |
| mRefreshCache = false; |
| refreshCache(); |
| } |
| return mPhoneBookRecords; |
| } |
| |
| if (!mIsPbrPresent) return null; |
| |
| // Check if the PBR file is present in the cache, if not read it |
| // from the USIM. |
| if (mPbrRecords == null) { |
| readPbrFileAndWait(); |
| } |
| |
| if (mPbrRecords == null) |
| return null; |
| |
| int numRecs = mPbrRecords.size(); |
| |
| log("loadEfFilesFromUsim: Loading adn and emails"); |
| for (int i = 0; i < numRecs; i++) { |
| readAdnFileAndWait(i); |
| readEmailFileAndWait(i); |
| } |
| |
| updatePhoneAdnRecord(); |
| // All EF files are loaded, return all the records |
| } |
| return mPhoneBookRecords; |
| } |
| |
| // Refresh the phonebook cache. |
| private void refreshCache() { |
| if (mPbrRecords == null) return; |
| mPhoneBookRecords.clear(); |
| |
| int numRecs = mPbrRecords.size(); |
| for (int i = 0; i < numRecs; i++) { |
| readAdnFileAndWait(i); |
| } |
| } |
| |
| // Invalidate the phonebook cache. |
| public void invalidateCache() { |
| mRefreshCache = true; |
| } |
| |
| // Read the phonebook reference file EF_PBR. |
| private void readPbrFileAndWait() { |
| mFh.loadEFLinearFixedAll(EF_PBR, obtainMessage(EVENT_PBR_LOAD_DONE)); |
| try { |
| mLock.wait(); |
| } catch (InterruptedException e) { |
| Rlog.e(LOG_TAG, "Interrupted Exception in readAdnFileAndWait"); |
| } |
| } |
| |
| // Read EF_EMAIL which contains the email records. |
| private void readEmailFileAndWait(int recId) { |
| SparseArray<File> files; |
| files = mPbrRecords.get(recId).mFileIds; |
| if (files == null) return; |
| |
| File email = files.get(USIM_EFEMAIL_TAG); |
| if (email != null) { |
| |
| /** |
| * Check if the EF_EMAIL is a Type 1 file or a type 2 file. |
| * If mEmailPresentInIap is true, its a type 2 file. |
| * So we read the IAP file and then read the email records. |
| * instead of reading directly. |
| */ |
| if (email.getParentTag() == USIM_TYPE2_TAG) { |
| if (files.get(USIM_EFIAP_TAG) == null) { |
| Rlog.e(LOG_TAG, "Can't locate EF_IAP in EF_PBR."); |
| return; |
| } |
| |
| log("EF_IAP exists. Loading EF_IAP to retrieve the index."); |
| readIapFileAndWait(files.get(USIM_EFIAP_TAG).getEfid()); |
| if (mIapFileRecord == null) { |
| Rlog.e(LOG_TAG, "Error: IAP file is empty"); |
| return; |
| } |
| |
| log("EF_EMAIL order in PBR record: " + email.getIndex()); |
| } |
| |
| int emailEfid = email.getEfid(); |
| log("EF_EMAIL exists in PBR. efid = 0x" + |
| Integer.toHexString(emailEfid).toUpperCase()); |
| |
| /** |
| * Make sure this EF_EMAIL was never read earlier. Sometimes two PBR record points |
| */ |
| // to the same EF_EMAIL |
| for (int i = 0; i < recId; i++) { |
| if (mPbrRecords.get(i) != null) { |
| SparseArray<File> previousFileIds = mPbrRecords.get(i).mFileIds; |
| if (previousFileIds != null) { |
| File id = previousFileIds.get(USIM_EFEMAIL_TAG); |
| if (id != null && id.getEfid() == emailEfid) { |
| log("Skipped this EF_EMAIL which was loaded earlier"); |
| return; |
| } |
| } |
| } |
| } |
| |
| // Read the EFEmail file. |
| mFh.loadEFLinearFixedAll(emailEfid, |
| obtainMessage(EVENT_EMAIL_LOAD_DONE)); |
| try { |
| mLock.wait(); |
| } catch (InterruptedException e) { |
| Rlog.e(LOG_TAG, "Interrupted Exception in readEmailFileAndWait"); |
| } |
| |
| if (mEmailFileRecord == null) { |
| Rlog.e(LOG_TAG, "Error: Email file is empty"); |
| return; |
| } |
| |
| // Build email list |
| if (email.getParentTag() == USIM_TYPE2_TAG && mIapFileRecord != null) { |
| // If the tag is type 2 and EF_IAP exists, we need to build tpe 2 email list |
| buildType2EmailList(recId); |
| } |
| else { |
| // If one the followings is true, we build type 1 email list |
| // 1. EF_IAP does not exist or it is failed to load |
| // 2. ICC cards can be made such that they have an IAP file but all |
| // records are empty. In that case buildType2EmailList will fail and |
| // we need to build type 1 email list. |
| |
| // Build type 1 email list |
| buildType1EmailList(recId); |
| } |
| } |
| } |
| |
| // Build type 1 email list |
| private void buildType1EmailList(int recId) { |
| /** |
| * If this is type 1, the number of records in EF_EMAIL would be same as the record number |
| * in the master/reference file. |
| */ |
| if (mPbrRecords.get(recId) == null) |
| return; |
| |
| int numRecs = mPbrRecords.get(recId).mMasterFileRecordNum; |
| log("Building type 1 email list. recId = " |
| + recId + ", numRecs = " + numRecs); |
| |
| byte[] emailRec; |
| for (int i = 0; i < numRecs; i++) { |
| try { |
| emailRec = mEmailFileRecord.get(i); |
| } catch (IndexOutOfBoundsException e) { |
| Rlog.e(LOG_TAG, "Error: Improper ICC card: No email record for ADN, continuing"); |
| break; |
| } |
| |
| /** |
| * 3GPP TS 31.102 4.4.2.13 EF_EMAIL (e-mail address) |
| * |
| * The fields below are mandatory if and only if the file |
| * is not type 1 (as specified in EF_PBR) |
| * |
| * Byte [X + 1]: ADN file SFI (Short File Identification) |
| * Byte [X + 2]: ADN file Record Identifier |
| */ |
| int sfi = emailRec[emailRec.length - 2]; |
| int adnRecId = emailRec[emailRec.length - 1]; |
| |
| String email = readEmailRecord(i); |
| |
| if (email == null || email.equals("")) { |
| continue; |
| } |
| |
| // Get the associated ADN's efid first. |
| int adnEfid = 0; |
| if (sfi == INVALID_SFI || mSfiEfidTable.get(sfi) == 0) { |
| |
| // If SFI is invalid or cannot be mapped to any ADN, use the ADN's efid |
| // in the same PBR files. |
| File file = mPbrRecords.get(recId).mFileIds.get(USIM_EFADN_TAG); |
| if (file == null) |
| continue; |
| adnEfid = file.getEfid(); |
| } |
| else { |
| adnEfid = mSfiEfidTable.get(sfi); |
| } |
| /** |
| * SIM record numbers are 1 based. |
| * The key is constructed by efid and record index. |
| */ |
| int index = (((adnEfid & 0xFFFF) << 8) | ((adnRecId - 1) & 0xFF)); |
| ArrayList<String> emailList = mEmailsForAdnRec.get(index); |
| if (emailList == null) { |
| emailList = new ArrayList<String>(); |
| } |
| log("Adding email #" + i + " list to index 0x" + |
| Integer.toHexString(index).toUpperCase()); |
| emailList.add(email); |
| mEmailsForAdnRec.put(index, emailList); |
| } |
| } |
| |
| // Build type 2 email list |
| private boolean buildType2EmailList(int recId) { |
| |
| if (mPbrRecords.get(recId) == null) |
| return false; |
| |
| int numRecs = mPbrRecords.get(recId).mMasterFileRecordNum; |
| log("Building type 2 email list. recId = " |
| + recId + ", numRecs = " + numRecs); |
| |
| /** |
| * 3GPP TS 31.102 4.4.2.1 EF_PBR (Phone Book Reference file) table 4.1 |
| |
| * The number of records in the IAP file is same as the number of records in the master |
| * file (e.g EF_ADN). The order of the pointers in an EF_IAP shall be the same as the |
| * order of file IDs that appear in the TLV object indicated by Tag 'A9' in the |
| * reference file record (e.g value of mEmailTagNumberInIap) |
| */ |
| |
| File adnFile = mPbrRecords.get(recId).mFileIds.get(USIM_EFADN_TAG); |
| if (adnFile == null) { |
| Rlog.e(LOG_TAG, "Error: Improper ICC card: EF_ADN does not exist in PBR files"); |
| return false; |
| } |
| int adnEfid = adnFile.getEfid(); |
| |
| for (int i = 0; i < numRecs; i++) { |
| byte[] record; |
| int emailRecId; |
| try { |
| record = mIapFileRecord.get(i); |
| emailRecId = |
| record[mPbrRecords.get(recId).mFileIds.get(USIM_EFEMAIL_TAG).getIndex()]; |
| } catch (IndexOutOfBoundsException e) { |
| Rlog.e(LOG_TAG, "Error: Improper ICC card: Corrupted EF_IAP"); |
| continue; |
| } |
| |
| String email = readEmailRecord(emailRecId - 1); |
| if (email != null && !email.equals("")) { |
| // The key is constructed by efid and record index. |
| int index = (((adnEfid & 0xFFFF) << 8) | (i & 0xFF)); |
| ArrayList<String> emailList = mEmailsForAdnRec.get(index); |
| if (emailList == null) { |
| emailList = new ArrayList<String>(); |
| } |
| emailList.add(email); |
| log("Adding email list to index 0x" + |
| Integer.toHexString(index).toUpperCase()); |
| mEmailsForAdnRec.put(index, emailList); |
| } |
| } |
| return true; |
| } |
| |
| // Read Phonebook Index Admistration EF_IAP file |
| private void readIapFileAndWait(int efid) { |
| mFh.loadEFLinearFixedAll(efid, obtainMessage(EVENT_IAP_LOAD_DONE)); |
| try { |
| mLock.wait(); |
| } catch (InterruptedException e) { |
| Rlog.e(LOG_TAG, "Interrupted Exception in readIapFileAndWait"); |
| } |
| } |
| |
| private void updatePhoneAdnRecord() { |
| |
| int numAdnRecs = mPhoneBookRecords.size(); |
| |
| for (int i = 0; i < numAdnRecs; i++) { |
| |
| AdnRecord rec = mPhoneBookRecords.get(i); |
| |
| int adnEfid = rec.getEfid(); |
| int adnRecId = rec.getRecId(); |
| |
| int index = (((adnEfid & 0xFFFF) << 8) | ((adnRecId - 1) & 0xFF)); |
| |
| ArrayList<String> emailList; |
| try { |
| emailList = mEmailsForAdnRec.get(index); |
| } catch (IndexOutOfBoundsException e) { |
| continue; |
| } |
| |
| if (emailList == null) |
| continue; |
| |
| String[] emails = new String[emailList.size()]; |
| System.arraycopy(emailList.toArray(), 0, emails, 0, emailList.size()); |
| rec.setEmails(emails); |
| log("Adding email list to ADN (0x" + |
| Integer.toHexString(mPhoneBookRecords.get(i).getEfid()).toUpperCase() + |
| ") record #" + mPhoneBookRecords.get(i).getRecId()); |
| mPhoneBookRecords.set(i, rec); |
| } |
| } |
| |
| // Read email from the record of EF_EMAIL |
| private String readEmailRecord(int recId) { |
| byte[] emailRec; |
| try { |
| emailRec = mEmailFileRecord.get(recId); |
| } catch (IndexOutOfBoundsException e) { |
| return null; |
| } |
| |
| // The length of the record is X+2 byte, where X bytes is the email address |
| return IccUtils.adnStringFieldToString(emailRec, 0, emailRec.length - 2); |
| } |
| |
| // Read EF_ADN file |
| private void readAdnFileAndWait(int recId) { |
| SparseArray<File> files; |
| files = mPbrRecords.get(recId).mFileIds; |
| if (files == null || files.size() == 0) return; |
| |
| int extEf = 0; |
| // Only call fileIds.get while EF_EXT1_TAG is available |
| if (files.get(USIM_EFEXT1_TAG) != null) { |
| extEf = files.get(USIM_EFEXT1_TAG).getEfid(); |
| } |
| |
| if (files.get(USIM_EFADN_TAG) == null) |
| return; |
| |
| int previousSize = mPhoneBookRecords.size(); |
| mAdnCache.requestLoadAllAdnLike(files.get(USIM_EFADN_TAG).getEfid(), |
| extEf, obtainMessage(EVENT_USIM_ADN_LOAD_DONE)); |
| try { |
| mLock.wait(); |
| } catch (InterruptedException e) { |
| Rlog.e(LOG_TAG, "Interrupted Exception in readAdnFileAndWait"); |
| } |
| |
| /** |
| * The recent added ADN record # would be the reference record size |
| * for the rest of EFs associated within this PBR. |
| */ |
| mPbrRecords.get(recId).mMasterFileRecordNum = mPhoneBookRecords.size() - previousSize; |
| } |
| |
| // Create the phonebook reference file based on EF_PBR |
| private void createPbrFile(ArrayList<byte[]> records) { |
| if (records == null) { |
| mPbrRecords = null; |
| mIsPbrPresent = false; |
| return; |
| } |
| |
| mPbrRecords = new ArrayList<PbrRecord>(); |
| for (int i = 0; i < records.size(); i++) { |
| // Some cards have two records but the 2nd record is filled with all invalid char 0xff. |
| // So we need to check if the record is valid or not before adding into the PBR records. |
| if (records.get(i)[0] != INVALID_BYTE) { |
| mPbrRecords.add(new PbrRecord(records.get(i))); |
| } |
| } |
| |
| for (PbrRecord record : mPbrRecords) { |
| File file = record.mFileIds.get(USIM_EFADN_TAG); |
| // If the file does not contain EF_ADN, we'll just skip it. |
| if (file != null) { |
| int sfi = file.getSfi(); |
| if (sfi != INVALID_SFI) { |
| mSfiEfidTable.put(sfi, record.mFileIds.get(USIM_EFADN_TAG).getEfid()); |
| } |
| } |
| } |
| } |
| |
| @Override |
| public void handleMessage(Message msg) { |
| AsyncResult ar; |
| |
| switch(msg.what) { |
| case EVENT_PBR_LOAD_DONE: |
| log("Loading PBR records done"); |
| ar = (AsyncResult) msg.obj; |
| if (ar.exception == null) { |
| createPbrFile((ArrayList<byte[]>)ar.result); |
| } |
| synchronized (mLock) { |
| mLock.notify(); |
| } |
| break; |
| case EVENT_USIM_ADN_LOAD_DONE: |
| log("Loading USIM ADN records done"); |
| ar = (AsyncResult) msg.obj; |
| if (ar.exception == null) { |
| mPhoneBookRecords.addAll((ArrayList<AdnRecord>)ar.result); |
| } |
| synchronized (mLock) { |
| mLock.notify(); |
| } |
| break; |
| case EVENT_IAP_LOAD_DONE: |
| log("Loading USIM IAP records done"); |
| ar = (AsyncResult) msg.obj; |
| if (ar.exception == null) { |
| mIapFileRecord = ((ArrayList<byte[]>)ar.result); |
| } |
| synchronized (mLock) { |
| mLock.notify(); |
| } |
| break; |
| case EVENT_EMAIL_LOAD_DONE: |
| log("Loading USIM Email records done"); |
| ar = (AsyncResult) msg.obj; |
| if (ar.exception == null) { |
| mEmailFileRecord = ((ArrayList<byte[]>)ar.result); |
| } |
| |
| synchronized (mLock) { |
| mLock.notify(); |
| } |
| break; |
| } |
| } |
| |
| // PbrRecord represents a record in EF_PBR |
| private class PbrRecord { |
| // TLV tags |
| private SparseArray<File> mFileIds; |
| |
| /** |
| * 3GPP TS 31.102 4.4.2.1 EF_PBR (Phone Book Reference file) |
| * If this is type 1 files, files that contain as many records as the |
| * reference/master file (EF_ADN, EF_ADN1) and are linked on record number |
| * bases (Rec1 -> Rec1). The master file record number is the reference. |
| */ |
| private int mMasterFileRecordNum; |
| |
| PbrRecord(byte[] record) { |
| mFileIds = new SparseArray<File>(); |
| SimTlv recTlv; |
| log("PBR rec: " + IccUtils.bytesToHexString(record)); |
| recTlv = new SimTlv(record, 0, record.length); |
| parseTag(recTlv); |
| } |
| |
| void parseTag(SimTlv tlv) { |
| SimTlv tlvEfSfi; |
| int tag; |
| byte[] data; |
| |
| do { |
| tag = tlv.getTag(); |
| switch(tag) { |
| case USIM_TYPE1_TAG: // A8 |
| case USIM_TYPE3_TAG: // AA |
| case USIM_TYPE2_TAG: // A9 |
| data = tlv.getData(); |
| tlvEfSfi = new SimTlv(data, 0, data.length); |
| parseEfAndSFI(tlvEfSfi, tag); |
| break; |
| } |
| } while (tlv.nextObject()); |
| } |
| |
| void parseEfAndSFI(SimTlv tlv, int parentTag) { |
| int tag; |
| byte[] data; |
| int tagNumberWithinParentTag = 0; |
| do { |
| tag = tlv.getTag(); |
| switch(tag) { |
| case USIM_EFEMAIL_TAG: |
| case USIM_EFADN_TAG: |
| case USIM_EFEXT1_TAG: |
| case USIM_EFANR_TAG: |
| case USIM_EFPBC_TAG: |
| case USIM_EFGRP_TAG: |
| case USIM_EFAAS_TAG: |
| case USIM_EFGSD_TAG: |
| case USIM_EFUID_TAG: |
| case USIM_EFCCP1_TAG: |
| case USIM_EFIAP_TAG: |
| case USIM_EFSNE_TAG: |
| /** 3GPP TS 31.102, 4.4.2.1 EF_PBR (Phone Book Reference file) |
| * |
| * The SFI value assigned to an EF which is indicated in EF_PBR shall |
| * correspond to the SFI indicated in the TLV object in EF_PBR. |
| |
| * The primitive tag identifies clearly the type of data, its value |
| * field indicates the file identifier and, if applicable, the SFI |
| * value of the specified EF. That is, the length value of a primitive |
| * tag indicates if an SFI value is available for the EF or not: |
| * - Length = '02' Value: 'EFID (2 bytes)' |
| * - Length = '03' Value: 'EFID (2 bytes)', 'SFI (1 byte)' |
| */ |
| |
| int sfi = INVALID_SFI; |
| data = tlv.getData(); |
| |
| if (data.length < 2 || data.length > 3) { |
| log("Invalid TLV length: " + data.length); |
| break; |
| } |
| |
| if (data.length == 3) { |
| sfi = data[2] & 0xFF; |
| } |
| |
| int efid = ((data[0] & 0xFF) << 8) | (data[1] & 0xFF); |
| |
| mFileIds.put(tag, new File(parentTag, efid, sfi, tagNumberWithinParentTag)); |
| break; |
| } |
| tagNumberWithinParentTag++; |
| } while(tlv.nextObject()); |
| } |
| } |
| |
| private void log(String msg) { |
| if(DBG) Rlog.d(LOG_TAG, msg); |
| } |
| } |