1 /* 2 * Copyright (C) 2021 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.uicc; 18 19 import android.annotation.RequiresFeature; 20 import android.content.Context; 21 import android.os.AsyncResult; 22 import android.os.Handler; 23 import android.os.Message; 24 import android.telephony.Rlog; 25 import android.telephony.TelephonyManager; 26 import android.text.TextUtils; 27 28 import com.android.internal.annotations.VisibleForTesting; 29 import com.android.internal.telephony.CommandsInterface; 30 import com.android.internal.telephony.RadioInterfaceCapabilityController; 31 import com.android.internal.telephony.uicc.AdnCapacity; 32 import com.android.internal.telephony.uicc.IccConstants; 33 34 import java.util.ArrayList; 35 import java.util.Collections; 36 import java.util.concurrent.atomic.AtomicBoolean; 37 import java.util.concurrent.atomic.AtomicReference; 38 import java.util.concurrent.ConcurrentSkipListMap; 39 import java.util.Iterator; 40 import java.util.List; 41 import java.util.Map; 42 import java.util.stream.Collectors; 43 44 45 /** 46 * Used to store SIM phonebook records. 47 * <p/> 48 * This will be {@link #INVALID} if either is the case: 49 * <ol> 50 * <li>The device does not support 51 * {@link android.telephony.TelephonyManager#CAPABILITY_SIM_PHONEBOOK_IN_MODEM}.</li> 52 * </ol> 53 * {@hide} 54 */ 55 @RequiresFeature( 56 enforcement = "android.telephony.TelephonyManager#isRadioInterfaceCapabilitySupported", 57 value = "TelephonyManager.CAPABILITY_SIM_PHONEBOOK_IN_MODEM") 58 public class SimPhonebookRecordCache extends Handler { 59 // Instance Variables 60 private String LOG_TAG = "SimPhonebookRecordCache"; 61 private static final boolean DBG = true; 62 63 @VisibleForTesting 64 static final boolean ENABLE_INFLATE_WITH_EMPTY_RECORDS = true; 65 // Event Constants 66 private static final int EVENT_PHONEBOOK_CHANGED = 1; 67 private static final int EVENT_PHONEBOOK_RECORDS_RECEIVED = 2; 68 private static final int EVENT_GET_PHONEBOOK_RECORDS_DONE = 3; 69 private static final int EVENT_GET_PHONEBOOK_CAPACITY_DONE = 4; 70 private static final int EVENT_UPDATE_PHONEBOOK_RECORD_DONE = 5; 71 private static final int EVENT_SIM_REFRESH = 6; 72 private static final int EVENT_GET_PHONEBOOK_RECORDS_RETRY = 7; 73 74 private static final int MAX_RETRY_COUNT = 3; 75 private static final int RETRY_INTERVAL = 3000; // 3S 76 private static final int INVALID_RECORD_ID = -1; 77 78 // member variables 79 private final CommandsInterface mCi; 80 private int mPhoneId; 81 private Context mContext; 82 83 // Presenting ADN capacity, including ADN, EMAIL ANR, and so on. 84 private AtomicReference<AdnCapacity> mAdnCapacity = new AtomicReference<AdnCapacity>(null); 85 private Object mReadLock = new Object(); 86 private final ConcurrentSkipListMap<Integer, AdnRecord> mSimPbRecords = 87 new ConcurrentSkipListMap<Integer, AdnRecord>(); 88 private final List<UpdateRequest> mUpdateRequests = 89 Collections.synchronizedList(new ArrayList<UpdateRequest>()); 90 // If true, clear the records in the cache and re-query from modem 91 private AtomicBoolean mIsCacheInvalidated = new AtomicBoolean(false); 92 private AtomicBoolean mIsRecordLoading = new AtomicBoolean(false); 93 private AtomicBoolean mIsInRetry = new AtomicBoolean(false); 94 private AtomicBoolean mIsInitialized = new AtomicBoolean(false); 95 96 // People waiting for SIM phonebook records to be loaded 97 ArrayList<Message> mAdnLoadingWaiters = new ArrayList<Message>(); 98 /** 99 * The manual update from upper layer will result in notifying SIM phonebook changed, 100 * leading to fetch the Adn capacity, then whether to need to reload phonebook records 101 * is a problem. the SIM phoneback changed shall follow by updating record done, so that 102 * uses this flag to avoid unnecessary loading. 103 */ 104 boolean mIsUpdateDone = false; 105 SimPhonebookRecordCache(Context context, int phoneId, CommandsInterface ci)106 public SimPhonebookRecordCache(Context context, int phoneId, CommandsInterface ci) { 107 mCi = ci; 108 mPhoneId = phoneId; 109 mContext = context; 110 LOG_TAG += "[" + phoneId + "]"; 111 mCi.registerForSimPhonebookChanged(this, EVENT_PHONEBOOK_CHANGED, null); 112 mCi.registerForIccRefresh(this, EVENT_SIM_REFRESH, null); 113 mCi.registerForSimPhonebookRecordsReceived(this, EVENT_PHONEBOOK_RECORDS_RECEIVED, null); 114 } 115 116 /** 117 * This is recommended to use in work thread like IccPhoneBookInterfaceManager 118 * because it can't block main thread. 119 * @return true if this feature is supported 120 */ isEnabled()121 public boolean isEnabled() { 122 boolean isEnabled = RadioInterfaceCapabilityController 123 .getInstance() 124 .getCapabilities() 125 .contains(TelephonyManager.CAPABILITY_SIM_PHONEBOOK_IN_MODEM); 126 return mIsInitialized.get() || isEnabled; 127 } 128 dispose()129 public void dispose() { 130 reset(); 131 mCi.unregisterForSimPhonebookChanged(this); 132 mCi.unregisterForIccRefresh(this); 133 mCi.unregisterForSimPhonebookRecordsReceived(this); 134 } 135 reset()136 private void reset() { 137 mAdnCapacity.set(null); 138 mSimPbRecords.clear(); 139 mIsCacheInvalidated.set(false); 140 mIsRecordLoading.set(false); 141 mIsInRetry.set(false); 142 mIsInitialized.set(false); 143 mIsUpdateDone = false; 144 } 145 sendErrorResponse(Message response, String errString)146 private void sendErrorResponse(Message response, String errString) { 147 if (response != null) { 148 Exception e = new RuntimeException(errString); 149 AsyncResult.forMessage(response).exception = e; 150 response.sendToTarget(); 151 } 152 } 153 notifyAndClearWaiters()154 private void notifyAndClearWaiters() { 155 synchronized (mReadLock) { 156 for (Message response : mAdnLoadingWaiters){ 157 if (response != null) { 158 List<AdnRecord> result = 159 new ArrayList<AdnRecord>(mSimPbRecords.values()); 160 AsyncResult.forMessage(response, result, null); 161 response.sendToTarget(); 162 } 163 } 164 mAdnLoadingWaiters.clear(); 165 } 166 } 167 sendResponsesToWaitersWithError()168 private void sendResponsesToWaitersWithError() { 169 synchronized (mReadLock) { 170 mReadLock.notify(); 171 172 for (Message response : mAdnLoadingWaiters) { 173 sendErrorResponse(response, "Query adn record failed"); 174 } 175 mAdnLoadingWaiters.clear(); 176 } 177 } 178 getSimPhonebookCapacity()179 private void getSimPhonebookCapacity() { 180 logd("Start to getSimPhonebookCapacity"); 181 mCi.getSimPhonebookCapacity(obtainMessage(EVENT_GET_PHONEBOOK_CAPACITY_DONE)); 182 } 183 getAdnCapacity()184 public AdnCapacity getAdnCapacity() { 185 return mAdnCapacity.get(); 186 } 187 fillCache()188 private void fillCache() { 189 synchronized (mReadLock) { 190 fillCacheWithoutWaiting(); 191 try { 192 mReadLock.wait(); 193 } catch (InterruptedException e) { 194 loge("Interrupted Exception in queryAdnRecord"); 195 } 196 } 197 } 198 fillCacheWithoutWaiting()199 private void fillCacheWithoutWaiting() { 200 logd("Start to queryAdnRecord"); 201 if (mIsRecordLoading.compareAndSet(false, true)) { 202 mCi.getSimPhonebookRecords(obtainMessage(EVENT_GET_PHONEBOOK_RECORDS_DONE)); 203 } else { 204 logd("The loading is ongoing"); 205 } 206 } 207 requestLoadAllPbRecords(Message response)208 public void requestLoadAllPbRecords(Message response) { 209 if (response == null && !mIsInitialized.get()) { 210 logd("Try to enforce flushing cache"); 211 fillCacheWithoutWaiting(); 212 return; 213 } 214 215 synchronized (mReadLock) { 216 mAdnLoadingWaiters.add(response); 217 final int pendingSize = mAdnLoadingWaiters.size(); 218 final boolean isCapacityInvalid = isAdnCapacityInvalid(); 219 if (isCapacityInvalid) { 220 getSimPhonebookCapacity(); 221 } 222 if (pendingSize > 1 || mIsInRetry.get() 223 || !mIsInitialized.get() || isCapacityInvalid) { 224 logd("Add to the pending list as pending size = " 225 + pendingSize + " is retrying = " + mIsInRetry.get() 226 + " IsInitialized = " + mIsInitialized.get()); 227 return; 228 } 229 } 230 if (!mIsRecordLoading.get() && !mIsInRetry.get()) { 231 logd("ADN cache has already filled in"); 232 if (!mIsCacheInvalidated.get()) { 233 notifyAndClearWaiters(); 234 return; 235 } 236 } 237 fillCache(); 238 } 239 isAdnCapacityInvalid()240 private boolean isAdnCapacityInvalid() { 241 return getAdnCapacity() == null || !getAdnCapacity().isSimValid(); 242 } 243 244 @VisibleForTesting isLoading()245 public boolean isLoading() { 246 return mIsRecordLoading.get(); 247 } 248 249 @VisibleForTesting getAdnRecords()250 public List<AdnRecord> getAdnRecords() { 251 return mSimPbRecords.values().stream().collect(Collectors.toList()); 252 } 253 254 @VisibleForTesting clear()255 public void clear() { 256 if (!ENABLE_INFLATE_WITH_EMPTY_RECORDS) { 257 mSimPbRecords.clear(); 258 } 259 } 260 notifyAdnLoadingWaiters()261 private void notifyAdnLoadingWaiters() { 262 synchronized (mReadLock) { 263 mReadLock.notify(); 264 } 265 notifyAndClearWaiters(); 266 } 267 updateSimPbAdnByRecordId(int recordId, AdnRecord newAdn, Message response)268 public void updateSimPbAdnByRecordId(int recordId, AdnRecord newAdn, Message response) { 269 if (newAdn == null) { 270 sendErrorResponse(response, "There is an invalid new Adn for update"); 271 return; 272 } 273 boolean found = mSimPbRecords.containsKey(recordId); 274 if (!found) { 275 sendErrorResponse(response, "There is an invalid old Adn for update"); 276 return; 277 } 278 updateSimPhonebookByNewAdn(recordId, newAdn, response); 279 } 280 updateSimPbAdnBySearch(AdnRecord oldAdn, AdnRecord newAdn, Message response)281 public void updateSimPbAdnBySearch(AdnRecord oldAdn, AdnRecord newAdn, Message response) { 282 if (newAdn == null) { 283 sendErrorResponse(response, "There is an invalid new Adn for update"); 284 return; 285 } 286 287 int recordId = INVALID_RECORD_ID; // The ID isn't specified by caller 288 289 if (oldAdn != null && !oldAdn.isEmpty()) { 290 for(AdnRecord adn : mSimPbRecords.values()) { 291 if (oldAdn.isEqual(adn)) { 292 recordId = adn.getRecId(); 293 break; 294 } 295 } 296 } 297 if (recordId == INVALID_RECORD_ID 298 && mAdnCapacity.get() != null && mAdnCapacity.get().isSimFull()) { 299 sendErrorResponse(response, "SIM Phonebook record is full"); 300 return; 301 } 302 303 updateSimPhonebookByNewAdn(recordId, newAdn, response); 304 } 305 updateSimPhonebookByNewAdn(int recordId, AdnRecord newAdn, Message response)306 private void updateSimPhonebookByNewAdn(int recordId, AdnRecord newAdn, Message response) { 307 logd("update sim contact for record ID = " + recordId); 308 final int updatingRecordId = recordId == INVALID_RECORD_ID ? 0 : recordId; 309 SimPhonebookRecord updateAdn = new SimPhonebookRecord.Builder() 310 .setRecordId(updatingRecordId) 311 .setAlphaTag(newAdn.getAlphaTag()) 312 .setNumber(newAdn.getNumber()) 313 .setEmails(newAdn.getEmails()) 314 .setAdditionalNumbers(newAdn.getAdditionalNumbers()) 315 .build(); 316 UpdateRequest updateRequest = new UpdateRequest(recordId, newAdn, updateAdn, response); 317 mUpdateRequests.add(updateRequest); 318 final boolean isCapacityInvalid = isAdnCapacityInvalid(); 319 if (isCapacityInvalid) { 320 getSimPhonebookCapacity(); 321 } 322 if (mIsRecordLoading.get() || mIsInRetry.get() || mUpdateRequests.size() > 1 323 || !mIsInitialized.get() || isCapacityInvalid) { 324 logd("It is pending on update as " + " mIsRecordLoading = " + mIsRecordLoading.get() 325 + " mIsInRetry = " + mIsInRetry.get() + " pending size = " 326 + mUpdateRequests.size() + " mIsInitialized = " + mIsInitialized.get()); 327 return; 328 } 329 330 updateSimPhonebook(updateRequest); 331 } 332 updateSimPhonebook(UpdateRequest request)333 private void updateSimPhonebook(UpdateRequest request) { 334 logd("update Sim phonebook"); 335 mCi.updateSimPhonebookRecord(request.phonebookRecord, 336 obtainMessage(EVENT_UPDATE_PHONEBOOK_RECORD_DONE, request)); 337 } 338 339 @Override handleMessage(Message msg)340 public void handleMessage(Message msg) { 341 AsyncResult ar; 342 switch(msg.what) { 343 case EVENT_PHONEBOOK_CHANGED: 344 logd("EVENT_PHONEBOOK_CHANGED"); 345 handlePhonebookChanged(); 346 break; 347 case EVENT_GET_PHONEBOOK_RECORDS_DONE: 348 logd("EVENT_GET_PHONEBOOK_RECORDS_DONE"); 349 ar = (AsyncResult)msg.obj; 350 if (ar != null && ar.exception != null) { 351 loge("Failed to gain phonebook records"); 352 invalidateSimPbCache(); 353 if (!mIsInRetry.get()) { 354 sendGettingPhonebookRecordsRetry(0); 355 } 356 } 357 break; 358 case EVENT_GET_PHONEBOOK_CAPACITY_DONE: 359 logd("EVENT_GET_PHONEBOOK_CAPACITY_DONE"); 360 ar = (AsyncResult)msg.obj; 361 if (ar != null && ar.exception == null) { 362 AdnCapacity capacity = (AdnCapacity)ar.result; 363 handlePhonebookCapacityChanged(capacity); 364 } else { 365 if (!isAdnCapacityInvalid()) { 366 mAdnCapacity.set(new AdnCapacity()); 367 } 368 invalidateSimPbCache(); 369 } 370 break; 371 case EVENT_PHONEBOOK_RECORDS_RECEIVED: 372 logd("EVENT_PHONEBOOK_RECORDS_RECEIVED"); 373 ar = (AsyncResult)msg.obj; 374 if (ar.exception != null) { 375 loge("Unexpected exception happened"); 376 ar.result = null; 377 } 378 379 handlePhonebookRecordReceived((ReceivedPhonebookRecords)(ar.result)); 380 break; 381 case EVENT_UPDATE_PHONEBOOK_RECORD_DONE: 382 logd("EVENT_UPDATE_PHONEBOOK_RECORD_DONE"); 383 ar = (AsyncResult)msg.obj; 384 handleUpdatePhonebookRecordDone(ar); 385 break; 386 case EVENT_SIM_REFRESH: 387 logd("EVENT_SIM_REFRESH"); 388 ar = (AsyncResult)msg.obj; 389 if (ar.exception == null) { 390 handleSimRefresh((IccRefreshResponse)ar.result); 391 } else { 392 logd("SIM refresh Exception: " + ar.exception); 393 } 394 break; 395 case EVENT_GET_PHONEBOOK_RECORDS_RETRY: 396 int retryCount = msg.arg1; 397 logd("EVENT_GET_PHONEBOOK_RECORDS_RETRY cnt = " + retryCount); 398 if (retryCount < MAX_RETRY_COUNT) { 399 mIsRecordLoading.set(false); 400 fillCacheWithoutWaiting(); 401 sendGettingPhonebookRecordsRetry(++retryCount); 402 } else { 403 responseToWaitersWithErrorOrSuccess(false); 404 } 405 break; 406 default: 407 loge("Unexpected event: " + msg.what); 408 } 409 410 } 411 responseToWaitersWithErrorOrSuccess(boolean success)412 private void responseToWaitersWithErrorOrSuccess(boolean success) { 413 logd("responseToWaitersWithErrorOrSuccess success = " + success); 414 mIsRecordLoading.set(false); 415 mIsInRetry.set(false); 416 if (success) { 417 notifyAdnLoadingWaiters(); 418 } else { 419 sendResponsesToWaitersWithError(); 420 421 } 422 tryFireUpdatePendingList(); 423 } 424 handlePhonebookChanged()425 private void handlePhonebookChanged() { 426 if (mUpdateRequests.isEmpty()) { 427 // If this event is received, means this feature is supported. 428 getSimPhonebookCapacity(); 429 } else { 430 logd("Do nothing in the midst of multiple update"); 431 } 432 } 433 handlePhonebookCapacityChanged(AdnCapacity newCapacity)434 private void handlePhonebookCapacityChanged(AdnCapacity newCapacity) { 435 AdnCapacity oldCapacity = mAdnCapacity.get(); 436 if (newCapacity == null) { 437 newCapacity = new AdnCapacity(); 438 } 439 mAdnCapacity.set(newCapacity); 440 if (oldCapacity == null && newCapacity != null) { 441 inflateWithEmptyRecords(newCapacity); 442 if (!newCapacity.isSimEmpty()){ 443 mIsCacheInvalidated.set(true); 444 fillCacheWithoutWaiting(); 445 } else if (newCapacity.isSimValid()) { 446 notifyAdnLoadingWaiters(); 447 tryFireUpdatePendingList(); 448 } else { 449 notifyAdnLoadingWaiters(); 450 logd("ADN capacity is invalid"); 451 } 452 mIsInitialized.set(true); // Let's say the whole process is ready 453 } else { 454 // There is nothing from PB, so notify waiters directly if any 455 if (newCapacity.isSimValid() && newCapacity.isSimEmpty()) { 456 mIsCacheInvalidated.set(false); 457 notifyAdnLoadingWaiters(); 458 tryFireUpdatePendingList(); 459 } else if (!newCapacity.isSimValid()) { 460 mIsCacheInvalidated.set(false); 461 notifyAdnLoadingWaiters(); 462 } else if (!mIsUpdateDone && !newCapacity.isSimEmpty()) { 463 invalidateSimPbCache(); 464 fillCacheWithoutWaiting(); 465 } 466 mIsUpdateDone = false; 467 } 468 } 469 inflateWithEmptyRecords(AdnCapacity capacity)470 private void inflateWithEmptyRecords(AdnCapacity capacity) { 471 if (ENABLE_INFLATE_WITH_EMPTY_RECORDS) { 472 logd("inflateWithEmptyRecords"); 473 if (capacity != null && mSimPbRecords.isEmpty()) { 474 for (int i = 1; i <= capacity.getMaxAdnCount(); i++) { 475 mSimPbRecords.putIfAbsent(i, 476 new AdnRecord(IccConstants.EF_ADN, i, null, null, null, null)); 477 } 478 } 479 } 480 } 481 handlePhonebookRecordReceived(ReceivedPhonebookRecords records)482 private void handlePhonebookRecordReceived(ReceivedPhonebookRecords records) { 483 if (records != null) { 484 if (records.isOk()) { 485 logd("Partial data is received"); 486 populateAdnRecords(records.getPhonebookRecords()); 487 } else if (records.isCompleted()) { 488 logd("The whole loading process is finished"); 489 populateAdnRecords(records.getPhonebookRecords()); 490 mIsRecordLoading.set(false); 491 mIsInRetry.set(false); 492 mIsCacheInvalidated.set(false); 493 notifyAdnLoadingWaiters(); 494 tryFireUpdatePendingList(); 495 } else if (records.isRetryNeeded() && !mIsInRetry.get()) { 496 logd("Start to retry as aborted"); 497 sendGettingPhonebookRecordsRetry(0); 498 } else { 499 loge("Error happened"); 500 // Let's keep the stale data, in example of SIM getting removed during loading, 501 // expects to finish the whole process. 502 responseToWaitersWithErrorOrSuccess(true); 503 } 504 } else { 505 loge("No records there"); 506 responseToWaitersWithErrorOrSuccess(true); 507 } 508 } 509 handleUpdatePhonebookRecordDone(AsyncResult ar)510 private void handleUpdatePhonebookRecordDone(AsyncResult ar) { 511 Exception e = null; 512 UpdateRequest updateRequest = (UpdateRequest)ar.userObj; 513 mIsUpdateDone = true; 514 if (ar.exception == null) { 515 int myRecordId = updateRequest.myRecordId; 516 AdnRecord adn = updateRequest.adnRecord; 517 int recordId = ((int[]) (ar.result))[0]; 518 logd("my record ID = " + myRecordId + " new record ID = " + recordId); 519 if (myRecordId == INVALID_RECORD_ID || myRecordId == recordId) { 520 if (!adn.isEmpty()) { 521 addOrChangeSimPbRecord(adn, recordId); 522 } else { 523 deleteSimPbRecord(recordId); 524 } 525 } else { 526 e = new RuntimeException("The record ID for update doesn't match"); 527 } 528 529 } else { 530 e = new RuntimeException("Update adn record failed", ar.exception); 531 } 532 533 if (mUpdateRequests.contains(updateRequest)) { 534 mUpdateRequests.remove(updateRequest); 535 updateRequest.responseResult(e); 536 } else { 537 loge("this update request isn't found"); 538 } 539 tryFireUpdatePendingList(); 540 } 541 tryFireUpdatePendingList()542 private void tryFireUpdatePendingList() { 543 if (!mUpdateRequests.isEmpty()) { 544 updateSimPhonebook(mUpdateRequests.get(0)); 545 } 546 } 547 handleSimRefresh(IccRefreshResponse iccRefreshResponse)548 private void handleSimRefresh(IccRefreshResponse iccRefreshResponse) { 549 if (iccRefreshResponse != null) { 550 if (iccRefreshResponse.refreshResult == IccRefreshResponse.REFRESH_RESULT_FILE_UPDATE 551 && (iccRefreshResponse.efId == IccConstants.EF_PBR || 552 iccRefreshResponse.efId == IccConstants.EF_ADN) || 553 iccRefreshResponse.refreshResult == IccRefreshResponse.REFRESH_RESULT_INIT) { 554 invalidateSimPbCache(); 555 getSimPhonebookCapacity(); 556 } 557 } else { 558 logd("IccRefreshResponse received is null"); 559 } 560 } 561 populateAdnRecords(List<SimPhonebookRecord> records)562 private void populateAdnRecords(List<SimPhonebookRecord> records) { 563 if (records != null) { 564 Map<Integer, AdnRecord> newRecords = records.stream().map(record -> {return 565 new AdnRecord(IccConstants.EF_ADN, 566 record.getRecordId(), 567 record.getAlphaTag(), 568 record.getNumber(), 569 record.getEmails(), 570 record.getAdditionalNumbers());}) 571 .collect(Collectors.toMap(AdnRecord::getRecId, adn -> adn)); 572 mSimPbRecords.putAll(newRecords); 573 } 574 } 575 sendGettingPhonebookRecordsRetry(int times)576 private void sendGettingPhonebookRecordsRetry (int times) { 577 if (hasMessages(EVENT_GET_PHONEBOOK_RECORDS_RETRY)) { 578 removeMessages(EVENT_GET_PHONEBOOK_RECORDS_RETRY); 579 } 580 mIsInRetry.set(true); 581 Message message = obtainMessage(EVENT_GET_PHONEBOOK_RECORDS_RETRY, 1, 0); 582 sendMessageDelayed(message, RETRY_INTERVAL); 583 } 584 addOrChangeSimPbRecord(AdnRecord record, int recordId)585 private void addOrChangeSimPbRecord(AdnRecord record, int recordId) { 586 logd("Record number for the added or changed ADN is " + recordId); 587 record.setRecId(recordId); 588 if (ENABLE_INFLATE_WITH_EMPTY_RECORDS) { 589 mSimPbRecords.replace(recordId, record); 590 } else { 591 mSimPbRecords.put(recordId, record); 592 } 593 } 594 595 deleteSimPbRecord(int recordId)596 private void deleteSimPbRecord(int recordId) { 597 logd("Record number for the deleted ADN is " + recordId); 598 if (ENABLE_INFLATE_WITH_EMPTY_RECORDS) { 599 mSimPbRecords.replace(recordId, 600 new AdnRecord(IccConstants.EF_ADN, recordId, null, null, null, null)); 601 } else { 602 if (mSimPbRecords.containsKey(recordId)) { 603 mSimPbRecords.remove(recordId); 604 } 605 } 606 } 607 invalidateSimPbCache()608 private void invalidateSimPbCache() { 609 logd("invalidateSimPbCache"); 610 mIsCacheInvalidated.set(true); 611 if (ENABLE_INFLATE_WITH_EMPTY_RECORDS) { 612 mSimPbRecords.replaceAll((k, v) -> 613 new AdnRecord(IccConstants.EF_ADN, k, null, null, null, null)); 614 } else { 615 mSimPbRecords.clear(); 616 } 617 } 618 logd(String msg)619 private void logd(String msg) { 620 if (DBG) { 621 Rlog.d(LOG_TAG, msg); 622 } 623 } 624 loge(String msg)625 private void loge(String msg) { 626 if (DBG) { 627 Rlog.e(LOG_TAG, msg); 628 } 629 } 630 631 private final static class UpdateRequest { 632 private int myRecordId; 633 private Message response; 634 private AdnRecord adnRecord; 635 private SimPhonebookRecord phonebookRecord; 636 UpdateRequest(int recordId, AdnRecord record, SimPhonebookRecord phonebookRecord, Message response)637 UpdateRequest(int recordId, AdnRecord record, SimPhonebookRecord phonebookRecord, 638 Message response) { 639 this.myRecordId = recordId; 640 this.adnRecord = record; 641 this.phonebookRecord = phonebookRecord; 642 this.response = response; 643 } 644 responseResult(Exception e)645 void responseResult(Exception e) { 646 if (response != null) { 647 AsyncResult.forMessage(response, null, e); 648 response.sendToTarget(); 649 } 650 } 651 } 652 } 653