1 /* 2 * Copyright (C) 2006 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; 18 19 import android.content.pm.PackageManager; 20 import android.os.AsyncResult; 21 import android.os.Handler; 22 import android.os.Looper; 23 import android.os.Message; 24 import android.telephony.Rlog; 25 26 import com.android.internal.telephony.uicc.AdnRecord; 27 import com.android.internal.telephony.uicc.AdnRecordCache; 28 import com.android.internal.telephony.uicc.IccCardApplicationStatus.AppType; 29 import com.android.internal.telephony.uicc.IccConstants; 30 import com.android.internal.telephony.uicc.IccFileHandler; 31 import com.android.internal.telephony.uicc.IccRecords; 32 import com.android.internal.telephony.uicc.UiccCardApplication; 33 34 import java.util.List; 35 import java.util.concurrent.atomic.AtomicBoolean; 36 37 /** 38 * SimPhoneBookInterfaceManager to provide an inter-process communication to 39 * access ADN-like SIM records. 40 */ 41 public class IccPhoneBookInterfaceManager { 42 static final String LOG_TAG = "IccPhoneBookIM"; 43 protected static final boolean DBG = true; 44 45 protected Phone mPhone; 46 private UiccCardApplication mCurrentApp = null; 47 protected AdnRecordCache mAdnCache; 48 protected final Object mLock = new Object(); 49 protected int mRecordSize[]; 50 protected boolean mSuccess; 51 private boolean mIs3gCard = false; // flag to determine if card is 3G or 2G 52 protected List<AdnRecord> mRecords; 53 54 55 protected static final boolean ALLOW_SIM_OP_IN_UI_THREAD = false; 56 57 protected static final int EVENT_GET_SIZE_DONE = 1; 58 protected static final int EVENT_LOAD_DONE = 2; 59 protected static final int EVENT_UPDATE_DONE = 3; 60 61 protected Handler mBaseHandler = new Handler() { 62 @Override 63 public void handleMessage(Message msg) { 64 AsyncResult ar; 65 66 switch (msg.what) { 67 case EVENT_GET_SIZE_DONE: 68 ar = (AsyncResult) msg.obj; 69 synchronized (mLock) { 70 if (ar.exception == null) { 71 mRecordSize = (int[])ar.result; 72 // recordSize[0] is the record length 73 // recordSize[1] is the total length of the EF file 74 // recordSize[2] is the number of records in the EF file 75 logd("GET_RECORD_SIZE Size " + mRecordSize[0] + 76 " total " + mRecordSize[1] + 77 " #record " + mRecordSize[2]); 78 } 79 notifyPending(ar); 80 } 81 break; 82 case EVENT_UPDATE_DONE: 83 ar = (AsyncResult) msg.obj; 84 synchronized (mLock) { 85 mSuccess = (ar.exception == null); 86 notifyPending(ar); 87 } 88 break; 89 case EVENT_LOAD_DONE: 90 ar = (AsyncResult)msg.obj; 91 synchronized (mLock) { 92 if (ar.exception == null) { 93 mRecords = (List<AdnRecord>) ar.result; 94 } else { 95 if(DBG) logd("Cannot load ADN records"); 96 mRecords = null; 97 } 98 notifyPending(ar); 99 } 100 break; 101 } 102 } 103 104 private void notifyPending(AsyncResult ar) { 105 if (ar.userObj != null) { 106 AtomicBoolean status = (AtomicBoolean) ar.userObj; 107 status.set(true); 108 } 109 mLock.notifyAll(); 110 } 111 }; 112 IccPhoneBookInterfaceManager(Phone phone)113 public IccPhoneBookInterfaceManager(Phone phone) { 114 this.mPhone = phone; 115 IccRecords r = phone.getIccRecords(); 116 if (r != null) { 117 mAdnCache = r.getAdnCache(); 118 } 119 } 120 dispose()121 public void dispose() { 122 } 123 updateIccRecords(IccRecords iccRecords)124 public void updateIccRecords(IccRecords iccRecords) { 125 if (iccRecords != null) { 126 mAdnCache = iccRecords.getAdnCache(); 127 } else { 128 mAdnCache = null; 129 } 130 } 131 logd(String msg)132 protected void logd(String msg) { 133 Rlog.d(LOG_TAG, "[IccPbInterfaceManager] " + msg); 134 } 135 loge(String msg)136 protected void loge(String msg) { 137 Rlog.e(LOG_TAG, "[IccPbInterfaceManager] " + msg); 138 } 139 140 /** 141 * Replace oldAdn with newAdn in ADN-like record in EF 142 * 143 * getAdnRecordsInEf must be called at least once before this function, 144 * otherwise an error will be returned. Currently the email field 145 * if set in the ADN record is ignored. 146 * throws SecurityException if no WRITE_CONTACTS permission 147 * 148 * @param efid must be one among EF_ADN, EF_FDN, and EF_SDN 149 * @param oldTag adn tag to be replaced 150 * @param oldPhoneNumber adn number to be replaced 151 * Set both oldTag and oldPhoneNubmer to "" means to replace an 152 * empty record, aka, insert new record 153 * @param newTag adn tag to be stored 154 * @param newPhoneNumber adn number ot be stored 155 * Set both newTag and newPhoneNubmer to "" means to replace the old 156 * record with empty one, aka, delete old record 157 * @param pin2 required to update EF_FDN, otherwise must be null 158 * @return true for success 159 */ 160 public boolean updateAdnRecordsInEfBySearch(int efid, String oldTag, String oldPhoneNumber, String newTag, String newPhoneNumber, String pin2)161 updateAdnRecordsInEfBySearch (int efid, 162 String oldTag, String oldPhoneNumber, 163 String newTag, String newPhoneNumber, String pin2) { 164 165 166 if (mPhone.getContext().checkCallingOrSelfPermission( 167 android.Manifest.permission.WRITE_CONTACTS) 168 != PackageManager.PERMISSION_GRANTED) { 169 throw new SecurityException( 170 "Requires android.permission.WRITE_CONTACTS permission"); 171 } 172 173 174 if (DBG) logd("updateAdnRecordsInEfBySearch: efid=0x" + 175 Integer.toHexString(efid).toUpperCase() + 176 " ("+ oldTag + "," + oldPhoneNumber + ")"+ "==>" + 177 " ("+ newTag + "," + newPhoneNumber + ")"+ " pin2=" + pin2); 178 179 efid = updateEfForIccType(efid); 180 181 synchronized(mLock) { 182 checkThread(); 183 mSuccess = false; 184 AtomicBoolean status = new AtomicBoolean(false); 185 Message response = mBaseHandler.obtainMessage(EVENT_UPDATE_DONE, status); 186 AdnRecord oldAdn = new AdnRecord(oldTag, oldPhoneNumber); 187 AdnRecord newAdn = new AdnRecord(newTag, newPhoneNumber); 188 if (mAdnCache != null) { 189 mAdnCache.updateAdnBySearch(efid, oldAdn, newAdn, pin2, response); 190 waitForResult(status); 191 } else { 192 loge("Failure while trying to update by search due to uninitialised adncache"); 193 } 194 } 195 return mSuccess; 196 } 197 198 /** 199 * Update an ADN-like EF record by record index 200 * 201 * This is useful for iteration the whole ADN file, such as write the whole 202 * phone book or erase/format the whole phonebook. Currently the email field 203 * if set in the ADN record is ignored. 204 * throws SecurityException if no WRITE_CONTACTS permission 205 * 206 * @param efid must be one among EF_ADN, EF_FDN, and EF_SDN 207 * @param newTag adn tag to be stored 208 * @param newPhoneNumber adn number to be stored 209 * Set both newTag and newPhoneNubmer to "" means to replace the old 210 * record with empty one, aka, delete old record 211 * @param index is 1-based adn record index to be updated 212 * @param pin2 required to update EF_FDN, otherwise must be null 213 * @return true for success 214 */ 215 public boolean updateAdnRecordsInEfByIndex(int efid, String newTag, String newPhoneNumber, int index, String pin2)216 updateAdnRecordsInEfByIndex(int efid, String newTag, 217 String newPhoneNumber, int index, String pin2) { 218 219 if (mPhone.getContext().checkCallingOrSelfPermission( 220 android.Manifest.permission.WRITE_CONTACTS) 221 != PackageManager.PERMISSION_GRANTED) { 222 throw new SecurityException( 223 "Requires android.permission.WRITE_CONTACTS permission"); 224 } 225 226 if (DBG) logd("updateAdnRecordsInEfByIndex: efid=0x" + 227 Integer.toHexString(efid).toUpperCase() + 228 " Index=" + index + " ==> " + 229 "("+ newTag + "," + newPhoneNumber + ")"+ " pin2=" + pin2); 230 synchronized(mLock) { 231 checkThread(); 232 mSuccess = false; 233 AtomicBoolean status = new AtomicBoolean(false); 234 Message response = mBaseHandler.obtainMessage(EVENT_UPDATE_DONE, status); 235 AdnRecord newAdn = new AdnRecord(newTag, newPhoneNumber); 236 if (mAdnCache != null) { 237 mAdnCache.updateAdnByIndex(efid, newAdn, index, pin2, response); 238 waitForResult(status); 239 } else { 240 loge("Failure while trying to update by index due to uninitialised adncache"); 241 } 242 } 243 return mSuccess; 244 } 245 246 /** 247 * Get the capacity of records in efid 248 * 249 * @param efid the EF id of a ADN-like ICC 250 * @return int[3] array 251 * recordSizes[0] is the single record length 252 * recordSizes[1] is the total length of the EF file 253 * recordSizes[2] is the number of records in the EF file 254 */ getAdnRecordsSize(int efid)255 public int[] getAdnRecordsSize(int efid) { 256 if (DBG) logd("getAdnRecordsSize: efid=" + efid); 257 synchronized(mLock) { 258 checkThread(); 259 mRecordSize = new int[3]; 260 261 //Using mBaseHandler, no difference in EVENT_GET_SIZE_DONE handling 262 AtomicBoolean status = new AtomicBoolean(false); 263 Message response = mBaseHandler.obtainMessage(EVENT_GET_SIZE_DONE, status); 264 265 IccFileHandler fh = mPhone.getIccFileHandler(); 266 if (fh != null) { 267 fh.getEFLinearRecordSize(efid, response); 268 waitForResult(status); 269 } 270 } 271 272 return mRecordSize; 273 } 274 275 276 /** 277 * Loads the AdnRecords in efid and returns them as a 278 * List of AdnRecords 279 * 280 * throws SecurityException if no READ_CONTACTS permission 281 * 282 * @param efid the EF id of a ADN-like ICC 283 * @return List of AdnRecord 284 */ getAdnRecordsInEf(int efid)285 public List<AdnRecord> getAdnRecordsInEf(int efid) { 286 287 if (mPhone.getContext().checkCallingOrSelfPermission( 288 android.Manifest.permission.READ_CONTACTS) 289 != PackageManager.PERMISSION_GRANTED) { 290 throw new SecurityException( 291 "Requires android.permission.READ_CONTACTS permission"); 292 } 293 294 efid = updateEfForIccType(efid); 295 if (DBG) logd("getAdnRecordsInEF: efid=0x" + Integer.toHexString(efid).toUpperCase()); 296 297 synchronized(mLock) { 298 checkThread(); 299 AtomicBoolean status = new AtomicBoolean(false); 300 Message response = mBaseHandler.obtainMessage(EVENT_LOAD_DONE, status); 301 if (mAdnCache != null) { 302 mAdnCache.requestLoadAllAdnLike(efid, mAdnCache.extensionEfForEf(efid), response); 303 waitForResult(status); 304 } else { 305 loge("Failure while trying to load from SIM due to uninitialised adncache"); 306 } 307 } 308 return mRecords; 309 } 310 checkThread()311 protected void checkThread() { 312 if (!ALLOW_SIM_OP_IN_UI_THREAD) { 313 // Make sure this isn't the UI thread, since it will block 314 if (mBaseHandler.getLooper().equals(Looper.myLooper())) { 315 loge("query() called on the main UI thread!"); 316 throw new IllegalStateException( 317 "You cannot call query on this provder from the main UI thread."); 318 } 319 } 320 } 321 waitForResult(AtomicBoolean status)322 protected void waitForResult(AtomicBoolean status) { 323 while (!status.get()) { 324 try { 325 mLock.wait(); 326 } catch (InterruptedException e) { 327 logd("interrupted while trying to update by search"); 328 } 329 } 330 } 331 updateEfForIccType(int efid)332 private int updateEfForIccType(int efid) { 333 // Check if we are trying to read ADN records 334 if (efid == IccConstants.EF_ADN) { 335 if (mPhone.getCurrentUiccAppType() == AppType.APPTYPE_USIM) { 336 return IccConstants.EF_PBR; 337 } 338 } 339 return efid; 340 } 341 } 342 343