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