1 /*
2  * Copyright (C) 2016 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 package android.provider;
17 
18 import android.annotation.WorkerThread;
19 import android.content.Context;
20 import android.net.Uri;
21 import android.os.Bundle;
22 
23 /**
24  * <p>
25  * The contract between the blockednumber provider and applications. Contains definitions for
26  * the supported URIs and columns.
27  * </p>
28  *
29  * <h3> Overview </h3>
30  * <p>
31  * The content provider exposes a table containing blocked numbers. The columns and URIs for
32  * accessing this table are defined by the {@link BlockedNumbers} class. Messages, and calls from
33  * blocked numbers are discarded by the platform. Notifications upon provider changes can be
34  * received using a {@link android.database.ContentObserver}.
35  * </p>
36  * <p>
37  * The platform will not block messages, and calls from emergency numbers as defined by
38  * {@link android.telephony.PhoneNumberUtils#isEmergencyNumber(String)}. If the user contacts
39  * emergency services, number blocking is disabled by the platform for a duration defined by
40  * {@link android.telephony.CarrierConfigManager#KEY_DURATION_BLOCKING_DISABLED_AFTER_EMERGENCY_INT}.
41  * </p>
42  *
43  * <h3> Permissions </h3>
44  * <p>
45  * Only the system, the default SMS application, and the default phone app
46  * (See {@link android.telecom.TelecomManager#getDefaultDialerPackage()}), and carrier apps
47  * (See {@link android.service.carrier.CarrierService}) can read, and write to the blockednumber
48  * provider. However, {@link #canCurrentUserBlockNumbers(Context)} can be accessed by any
49  * application.
50  * </p>
51  *
52  * <h3> Data </h3>
53  * <p>
54  * Other than regular phone numbers, the blocked number provider can also store addresses (such
55  * as email) from which a user can receive messages, and calls. The blocked numbers are stored
56  * in the {@link BlockedNumbers#COLUMN_ORIGINAL_NUMBER} column. A normalized version of phone
57  * numbers (if normalization is possible) is stored in {@link BlockedNumbers#COLUMN_E164_NUMBER}
58  * column. The platform blocks calls, and messages from an address if it is present in in the
59  * {@link BlockedNumbers#COLUMN_ORIGINAL_NUMBER} column or if the E164 version of the address
60  * matches the {@link BlockedNumbers#COLUMN_E164_NUMBER} column.
61  * </p>
62  *
63  * <h3> Operations </h3>
64  * <dl>
65  * <dt><b>Insert</b></dt>
66  * <dd>
67  * <p>
68  * {@link BlockedNumbers#COLUMN_ORIGINAL_NUMBER} is a required column that needs to be populated.
69  * Apps can optionally provide the {@link BlockedNumbers#COLUMN_E164_NUMBER} which is the phone
70  * number's E164 representation. The provider automatically populates this column if the app does
71  * not provide it. Note that this column is not populated if normalization fails or if the address
72  * is not a phone number (eg: email).
73  * <p>
74  * Attempting to insert an existing blocked number (same
75  * {@link BlockedNumbers#COLUMN_ORIGINAL_NUMBER} column) will result in replacing the existing
76  * blocked number.
77  * <p>
78  * Examples:
79  * <pre>
80  * ContentValues values = new ContentValues();
81  * values.put(BlockedNumbers.COLUMN_ORIGINAL_NUMBER, "1234567890");
82  * Uri uri = getContentResolver().insert(BlockedNumbers.CONTENT_URI, values);
83  * </pre>
84  * <pre>
85  * ContentValues values = new ContentValues();
86  * values.put(BlockedNumbers.COLUMN_ORIGINAL_NUMBER, "1234567890");
87  * values.put(BlockedNumbers.COLUMN_E164_NUMBER, "+11234567890");
88  * Uri uri = getContentResolver().insert(BlockedNumbers.CONTENT_URI, values);
89  * </pre>
90  * <pre>
91  * ContentValues values = new ContentValues();
92  * values.put(BlockedNumbers.COLUMN_ORIGINAL_NUMBER, "12345@abdcde.com");
93  * Uri uri = getContentResolver().insert(BlockedNumbers.CONTENT_URI, values);
94  * </pre>
95  * </p>
96  * </dd>
97  * <dt><b>Update</b></dt>
98  * <dd>
99  * <p>
100  * Updates are not supported. Use Delete, and Insert instead.
101  * </p>
102  * </dd>
103  * <dt><b>Delete</b></dt>
104  * <dd>
105  * <p>
106  * Deletions can be performed as follows:
107  * <pre>
108  * ContentValues values = new ContentValues();
109  * values.put(BlockedNumbers.COLUMN_ORIGINAL_NUMBER, "1234567890");
110  * Uri uri = getContentResolver().insert(BlockedNumbers.CONTENT_URI, values);
111  * getContentResolver().delete(uri, null, null);
112  * </pre>
113  * To check if a particular number is blocked, use the method
114  * {@link #isBlocked(Context, String)}.
115  * </p>
116  * </dd>
117  * <dt><b>Query</b></dt>
118  * <dd>
119  * <p>
120  * All blocked numbers can be enumerated as follows:
121  * <pre>
122  * Cursor c = getContentResolver().query(BlockedNumbers.CONTENT_URI,
123  *          new String[]{BlockedNumbers.COLUMN_ID, BlockedNumbers.COLUMN_ORIGINAL_NUMBER,
124  *          BlockedNumbers.COLUMN_E164_NUMBER}, null, null, null);
125  * </pre>
126  * </p>
127  * </dd>
128  * <dt><b>Unblock</b></dt>
129  * <dd>
130  * <p>
131  * Use the method {@link #unblock(Context, String)} to unblock numbers.
132  * </p>
133  * </dd>
134  *
135  * <h3> Multi-user </h3>
136  * <p>
137  * Apps must use the method {@link #canCurrentUserBlockNumbers(Context)} before performing any
138  * operation on the blocked number provider. If {@link #canCurrentUserBlockNumbers(Context)} returns
139  * {@code false}, all operations on the provider will fail with a {@link SecurityException}. The
140  * platform will block calls, and messages from numbers in the provider independent of the current
141  * user.
142  * </p>
143  */
144 public class BlockedNumberContract {
BlockedNumberContract()145     private BlockedNumberContract() {
146     }
147 
148     /** The authority for the blocked number provider */
149     public static final String AUTHORITY = "com.android.blockednumber";
150 
151     /** A content:// style uri to the authority for the blocked number provider */
152     public static final Uri AUTHORITY_URI = Uri.parse("content://" + AUTHORITY);
153 
154     /**
155      * Constants to interact with the blocked numbers list.
156      */
157     public static class BlockedNumbers {
BlockedNumbers()158         private BlockedNumbers() {
159         }
160 
161         /**
162          * Content URI for the blocked numbers.
163          * <h3> Supported operations </h3>
164          * <p> blocked
165          * <ul>
166          * <li> query
167          * <li> delete
168          * <li> insert
169          * </ul>
170          * <p> blocked/ID
171          * <ul>
172          * <li> query (selection is not supported)
173          * <li> delete (selection is not supported)
174          * </ul>
175          */
176         public static final Uri CONTENT_URI = Uri.withAppendedPath(AUTHORITY_URI, "blocked");
177 
178         /**
179          * The MIME type of {@link #CONTENT_URI} itself providing a directory of blocked phone
180          * numbers.
181          */
182         public static final String CONTENT_TYPE = "vnd.android.cursor.dir/blocked_number";
183 
184         /**
185          * The MIME type of a blocked phone number under {@link #CONTENT_URI}.
186          */
187         public static final String CONTENT_ITEM_TYPE = "vnd.android.cursor.item/blocked_number";
188 
189         /**
190          * Auto-generated ID field which monotonically increases.
191          * <p>TYPE: long</p>
192          */
193         public static final String COLUMN_ID = "_id";
194 
195         /**
196          * Phone number to block.
197          * <p>Must be specified in {@code insert}.
198          * <p>TYPE: String</p>
199          */
200         public static final String COLUMN_ORIGINAL_NUMBER = "original_number";
201 
202         /**
203          * Phone number to block.  The system generates it from {@link #COLUMN_ORIGINAL_NUMBER}
204          * by removing all formatting characters.
205          * <p>Optional in {@code insert}.  When not specified, the system tries to generate it
206          * assuming the current country. (Which will still be null if the number is not valid.)
207          * <p>TYPE: String</p>
208          */
209         public static final String COLUMN_E164_NUMBER = "e164_number";
210     }
211 
212     /** @hide */
213     public static final String METHOD_IS_BLOCKED = "is_blocked";
214 
215     /** @hide */
216     public static final String METHOD_UNBLOCK= "unblock";
217 
218     /** @hide */
219     public static final String RES_NUMBER_IS_BLOCKED = "blocked";
220 
221     /** @hide */
222     public static final String RES_NUM_ROWS_DELETED = "num_deleted";
223 
224     /** @hide */
225     public static final String METHOD_CAN_CURRENT_USER_BLOCK_NUMBERS =
226             "can_current_user_block_numbers";
227 
228     /** @hide */
229     public static final String RES_CAN_BLOCK_NUMBERS = "can_block";
230 
231     /**
232      * Returns whether a given number is in the blocked list.
233      *
234      * <p> This matches the {@code phoneNumber} against the
235      * {@link BlockedNumbers#COLUMN_ORIGINAL_NUMBER} column, and the E164 representation of the
236      * {@code phoneNumber} with the {@link BlockedNumbers#COLUMN_E164_NUMBER} column.
237      *
238      * <p> Note that if the {@link #canCurrentUserBlockNumbers} is {@code false} for the user
239      * context {@code context}, this method will throw a {@link SecurityException}.
240      *
241      * @return {@code true} if the {@code phoneNumber} is blocked.
242      */
243     @WorkerThread
isBlocked(Context context, String phoneNumber)244     public static boolean isBlocked(Context context, String phoneNumber) {
245         final Bundle res = context.getContentResolver().call(
246                 AUTHORITY_URI, METHOD_IS_BLOCKED, phoneNumber, null);
247         return res != null && res.getBoolean(RES_NUMBER_IS_BLOCKED, false);
248     }
249 
250     /**
251      * Unblocks the {@code phoneNumber} if it is blocked.
252      *
253      * <p> This deletes all rows where the {@code phoneNumber} matches the
254      * {@link BlockedNumbers#COLUMN_ORIGINAL_NUMBER} column or the E164 representation of the
255      * {@code phoneNumber} matches the {@link BlockedNumbers#COLUMN_E164_NUMBER} column.
256      *
257      * <p>To delete rows based on exact match with specific columns such as
258      * {@link BlockedNumbers#COLUMN_ID} use
259      * {@link android.content.ContentProvider#delete(Uri, String, String[])} with
260      * {@link BlockedNumbers#CONTENT_URI} URI.
261      *
262      * <p> Note that if the {@link #canCurrentUserBlockNumbers} is {@code false} for the user
263      * context {@code context}, this method will throw a {@link SecurityException}.
264      *
265      * @return the number of rows deleted in the blocked number provider as a result of unblock.
266      */
267     @WorkerThread
unblock(Context context, String phoneNumber)268     public static int unblock(Context context, String phoneNumber) {
269         final Bundle res = context.getContentResolver().call(
270                 AUTHORITY_URI, METHOD_UNBLOCK, phoneNumber, null);
271         return res.getInt(RES_NUM_ROWS_DELETED, 0);
272     }
273 
274     /**
275      * Checks if blocking numbers is supported for the current user.
276      * <p> Typically, blocking numbers is only supported for one user at a time.
277      *
278      * @return {@code true} if the current user can block numbers.
279      */
canCurrentUserBlockNumbers(Context context)280     public static boolean canCurrentUserBlockNumbers(Context context) {
281         final Bundle res = context.getContentResolver().call(
282                 AUTHORITY_URI, METHOD_CAN_CURRENT_USER_BLOCK_NUMBERS, null, null);
283         return res != null && res.getBoolean(RES_CAN_BLOCK_NUMBERS, false);
284     }
285 
286     /**
287      * <p>
288      * The contract between the blockednumber provider and the system.
289      * </p>
290      * <p>This is a wrapper over {@link BlockedNumberContract} that also manages the blocking
291      * behavior when the user contacts emergency services. See
292      * {@link #notifyEmergencyContact(Context)} for details. All methods are protected by
293      * {@link android.Manifest.permission#READ_BLOCKED_NUMBERS} and
294      * {@link android.Manifest.permission#WRITE_BLOCKED_NUMBERS} appropriately which ensure that
295      * only system can access the methods defined here.
296      * </p>
297      * @hide
298      */
299     public static class SystemContract {
300         /**
301          * A protected broadcast intent action for letting components with
302          * {@link android.Manifest.permission#READ_BLOCKED_NUMBERS} know that the block suppression
303          * status as returned by {@link #getBlockSuppressionStatus(Context)} has been updated.
304          */
305         public static final String ACTION_BLOCK_SUPPRESSION_STATE_CHANGED =
306                 "android.provider.action.BLOCK_SUPPRESSION_STATE_CHANGED";
307 
308         public static final String METHOD_NOTIFY_EMERGENCY_CONTACT = "notify_emergency_contact";
309 
310         public static final String METHOD_END_BLOCK_SUPPRESSION = "end_block_suppression";
311 
312         public static final String METHOD_SHOULD_SYSTEM_BLOCK_NUMBER = "should_system_block_number";
313 
314         public static final String METHOD_GET_BLOCK_SUPPRESSION_STATUS =
315                 "get_block_suppression_status";
316 
317         public static final String RES_IS_BLOCKING_SUPPRESSED = "blocking_suppressed";
318 
319         public static final String RES_BLOCKING_SUPPRESSED_UNTIL_TIMESTAMP =
320                 "blocking_suppressed_until_timestamp";
321 
322         /**
323          * Notifies the provider that emergency services were contacted by the user.
324          * <p> This results in {@link #shouldSystemBlockNumber} returning {@code false} independent
325          * of the contents of the provider for a duration defined by
326          * {@link android.telephony.CarrierConfigManager#KEY_DURATION_BLOCKING_DISABLED_AFTER_EMERGENCY_INT}
327          * the provider unless {@link #endBlockSuppression(Context)} is called.
328          */
notifyEmergencyContact(Context context)329         public static void notifyEmergencyContact(Context context) {
330             context.getContentResolver().call(
331                     AUTHORITY_URI, METHOD_NOTIFY_EMERGENCY_CONTACT, null, null);
332         }
333 
334         /**
335          * Notifies the provider to disable suppressing blocking. If emergency services were not
336          * contacted recently at all, calling this method is a no-op.
337          */
endBlockSuppression(Context context)338         public static void endBlockSuppression(Context context) {
339             context.getContentResolver().call(
340                     AUTHORITY_URI, METHOD_END_BLOCK_SUPPRESSION, null, null);
341         }
342 
343         /**
344          * Returns {@code true} if {@code phoneNumber} is blocked taking
345          * {@link #notifyEmergencyContact(Context)} into consideration. If emergency services have
346          * not been contacted recently, this method is equivalent to
347          * {@link #isBlocked(Context, String)}.
348          */
shouldSystemBlockNumber(Context context, String phoneNumber)349         public static boolean shouldSystemBlockNumber(Context context, String phoneNumber) {
350             final Bundle res = context.getContentResolver().call(
351                     AUTHORITY_URI, METHOD_SHOULD_SYSTEM_BLOCK_NUMBER, phoneNumber, null);
352             return res != null && res.getBoolean(RES_NUMBER_IS_BLOCKED, false);
353         }
354 
355         /**
356          * Returns the current status of block suppression.
357          */
getBlockSuppressionStatus(Context context)358         public static BlockSuppressionStatus getBlockSuppressionStatus(Context context) {
359             final Bundle res = context.getContentResolver().call(
360                     AUTHORITY_URI, METHOD_GET_BLOCK_SUPPRESSION_STATUS, null, null);
361             return new BlockSuppressionStatus(res.getBoolean(RES_IS_BLOCKING_SUPPRESSED, false),
362                     res.getLong(RES_BLOCKING_SUPPRESSED_UNTIL_TIMESTAMP, 0));
363         }
364 
365         /**
366          * Represents the current status of {@link #shouldSystemBlockNumber(Context, String)}. If
367          * emergency services have been contacted recently, {@link #isSuppressed} is {@code true},
368          * and blocking is disabled until the timestamp {@link #untilTimestampMillis}.
369          */
370         public static class BlockSuppressionStatus {
371             public final boolean isSuppressed;
372             /**
373              * Timestamp in milliseconds from epoch.
374              */
375             public final long untilTimestampMillis;
376 
BlockSuppressionStatus(boolean isSuppressed, long untilTimestampMillis)377             public BlockSuppressionStatus(boolean isSuppressed, long untilTimestampMillis) {
378                 this.isSuppressed = isSuppressed;
379                 this.untilTimestampMillis = untilTimestampMillis;
380             }
381         }
382     }
383 }
384