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