Home | History | Annotate | Download | only in provider
      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 (at) 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 {
    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