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