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