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