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