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(ContentValues values)169 private AdnRecord generateAdnRecordWithNewTagByContentValues(ContentValues values) { 170 return generateAdnRecordWithNewTagByContentValues(0, 0, values); 171 } 172 generateAdnRecordWithNewTagByContentValues( int efId, int recordNumber, ContentValues values)173 private AdnRecord generateAdnRecordWithNewTagByContentValues( 174 int efId, int recordNumber, ContentValues values) { 175 if (values == null) { 176 return null; 177 } 178 final String newTag = values.getAsString(IccProvider.STR_NEW_TAG); 179 final String newPhoneNumber = values.getAsString(IccProvider.STR_NEW_NUMBER); 180 final String newEmail = values.getAsString(IccProvider.STR_NEW_EMAILS); 181 final String newAnr = values.getAsString(IccProvider.STR_NEW_ANRS); 182 String[] newEmailArray = TextUtils.isEmpty(newEmail) 183 ? null : getEmailStringArray(newEmail); 184 String[] newAnrArray = TextUtils.isEmpty(newAnr) ? null : getAnrStringArray(newAnr); 185 return new AdnRecord( 186 efId, recordNumber, newTag, newPhoneNumber, newEmailArray, newAnrArray); 187 } 188 189 /** 190 * Replace oldAdn with newAdn in ADN-like record in EF 191 * 192 * getAdnRecordsInEf must be called at least once before this function, 193 * otherwise an error will be returned. 194 * throws SecurityException if no WRITE_CONTACTS permission 195 * 196 * @param efid must be one among EF_ADN, EF_FDN, and EF_SDN 197 * @param values old adn tag, phone number, email and anr to be replaced 198 * new adn tag, phone number, email and anr to be stored 199 * @param pin2 required to update EF_FDN, otherwise must be null 200 * @return true for success 201 */ updateAdnRecordsInEfBySearchForSubscriber(int efid, ContentValues values, String pin2)202 public boolean updateAdnRecordsInEfBySearchForSubscriber(int efid, ContentValues values, 203 String pin2) { 204 205 if (mPhone.getContext().checkCallingOrSelfPermission( 206 android.Manifest.permission.WRITE_CONTACTS) != PackageManager.PERMISSION_GRANTED) { 207 throw new SecurityException("Requires android.permission.WRITE_CONTACTS permission"); 208 } 209 210 efid = updateEfForIccType(efid); 211 212 if (DBG) { 213 logd("updateAdnRecordsWithContentValuesInEfBySearch: efid=" + efid + ", values = " + 214 values + ", pin2=" + pin2); 215 } 216 217 checkThread(); 218 Request updateRequest = new Request(); 219 synchronized (updateRequest) { 220 Message response = mBaseHandler.obtainMessage(EVENT_UPDATE_DONE, updateRequest); 221 AdnRecord oldAdn = generateAdnRecordWithOldTagByContentValues(values); 222 AdnRecord newAdn = generateAdnRecordWithNewTagByContentValues(values); 223 if (usesPbCache(efid)) { 224 mSimPbRecordCache.updateSimPbAdnBySearch(oldAdn, newAdn, response); 225 waitForResult(updateRequest); 226 return (boolean) updateRequest.mResult; 227 } else { 228 if (mAdnCache != null) { 229 mAdnCache.updateAdnBySearch(efid, oldAdn, newAdn, pin2, response); 230 waitForResult(updateRequest); 231 return (boolean) updateRequest.mResult; 232 } else { 233 loge("Failure while trying to update by search due to uninitialised adncache"); 234 return false; 235 } 236 } 237 } 238 } 239 240 /** 241 * Update an ADN-like EF record by record index 242 * 243 * This is useful for iteration the whole ADN file, such as write the whole 244 * phone book or erase/format the whole phonebook. Currently the email field 245 * if set in the ADN record is ignored. 246 * throws SecurityException if no WRITE_CONTACTS permission 247 * 248 * @param efid must be one among EF_ADN, EF_FDN, and EF_SDN 249 * @param newTag adn tag to be stored 250 * @param newPhoneNumber adn number to be stored 251 * Set both newTag and newPhoneNumber to "" means to replace the old 252 * record with empty one, aka, delete old record 253 * @param index is 1-based adn record index to be updated 254 * @param pin2 required to update EF_FDN, otherwise must be null 255 * @return true for success 256 */ 257 public boolean updateAdnRecordsInEfByIndex(int efid, ContentValues values, int index, String pin2)258 updateAdnRecordsInEfByIndex(int efid, ContentValues values, int index, String pin2) { 259 260 if (mPhone.getContext().checkCallingOrSelfPermission( 261 android.Manifest.permission.WRITE_CONTACTS) 262 != PackageManager.PERMISSION_GRANTED) { 263 throw new SecurityException( 264 "Requires android.permission.WRITE_CONTACTS permission"); 265 } 266 if (DBG) { 267 logd("updateAdnRecordsInEfByIndex: efid=" + efid + ", values = " + 268 values + " index=" + index + ", pin2=" + pin2); 269 } 270 271 checkThread(); 272 Request updateRequest = new Request(); 273 synchronized (updateRequest) { 274 Message response = mBaseHandler.obtainMessage(EVENT_UPDATE_DONE, updateRequest); 275 AdnRecord newAdn = generateAdnRecordWithNewTagByContentValues(efid, index, values); 276 if (usesPbCache(efid)) { 277 mSimPbRecordCache.updateSimPbAdnByRecordId(index, newAdn, response); 278 waitForResult(updateRequest); 279 return (boolean) updateRequest.mResult; 280 } else { 281 if (mAdnCache != null) { 282 mAdnCache.updateAdnByIndex(efid, newAdn, index, pin2, response); 283 waitForResult(updateRequest); 284 return (boolean) updateRequest.mResult; 285 } else { 286 loge("Failure while trying to update by index due to uninitialised adncache"); 287 return false; 288 } 289 } 290 } 291 } 292 293 /** 294 * Get the capacity of records in efid 295 * 296 * @param efid the EF id of a ADN-like ICC 297 * @return int[3] array 298 * recordSizes[0] is the single record length 299 * recordSizes[1] is the total length of the EF file 300 * recordSizes[2] is the number of records in the EF file 301 */ getAdnRecordsSize(int efid)302 public int[] getAdnRecordsSize(int efid) { 303 if (DBG) logd("getAdnRecordsSize: efid=" + efid); 304 checkThread(); 305 Request getSizeRequest = new Request(); 306 synchronized (getSizeRequest) { 307 //Using mBaseHandler, no difference in EVENT_GET_SIZE_DONE handling 308 Message response = mBaseHandler.obtainMessage(EVENT_GET_SIZE_DONE, getSizeRequest); 309 IccFileHandler fh = mPhone.getIccFileHandler(); 310 if (fh != null) { 311 fh.getEFLinearRecordSize(efid, response); 312 waitForResult(getSizeRequest); 313 } 314 } 315 316 return getSizeRequest.mResult == null ? new int[3] : (int[]) getSizeRequest.mResult; 317 } 318 319 320 /** 321 * Loads the AdnRecords in efid and returns them as a 322 * List of AdnRecords 323 * 324 * throws SecurityException if no READ_CONTACTS permission 325 * 326 * @param efid the EF id of a ADN-like ICC 327 * @return List of AdnRecord 328 */ getAdnRecordsInEf(int efid)329 public List<AdnRecord> getAdnRecordsInEf(int efid) { 330 331 if (mPhone.getContext().checkCallingOrSelfPermission( 332 android.Manifest.permission.READ_CONTACTS) 333 != PackageManager.PERMISSION_GRANTED) { 334 throw new SecurityException( 335 "Requires android.permission.READ_CONTACTS permission"); 336 } 337 338 efid = updateEfForIccType(efid); 339 if (DBG) logd("getAdnRecordsInEF: efid=0x" + Integer.toHexString(efid).toUpperCase()); 340 341 checkThread(); 342 Request loadRequest = new Request(); 343 synchronized (loadRequest) { 344 Message response = mBaseHandler.obtainMessage(EVENT_LOAD_DONE, loadRequest); 345 if (usesPbCache(efid)) { 346 mSimPbRecordCache.requestLoadAllPbRecords(response); 347 waitForResult(loadRequest); 348 return (List<AdnRecord>) loadRequest.mResult; 349 } else { 350 if (mAdnCache != null) { 351 mAdnCache.requestLoadAllAdnLike(efid, 352 mAdnCache.extensionEfForEf(efid), response); 353 waitForResult(loadRequest); 354 return (List<AdnRecord>) loadRequest.mResult; 355 } else { 356 loge("Failure while trying to load from SIM due to uninitialised adncache"); 357 return null; 358 } 359 } 360 } 361 } 362 363 @UnsupportedAppUsage checkThread()364 protected void checkThread() { 365 // Make sure this isn't the UI thread, since it will block 366 if (mBaseHandler.getLooper().equals(Looper.myLooper())) { 367 loge("query() called on the main UI thread!"); 368 throw new IllegalStateException( 369 "You cannot call query on this provder from the main UI thread."); 370 } 371 } 372 waitForResult(Request request)373 protected void waitForResult(Request request) { 374 synchronized (request) { 375 while (!request.mStatus.get()) { 376 try { 377 request.wait(); 378 } catch (InterruptedException e) { 379 logd("interrupted while trying to update by search"); 380 } 381 } 382 } 383 } 384 385 @UnsupportedAppUsage updateEfForIccType(int efid)386 private int updateEfForIccType(int efid) { 387 // Check if we are trying to read ADN records 388 if (efid == IccConstants.EF_ADN) { 389 if (mPhone.getCurrentUiccAppType() == AppType.APPTYPE_USIM) { 390 return IccConstants.EF_PBR; 391 } 392 } 393 return efid; 394 } 395 getEmailStringArray(String str)396 private String[] getEmailStringArray(String str) { 397 return str != null ? str.split(",") : null; 398 } 399 getAnrStringArray(String str)400 private String[] getAnrStringArray(String str) { 401 return str != null ? str.split(":") : null; 402 } 403 404 /** 405 * Get the capacity of ADN records 406 * 407 * @return AdnCapacity 408 */ getAdnRecordsCapacity()409 public AdnCapacity getAdnRecordsCapacity() { 410 if (DBG) logd("getAdnRecordsCapacity" ); 411 if (mPhone.getContext().checkCallingOrSelfPermission( 412 android.Manifest.permission.READ_CONTACTS) 413 != PackageManager.PERMISSION_GRANTED) { 414 throw new SecurityException( 415 "Requires android.permission.READ_CONTACTS permission"); 416 } 417 int phoneId = mPhone.getPhoneId(); 418 419 UiccProfile profile = UiccController.getInstance().getUiccProfileForPhone(phoneId); 420 421 if (profile != null) { 422 IccCardConstants.State cardstate = profile.getState(); 423 if (cardstate == IccCardConstants.State.READY 424 || cardstate == IccCardConstants.State.LOADED) { 425 checkThread(); 426 AdnCapacity capacity = mSimPbRecordCache.isEnabled() 427 ? mSimPbRecordCache.getAdnCapacity() : null; 428 if (capacity == null) { 429 loge("Adn capacity is null"); 430 return null; 431 } 432 433 if (DBG) logd("getAdnRecordsCapacity on slot " + phoneId 434 + ": max adn=" + capacity.getMaxAdnCount() 435 + ", used adn=" + capacity.getUsedAdnCount() 436 + ", max email=" + capacity.getMaxEmailCount() 437 + ", used email=" + capacity.getUsedEmailCount() 438 + ", max anr=" + capacity.getMaxAnrCount() 439 + ", used anr=" + capacity.getUsedAnrCount() 440 + ", max name length="+ capacity.getMaxNameLength() 441 + ", max number length =" + capacity.getMaxNumberLength() 442 + ", max email length =" + capacity.getMaxEmailLength() 443 + ", max anr length =" + capacity.getMaxAnrLength()); 444 return capacity; 445 } else { 446 logd("No UICC when getAdnRecordsCapacity."); 447 } 448 } else { 449 logd("sim state is not ready when getAdnRecordsCapacity."); 450 } 451 return null; 452 } 453 usesPbCache(int efid)454 private boolean usesPbCache(int efid) { 455 return mSimPbRecordCache.isEnabled() && 456 (efid == IccConstants.EF_PBR || efid == IccConstants.EF_ADN); 457 } 458 } 459