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 final 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:" + efid);
146             return;
147         }
148 
149         Message pendingResponse = mUserWriteResponse.get(efid);
150         if (pendingResponse != null) {
151             sendErrorResponse(response, "Have pending update for EF:" + efid);
152             return;
153         }
154 
155         mUserWriteResponse.put(efid, response);
156 
157         new AdnRecordLoader(mFh).updateEF(adn, efid, extensionEF,
158                 recordIndex, pin2,
159                 obtainMessage(EVENT_UPDATE_ADN_DONE, efid, recordIndex, adn));
160     }
161 
162     /**
163      * Replace oldAdn with newAdn in ADN-like record in EF
164      *
165      * The ADN-like records must be read through requestLoadAllAdnLike() before
166      *
167      * @param efid must be one of EF_ADN, EF_FDN, and EF_SDN
168      * @param oldAdn is the adn to be replaced
169      *        If oldAdn.isEmpty() is ture, it insert the newAdn
170      * @param newAdn is the adn to be stored
171      *        If newAdn.isEmpty() is true, it delete the oldAdn
172      * @param pin2 is required to update EF_FDN, otherwise must be null
173      * @param response message to be posted when done
174      *        response.exception hold the exception in error
175      */
updateAdnBySearch(int efid, AdnRecord oldAdn, AdnRecord newAdn, String pin2, Message response)176     public void updateAdnBySearch(int efid, AdnRecord oldAdn, AdnRecord newAdn,
177             String pin2, Message response) {
178 
179         int extensionEF;
180         extensionEF = extensionEfForEf(efid);
181 
182         if (extensionEF < 0) {
183             sendErrorResponse(response, "EF is not known ADN-like EF:" + efid);
184             return;
185         }
186 
187         ArrayList<AdnRecord>  oldAdnList;
188 
189         if (efid == EF_PBR) {
190             oldAdnList = mUsimPhoneBookManager.loadEfFilesFromUsim();
191         } else {
192             oldAdnList = getRecordsIfLoaded(efid);
193         }
194 
195         if (oldAdnList == null) {
196             sendErrorResponse(response, "Adn list not exist for EF:" + efid);
197             return;
198         }
199 
200         int index = -1;
201         int count = 1;
202         for (Iterator<AdnRecord> it = oldAdnList.iterator(); it.hasNext(); ) {
203             if (oldAdn.isEqual(it.next())) {
204                 index = count;
205                 break;
206             }
207             count++;
208         }
209 
210         if (index == -1) {
211             sendErrorResponse(response, "Adn record don't exist for " + oldAdn);
212             return;
213         }
214 
215         if (efid == EF_PBR) {
216             AdnRecord foundAdn = oldAdnList.get(index-1);
217             efid = foundAdn.mEfid;
218             extensionEF = foundAdn.mExtRecord;
219             index = foundAdn.mRecordNumber;
220 
221             newAdn.mEfid = efid;
222             newAdn.mExtRecord = extensionEF;
223             newAdn.mRecordNumber = index;
224         }
225 
226         Message pendingResponse = mUserWriteResponse.get(efid);
227 
228         if (pendingResponse != null) {
229             sendErrorResponse(response, "Have pending update for EF:" + efid);
230             return;
231         }
232 
233         mUserWriteResponse.put(efid, response);
234 
235         new AdnRecordLoader(mFh).updateEF(newAdn, efid, extensionEF,
236                 index, pin2,
237                 obtainMessage(EVENT_UPDATE_ADN_DONE, efid, index, newAdn));
238     }
239 
240 
241     /**
242      * Responds with exception (in response) if efid is not a known ADN-like
243      * record
244      */
245     public void
requestLoadAllAdnLike(int efid, int extensionEf, Message response)246     requestLoadAllAdnLike (int efid, int extensionEf, Message response) {
247         ArrayList<Message> waiters;
248         ArrayList<AdnRecord> result;
249 
250         if (efid == EF_PBR) {
251             result = mUsimPhoneBookManager.loadEfFilesFromUsim();
252         } else {
253             result = getRecordsIfLoaded(efid);
254         }
255 
256         // Have we already loaded this efid?
257         if (result != null) {
258             if (response != null) {
259                 AsyncResult.forMessage(response).result = result;
260                 response.sendToTarget();
261             }
262 
263             return;
264         }
265 
266         // Have we already *started* loading this efid?
267 
268         waiters = mAdnLikeWaiters.get(efid);
269 
270         if (waiters != null) {
271             // There's a pending request for this EF already
272             // just add ourselves to it
273 
274             waiters.add(response);
275             return;
276         }
277 
278         // Start loading efid
279 
280         waiters = new ArrayList<Message>();
281         waiters.add(response);
282 
283         mAdnLikeWaiters.put(efid, waiters);
284 
285 
286         if (extensionEf < 0) {
287             // respond with error if not known ADN-like record
288 
289             if (response != null) {
290                 AsyncResult.forMessage(response).exception
291                     = new RuntimeException("EF is not known ADN-like EF:" + efid);
292                 response.sendToTarget();
293             }
294 
295             return;
296         }
297 
298         new AdnRecordLoader(mFh).loadAllFromEF(efid, extensionEf,
299             obtainMessage(EVENT_LOAD_ALL_ADN_LIKE_DONE, efid, 0));
300     }
301 
302     //***** Private methods
303 
304     private void
notifyWaiters(ArrayList<Message> waiters, AsyncResult ar)305     notifyWaiters(ArrayList<Message> waiters, AsyncResult ar) {
306 
307         if (waiters == null) {
308             return;
309         }
310 
311         for (int i = 0, s = waiters.size() ; i < s ; i++) {
312             Message waiter = waiters.get(i);
313 
314             AsyncResult.forMessage(waiter, ar.result, ar.exception);
315             waiter.sendToTarget();
316         }
317     }
318 
319     //***** Overridden from Handler
320 
321     @Override
322     public void
handleMessage(Message msg)323     handleMessage(Message msg) {
324         AsyncResult ar;
325         int efid;
326 
327         switch(msg.what) {
328             case EVENT_LOAD_ALL_ADN_LIKE_DONE:
329                 /* arg1 is efid, obj.result is ArrayList<AdnRecord>*/
330                 ar = (AsyncResult) msg.obj;
331                 efid = msg.arg1;
332                 ArrayList<Message> waiters;
333 
334                 waiters = mAdnLikeWaiters.get(efid);
335                 mAdnLikeWaiters.delete(efid);
336 
337                 if (ar.exception == null) {
338                     mAdnLikeFiles.put(efid, (ArrayList<AdnRecord>) ar.result);
339                 }
340                 notifyWaiters(waiters, ar);
341                 break;
342             case EVENT_UPDATE_ADN_DONE:
343                 ar = (AsyncResult)msg.obj;
344                 efid = msg.arg1;
345                 int index = msg.arg2;
346                 AdnRecord adn = (AdnRecord) (ar.userObj);
347 
348                 if (ar.exception == null) {
349                     mAdnLikeFiles.get(efid).set(index - 1, adn);
350                     mUsimPhoneBookManager.invalidateCache();
351                 }
352 
353                 Message response = mUserWriteResponse.get(efid);
354                 mUserWriteResponse.delete(efid);
355 
356                 AsyncResult.forMessage(response, null, ar.exception);
357                 response.sendToTarget();
358                 break;
359         }
360 
361     }
362 
363 
364 }
365