/* * Copyright (C) 2006 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.android.internal.telephony.uicc; import android.compat.annotation.UnsupportedAppUsage; import android.os.AsyncResult; import android.os.Build; import android.os.Handler; import android.os.Message; import android.util.SparseArray; import com.android.internal.annotations.VisibleForTesting; import com.android.internal.telephony.gsm.UsimPhoneBookManager; import java.util.ArrayList; import java.util.Iterator; import java.util.Locale; /** * {@hide} */ public class AdnRecordCache extends Handler implements IccConstants { //***** Instance Variables @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553) private IccFileHandler mFh; @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553) private UsimPhoneBookManager mUsimPhoneBookManager; // Indexed by EF ID SparseArray> mAdnLikeFiles = new SparseArray>(); // People waiting for ADN-like files to be loaded @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553) SparseArray> mAdnLikeWaiters = new SparseArray>(); // People waiting for adn record to be updated @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553) SparseArray mUserWriteResponse = new SparseArray(); //***** Event Constants static final int EVENT_LOAD_ALL_ADN_LIKE_DONE = 1; static final int EVENT_UPDATE_ADN_DONE = 2; //***** Constructor AdnRecordCache(IccFileHandler fh) { mFh = fh; mUsimPhoneBookManager = new UsimPhoneBookManager(mFh, this); } public AdnRecordCache(IccFileHandler fh, UsimPhoneBookManager usimPhoneBookManager) { mFh = fh; mUsimPhoneBookManager = usimPhoneBookManager; } //***** Called from SIMRecords /** * Called from SIMRecords.onRadioNotAvailable and SIMRecords.handleSimRefresh. */ @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553) public void reset() { mAdnLikeFiles.clear(); mUsimPhoneBookManager.reset(); clearWaiters(); clearUserWriters(); } private void clearWaiters() { int size = mAdnLikeWaiters.size(); for (int i = 0; i < size; i++) { ArrayList waiters = mAdnLikeWaiters.valueAt(i); AsyncResult ar = new AsyncResult(null, null, new RuntimeException("AdnCache reset")); notifyWaiters(waiters, ar); } mAdnLikeWaiters.clear(); } private void clearUserWriters() { int size = mUserWriteResponse.size(); for (int i = 0; i < size; i++) { sendErrorResponse(mUserWriteResponse.valueAt(i), "AdnCace reset"); } mUserWriteResponse.clear(); } /** * @return List of AdnRecords for efid if we've already loaded them this * radio session, or null if we haven't */ @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553) public ArrayList getRecordsIfLoaded(int efid) { return mAdnLikeFiles.get(efid); } /** * Returns extension ef associated with ADN-like EF or -1 if * we don't know. * * See 3GPP TS 51.011 for this mapping */ @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553) public int extensionEfForEf(int efid) { switch (efid) { case EF_MBDN: return EF_EXT6; case EF_ADN: return EF_EXT1; case EF_SDN: return EF_EXT3; case EF_FDN: return EF_EXT2; case EF_MSISDN: return EF_EXT1; case EF_PBR: return 0; // The EF PBR doesn't have an extension record default: // See TS 131.102 4.4.2.1 '4FXX' are entity files from EF PBR return (0x4FFF & efid) == efid ? 0 : -1; } } @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553) private void sendErrorResponse(Message response, String errString) { if (response != null) { Exception e = new RuntimeException(errString); AsyncResult.forMessage(response).exception = e; response.sendToTarget(); } } /** * Update an ADN-like record in EF by record index * * @param efid must be one among EF_ADN, EF_FDN, and EF_SDN * @param adn is the new adn to be stored * @param recordIndex is the 1-based adn record index * @param pin2 is required to update EF_FDN, otherwise must be null * @param response message to be posted when done * response.exception hold the exception in error */ @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553) public void updateAdnByIndex(int efid, AdnRecord adn, int recordIndex, String pin2, Message response) { int extensionEF = extensionEfForEf(efid); if (extensionEF < 0) { sendErrorResponse(response, "EF is not known ADN-like EF:0x" + Integer.toHexString(efid).toUpperCase(Locale.ROOT)); return; } Message pendingResponse = mUserWriteResponse.get(efid); if (pendingResponse != null) { sendErrorResponse(response, "Have pending update for EF:0x" + Integer.toHexString(efid).toUpperCase(Locale.ROOT)); return; } mUserWriteResponse.put(efid, response); new AdnRecordLoader(mFh).updateEF(adn, efid, extensionEF, recordIndex, pin2, obtainMessage(EVENT_UPDATE_ADN_DONE, efid, recordIndex, adn)); } /** * Replace oldAdn with newAdn in ADN-like record in EF * * The ADN-like records must be read through requestLoadAllAdnLike() before * * @param efid must be one of EF_ADN, EF_FDN, and EF_SDN * @param oldAdn is the adn to be replaced * If oldAdn.isEmpty() is ture, it insert the newAdn * @param newAdn is the adn to be stored * If newAdn.isEmpty() is true, it delete the oldAdn * @param pin2 is required to update EF_FDN, otherwise must be null * @param response message to be posted when done * response.exception hold the exception in error */ public void updateAdnBySearch(int efid, AdnRecord oldAdn, AdnRecord newAdn, String pin2, Message response) { int extensionEF; extensionEF = extensionEfForEf(efid); if (extensionEF < 0) { sendErrorResponse(response, "EF is not known ADN-like EF:0x" + Integer.toHexString(efid).toUpperCase(Locale.ROOT)); return; } ArrayList oldAdnList; if (efid == EF_PBR) { oldAdnList = mUsimPhoneBookManager.loadEfFilesFromUsim(); } else { oldAdnList = getRecordsIfLoaded(efid); } if (oldAdnList == null) { sendErrorResponse(response, "Adn list not exist for EF:0x" + Integer.toHexString(efid).toUpperCase(Locale.ROOT)); return; } int index = -1; int count = 1; for (Iterator it = oldAdnList.iterator(); it.hasNext(); ) { if (oldAdn.isEqual(it.next())) { index = count; break; } count++; } if (index == -1) { sendErrorResponse(response, "Adn record don't exist for " + oldAdn); return; } if (efid == EF_PBR) { AdnRecord foundAdn = oldAdnList.get(index-1); efid = foundAdn.mEfid; extensionEF = foundAdn.mExtRecord; index = foundAdn.mRecordNumber; newAdn.mEfid = efid; newAdn.mExtRecord = extensionEF; newAdn.mRecordNumber = index; } Message pendingResponse = mUserWriteResponse.get(efid); if (pendingResponse != null) { sendErrorResponse(response, "Have pending update for EF:0x" + Integer.toHexString(efid).toUpperCase(Locale.ROOT)); return; } mUserWriteResponse.put(efid, response); new AdnRecordLoader(mFh).updateEF(newAdn, efid, extensionEF, index, pin2, obtainMessage(EVENT_UPDATE_ADN_DONE, efid, index, newAdn)); } /** * Responds with exception (in response) if efid is not a known ADN-like * record */ public void requestLoadAllAdnLike (int efid, int extensionEf, Message response) { ArrayList waiters; ArrayList result; if (efid == EF_PBR) { result = mUsimPhoneBookManager.loadEfFilesFromUsim(); } else { result = getRecordsIfLoaded(efid); } // Have we already loaded this efid? if (result != null) { if (response != null) { AsyncResult.forMessage(response).result = result; response.sendToTarget(); } return; } // Have we already *started* loading this efid? waiters = mAdnLikeWaiters.get(efid); if (waiters != null) { // There's a pending request for this EF already // just add ourselves to it waiters.add(response); return; } // Start loading efid waiters = new ArrayList(); waiters.add(response); mAdnLikeWaiters.put(efid, waiters); if (extensionEf < 0) { // respond with error if not known ADN-like record if (response != null) { AsyncResult.forMessage(response).exception = new RuntimeException("EF is not known ADN-like EF:0x" + Integer.toHexString(efid).toUpperCase(Locale.ROOT)); response.sendToTarget(); } return; } new AdnRecordLoader(mFh).loadAllFromEF(efid, extensionEf, obtainMessage(EVENT_LOAD_ALL_ADN_LIKE_DONE, efid, 0)); } //***** Private methods private void notifyWaiters(ArrayList waiters, AsyncResult ar) { if (waiters == null) { return; } for (int i = 0, s = waiters.size() ; i < s ; i++) { Message waiter = waiters.get(i); AsyncResult.forMessage(waiter, ar.result, ar.exception); waiter.sendToTarget(); } } //***** Overridden from Handler @Override public void handleMessage(Message msg) { AsyncResult ar; int efid; switch(msg.what) { case EVENT_LOAD_ALL_ADN_LIKE_DONE: /* arg1 is efid, obj.result is ArrayList*/ ar = (AsyncResult) msg.obj; efid = msg.arg1; ArrayList waiters; waiters = mAdnLikeWaiters.get(efid); mAdnLikeWaiters.delete(efid); if (ar.exception == null) { mAdnLikeFiles.put(efid, (ArrayList) ar.result); } notifyWaiters(waiters, ar); break; case EVENT_UPDATE_ADN_DONE: ar = (AsyncResult)msg.obj; efid = msg.arg1; int index = msg.arg2; AdnRecord adn = (AdnRecord) (ar.userObj); if (ar.exception == null) { mAdnLikeFiles.get(efid).set(index - 1, adn); mUsimPhoneBookManager.invalidateCache(); } Message response = mUserWriteResponse.get(efid); mUserWriteResponse.delete(efid); // response may be cleared when simrecord is reset, // so we should check if it is null. if (response != null) { AsyncResult.forMessage(response, null, ar.exception); response.sendToTarget(); } break; } } @VisibleForTesting protected void setAdnLikeWriters(int key, ArrayList waiters) { mAdnLikeWaiters.put(EF_MBDN, waiters); } @VisibleForTesting protected void setAdnLikeFiles(int key, ArrayList adnRecordList) { mAdnLikeFiles.put(EF_MBDN, adnRecordList); } @VisibleForTesting protected void setUserWriteResponse(int key, Message message) { mUserWriteResponse.put(EF_MBDN, message); } @VisibleForTesting protected UsimPhoneBookManager getUsimPhoneBookManager() { return mUsimPhoneBookManager; } }