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 */
16package android.provider;
17
18import android.annotation.WorkerThread;
19import android.content.Context;
20import android.net.Uri;
21import android.os.Bundle;
22import android.telecom.Log;
23
24/**
25 * <p>
26 * The contract between the blockednumber provider and applications. Contains definitions for
27 * the supported URIs and columns.
28 * </p>
29 *
30 * <h3> Overview </h3>
31 * <p>
32 * The content provider exposes a table containing blocked numbers. The columns and URIs for
33 * accessing this table are defined by the {@link BlockedNumbers} class. Messages, and calls from
34 * blocked numbers are discarded by the platform. Notifications upon provider changes can be
35 * received using a {@link android.database.ContentObserver}.
36 * </p>
37 * <p>
38 * The platform will not block messages, and calls from emergency numbers as defined by
39 * {@link android.telephony.PhoneNumberUtils#isEmergencyNumber(String)}. If the user contacts
40 * emergency services, number blocking is disabled by the platform for a duration defined by
41 * {@link android.telephony.CarrierConfigManager#KEY_DURATION_BLOCKING_DISABLED_AFTER_EMERGENCY_INT}.
42 * </p>
43 *
44 * <h3> Permissions </h3>
45 * <p>
46 * Only the system, the default SMS application, and the default phone app
47 * (See {@link android.telecom.TelecomManager#getDefaultDialerPackage()}), and carrier apps
48 * (See {@link android.service.carrier.CarrierService}) can read, and write to the blockednumber
49 * provider. However, {@link #canCurrentUserBlockNumbers(Context)} can be accessed by any
50 * application.
51 * </p>
52 *
53 * <h3> Data </h3>
54 * <p>
55 * Other than regular phone numbers, the blocked number provider can also store addresses (such
56 * as email) from which a user can receive messages, and calls. The blocked numbers are stored
57 * in the {@link BlockedNumbers#COLUMN_ORIGINAL_NUMBER} column. A normalized version of phone
58 * numbers (if normalization is possible) is stored in {@link BlockedNumbers#COLUMN_E164_NUMBER}
59 * column. The platform blocks calls, and messages from an address if it is present in in the
60 * {@link BlockedNumbers#COLUMN_ORIGINAL_NUMBER} column or if the E164 version of the address
61 * matches the {@link BlockedNumbers#COLUMN_E164_NUMBER} column.
62 * </p>
63 *
64 * <h3> Operations </h3>
65 * <dl>
66 * <dt><b>Insert</b></dt>
67 * <dd>
68 * <p>
69 * {@link BlockedNumbers#COLUMN_ORIGINAL_NUMBER} is a required column that needs to be populated.
70 * Apps can optionally provide the {@link BlockedNumbers#COLUMN_E164_NUMBER} which is the phone
71 * number's E164 representation. The provider automatically populates this column if the app does
72 * not provide it. Note that this column is not populated if normalization fails or if the address
73 * is not a phone number (eg: email).
74 * <p>
75 * Attempting to insert an existing blocked number (same
76 * {@link BlockedNumbers#COLUMN_ORIGINAL_NUMBER} column) will result in replacing the existing
77 * blocked number.
78 * <p>
79 * Examples:
80 * <pre>
81 * ContentValues values = new ContentValues();
82 * values.put(BlockedNumbers.COLUMN_ORIGINAL_NUMBER, "1234567890");
83 * Uri uri = getContentResolver().insert(BlockedNumbers.CONTENT_URI, values);
84 * </pre>
85 * <pre>
86 * ContentValues values = new ContentValues();
87 * values.put(BlockedNumbers.COLUMN_ORIGINAL_NUMBER, "1234567890");
88 * values.put(BlockedNumbers.COLUMN_E164_NUMBER, "+11234567890");
89 * Uri uri = getContentResolver().insert(BlockedNumbers.CONTENT_URI, values);
90 * </pre>
91 * <pre>
92 * ContentValues values = new ContentValues();
93 * values.put(BlockedNumbers.COLUMN_ORIGINAL_NUMBER, "12345@abdcde.com");
94 * Uri uri = getContentResolver().insert(BlockedNumbers.CONTENT_URI, values);
95 * </pre>
96 * </p>
97 * </dd>
98 * <dt><b>Update</b></dt>
99 * <dd>
100 * <p>
101 * Updates are not supported. Use Delete, and Insert instead.
102 * </p>
103 * </dd>
104 * <dt><b>Delete</b></dt>
105 * <dd>
106 * <p>
107 * Deletions can be performed as follows:
108 * <pre>
109 * ContentValues values = new ContentValues();
110 * values.put(BlockedNumbers.COLUMN_ORIGINAL_NUMBER, "1234567890");
111 * Uri uri = getContentResolver().insert(BlockedNumbers.CONTENT_URI, values);
112 * getContentResolver().delete(uri, null, null);
113 * </pre>
114 * To check if a particular number is blocked, use the method
115 * {@link #isBlocked(Context, String)}.
116 * </p>
117 * </dd>
118 * <dt><b>Query</b></dt>
119 * <dd>
120 * <p>
121 * All blocked numbers can be enumerated as follows:
122 * <pre>
123 * Cursor c = getContentResolver().query(BlockedNumbers.CONTENT_URI,
124 *          new String[]{BlockedNumbers.COLUMN_ID, BlockedNumbers.COLUMN_ORIGINAL_NUMBER,
125 *          BlockedNumbers.COLUMN_E164_NUMBER}, null, null, null);
126 * </pre>
127 * </p>
128 * </dd>
129 * <dt><b>Unblock</b></dt>
130 * <dd>
131 * <p>
132 * Use the method {@link #unblock(Context, String)} to unblock numbers.
133 * </p>
134 * </dd>
135 *
136 * <h3> Multi-user </h3>
137 * <p>
138 * Apps must use the method {@link #canCurrentUserBlockNumbers(Context)} before performing any
139 * operation on the blocked number provider. If {@link #canCurrentUserBlockNumbers(Context)} returns
140 * {@code false}, all operations on the provider will fail with a {@link SecurityException}. The
141 * platform will block calls, and messages from numbers in the provider independent of the current
142 * user.
143 * </p>
144 */
145public class BlockedNumberContract {
146    private BlockedNumberContract() {
147    }
148
149    /** The authority for the blocked number provider */
150    public static final String AUTHORITY = "com.android.blockednumber";
151
152    /** A content:// style uri to the authority for the blocked number provider */
153    public static final Uri AUTHORITY_URI = Uri.parse("content://" + AUTHORITY);
154
155    /**
156     * Constants to interact with the blocked numbers list.
157     */
158    public static class BlockedNumbers {
159        private BlockedNumbers() {
160        }
161
162        /**
163         * Content URI for the blocked numbers.
164         * <h3> Supported operations </h3>
165         * <p> blocked
166         * <ul>
167         * <li> query
168         * <li> delete
169         * <li> insert
170         * </ul>
171         * <p> blocked/ID
172         * <ul>
173         * <li> query (selection is not supported)
174         * <li> delete (selection is not supported)
175         * </ul>
176         */
177        public static final Uri CONTENT_URI = Uri.withAppendedPath(AUTHORITY_URI, "blocked");
178
179        /**
180         * The MIME type of {@link #CONTENT_URI} itself providing a directory of blocked phone
181         * numbers.
182         */
183        public static final String CONTENT_TYPE = "vnd.android.cursor.dir/blocked_number";
184
185        /**
186         * The MIME type of a blocked phone number under {@link #CONTENT_URI}.
187         */
188        public static final String CONTENT_ITEM_TYPE = "vnd.android.cursor.item/blocked_number";
189
190        /**
191         * Auto-generated ID field which monotonically increases.
192         * <p>TYPE: long</p>
193         */
194        public static final String COLUMN_ID = "_id";
195
196        /**
197         * Phone number to block.
198         * <p>Must be specified in {@code insert}.
199         * <p>TYPE: String</p>
200         */
201        public static final String COLUMN_ORIGINAL_NUMBER = "original_number";
202
203        /**
204         * Phone number to block.  The system generates it from {@link #COLUMN_ORIGINAL_NUMBER}
205         * by removing all formatting characters.
206         * <p>Optional in {@code insert}.  When not specified, the system tries to generate it
207         * assuming the current country. (Which will still be null if the number is not valid.)
208         * <p>TYPE: String</p>
209         */
210        public static final String COLUMN_E164_NUMBER = "e164_number";
211    }
212
213    /** @hide */
214    public static final String METHOD_IS_BLOCKED = "is_blocked";
215
216    /** @hide */
217    public static final String METHOD_UNBLOCK= "unblock";
218
219    /** @hide */
220    public static final String RES_NUMBER_IS_BLOCKED = "blocked";
221
222    /** @hide */
223    public static final String RES_NUM_ROWS_DELETED = "num_deleted";
224
225    /** @hide */
226    public static final String METHOD_CAN_CURRENT_USER_BLOCK_NUMBERS =
227            "can_current_user_block_numbers";
228
229    /** @hide */
230    public static final String RES_CAN_BLOCK_NUMBERS = "can_block";
231
232    /** @hide */
233    public static final String RES_ENHANCED_SETTING_IS_ENABLED = "enhanced_setting_enabled";
234
235    /** @hide */
236    public static final String RES_SHOW_EMERGENCY_CALL_NOTIFICATION =
237            "show_emergency_call_notification";
238
239    /** @hide */
240    public static final String EXTRA_ENHANCED_SETTING_KEY = "extra_enhanced_setting_key";
241
242    /** @hide */
243    public static final String EXTRA_ENHANCED_SETTING_VALUE = "extra_enhanced_setting_value";
244
245    /** @hide */
246    public static final String EXTRA_CONTACT_EXIST = "extra_contact_exist";
247
248    /** @hide */
249    public static final String EXTRA_CALL_PRESENTATION = "extra_call_presentation";
250
251    /**
252     * Returns whether a given number is in the blocked list.
253     *
254     * <p> This matches the {@code phoneNumber} against the
255     * {@link BlockedNumbers#COLUMN_ORIGINAL_NUMBER} column, and the E164 representation of the
256     * {@code phoneNumber} with the {@link BlockedNumbers#COLUMN_E164_NUMBER} column.
257     *
258     * <p> Note that if the {@link #canCurrentUserBlockNumbers} is {@code false} for the user
259     * context {@code context}, this method will throw a {@link SecurityException}.
260     *
261     * @return {@code true} if the {@code phoneNumber} is blocked.
262     */
263    @WorkerThread
264    public static boolean isBlocked(Context context, String phoneNumber) {
265        try {
266            final Bundle res = context.getContentResolver().call(
267                    AUTHORITY_URI, METHOD_IS_BLOCKED, phoneNumber, null);
268            return res != null && res.getBoolean(RES_NUMBER_IS_BLOCKED, false);
269        } catch (NullPointerException | IllegalArgumentException ex) {
270            // The content resolver can throw an NPE or IAE; we don't want to crash Telecom if
271            // either of these happen.
272            Log.w(null, "isBlocked: provider not ready.");
273            return false;
274        }
275    }
276
277    /**
278     * Unblocks the {@code phoneNumber} if it is blocked.
279     *
280     * <p> This deletes all rows where the {@code phoneNumber} matches the
281     * {@link BlockedNumbers#COLUMN_ORIGINAL_NUMBER} column or the E164 representation of the
282     * {@code phoneNumber} matches the {@link BlockedNumbers#COLUMN_E164_NUMBER} column.
283     *
284     * <p>To delete rows based on exact match with specific columns such as
285     * {@link BlockedNumbers#COLUMN_ID} use
286     * {@link android.content.ContentProvider#delete(Uri, String, String[])} with
287     * {@link BlockedNumbers#CONTENT_URI} URI.
288     *
289     * <p> Note that if the {@link #canCurrentUserBlockNumbers} is {@code false} for the user
290     * context {@code context}, this method will throw a {@link SecurityException}.
291     *
292     * @return the number of rows deleted in the blocked number provider as a result of unblock.
293     */
294    @WorkerThread
295    public static int unblock(Context context, String phoneNumber) {
296        final Bundle res = context.getContentResolver().call(
297                AUTHORITY_URI, METHOD_UNBLOCK, phoneNumber, null);
298        return res.getInt(RES_NUM_ROWS_DELETED, 0);
299    }
300
301    /**
302     * Checks if blocking numbers is supported for the current user.
303     * <p> Typically, blocking numbers is only supported for one user at a time.
304     *
305     * @return {@code true} if the current user can block numbers.
306     */
307    public static boolean canCurrentUserBlockNumbers(Context context) {
308        try {
309            final Bundle res = context.getContentResolver().call(
310                    AUTHORITY_URI, METHOD_CAN_CURRENT_USER_BLOCK_NUMBERS, null, null);
311            return res != null && res.getBoolean(RES_CAN_BLOCK_NUMBERS, false);
312        } catch (NullPointerException | IllegalArgumentException ex) {
313            // The content resolver can throw an NPE or IAE; we don't want to crash Telecom if
314            // either of these happen.
315            Log.w(null, "canCurrentUserBlockNumbers: provider not ready.");
316            return false;
317        }
318    }
319
320    /**
321     * <p>
322     * The contract between the blockednumber provider and the system.
323     * </p>
324     * <p>This is a wrapper over {@link BlockedNumberContract} that also manages the blocking
325     * behavior when the user contacts emergency services. See
326     * {@link #notifyEmergencyContact(Context)} for details. All methods are protected by
327     * {@link android.Manifest.permission#READ_BLOCKED_NUMBERS} and
328     * {@link android.Manifest.permission#WRITE_BLOCKED_NUMBERS} appropriately which ensure that
329     * only system can access the methods defined here.
330     * </p>
331     * @hide
332     */
333    public static class SystemContract {
334        /**
335         * A protected broadcast intent action for letting components with
336         * {@link android.Manifest.permission#READ_BLOCKED_NUMBERS} know that the block suppression
337         * status as returned by {@link #getBlockSuppressionStatus(Context)} has been updated.
338         */
339        public static final String ACTION_BLOCK_SUPPRESSION_STATE_CHANGED =
340                "android.provider.action.BLOCK_SUPPRESSION_STATE_CHANGED";
341
342        public static final String METHOD_NOTIFY_EMERGENCY_CONTACT = "notify_emergency_contact";
343
344        public static final String METHOD_END_BLOCK_SUPPRESSION = "end_block_suppression";
345
346        public static final String METHOD_SHOULD_SYSTEM_BLOCK_NUMBER = "should_system_block_number";
347
348        public static final String METHOD_GET_BLOCK_SUPPRESSION_STATUS =
349                "get_block_suppression_status";
350
351        public static final String METHOD_SHOULD_SHOW_EMERGENCY_CALL_NOTIFICATION =
352                "should_show_emergency_call_notification";
353
354        public static final String RES_IS_BLOCKING_SUPPRESSED = "blocking_suppressed";
355
356        public static final String RES_BLOCKING_SUPPRESSED_UNTIL_TIMESTAMP =
357                "blocking_suppressed_until_timestamp";
358
359        public static final String METHOD_GET_ENHANCED_BLOCK_SETTING = "get_enhanced_block_setting";
360        public static final String METHOD_SET_ENHANCED_BLOCK_SETTING = "set_enhanced_block_setting";
361
362        /* Preference key of block numbers not in contacts setting. */
363        public static final String ENHANCED_SETTING_KEY_BLOCK_UNREGISTERED =
364                "block_numbers_not_in_contacts_setting";
365        /* Preference key of block private number calls setting. */
366        public static final String ENHANCED_SETTING_KEY_BLOCK_PRIVATE =
367                "block_private_number_calls_setting";
368        /* Preference key of block payphone calls setting. */
369        public static final String ENHANCED_SETTING_KEY_BLOCK_PAYPHONE =
370                "block_payphone_calls_setting";
371        /* Preference key of block unknown calls setting. */
372        public static final String ENHANCED_SETTING_KEY_BLOCK_UNKNOWN =
373                "block_unknown_calls_setting";
374        /* Preference key for whether should show an emergency call notification. */
375        public static final String ENHANCED_SETTING_KEY_SHOW_EMERGENCY_CALL_NOTIFICATION =
376                "show_emergency_call_notification";
377
378        /**
379         * Notifies the provider that emergency services were contacted by the user.
380         * <p> This results in {@link #shouldSystemBlockNumber} returning {@code false} independent
381         * of the contents of the provider for a duration defined by
382         * {@link android.telephony.CarrierConfigManager#KEY_DURATION_BLOCKING_DISABLED_AFTER_EMERGENCY_INT}
383         * the provider unless {@link #endBlockSuppression(Context)} is called.
384         */
385        public static void notifyEmergencyContact(Context context) {
386            try {
387                context.getContentResolver().call(
388                        AUTHORITY_URI, METHOD_NOTIFY_EMERGENCY_CONTACT, null, null);
389            } catch (NullPointerException | IllegalArgumentException ex) {
390                // The content resolver can throw an NPE or IAE; we don't want to crash Telecom if
391                // either of these happen.
392                Log.w(null, "notifyEmergencyContact: provider not ready.");
393            }
394        }
395
396        /**
397         * Notifies the provider to disable suppressing blocking. If emergency services were not
398         * contacted recently at all, calling this method is a no-op.
399         */
400        public static void endBlockSuppression(Context context) {
401            context.getContentResolver().call(
402                    AUTHORITY_URI, METHOD_END_BLOCK_SUPPRESSION, null, null);
403        }
404
405        /**
406         * Returns {@code true} if {@code phoneNumber} is blocked taking
407         * {@link #notifyEmergencyContact(Context)} into consideration. If emergency services
408         * have not been contacted recently and enhanced call blocking not been enabled, this
409         * method is equivalent to {@link #isBlocked(Context, String)}.
410         *
411         * @param context the context of the caller.
412         * @param phoneNumber the number to check.
413         * @param extras the extra attribute of the number.
414         * @return {@code true} if should block the number. {@code false} otherwise.
415         */
416        public static boolean shouldSystemBlockNumber(Context context, String phoneNumber,
417                Bundle extras) {
418            try {
419                final Bundle res = context.getContentResolver().call(
420                        AUTHORITY_URI, METHOD_SHOULD_SYSTEM_BLOCK_NUMBER, phoneNumber, extras);
421                return res != null && res.getBoolean(RES_NUMBER_IS_BLOCKED, false);
422            } catch (NullPointerException | IllegalArgumentException ex) {
423                // The content resolver can throw an NPE or IAE; we don't want to crash Telecom if
424                // either of these happen.
425                Log.w(null, "shouldSystemBlockNumber: provider not ready.");
426                return false;
427            }
428        }
429
430        /**
431         * Returns the current status of block suppression.
432         */
433        public static BlockSuppressionStatus getBlockSuppressionStatus(Context context) {
434            final Bundle res = context.getContentResolver().call(
435                    AUTHORITY_URI, METHOD_GET_BLOCK_SUPPRESSION_STATUS, null, null);
436            return new BlockSuppressionStatus(res.getBoolean(RES_IS_BLOCKING_SUPPRESSED, false),
437                    res.getLong(RES_BLOCKING_SUPPRESSED_UNTIL_TIMESTAMP, 0));
438        }
439
440        /**
441         * Check whether should show the emergency call notification.
442         *
443         * @param context the context of the caller.
444         * @return {@code true} if should show emergency call notification. {@code false} otherwise.
445         */
446        public static boolean shouldShowEmergencyCallNotification(Context context) {
447            try {
448                final Bundle res = context.getContentResolver().call(
449                        AUTHORITY_URI, METHOD_SHOULD_SHOW_EMERGENCY_CALL_NOTIFICATION, null, null);
450                return res != null && res.getBoolean(RES_SHOW_EMERGENCY_CALL_NOTIFICATION, false);
451            } catch (NullPointerException | IllegalArgumentException ex) {
452                // The content resolver can throw an NPE or IAE; we don't want to crash Telecom if
453                // either of these happen.
454                Log.w(null, "shouldShowEmergencyCallNotification: provider not ready.");
455                return false;
456            }
457        }
458
459        /**
460         * Check whether the enhanced block setting is enabled.
461         *
462         * @param context the context of the caller.
463         * @param key the key of the setting to check, can be
464         *        {@link #ENHANCED_SETTING_KEY_BLOCK_UNREGISTERED}
465         *        {@link #ENHANCED_SETTING_KEY_BLOCK_PRIVATE}
466         *        {@link #ENHANCED_SETTING_KEY_BLOCK_PAYPHONE}
467         *        {@link #ENHANCED_SETTING_KEY_BLOCK_UNKNOWN}
468         *        {@link #ENHANCED_SETTING_KEY_EMERGENCY_CALL_NOTIFICATION_SHOWING}
469         * @return {@code true} if the setting is enabled. {@code false} otherwise.
470         */
471        public static boolean getEnhancedBlockSetting(Context context, String key) {
472            Bundle extras = new Bundle();
473            extras.putString(EXTRA_ENHANCED_SETTING_KEY, key);
474            try {
475                final Bundle res = context.getContentResolver().call(
476                        AUTHORITY_URI, METHOD_GET_ENHANCED_BLOCK_SETTING, null, extras);
477                return res != null && res.getBoolean(RES_ENHANCED_SETTING_IS_ENABLED, false);
478            } catch (NullPointerException | IllegalArgumentException ex) {
479                // The content resolver can throw an NPE or IAE; we don't want to crash Telecom if
480                // either of these happen.
481                Log.w(null, "getEnhancedBlockSetting: provider not ready.");
482                return false;
483            }
484        }
485
486        /**
487         * Set the enhanced block setting enabled status.
488         *
489         * @param context the context of the caller.
490         * @param key the key of the setting to set, can be
491         *        {@link #ENHANCED_SETTING_KEY_BLOCK_UNREGISTERED}
492         *        {@link #ENHANCED_SETTING_KEY_BLOCK_PRIVATE}
493         *        {@link #ENHANCED_SETTING_KEY_BLOCK_PAYPHONE}
494         *        {@link #ENHANCED_SETTING_KEY_BLOCK_UNKNOWN}
495         *        {@link #ENHANCED_SETTING_KEY_EMERGENCY_CALL_NOTIFICATION_SHOWING}
496         * @param value the enabled statue of the setting to set.
497         */
498        public static void setEnhancedBlockSetting(Context context, String key, boolean value) {
499            Bundle extras = new Bundle();
500            extras.putString(EXTRA_ENHANCED_SETTING_KEY, key);
501            extras.putBoolean(EXTRA_ENHANCED_SETTING_VALUE, value);
502            context.getContentResolver().call(AUTHORITY_URI, METHOD_SET_ENHANCED_BLOCK_SETTING,
503                    null, extras);
504        }
505
506        /**
507         * Represents the current status of
508         * {@link #shouldSystemBlockNumber(Context, String, Bundle)}. If emergency services
509         * have been contacted recently, {@link #isSuppressed} is {@code true}, and blocking
510         * is disabled until the timestamp {@link #untilTimestampMillis}.
511         */
512        public static class BlockSuppressionStatus {
513            public final boolean isSuppressed;
514            /**
515             * Timestamp in milliseconds from epoch.
516             */
517            public final long untilTimestampMillis;
518
519            public BlockSuppressionStatus(boolean isSuppressed, long untilTimestampMillis) {
520                this.isSuppressed = isSuppressed;
521                this.untilTimestampMillis = untilTimestampMillis;
522            }
523        }
524    }
525}
526