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.ContentProvider; 21 import android.content.ContentValues; 22 import android.content.UriMatcher; 23 import android.database.Cursor; 24 import android.database.MatrixCursor; 25 import android.database.MergeCursor; 26 import android.net.Uri; 27 import android.os.RemoteException; 28 import android.os.ServiceManager; 29 import android.telephony.SubscriptionInfo; 30 import android.telephony.SubscriptionManager; 31 import android.text.TextUtils; 32 33 import com.android.internal.telephony.uicc.AdnRecord; 34 import com.android.internal.telephony.uicc.IccConstants; 35 import com.android.telephony.Rlog; 36 37 import java.util.List; 38 39 /** 40 * {@hide} 41 */ 42 public class IccProvider extends ContentProvider { 43 private static final String TAG = "IccProvider"; 44 @UnsupportedAppUsage 45 private static final boolean DBG = true; 46 47 48 @UnsupportedAppUsage 49 private static final String[] ADDRESS_BOOK_COLUMN_NAMES = new String[] { 50 "name", 51 "number", 52 "emails", 53 "_id" 54 }; 55 56 protected static final int ADN = 1; 57 protected static final int ADN_SUB = 2; 58 protected static final int FDN = 3; 59 protected static final int FDN_SUB = 4; 60 protected static final int SDN = 5; 61 protected static final int SDN_SUB = 6; 62 protected static final int ADN_ALL = 7; 63 64 protected static final String STR_TAG = "tag"; 65 protected static final String STR_NUMBER = "number"; 66 protected static final String STR_EMAILS = "emails"; 67 protected static final String STR_PIN2 = "pin2"; 68 69 private static final UriMatcher URL_MATCHER = 70 new UriMatcher(UriMatcher.NO_MATCH); 71 72 static { 73 URL_MATCHER.addURI("icc", "adn", ADN); 74 URL_MATCHER.addURI("icc", "adn/subId/#", ADN_SUB); 75 URL_MATCHER.addURI("icc", "fdn", FDN); 76 URL_MATCHER.addURI("icc", "fdn/subId/#", FDN_SUB); 77 URL_MATCHER.addURI("icc", "sdn", SDN); 78 URL_MATCHER.addURI("icc", "sdn/subId/#", SDN_SUB); 79 } 80 81 private SubscriptionManager mSubscriptionManager; 82 83 @UnsupportedAppUsage IccProvider()84 public IccProvider() { 85 } 86 87 @Override onCreate()88 public boolean onCreate() { 89 mSubscriptionManager = SubscriptionManager.from(getContext()); 90 return true; 91 } 92 93 @Override query(Uri url, String[] projection, String selection, String[] selectionArgs, String sort)94 public Cursor query(Uri url, String[] projection, String selection, 95 String[] selectionArgs, String sort) { 96 if (DBG) log("query"); 97 98 switch (URL_MATCHER.match(url)) { 99 case ADN: 100 return loadFromEf(IccConstants.EF_ADN, 101 SubscriptionManager.getDefaultSubscriptionId()); 102 103 case ADN_SUB: 104 return loadFromEf(IccConstants.EF_ADN, getRequestSubId(url)); 105 106 case FDN: 107 return loadFromEf(IccConstants.EF_FDN, 108 SubscriptionManager.getDefaultSubscriptionId()); 109 110 case FDN_SUB: 111 return loadFromEf(IccConstants.EF_FDN, getRequestSubId(url)); 112 113 case SDN: 114 return loadFromEf(IccConstants.EF_SDN, 115 SubscriptionManager.getDefaultSubscriptionId()); 116 117 case SDN_SUB: 118 return loadFromEf(IccConstants.EF_SDN, getRequestSubId(url)); 119 120 case ADN_ALL: 121 return loadAllSimContacts(IccConstants.EF_ADN); 122 123 default: 124 throw new IllegalArgumentException("Unknown URL " + url); 125 } 126 } 127 loadAllSimContacts(int efType)128 private Cursor loadAllSimContacts(int efType) { 129 Cursor [] result; 130 List<SubscriptionInfo> subInfoList = mSubscriptionManager 131 .getActiveSubscriptionInfoList(false); 132 133 if ((subInfoList == null) || (subInfoList.size() == 0)) { 134 result = new Cursor[0]; 135 } else { 136 int subIdCount = subInfoList.size(); 137 result = new Cursor[subIdCount]; 138 int subId; 139 140 for (int i = 0; i < subIdCount; i++) { 141 subId = subInfoList.get(i).getSubscriptionId(); 142 result[i] = loadFromEf(efType, subId); 143 Rlog.i(TAG,"ADN Records loaded for Subscription ::" + subId); 144 } 145 } 146 147 return new MergeCursor(result); 148 } 149 150 @Override getType(Uri url)151 public String getType(Uri url) { 152 switch (URL_MATCHER.match(url)) { 153 case ADN: 154 case ADN_SUB: 155 case FDN: 156 case FDN_SUB: 157 case SDN: 158 case SDN_SUB: 159 case ADN_ALL: 160 return "vnd.android.cursor.dir/sim-contact"; 161 162 default: 163 throw new IllegalArgumentException("Unknown URL " + url); 164 } 165 } 166 167 @Override insert(Uri url, ContentValues initialValues)168 public Uri insert(Uri url, ContentValues initialValues) { 169 Uri resultUri; 170 int efType; 171 String pin2 = null; 172 int subId; 173 174 if (DBG) log("insert"); 175 176 int match = URL_MATCHER.match(url); 177 switch (match) { 178 case ADN: 179 efType = IccConstants.EF_ADN; 180 subId = SubscriptionManager.getDefaultSubscriptionId(); 181 break; 182 183 case ADN_SUB: 184 efType = IccConstants.EF_ADN; 185 subId = getRequestSubId(url); 186 break; 187 188 case FDN: 189 efType = IccConstants.EF_FDN; 190 subId = SubscriptionManager.getDefaultSubscriptionId(); 191 pin2 = initialValues.getAsString("pin2"); 192 break; 193 194 case FDN_SUB: 195 efType = IccConstants.EF_FDN; 196 subId = getRequestSubId(url); 197 pin2 = initialValues.getAsString("pin2"); 198 break; 199 200 default: 201 throw new UnsupportedOperationException( 202 "Cannot insert into URL: " + url); 203 } 204 205 String tag = initialValues.getAsString("tag"); 206 String number = initialValues.getAsString("number"); 207 // TODO(): Read email instead of sending null. 208 boolean success = addIccRecordToEf(efType, tag, number, null, pin2, subId); 209 210 if (!success) { 211 return null; 212 } 213 214 StringBuilder buf = new StringBuilder("content://icc/"); 215 switch (match) { 216 case ADN: 217 buf.append("adn/"); 218 break; 219 220 case ADN_SUB: 221 buf.append("adn/subId/"); 222 break; 223 224 case FDN: 225 buf.append("fdn/"); 226 break; 227 228 case FDN_SUB: 229 buf.append("fdn/subId/"); 230 break; 231 } 232 233 // TODO: we need to find out the rowId for the newly added record 234 buf.append(0); 235 236 resultUri = Uri.parse(buf.toString()); 237 238 getContext().getContentResolver().notifyChange(url, null); 239 /* 240 // notify interested parties that an insertion happened 241 getContext().getContentResolver().notifyInsert( 242 resultUri, rowID, null); 243 */ 244 245 return resultUri; 246 } 247 normalizeValue(String inVal)248 private String normalizeValue(String inVal) { 249 int len = inVal.length(); 250 // If name is empty in contact return null to avoid crash. 251 if (len == 0) { 252 if (DBG) log("len of input String is 0"); 253 return inVal; 254 } 255 String retVal = inVal; 256 257 if (inVal.charAt(0) == '\'' && inVal.charAt(len-1) == '\'') { 258 retVal = inVal.substring(1, len-1); 259 } 260 261 return retVal; 262 } 263 264 @Override delete(Uri url, String where, String[] whereArgs)265 public int delete(Uri url, String where, String[] whereArgs) { 266 int efType; 267 int subId; 268 269 int match = URL_MATCHER.match(url); 270 switch (match) { 271 case ADN: 272 efType = IccConstants.EF_ADN; 273 subId = SubscriptionManager.getDefaultSubscriptionId(); 274 break; 275 276 case ADN_SUB: 277 efType = IccConstants.EF_ADN; 278 subId = getRequestSubId(url); 279 break; 280 281 case FDN: 282 efType = IccConstants.EF_FDN; 283 subId = SubscriptionManager.getDefaultSubscriptionId(); 284 break; 285 286 case FDN_SUB: 287 efType = IccConstants.EF_FDN; 288 subId = getRequestSubId(url); 289 break; 290 291 default: 292 throw new UnsupportedOperationException( 293 "Cannot insert into URL: " + url); 294 } 295 296 if (DBG) log("delete"); 297 298 // parse where clause 299 String tag = null; 300 String number = null; 301 String[] emails = null; 302 String pin2 = null; 303 304 String[] tokens = where.split(" AND "); 305 int n = tokens.length; 306 307 while (--n >= 0) { 308 String param = tokens[n]; 309 if (DBG) log("parsing '" + param + "'"); 310 311 String[] pair = param.split("=", 2); 312 313 if (pair.length != 2) { 314 Rlog.e(TAG, "resolve: bad whereClause parameter: " + param); 315 continue; 316 } 317 String key = pair[0].trim(); 318 String val = pair[1].trim(); 319 320 if (STR_TAG.equals(key)) { 321 tag = normalizeValue(val); 322 } else if (STR_NUMBER.equals(key)) { 323 number = normalizeValue(val); 324 } else if (STR_EMAILS.equals(key)) { 325 //TODO(): Email is null. 326 emails = null; 327 } else if (STR_PIN2.equals(key)) { 328 pin2 = normalizeValue(val); 329 } 330 } 331 332 if (efType == FDN && TextUtils.isEmpty(pin2)) { 333 return 0; 334 } 335 336 boolean success = deleteIccRecordFromEf(efType, tag, number, emails, pin2, subId); 337 if (!success) { 338 return 0; 339 } 340 341 getContext().getContentResolver().notifyChange(url, null); 342 return 1; 343 } 344 345 @Override update(Uri url, ContentValues values, String where, String[] whereArgs)346 public int update(Uri url, ContentValues values, String where, String[] whereArgs) { 347 String pin2 = null; 348 int efType; 349 int subId; 350 351 if (DBG) log("update"); 352 353 int match = URL_MATCHER.match(url); 354 switch (match) { 355 case ADN: 356 efType = IccConstants.EF_ADN; 357 subId = SubscriptionManager.getDefaultSubscriptionId(); 358 break; 359 360 case ADN_SUB: 361 efType = IccConstants.EF_ADN; 362 subId = getRequestSubId(url); 363 break; 364 365 case FDN: 366 efType = IccConstants.EF_FDN; 367 subId = SubscriptionManager.getDefaultSubscriptionId(); 368 pin2 = values.getAsString("pin2"); 369 break; 370 371 case FDN_SUB: 372 efType = IccConstants.EF_FDN; 373 subId = getRequestSubId(url); 374 pin2 = values.getAsString("pin2"); 375 break; 376 377 default: 378 throw new UnsupportedOperationException( 379 "Cannot insert into URL: " + url); 380 } 381 382 String tag = values.getAsString("tag"); 383 String number = values.getAsString("number"); 384 String[] emails = null; 385 String newTag = values.getAsString("newTag"); 386 String newNumber = values.getAsString("newNumber"); 387 String[] newEmails = null; 388 // TODO(): Update for email. 389 boolean success = updateIccRecordInEf(efType, tag, number, 390 newTag, newNumber, pin2, subId); 391 392 if (!success) { 393 return 0; 394 } 395 396 getContext().getContentResolver().notifyChange(url, null); 397 return 1; 398 } 399 loadFromEf(int efType, int subId)400 private MatrixCursor loadFromEf(int efType, int subId) { 401 if (DBG) log("loadFromEf: efType=0x" + 402 Integer.toHexString(efType).toUpperCase() + ", subscription=" + subId); 403 404 List<AdnRecord> adnRecords = null; 405 try { 406 IIccPhoneBook iccIpb = IIccPhoneBook.Stub.asInterface( 407 ServiceManager.getService("simphonebook")); 408 if (iccIpb != null) { 409 adnRecords = iccIpb.getAdnRecordsInEfForSubscriber(subId, efType); 410 } 411 } catch (RemoteException ex) { 412 // ignore it 413 } catch (SecurityException ex) { 414 if (DBG) log(ex.toString()); 415 } 416 417 if (adnRecords != null) { 418 // Load the results 419 final int N = adnRecords.size(); 420 final MatrixCursor cursor = new MatrixCursor(ADDRESS_BOOK_COLUMN_NAMES, N); 421 if (DBG) log("adnRecords.size=" + N); 422 for (int i = 0; i < N ; i++) { 423 loadRecord(adnRecords.get(i), cursor, i); 424 } 425 return cursor; 426 } else { 427 // No results to load 428 Rlog.w(TAG, "Cannot load ADN records"); 429 return new MatrixCursor(ADDRESS_BOOK_COLUMN_NAMES); 430 } 431 } 432 433 private boolean addIccRecordToEf(int efType, String name, String number, String[] emails, String pin2, int subId)434 addIccRecordToEf(int efType, String name, String number, String[] emails, 435 String pin2, int subId) { 436 if (DBG) log("addIccRecordToEf: efType=0x" + Integer.toHexString(efType).toUpperCase() + 437 ", name=" + Rlog.pii(TAG, name) + ", number=" + Rlog.pii(TAG, number) + 438 ", emails=" + Rlog.pii(TAG, emails) + ", subscription=" + subId); 439 440 boolean success = false; 441 442 // TODO: do we need to call getAdnRecordsInEf() before calling 443 // updateAdnRecordsInEfBySearch()? In any case, we will leave 444 // the UI level logic to fill that prereq if necessary. But 445 // hopefully, we can remove this requirement. 446 447 try { 448 IIccPhoneBook iccIpb = IIccPhoneBook.Stub.asInterface( 449 ServiceManager.getService("simphonebook")); 450 if (iccIpb != null) { 451 success = iccIpb.updateAdnRecordsInEfBySearchForSubscriber(subId, efType, 452 "", "", name, number, pin2); 453 } 454 } catch (RemoteException ex) { 455 // ignore it 456 } catch (SecurityException ex) { 457 if (DBG) log(ex.toString()); 458 } 459 if (DBG) log("addIccRecordToEf: " + success); 460 return success; 461 } 462 463 private boolean updateIccRecordInEf(int efType, String oldName, String oldNumber, String newName, String newNumber, String pin2, int subId)464 updateIccRecordInEf(int efType, String oldName, String oldNumber, 465 String newName, String newNumber, String pin2, int subId) { 466 if (DBG) log("updateIccRecordInEf: efType=0x" + Integer.toHexString(efType).toUpperCase() + 467 ", oldname=" + Rlog.pii(TAG, oldName) + ", oldnumber=" + Rlog.pii(TAG, oldNumber) + 468 ", newname=" + Rlog.pii(TAG, newName) + ", newnumber=" + Rlog.pii(TAG, newName) + 469 ", subscription=" + subId); 470 471 boolean success = false; 472 473 try { 474 IIccPhoneBook iccIpb = IIccPhoneBook.Stub.asInterface( 475 ServiceManager.getService("simphonebook")); 476 if (iccIpb != null) { 477 success = iccIpb.updateAdnRecordsInEfBySearchForSubscriber(subId, efType, oldName, 478 oldNumber, newName, newNumber, pin2); 479 } 480 } catch (RemoteException ex) { 481 // ignore it 482 } catch (SecurityException ex) { 483 if (DBG) log(ex.toString()); 484 } 485 if (DBG) log("updateIccRecordInEf: " + success); 486 return success; 487 } 488 489 deleteIccRecordFromEf(int efType, String name, String number, String[] emails, String pin2, int subId)490 private boolean deleteIccRecordFromEf(int efType, String name, String number, String[] emails, 491 String pin2, int subId) { 492 if (DBG) log("deleteIccRecordFromEf: efType=0x" + 493 Integer.toHexString(efType).toUpperCase() + ", name=" + Rlog.pii(TAG, name) + 494 ", number=" + Rlog.pii(TAG, number) + ", emails=" + Rlog.pii(TAG, emails) + 495 ", pin2=" + Rlog.pii(TAG, pin2) + ", subscription=" + subId); 496 497 boolean success = false; 498 499 try { 500 IIccPhoneBook iccIpb = IIccPhoneBook.Stub.asInterface( 501 ServiceManager.getService("simphonebook")); 502 if (iccIpb != null) { 503 success = iccIpb.updateAdnRecordsInEfBySearchForSubscriber(subId, efType, 504 name, number, "", "", pin2); 505 } 506 } catch (RemoteException ex) { 507 // ignore it 508 } catch (SecurityException ex) { 509 if (DBG) log(ex.toString()); 510 } 511 if (DBG) log("deleteIccRecordFromEf: " + success); 512 return success; 513 } 514 515 /** 516 * Loads an AdnRecord into a MatrixCursor. Must be called with mLock held. 517 * 518 * @param record the ADN record to load from 519 * @param cursor the cursor to receive the results 520 */ 521 @UnsupportedAppUsage loadRecord(AdnRecord record, MatrixCursor cursor, int id)522 private void loadRecord(AdnRecord record, MatrixCursor cursor, int id) { 523 if (!record.isEmpty()) { 524 Object[] contact = new Object[4]; 525 String alphaTag = record.getAlphaTag(); 526 String number = record.getNumber(); 527 528 if (DBG) log("loadRecord: " + alphaTag + ", " + Rlog.pii(TAG, number)); 529 contact[0] = alphaTag; 530 contact[1] = number; 531 532 String[] emails = record.getEmails(); 533 if (emails != null) { 534 StringBuilder emailString = new StringBuilder(); 535 for (String email: emails) { 536 log("Adding email:" + Rlog.pii(TAG, email)); 537 emailString.append(email); 538 emailString.append(","); 539 } 540 contact[2] = emailString.toString(); 541 } 542 contact[3] = id; 543 cursor.addRow(contact); 544 } 545 } 546 547 @UnsupportedAppUsage log(String msg)548 private void log(String msg) { 549 Rlog.d(TAG, "[IccProvider] " + msg); 550 } 551 getRequestSubId(Uri url)552 private int getRequestSubId(Uri url) { 553 if (DBG) log("getRequestSubId url: " + url); 554 555 try { 556 return Integer.parseInt(url.getLastPathSegment()); 557 } catch (NumberFormatException ex) { 558 throw new IllegalArgumentException("Unknown URL " + url); 559 } 560 } 561 } 562