1 /* 2 * Copyright (C) 2020 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 android.provider; 18 19 import static android.provider.SimPhonebookContract.ElementaryFiles.EF_ADN; 20 import static android.provider.SimPhonebookContract.ElementaryFiles.EF_FDN; 21 import static android.provider.SimPhonebookContract.ElementaryFiles.EF_SDN; 22 import static android.provider.SimPhonebookContract.ElementaryFiles.PATH_SEGMENT_EF_ADN; 23 import static android.provider.SimPhonebookContract.ElementaryFiles.PATH_SEGMENT_EF_FDN; 24 import static android.provider.SimPhonebookContract.ElementaryFiles.PATH_SEGMENT_EF_SDN; 25 26 import android.annotation.IntDef; 27 import android.annotation.IntRange; 28 import android.annotation.NonNull; 29 import android.annotation.SystemApi; 30 import android.annotation.WorkerThread; 31 import android.content.ContentResolver; 32 import android.content.ContentValues; 33 import android.net.Uri; 34 import android.os.Bundle; 35 import android.telephony.SubscriptionInfo; 36 import android.telephony.TelephonyManager; 37 38 import com.android.internal.util.Preconditions; 39 40 import java.lang.annotation.Retention; 41 import java.lang.annotation.RetentionPolicy; 42 import java.util.Objects; 43 44 /** 45 * The contract between the provider of contact records on the device's SIM cards and applications. 46 * Contains definitions of the supported URIs and columns. 47 * 48 * <h3>Permissions</h3> 49 * <p> 50 * Querying this provider requires {@link android.Manifest.permission#READ_CONTACTS} and writing 51 * to this provider requires {@link android.Manifest.permission#WRITE_CONTACTS} 52 * </p> 53 */ 54 public final class SimPhonebookContract { 55 56 /** The authority for the SIM phonebook provider. */ 57 public static final String AUTHORITY = "com.android.simphonebook"; 58 /** The content:// style uri to the authority for the SIM phonebook provider. */ 59 @NonNull 60 public static final Uri AUTHORITY_URI = Uri.parse("content://com.android.simphonebook"); 61 /** 62 * The Uri path element used to indicate that the following path segment is a subscription ID 63 * for the SIM card that will be operated on. 64 * 65 * @hide 66 */ 67 public static final String SUBSCRIPTION_ID_PATH_SEGMENT = "subid"; 68 SimPhonebookContract()69 private SimPhonebookContract() { 70 } 71 72 /** 73 * Returns the Uri path segment used to reference the specified elementary file type for Uris 74 * returned by this API. 75 * 76 * @hide 77 */ 78 @NonNull getEfUriPath(@lementaryFiles.EfType int efType)79 public static String getEfUriPath(@ElementaryFiles.EfType int efType) { 80 switch (efType) { 81 case EF_ADN: 82 return PATH_SEGMENT_EF_ADN; 83 case EF_FDN: 84 return PATH_SEGMENT_EF_FDN; 85 case EF_SDN: 86 return PATH_SEGMENT_EF_SDN; 87 default: 88 throw new IllegalArgumentException("Unsupported EfType " + efType); 89 } 90 } 91 92 /** 93 * Constants for the contact records on a SIM card. 94 * 95 * <h3 id="simrecords-data">Data</h3> 96 * <p> 97 * Data is stored in a specific elementary file on a specific SIM card and these are isolated 98 * from each other. SIM cards are identified by their subscription ID. SIM cards may not support 99 * all or even any of the elementary file types. A SIM will have constraints on 100 * the values of the data that can be stored in each elementary file. The available SIMs, 101 * their supported elementary file types and the constraints on the data can be discovered by 102 * querying {@link ElementaryFiles#CONTENT_URI}. Each elementary file has a fixed capacity 103 * for the number of records that may be stored. This can be determined from the value 104 * of the {@link ElementaryFiles#MAX_RECORDS} column. 105 * </p> 106 * <p> 107 * The {@link SimRecords#PHONE_NUMBER} column can only contain dialable characters and this 108 * applies regardless of the SIM that is being used. See 109 * {@link android.telephony.PhoneNumberUtils#isDialable(char)} for more details. Additionally 110 * the phone number can contain at most {@link ElementaryFiles#PHONE_NUMBER_MAX_LENGTH} 111 * characters. The {@link SimRecords#NAME} column can contain at most 112 * {@link ElementaryFiles#NAME_MAX_LENGTH} bytes when it is encoded for storage on the SIM. 113 * Encoding is done internally and so the name should be provided to these provider APIs as a 114 * Java String but the number of bytes required to encode it for storage will vary depending on 115 * the characters it contains. This length can be determined by calling 116 * {@link SimRecords#getEncodedNameLength(ContentResolver, String)}. 117 * </p> 118 * <h3>Operations </h3> 119 * <dl> 120 * <dd><b>Insert</b></dd> 121 * <p> 122 * Only {@link ElementaryFiles#EF_ADN} supports inserts. {@link SimRecords#PHONE_NUMBER} 123 * is a required column. If the value provided for this column is missing, null, empty 124 * or violates the requirements discussed in the <a href="#simrecords-data">Data</a> 125 * section above an {@link IllegalArgumentException} will be thrown. The 126 * {@link SimRecords#NAME} column may be omitted but if provided and it violates any of 127 * the requirements discussed in the <a href="#simrecords-data">Data</a> section above 128 * an {@link IllegalArgumentException} will be thrown. 129 * </p> 130 * <p> 131 * If an insert is not possible because the elementary file is full then an 132 * {@link IllegalStateException} will be thrown. 133 * </p> 134 * <dd><b>Update</b></dd> 135 * <p> 136 * Updates can only be performed for individual records on {@link ElementaryFiles#EF_ADN}. 137 * A specific record is referenced via the Uri returned by 138 * {@link SimRecords#getItemUri(int, int, int)}. Updates have the same constraints and 139 * behavior for the {@link SimRecords#PHONE_NUMBER} and {@link SimRecords#NAME} as insert. 140 * However, in the case of update the {@link SimRecords#PHONE_NUMBER} may be omitted as 141 * the existing record will already have a valid value. 142 * </p> 143 * <dd><b>Delete</b></dd> 144 * <p> 145 * Delete may only be performed for individual records on {@link ElementaryFiles#EF_ADN}. 146 * Deleting records will free up space for use by future inserts. 147 * </p> 148 * <dd><b>Query</b></dd> 149 * <p> 150 * All the records stored on a specific elementary file can be read via a Uri returned by 151 * {@link SimRecords#getContentUri(int, int)}. This query always returns all records; there 152 * is no support for filtering via a selection. An individual record can be queried via a Uri 153 * returned by {@link SimRecords#getItemUri(int, int, int)}. Queries will throw an 154 * {@link IllegalArgumentException} when the SIM with the subscription ID or the elementary file 155 * type are invalid or unavailable. 156 * </p> 157 * </dl> 158 */ 159 public static final class SimRecords { 160 161 /** 162 * The subscription ID of the SIM the record is from. 163 * 164 * @see SubscriptionInfo#getSubscriptionId() 165 */ 166 public static final String SUBSCRIPTION_ID = "subscription_id"; 167 /** 168 * The type of the elementary file the record is from. 169 * 170 * @see ElementaryFiles#EF_ADN 171 * @see ElementaryFiles#EF_FDN 172 * @see ElementaryFiles#EF_SDN 173 */ 174 public static final String ELEMENTARY_FILE_TYPE = "elementary_file_type"; 175 /** 176 * The 1-based offset of the record in the elementary file that contains it. 177 * 178 * <p>This can be used to access individual SIM records by appending it to the 179 * elementary file URIs but it is not like a normal database ID because it is not 180 * auto-incrementing and it is not unique across SIM cards or elementary files. Hence, care 181 * should be taken when using it to ensure that it is applied to the correct SIM and EF. 182 * 183 * @see #getItemUri(int, int, int) 184 */ 185 public static final String RECORD_NUMBER = "record_number"; 186 /** 187 * The name for this record. 188 * 189 * <p>An {@link IllegalArgumentException} will be thrown by insert and update if this 190 * exceeds the maximum supported length. Use 191 * {@link #getEncodedNameLength(ContentResolver, String)} to check how long the name 192 * will be after encoding. 193 * 194 * @see ElementaryFiles#NAME_MAX_LENGTH 195 * @see #getEncodedNameLength(ContentResolver, String) 196 */ 197 public static final String NAME = "name"; 198 /** 199 * The phone number for this record. 200 * 201 * <p>Only dialable characters are supported. 202 * 203 * <p>An {@link IllegalArgumentException} will be thrown by insert and update if this 204 * exceeds the maximum supported length or contains unsupported characters. 205 * 206 * @see ElementaryFiles#PHONE_NUMBER_MAX_LENGTH 207 * @see android.telephony.PhoneNumberUtils#isDialable(char) 208 */ 209 public static final String PHONE_NUMBER = "phone_number"; 210 211 /** The MIME type of a CONTENT_URI subdirectory of a single SIM record. */ 212 public static final String CONTENT_ITEM_TYPE = "vnd.android.cursor.item/sim-contact_v2"; 213 /** The MIME type of CONTENT_URI providing a directory of SIM records. */ 214 public static final String CONTENT_TYPE = "vnd.android.cursor.dir/sim-contact_v2"; 215 216 /** 217 * Value returned from {@link #getEncodedNameLength(ContentResolver, String)} when the name 218 * length could not be determined because the name could not be encoded. 219 */ 220 public static final int ERROR_NAME_UNSUPPORTED = -1; 221 222 /** 223 * The method name used to get the encoded length of a value for {@link SimRecords#NAME} 224 * column. 225 * 226 * @hide 227 * @see #getEncodedNameLength(ContentResolver, String) 228 * @see ContentResolver#call(String, String, String, Bundle) 229 */ 230 public static final String GET_ENCODED_NAME_LENGTH_METHOD_NAME = "get_encoded_name_length"; 231 232 /** 233 * Extra key used for an integer value that contains the length in bytes of an encoded 234 * name. 235 * 236 * @hide 237 * @see #getEncodedNameLength(ContentResolver, String) 238 * @see #GET_ENCODED_NAME_LENGTH_METHOD_NAME 239 */ 240 public static final String EXTRA_ENCODED_NAME_LENGTH = 241 "android.provider.extra.ENCODED_NAME_LENGTH"; 242 243 244 /** 245 * Key for the PIN2 needed to modify FDN record that should be passed in the Bundle 246 * passed to {@link ContentResolver#insert(Uri, ContentValues, Bundle)}, 247 * {@link ContentResolver#update(Uri, ContentValues, Bundle)} 248 * and {@link ContentResolver#delete(Uri, Bundle)}. 249 * 250 * <p>Modifying FDN records also requires either 251 * {@link android.Manifest.permission#MODIFY_PHONE_STATE} or 252 * {@link TelephonyManager#hasCarrierPrivileges()} 253 * 254 * @hide 255 */ 256 @SystemApi 257 public static final String QUERY_ARG_PIN2 = "android:query-arg-pin2"; 258 SimRecords()259 private SimRecords() { 260 } 261 262 /** 263 * Returns the content Uri for the specified elementary file on the specified SIM. 264 * 265 * <p>When queried this Uri will return all of the contact records in the specified 266 * elementary file on the specified SIM. The available subscriptionIds and efTypes can 267 * be discovered by querying {@link ElementaryFiles#CONTENT_URI}. 268 * 269 * <p>If a SIM with the provided subscription ID does not exist or the SIM with the provided 270 * subscription ID doesn't support the specified entity file then all operations will 271 * throw an {@link IllegalArgumentException}. 272 * 273 * @param subscriptionId the subscriptionId of the SIM card that this Uri will reference 274 * @param efType the elementary file on the SIM that this Uri will reference 275 * @see ElementaryFiles#EF_ADN 276 * @see ElementaryFiles#EF_FDN 277 * @see ElementaryFiles#EF_SDN 278 */ 279 @NonNull getContentUri(int subscriptionId, @ElementaryFiles.EfType int efType)280 public static Uri getContentUri(int subscriptionId, @ElementaryFiles.EfType int efType) { 281 return buildContentUri(subscriptionId, efType).build(); 282 } 283 284 /** 285 * Content Uri for the specific SIM record with the provided {@link #RECORD_NUMBER}. 286 * 287 * <p>When queried this will return the record identified by the provided arguments. 288 * 289 * <p>For a non-existent record: 290 * <ul> 291 * <li>query will return an empty cursor</li> 292 * <li>update will return 0</li> 293 * <li>delete will return 0</li> 294 * </ul> 295 * 296 * @param subscriptionId the subscription ID of the SIM containing the record. If no SIM 297 * with this subscription ID exists then it will be treated as a 298 * non-existent record 299 * @param efType the elementary file type containing the record. If the specified 300 * SIM doesn't support this elementary file then it will be treated 301 * as a non-existent record. 302 * @param recordNumber the record number of the record this Uri should reference. This 303 * must be greater than 0. If there is no record with this record 304 * number in the specified entity file then it will be treated as a 305 * non-existent record. 306 * @see ElementaryFiles#SUBSCRIPTION_ID 307 * @see ElementaryFiles#EF_TYPE 308 * @see #RECORD_NUMBER 309 */ 310 @NonNull getItemUri( int subscriptionId, @ElementaryFiles.EfType int efType, @IntRange(from = 1) int recordNumber)311 public static Uri getItemUri( 312 int subscriptionId, @ElementaryFiles.EfType int efType, 313 @IntRange(from = 1) int recordNumber) { 314 // Elementary file record indices are 1-based. 315 Preconditions.checkArgument(recordNumber > 0, "Invalid recordNumber"); 316 317 return buildContentUri(subscriptionId, efType) 318 .appendPath(String.valueOf(recordNumber)) 319 .build(); 320 } 321 322 /** 323 * Returns the number of bytes required to encode the specified name when it is stored 324 * on the SIM. 325 * 326 * <p>{@link ElementaryFiles#NAME_MAX_LENGTH} is specified in bytes but the encoded name 327 * may require more than 1 byte per character depending on the characters it contains. So 328 * this method can be used to check whether a name exceeds the max length. 329 * 330 * @return the number of bytes required by the encoded name or 331 * {@link #ERROR_NAME_UNSUPPORTED} if the name could not be encoded. 332 * @throws IllegalStateException if the provider fails to return the length. 333 * @see SimRecords#NAME 334 * @see ElementaryFiles#NAME_MAX_LENGTH 335 */ 336 @WorkerThread 337 @IntRange(from = 0) getEncodedNameLength( @onNull ContentResolver resolver, @NonNull String name)338 public static int getEncodedNameLength( 339 @NonNull ContentResolver resolver, @NonNull String name) { 340 Objects.requireNonNull(name); 341 Bundle result = resolver.call(AUTHORITY, GET_ENCODED_NAME_LENGTH_METHOD_NAME, name, 342 null); 343 if (result == null || !result.containsKey(EXTRA_ENCODED_NAME_LENGTH)) { 344 throw new IllegalStateException("Provider malfunction: no length was returned."); 345 } 346 int length = result.getInt(EXTRA_ENCODED_NAME_LENGTH, ERROR_NAME_UNSUPPORTED); 347 if (length < 0 && length != ERROR_NAME_UNSUPPORTED) { 348 throw new IllegalStateException( 349 "Provider malfunction: invalid length was returned."); 350 } 351 return length; 352 } 353 buildContentUri( int subscriptionId, @ElementaryFiles.EfType int efType)354 private static Uri.Builder buildContentUri( 355 int subscriptionId, @ElementaryFiles.EfType int efType) { 356 return new Uri.Builder().scheme(ContentResolver.SCHEME_CONTENT) 357 .authority(AUTHORITY) 358 .appendPath(SUBSCRIPTION_ID_PATH_SEGMENT) 359 .appendPath(String.valueOf(subscriptionId)) 360 .appendPath(getEfUriPath(efType)); 361 } 362 363 } 364 365 /** 366 * Constants for metadata about the elementary files of the SIM cards in the phone. 367 * 368 * <h3>Operations </h3> 369 * <dl> 370 * <dd><b>Insert</b></dd> 371 * <p>Insert is not supported for the Uris defined in this class.</p> 372 * <dd><b>Update</b></dd> 373 * <p>Update is not supported for the Uris defined in this class.</p> 374 * <dd><b>Delete</b></dd> 375 * <p>Delete is not supported for the Uris defined in this class.</p> 376 * <dd><b>Query</b></dd> 377 * <p> 378 * The elementary files for all the inserted SIMs can be read via 379 * {@link ElementaryFiles#CONTENT_URI}. Unsupported elementary files are omitted from the 380 * results. This Uri always returns all supported elementary files for all available SIMs; it 381 * does not support filtering via a selection. A specific elementary file can be queried 382 * via a Uri returned by {@link ElementaryFiles#getItemUri(int, int)}. If the elementary file 383 * referenced by this Uri is unsupported by the SIM then the query will return an empty cursor. 384 * </p> 385 * </dl> 386 */ 387 public static final class ElementaryFiles { 388 389 /** {@link SubscriptionInfo#getSimSlotIndex()} of the SIM for this row. */ 390 public static final String SLOT_INDEX = "slot_index"; 391 /** {@link SubscriptionInfo#getSubscriptionId()} of the SIM for this row. */ 392 public static final String SUBSCRIPTION_ID = "subscription_id"; 393 /** 394 * The elementary file type for this row. 395 * 396 * @see ElementaryFiles#EF_ADN 397 * @see ElementaryFiles#EF_FDN 398 * @see ElementaryFiles#EF_SDN 399 */ 400 public static final String EF_TYPE = "ef_type"; 401 /** The maximum number of records supported by the elementary file. */ 402 public static final String MAX_RECORDS = "max_records"; 403 /** Count of the number of records that are currently stored in the elementary file. */ 404 public static final String RECORD_COUNT = "record_count"; 405 /** The maximum length supported for the name of a record in the elementary file. */ 406 public static final String NAME_MAX_LENGTH = "name_max_length"; 407 /** 408 * The maximum length supported for the phone number of a record in the elementary file. 409 */ 410 public static final String PHONE_NUMBER_MAX_LENGTH = "phone_number_max_length"; 411 412 /** 413 * A value for an elementary file that is not recognized. 414 * 415 * <p>Generally this should be ignored. If new values are added then this will be used 416 * for apps that target SDKs where they aren't defined. 417 */ 418 public static final int EF_UNKNOWN = 0; 419 /** 420 * Type for accessing records in the "abbreviated dialing number" (ADN) elementary file on 421 * the SIM. 422 * 423 * <p>ADN records are typically user created. 424 */ 425 public static final int EF_ADN = 1; 426 /** 427 * Type for accessing records in the "fixed dialing number" (FDN) elementary file on the 428 * SIM. 429 * 430 * <p>FDN numbers are the numbers that are allowed to dialed for outbound calls when FDN is 431 * enabled. 432 * 433 * <p>FDN records cannot be modified by applications. Hence, insert, update and 434 * delete methods operating on this Uri will throw UnsupportedOperationException 435 */ 436 public static final int EF_FDN = 2; 437 /** 438 * Type for accessing records in the "service dialing number" (SDN) elementary file on the 439 * SIM. 440 * 441 * <p>Typically SDNs are preset numbers provided by the carrier for common operations (e.g. 442 * voicemail, check balance, etc). 443 * 444 * <p>SDN records cannot be modified by applications. Hence, insert, update and delete 445 * methods operating on this Uri will throw UnsupportedOperationException 446 */ 447 public static final int EF_SDN = 3; 448 /** 449 * The Uri path segment used to target the ADN elementary file for SimPhonebookProvider 450 * content operations. 451 * 452 * @hide 453 */ 454 public static final String PATH_SEGMENT_EF_ADN = "adn"; 455 /** 456 * The Uri path segment used to target the FDN elementary file for SimPhonebookProvider 457 * content operations. 458 * 459 * @hide 460 */ 461 public static final String PATH_SEGMENT_EF_FDN = "fdn"; 462 /** 463 * The Uri path segment used to target the SDN elementary file for SimPhonebookProvider 464 * content operations. 465 * 466 * @hide 467 */ 468 public static final String PATH_SEGMENT_EF_SDN = "sdn"; 469 /** The MIME type of CONTENT_URI providing a directory of ADN-like elementary files. */ 470 public static final String CONTENT_TYPE = "vnd.android.cursor.dir/sim-elementary-file"; 471 /** The MIME type of a CONTENT_URI subdirectory of a single ADN-like elementary file. */ 472 public static final String CONTENT_ITEM_TYPE = 473 "vnd.android.cursor.item/sim-elementary-file"; 474 /** 475 * The Uri path segment used to construct Uris for the metadata defined in this class. 476 * 477 * @hide 478 */ 479 public static final String ELEMENTARY_FILES_PATH_SEGMENT = "elementary_files"; 480 481 /** Content URI for the ADN-like elementary files available on the device. */ 482 @NonNull 483 public static final Uri CONTENT_URI = AUTHORITY_URI 484 .buildUpon() 485 .appendPath(ELEMENTARY_FILES_PATH_SEGMENT).build(); 486 ElementaryFiles()487 private ElementaryFiles() { 488 } 489 490 /** 491 * Returns a content uri for a specific elementary file. 492 * 493 * <p>If a SIM with the specified subscriptionId is not present an exception will be thrown. 494 * If the SIM doesn't support the specified elementary file it will return an empty cursor. 495 */ 496 @NonNull getItemUri(int subscriptionId, @EfType int efType)497 public static Uri getItemUri(int subscriptionId, @EfType int efType) { 498 return CONTENT_URI.buildUpon().appendPath(SUBSCRIPTION_ID_PATH_SEGMENT) 499 .appendPath(String.valueOf(subscriptionId)) 500 .appendPath(getEfUriPath(efType)) 501 .build(); 502 } 503 504 /** 505 * Annotation for the valid elementary file types. 506 * 507 * @hide 508 */ 509 @Retention(RetentionPolicy.SOURCE) 510 @IntDef( 511 prefix = {"EF"}, 512 value = {EF_UNKNOWN, EF_ADN, EF_FDN, EF_SDN}) 513 public @interface EfType { 514 } 515 } 516 } 517