• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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 {
446                 notifyAdnLoadingWaiters();
447             }
448             mIsInitialized.set(true); // Let's say the whole process is ready
449         } else {
450             // There is nothing from PB, so notify waiters directly if any
451             if (newCapacity.isSimEmpty()
452                     || !newCapacity.isSimValid()) {
453                 mIsCacheInvalidated.set(false);
454                 notifyAdnLoadingWaiters();
455             } else if (!mIsUpdateDone) {
456                 invalidateSimPbCache();
457                 fillCacheWithoutWaiting();
458             }
459             mIsUpdateDone = false;
460         }
461     }
462 
inflateWithEmptyRecords(AdnCapacity capacity)463     private void inflateWithEmptyRecords(AdnCapacity capacity) {
464         if (ENABLE_INFLATE_WITH_EMPTY_RECORDS) {
465             logd("inflateWithEmptyRecords");
466             if (capacity != null && mSimPbRecords.isEmpty()) {
467                 for (int i = 1; i <= capacity.getMaxAdnCount(); i++) {
468                     mSimPbRecords.putIfAbsent(i,
469                             new AdnRecord(IccConstants.EF_ADN, i, null, null, null, null));
470                 }
471             }
472         }
473     }
474 
handlePhonebookRecordReceived(ReceivedPhonebookRecords records)475     private void handlePhonebookRecordReceived(ReceivedPhonebookRecords records) {
476         if (records != null) {
477             if (records.isOk()) {
478                 logd("Partial data is received");
479                 populateAdnRecords(records.getPhonebookRecords());
480             } else if (records.isCompleted()) {
481                 logd("The whole loading process is finished");
482                 populateAdnRecords(records.getPhonebookRecords());
483                 mIsRecordLoading.set(false);
484                 mIsInRetry.set(false);
485                 mIsCacheInvalidated.set(false);
486                 notifyAdnLoadingWaiters();
487                 tryFireUpdatePendingList();
488             } else if (records.isRetryNeeded() && !mIsInRetry.get()) {
489                 logd("Start to retry as aborted");
490                 sendGettingPhonebookRecordsRetry(0);
491             } else {
492                 loge("Error happened");
493                 // Let's keep the stale data, in example of SIM getting removed during loading,
494                 // expects to finish the whole process.
495                 responseToWaitersWithErrorOrSuccess(true);
496             }
497         } else {
498             loge("No records there");
499             responseToWaitersWithErrorOrSuccess(true);
500         }
501     }
502 
handleUpdatePhonebookRecordDone(AsyncResult ar)503     private void handleUpdatePhonebookRecordDone(AsyncResult ar) {
504         Exception e = null;
505         UpdateRequest updateRequest = (UpdateRequest)ar.userObj;
506         mIsUpdateDone = true;
507         if (ar.exception == null) {
508             int myRecordId = updateRequest.myRecordId;
509             AdnRecord adn = updateRequest.adnRecord;
510             int recordId = ((int[]) (ar.result))[0];
511             logd("my record ID = " + myRecordId + " new record ID = " + recordId);
512             if (myRecordId == INVALID_RECORD_ID || myRecordId == recordId) {
513                 if (!adn.isEmpty()) {
514                     addOrChangeSimPbRecord(adn, recordId);
515                 } else {
516                     deleteSimPbRecord(recordId);
517                 }
518             } else {
519                 e = new RuntimeException("The record ID for update doesn't match");
520             }
521 
522         } else {
523             e = new RuntimeException("Update adn record failed", ar.exception);
524         }
525 
526         if (mUpdateRequests.contains(updateRequest)) {
527             mUpdateRequests.remove(updateRequest);
528             updateRequest.responseResult(e);
529         } else {
530             loge("this update request isn't found");
531         }
532         tryFireUpdatePendingList();
533     }
534 
tryFireUpdatePendingList()535     private void tryFireUpdatePendingList() {
536         if (!mUpdateRequests.isEmpty()) {
537             updateSimPhonebook(mUpdateRequests.get(0));
538         }
539     }
540 
handleSimRefresh(IccRefreshResponse iccRefreshResponse)541     private void handleSimRefresh(IccRefreshResponse iccRefreshResponse) {
542         if (iccRefreshResponse != null) {
543             if (iccRefreshResponse.refreshResult == IccRefreshResponse.REFRESH_RESULT_FILE_UPDATE
544                     && (iccRefreshResponse.efId == IccConstants.EF_PBR ||
545                     iccRefreshResponse.efId == IccConstants.EF_ADN) ||
546                     iccRefreshResponse.refreshResult == IccRefreshResponse.REFRESH_RESULT_INIT) {
547                 invalidateSimPbCache();
548                 getSimPhonebookCapacity();
549             }
550         } else {
551             logd("IccRefreshResponse received is null");
552         }
553     }
554 
populateAdnRecords(List<SimPhonebookRecord> records)555     private void populateAdnRecords(List<SimPhonebookRecord> records) {
556         if (records != null) {
557             Map<Integer, AdnRecord> newRecords = records.stream().map(record -> {return
558                     new AdnRecord(IccConstants.EF_ADN,
559                     record.getRecordId(),
560                     record.getAlphaTag(),
561                     record.getNumber(),
562                     record.getEmails(),
563                     record.getAdditionalNumbers());})
564                     .collect(Collectors.toMap(AdnRecord::getRecId, adn -> adn));
565             mSimPbRecords.putAll(newRecords);
566         }
567     }
568 
sendGettingPhonebookRecordsRetry(int times)569     private void sendGettingPhonebookRecordsRetry (int times) {
570         if (hasMessages(EVENT_GET_PHONEBOOK_RECORDS_RETRY)) {
571             removeMessages(EVENT_GET_PHONEBOOK_RECORDS_RETRY);
572         }
573         mIsInRetry.set(true);
574         Message message = obtainMessage(EVENT_GET_PHONEBOOK_RECORDS_RETRY, 1, 0);
575         sendMessageDelayed(message, RETRY_INTERVAL);
576     }
577 
addOrChangeSimPbRecord(AdnRecord record, int recordId)578     private void addOrChangeSimPbRecord(AdnRecord record, int recordId) {
579         logd("Record number for the added or changed ADN is " + recordId);
580         record.setRecId(recordId);
581         if (ENABLE_INFLATE_WITH_EMPTY_RECORDS) {
582             mSimPbRecords.replace(recordId, record);
583         } else {
584             mSimPbRecords.put(recordId, record);
585         }
586     }
587 
588 
deleteSimPbRecord(int recordId)589     private void deleteSimPbRecord(int recordId) {
590         logd("Record number for the deleted ADN is " + recordId);
591         if (ENABLE_INFLATE_WITH_EMPTY_RECORDS) {
592             mSimPbRecords.replace(recordId,
593                     new AdnRecord(IccConstants.EF_ADN, recordId, null, null, null, null));
594         } else {
595             if (mSimPbRecords.containsKey(recordId)) {
596                 mSimPbRecords.remove(recordId);
597             }
598         }
599     }
600 
invalidateSimPbCache()601     private void invalidateSimPbCache() {
602         logd("invalidateSimPbCache");
603         mIsCacheInvalidated.set(true);
604         if (ENABLE_INFLATE_WITH_EMPTY_RECORDS) {
605             mSimPbRecords.replaceAll((k, v) ->
606                     new AdnRecord(IccConstants.EF_ADN, k, null, null, null, null));
607         } else {
608             mSimPbRecords.clear();
609         }
610     }
611 
logd(String msg)612     private void logd(String msg) {
613         if (DBG) {
614             Rlog.d(LOG_TAG, msg);
615         }
616     }
617 
loge(String msg)618     private void loge(String msg) {
619         if (DBG) {
620             Rlog.e(LOG_TAG, msg);
621         }
622     }
623 
624     private final static class UpdateRequest {
625         private int myRecordId;
626         private Message response;
627         private AdnRecord adnRecord;
628         private SimPhonebookRecord phonebookRecord;
629 
UpdateRequest(int recordId, AdnRecord record, SimPhonebookRecord phonebookRecord, Message response)630         UpdateRequest(int recordId, AdnRecord record, SimPhonebookRecord phonebookRecord,
631                 Message response) {
632             this.myRecordId = recordId;
633             this.adnRecord = record;
634             this.phonebookRecord = phonebookRecord;
635             this.response = response;
636         }
637 
responseResult(Exception e)638         void responseResult(Exception e) {
639             if (response != null) {
640                 AsyncResult.forMessage(response, null, e);
641                 response.sendToTarget();
642             }
643         }
644     }
645 }
646