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
17package com.android.internal.telephony.uicc;
18
19import android.os.AsyncResult;
20import android.os.Handler;
21import android.os.Message;
22import android.util.SparseArray;
23
24import com.android.internal.telephony.gsm.UsimPhoneBookManager;
25
26import java.util.ArrayList;
27import java.util.Iterator;
28
29/**
30 * {@hide}
31 */
32public 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
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     */
68    public void reset() {
69        mAdnLikeFiles.clear();
70        mUsimPhoneBookManager.reset();
71
72        clearWaiters();
73        clearUserWriters();
74
75    }
76
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
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>
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     */
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
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     */
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     */
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
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
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
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