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