/* * 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; import android.compat.annotation.UnsupportedAppUsage; import android.content.ContentValues; import android.content.pm.PackageManager; import android.os.AsyncResult; import android.os.Build; import android.os.Handler; import android.os.Looper; import android.os.Message; import android.text.TextUtils; import com.android.internal.telephony.uicc.AdnCapacity; import com.android.internal.telephony.uicc.AdnRecord; import com.android.internal.telephony.uicc.AdnRecordCache; import com.android.internal.telephony.uicc.IccCardApplicationStatus.AppType; import com.android.internal.telephony.uicc.IccConstants; import com.android.internal.telephony.uicc.IccFileHandler; import com.android.internal.telephony.uicc.IccRecords; import com.android.internal.telephony.uicc.SimPhonebookRecordCache; import com.android.internal.telephony.uicc.UiccController; import com.android.internal.telephony.uicc.UiccProfile; import com.android.telephony.Rlog; import java.util.List; import java.util.Locale; import java.util.concurrent.atomic.AtomicBoolean; /** * IccPhoneBookInterfaceManager to provide an inter-process communication to * access ADN-like SIM records. */ public class IccPhoneBookInterfaceManager { static final String LOG_TAG = "IccPhoneBookIM"; @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553) protected static final boolean DBG = true; @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553) protected Phone mPhone; @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553) protected AdnRecordCache mAdnCache; protected SimPhonebookRecordCache mSimPbRecordCache; protected static final int EVENT_GET_SIZE_DONE = 1; protected static final int EVENT_LOAD_DONE = 2; protected static final int EVENT_UPDATE_DONE = 3; private static final class Request { AtomicBoolean mStatus = new AtomicBoolean(false); Object mResult = null; } @UnsupportedAppUsage protected Handler mBaseHandler = new Handler() { @Override public void handleMessage(Message msg) { AsyncResult ar = (AsyncResult) msg.obj; Request request = (Request) ar.userObj; switch (msg.what) { case EVENT_GET_SIZE_DONE: int[] recordSize = null; if (ar.exception == null) { recordSize = (int[]) ar.result; // recordSize[0] is the record length // recordSize[1] is the total length of the EF file // recordSize[2] is the number of records in the EF file logd("GET_RECORD_SIZE Size " + recordSize[0] + " total " + recordSize[1] + " #record " + recordSize[2]); } else { loge("EVENT_GET_SIZE_DONE: failed; ex=" + ar.exception); } notifyPending(request, recordSize); break; case EVENT_UPDATE_DONE: boolean success = (ar.exception == null); if (!success) { loge("EVENT_UPDATE_DONE - failed; ex=" + ar.exception); } notifyPending(request, success); break; case EVENT_LOAD_DONE: List records = null; if (ar.exception == null) { records = (List) ar.result; } else { loge("EVENT_LOAD_DONE: Cannot load ADN records; ex=" + ar.exception); } notifyPending(request, records); break; } } private void notifyPending(Request request, Object result) { if (request != null) { synchronized (request) { request.mResult = result; request.mStatus.set(true); request.notifyAll(); } } } }; public IccPhoneBookInterfaceManager(Phone phone) { this.mPhone = phone; IccRecords r = phone.getIccRecords(); if (r != null) { mAdnCache = r.getAdnCache(); } mSimPbRecordCache = new SimPhonebookRecordCache( phone.getContext(), phone.getPhoneId(), phone.mCi); } public void dispose() { mSimPbRecordCache.dispose(); } public void updateIccRecords(IccRecords iccRecords) { if (iccRecords != null) { mAdnCache = iccRecords.getAdnCache(); } else { mAdnCache = null; } } @UnsupportedAppUsage protected void logd(String msg) { Rlog.d(LOG_TAG, "[IccPbInterfaceManager] " + msg); } @UnsupportedAppUsage protected void loge(String msg) { Rlog.e(LOG_TAG, "[IccPbInterfaceManager] " + msg); } private AdnRecord generateAdnRecordWithOldTagByContentValues(ContentValues values) { if (values == null) { return null; } final String oldTag = values.getAsString(IccProvider.STR_TAG); final String oldPhoneNumber = values.getAsString(IccProvider.STR_NUMBER); final String oldEmail = values.getAsString(IccProvider.STR_EMAILS); final String oldAnr = values.getAsString(IccProvider.STR_ANRS);; String[] oldEmailArray = TextUtils.isEmpty(oldEmail) ? null : getEmailStringArray(oldEmail); String[] oldAnrArray = TextUtils.isEmpty(oldAnr) ? null : getAnrStringArray(oldAnr); return new AdnRecord(oldTag, oldPhoneNumber, oldEmailArray, oldAnrArray); } private AdnRecord generateAdnRecordWithNewTagByContentValues( int efId, int recordNumber, ContentValues values) { if (values == null) { return null; } final String newTag = values.getAsString(IccProvider.STR_NEW_TAG); final String newPhoneNumber = values.getAsString(IccProvider.STR_NEW_NUMBER); final String newEmail = values.getAsString(IccProvider.STR_NEW_EMAILS); final String newAnr = values.getAsString(IccProvider.STR_NEW_ANRS); String[] newEmailArray = TextUtils.isEmpty(newEmail) ? null : getEmailStringArray(newEmail); String[] newAnrArray = TextUtils.isEmpty(newAnr) ? null : getAnrStringArray(newAnr); return new AdnRecord( efId, recordNumber, newTag, newPhoneNumber, newEmailArray, newAnrArray); } /** * Replace oldAdn with newAdn in ADN-like record in EF * * getAdnRecordsInEf must be called at least once before this function, * otherwise an error will be returned. * throws SecurityException if no WRITE_CONTACTS permission * * @param efid must be one among EF_ADN, EF_FDN, and EF_SDN * @param values old adn tag, phone number, email and anr to be replaced * new adn tag, phone number, email and anr to be stored * @param pin2 required to update EF_FDN, otherwise must be null * @return true for success */ public boolean updateAdnRecordsInEfBySearchForSubscriber(int efid, ContentValues values, String pin2) { if (mPhone.getContext().checkCallingOrSelfPermission( android.Manifest.permission.WRITE_CONTACTS) != PackageManager.PERMISSION_GRANTED) { throw new SecurityException("Requires android.permission.WRITE_CONTACTS permission"); } efid = updateEfForIccType(efid); if (DBG) { logd("updateAdnRecordsWithContentValuesInEfBySearch: efid=" + efid + ", values = " + values + ", pin2=" + pin2); } checkThread(); Request updateRequest = new Request(); synchronized (updateRequest) { Message response = mBaseHandler.obtainMessage(EVENT_UPDATE_DONE, updateRequest); AdnRecord oldAdn = generateAdnRecordWithOldTagByContentValues(values); if (usesPbCache(efid)) { AdnRecord newAdn = generateAdnRecordWithNewTagByContentValues(IccConstants.EF_ADN, 0, values); mSimPbRecordCache.updateSimPbAdnBySearch(oldAdn, newAdn, response); waitForResult(updateRequest); return (boolean) updateRequest.mResult; } else { AdnRecord newAdn = generateAdnRecordWithNewTagByContentValues(efid, 0, values); if (mAdnCache != null) { mAdnCache.updateAdnBySearch(efid, oldAdn, newAdn, pin2, response); waitForResult(updateRequest); return (boolean) updateRequest.mResult; } else { loge("Failure while trying to update by search due to uninitialised adncache"); return false; } } } } /** * Update an ADN-like EF record by record index * * This is useful for iteration the whole ADN file, such as write the whole * phone book or erase/format the whole phonebook. Currently the email field * if set in the ADN record is ignored. * throws SecurityException if no WRITE_CONTACTS permission * * @param efid must be one among EF_ADN, EF_FDN, and EF_SDN * @param newTag adn tag to be stored * @param newPhoneNumber adn number to be stored * Set both newTag and newPhoneNumber to "" means to replace the old * record with empty one, aka, delete old record * @param index is 1-based adn record index to be updated * @param pin2 required to update EF_FDN, otherwise must be null * @return true for success */ public boolean updateAdnRecordsInEfByIndex(int efid, ContentValues values, int index, String pin2) { if (mPhone.getContext().checkCallingOrSelfPermission( android.Manifest.permission.WRITE_CONTACTS) != PackageManager.PERMISSION_GRANTED) { throw new SecurityException( "Requires android.permission.WRITE_CONTACTS permission"); } if (DBG) { logd("updateAdnRecordsInEfByIndex: efid=" + efid + ", values = " + values + " index=" + index + ", pin2=" + pin2); } checkThread(); Request updateRequest = new Request(); synchronized (updateRequest) { Message response = mBaseHandler.obtainMessage(EVENT_UPDATE_DONE, updateRequest); if (usesPbCache(efid)) { AdnRecord newAdn = generateAdnRecordWithNewTagByContentValues(IccConstants.EF_ADN, index, values); mSimPbRecordCache.updateSimPbAdnByRecordId(index, newAdn, response); waitForResult(updateRequest); return (boolean) updateRequest.mResult; } else { AdnRecord newAdn = generateAdnRecordWithNewTagByContentValues(efid, index, values); if (mAdnCache != null) { mAdnCache.updateAdnByIndex(efid, newAdn, index, pin2, response); waitForResult(updateRequest); return (boolean) updateRequest.mResult; } else { loge("Failure while trying to update by index due to uninitialised adncache"); return false; } } } } /** * Get the capacity of records in efid * * @param efid the EF id of a ADN-like ICC * @return int[3] array * recordSizes[0] is the single record length * recordSizes[1] is the total length of the EF file * recordSizes[2] is the number of records in the EF file */ public int[] getAdnRecordsSize(int efid) { if (DBG) logd("getAdnRecordsSize: efid=" + efid); checkThread(); Request getSizeRequest = new Request(); synchronized (getSizeRequest) { //Using mBaseHandler, no difference in EVENT_GET_SIZE_DONE handling Message response = mBaseHandler.obtainMessage(EVENT_GET_SIZE_DONE, getSizeRequest); IccFileHandler fh = mPhone.getIccFileHandler(); if (fh != null) { fh.getEFLinearRecordSize(efid, response); waitForResult(getSizeRequest); } } return getSizeRequest.mResult == null ? new int[3] : (int[]) getSizeRequest.mResult; } /** * Loads the AdnRecords in efid and returns them as a * List of AdnRecords * * throws SecurityException if no READ_CONTACTS permission * * @param efid the EF id of a ADN-like ICC * @return List of AdnRecord */ public List getAdnRecordsInEf(int efid) { if (mPhone.getContext().checkCallingOrSelfPermission( android.Manifest.permission.READ_CONTACTS) != PackageManager.PERMISSION_GRANTED) { throw new SecurityException( "Requires android.permission.READ_CONTACTS permission"); } efid = updateEfForIccType(efid); if (DBG) { logd("getAdnRecordsInEF: efid=0x" + Integer.toHexString(efid) .toUpperCase(Locale.ROOT)); } checkThread(); Request loadRequest = new Request(); synchronized (loadRequest) { Message response = mBaseHandler.obtainMessage(EVENT_LOAD_DONE, loadRequest); if (usesPbCache(efid)) { mSimPbRecordCache.requestLoadAllPbRecords(response); waitForResult(loadRequest); return (List) loadRequest.mResult; } else { if (mAdnCache != null) { mAdnCache.requestLoadAllAdnLike(efid, mAdnCache.extensionEfForEf(efid), response); waitForResult(loadRequest); return (List) loadRequest.mResult; } else { loge("Failure while trying to load from SIM due to uninitialised adncache"); return null; } } } } @UnsupportedAppUsage protected void checkThread() { // Make sure this isn't the UI thread, since it will block if (mBaseHandler.getLooper().equals(Looper.myLooper())) { loge("query() called on the main UI thread!"); throw new IllegalStateException( "You cannot call query on this provder from the main UI thread."); } } protected void waitForResult(Request request) { synchronized (request) { while (!request.mStatus.get()) { try { request.wait(); } catch (InterruptedException e) { logd("interrupted while trying to update by search"); } } } } @UnsupportedAppUsage private int updateEfForIccType(int efid) { // Check if we are trying to read ADN records if (efid == IccConstants.EF_ADN) { if (mPhone.getCurrentUiccAppType() == AppType.APPTYPE_USIM) { return IccConstants.EF_PBR; } } return efid; } private String[] getEmailStringArray(String str) { return str != null ? str.split(",") : null; } private String[] getAnrStringArray(String str) { return str != null ? str.split(":") : null; } /** * Get the capacity of ADN records * * @return AdnCapacity */ public AdnCapacity getAdnRecordsCapacity() { if (DBG) logd("getAdnRecordsCapacity" ); if (mPhone.getContext().checkCallingOrSelfPermission( android.Manifest.permission.READ_CONTACTS) != PackageManager.PERMISSION_GRANTED) { throw new SecurityException( "Requires android.permission.READ_CONTACTS permission"); } int phoneId = mPhone.getPhoneId(); UiccProfile profile = UiccController.getInstance().getUiccProfileForPhone(phoneId); if (profile != null) { IccCardConstants.State cardstate = profile.getState(); if (cardstate == IccCardConstants.State.READY || cardstate == IccCardConstants.State.LOADED) { checkThread(); AdnCapacity capacity = mSimPbRecordCache.isEnabled() ? mSimPbRecordCache.getAdnCapacity() : null; if (capacity == null) { loge("Adn capacity is null"); return null; } if (DBG) logd("getAdnRecordsCapacity on slot " + phoneId + ": max adn=" + capacity.getMaxAdnCount() + ", used adn=" + capacity.getUsedAdnCount() + ", max email=" + capacity.getMaxEmailCount() + ", used email=" + capacity.getUsedEmailCount() + ", max anr=" + capacity.getMaxAnrCount() + ", used anr=" + capacity.getUsedAnrCount() + ", max name length="+ capacity.getMaxNameLength() + ", max number length =" + capacity.getMaxNumberLength() + ", max email length =" + capacity.getMaxEmailLength() + ", max anr length =" + capacity.getMaxAnrLength()); return capacity; } else { logd("No UICC when getAdnRecordsCapacity."); } } else { logd("sim state is not ready when getAdnRecordsCapacity."); } return null; } private boolean usesPbCache(int efid) { return mSimPbRecordCache.isEnabled() && (efid == IccConstants.EF_PBR || efid == IccConstants.EF_ADN); } }