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.compat.annotation.UnsupportedAppUsage; 20 import android.content.ContentValues; 21 import android.content.pm.PackageManager; 22 import android.os.AsyncResult; 23 import android.os.Build; 24 import android.os.Handler; 25 import android.os.Looper; 26 import android.os.Message; 27 import android.text.TextUtils; 28 29 import com.android.internal.telephony.uicc.AdnCapacity; 30 import com.android.internal.telephony.uicc.AdnRecord; 31 import com.android.internal.telephony.uicc.AdnRecordCache; 32 import com.android.internal.telephony.uicc.IccCardApplicationStatus.AppType; 33 import com.android.internal.telephony.uicc.IccConstants; 34 import com.android.internal.telephony.uicc.IccFileHandler; 35 import com.android.internal.telephony.uicc.IccRecords; 36 import com.android.internal.telephony.uicc.SimPhonebookRecordCache; 37 import com.android.internal.telephony.uicc.UiccController; 38 import com.android.internal.telephony.uicc.UiccProfile; 39 import com.android.telephony.Rlog; 40 41 import java.util.List; 42 import java.util.concurrent.atomic.AtomicBoolean; 43 44 /** 45 * IccPhoneBookInterfaceManager to provide an inter-process communication to 46 * access ADN-like SIM records. 47 */ 48 public class IccPhoneBookInterfaceManager { 49 static final String LOG_TAG = "IccPhoneBookIM"; 50 @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553) 51 protected static final boolean DBG = true; 52 53 @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553) 54 protected Phone mPhone; 55 @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553) 56 protected AdnRecordCache mAdnCache; 57 protected SimPhonebookRecordCache mSimPbRecordCache; 58 59 protected static final int EVENT_GET_SIZE_DONE = 1; 60 protected static final int EVENT_LOAD_DONE = 2; 61 protected static final int EVENT_UPDATE_DONE = 3; 62 63 private static final class Request { 64 AtomicBoolean mStatus = new AtomicBoolean(false); 65 Object mResult = null; 66 } 67 68 @UnsupportedAppUsage 69 protected Handler mBaseHandler = new Handler() { 70 @Override 71 public void handleMessage(Message msg) { 72 AsyncResult ar = (AsyncResult) msg.obj; 73 Request request = (Request) ar.userObj; 74 75 switch (msg.what) { 76 case EVENT_GET_SIZE_DONE: 77 int[] recordSize = null; 78 if (ar.exception == null) { 79 recordSize = (int[]) ar.result; 80 // recordSize[0] is the record length 81 // recordSize[1] is the total length of the EF file 82 // recordSize[2] is the number of records in the EF file 83 logd("GET_RECORD_SIZE Size " + recordSize[0] 84 + " total " + recordSize[1] 85 + " #record " + recordSize[2]); 86 } else { 87 loge("EVENT_GET_SIZE_DONE: failed; ex=" + ar.exception); 88 } 89 notifyPending(request, recordSize); 90 break; 91 case EVENT_UPDATE_DONE: 92 boolean success = (ar.exception == null); 93 if (!success) { 94 loge("EVENT_UPDATE_DONE - failed; ex=" + ar.exception); 95 } 96 notifyPending(request, success); 97 break; 98 case EVENT_LOAD_DONE: 99 List<AdnRecord> records = null; 100 if (ar.exception == null) { 101 records = (List<AdnRecord>) ar.result; 102 } else { 103 loge("EVENT_LOAD_DONE: Cannot load ADN records; ex=" 104 + ar.exception); 105 } 106 notifyPending(request, records); 107 break; 108 } 109 } 110 111 private void notifyPending(Request request, Object result) { 112 if (request != null) { 113 synchronized (request) { 114 request.mResult = result; 115 request.mStatus.set(true); 116 request.notifyAll(); 117 } 118 } 119 } 120 }; 121 IccPhoneBookInterfaceManager(Phone phone)122 public IccPhoneBookInterfaceManager(Phone phone) { 123 this.mPhone = phone; 124 IccRecords r = phone.getIccRecords(); 125 if (r != null) { 126 mAdnCache = r.getAdnCache(); 127 } 128 129 mSimPbRecordCache = new SimPhonebookRecordCache( 130 phone.getContext(), phone.getPhoneId(), phone.mCi); 131 } 132 dispose()133 public void dispose() { 134 mSimPbRecordCache.dispose(); 135 } 136 updateIccRecords(IccRecords iccRecords)137 public void updateIccRecords(IccRecords iccRecords) { 138 if (iccRecords != null) { 139 mAdnCache = iccRecords.getAdnCache(); 140 } else { 141 mAdnCache = null; 142 } 143 } 144 145 @UnsupportedAppUsage logd(String msg)146 protected void logd(String msg) { 147 Rlog.d(LOG_TAG, "[IccPbInterfaceManager] " + msg); 148 } 149 150 @UnsupportedAppUsage loge(String msg)151 protected void loge(String msg) { 152 Rlog.e(LOG_TAG, "[IccPbInterfaceManager] " + msg); 153 } 154 generateAdnRecordWithOldTagByContentValues(ContentValues values)155 private AdnRecord generateAdnRecordWithOldTagByContentValues(ContentValues values) { 156 if (values == null) { 157 return null; 158 } 159 final String oldTag = values.getAsString(IccProvider.STR_TAG); 160 final String oldPhoneNumber = values.getAsString(IccProvider.STR_NUMBER); 161 final String oldEmail = values.getAsString(IccProvider.STR_EMAILS); 162 final String oldAnr = values.getAsString(IccProvider.STR_ANRS);; 163 String[] oldEmailArray = TextUtils.isEmpty(oldEmail) 164 ? null : getEmailStringArray(oldEmail); 165 String[] oldAnrArray = TextUtils.isEmpty(oldAnr) ? null : getAnrStringArray(oldAnr); 166 return new AdnRecord(oldTag, oldPhoneNumber, oldEmailArray, oldAnrArray); 167 } 168 generateAdnRecordWithNewTagByContentValues( int efId, int recordNumber, ContentValues values)169 private AdnRecord generateAdnRecordWithNewTagByContentValues( 170 int efId, int recordNumber, ContentValues values) { 171 if (values == null) { 172 return null; 173 } 174 final String newTag = values.getAsString(IccProvider.STR_NEW_TAG); 175 final String newPhoneNumber = values.getAsString(IccProvider.STR_NEW_NUMBER); 176 final String newEmail = values.getAsString(IccProvider.STR_NEW_EMAILS); 177 final String newAnr = values.getAsString(IccProvider.STR_NEW_ANRS); 178 String[] newEmailArray = TextUtils.isEmpty(newEmail) 179 ? null : getEmailStringArray(newEmail); 180 String[] newAnrArray = TextUtils.isEmpty(newAnr) ? null : getAnrStringArray(newAnr); 181 return new AdnRecord( 182 efId, recordNumber, newTag, newPhoneNumber, newEmailArray, newAnrArray); 183 } 184 185 /** 186 * Replace oldAdn with newAdn in ADN-like record in EF 187 * 188 * getAdnRecordsInEf must be called at least once before this function, 189 * otherwise an error will be returned. 190 * throws SecurityException if no WRITE_CONTACTS permission 191 * 192 * @param efid must be one among EF_ADN, EF_FDN, and EF_SDN 193 * @param values old adn tag, phone number, email and anr to be replaced 194 * new adn tag, phone number, email and anr to be stored 195 * @param pin2 required to update EF_FDN, otherwise must be null 196 * @return true for success 197 */ updateAdnRecordsInEfBySearchForSubscriber(int efid, ContentValues values, String pin2)198 public boolean updateAdnRecordsInEfBySearchForSubscriber(int efid, ContentValues values, 199 String pin2) { 200 201 if (mPhone.getContext().checkCallingOrSelfPermission( 202 android.Manifest.permission.WRITE_CONTACTS) != PackageManager.PERMISSION_GRANTED) { 203 throw new SecurityException("Requires android.permission.WRITE_CONTACTS permission"); 204 } 205 206 efid = updateEfForIccType(efid); 207 208 if (DBG) { 209 logd("updateAdnRecordsWithContentValuesInEfBySearch: efid=" + efid + ", values = " + 210 values + ", pin2=" + pin2); 211 } 212 213 checkThread(); 214 Request updateRequest = new Request(); 215 synchronized (updateRequest) { 216 Message response = mBaseHandler.obtainMessage(EVENT_UPDATE_DONE, updateRequest); 217 AdnRecord oldAdn = generateAdnRecordWithOldTagByContentValues(values); 218 if (usesPbCache(efid)) { 219 AdnRecord newAdn = 220 generateAdnRecordWithNewTagByContentValues(IccConstants.EF_ADN, 0, values); 221 mSimPbRecordCache.updateSimPbAdnBySearch(oldAdn, newAdn, response); 222 waitForResult(updateRequest); 223 return (boolean) updateRequest.mResult; 224 } else { 225 AdnRecord newAdn = generateAdnRecordWithNewTagByContentValues(efid, 0, values); 226 if (mAdnCache != null) { 227 mAdnCache.updateAdnBySearch(efid, oldAdn, newAdn, pin2, response); 228 waitForResult(updateRequest); 229 return (boolean) updateRequest.mResult; 230 } else { 231 loge("Failure while trying to update by search due to uninitialised adncache"); 232 return false; 233 } 234 } 235 } 236 } 237 238 /** 239 * Update an ADN-like EF record by record index 240 * 241 * This is useful for iteration the whole ADN file, such as write the whole 242 * phone book or erase/format the whole phonebook. Currently the email field 243 * if set in the ADN record is ignored. 244 * throws SecurityException if no WRITE_CONTACTS permission 245 * 246 * @param efid must be one among EF_ADN, EF_FDN, and EF_SDN 247 * @param newTag adn tag to be stored 248 * @param newPhoneNumber adn number to be stored 249 * Set both newTag and newPhoneNumber to "" means to replace the old 250 * record with empty one, aka, delete old record 251 * @param index is 1-based adn record index to be updated 252 * @param pin2 required to update EF_FDN, otherwise must be null 253 * @return true for success 254 */ 255 public boolean updateAdnRecordsInEfByIndex(int efid, ContentValues values, int index, String pin2)256 updateAdnRecordsInEfByIndex(int efid, ContentValues values, int index, String pin2) { 257 258 if (mPhone.getContext().checkCallingOrSelfPermission( 259 android.Manifest.permission.WRITE_CONTACTS) 260 != PackageManager.PERMISSION_GRANTED) { 261 throw new SecurityException( 262 "Requires android.permission.WRITE_CONTACTS permission"); 263 } 264 if (DBG) { 265 logd("updateAdnRecordsInEfByIndex: efid=" + efid + ", values = " + 266 values + " index=" + index + ", pin2=" + pin2); 267 } 268 269 checkThread(); 270 Request updateRequest = new Request(); 271 synchronized (updateRequest) { 272 Message response = mBaseHandler.obtainMessage(EVENT_UPDATE_DONE, updateRequest); 273 if (usesPbCache(efid)) { 274 AdnRecord newAdn = 275 generateAdnRecordWithNewTagByContentValues(IccConstants.EF_ADN, 276 index, values); 277 mSimPbRecordCache.updateSimPbAdnByRecordId(index, newAdn, response); 278 waitForResult(updateRequest); 279 return (boolean) updateRequest.mResult; 280 } else { 281 AdnRecord newAdn = generateAdnRecordWithNewTagByContentValues(efid, index, values); 282 if (mAdnCache != null) { 283 mAdnCache.updateAdnByIndex(efid, newAdn, index, pin2, response); 284 waitForResult(updateRequest); 285 return (boolean) updateRequest.mResult; 286 } else { 287 loge("Failure while trying to update by index due to uninitialised adncache"); 288 return false; 289 } 290 } 291 } 292 } 293 294 /** 295 * Get the capacity of records in efid 296 * 297 * @param efid the EF id of a ADN-like ICC 298 * @return int[3] array 299 * recordSizes[0] is the single record length 300 * recordSizes[1] is the total length of the EF file 301 * recordSizes[2] is the number of records in the EF file 302 */ getAdnRecordsSize(int efid)303 public int[] getAdnRecordsSize(int efid) { 304 if (DBG) logd("getAdnRecordsSize: efid=" + efid); 305 checkThread(); 306 Request getSizeRequest = new Request(); 307 synchronized (getSizeRequest) { 308 //Using mBaseHandler, no difference in EVENT_GET_SIZE_DONE handling 309 Message response = mBaseHandler.obtainMessage(EVENT_GET_SIZE_DONE, getSizeRequest); 310 IccFileHandler fh = mPhone.getIccFileHandler(); 311 if (fh != null) { 312 fh.getEFLinearRecordSize(efid, response); 313 waitForResult(getSizeRequest); 314 } 315 } 316 317 return getSizeRequest.mResult == null ? new int[3] : (int[]) getSizeRequest.mResult; 318 } 319 320 321 /** 322 * Loads the AdnRecords in efid and returns them as a 323 * List of AdnRecords 324 * 325 * throws SecurityException if no READ_CONTACTS permission 326 * 327 * @param efid the EF id of a ADN-like ICC 328 * @return List of AdnRecord 329 */ getAdnRecordsInEf(int efid)330 public List<AdnRecord> getAdnRecordsInEf(int efid) { 331 332 if (mPhone.getContext().checkCallingOrSelfPermission( 333 android.Manifest.permission.READ_CONTACTS) 334 != PackageManager.PERMISSION_GRANTED) { 335 throw new SecurityException( 336 "Requires android.permission.READ_CONTACTS permission"); 337 } 338 339 efid = updateEfForIccType(efid); 340 if (DBG) logd("getAdnRecordsInEF: efid=0x" + Integer.toHexString(efid).toUpperCase()); 341 342 checkThread(); 343 Request loadRequest = new Request(); 344 synchronized (loadRequest) { 345 Message response = mBaseHandler.obtainMessage(EVENT_LOAD_DONE, loadRequest); 346 if (usesPbCache(efid)) { 347 mSimPbRecordCache.requestLoadAllPbRecords(response); 348 waitForResult(loadRequest); 349 return (List<AdnRecord>) loadRequest.mResult; 350 } else { 351 if (mAdnCache != null) { 352 mAdnCache.requestLoadAllAdnLike(efid, 353 mAdnCache.extensionEfForEf(efid), response); 354 waitForResult(loadRequest); 355 return (List<AdnRecord>) loadRequest.mResult; 356 } else { 357 loge("Failure while trying to load from SIM due to uninitialised adncache"); 358 return null; 359 } 360 } 361 } 362 } 363 364 @UnsupportedAppUsage checkThread()365 protected void checkThread() { 366 // Make sure this isn't the UI thread, since it will block 367 if (mBaseHandler.getLooper().equals(Looper.myLooper())) { 368 loge("query() called on the main UI thread!"); 369 throw new IllegalStateException( 370 "You cannot call query on this provder from the main UI thread."); 371 } 372 } 373 waitForResult(Request request)374 protected void waitForResult(Request request) { 375 synchronized (request) { 376 while (!request.mStatus.get()) { 377 try { 378 request.wait(); 379 } catch (InterruptedException e) { 380 logd("interrupted while trying to update by search"); 381 } 382 } 383 } 384 } 385 386 @UnsupportedAppUsage updateEfForIccType(int efid)387 private int updateEfForIccType(int efid) { 388 // Check if we are trying to read ADN records 389 if (efid == IccConstants.EF_ADN) { 390 if (mPhone.getCurrentUiccAppType() == AppType.APPTYPE_USIM) { 391 return IccConstants.EF_PBR; 392 } 393 } 394 return efid; 395 } 396 getEmailStringArray(String str)397 private String[] getEmailStringArray(String str) { 398 return str != null ? str.split(",") : null; 399 } 400 getAnrStringArray(String str)401 private String[] getAnrStringArray(String str) { 402 return str != null ? str.split(":") : null; 403 } 404 405 /** 406 * Get the capacity of ADN records 407 * 408 * @return AdnCapacity 409 */ getAdnRecordsCapacity()410 public AdnCapacity getAdnRecordsCapacity() { 411 if (DBG) logd("getAdnRecordsCapacity" ); 412 if (mPhone.getContext().checkCallingOrSelfPermission( 413 android.Manifest.permission.READ_CONTACTS) 414 != PackageManager.PERMISSION_GRANTED) { 415 throw new SecurityException( 416 "Requires android.permission.READ_CONTACTS permission"); 417 } 418 int phoneId = mPhone.getPhoneId(); 419 420 UiccProfile profile = UiccController.getInstance().getUiccProfileForPhone(phoneId); 421 422 if (profile != null) { 423 IccCardConstants.State cardstate = profile.getState(); 424 if (cardstate == IccCardConstants.State.READY 425 || cardstate == IccCardConstants.State.LOADED) { 426 checkThread(); 427 AdnCapacity capacity = mSimPbRecordCache.isEnabled() 428 ? mSimPbRecordCache.getAdnCapacity() : null; 429 if (capacity == null) { 430 loge("Adn capacity is null"); 431 return null; 432 } 433 434 if (DBG) logd("getAdnRecordsCapacity on slot " + phoneId 435 + ": max adn=" + capacity.getMaxAdnCount() 436 + ", used adn=" + capacity.getUsedAdnCount() 437 + ", max email=" + capacity.getMaxEmailCount() 438 + ", used email=" + capacity.getUsedEmailCount() 439 + ", max anr=" + capacity.getMaxAnrCount() 440 + ", used anr=" + capacity.getUsedAnrCount() 441 + ", max name length="+ capacity.getMaxNameLength() 442 + ", max number length =" + capacity.getMaxNumberLength() 443 + ", max email length =" + capacity.getMaxEmailLength() 444 + ", max anr length =" + capacity.getMaxAnrLength()); 445 return capacity; 446 } else { 447 logd("No UICC when getAdnRecordsCapacity."); 448 } 449 } else { 450 logd("sim state is not ready when getAdnRecordsCapacity."); 451 } 452 return null; 453 } 454 usesPbCache(int efid)455 private boolean usesPbCache(int efid) { 456 return mSimPbRecordCache.isEnabled() && 457 (efid == IccConstants.EF_PBR || efid == IccConstants.EF_ADN); 458 } 459 } 460