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