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