blob: 8a0276bc212c3372f62e988b2a2bac075de41fca [file] [log] [blame]
/*
* 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.content.pm.PackageManager;
import android.os.AsyncResult;
import android.os.Handler;
import android.os.Looper;
import android.os.Message;
import android.os.ServiceManager;
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.IccRecords;
import com.android.internal.telephony.uicc.UiccCard;
import com.android.internal.telephony.uicc.UiccCardApplication;
import java.util.List;
import java.util.concurrent.atomic.AtomicBoolean;
/**
* SimPhoneBookInterfaceManager to provide an inter-process communication to
* access ADN-like SIM records.
*/
public abstract class IccPhoneBookInterfaceManager {
protected static final boolean DBG = true;
protected PhoneBase mPhone;
private UiccCardApplication mCurrentApp = null;
protected AdnRecordCache mAdnCache;
protected final Object mLock = new Object();
protected int mRecordSize[];
protected boolean mSuccess;
private boolean mIs3gCard = false; // flag to determine if card is 3G or 2G
protected List<AdnRecord> mRecords;
protected static final boolean ALLOW_SIM_OP_IN_UI_THREAD = false;
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;
protected Handler mBaseHandler = new Handler() {
@Override
public void handleMessage(Message msg) {
AsyncResult ar;
switch (msg.what) {
case EVENT_GET_SIZE_DONE:
ar = (AsyncResult) msg.obj;
synchronized (mLock) {
if (ar.exception == null) {
mRecordSize = (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 " + mRecordSize[0] +
" total " + mRecordSize[1] +
" #record " + mRecordSize[2]);
}
notifyPending(ar);
}
break;
case EVENT_UPDATE_DONE:
ar = (AsyncResult) msg.obj;
synchronized (mLock) {
mSuccess = (ar.exception == null);
notifyPending(ar);
}
break;
case EVENT_LOAD_DONE:
ar = (AsyncResult)msg.obj;
synchronized (mLock) {
if (ar.exception == null) {
mRecords = (List<AdnRecord>) ar.result;
} else {
if(DBG) logd("Cannot load ADN records");
if (mRecords != null) {
mRecords.clear();
}
}
notifyPending(ar);
}
break;
}
}
private void notifyPending(AsyncResult ar) {
if (ar.userObj != null) {
AtomicBoolean status = (AtomicBoolean) ar.userObj;
status.set(true);
}
mLock.notifyAll();
}
};
public IccPhoneBookInterfaceManager(PhoneBase phone) {
this.mPhone = phone;
IccRecords r = phone.mIccRecords.get();
if (r != null) {
mAdnCache = r.getAdnCache();
}
}
public void dispose() {
}
public void updateIccRecords(IccRecords iccRecords) {
if (iccRecords != null) {
mAdnCache = iccRecords.getAdnCache();
} else {
mAdnCache = null;
}
}
protected abstract void logd(String msg);
protected abstract void loge(String msg);
/**
* 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. 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 oldTag adn tag to be replaced
* @param oldPhoneNumber adn number to be replaced
* Set both oldTag and oldPhoneNubmer to "" means to replace an
* empty record, aka, insert new record
* @param newTag adn tag to be stored
* @param newPhoneNumber adn number ot be stored
* Set both newTag and newPhoneNubmer to "" means to replace the old
* record with empty one, aka, delete old record
* @param pin2 required to update EF_FDN, otherwise must be null
* @return true for success
*/
public boolean
updateAdnRecordsInEfBySearch (int efid,
String oldTag, String oldPhoneNumber,
String newTag, String newPhoneNumber, 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("updateAdnRecordsInEfBySearch: efid=" + efid +
" ("+ oldTag + "," + oldPhoneNumber + ")"+ "==>" +
" ("+ newTag + "," + newPhoneNumber + ")"+ " pin2=" + pin2);
efid = updateEfForIccType(efid);
synchronized(mLock) {
checkThread();
mSuccess = false;
AtomicBoolean status = new AtomicBoolean(false);
Message response = mBaseHandler.obtainMessage(EVENT_UPDATE_DONE, status);
AdnRecord oldAdn = new AdnRecord(oldTag, oldPhoneNumber);
AdnRecord newAdn = new AdnRecord(newTag, newPhoneNumber);
if (mAdnCache != null) {
mAdnCache.updateAdnBySearch(efid, oldAdn, newAdn, pin2, response);
waitForResult(status);
} else {
loge("Failure while trying to update by search due to uninitialised adncache");
}
}
return mSuccess;
}
/**
* 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 newPhoneNubmer 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, String newTag,
String newPhoneNumber, 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 +
" Index=" + index + " ==> " +
"("+ newTag + "," + newPhoneNumber + ")"+ " pin2=" + pin2);
synchronized(mLock) {
checkThread();
mSuccess = false;
AtomicBoolean status = new AtomicBoolean(false);
Message response = mBaseHandler.obtainMessage(EVENT_UPDATE_DONE, status);
AdnRecord newAdn = new AdnRecord(newTag, newPhoneNumber);
if (mAdnCache != null) {
mAdnCache.updateAdnByIndex(efid, newAdn, index, pin2, response);
waitForResult(status);
} else {
loge("Failure while trying to update by index due to uninitialised adncache");
}
}
return mSuccess;
}
/**
* 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 abstract int[] getAdnRecordsSize(int efid);
/**
* 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<AdnRecord> 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=" + efid);
synchronized(mLock) {
checkThread();
AtomicBoolean status = new AtomicBoolean(false);
Message response = mBaseHandler.obtainMessage(EVENT_LOAD_DONE, status);
if (mAdnCache != null) {
mAdnCache.requestLoadAllAdnLike(efid, mAdnCache.extensionEfForEf(efid), response);
waitForResult(status);
} else {
loge("Failure while trying to load from SIM due to uninitialised adncache");
}
}
return mRecords;
}
protected void checkThread() {
if (!ALLOW_SIM_OP_IN_UI_THREAD) {
// 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(AtomicBoolean status) {
while (!status.get()) {
try {
mLock.wait();
} catch (InterruptedException e) {
logd("interrupted while trying to update by search");
}
}
}
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;
}
}