content: /*
   * Copyright (C) 2016 The Android Open Source Project
   *
   * Licensed under the Apache License, Version 2.0 (the "License");
   * you may not use this file except in compliance with the License.
   * You may obtain a copy of the License at
   *
   *      http://www.apache.org/licenses/LICENSE-2.0
   *
   * Unless required by applicable law or agreed to in writing, software
   * distributed under the License is distributed on an "AS IS" BASIS,
   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
   * See the License for the specific language governing permissions and
   * limitations under the License
   */
  package android.provider;
  
  import android.annotation.IntDef;
  import android.annotation.WorkerThread;
  import android.content.Context;
  import android.net.Uri;
  import android.os.Bundle;
  import android.telecom.Log;
  
  import java.lang.annotation.Retention;
  import java.lang.annotation.RetentionPolicy;
  
  /**
   * <p>
   * The contract between the blockednumber provider and applications. Contains definitions for
   * the supported URIs and columns.
   * </p>
   *
   * <h3> Overview </h3>
   * <p>
   * The content provider exposes a table containing blocked numbers. The columns and URIs for
   * accessing this table are defined by the {@link BlockedNumbers} class. Messages, and calls from
   * blocked numbers are discarded by the platform. Notifications upon provider changes can be
   * received using a {@link android.database.ContentObserver}.
   * </p>
   * <p>
   * The platform will not block messages, and calls from emergency numbers as defined by
   * {@link android.telephony.PhoneNumberUtils#isEmergencyNumber(String)}. If the user contacts
   * emergency services, number blocking is disabled by the platform for a duration defined by
   * {@link android.telephony.CarrierConfigManager#KEY_DURATION_BLOCKING_DISABLED_AFTER_EMERGENCY_INT}.
   * </p>
   *
   * <h3> Permissions </h3>
   * <p>
   * Only the system, the default SMS application, and the default phone app
   * (See {@link android.telecom.TelecomManager#getDefaultDialerPackage()}), and carrier apps
   * (See {@link android.service.carrier.CarrierService}) can read, and write to the blockednumber
   * provider. However, {@link #canCurrentUserBlockNumbers(Context)} can be accessed by any
   * application.
   * </p>
   *
   * <h3> Data </h3>
   * <p>
   * Other than regular phone numbers, the blocked number provider can also store addresses (such
   * as email) from which a user can receive messages, and calls. The blocked numbers are stored
   * in the {@link BlockedNumbers#COLUMN_ORIGINAL_NUMBER} column. A normalized version of phone
   * numbers (if normalization is possible) is stored in {@link BlockedNumbers#COLUMN_E164_NUMBER}
   * column. The platform blocks calls, and messages from an address if it is present in in the
   * {@link BlockedNumbers#COLUMN_ORIGINAL_NUMBER} column or if the E164 version of the address
   * matches the {@link BlockedNumbers#COLUMN_E164_NUMBER} column.
   * </p>
   *
   * <h3> Operations </h3>
   * <dl>
   * <dt><b>Insert</b></dt>
   * <dd>
   * <p>
   * {@link BlockedNumbers#COLUMN_ORIGINAL_NUMBER} is a required column that needs to be populated.
   * Apps can optionally provide the {@link BlockedNumbers#COLUMN_E164_NUMBER} which is the phone
   * number's E164 representation. The provider automatically populates this column if the app does
   * not provide it. Note that this column is not populated if normalization fails or if the address
   * is not a phone number (eg: email).
   * <p>
   * Attempting to insert an existing blocked number (same
   * {@link BlockedNumbers#COLUMN_ORIGINAL_NUMBER} column) will result in replacing the existing
   * blocked number.
   * <p>
   * Examples:
   * <pre>
   * ContentValues values = new ContentValues();
   * values.put(BlockedNumbers.COLUMN_ORIGINAL_NUMBER, "1234567890");
   * Uri uri = getContentResolver().insert(BlockedNumbers.CONTENT_URI, values);
   * </pre>
   * <pre>
   * ContentValues values = new ContentValues();
   * values.put(BlockedNumbers.COLUMN_ORIGINAL_NUMBER, "1234567890");
   * values.put(BlockedNumbers.COLUMN_E164_NUMBER, "+11234567890");
   * Uri uri = getContentResolver().insert(BlockedNumbers.CONTENT_URI, values);
   * </pre>
   * <pre>
   * ContentValues values = new ContentValues();
   * values.put(BlockedNumbers.COLUMN_ORIGINAL_NUMBER, "12345@abdcde.com");
   * Uri uri = getContentResolver().insert(BlockedNumbers.CONTENT_URI, values);
   * </pre>
   * </p>
   * </dd>
   * <dt><b>Update</b></dt>
   * <dd>
   * <p>
   * Updates are not supported. Use Delete, and Insert instead.
   * </p>
   * </dd>
   * <dt><b>Delete</b></dt>
   * <dd>
   * <p>
   * Deletions can be performed as follows:
   * <pre>
   * ContentValues values = new ContentValues();
   * values.put(BlockedNumbers.COLUMN_ORIGINAL_NUMBER, "1234567890");
   * Uri uri = getContentResolver().insert(BlockedNumbers.CONTENT_URI, values);
   * getContentResolver().delete(uri, null, null);
   * </pre>
   * To check if a particular number is blocked, use the method
   * {@link #isBlocked(Context, String)}.
   * </p>
   * </dd>
   * <dt><b>Query</b></dt>
   * <dd>
   * <p>
   * All blocked numbers can be enumerated as follows:
   * <pre>
   * Cursor c = getContentResolver().query(BlockedNumbers.CONTENT_URI,
   *          new String[]{BlockedNumbers.COLUMN_ID, BlockedNumbers.COLUMN_ORIGINAL_NUMBER,
   *          BlockedNumbers.COLUMN_E164_NUMBER}, null, null, null);
   * </pre>
   * </p>
   * </dd>
   * <dt><b>Unblock</b></dt>
   * <dd>
   * <p>
   * Use the method {@link #unblock(Context, String)} to unblock numbers.
   * </p>
   * </dd>
   *
   * <h3> Multi-user </h3>
   * <p>
   * Apps must use the method {@link #canCurrentUserBlockNumbers(Context)} before performing any
   * operation on the blocked number provider. If {@link #canCurrentUserBlockNumbers(Context)} returns
   * {@code false}, all operations on the provider will fail with a {@link SecurityException}. The
   * platform will block calls, and messages from numbers in the provider independent of the current
   * user.
   * </p>
   */
  public class BlockedNumberContract {
      private BlockedNumberContract() {
      }
  
      /** The authority for the blocked number provider */
      public static final String AUTHORITY = "com.android.blockednumber";
  
      /** A content:// style uri to the authority for the blocked number provider */
      public static final Uri AUTHORITY_URI = Uri.parse("content://" + AUTHORITY);
  
      private static final String LOG_TAG = BlockedNumberContract.class.getSimpleName();
  
      /**
       * Constants to interact with the blocked numbers list.
       */
      public static class BlockedNumbers {
          private BlockedNumbers() {
          }
  
          /**
           * Content URI for the blocked numbers.
           * <h3> Supported operations </h3>
           * <p> blocked
           * <ul>
           * <li> query
           * <li> delete
           * <li> insert
           * </ul>
           * <p> blocked/ID
           * <ul>
           * <li> query (selection is not supported)
           * <li> delete (selection is not supported)
           * </ul>
           */
          public static final Uri CONTENT_URI = Uri.withAppendedPath(AUTHORITY_URI, "blocked");
  
          /**
           * The MIME type of {@link #CONTENT_URI} itself providing a directory of blocked phone
           * numbers.
           */
          public static final String CONTENT_TYPE = "vnd.android.cursor.dir/blocked_number";
  
          /**
           * The MIME type of a blocked phone number under {@link #CONTENT_URI}.
           */
          public static final String CONTENT_ITEM_TYPE = "vnd.android.cursor.item/blocked_number";
  
          /**
           * Auto-generated ID field which monotonically increases.
           * <p>TYPE: long</p>
           */
          public static final String COLUMN_ID = "_id";
  
          /**
           * Phone number to block.
           * <p>Must be specified in {@code insert}.
           * <p>TYPE: String</p>
           */
          public static final String COLUMN_ORIGINAL_NUMBER = "original_number";
  
          /**
           * Phone number to block.  The system generates it from {@link #COLUMN_ORIGINAL_NUMBER}
           * by removing all formatting characters.
           * <p>Optional in {@code insert}.  When not specified, the system tries to generate it
           * assuming the current country. (Which will still be null if the number is not valid.)
           * <p>TYPE: String</p>
           */
          public static final String COLUMN_E164_NUMBER = "e164_number";
      }
  
      /** @hide */
      public static final String METHOD_IS_BLOCKED = "is_blocked";
  
      /** @hide */
      public static final String METHOD_UNBLOCK= "unblock";
  
      /** @hide */
      public static final String RES_NUMBER_IS_BLOCKED = "blocked";
  
      /** @hide */
      @Retention(RetentionPolicy.SOURCE)
      @IntDef(
              prefix = { "STATUS_" },
              value = {STATUS_NOT_BLOCKED, STATUS_BLOCKED_IN_LIST, STATUS_BLOCKED_RESTRICTED,
                      STATUS_BLOCKED_UNKNOWN_NUMBER, STATUS_BLOCKED_PAYPHONE,
                      STATUS_BLOCKED_NOT_IN_CONTACTS})
      public @interface BlockStatus {}
  
      /**
       * Integer reason code used with {@link #RES_BLOCK_STATUS} to indicate that a call was not
       * blocked.
       * @hide
       */
      public static final int STATUS_NOT_BLOCKED = 0;
  
      /**
       * Integer reason code used with {@link #RES_BLOCK_STATUS} to indicate that a call was blocked
       * because it is in the list of blocked numbers maintained by the provider.
       * @hide
       */
      public static final int STATUS_BLOCKED_IN_LIST = 1;
  
      /**
       * Integer reason code used with {@link #RES_BLOCK_STATUS} to indicate that a call was blocked
       * because it is from a restricted number.
       * @hide
       */
      public static final int STATUS_BLOCKED_RESTRICTED = 2;
  
      /**
       * Integer reason code used with {@link #RES_BLOCK_STATUS} to indicate that a call was blocked
       * because it is from an unknown number.
       * @hide
       */
      public static final int STATUS_BLOCKED_UNKNOWN_NUMBER = 3;
  
      /**
       * Integer reason code used with {@link #RES_BLOCK_STATUS} to indicate that a call was blocked
       * because it is from a pay phone.
       * @hide
       */
      public static final int STATUS_BLOCKED_PAYPHONE = 4;
  
      /**
       * Integer reason code used with {@link #RES_BLOCK_STATUS} to indicate that a call was blocked
       * because it is from a number not in the users contacts.
       * @hide
       */
      public static final int STATUS_BLOCKED_NOT_IN_CONTACTS = 5;
  
      /**
       * Integer reason indicating whether a call was blocked, and if so why.
       * @hide
       */
      public static final String RES_BLOCK_STATUS = "block_status";
  
      /** @hide */
      public static final String RES_NUM_ROWS_DELETED = "num_deleted";
  
      /** @hide */
      public static final String METHOD_CAN_CURRENT_USER_BLOCK_NUMBERS =
              "can_current_user_block_numbers";
  
      /** @hide */
      public static final String RES_CAN_BLOCK_NUMBERS = "can_block";
  
      /** @hide */
      public static final String RES_ENHANCED_SETTING_IS_ENABLED = "enhanced_setting_enabled";
  
      /** @hide */
      public static final String RES_SHOW_EMERGENCY_CALL_NOTIFICATION =
              "show_emergency_call_notification";
  
      /** @hide */
      public static final String EXTRA_ENHANCED_SETTING_KEY = "extra_enhanced_setting_key";
  
      /** @hide */
      public static final String EXTRA_ENHANCED_SETTING_VALUE = "extra_enhanced_setting_value";
  
      /** @hide */
      public static final String EXTRA_CONTACT_EXIST = "extra_contact_exist";
  
      /** @hide */
      public static final String EXTRA_CALL_PRESENTATION = "extra_call_presentation";
  
      /**
       * Returns whether a given number is in the blocked list.
       *
       * <p> This matches the {@code phoneNumber} against the
       * {@link BlockedNumbers#COLUMN_ORIGINAL_NUMBER} column, and the E164 representation of the
       * {@code phoneNumber} with the {@link BlockedNumbers#COLUMN_E164_NUMBER} column.
       *
       * <p> Note that if the {@link #canCurrentUserBlockNumbers} is {@code false} for the user
       * context {@code context}, this method will throw a {@link SecurityException}.
       *
       * @return {@code true} if the {@code phoneNumber} is blocked.
       */
      @WorkerThread
      public static boolean isBlocked(Context context, String phoneNumber) {
          try {
              final Bundle res = context.getContentResolver().call(
                      AUTHORITY_URI, METHOD_IS_BLOCKED, phoneNumber, null);
              boolean isBlocked = res != null && res.getBoolean(RES_NUMBER_IS_BLOCKED, false);
              Log.d(LOG_TAG, "isBlocked: phoneNumber=%s, isBlocked=%b", Log.piiHandle(phoneNumber),
                      isBlocked);
              return isBlocked;
          } catch (NullPointerException | IllegalArgumentException ex) {
              // The content resolver can throw an NPE or IAE; we don't want to crash Telecom if
              // either of these happen.
              Log.w(null, "isBlocked: provider not ready.");
              return false;
          }
      }
  
      /**
       * Unblocks the {@code phoneNumber} if it is blocked.
       *
       * <p> This deletes all rows where the {@code phoneNumber} matches the
       * {@link BlockedNumbers#COLUMN_ORIGINAL_NUMBER} column or the E164 representation of the
       * {@code phoneNumber} matches the {@link BlockedNumbers#COLUMN_E164_NUMBER} column.
       *
       * <p>To delete rows based on exact match with specific columns such as
       * {@link BlockedNumbers#COLUMN_ID} use
       * {@link android.content.ContentProvider#delete(Uri, String, String[])} with
       * {@link BlockedNumbers#CONTENT_URI} URI.
       *
       * <p> Note that if the {@link #canCurrentUserBlockNumbers} is {@code false} for the user
       * context {@code context}, this method will throw a {@link SecurityException}.
       *
       * @return the number of rows deleted in the blocked number provider as a result of unblock.
       */
      @WorkerThread
      public static int unblock(Context context, String phoneNumber) {
          Log.d(LOG_TAG, "unblock: phoneNumber=%s", Log.piiHandle(phoneNumber));
          final Bundle res = context.getContentResolver().call(
                  AUTHORITY_URI, METHOD_UNBLOCK, phoneNumber, null);
          return res.getInt(RES_NUM_ROWS_DELETED, 0);
      }
  
      /**
       * Checks if blocking numbers is supported for the current user.
       * <p> Typically, blocking numbers is only supported for one user at a time.
       *
       * @return {@code true} if the current user can block numbers.
       */
      public static boolean canCurrentUserBlockNumbers(Context context) {
          try {
              final Bundle res = context.getContentResolver().call(
                      AUTHORITY_URI, METHOD_CAN_CURRENT_USER_BLOCK_NUMBERS, null, null);
              return res != null && res.getBoolean(RES_CAN_BLOCK_NUMBERS, false);
          } catch (NullPointerException | IllegalArgumentException ex) {
              // The content resolver can throw an NPE or IAE; we don't want to crash Telecom if
              // either of these happen.
              Log.w(null, "canCurrentUserBlockNumbers: provider not ready.");
              return false;
          }
      }
  
      /**
       * <p>
       * The contract between the blockednumber provider and the system.
       * </p>
       * <p>This is a wrapper over {@link BlockedNumberContract} that also manages the blocking
       * behavior when the user contacts emergency services. See
       * {@link #notifyEmergencyContact(Context)} for details. All methods are protected by
       * {@link android.Manifest.permission#READ_BLOCKED_NUMBERS} and
       * {@link android.Manifest.permission#WRITE_BLOCKED_NUMBERS} appropriately which ensure that
       * only system can access the methods defined here.
       * </p>
       * @hide
       */
      public static class SystemContract {
          /**
           * A protected broadcast intent action for letting components with
           * {@link android.Manifest.permission#READ_BLOCKED_NUMBERS} know that the block suppression
           * status as returned by {@link #getBlockSuppressionStatus(Context)} has been updated.
           */
          public static final String ACTION_BLOCK_SUPPRESSION_STATE_CHANGED =
                  "android.provider.action.BLOCK_SUPPRESSION_STATE_CHANGED";
  
          public static final String METHOD_NOTIFY_EMERGENCY_CONTACT = "notify_emergency_contact";
  
          public static final String METHOD_END_BLOCK_SUPPRESSION = "end_block_suppression";
  
          public static final String METHOD_SHOULD_SYSTEM_BLOCK_NUMBER = "should_system_block_number";
  
          public static final String METHOD_GET_BLOCK_SUPPRESSION_STATUS =
                  "get_block_suppression_status";
  
          public static final String METHOD_SHOULD_SHOW_EMERGENCY_CALL_NOTIFICATION =
                  "should_show_emergency_call_notification";
  
          public static final String RES_IS_BLOCKING_SUPPRESSED = "blocking_suppressed";
  
          public static final String RES_BLOCKING_SUPPRESSED_UNTIL_TIMESTAMP =
                  "blocking_suppressed_until_timestamp";
  
          public static final String METHOD_GET_ENHANCED_BLOCK_SETTING = "get_enhanced_block_setting";
          public static final String METHOD_SET_ENHANCED_BLOCK_SETTING = "set_enhanced_block_setting";
  
          /* Preference key of block numbers not in contacts setting. */
          public static final String ENHANCED_SETTING_KEY_BLOCK_UNREGISTERED =
                  "block_numbers_not_in_contacts_setting";
          /* Preference key of block private number calls setting. */
          public static final String ENHANCED_SETTING_KEY_BLOCK_PRIVATE =
                  "block_private_number_calls_setting";
          /* Preference key of block payphone calls setting. */
          public static final String ENHANCED_SETTING_KEY_BLOCK_PAYPHONE =
                  "block_payphone_calls_setting";
          /* Preference key of block unknown calls setting. */
          public static final String ENHANCED_SETTING_KEY_BLOCK_UNKNOWN =
                  "block_unknown_calls_setting";
          /* Preference key for whether should show an emergency call notification. */
          public static final String ENHANCED_SETTING_KEY_SHOW_EMERGENCY_CALL_NOTIFICATION =
                  "show_emergency_call_notification";
  
          /**
           * Notifies the provider that emergency services were contacted by the user.
           * <p> This results in {@link #shouldSystemBlockNumber} returning {@code false} independent
           * of the contents of the provider for a duration defined by
           * {@link android.telephony.CarrierConfigManager#KEY_DURATION_BLOCKING_DISABLED_AFTER_EMERGENCY_INT}
           * the provider unless {@link #endBlockSuppression(Context)} is called.
           */
          public static void notifyEmergencyContact(Context context) {
              try {
                  Log.i(LOG_TAG, "notifyEmergencyContact; caller=%s", context.getOpPackageName());
                  context.getContentResolver().call(
                          AUTHORITY_URI, METHOD_NOTIFY_EMERGENCY_CONTACT, null, null);
              } catch (NullPointerException | IllegalArgumentException ex) {
                  // The content resolver can throw an NPE or IAE; we don't want to crash Telecom if
                  // either of these happen.
                  Log.w(null, "notifyEmergencyContact: provider not ready.");
              }
          }
  
          /**
           * Notifies the provider to disable suppressing blocking. If emergency services were not
           * contacted recently at all, calling this method is a no-op.
           */
          public static void endBlockSuppression(Context context) {
              String caller = context.getOpPackageName();
              Log.i(LOG_TAG, "endBlockSuppression: caller=%s", caller);
              context.getContentResolver().call(
                      AUTHORITY_URI, METHOD_END_BLOCK_SUPPRESSION, null, null);
          }
  
          /**
           * Returns {@code true} if {@code phoneNumber} is blocked taking
           * {@link #notifyEmergencyContact(Context)} into consideration. If emergency services
           * have not been contacted recently and enhanced call blocking not been enabled, this
           * method is equivalent to {@link #isBlocked(Context, String)}.
           *
           * @param context the context of the caller.
           * @param phoneNumber the number to check.
           * @param extras the extra attribute of the number.
           * @return result code indicating if the number should be blocked, and if so why.
           *         Valid values are: {@link #STATUS_NOT_BLOCKED}, {@link #STATUS_BLOCKED_IN_LIST},
           *         {@link #STATUS_BLOCKED_NOT_IN_CONTACTS}, {@link #STATUS_BLOCKED_PAYPHONE},
           *         {@link #STATUS_BLOCKED_RESTRICTED}, {@link #STATUS_BLOCKED_UNKNOWN_NUMBER}.
           */
          public static int shouldSystemBlockNumber(Context context, String phoneNumber,
                  Bundle extras) {
              try {
                  String caller = context.getOpPackageName();
                  final Bundle res = context.getContentResolver().call(
                          AUTHORITY_URI, METHOD_SHOULD_SYSTEM_BLOCK_NUMBER, phoneNumber, extras);
                  int blockResult = res != null ? res.getInt(RES_BLOCK_STATUS, STATUS_NOT_BLOCKED) :
                          BlockedNumberContract.STATUS_NOT_BLOCKED;
                  Log.d(LOG_TAG, "shouldSystemBlockNumber: number=%s, caller=%s, result=%s",
                          Log.piiHandle(phoneNumber), caller, blockStatusToString(blockResult));
                  return blockResult;
              } catch (NullPointerException | IllegalArgumentException ex) {
                  // The content resolver can throw an NPE or IAE; we don't want to crash Telecom if
                  // either of these happen.
                  Log.w(null, "shouldSystemBlockNumber: provider not ready.");
                  return BlockedNumberContract.STATUS_NOT_BLOCKED;
              }
          }
  
          /**
           * Returns the current status of block suppression.
           */
          public static BlockSuppressionStatus getBlockSuppressionStatus(Context context) {
              final Bundle res = context.getContentResolver().call(
                      AUTHORITY_URI, METHOD_GET_BLOCK_SUPPRESSION_STATUS, null, null);
              BlockSuppressionStatus blockSuppressionStatus = new BlockSuppressionStatus(
                      res.getBoolean(RES_IS_BLOCKING_SUPPRESSED, false),
                      res.getLong(RES_BLOCKING_SUPPRESSED_UNTIL_TIMESTAMP, 0));
              Log.d(LOG_TAG, "getBlockSuppressionStatus: caller=%s, status=%s",
                      context.getOpPackageName(), blockSuppressionStatus);
              return blockSuppressionStatus;
          }
  
          /**
           * Check whether should show the emergency call notification.
           *
           * @param context the context of the caller.
           * @return {@code true} if should show emergency call notification. {@code false} otherwise.
           */
          public static boolean shouldShowEmergencyCallNotification(Context context) {
              try {
                  final Bundle res = context.getContentResolver().call(
                          AUTHORITY_URI, METHOD_SHOULD_SHOW_EMERGENCY_CALL_NOTIFICATION, null, null);
                  return res != null && res.getBoolean(RES_SHOW_EMERGENCY_CALL_NOTIFICATION, false);
              } catch (NullPointerException | IllegalArgumentException ex) {
                  // The content resolver can throw an NPE or IAE; we don't want to crash Telecom if
                  // either of these happen.
                  Log.w(null, "shouldShowEmergencyCallNotification: provider not ready.");
                  return false;
              }
          }
  
          /**
           * Check whether the enhanced block setting is enabled.
           *
           * @param context the context of the caller.
           * @param key the key of the setting to check, can be
           *        {@link #ENHANCED_SETTING_KEY_BLOCK_UNREGISTERED}
           *        {@link #ENHANCED_SETTING_KEY_BLOCK_PRIVATE}
           *        {@link #ENHANCED_SETTING_KEY_BLOCK_PAYPHONE}
           *        {@link #ENHANCED_SETTING_KEY_BLOCK_UNKNOWN}
           *        {@link #ENHANCED_SETTING_KEY_EMERGENCY_CALL_NOTIFICATION_SHOWING}
           * @return {@code true} if the setting is enabled. {@code false} otherwise.
           */
          public static boolean getEnhancedBlockSetting(Context context, String key) {
              Bundle extras = new Bundle();
              extras.putString(EXTRA_ENHANCED_SETTING_KEY, key);
              try {
                  final Bundle res = context.getContentResolver().call(
                          AUTHORITY_URI, METHOD_GET_ENHANCED_BLOCK_SETTING, null, extras);
                  return res != null && res.getBoolean(RES_ENHANCED_SETTING_IS_ENABLED, false);
              } catch (NullPointerException | IllegalArgumentException ex) {
                  // The content resolver can throw an NPE or IAE; we don't want to crash Telecom if
                  // either of these happen.
                  Log.w(null, "getEnhancedBlockSetting: provider not ready.");
                  return false;
              }
          }
  
          /**
           * Set the enhanced block setting enabled status.
           *
           * @param context the context of the caller.
           * @param key the key of the setting to set, can be
           *        {@link #ENHANCED_SETTING_KEY_BLOCK_UNREGISTERED}
           *        {@link #ENHANCED_SETTING_KEY_BLOCK_PRIVATE}
           *        {@link #ENHANCED_SETTING_KEY_BLOCK_PAYPHONE}
           *        {@link #ENHANCED_SETTING_KEY_BLOCK_UNKNOWN}
           *        {@link #ENHANCED_SETTING_KEY_EMERGENCY_CALL_NOTIFICATION_SHOWING}
           * @param value the enabled statue of the setting to set.
           */
          public static void setEnhancedBlockSetting(Context context, String key, boolean value) {
              Bundle extras = new Bundle();
              extras.putString(EXTRA_ENHANCED_SETTING_KEY, key);
              extras.putBoolean(EXTRA_ENHANCED_SETTING_VALUE, value);
              context.getContentResolver().call(AUTHORITY_URI, METHOD_SET_ENHANCED_BLOCK_SETTING,
                      null, extras);
          }
  
          /**
           * Converts a block status constant to a string equivalent for logging.
           * @hide
           */
          public static String blockStatusToString(int blockStatus) {
              switch (blockStatus) {
                  case STATUS_NOT_BLOCKED:
                      return "not blocked";
                  case STATUS_BLOCKED_IN_LIST:
                      return "blocked - in list";
                  case STATUS_BLOCKED_RESTRICTED:
                      return "blocked - restricted";
                  case STATUS_BLOCKED_UNKNOWN_NUMBER:
                      return "blocked - unknown";
                  case STATUS_BLOCKED_PAYPHONE:
                      return "blocked - payphone";
                  case STATUS_BLOCKED_NOT_IN_CONTACTS:
                      return "blocked - not in contacts";
              }
              return "unknown";
          }
  
          /**
           * Represents the current status of
           * {@link #shouldSystemBlockNumber(Context, String, Bundle)}. If emergency services
           * have been contacted recently, {@link #isSuppressed} is {@code true}, and blocking
           * is disabled until the timestamp {@link #untilTimestampMillis}.
           */
          public static class BlockSuppressionStatus {
              public final boolean isSuppressed;
              /**
               * Timestamp in milliseconds from epoch.
               */
              public final long untilTimestampMillis;
  
              public BlockSuppressionStatus(boolean isSuppressed, long untilTimestampMillis) {
                  this.isSuppressed = isSuppressed;
                  this.untilTimestampMillis = untilTimestampMillis;
              }
  
              @Override
              public String toString() {
                  return "[BlockSuppressionStatus; isSuppressed=" + isSuppressed + ", until="
                          + untilTimestampMillis + "]";
              }
          }
      }
  }
  