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; 18 19 import android.compat.annotation.UnsupportedAppUsage; 20 import android.content.pm.PackageManager; 21 import android.os.AsyncResult; 22 import android.os.Handler; 23 import android.os.Looper; 24 import android.os.Message; 25 26 import com.android.internal.telephony.uicc.AdnRecord; 27 import com.android.internal.telephony.uicc.AdnRecordCache; 28 import com.android.internal.telephony.uicc.IccCardApplicationStatus.AppType; 29 import com.android.internal.telephony.uicc.IccConstants; 30 import com.android.internal.telephony.uicc.IccFileHandler; 31 import com.android.internal.telephony.uicc.IccRecords; 32 import com.android.telephony.Rlog; 33 34 import java.util.List; 35 import java.util.concurrent.atomic.AtomicBoolean; 36 37 /** 38 * SimPhoneBookInterfaceManager to provide an inter-process communication to 39 * access ADN-like SIM records. 40 */ 41 public class IccPhoneBookInterfaceManager { 42 static final String LOG_TAG = "IccPhoneBookIM"; 43 @UnsupportedAppUsage 44 protected static final boolean DBG = true; 45 46 @UnsupportedAppUsage 47 protected Phone mPhone; 48 @UnsupportedAppUsage 49 protected AdnRecordCache mAdnCache; 50 51 protected static final int EVENT_GET_SIZE_DONE = 1; 52 protected static final int EVENT_LOAD_DONE = 2; 53 protected static final int EVENT_UPDATE_DONE = 3; 54 55 private static final class Request { 56 AtomicBoolean mStatus = new AtomicBoolean(false); 57 Object mResult = null; 58 } 59 60 @UnsupportedAppUsage 61 protected Handler mBaseHandler = new Handler() { 62 @Override 63 public void handleMessage(Message msg) { 64 AsyncResult ar = (AsyncResult) msg.obj; 65 Request request = (Request) ar.userObj; 66 67 switch (msg.what) { 68 case EVENT_GET_SIZE_DONE: 69 int[] recordSize = null; 70 if (ar.exception == null) { 71 recordSize = (int[]) ar.result; 72 // recordSize[0] is the record length 73 // recordSize[1] is the total length of the EF file 74 // recordSize[2] is the number of records in the EF file 75 logd("GET_RECORD_SIZE Size " + recordSize[0] 76 + " total " + recordSize[1] 77 + " #record " + recordSize[2]); 78 } else { 79 loge("EVENT_GET_SIZE_DONE: failed; ex=" + ar.exception); 80 } 81 notifyPending(request, recordSize); 82 break; 83 case EVENT_UPDATE_DONE: 84 boolean success = (ar.exception == null); 85 if (!success) { 86 loge("EVENT_UPDATE_DONE - failed; ex=" + ar.exception); 87 } 88 notifyPending(request, success); 89 break; 90 case EVENT_LOAD_DONE: 91 List<AdnRecord> records = null; 92 if (ar.exception == null) { 93 records = (List<AdnRecord>) ar.result; 94 } else { 95 loge("EVENT_LOAD_DONE: Cannot load ADN records; ex=" 96 + ar.exception); 97 } 98 notifyPending(request, records); 99 break; 100 } 101 } 102 103 private void notifyPending(Request request, Object result) { 104 if (request != null) { 105 synchronized (request) { 106 request.mResult = result; 107 request.mStatus.set(true); 108 request.notifyAll(); 109 } 110 } 111 } 112 }; 113 IccPhoneBookInterfaceManager(Phone phone)114 public IccPhoneBookInterfaceManager(Phone phone) { 115 this.mPhone = phone; 116 IccRecords r = phone.getIccRecords(); 117 if (r != null) { 118 mAdnCache = r.getAdnCache(); 119 } 120 } 121 dispose()122 public void dispose() { 123 } 124 updateIccRecords(IccRecords iccRecords)125 public void updateIccRecords(IccRecords iccRecords) { 126 if (iccRecords != null) { 127 mAdnCache = iccRecords.getAdnCache(); 128 } else { 129 mAdnCache = null; 130 } 131 } 132 133 @UnsupportedAppUsage logd(String msg)134 protected void logd(String msg) { 135 Rlog.d(LOG_TAG, "[IccPbInterfaceManager] " + msg); 136 } 137 138 @UnsupportedAppUsage loge(String msg)139 protected void loge(String msg) { 140 Rlog.e(LOG_TAG, "[IccPbInterfaceManager] " + msg); 141 } 142 143 /** 144 * Replace oldAdn with newAdn in ADN-like record in EF 145 * 146 * getAdnRecordsInEf must be called at least once before this function, 147 * otherwise an error will be returned. Currently the email field 148 * if set in the ADN record is ignored. 149 * throws SecurityException if no WRITE_CONTACTS permission 150 * 151 * @param efid must be one among EF_ADN, EF_FDN, and EF_SDN 152 * @param oldTag adn tag to be replaced 153 * @param oldPhoneNumber adn number to be replaced 154 * Set both oldTag and oldPhoneNubmer to "" means to replace an 155 * empty record, aka, insert new record 156 * @param newTag adn tag to be stored 157 * @param newPhoneNumber adn number ot be stored 158 * Set both newTag and newPhoneNubmer to "" means to replace the old 159 * record with empty one, aka, delete old record 160 * @param pin2 required to update EF_FDN, otherwise must be null 161 * @return true for success 162 */ 163 public boolean updateAdnRecordsInEfBySearch(int efid, String oldTag, String oldPhoneNumber, String newTag, String newPhoneNumber, String pin2)164 updateAdnRecordsInEfBySearch (int efid, 165 String oldTag, String oldPhoneNumber, 166 String newTag, String newPhoneNumber, String pin2) { 167 168 169 if (mPhone.getContext().checkCallingOrSelfPermission( 170 android.Manifest.permission.WRITE_CONTACTS) 171 != PackageManager.PERMISSION_GRANTED) { 172 throw new SecurityException( 173 "Requires android.permission.WRITE_CONTACTS permission"); 174 } 175 176 177 if (DBG) logd("updateAdnRecordsInEfBySearch: efid=0x" + 178 Integer.toHexString(efid).toUpperCase() + " ("+ Rlog.pii(LOG_TAG, oldTag) + "," + 179 Rlog.pii(LOG_TAG, oldPhoneNumber) + ")" + "==>" + " ("+ Rlog.pii(LOG_TAG, newTag) + 180 "," + Rlog.pii(LOG_TAG, newPhoneNumber) + ")"+ " pin2=" + Rlog.pii(LOG_TAG, pin2)); 181 182 efid = updateEfForIccType(efid); 183 184 checkThread(); 185 Request updateRequest = new Request(); 186 synchronized (updateRequest) { 187 Message response = mBaseHandler.obtainMessage(EVENT_UPDATE_DONE, updateRequest); 188 AdnRecord oldAdn = new AdnRecord(oldTag, oldPhoneNumber); 189 AdnRecord newAdn = new AdnRecord(newTag, newPhoneNumber); 190 if (mAdnCache != null) { 191 mAdnCache.updateAdnBySearch(efid, oldAdn, newAdn, pin2, response); 192 waitForResult(updateRequest); 193 } else { 194 loge("Failure while trying to update by search due to uninitialised adncache"); 195 } 196 } 197 return (boolean) updateRequest.mResult; 198 } 199 200 /** 201 * Update an ADN-like EF record by record index 202 * 203 * This is useful for iteration the whole ADN file, such as write the whole 204 * phone book or erase/format the whole phonebook. Currently the email field 205 * if set in the ADN record is ignored. 206 * throws SecurityException if no WRITE_CONTACTS permission 207 * 208 * @param efid must be one among EF_ADN, EF_FDN, and EF_SDN 209 * @param newTag adn tag to be stored 210 * @param newPhoneNumber adn number to be stored 211 * Set both newTag and newPhoneNubmer to "" means to replace the old 212 * record with empty one, aka, delete old record 213 * @param index is 1-based adn record index to be updated 214 * @param pin2 required to update EF_FDN, otherwise must be null 215 * @return true for success 216 */ 217 public boolean updateAdnRecordsInEfByIndex(int efid, String newTag, String newPhoneNumber, int index, String pin2)218 updateAdnRecordsInEfByIndex(int efid, String newTag, 219 String newPhoneNumber, int index, String pin2) { 220 221 if (mPhone.getContext().checkCallingOrSelfPermission( 222 android.Manifest.permission.WRITE_CONTACTS) 223 != PackageManager.PERMISSION_GRANTED) { 224 throw new SecurityException( 225 "Requires android.permission.WRITE_CONTACTS permission"); 226 } 227 228 if (DBG) logd("updateAdnRecordsInEfByIndex: efid=0x" + 229 Integer.toHexString(efid).toUpperCase() + " Index=" + index + " ==> " + "(" + 230 Rlog.pii(LOG_TAG, newTag) + "," + Rlog.pii(LOG_TAG, newPhoneNumber) + ")" + 231 " pin2=" + Rlog.pii(LOG_TAG, pin2)); 232 233 234 checkThread(); 235 Request updateRequest = new Request(); 236 synchronized (updateRequest) { 237 Message response = mBaseHandler.obtainMessage(EVENT_UPDATE_DONE, updateRequest); 238 AdnRecord newAdn = new AdnRecord(newTag, newPhoneNumber); 239 if (mAdnCache != null) { 240 mAdnCache.updateAdnByIndex(efid, newAdn, index, pin2, response); 241 waitForResult(updateRequest); 242 } else { 243 loge("Failure while trying to update by index due to uninitialised adncache"); 244 } 245 } 246 return (boolean) updateRequest.mResult; 247 } 248 249 /** 250 * Get the capacity of records in efid 251 * 252 * @param efid the EF id of a ADN-like ICC 253 * @return int[3] array 254 * recordSizes[0] is the single record length 255 * recordSizes[1] is the total length of the EF file 256 * recordSizes[2] is the number of records in the EF file 257 */ getAdnRecordsSize(int efid)258 public int[] getAdnRecordsSize(int efid) { 259 if (DBG) logd("getAdnRecordsSize: efid=" + efid); 260 checkThread(); 261 Request getSizeRequest = new Request(); 262 synchronized (getSizeRequest) { 263 //Using mBaseHandler, no difference in EVENT_GET_SIZE_DONE handling 264 Message response = mBaseHandler.obtainMessage(EVENT_GET_SIZE_DONE, getSizeRequest); 265 IccFileHandler fh = mPhone.getIccFileHandler(); 266 if (fh != null) { 267 fh.getEFLinearRecordSize(efid, response); 268 waitForResult(getSizeRequest); 269 } 270 } 271 272 return getSizeRequest.mResult == null ? new int[3] : (int[]) getSizeRequest.mResult; 273 } 274 275 276 /** 277 * Loads the AdnRecords in efid and returns them as a 278 * List of AdnRecords 279 * 280 * throws SecurityException if no READ_CONTACTS permission 281 * 282 * @param efid the EF id of a ADN-like ICC 283 * @return List of AdnRecord 284 */ getAdnRecordsInEf(int efid)285 public List<AdnRecord> getAdnRecordsInEf(int efid) { 286 287 if (mPhone.getContext().checkCallingOrSelfPermission( 288 android.Manifest.permission.READ_CONTACTS) 289 != PackageManager.PERMISSION_GRANTED) { 290 throw new SecurityException( 291 "Requires android.permission.READ_CONTACTS permission"); 292 } 293 294 efid = updateEfForIccType(efid); 295 if (DBG) logd("getAdnRecordsInEF: efid=0x" + Integer.toHexString(efid).toUpperCase()); 296 297 checkThread(); 298 Request loadRequest = new Request(); 299 synchronized (loadRequest) { 300 Message response = mBaseHandler.obtainMessage(EVENT_LOAD_DONE, loadRequest); 301 if (mAdnCache != null) { 302 mAdnCache.requestLoadAllAdnLike(efid, mAdnCache.extensionEfForEf(efid), response); 303 waitForResult(loadRequest); 304 } else { 305 loge("Failure while trying to load from SIM due to uninitialised adncache"); 306 } 307 } 308 return (List<AdnRecord>) loadRequest.mResult; 309 } 310 311 @UnsupportedAppUsage checkThread()312 protected void checkThread() { 313 // Make sure this isn't the UI thread, since it will block 314 if (mBaseHandler.getLooper().equals(Looper.myLooper())) { 315 loge("query() called on the main UI thread!"); 316 throw new IllegalStateException( 317 "You cannot call query on this provder from the main UI thread."); 318 } 319 } 320 waitForResult(Request request)321 protected void waitForResult(Request request) { 322 synchronized (request) { 323 while (!request.mStatus.get()) { 324 try { 325 request.wait(); 326 } catch (InterruptedException e) { 327 logd("interrupted while trying to update by search"); 328 } 329 } 330 } 331 } 332 333 @UnsupportedAppUsage updateEfForIccType(int efid)334 private int updateEfForIccType(int efid) { 335 // Check if we are trying to read ADN records 336 if (efid == IccConstants.EF_ADN) { 337 if (mPhone.getCurrentUiccAppType() == AppType.APPTYPE_USIM) { 338 return IccConstants.EF_PBR; 339 } 340 } 341 return efid; 342 } 343 } 344 345