1 /*
2  * Copyright (C) 2006 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.compat.annotation.UnsupportedAppUsage;
20 import android.os.AsyncResult;
21 import android.os.Handler;
22 import android.os.Message;
23 import android.util.SparseArray;
24 
25 import com.android.internal.telephony.gsm.UsimPhoneBookManager;
26 
27 import java.util.ArrayList;
28 import java.util.Iterator;
29 
30 /**
31  * {@hide}
32  */
33 public class AdnRecordCache extends Handler implements IccConstants {
34     //***** Instance Variables
35 
36     @UnsupportedAppUsage
37     private IccFileHandler mFh;
38     @UnsupportedAppUsage
39     private UsimPhoneBookManager mUsimPhoneBookManager;
40 
41     // Indexed by EF ID
42     SparseArray<ArrayList<AdnRecord>> mAdnLikeFiles
43         = new SparseArray<ArrayList<AdnRecord>>();
44 
45     // People waiting for ADN-like files to be loaded
46     @UnsupportedAppUsage
47     SparseArray<ArrayList<Message>> mAdnLikeWaiters
48         = new SparseArray<ArrayList<Message>>();
49 
50     // People waiting for adn record to be updated
51     @UnsupportedAppUsage
52     SparseArray<Message> mUserWriteResponse = new SparseArray<Message>();
53 
54     //***** Event Constants
55 
56     static final int EVENT_LOAD_ALL_ADN_LIKE_DONE = 1;
57     static final int EVENT_UPDATE_ADN_DONE = 2;
58 
59     //***** Constructor
60 
61 
62 
AdnRecordCache(IccFileHandler fh)63     AdnRecordCache(IccFileHandler fh) {
64         mFh = fh;
65         mUsimPhoneBookManager = new UsimPhoneBookManager(mFh, this);
66     }
67 
68     //***** Called from SIMRecords
69 
70     /**
71      * Called from SIMRecords.onRadioNotAvailable and SIMRecords.handleSimRefresh.
72      */
73     @UnsupportedAppUsage
reset()74     public void reset() {
75         mAdnLikeFiles.clear();
76         mUsimPhoneBookManager.reset();
77 
78         clearWaiters();
79         clearUserWriters();
80 
81     }
82 
clearWaiters()83     private void clearWaiters() {
84         int size = mAdnLikeWaiters.size();
85         for (int i = 0; i < size; i++) {
86             ArrayList<Message> waiters = mAdnLikeWaiters.valueAt(i);
87             AsyncResult ar = new AsyncResult(null, null, new RuntimeException("AdnCache reset"));
88             notifyWaiters(waiters, ar);
89         }
90         mAdnLikeWaiters.clear();
91     }
92 
clearUserWriters()93     private void clearUserWriters() {
94         int size = mUserWriteResponse.size();
95         for (int i = 0; i < size; i++) {
96             sendErrorResponse(mUserWriteResponse.valueAt(i), "AdnCace reset");
97         }
98         mUserWriteResponse.clear();
99     }
100 
101     /**
102      * @return List of AdnRecords for efid if we've already loaded them this
103      * radio session, or null if we haven't
104      */
105     @UnsupportedAppUsage
106     public ArrayList<AdnRecord>
getRecordsIfLoaded(int efid)107     getRecordsIfLoaded(int efid) {
108         return mAdnLikeFiles.get(efid);
109     }
110 
111     /**
112      * Returns extension ef associated with ADN-like EF or -1 if
113      * we don't know.
114      *
115      * See 3GPP TS 51.011 for this mapping
116      */
117     @UnsupportedAppUsage
extensionEfForEf(int efid)118     public int extensionEfForEf(int efid) {
119         switch (efid) {
120             case EF_MBDN: return EF_EXT6;
121             case EF_ADN: return EF_EXT1;
122             case EF_SDN: return EF_EXT3;
123             case EF_FDN: return EF_EXT2;
124             case EF_MSISDN: return EF_EXT1;
125             case EF_PBR: return 0; // The EF PBR doesn't have an extension record
126             default: return -1;
127         }
128     }
129 
130     @UnsupportedAppUsage
sendErrorResponse(Message response, String errString)131     private void sendErrorResponse(Message response, String errString) {
132         if (response != null) {
133             Exception e = new RuntimeException(errString);
134             AsyncResult.forMessage(response).exception = e;
135             response.sendToTarget();
136         }
137     }
138 
139     /**
140      * Update an ADN-like record in EF by record index
141      *
142      * @param efid must be one among EF_ADN, EF_FDN, and EF_SDN
143      * @param adn is the new adn to be stored
144      * @param recordIndex is the 1-based adn record index
145      * @param pin2 is required to update EF_FDN, otherwise must be null
146      * @param response message to be posted when done
147      *        response.exception hold the exception in error
148      */
149     @UnsupportedAppUsage
updateAdnByIndex(int efid, AdnRecord adn, int recordIndex, String pin2, Message response)150     public void updateAdnByIndex(int efid, AdnRecord adn, int recordIndex, String pin2,
151             Message response) {
152 
153         int extensionEF = extensionEfForEf(efid);
154         if (extensionEF < 0) {
155             sendErrorResponse(response, "EF is not known ADN-like EF:0x" +
156                     Integer.toHexString(efid).toUpperCase());
157             return;
158         }
159 
160         Message pendingResponse = mUserWriteResponse.get(efid);
161         if (pendingResponse != null) {
162             sendErrorResponse(response, "Have pending update for EF:0x" +
163                     Integer.toHexString(efid).toUpperCase());
164             return;
165         }
166 
167         mUserWriteResponse.put(efid, response);
168 
169         new AdnRecordLoader(mFh).updateEF(adn, efid, extensionEF,
170                 recordIndex, pin2,
171                 obtainMessage(EVENT_UPDATE_ADN_DONE, efid, recordIndex, adn));
172     }
173 
174     /**
175      * Replace oldAdn with newAdn in ADN-like record in EF
176      *
177      * The ADN-like records must be read through requestLoadAllAdnLike() before
178      *
179      * @param efid must be one of EF_ADN, EF_FDN, and EF_SDN
180      * @param oldAdn is the adn to be replaced
181      *        If oldAdn.isEmpty() is ture, it insert the newAdn
182      * @param newAdn is the adn to be stored
183      *        If newAdn.isEmpty() is true, it delete the oldAdn
184      * @param pin2 is required to update EF_FDN, otherwise must be null
185      * @param response message to be posted when done
186      *        response.exception hold the exception in error
187      */
updateAdnBySearch(int efid, AdnRecord oldAdn, AdnRecord newAdn, String pin2, Message response)188     public void updateAdnBySearch(int efid, AdnRecord oldAdn, AdnRecord newAdn,
189             String pin2, Message response) {
190 
191         int extensionEF;
192         extensionEF = extensionEfForEf(efid);
193 
194         if (extensionEF < 0) {
195             sendErrorResponse(response, "EF is not known ADN-like EF:0x" +
196                     Integer.toHexString(efid).toUpperCase());
197             return;
198         }
199 
200         ArrayList<AdnRecord>  oldAdnList;
201 
202         if (efid == EF_PBR) {
203             oldAdnList = mUsimPhoneBookManager.loadEfFilesFromUsim();
204         } else {
205             oldAdnList = getRecordsIfLoaded(efid);
206         }
207 
208         if (oldAdnList == null) {
209             sendErrorResponse(response, "Adn list not exist for EF:0x" +
210                     Integer.toHexString(efid).toUpperCase());
211             return;
212         }
213 
214         int index = -1;
215         int count = 1;
216         for (Iterator<AdnRecord> it = oldAdnList.iterator(); it.hasNext(); ) {
217             if (oldAdn.isEqual(it.next())) {
218                 index = count;
219                 break;
220             }
221             count++;
222         }
223 
224         if (index == -1) {
225             sendErrorResponse(response, "Adn record don't exist for " + oldAdn);
226             return;
227         }
228 
229         if (efid == EF_PBR) {
230             AdnRecord foundAdn = oldAdnList.get(index-1);
231             efid = foundAdn.mEfid;
232             extensionEF = foundAdn.mExtRecord;
233             index = foundAdn.mRecordNumber;
234 
235             newAdn.mEfid = efid;
236             newAdn.mExtRecord = extensionEF;
237             newAdn.mRecordNumber = index;
238         }
239 
240         Message pendingResponse = mUserWriteResponse.get(efid);
241 
242         if (pendingResponse != null) {
243             sendErrorResponse(response, "Have pending update for EF:0x" +
244                     Integer.toHexString(efid).toUpperCase());
245             return;
246         }
247 
248         mUserWriteResponse.put(efid, response);
249 
250         new AdnRecordLoader(mFh).updateEF(newAdn, efid, extensionEF,
251                 index, pin2,
252                 obtainMessage(EVENT_UPDATE_ADN_DONE, efid, index, newAdn));
253     }
254 
255 
256     /**
257      * Responds with exception (in response) if efid is not a known ADN-like
258      * record
259      */
260     public void
requestLoadAllAdnLike(int efid, int extensionEf, Message response)261     requestLoadAllAdnLike (int efid, int extensionEf, Message response) {
262         ArrayList<Message> waiters;
263         ArrayList<AdnRecord> result;
264 
265         if (efid == EF_PBR) {
266             result = mUsimPhoneBookManager.loadEfFilesFromUsim();
267         } else {
268             result = getRecordsIfLoaded(efid);
269         }
270 
271         // Have we already loaded this efid?
272         if (result != null) {
273             if (response != null) {
274                 AsyncResult.forMessage(response).result = result;
275                 response.sendToTarget();
276             }
277 
278             return;
279         }
280 
281         // Have we already *started* loading this efid?
282 
283         waiters = mAdnLikeWaiters.get(efid);
284 
285         if (waiters != null) {
286             // There's a pending request for this EF already
287             // just add ourselves to it
288 
289             waiters.add(response);
290             return;
291         }
292 
293         // Start loading efid
294 
295         waiters = new ArrayList<Message>();
296         waiters.add(response);
297 
298         mAdnLikeWaiters.put(efid, waiters);
299 
300 
301         if (extensionEf < 0) {
302             // respond with error if not known ADN-like record
303 
304             if (response != null) {
305                 AsyncResult.forMessage(response).exception
306                     = new RuntimeException("EF is not known ADN-like EF:0x" +
307                         Integer.toHexString(efid).toUpperCase());
308                 response.sendToTarget();
309             }
310 
311             return;
312         }
313 
314         new AdnRecordLoader(mFh).loadAllFromEF(efid, extensionEf,
315             obtainMessage(EVENT_LOAD_ALL_ADN_LIKE_DONE, efid, 0));
316     }
317 
318     //***** Private methods
319 
320     private void
notifyWaiters(ArrayList<Message> waiters, AsyncResult ar)321     notifyWaiters(ArrayList<Message> waiters, AsyncResult ar) {
322 
323         if (waiters == null) {
324             return;
325         }
326 
327         for (int i = 0, s = waiters.size() ; i < s ; i++) {
328             Message waiter = waiters.get(i);
329 
330             AsyncResult.forMessage(waiter, ar.result, ar.exception);
331             waiter.sendToTarget();
332         }
333     }
334 
335     //***** Overridden from Handler
336 
337     @Override
338     public void
handleMessage(Message msg)339     handleMessage(Message msg) {
340         AsyncResult ar;
341         int efid;
342 
343         switch(msg.what) {
344             case EVENT_LOAD_ALL_ADN_LIKE_DONE:
345                 /* arg1 is efid, obj.result is ArrayList<AdnRecord>*/
346                 ar = (AsyncResult) msg.obj;
347                 efid = msg.arg1;
348                 ArrayList<Message> waiters;
349 
350                 waiters = mAdnLikeWaiters.get(efid);
351                 mAdnLikeWaiters.delete(efid);
352 
353                 if (ar.exception == null) {
354                     mAdnLikeFiles.put(efid, (ArrayList<AdnRecord>) ar.result);
355                 }
356                 notifyWaiters(waiters, ar);
357                 break;
358             case EVENT_UPDATE_ADN_DONE:
359                 ar = (AsyncResult)msg.obj;
360                 efid = msg.arg1;
361                 int index = msg.arg2;
362                 AdnRecord adn = (AdnRecord) (ar.userObj);
363 
364                 if (ar.exception == null) {
365                     mAdnLikeFiles.get(efid).set(index - 1, adn);
366                     mUsimPhoneBookManager.invalidateCache();
367                 }
368 
369                 Message response = mUserWriteResponse.get(efid);
370                 mUserWriteResponse.delete(efid);
371 
372                 // response may be cleared when simrecord is reset,
373                 // so we should check if it is null.
374                 if (response != null) {
375                     AsyncResult.forMessage(response, null, ar.exception);
376                     response.sendToTarget();
377                 }
378                 break;
379         }
380     }
381 }
382