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