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