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;
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 */
144public class 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 {
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
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
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     */
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         */
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         */
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         */
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         */
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
377            public BlockSuppressionStatus(boolean isSuppressed, long untilTimestampMillis) {
378                this.isSuppressed = isSuppressed;
379                this.untilTimestampMillis = untilTimestampMillis;
380            }
381        }
382    }
383}
384