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 import 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 (at) 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  */
    145 public 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