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