diff options
author | Justin Klaassen <justinklaassen@google.com> | 2017-09-15 17:58:39 -0400 |
---|---|---|
committer | Justin Klaassen <justinklaassen@google.com> | 2017-09-15 17:58:39 -0400 |
commit | 10d07c88d69cc64f73a069163e7ea5ba2519a099 (patch) | |
tree | 8dbd149eb350320a29c3d10e7ad3201de1c5cbee /android/telephony | |
parent | 677516fb6b6f207d373984757d3d9450474b6b00 (diff) | |
download | android-28-10d07c88d69cc64f73a069163e7ea5ba2519a099.tar.gz |
Import Android SDK Platform PI [4335822]
/google/data/ro/projects/android/fetch_artifact \
--bid 4335822 \
--target sdk_phone_armv7-win_sdk \
sdk-repo-linux-sources-4335822.zip
AndroidVersion.ApiLevel has been modified to appear as 28
Change-Id: Ic8f04be005a71c2b9abeaac754d8da8d6f9a2c32
Diffstat (limited to 'android/telephony')
106 files changed, 40048 insertions, 0 deletions
diff --git a/android/telephony/CarrierConfigManager.java b/android/telephony/CarrierConfigManager.java new file mode 100644 index 00000000..689ce954 --- /dev/null +++ b/android/telephony/CarrierConfigManager.java @@ -0,0 +1,1968 @@ +/* + * Copyright (C) 2015 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.telephony; + +import android.annotation.NonNull; +import android.annotation.Nullable; +import android.annotation.RequiresPermission; +import android.annotation.SuppressLint; +import android.annotation.SystemApi; +import android.annotation.SystemService; +import android.content.Context; +import android.os.PersistableBundle; +import android.os.RemoteException; +import android.os.ServiceManager; +import android.service.carrier.CarrierService; + +import com.android.ims.ImsReasonInfo; +import com.android.internal.telephony.ICarrierConfigLoader; + +/** + * Provides access to telephony configuration values that are carrier-specific. + */ +@SystemService(Context.CARRIER_CONFIG_SERVICE) +public class CarrierConfigManager { + private final static String TAG = "CarrierConfigManager"; + + /** + * @hide + */ + public CarrierConfigManager() { + } + + /** + * This intent is broadcast by the system when carrier config changes. + */ + public static final String + ACTION_CARRIER_CONFIG_CHANGED = "android.telephony.action.CARRIER_CONFIG_CHANGED"; + + // Below are the keys used in carrier config bundles. To add a new variable, define the key and + // give it a default value in sDefaults. If you need to ship a per-network override in the + // system image, that can be added in packages/apps/CarrierConfig. + + /** + * This flag specifies whether VoLTE availability is based on provisioning. By default this is + * false. + */ + public static final String + KEY_CARRIER_VOLTE_PROVISIONED_BOOL = "carrier_volte_provisioned_bool"; + + /** + * Flag indicating whether the Phone app should ignore EVENT_SIM_NETWORK_LOCKED + * events from the Sim. + * If true, this will prevent the IccNetworkDepersonalizationPanel from being shown, and + * effectively disable the "Sim network lock" feature. + */ + public static final String + KEY_IGNORE_SIM_NETWORK_LOCKED_EVENTS_BOOL = "ignore_sim_network_locked_events_bool"; + + /** + * When checking if a given number is the voicemail number, if this flag is true + * then in addition to comparing the given number to the voicemail number, we also compare it + * to the mdn. If this flag is false, the given number is only compared to the voicemail number. + * By default this value is false. + */ + public static final String KEY_MDN_IS_ADDITIONAL_VOICEMAIL_NUMBER_BOOL = + "mdn_is_additional_voicemail_number_bool"; + + /** + * Flag indicating whether the Phone app should provide a "Dismiss" button on the SIM network + * unlock screen. The default value is true. If set to false, there will be *no way* to dismiss + * the SIM network unlock screen if you don't enter the correct unlock code. (One important + * consequence: there will be no way to make an Emergency Call if your SIM is network-locked and + * you don't know the PIN.) + */ + public static final String + KEY_SIM_NETWORK_UNLOCK_ALLOW_DISMISS_BOOL = "sim_network_unlock_allow_dismiss_bool"; + + /** Flag indicating if the phone is a world phone */ + public static final String KEY_WORLD_PHONE_BOOL = "world_phone_bool"; + + /** + * Flag to require or skip entitlement checks. + * If true, entitlement checks will be executed if device has been configured for it, + * If false, entitlement checks will be skipped. + */ + public static final String + KEY_REQUIRE_ENTITLEMENT_CHECKS_BOOL = "require_entitlement_checks_bool"; + + /** + * Flag indicating whether radio is to be restarted on error PDP_FAIL_REGULAR_DEACTIVATION + * This is false by default. + */ + public static final String + KEY_RESTART_RADIO_ON_PDP_FAIL_REGULAR_DEACTIVATION_BOOL = + "restart_radio_on_pdp_fail_regular_deactivation_bool"; + + /** + * If true, enable vibration (haptic feedback) for key presses in the EmergencyDialer activity. + * The pattern is set on a per-platform basis using config_virtualKeyVibePattern. To be + * consistent with the regular Dialer, this value should agree with the corresponding values + * from config.xml under apps/Contacts. + */ + public static final String + KEY_ENABLE_DIALER_KEY_VIBRATION_BOOL = "enable_dialer_key_vibration_bool"; + + /** Flag indicating if dtmf tone type is enabled */ + public static final String KEY_DTMF_TYPE_ENABLED_BOOL = "dtmf_type_enabled_bool"; + + /** Flag indicating if auto retry is enabled */ + public static final String KEY_AUTO_RETRY_ENABLED_BOOL = "auto_retry_enabled_bool"; + + /** + * Determine whether we want to play local DTMF tones in a call, or just let the radio/BP handle + * playing of the tones. + */ + public static final String KEY_ALLOW_LOCAL_DTMF_TONES_BOOL = "allow_local_dtmf_tones_bool"; + + /** + * Determines if the carrier requires converting the destination number before sending out an + * SMS. Certain networks and numbering plans require different formats. + */ + public static final String KEY_SMS_REQUIRES_DESTINATION_NUMBER_CONVERSION_BOOL= + "sms_requires_destination_number_conversion_bool"; + + /** + * If true, show an onscreen "Dial" button in the dialer. In practice this is used on all + * platforms, even the ones with hard SEND/END keys, but for maximum flexibility it's controlled + * by a flag here (which can be overridden on a per-product basis.) + */ + public static final String KEY_SHOW_ONSCREEN_DIAL_BUTTON_BOOL = "show_onscreen_dial_button_bool"; + + /** Determines if device implements a noise suppression device for in call audio. */ + public static final String + KEY_HAS_IN_CALL_NOISE_SUPPRESSION_BOOL = "has_in_call_noise_suppression_bool"; + + /** + * Determines if the current device should allow emergency numbers to be logged in the Call Log. + * (Some carriers require that emergency calls *not* be logged, presumably to avoid the risk of + * accidental redialing from the call log UI. This is a good idea, so the default here is + * false.) + */ + public static final String + KEY_ALLOW_EMERGENCY_NUMBERS_IN_CALL_LOG_BOOL = "allow_emergency_numbers_in_call_log_bool"; + + /** If true, removes the Voice Privacy option from Call Settings */ + public static final String KEY_VOICE_PRIVACY_DISABLE_UI_BOOL = "voice_privacy_disable_ui_bool"; + + /** Control whether users can reach the carrier portions of Cellular Network Settings. */ + public static final String + KEY_HIDE_CARRIER_NETWORK_SETTINGS_BOOL = "hide_carrier_network_settings_bool"; + + /** + * Control whether users receive a simplified network settings UI and improved network + * selection. + */ + public static final String + KEY_SIMPLIFIED_NETWORK_SETTINGS_BOOL = "simplified_network_settings_bool"; + + /** Control whether users can reach the SIM lock settings. */ + public static final String + KEY_HIDE_SIM_LOCK_SETTINGS_BOOL = "hide_sim_lock_settings_bool"; + + /** Control whether users can edit APNs in Settings. */ + public static final String KEY_APN_EXPAND_BOOL = "apn_expand_bool"; + + /** Control whether users can choose a network operator. */ + public static final String KEY_OPERATOR_SELECTION_EXPAND_BOOL = "operator_selection_expand_bool"; + + /** Used in Cellular Network Settings for preferred network type. */ + public static final String KEY_PREFER_2G_BOOL = "prefer_2g_bool"; + + /** Show cdma network mode choices 1x, 3G, global etc. */ + public static final String KEY_SHOW_CDMA_CHOICES_BOOL = "show_cdma_choices_bool"; + + /** CDMA activation goes through HFA */ + public static final String KEY_USE_HFA_FOR_PROVISIONING_BOOL = "use_hfa_for_provisioning_bool"; + + /** + * CDMA activation goes through OTASP. + * <p> + * TODO: This should be combined with config_use_hfa_for_provisioning and implemented as an enum + * (NONE, HFA, OTASP). + */ + public static final String KEY_USE_OTASP_FOR_PROVISIONING_BOOL = "use_otasp_for_provisioning_bool"; + + /** Display carrier settings menu if true */ + public static final String KEY_CARRIER_SETTINGS_ENABLE_BOOL = "carrier_settings_enable_bool"; + + /** Does not display additional call setting for IMS phone based on GSM Phone */ + public static final String KEY_ADDITIONAL_CALL_SETTING_BOOL = "additional_call_setting_bool"; + + /** Show APN Settings for some CDMA carriers */ + public static final String KEY_SHOW_APN_SETTING_CDMA_BOOL = "show_apn_setting_cdma_bool"; + + /** After a CDMA conference call is merged, the swap button should be displayed. */ + public static final String KEY_SUPPORT_SWAP_AFTER_MERGE_BOOL = "support_swap_after_merge_bool"; + + /** + * Since the default voicemail number is empty, if a SIM card does not have a voicemail number + * available the user cannot use voicemail. This flag allows the user to edit the voicemail + * number in such cases, and is false by default. + */ + public static final String KEY_EDITABLE_VOICEMAIL_NUMBER_BOOL= "editable_voicemail_number_bool"; + + /** + * Determine whether the voicemail notification is persistent in the notification bar. If true, + * the voicemail notifications cannot be dismissed from the notification bar. + */ + public static final String + KEY_VOICEMAIL_NOTIFICATION_PERSISTENT_BOOL = "voicemail_notification_persistent_bool"; + + /** For IMS video over LTE calls, determines whether video pause signalling is supported. */ + public static final String + KEY_SUPPORT_PAUSE_IMS_VIDEO_CALLS_BOOL = "support_pause_ims_video_calls_bool"; + + /** + * Disables dialing "*228" (OTASP provisioning) on CDMA carriers where it is not supported or is + * potentially harmful by locking the SIM to 3G. + */ + public static final String + KEY_DISABLE_CDMA_ACTIVATION_CODE_BOOL = "disable_cdma_activation_code_bool"; + + /** + * List of RIL radio technologies (See {@link ServiceState} {@code RIL_RADIO_TECHNOLOGY_*} + * constants) which support only a single data connection at a time. Some carriers do not + * support multiple pdp on UMTS. + */ + public static final String + KEY_ONLY_SINGLE_DC_ALLOWED_INT_ARRAY = "only_single_dc_allowed_int_array"; + + /** + * Override the platform's notion of a network operator being considered roaming. + * Value is string array of MCCMNCs to be considered roaming for 3GPP RATs. + */ + public static final String + KEY_GSM_ROAMING_NETWORKS_STRING_ARRAY = "gsm_roaming_networks_string_array"; + + /** + * Override the platform's notion of a network operator being considered not roaming. + * Value is string array of MCCMNCs to be considered not roaming for 3GPP RATs. + */ + public static final String + KEY_GSM_NONROAMING_NETWORKS_STRING_ARRAY = "gsm_nonroaming_networks_string_array"; + + /** + * Override the device's configuration for the ImsService to use for this SIM card. + */ + public static final String KEY_CONFIG_IMS_PACKAGE_OVERRIDE_STRING = + "config_ims_package_override_string"; + + /** + * Override the package that will manage {@link SubscriptionPlan} + * information instead of the {@link CarrierService} that defines this + * value. + * + * @see SubscriptionManager#getSubscriptionPlans(int) + * @see SubscriptionManager#setSubscriptionPlans(int, java.util.List) + * @hide + */ + @SystemApi + public static final String KEY_CONFIG_PLANS_PACKAGE_OVERRIDE_STRING = + "config_plans_package_override_string"; + + /** + * Override the platform's notion of a network operator being considered roaming. + * Value is string array of SIDs to be considered roaming for 3GPP2 RATs. + */ + public static final String + KEY_CDMA_ROAMING_NETWORKS_STRING_ARRAY = "cdma_roaming_networks_string_array"; + + /** + * Override the platform's notion of a network operator being considered non roaming. + * Value is string array of SIDs to be considered not roaming for 3GPP2 RATs. + */ + public static final String + KEY_CDMA_NONROAMING_NETWORKS_STRING_ARRAY = "cdma_nonroaming_networks_string_array"; + + /** + * Override the platform's notion of a network operator being considered non roaming. + * If true all networks are considered as home network a.k.a non-roaming. When false, + * the 2 pairs of CMDA and GSM roaming/non-roaming arrays are consulted. + * + * @see KEY_GSM_ROAMING_NETWORKS_STRING_ARRAY + * @see KEY_GSM_NONROAMING_NETWORKS_STRING_ARRAY + * @see KEY_CDMA_ROAMING_NETWORKS_STRING_ARRAY + * @see KEY_CDMA_NONROAMING_NETWORKS_STRING_ARRAY + */ + public static final String + KEY_FORCE_HOME_NETWORK_BOOL = "force_home_network_bool"; + + /** + * Flag specifying whether VoLTE should be available for carrier, independent of carrier + * provisioning. If false: hard disabled. If true: then depends on carrier provisioning, + * availability, etc. + */ + public static final String KEY_CARRIER_VOLTE_AVAILABLE_BOOL = "carrier_volte_available_bool"; + + /** + * Flag specifying whether video telephony is available for carrier. If false: hard disabled. + * If true: then depends on carrier provisioning, availability, etc. + */ + public static final String KEY_CARRIER_VT_AVAILABLE_BOOL = "carrier_vt_available_bool"; + + /** + * Flag specifying whether the carrier wants to notify the user when a VT call has been handed + * over from WIFI to LTE. + * <p> + * The handover notification is sent as a + * {@link TelephonyManager#EVENT_HANDOVER_VIDEO_FROM_WIFI_TO_LTE} + * {@link android.telecom.Connection} event, which an {@link android.telecom.InCallService} + * should use to trigger the display of a user-facing message. + * <p> + * The Connection event is sent to the InCallService only once, the first time it occurs. + * @hide + */ + public static final String KEY_NOTIFY_HANDOVER_VIDEO_FROM_WIFI_TO_LTE_BOOL = + "notify_handover_video_from_wifi_to_lte_bool"; + + /** + * Flag specifying whether the carrier supports downgrading a video call (tx, rx or tx/rx) + * directly to an audio call. + * @hide + */ + public static final String KEY_SUPPORT_DOWNGRADE_VT_TO_AUDIO_BOOL = + "support_downgrade_vt_to_audio_bool"; + + /** + * Where there is no preloaded voicemail number on a SIM card, specifies the carrier's default + * voicemail number. + * When empty string, no default voicemail number is specified. + */ + public static final String KEY_DEFAULT_VM_NUMBER_STRING = "default_vm_number_string"; + + /** + * When {@code true}, changes to the mobile data enabled switch will not cause the VT + * registration state to change. That is, turning on or off mobile data will not cause VT to be + * enabled or disabled. + * When {@code false}, disabling mobile data will cause VT to be de-registered. + * <p> + * See also {@link #KEY_VILTE_DATA_IS_METERED_BOOL}. + * @hide + */ + public static final String KEY_IGNORE_DATA_ENABLED_CHANGED_FOR_VIDEO_CALLS = + "ignore_data_enabled_changed_for_video_calls"; + + /** + * Flag indicating whether data used for a video call over LTE is metered or not. + * <p> + * When {@code true}, if the device hits the data limit or data is disabled during a ViLTE call, + * the call will be downgraded to audio-only (or paused if + * {@link #KEY_SUPPORT_PAUSE_IMS_VIDEO_CALLS_BOOL} is {@code true}). + * + * @hide + */ + public static final String KEY_VILTE_DATA_IS_METERED_BOOL = "vilte_data_is_metered_bool"; + + /** + * Flag specifying whether WFC over IMS should be available for carrier: independent of + * carrier provisioning. If false: hard disabled. If true: then depends on carrier + * provisioning, availability etc. + */ + public static final String KEY_CARRIER_WFC_IMS_AVAILABLE_BOOL = "carrier_wfc_ims_available_bool"; + + /** + * Specifies a map from dialstrings to replacements for roaming network service numbers which + * cannot be replaced on the carrier side. + * <p> + * Individual entries have the format: + * [dialstring to replace]:[replacement] + */ + public static final String KEY_DIAL_STRING_REPLACE_STRING_ARRAY = + "dial_string_replace_string_array"; + + /** + * Flag specifying whether WFC over IMS supports the "wifi only" option. If false, the wifi + * calling settings will not include an option for "wifi only". If true, the wifi calling + * settings will include an option for "wifi only" + * <p> + * By default, it is assumed that WFC supports "wifi only". + */ + public static final String KEY_CARRIER_WFC_SUPPORTS_WIFI_ONLY_BOOL = + "carrier_wfc_supports_wifi_only_bool"; + + /** + * Default WFC_IMS_MODE for home network 0: WIFI_ONLY + * 1: CELLULAR_PREFERRED + * 2: WIFI_PREFERRED + * @hide + */ + public static final String KEY_CARRIER_DEFAULT_WFC_IMS_MODE_INT = + "carrier_default_wfc_ims_mode_int"; + + /** + * Default WFC_IMS_MODE for roaming + * See {@link KEY_CARRIER_DEFAULT_WFC_IMS_MODE_INT} for valid values. + * + * @hide + */ + public static final String KEY_CARRIER_DEFAULT_WFC_IMS_ROAMING_MODE_INT = + "carrier_default_wfc_ims_roaming_mode_int"; + + /** + * Default WFC_IMS_enabled: true VoWiFi by default is on + * false VoWiFi by default is off + * @hide + */ + public static final String KEY_CARRIER_DEFAULT_WFC_IMS_ENABLED_BOOL = + "carrier_default_wfc_ims_enabled_bool"; + + /** + * Default WFC_IMS_roaming_enabled: true VoWiFi roaming by default is on + * false VoWiFi roaming by default is off + * @hide + */ + public static final String KEY_CARRIER_DEFAULT_WFC_IMS_ROAMING_ENABLED_BOOL = + "carrier_default_wfc_ims_roaming_enabled_bool"; + + /** + * Flag indicating whether failed calls due to no service should prompt the user to enable + * WIFI calling. When {@code true}, if the user attempts to establish a call when there is no + * service available, they are connected to WIFI, and WIFI calling is disabled, a different + * call failure message will be used to encourage the user to enable WIFI calling. + * @hide + */ + public static final String KEY_CARRIER_PROMOTE_WFC_ON_CALL_FAIL_BOOL = + "carrier_promote_wfc_on_call_fail_bool"; + + /** Flag specifying whether provisioning is required for VOLTE. */ + public static final String KEY_CARRIER_VOLTE_PROVISIONING_REQUIRED_BOOL + = "carrier_volte_provisioning_required_bool"; + + /** + * Flag specifying if WFC provisioning depends on VoLTE provisioning. + * + * {@code false}: default value; honor actual WFC provisioning state. + * {@code true}: when VoLTE is not provisioned, treat WFC as not provisioned; when VoLTE is + * provisioned, honor actual WFC provisioning state. + * + * As of now, Verizon is the only carrier enforcing this dependency in their + * WFC awareness and activation requirements. + * + * @hide + * */ + public static final String KEY_CARRIER_VOLTE_OVERRIDE_WFC_PROVISIONING_BOOL + = "carrier_volte_override_wfc_provisioning_bool"; + + /** Flag specifying whether VoLTE TTY is supported. */ + public static final String KEY_CARRIER_VOLTE_TTY_SUPPORTED_BOOL + = "carrier_volte_tty_supported_bool"; + + /** + * Flag specifying whether IMS service can be turned off. If false then the service will not be + * turned-off completely, but individual features can be disabled. + */ + public static final String KEY_CARRIER_ALLOW_TURNOFF_IMS_BOOL + = "carrier_allow_turnoff_ims_bool"; + + /** + * Flag specifying whether Generic Bootstrapping Architecture capable SIM is required for IMS. + */ + public static final String KEY_CARRIER_IMS_GBA_REQUIRED_BOOL + = "carrier_ims_gba_required_bool"; + + /** + * Flag specifying whether IMS instant lettering is available for the carrier. {@code True} if + * instant lettering is available for the carrier, {@code false} otherwise. + */ + public static final String KEY_CARRIER_INSTANT_LETTERING_AVAILABLE_BOOL = + "carrier_instant_lettering_available_bool"; + + /* + * Flag specifying whether IMS should be the first phone attempted for E911 even if the + * phone is not in service. + */ + public static final String KEY_CARRIER_USE_IMS_FIRST_FOR_EMERGENCY_BOOL + = "carrier_use_ims_first_for_emergency_bool"; + + /** + * When IMS instant lettering is available for a carrier (see + * {@link #KEY_CARRIER_INSTANT_LETTERING_AVAILABLE_BOOL}), determines the list of characters + * which may not be contained in messages. Should be specified as a regular expression suitable + * for use with {@link String#matches(String)}. + */ + public static final String KEY_CARRIER_INSTANT_LETTERING_INVALID_CHARS_STRING = + "carrier_instant_lettering_invalid_chars_string"; + + /** + * When IMS instant lettering is available for a carrier (see + * {@link #KEY_CARRIER_INSTANT_LETTERING_AVAILABLE_BOOL}), determines a list of characters which + * must be escaped with a backslash '\' character. Should be specified as a string containing + * the characters to be escaped. For example to escape quote and backslash the string would be + * a quote and a backslash. + */ + public static final String KEY_CARRIER_INSTANT_LETTERING_ESCAPED_CHARS_STRING = + "carrier_instant_lettering_escaped_chars_string"; + + /** + * When IMS instant lettering is available for a carrier (see + * {@link #KEY_CARRIER_INSTANT_LETTERING_AVAILABLE_BOOL}), determines the character encoding + * which will be used when determining the length of messages. Used in the InCall UI to limit + * the number of characters the user may type. If empty-string, the instant lettering + * message size limit will be enforced on a 1:1 basis. That is, each character will count + * towards the messages size limit as a single bye. If a character encoding is specified, the + * message size limit will be based on the number of bytes in the message per the specified + * encoding. + */ + public static final String KEY_CARRIER_INSTANT_LETTERING_ENCODING_STRING = + "carrier_instant_lettering_encoding_string"; + + /** + * When IMS instant lettering is available for a carrier (see + * {@link #KEY_CARRIER_INSTANT_LETTERING_AVAILABLE_BOOL}), the length limit for messages. Used + * in the InCall UI to ensure the user cannot enter more characters than allowed by the carrier. + * See also {@link #KEY_CARRIER_INSTANT_LETTERING_ENCODING_STRING} for more information on how + * the length of the message is calculated. + */ + public static final String KEY_CARRIER_INSTANT_LETTERING_LENGTH_LIMIT_INT = + "carrier_instant_lettering_length_limit_int"; + + /** + * If Voice Radio Technology is RIL_RADIO_TECHNOLOGY_LTE:14 or RIL_RADIO_TECHNOLOGY_UNKNOWN:0 + * this is the value that should be used instead. A configuration value of + * RIL_RADIO_TECHNOLOGY_UNKNOWN:0 means there is no replacement value and that the default + * assumption for phone type (GSM) should be used. + */ + public static final String KEY_VOLTE_REPLACEMENT_RAT_INT = "volte_replacement_rat_int"; + + /** + * The default sim call manager to use when the default dialer doesn't implement one. A sim call + * manager can control and route outgoing and incoming phone calls, even if they're placed + * using another connection service (PSTN, for example). + */ + public static final String KEY_DEFAULT_SIM_CALL_MANAGER_STRING = "default_sim_call_manager_string"; + + /** + * The default flag specifying whether ETWS/CMAS test setting is forcibly disabled in + * Settings->More->Emergency broadcasts menu even though developer options is turned on. + */ + public static final String KEY_CARRIER_FORCE_DISABLE_ETWS_CMAS_TEST_BOOL = + "carrier_force_disable_etws_cmas_test_bool"; + + /** + * The default flag specifying whether "Turn on Notifications" option will be always shown in + * Settings->More->Emergency broadcasts menu regardless developer options is turned on or not. + */ + public static final String KEY_ALWAYS_SHOW_EMERGENCY_ALERT_ONOFF_BOOL = + "always_show_emergency_alert_onoff_bool"; + + /** + * The flag to disable cell broadcast severe alert when extreme alert is disabled. + * @hide + */ + public static final String KEY_DISABLE_SEVERE_WHEN_EXTREME_DISABLED_BOOL = + "disable_severe_when_extreme_disabled_bool"; + + /** + * The message expiration time in milliseconds for duplicate detection purposes. + * @hide + */ + public static final String KEY_MESSAGE_EXPIRATION_TIME_LONG = + "message_expiration_time_long"; + + /** + * The data call retry configuration for different types of APN. + * @hide + */ + public static final String KEY_CARRIER_DATA_CALL_RETRY_CONFIG_STRINGS = + "carrier_data_call_retry_config_strings"; + + /** + * Delay in milliseconds between trying APN from the pool + * @hide + */ + public static final String KEY_CARRIER_DATA_CALL_APN_DELAY_DEFAULT_LONG = + "carrier_data_call_apn_delay_default_long"; + + /** + * Faster delay in milliseconds between trying APN from the pool + * @hide + */ + public static final String KEY_CARRIER_DATA_CALL_APN_DELAY_FASTER_LONG = + "carrier_data_call_apn_delay_faster_long"; + + /** + * Delay in milliseconds for retrying APN after disconnect + * @hide + */ + public static final String KEY_CARRIER_DATA_CALL_APN_RETRY_AFTER_DISCONNECT_LONG = + "carrier_data_call_apn_retry_after_disconnect_long"; + + /** + * Data call setup permanent failure causes by the carrier + */ + public static final String KEY_CARRIER_DATA_CALL_PERMANENT_FAILURE_STRINGS = + "carrier_data_call_permanent_failure_strings"; + + /** + * Default APN types that are metered by the carrier + * @hide + */ + public static final String KEY_CARRIER_METERED_APN_TYPES_STRINGS = + "carrier_metered_apn_types_strings"; + /** + * Default APN types that are roaming-metered by the carrier + * @hide + */ + public static final String KEY_CARRIER_METERED_ROAMING_APN_TYPES_STRINGS = + "carrier_metered_roaming_apn_types_strings"; + + /** + * Default APN types that are metered on IWLAN by the carrier + * @hide + */ + public static final String KEY_CARRIER_METERED_IWLAN_APN_TYPES_STRINGS = + "carrier_metered_iwlan_apn_types_strings"; + + /** + * CDMA carrier ERI (Enhanced Roaming Indicator) file name + * @hide + */ + public static final String KEY_CARRIER_ERI_FILE_NAME_STRING = + "carrier_eri_file_name_string"; + + /* The following 3 fields are related to carrier visual voicemail. */ + + /** + * The carrier number mobile outgoing (MO) sms messages are sent to. + */ + public static final String KEY_VVM_DESTINATION_NUMBER_STRING = "vvm_destination_number_string"; + + /** + * The port through which the mobile outgoing (MO) sms messages are sent through. + */ + public static final String KEY_VVM_PORT_NUMBER_INT = "vvm_port_number_int"; + + /** + * The type of visual voicemail protocol the carrier adheres to. See {@link TelephonyManager} + * for possible values. For example {@link TelephonyManager#VVM_TYPE_OMTP}. + */ + public static final String KEY_VVM_TYPE_STRING = "vvm_type_string"; + + /** + * Whether cellular data is required to access visual voicemail. + */ + public static final String KEY_VVM_CELLULAR_DATA_REQUIRED_BOOL = + "vvm_cellular_data_required_bool"; + + /** + * The default OMTP visual voicemail client prefix to use. Defaulted to "//VVM" + */ + public static final String KEY_VVM_CLIENT_PREFIX_STRING = + "vvm_client_prefix_string"; + + /** + * Whether to use SSL to connect to the visual voicemail IMAP server. Defaulted to false. + */ + public static final String KEY_VVM_SSL_ENABLED_BOOL = "vvm_ssl_enabled_bool"; + + /** + * A set of capabilities that should not be used even if it is reported by the visual voicemail + * IMAP CAPABILITY command. + */ + public static final String KEY_VVM_DISABLED_CAPABILITIES_STRING_ARRAY = + "vvm_disabled_capabilities_string_array"; + + /** + * Whether legacy mode should be used when the visual voicemail client is disabled. + * + * <p>Legacy mode is a mode that on the carrier side visual voicemail is still activated, but on + * the client side all network operations are disabled. SMSs are still monitored so a new + * message SYNC SMS will be translated to show a message waiting indicator, like traditional + * voicemails. + * + * <p>This is for carriers that does not support VVM deactivation so voicemail can continue to + * function without the data cost. + */ + public static final String KEY_VVM_LEGACY_MODE_ENABLED_BOOL = + "vvm_legacy_mode_enabled_bool"; + + /** + * Whether to prefetch audio data on new voicemail arrival, defaulted to true. + */ + public static final String KEY_VVM_PREFETCH_BOOL = "vvm_prefetch_bool"; + + /** + * The package name of the carrier's visual voicemail app to ensure that dialer visual voicemail + * and carrier visual voicemail are not active at the same time. + * + * @deprecated use {@link #KEY_CARRIER_VVM_PACKAGE_NAME_STRING_ARRAY}. + */ + @Deprecated + public static final String KEY_CARRIER_VVM_PACKAGE_NAME_STRING = "carrier_vvm_package_name_string"; + + /** + * A list of the carrier's visual voicemail app package names to ensure that dialer visual + * voicemail and carrier visual voicemail are not active at the same time. + */ + public static final String KEY_CARRIER_VVM_PACKAGE_NAME_STRING_ARRAY = + "carrier_vvm_package_name_string_array"; + + /** + * Flag specifying whether ICCID is showed in SIM Status screen, default to false. + */ + public static final String KEY_SHOW_ICCID_IN_SIM_STATUS_BOOL = "show_iccid_in_sim_status_bool"; + + /** + * Flag specifying whether an additional (client initiated) intent needs to be sent on System + * update + */ + public static final String KEY_CI_ACTION_ON_SYS_UPDATE_BOOL = "ci_action_on_sys_update_bool"; + + /** + * Intent to be sent for the additional action on System update + */ + public static final String KEY_CI_ACTION_ON_SYS_UPDATE_INTENT_STRING = + "ci_action_on_sys_update_intent_string"; + + /** + * Extra to be included in the intent sent for additional action on System update + */ + public static final String KEY_CI_ACTION_ON_SYS_UPDATE_EXTRA_STRING = + "ci_action_on_sys_update_extra_string"; + + /** + * Value of extra included in intent sent for additional action on System update + */ + public static final String KEY_CI_ACTION_ON_SYS_UPDATE_EXTRA_VAL_STRING = + "ci_action_on_sys_update_extra_val_string"; + + /** + * Specifies the amount of gap to be added in millis between postdial DTMF tones. When a + * non-zero value is specified, the UE shall wait for the specified amount of time before it + * sends out successive DTMF tones on the network. + */ + public static final String KEY_GSM_DTMF_TONE_DELAY_INT = "gsm_dtmf_tone_delay_int"; + + /** + * Specifies the amount of gap to be added in millis between DTMF tones. When a non-zero value + * is specified, the UE shall wait for the specified amount of time before it sends out + * successive DTMF tones on the network. + */ + public static final String KEY_IMS_DTMF_TONE_DELAY_INT = "ims_dtmf_tone_delay_int"; + + /** + * Specifies the amount of gap to be added in millis between postdial DTMF tones. When a + * non-zero value is specified, the UE shall wait for the specified amount of time before it + * sends out successive DTMF tones on the network. + */ + public static final String KEY_CDMA_DTMF_TONE_DELAY_INT = "cdma_dtmf_tone_delay_int"; + + /** + * Determines whether conference calls are supported by a carrier. When {@code true}, + * conference calling is supported, {@code false otherwise}. + */ + public static final String KEY_SUPPORT_CONFERENCE_CALL_BOOL = "support_conference_call_bool"; + + /** + * Determines whether a maximum size limit for IMS conference calls is enforced on the device. + * When {@code true}, IMS conference calls will be limited to at most + * {@link #KEY_IMS_CONFERENCE_SIZE_LIMIT_INT} participants. When {@code false}, no attempt is made + * to limit the number of participants in a conference (the carrier will raise an error when an + * attempt is made to merge too many participants into a conference). + */ + public static final String KEY_IS_IMS_CONFERENCE_SIZE_ENFORCED_BOOL = + "is_ims_conference_size_enforced_bool"; + + /** + * Determines the maximum number of participants the carrier supports for a conference call. + * This number is exclusive of the current device. A conference between 3 devices, for example, + * would have a size limit of 2 participants. + * Enforced when {@link #KEY_IS_IMS_CONFERENCE_SIZE_ENFORCED_BOOL} is {@code true}. + */ + public static final String KEY_IMS_CONFERENCE_SIZE_LIMIT_INT = "ims_conference_size_limit_int"; + + /** + * Determines whether High Definition audio property is displayed in the dialer UI. + * If {@code false}, remove the HD audio property from the connection so that HD audio related + * UI is not displayed. If {@code true}, keep HD audio property as it is configured. + */ + public static final String KEY_DISPLAY_HD_AUDIO_PROPERTY_BOOL = + "display_hd_audio_property_bool"; + + /** + * Determines whether IMS conference calls are supported by a carrier. When {@code true}, + * IMS conference calling is supported, {@code false} otherwise. + * @hide + */ + public static final String KEY_SUPPORT_IMS_CONFERENCE_CALL_BOOL = + "support_ims_conference_call_bool"; + + /** + * Determines whether video conference calls are supported by a carrier. When {@code true}, + * video calls can be merged into conference calls, {@code false} otherwiwse. + * <p> + * Note: even if video conference calls are not supported, audio calls may be merged into a + * conference if {@link #KEY_SUPPORT_CONFERENCE_CALL_BOOL} is {@code true}. + * @hide + */ + public static final String KEY_SUPPORT_VIDEO_CONFERENCE_CALL_BOOL = + "support_video_conference_call_bool"; + + /** + * Determine whether user can toggle Enhanced 4G LTE Mode in Settings. + */ + public static final String KEY_EDITABLE_ENHANCED_4G_LTE_BOOL = "editable_enhanced_4g_lte_bool"; + + /** + * Determines whether the Enhanced 4G LTE toggle will be shown in the settings. When this + * option is {@code true}, the toggle will be hidden regardless of whether the device and + * carrier supports 4G LTE or not. + */ + public static final String KEY_HIDE_ENHANCED_4G_LTE_BOOL = "hide_enhanced_4g_lte_bool"; + + /** + * Determine whether IMS apn can be shown. + */ + public static final String KEY_HIDE_IMS_APN_BOOL = "hide_ims_apn_bool"; + + /** + * Determine whether preferred network type can be shown. + */ + public static final String KEY_HIDE_PREFERRED_NETWORK_TYPE_BOOL = "hide_preferred_network_type_bool"; + + /** + * String array for package names that need to be enabled for this carrier. + * If user has explicitly disabled some packages in the list, won't re-enable. + * Other carrier specific apps which are not in this list may be disabled for current carrier, + * and only be re-enabled when this config for another carrier includes it. + */ + public static final String KEY_ENABLE_APPS_STRING_ARRAY = "enable_apps_string_array"; + + /** + * Determine whether user can switch Wi-Fi preferred or Cellular preferred in calling preference. + * Some operators support Wi-Fi Calling only, not VoLTE. + * They don't need "Cellular preferred" option. + * In this case, set uneditalbe attribute for preferred preference. + * @hide + */ + public static final String KEY_EDITABLE_WFC_MODE_BOOL = "editable_wfc_mode_bool"; + + /** + * Flag to indicate if Wi-Fi needs to be disabled in ECBM + * @hide + **/ + public static final String + KEY_CONFIG_WIFI_DISABLE_IN_ECBM = "config_wifi_disable_in_ecbm"; + + /** + * List operator-specific error codes and indices of corresponding error strings in + * wfcOperatorErrorAlertMessages and wfcOperatorErrorNotificationMessages. + * + * Example: "REG09|0" specifies error code "REG09" and index "0". This index will be + * used to find alert and notification messages in wfcOperatorErrorAlertMessages and + * wfcOperatorErrorNotificationMessages. + * + * @hide + */ + public static final String KEY_WFC_OPERATOR_ERROR_CODES_STRING_ARRAY = + "wfc_operator_error_codes_string_array"; + + /** + * Indexes of SPN format strings in wfcSpnFormats and wfcDataSpnFormats. + * @hide + */ + public static final String KEY_WFC_SPN_FORMAT_IDX_INT = "wfc_spn_format_idx_int"; + /** @hide */ + public static final String KEY_WFC_DATA_SPN_FORMAT_IDX_INT = "wfc_data_spn_format_idx_int"; + + /** + * The Component Name of the activity that can setup the emergency addrees for WiFi Calling + * as per carrier requirement. + * @hide + */ + public static final String KEY_WFC_EMERGENCY_ADDRESS_CARRIER_APP_STRING = + "wfc_emergency_address_carrier_app_string"; + + /** + * Boolean to decide whether to use #KEY_CARRIER_NAME_STRING from CarrierConfig app. + * @hide + */ + public static final String KEY_CARRIER_NAME_OVERRIDE_BOOL = "carrier_name_override_bool"; + + /** + * String to identify carrier name in CarrierConfig app. This string is used only if + * #KEY_CARRIER_NAME_OVERRIDE_BOOL is true + * @hide + */ + public static final String KEY_CARRIER_NAME_STRING = "carrier_name_string"; + + /** + * If this is true, the SIM card (through Customer Service Profile EF file) will be able to + * prevent manual operator selection. If false, this SIM setting will be ignored and manual + * operator selection will always be available. See CPHS4_2.WW6, CPHS B.4.7.1 for more + * information + */ + public static final String KEY_CSP_ENABLED_BOOL = "csp_enabled_bool"; + + /** + * Allow user to add APNs + */ + public static final String KEY_ALLOW_ADDING_APNS_BOOL = "allow_adding_apns_bool"; + + /** + * APN types that user is not allowed to modify + * @hide + */ + public static final String KEY_READ_ONLY_APN_TYPES_STRING_ARRAY = + "read_only_apn_types_string_array"; + + /** + * APN fields that user is not allowed to modify + * @hide + */ + public static final String KEY_READ_ONLY_APN_FIELDS_STRING_ARRAY = + "read_only_apn_fields_string_array"; + + /** + * Boolean indicating if intent for emergency call state changes should be broadcast + * @hide + */ + public static final String KEY_BROADCAST_EMERGENCY_CALL_STATE_CHANGES_BOOL = + "broadcast_emergency_call_state_changes_bool"; + + /** + * Indicates whether STK LAUNCH_BROWSER command is disabled. + * If {@code true}, then the browser will not be launched + * on UI for the LAUNCH_BROWSER STK command. + * @hide + */ + public static final String KEY_STK_DISABLE_LAUNCH_BROWSER_BOOL = + "stk_disable_launch_browser_bool"; + + + // These variables are used by the MMS service and exposed through another API, {@link + // SmsManager}. The variable names and string values are copied from there. + public static final String KEY_MMS_ALIAS_ENABLED_BOOL = "aliasEnabled"; + public static final String KEY_MMS_ALLOW_ATTACH_AUDIO_BOOL = "allowAttachAudio"; + public static final String KEY_MMS_APPEND_TRANSACTION_ID_BOOL = "enabledTransID"; + public static final String KEY_MMS_GROUP_MMS_ENABLED_BOOL = "enableGroupMms"; + public static final String KEY_MMS_MMS_DELIVERY_REPORT_ENABLED_BOOL = "enableMMSDeliveryReports"; + public static final String KEY_MMS_MMS_ENABLED_BOOL = "enabledMMS"; + public static final String KEY_MMS_MMS_READ_REPORT_ENABLED_BOOL = "enableMMSReadReports"; + public static final String KEY_MMS_MULTIPART_SMS_ENABLED_BOOL = "enableMultipartSMS"; + public static final String KEY_MMS_NOTIFY_WAP_MMSC_ENABLED_BOOL = "enabledNotifyWapMMSC"; + public static final String KEY_MMS_SEND_MULTIPART_SMS_AS_SEPARATE_MESSAGES_BOOL = "sendMultipartSmsAsSeparateMessages"; + public static final String KEY_MMS_SHOW_CELL_BROADCAST_APP_LINKS_BOOL = "config_cellBroadcastAppLinks"; + public static final String KEY_MMS_SMS_DELIVERY_REPORT_ENABLED_BOOL = "enableSMSDeliveryReports"; + public static final String KEY_MMS_SUPPORT_HTTP_CHARSET_HEADER_BOOL = "supportHttpCharsetHeader"; + public static final String KEY_MMS_SUPPORT_MMS_CONTENT_DISPOSITION_BOOL = "supportMmsContentDisposition"; + public static final String KEY_MMS_ALIAS_MAX_CHARS_INT = "aliasMaxChars"; + public static final String KEY_MMS_ALIAS_MIN_CHARS_INT = "aliasMinChars"; + public static final String KEY_MMS_HTTP_SOCKET_TIMEOUT_INT = "httpSocketTimeout"; + public static final String KEY_MMS_MAX_IMAGE_HEIGHT_INT = "maxImageHeight"; + public static final String KEY_MMS_MAX_IMAGE_WIDTH_INT = "maxImageWidth"; + public static final String KEY_MMS_MAX_MESSAGE_SIZE_INT = "maxMessageSize"; + public static final String KEY_MMS_MESSAGE_TEXT_MAX_SIZE_INT = "maxMessageTextSize"; + public static final String KEY_MMS_RECIPIENT_LIMIT_INT = "recipientLimit"; + public static final String KEY_MMS_SMS_TO_MMS_TEXT_LENGTH_THRESHOLD_INT = "smsToMmsTextLengthThreshold"; + public static final String KEY_MMS_SMS_TO_MMS_TEXT_THRESHOLD_INT = "smsToMmsTextThreshold"; + public static final String KEY_MMS_SUBJECT_MAX_LENGTH_INT = "maxSubjectLength"; + public static final String KEY_MMS_EMAIL_GATEWAY_NUMBER_STRING = "emailGatewayNumber"; + public static final String KEY_MMS_HTTP_PARAMS_STRING = "httpParams"; + public static final String KEY_MMS_NAI_SUFFIX_STRING = "naiSuffix"; + public static final String KEY_MMS_UA_PROF_TAG_NAME_STRING = "uaProfTagName"; + public static final String KEY_MMS_UA_PROF_URL_STRING = "uaProfUrl"; + public static final String KEY_MMS_USER_AGENT_STRING = "userAgent"; + /** @hide */ + public static final String KEY_MMS_CLOSE_CONNECTION_BOOL = "mmsCloseConnection"; + + /** + * If carriers require differentiate un-provisioned status: cold sim or out of credit sim + * a package name and activity name can be provided to launch a supported carrier application + * that check the sim provisioning status + * The first element is the package name and the second element is the activity name + * of the provisioning app + * example: + * <item>com.google.android.carrierPackageName</item> + * <item>com.google.android.carrierPackageName.CarrierActivityName</item> + * The ComponentName of the carrier activity that can setup the device and activate with the + * network as part of the Setup Wizard flow. + * @hide + */ + public static final String KEY_CARRIER_SETUP_APP_STRING = "carrier_setup_app_string"; + + /** + * Defines carrier-specific actions which act upon + * com.android.internal.telephony.CARRIER_SIGNAL_REDIRECTED, used for customization of the + * default carrier app + * Format: "CARRIER_ACTION_IDX, ..." + * Where {@code CARRIER_ACTION_IDX} is an integer defined in + * {@link com.android.carrierdefaultapp.CarrierActionUtils CarrierActionUtils} + * Example: + * {@link com.android.carrierdefaultapp.CarrierActionUtils#CARRIER_ACTION_DISABLE_METERED_APNS + * disable_metered_apns} + * @hide + */ + public static final String KEY_CARRIER_DEFAULT_ACTIONS_ON_REDIRECTION_STRING_ARRAY = + "carrier_default_actions_on_redirection_string_array"; + + /** + * Defines carrier-specific actions which act upon + * com.android.internal.telephony.CARRIER_SIGNAL_REQUEST_NETWORK_FAILED + * and configured signal args: + * {@link com.android.internal.telephony.TelephonyIntents#EXTRA_APN_TYPE_KEY apnType}, + * {@link com.android.internal.telephony.TelephonyIntents#EXTRA_ERROR_CODE_KEY errorCode} + * used for customization of the default carrier app + * Format: + * { + * "APN_1, ERROR_CODE_1 : CARRIER_ACTION_IDX_1, CARRIER_ACTION_IDX_2...", + * "APN_1, ERROR_CODE_2 : CARRIER_ACTION_IDX_1 " + * } + * Where {@code APN_1} is a string defined in + * {@link com.android.internal.telephony.PhoneConstants PhoneConstants} + * Example: "default" + * + * {@code ERROR_CODE_1} is an integer defined in + * {@link com.android.internal.telephony.dataconnection.DcFailCause DcFailure} + * Example: + * {@link com.android.internal.telephony.dataconnection.DcFailCause#MISSING_UNKNOWN_APN} + * + * {@code CARRIER_ACTION_IDX_1} is an integer defined in + * {@link com.android.carrierdefaultapp.CarrierActionUtils CarrierActionUtils} + * Example: + * {@link com.android.carrierdefaultapp.CarrierActionUtils#CARRIER_ACTION_DISABLE_METERED_APNS} + * @hide + */ + public static final String KEY_CARRIER_DEFAULT_ACTIONS_ON_DCFAILURE_STRING_ARRAY = + "carrier_default_actions_on_dcfailure_string_array"; + + /** + * Defines carrier-specific actions which act upon + * com.android.internal.telephony.CARRIER_SIGNAL_RESET, used for customization of the + * default carrier app + * Format: "CARRIER_ACTION_IDX, ..." + * Where {@code CARRIER_ACTION_IDX} is an integer defined in + * {@link com.android.carrierdefaultapp.CarrierActionUtils CarrierActionUtils} + * Example: + * {@link com.android.carrierdefaultapp.CarrierActionUtils + * #CARRIER_ACTION_CANCEL_ALL_NOTIFICATIONS clear all notifications on reset} + * @hide + */ + public static final String KEY_CARRIER_DEFAULT_ACTIONS_ON_RESET = + "carrier_default_actions_on_reset_string_array"; + + /** + * Defines carrier-specific actions which act upon + * com.android.internal.telephony.CARRIER_SIGNAL_DEFAULT_NETWORK_AVAILABLE, + * used for customization of the default carrier app + * Format: + * { + * "true : CARRIER_ACTION_IDX_1", + * "false: CARRIER_ACTION_IDX_2" + * } + * Where {@code true} is a boolean indicates default network available/unavailable + * Where {@code CARRIER_ACTION_IDX} is an integer defined in + * {@link com.android.carrierdefaultapp.CarrierActionUtils CarrierActionUtils} + * Example: + * {@link com.android.carrierdefaultapp.CarrierActionUtils + * #CARRIER_ACTION_ENABLE_DEFAULT_URL_HANDLER enable the app as the default URL handler} + * @hide + */ + public static final String KEY_CARRIER_DEFAULT_ACTIONS_ON_DEFAULT_NETWORK_AVAILABLE = + "carrier_default_actions_on_default_network_available_string_array"; + /** + * Defines a list of acceptable redirection url for default carrier app + * @hides + */ + public static final String KEY_CARRIER_DEFAULT_REDIRECTION_URL_STRING_ARRAY = + "carrier_default_redirection_url_string_array"; + + /** + * Each config includes the componentName of the carrier app, followed by a list of interesting + * signals(declared in the manifest) which could wake up the app. + * @see com.android.internal.telephony.TelephonyIntents + * Example: + * <item>com.google.android.carrierAPK/.CarrierSignalReceiverA: + * com.android.internal.telephony.CARRIER_SIGNAL_REDIRECTED, + * com.android.internal.telephony.CARRIER_SIGNAL_PCO_VALUE + * </item> + * <item>com.google.android.carrierAPK/.CarrierSignalReceiverB: + * com.android.internal.telephony.CARRIER_SIGNAL_PCO_VALUE + * </item> + * @hide + */ + public static final String KEY_CARRIER_APP_WAKE_SIGNAL_CONFIG_STRING_ARRAY = + "carrier_app_wake_signal_config"; + + /** + * Each config includes the componentName of the carrier app, followed by a list of interesting + * signals for the app during run-time. The list of signals(intents) are targeting on run-time + * broadcast receivers only, aiming to avoid unnecessary wake-ups and should not be declared in + * the app's manifest. + * @see com.android.internal.telephony.TelephonyIntents + * Example: + * <item>com.google.android.carrierAPK/.CarrierSignalReceiverA: + * com.android.internal.telephony.CARRIER_SIGNAL_REQUEST_NETWORK_FAILED, + * com.android.internal.telephony.CARRIER_SIGNAL_PCO_VALUE + * </item> + * <item>com.google.android.carrierAPK/.CarrierSignalReceiverB: + * com.android.internal.telephony.CARRIER_SIGNAL_REQUEST_NETWORK_FAILED + * </item> + * @hide + */ + public static final String KEY_CARRIER_APP_NO_WAKE_SIGNAL_CONFIG_STRING_ARRAY = + "carrier_app_no_wake_signal_config"; + + /** + * Default value for {@link Settings.Global#DATA_ROAMING} + * @hide + */ + public static final String KEY_CARRIER_DEFAULT_DATA_ROAMING_ENABLED_BOOL = + "carrier_default_data_roaming_enabled_bool"; + + /** + * Determines whether the carrier supports making non-emergency phone calls while the phone is + * in emergency callback mode. Default value is {@code true}, meaning that non-emergency calls + * are allowed in emergency callback mode. + */ + public static final String KEY_ALLOW_NON_EMERGENCY_CALLS_IN_ECM_BOOL = + "allow_non_emergency_calls_in_ecm_bool"; + + /** + * Flag indicating whether to allow carrier video calls to emergency numbers. + * When {@code true}, video calls to emergency numbers will be allowed. When {@code false}, + * video calls to emergency numbers will be initiated as audio-only calls instead. + */ + public static final String KEY_ALLOW_EMERGENCY_VIDEO_CALLS_BOOL = + "allow_emergency_video_calls_bool"; + + /** + * Flag indicating whether the carrier supports RCS presence indication for video calls. When + * {@code true}, the carrier supports RCS presence indication for video calls. When presence + * is supported, the device should use the + * {@link android.provider.ContactsContract.Data#CARRIER_PRESENCE} bit mask and set the + * {@link android.provider.ContactsContract.Data#CARRIER_PRESENCE_VT_CAPABLE} bit to indicate + * whether each contact supports video calling. The UI is made aware that presence is enabled + * via {@link android.telecom.PhoneAccount#CAPABILITY_VIDEO_CALLING_RELIES_ON_PRESENCE} + * and can choose to hide or show the video calling icon based on whether a contact supports + * video. + */ + public static final String KEY_USE_RCS_PRESENCE_BOOL = "use_rcs_presence_bool"; + + /** + * The duration in seconds that platform call and message blocking is disabled after the user + * contacts emergency services. Platform considers values in the range 0 to 604800 (one week) as + * valid. See {@link android.provider.BlockedNumberContract#isBlocked(Context, String)}). + */ + public static final String KEY_DURATION_BLOCKING_DISABLED_AFTER_EMERGENCY_INT = + "duration_blocking_disabled_after_emergency_int"; + + /** + * For carriers which require an empty flash to be sent before sending the normal 3-way calling + * flash, the duration in milliseconds of the empty flash to send. When {@code 0}, no empty + * flash is sent. + */ + public static final String KEY_CDMA_3WAYCALL_FLASH_DELAY_INT = "cdma_3waycall_flash_delay_int"; + + + /** + * @hide + * The default value for preferred CDMA roaming mode (aka CDMA system select.) + * CDMA_ROAMING_MODE_RADIO_DEFAULT = the default roaming mode from the radio + * CDMA_ROAMING_MODE_HOME = Home Networks + * CDMA_ROAMING_MODE_AFFILIATED = Roaming on Affiliated networks + * CDMA_ROAMING_MODE_ANY = Roaming on any networks + */ + public static final String KEY_CDMA_ROAMING_MODE_INT = "cdma_roaming_mode_int"; + /** @hide */ + public static final int CDMA_ROAMING_MODE_RADIO_DEFAULT = -1; + /** @hide */ + public static final int CDMA_ROAMING_MODE_HOME = 0; + /** @hide */ + public static final int CDMA_ROAMING_MODE_AFFILIATED = 1; + /** @hide */ + public static final int CDMA_ROAMING_MODE_ANY = 2; + /** + * Boolean indicating if support is provided for directly dialing FDN number from FDN list. + * If false, this feature is not supported. + * @hide + */ + public static final String KEY_SUPPORT_DIRECT_FDN_DIALING_BOOL = + "support_direct_fdn_dialing_bool"; + + /** + * Report IMEI as device id even if it's a CDMA/LTE phone. + * + * @hide + */ + public static final String KEY_FORCE_IMEI_BOOL = "force_imei_bool"; + + /** + * The families of Radio Access Technologies that will get clustered and ratcheted, + * ie, we will report transitions up within the family, but not down until we change + * cells. This prevents flapping between base technologies and higher techs that are + * granted on demand within the cell. + * @hide + */ + public static final String KEY_RATCHET_RAT_FAMILIES = + "ratchet_rat_families"; + + /** + * Flag indicating whether some telephony logic will treat a call which was formerly a video + * call as if it is still a video call. When {@code true}: + * <p> + * Logic which will automatically drop a video call which takes place over WIFI when a + * voice call is answered (see {@link #KEY_DROP_VIDEO_CALL_WHEN_ANSWERING_AUDIO_CALL_BOOL}. + * <p> + * Logic which determines whether the user can use TTY calling. + */ + public static final String KEY_TREAT_DOWNGRADED_VIDEO_CALLS_AS_VIDEO_CALLS_BOOL = + "treat_downgraded_video_calls_as_video_calls_bool"; + + /** + * When {@code true}, if the user is in an ongoing video call over WIFI and answers an incoming + * audio call, the video call will be disconnected before the audio call is answered. This is + * in contrast to the usual expected behavior where a foreground video call would be put into + * the background and held when an incoming audio call is answered. + */ + public static final String KEY_DROP_VIDEO_CALL_WHEN_ANSWERING_AUDIO_CALL_BOOL = + "drop_video_call_when_answering_audio_call_bool"; + + /** + * Flag indicating whether the carrier supports merging wifi calls when VoWIFI is disabled. + * This can happen in the case of a carrier which allows offloading video calls to WIFI + * separately of whether voice over wifi is enabled. In such a scenario when two video calls + * are downgraded to voice, they remain over wifi. However, if VoWIFI is disabled, these calls + * cannot be merged. + */ + public static final String KEY_ALLOW_MERGE_WIFI_CALLS_WHEN_VOWIFI_OFF_BOOL = + "allow_merge_wifi_calls_when_vowifi_off_bool"; + + /** + * Flag indicating whether the carrier supports the Hold command while in an IMS call. + * <p> + * The device configuration value {@code config_device_respects_hold_carrier_config} ultimately + * controls whether this carrier configuration option is used. Where + * {@code config_device_respects_hold_carrier_config} is false, the value of the + * {@link #KEY_ALLOW_HOLD_IN_IMS_CALL_BOOL} carrier configuration option is ignored. + * @hide + */ + public static final String KEY_ALLOW_HOLD_IN_IMS_CALL_BOOL = "allow_hold_in_ims_call"; + + /** + * When true, indicates that adding a call is disabled when there is an ongoing video call + * or when there is an ongoing call on wifi which was downgraded from video and VoWifi is + * turned off. + */ + public static final String KEY_ALLOW_ADD_CALL_DURING_VIDEO_CALL_BOOL = + "allow_add_call_during_video_call"; + + /** + * When true, indicates that the HD audio icon in the in-call screen should not be shown for + * VoWifi calls. + * @hide + */ + public static final String KEY_WIFI_CALLS_CAN_BE_HD_AUDIO = "wifi_calls_can_be_hd_audio"; + + /** + * When true, indicates that the HD audio icon in the in-call screen should not be shown for + * video calls. + * @hide + */ + public static final String KEY_VIDEO_CALLS_CAN_BE_HD_AUDIO = "video_calls_can_be_hd_audio"; + + /** + * Whether system apps are allowed to use fallback if carrier video call is not available. + * Defaults to {@code true}. + * + * @hide + */ + public static final String KEY_ALLOW_VIDEO_CALLING_FALLBACK_BOOL = + "allow_video_calling_fallback_bool"; + + /** + * Defines operator-specific {@link com.android.ims.ImsReasonInfo} mappings. + * + * Format: "ORIGINAL_CODE|MESSAGE|NEW_CODE" + * Where {@code ORIGINAL_CODE} corresponds to a {@link ImsReasonInfo#getCode()} code, + * {@code MESSAGE} corresponds to an expected {@link ImsReasonInfo#getExtraMessage()} string, + * and {@code NEW_CODE} is the new {@code ImsReasonInfo#CODE_*} which this combination of + * original code and message shall be remapped to. + * + * Note: If {@code *} is specified for the original code, any ImsReasonInfo with the matching + * {@code MESSAGE} will be remapped to {@code NEW_CODE}. + * + * Example: "501|call completion elsewhere|1014" + * When the {@link ImsReasonInfo#getCode()} is {@link ImsReasonInfo#CODE_USER_TERMINATED} and + * the {@link ImsReasonInfo#getExtraMessage()} is {@code "call completion elsewhere"}, + * {@link ImsReasonInfo#CODE_ANSWERED_ELSEWHERE} shall be used as the {@link ImsReasonInfo} + * code instead. + * @hide + */ + public static final String KEY_IMS_REASONINFO_MAPPING_STRING_ARRAY = + "ims_reasoninfo_mapping_string_array"; + + /** + * When {@code false}, use default title for Enhanced 4G LTE Mode settings. + * When {@code true}, use the variant. + * @hide + */ + public static final String KEY_ENHANCED_4G_LTE_TITLE_VARIANT_BOOL = + "enhanced_4g_lte_title_variant_bool"; + + /** + * Indicates whether the carrier wants to notify the user when handover of an LTE video call to + * WIFI fails. + * <p> + * When {@code true}, if a video call starts on LTE and the modem reports a failure to handover + * the call to WIFI or if no handover success is reported within 60 seconds of call initiation, + * the {@link android.telephony.TelephonyManager#EVENT_HANDOVER_TO_WIFI_FAILED} event is raised + * on the connection. + * @hide + */ + public static final String KEY_NOTIFY_VT_HANDOVER_TO_WIFI_FAILURE_BOOL = + "notify_vt_handover_to_wifi_failure_bool"; + + /** + * A upper case list of CNAP names that are unhelpful to the user for distinguising calls and + * should be filtered out of the CNAP information. This includes CNAP names such as "WIRELESS + * CALLER" or "UNKNOWN NAME". By default, if there are no filtered names for this carrier, null + * is returned. + * @hide + */ + public static final String KEY_FILTERED_CNAP_NAMES_STRING_ARRAY = "filtered_cnap_names_string_array"; + + /** + * The RCS configuration server URL. This URL is used to initiate RCS provisioning. + */ + public static final String KEY_RCS_CONFIG_SERVER_URL_STRING = "rcs_config_server_url_string"; + + /** + * Determine whether user can change Wi-Fi Calling preference in roaming. + * {@code false} - roaming preference {@link KEY_CARRIER_DEFAULT_WFC_IMS_ROAMING_MODE_INT} is + * the same as home preference {@link KEY_CARRIER_DEFAULT_WFC_IMS_MODE_INT} + * and cannot be changed. + * {@code true} - roaming preference can be changed by user independently. + * + * @hide + */ + public static final String KEY_EDITABLE_WFC_ROAMING_MODE_BOOL = + "editable_wfc_roaming_mode_bool"; + + /** + * Determine whether current lpp_mode used for E-911 needs to be kept persistently. + * {@code false} - not keeping the lpp_mode means using default configuration of gps.conf + * when sim is not presented. + * {@code true} - current lpp_profile of carrier will be kepted persistently + * even after sim is removed. + * + * @hide + */ + public static final String KEY_PERSIST_LPP_MODE_BOOL = "persist_lpp_mode_bool"; + + /** + * Carrier specified WiFi networks. + * @hide + */ + public static final String KEY_CARRIER_WIFI_STRING_ARRAY = "carrier_wifi_string_array"; + + /** + * Time delay (in ms) after which we show the notification to switch the preferred + * network. + * @hide + */ + public static final String KEY_PREF_NETWORK_NOTIFICATION_DELAY_INT = + "network_notification_delay_int"; + + /** + * Time delay (in ms) after which we show the notification for emergency calls, + * while the device is registered over WFC. Default value is -1, which indicates + * that this notification is not pertinent for a particular carrier. We've added a delay + * to prevent false positives. + * @hide + */ + public static final String KEY_EMERGENCY_NOTIFICATION_DELAY_INT = + "emergency_notification_delay_int"; + + /** + * When {@code true}, the carrier allows the user of the + * {@link TelephonyManager#sendUssdRequest(String, TelephonyManager.UssdResponseCallback, + * Handler)} API to perform USSD requests. {@code True} by default. + * @hide + */ + public static final String KEY_ALLOW_USSD_REQUESTS_VIA_TELEPHONY_MANAGER_BOOL = + "allow_ussd_requests_via_telephony_manager_bool"; + + /** + * Indicates whether the carrier supports 3gpp call forwarding MMI codes while roaming. If + * false, the user will be notified that call forwarding is not available when the MMI code + * fails. + */ + public static final String KEY_SUPPORT_3GPP_CALL_FORWARDING_WHILE_ROAMING_BOOL = + "support_3gpp_call_forwarding_while_roaming_bool"; + + /** + * When {@code true}, the user will be notified when they attempt to place an international call + * when the call is placed using wifi calling. + * @hide + */ + public static final String KEY_NOTIFY_INTERNATIONAL_CALL_ON_WFC_BOOL = + "notify_international_call_on_wfc_bool"; + + /** + * An array containing custom call forwarding number prefixes that will be blocked while the + * device is reporting that it is roaming. By default, there are no custom call + * forwarding prefixes and none of these numbers will be filtered. If one or more entries are + * present, the system will not complete the call and display an error message. + * + * To display a message to the user when call forwarding fails for 3gpp MMI codes while roaming, + * use the {@link #KEY_SUPPORT_3GPP_CALL_FORWARDING_WHILE_ROAMING_BOOL} option instead. + */ + public static final String KEY_CALL_FORWARDING_BLOCKS_WHILE_ROAMING_STRING_ARRAY = + "call_forwarding_blocks_while_roaming_string_array"; + + /** + * The day of the month (1-31) on which the data cycle rolls over. + * <p> + * If the current month does not have this day, the cycle will roll over at + * the start of the next month. + * <p> + * This setting may be still overridden by explicit user choice. By default, + * the platform value will be used. + */ + public static final String KEY_MONTHLY_DATA_CYCLE_DAY_INT = + "monthly_data_cycle_day_int"; + + /** + * When {@link #KEY_MONTHLY_DATA_CYCLE_DAY_INT}, {@link #KEY_DATA_LIMIT_THRESHOLD_BYTES_LONG}, + * or {@link #KEY_DATA_WARNING_THRESHOLD_BYTES_LONG} are set to this value, the platform default + * value will be used for that key. + * + * @hide + */ + @Deprecated + public static final int DATA_CYCLE_USE_PLATFORM_DEFAULT = -1; + + /** + * Flag indicating that a data cycle threshold should be disabled. + * <p> + * If {@link #KEY_DATA_WARNING_THRESHOLD_BYTES_LONG} is set to this value, the platform's + * default data warning, if one exists, will be disabled. A user selected data warning will not + * be overridden. + * <p> + * If {@link #KEY_DATA_LIMIT_THRESHOLD_BYTES_LONG} is set to this value, the platform's + * default data limit, if one exists, will be disabled. A user selected data limit will not be + * overridden. + */ + public static final int DATA_CYCLE_THRESHOLD_DISABLED = -2; + + /** + * Controls the data usage warning. + * <p> + * If the user uses more than this amount of data in their billing cycle, as defined by + * {@link #KEY_MONTHLY_DATA_CYCLE_DAY_INT}, the user will be alerted about the usage. + * If the value is set to {@link #DATA_CYCLE_THRESHOLD_DISABLED}, the data usage warning will + * be disabled. + * <p> + * This setting may be overridden by explicit user choice. By default, the platform value + * will be used. + */ + public static final String KEY_DATA_WARNING_THRESHOLD_BYTES_LONG = + "data_warning_threshold_bytes_long"; + + /** + * Controls the cellular data limit. + * <p> + * If the user uses more than this amount of data in their billing cycle, as defined by + * {@link #KEY_MONTHLY_DATA_CYCLE_DAY_INT}, cellular data will be turned off by the user's + * phone. If the value is set to {@link #DATA_CYCLE_THRESHOLD_DISABLED}, the data limit will be + * disabled. + * <p> + * This setting may be overridden by explicit user choice. By default, the platform value + * will be used. + */ + public static final String KEY_DATA_LIMIT_THRESHOLD_BYTES_LONG = + "data_limit_threshold_bytes_long"; + + /** + * Offset to be reduced from rsrp threshold while calculating signal strength level. + * @hide + */ + public static final String KEY_LTE_EARFCNS_RSRP_BOOST_INT = "lte_earfcns_rsrp_boost_int"; + + /** + * List of EARFCN (E-UTRA Absolute Radio Frequency Channel Number, + * Reference: 3GPP TS 36.104 5.4.3) inclusive ranges on which lte_rsrp_boost_int + * will be applied. Format of the String array is expected to be {"erafcn1_start-earfcn1_end", + * "earfcn2_start-earfcn2_end" ... } + * @hide + */ + public static final String KEY_BOOSTED_LTE_EARFCNS_STRING_ARRAY = + "boosted_lte_earfcns_string_array"; + + /** + * Key identifying if voice call barring notification is required to be shown to the user. + * @hide + */ + public static final String KEY_DISABLE_VOICE_BARRING_NOTIFICATION_BOOL = + "disable_voice_barring_notification_bool"; + + /** + * List of operators considered non-roaming which won't show roaming icon. + * <p> + * Can use mcc or mcc+mnc as item. For example, 302 or 21407. + * If operators, 21404 and 21407, make roaming agreements, users of 21404 should not see + * the roaming icon as using 21407 network. + * @hide + */ + public static final String KEY_NON_ROAMING_OPERATOR_STRING_ARRAY = + "non_roaming_operator_string_array"; + + /** + * List of operators considered roaming with the roaming icon. + * <p> + * Can use mcc or mcc+mnc as item. For example, 302 or 21407. + * If operators, 21404 and 21407, make roaming agreements, users of 21404 should see + * the roaming icon as using 21407 network. + * <p> + * A match on this supersedes a match on {@link #KEY_NON_ROAMING_OPERATOR_STRING_ARRAY}. + * @hide + */ + public static final String KEY_ROAMING_OPERATOR_STRING_ARRAY = + "roaming_operator_string_array"; + + /** + * URL from which the proto containing the public key of the Carrier used for + * IMSI encryption will be downloaded. + * @hide + */ + public static final String IMSI_KEY_DOWNLOAD_URL_STRING = "imsi_key_download_url_string"; + + /** + * Identifies if the key is available for WLAN or EPDG or both. The value is a bitmask. + * 0 indicates that neither EPDG or WLAN is enabled. + * 1 indicates that key type {@link TelephonyManager#KEY_TYPE_EPDG} is enabled. + * 2 indicates that key type {@link TelephonyManager#KEY_TYPE_WLAN} is enabled. + * 3 indicates that both are enabled. + * @hide + */ + public static final String IMSI_KEY_AVAILABILITY_INT = "imsi_key_availability_int"; + + + /** + * Key identifying if the CDMA Caller ID presentation and suppression MMI codes + * should be converted to 3GPP CLIR codes when a multimode (CDMA+UMTS+LTE) device is roaming + * on a 3GPP network. Specifically *67<number> will be converted to #31#<number> and + * *82<number> will be converted to *31#<number> before dialing a call when this key is + * set TRUE and device is roaming on a 3GPP network. + * @hide + */ + public static final String KEY_CONVERT_CDMA_CALLER_ID_MMI_CODES_WHILE_ROAMING_ON_3GPP_BOOL = + "convert_cdma_caller_id_mmi_codes_while_roaming_on_3gpp_bool"; + + /** + * Flag specifying whether IMS registration state menu is shown in Status Info setting, + * default to false. + * @hide + */ + public static final String KEY_SHOW_IMS_REGISTRATION_STATUS_BOOL = + "show_ims_registration_status_bool"; + + /** The default value for every variable. */ + private final static PersistableBundle sDefaults; + + static { + sDefaults = new PersistableBundle(); + sDefaults.putBoolean(KEY_ALLOW_HOLD_IN_IMS_CALL_BOOL, true); + sDefaults.putBoolean(KEY_ADDITIONAL_CALL_SETTING_BOOL, true); + sDefaults.putBoolean(KEY_ALLOW_EMERGENCY_NUMBERS_IN_CALL_LOG_BOOL, false); + sDefaults.putBoolean(KEY_ALLOW_LOCAL_DTMF_TONES_BOOL, true); + sDefaults.putBoolean(KEY_APN_EXPAND_BOOL, true); + sDefaults.putBoolean(KEY_AUTO_RETRY_ENABLED_BOOL, false); + sDefaults.putBoolean(KEY_CARRIER_SETTINGS_ENABLE_BOOL, false); + sDefaults.putBoolean(KEY_CARRIER_VOLTE_AVAILABLE_BOOL, false); + sDefaults.putBoolean(KEY_CARRIER_VT_AVAILABLE_BOOL, false); + sDefaults.putBoolean(KEY_NOTIFY_HANDOVER_VIDEO_FROM_WIFI_TO_LTE_BOOL, false); + sDefaults.putBoolean(KEY_SUPPORT_DOWNGRADE_VT_TO_AUDIO_BOOL, true); + sDefaults.putString(KEY_DEFAULT_VM_NUMBER_STRING, ""); + sDefaults.putBoolean(KEY_IGNORE_DATA_ENABLED_CHANGED_FOR_VIDEO_CALLS, true); + sDefaults.putBoolean(KEY_VILTE_DATA_IS_METERED_BOOL, true); + sDefaults.putBoolean(KEY_CARRIER_WFC_IMS_AVAILABLE_BOOL, false); + sDefaults.putBoolean(KEY_CARRIER_WFC_SUPPORTS_WIFI_ONLY_BOOL, false); + sDefaults.putBoolean(KEY_CARRIER_DEFAULT_WFC_IMS_ENABLED_BOOL, false); + sDefaults.putBoolean(KEY_CARRIER_DEFAULT_WFC_IMS_ROAMING_ENABLED_BOOL, false); + sDefaults.putBoolean(KEY_CARRIER_PROMOTE_WFC_ON_CALL_FAIL_BOOL, false); + sDefaults.putInt(KEY_CARRIER_DEFAULT_WFC_IMS_MODE_INT, 2); + sDefaults.putInt(KEY_CARRIER_DEFAULT_WFC_IMS_ROAMING_MODE_INT, 2); + sDefaults.putBoolean(KEY_CARRIER_FORCE_DISABLE_ETWS_CMAS_TEST_BOOL, false); + sDefaults.putBoolean(KEY_CARRIER_VOLTE_PROVISIONING_REQUIRED_BOOL, false); + sDefaults.putBoolean(KEY_CARRIER_VOLTE_OVERRIDE_WFC_PROVISIONING_BOOL, false); + sDefaults.putBoolean(KEY_CARRIER_VOLTE_TTY_SUPPORTED_BOOL, true); + sDefaults.putBoolean(KEY_CARRIER_ALLOW_TURNOFF_IMS_BOOL, true); + sDefaults.putBoolean(KEY_CARRIER_IMS_GBA_REQUIRED_BOOL, false); + sDefaults.putBoolean(KEY_CARRIER_INSTANT_LETTERING_AVAILABLE_BOOL, false); + sDefaults.putBoolean(KEY_CARRIER_USE_IMS_FIRST_FOR_EMERGENCY_BOOL, true); + sDefaults.putString(KEY_CARRIER_INSTANT_LETTERING_INVALID_CHARS_STRING, ""); + sDefaults.putString(KEY_CARRIER_INSTANT_LETTERING_ESCAPED_CHARS_STRING, ""); + sDefaults.putString(KEY_CARRIER_INSTANT_LETTERING_ENCODING_STRING, ""); + sDefaults.putInt(KEY_CARRIER_INSTANT_LETTERING_LENGTH_LIMIT_INT, 64); + sDefaults.putBoolean(KEY_DISABLE_CDMA_ACTIVATION_CODE_BOOL, false); + sDefaults.putBoolean(KEY_DTMF_TYPE_ENABLED_BOOL, false); + sDefaults.putBoolean(KEY_ENABLE_DIALER_KEY_VIBRATION_BOOL, true); + sDefaults.putBoolean(KEY_HAS_IN_CALL_NOISE_SUPPRESSION_BOOL, false); + sDefaults.putBoolean(KEY_HIDE_CARRIER_NETWORK_SETTINGS_BOOL, false); + sDefaults.putBoolean(KEY_SIMPLIFIED_NETWORK_SETTINGS_BOOL, false); + sDefaults.putBoolean(KEY_HIDE_SIM_LOCK_SETTINGS_BOOL, false); + + sDefaults.putBoolean(KEY_CARRIER_VOLTE_PROVISIONED_BOOL, false); + sDefaults.putBoolean(KEY_IGNORE_SIM_NETWORK_LOCKED_EVENTS_BOOL, false); + sDefaults.putBoolean(KEY_MDN_IS_ADDITIONAL_VOICEMAIL_NUMBER_BOOL, false); + sDefaults.putBoolean(KEY_OPERATOR_SELECTION_EXPAND_BOOL, true); + sDefaults.putBoolean(KEY_PREFER_2G_BOOL, true); + sDefaults.putBoolean(KEY_SHOW_APN_SETTING_CDMA_BOOL, false); + sDefaults.putBoolean(KEY_SHOW_CDMA_CHOICES_BOOL, false); + sDefaults.putBoolean(KEY_SMS_REQUIRES_DESTINATION_NUMBER_CONVERSION_BOOL, false); + sDefaults.putBoolean(KEY_SHOW_ONSCREEN_DIAL_BUTTON_BOOL, true); + sDefaults.putBoolean(KEY_SIM_NETWORK_UNLOCK_ALLOW_DISMISS_BOOL, true); + sDefaults.putBoolean(KEY_SUPPORT_PAUSE_IMS_VIDEO_CALLS_BOOL, false); + sDefaults.putBoolean(KEY_SUPPORT_SWAP_AFTER_MERGE_BOOL, true); + sDefaults.putBoolean(KEY_USE_HFA_FOR_PROVISIONING_BOOL, false); + sDefaults.putBoolean(KEY_EDITABLE_VOICEMAIL_NUMBER_BOOL, false); + sDefaults.putBoolean(KEY_USE_OTASP_FOR_PROVISIONING_BOOL, false); + sDefaults.putBoolean(KEY_VOICEMAIL_NOTIFICATION_PERSISTENT_BOOL, false); + sDefaults.putBoolean(KEY_VOICE_PRIVACY_DISABLE_UI_BOOL, false); + sDefaults.putBoolean(KEY_WORLD_PHONE_BOOL, false); + sDefaults.putBoolean(KEY_REQUIRE_ENTITLEMENT_CHECKS_BOOL, true); + sDefaults.putBoolean(KEY_RESTART_RADIO_ON_PDP_FAIL_REGULAR_DEACTIVATION_BOOL, false); + sDefaults.putInt(KEY_VOLTE_REPLACEMENT_RAT_INT, 0); + sDefaults.putString(KEY_DEFAULT_SIM_CALL_MANAGER_STRING, ""); + sDefaults.putString(KEY_VVM_DESTINATION_NUMBER_STRING, ""); + sDefaults.putInt(KEY_VVM_PORT_NUMBER_INT, 0); + sDefaults.putString(KEY_VVM_TYPE_STRING, ""); + sDefaults.putBoolean(KEY_VVM_CELLULAR_DATA_REQUIRED_BOOL, false); + sDefaults.putString(KEY_VVM_CLIENT_PREFIX_STRING,"//VVM"); + sDefaults.putBoolean(KEY_VVM_SSL_ENABLED_BOOL,false); + sDefaults.putStringArray(KEY_VVM_DISABLED_CAPABILITIES_STRING_ARRAY, null); + sDefaults.putBoolean(KEY_VVM_LEGACY_MODE_ENABLED_BOOL,false); + sDefaults.putBoolean(KEY_VVM_PREFETCH_BOOL, true); + sDefaults.putString(KEY_CARRIER_VVM_PACKAGE_NAME_STRING, ""); + sDefaults.putStringArray(KEY_CARRIER_VVM_PACKAGE_NAME_STRING_ARRAY, null); + sDefaults.putBoolean(KEY_SHOW_ICCID_IN_SIM_STATUS_BOOL, false); + sDefaults.putBoolean(KEY_CI_ACTION_ON_SYS_UPDATE_BOOL, false); + sDefaults.putString(KEY_CI_ACTION_ON_SYS_UPDATE_INTENT_STRING, ""); + sDefaults.putString(KEY_CI_ACTION_ON_SYS_UPDATE_EXTRA_STRING, ""); + sDefaults.putString(KEY_CI_ACTION_ON_SYS_UPDATE_EXTRA_VAL_STRING, ""); + sDefaults.putBoolean(KEY_CSP_ENABLED_BOOL, false); + sDefaults.putBoolean(KEY_ALLOW_ADDING_APNS_BOOL, true); + sDefaults.putStringArray(KEY_READ_ONLY_APN_TYPES_STRING_ARRAY, new String[] {"dun"}); + sDefaults.putStringArray(KEY_READ_ONLY_APN_FIELDS_STRING_ARRAY, null); + sDefaults.putBoolean(KEY_BROADCAST_EMERGENCY_CALL_STATE_CHANGES_BOOL, false); + sDefaults.putBoolean(KEY_ALWAYS_SHOW_EMERGENCY_ALERT_ONOFF_BOOL, false); + sDefaults.putBoolean(KEY_DISABLE_SEVERE_WHEN_EXTREME_DISABLED_BOOL, true); + sDefaults.putLong(KEY_MESSAGE_EXPIRATION_TIME_LONG, 86400000L); + sDefaults.putStringArray(KEY_CARRIER_DATA_CALL_RETRY_CONFIG_STRINGS, new String[]{ + "default:default_randomization=2000,5000,10000,20000,40000,80000:5000,160000:5000," + + "320000:5000,640000:5000,1280000:5000,1800000:5000", + "mms:default_randomization=2000,5000,10000,20000,40000,80000:5000,160000:5000," + + "320000:5000,640000:5000,1280000:5000,1800000:5000", + "others:max_retries=3, 5000, 5000, 5000"}); + sDefaults.putLong(KEY_CARRIER_DATA_CALL_APN_DELAY_DEFAULT_LONG, 20000); + sDefaults.putLong(KEY_CARRIER_DATA_CALL_APN_DELAY_FASTER_LONG, 3000); + sDefaults.putLong(KEY_CARRIER_DATA_CALL_APN_RETRY_AFTER_DISCONNECT_LONG, 10000); + sDefaults.putString(KEY_CARRIER_ERI_FILE_NAME_STRING, "eri.xml"); + sDefaults.putInt(KEY_DURATION_BLOCKING_DISABLED_AFTER_EMERGENCY_INT, 7200); + sDefaults.putStringArray(KEY_CARRIER_METERED_APN_TYPES_STRINGS, + new String[]{"default", "mms", "dun", "supl"}); + sDefaults.putStringArray(KEY_CARRIER_METERED_ROAMING_APN_TYPES_STRINGS, + new String[]{"default", "mms", "dun", "supl"}); + // By default all APNs are unmetered if the device is on IWLAN. + sDefaults.putStringArray(KEY_CARRIER_METERED_IWLAN_APN_TYPES_STRINGS, + new String[]{}); + + sDefaults.putIntArray(KEY_ONLY_SINGLE_DC_ALLOWED_INT_ARRAY, + new int[]{ + 4, /* IS95A */ + 5, /* IS95B */ + 6, /* 1xRTT */ + 7, /* EVDO_0 */ + 8, /* EVDO_A */ + 12 /* EVDO_B */ + }); + sDefaults.putStringArray(KEY_GSM_ROAMING_NETWORKS_STRING_ARRAY, null); + sDefaults.putStringArray(KEY_GSM_NONROAMING_NETWORKS_STRING_ARRAY, null); + sDefaults.putString(KEY_CONFIG_IMS_PACKAGE_OVERRIDE_STRING, null); + sDefaults.putStringArray(KEY_CDMA_ROAMING_NETWORKS_STRING_ARRAY, null); + sDefaults.putStringArray(KEY_CDMA_NONROAMING_NETWORKS_STRING_ARRAY, null); + sDefaults.putStringArray(KEY_DIAL_STRING_REPLACE_STRING_ARRAY, null); + sDefaults.putBoolean(KEY_FORCE_HOME_NETWORK_BOOL, false); + sDefaults.putInt(KEY_GSM_DTMF_TONE_DELAY_INT, 0); + sDefaults.putInt(KEY_IMS_DTMF_TONE_DELAY_INT, 0); + sDefaults.putInt(KEY_CDMA_DTMF_TONE_DELAY_INT, 100); + sDefaults.putInt(KEY_CDMA_3WAYCALL_FLASH_DELAY_INT , 0); + sDefaults.putBoolean(KEY_SUPPORT_CONFERENCE_CALL_BOOL, true); + sDefaults.putBoolean(KEY_SUPPORT_IMS_CONFERENCE_CALL_BOOL, true); + sDefaults.putBoolean(KEY_SUPPORT_VIDEO_CONFERENCE_CALL_BOOL, false); + sDefaults.putBoolean(KEY_IS_IMS_CONFERENCE_SIZE_ENFORCED_BOOL, false); + sDefaults.putInt(KEY_IMS_CONFERENCE_SIZE_LIMIT_INT, 5); + sDefaults.putBoolean(KEY_DISPLAY_HD_AUDIO_PROPERTY_BOOL, true); + sDefaults.putBoolean(KEY_EDITABLE_ENHANCED_4G_LTE_BOOL, true); + sDefaults.putBoolean(KEY_HIDE_ENHANCED_4G_LTE_BOOL, false); + sDefaults.putBoolean(KEY_HIDE_IMS_APN_BOOL, false); + sDefaults.putBoolean(KEY_HIDE_PREFERRED_NETWORK_TYPE_BOOL, false); + sDefaults.putBoolean(KEY_ALLOW_EMERGENCY_VIDEO_CALLS_BOOL, false); + sDefaults.putStringArray(KEY_ENABLE_APPS_STRING_ARRAY, null); + sDefaults.putBoolean(KEY_EDITABLE_WFC_MODE_BOOL, true); + sDefaults.putStringArray(KEY_WFC_OPERATOR_ERROR_CODES_STRING_ARRAY, null); + sDefaults.putInt(KEY_WFC_SPN_FORMAT_IDX_INT, 0); + sDefaults.putInt(KEY_WFC_DATA_SPN_FORMAT_IDX_INT, 0); + sDefaults.putString(KEY_WFC_EMERGENCY_ADDRESS_CARRIER_APP_STRING, ""); + sDefaults.putBoolean(KEY_CONFIG_WIFI_DISABLE_IN_ECBM, false); + sDefaults.putBoolean(KEY_CARRIER_NAME_OVERRIDE_BOOL, false); + sDefaults.putString(KEY_CARRIER_NAME_STRING, ""); + sDefaults.putBoolean(KEY_SUPPORT_DIRECT_FDN_DIALING_BOOL, false); + sDefaults.putBoolean(KEY_CARRIER_DEFAULT_DATA_ROAMING_ENABLED_BOOL, false); + + // MMS defaults + sDefaults.putBoolean(KEY_MMS_ALIAS_ENABLED_BOOL, false); + sDefaults.putBoolean(KEY_MMS_ALLOW_ATTACH_AUDIO_BOOL, true); + sDefaults.putBoolean(KEY_MMS_APPEND_TRANSACTION_ID_BOOL, false); + sDefaults.putBoolean(KEY_MMS_GROUP_MMS_ENABLED_BOOL, true); + sDefaults.putBoolean(KEY_MMS_MMS_DELIVERY_REPORT_ENABLED_BOOL, false); + sDefaults.putBoolean(KEY_MMS_MMS_ENABLED_BOOL, true); + sDefaults.putBoolean(KEY_MMS_MMS_READ_REPORT_ENABLED_BOOL, false); + sDefaults.putBoolean(KEY_MMS_MULTIPART_SMS_ENABLED_BOOL, true); + sDefaults.putBoolean(KEY_MMS_NOTIFY_WAP_MMSC_ENABLED_BOOL, false); + sDefaults.putBoolean(KEY_MMS_SEND_MULTIPART_SMS_AS_SEPARATE_MESSAGES_BOOL, false); + sDefaults.putBoolean(KEY_MMS_SHOW_CELL_BROADCAST_APP_LINKS_BOOL, true); + sDefaults.putBoolean(KEY_MMS_SMS_DELIVERY_REPORT_ENABLED_BOOL, true); + sDefaults.putBoolean(KEY_MMS_SUPPORT_HTTP_CHARSET_HEADER_BOOL, false); + sDefaults.putBoolean(KEY_MMS_SUPPORT_MMS_CONTENT_DISPOSITION_BOOL, true); + sDefaults.putBoolean(KEY_MMS_CLOSE_CONNECTION_BOOL, false); + sDefaults.putInt(KEY_MMS_ALIAS_MAX_CHARS_INT, 48); + sDefaults.putInt(KEY_MMS_ALIAS_MIN_CHARS_INT, 2); + sDefaults.putInt(KEY_MMS_HTTP_SOCKET_TIMEOUT_INT, 60 * 1000); + sDefaults.putInt(KEY_MMS_MAX_IMAGE_HEIGHT_INT, 480); + sDefaults.putInt(KEY_MMS_MAX_IMAGE_WIDTH_INT, 640); + sDefaults.putInt(KEY_MMS_MAX_MESSAGE_SIZE_INT, 300 * 1024); + sDefaults.putInt(KEY_MMS_MESSAGE_TEXT_MAX_SIZE_INT, -1); + sDefaults.putInt(KEY_MMS_RECIPIENT_LIMIT_INT, Integer.MAX_VALUE); + sDefaults.putInt(KEY_MMS_SMS_TO_MMS_TEXT_LENGTH_THRESHOLD_INT, -1); + sDefaults.putInt(KEY_MMS_SMS_TO_MMS_TEXT_THRESHOLD_INT, -1); + sDefaults.putInt(KEY_MMS_SUBJECT_MAX_LENGTH_INT, 40); + sDefaults.putString(KEY_MMS_EMAIL_GATEWAY_NUMBER_STRING, ""); + sDefaults.putString(KEY_MMS_HTTP_PARAMS_STRING, ""); + sDefaults.putString(KEY_MMS_NAI_SUFFIX_STRING, ""); + sDefaults.putString(KEY_MMS_UA_PROF_TAG_NAME_STRING, "x-wap-profile"); + sDefaults.putString(KEY_MMS_UA_PROF_URL_STRING, ""); + sDefaults.putString(KEY_MMS_USER_AGENT_STRING, ""); + sDefaults.putBoolean(KEY_ALLOW_NON_EMERGENCY_CALLS_IN_ECM_BOOL, true); + sDefaults.putBoolean(KEY_USE_RCS_PRESENCE_BOOL, false); + sDefaults.putBoolean(KEY_FORCE_IMEI_BOOL, false); + sDefaults.putInt(KEY_CDMA_ROAMING_MODE_INT, CDMA_ROAMING_MODE_RADIO_DEFAULT); + sDefaults.putString(KEY_RCS_CONFIG_SERVER_URL_STRING, ""); + + // Carrier Signalling Receivers + sDefaults.putString(KEY_CARRIER_SETUP_APP_STRING, ""); + sDefaults.putStringArray(KEY_CARRIER_APP_WAKE_SIGNAL_CONFIG_STRING_ARRAY, + new String[]{ + "com.android.carrierdefaultapp/.CarrierDefaultBroadcastReceiver:" + + "com.android.internal.telephony.CARRIER_SIGNAL_RESET" + }); + sDefaults.putStringArray(KEY_CARRIER_APP_NO_WAKE_SIGNAL_CONFIG_STRING_ARRAY, null); + + + // Default carrier app configurations + sDefaults.putStringArray(KEY_CARRIER_DEFAULT_ACTIONS_ON_REDIRECTION_STRING_ARRAY, + new String[]{ + "9, 4, 1" + //9: CARRIER_ACTION_REGISTER_NETWORK_AVAIL + //4: CARRIER_ACTION_DISABLE_METERED_APNS + //1: CARRIER_ACTION_SHOW_PORTAL_NOTIFICATION + }); + sDefaults.putStringArray(KEY_CARRIER_DEFAULT_ACTIONS_ON_RESET, new String[]{ + "6, 8" + //6: CARRIER_ACTION_CANCEL_ALL_NOTIFICATIONS + //8: CARRIER_ACTION_DISABLE_DEFAULT_URL_HANDLER + }); + sDefaults.putStringArray(KEY_CARRIER_DEFAULT_ACTIONS_ON_DEFAULT_NETWORK_AVAILABLE, + new String[] { + String.valueOf(false) + ": 7", + //7: CARRIER_ACTION_ENABLE_DEFAULT_URL_HANDLER + String.valueOf(true) + ": 8" + //8: CARRIER_ACTION_DISABLE_DEFAULT_URL_HANDLER + }); + sDefaults.putStringArray(KEY_CARRIER_DEFAULT_REDIRECTION_URL_STRING_ARRAY, null); + + sDefaults.putInt(KEY_MONTHLY_DATA_CYCLE_DAY_INT, DATA_CYCLE_USE_PLATFORM_DEFAULT); + sDefaults.putLong(KEY_DATA_WARNING_THRESHOLD_BYTES_LONG, DATA_CYCLE_USE_PLATFORM_DEFAULT); + sDefaults.putLong(KEY_DATA_LIMIT_THRESHOLD_BYTES_LONG, DATA_CYCLE_USE_PLATFORM_DEFAULT); + + // Rat families: {GPRS, EDGE}, {EVDO, EVDO_A, EVDO_B}, {UMTS, HSPA, HSDPA, HSUPA, HSPAP}, + // {LTE, LTE_CA} + // Order is important - lowest precidence first + sDefaults.putStringArray(KEY_RATCHET_RAT_FAMILIES, + new String[]{"1,2","7,8,12","3,11,9,10,15","14,19"}); + sDefaults.putBoolean(KEY_TREAT_DOWNGRADED_VIDEO_CALLS_AS_VIDEO_CALLS_BOOL, false); + sDefaults.putBoolean(KEY_DROP_VIDEO_CALL_WHEN_ANSWERING_AUDIO_CALL_BOOL, false); + sDefaults.putBoolean(KEY_ALLOW_MERGE_WIFI_CALLS_WHEN_VOWIFI_OFF_BOOL, true); + sDefaults.putBoolean(KEY_ALLOW_ADD_CALL_DURING_VIDEO_CALL_BOOL, true); + sDefaults.putBoolean(KEY_WIFI_CALLS_CAN_BE_HD_AUDIO, true); + sDefaults.putBoolean(KEY_VIDEO_CALLS_CAN_BE_HD_AUDIO, true); + sDefaults.putBoolean(KEY_ALLOW_VIDEO_CALLING_FALLBACK_BOOL, true); + + sDefaults.putStringArray(KEY_IMS_REASONINFO_MAPPING_STRING_ARRAY, null); + sDefaults.putBoolean(KEY_ENHANCED_4G_LTE_TITLE_VARIANT_BOOL, false); + sDefaults.putBoolean(KEY_NOTIFY_VT_HANDOVER_TO_WIFI_FAILURE_BOOL, false); + sDefaults.putStringArray(KEY_FILTERED_CNAP_NAMES_STRING_ARRAY, null); + sDefaults.putBoolean(KEY_EDITABLE_WFC_ROAMING_MODE_BOOL, false); + sDefaults.putBoolean(KEY_STK_DISABLE_LAUNCH_BROWSER_BOOL, false); + sDefaults.putBoolean(KEY_PERSIST_LPP_MODE_BOOL, false); + sDefaults.putStringArray(KEY_CARRIER_WIFI_STRING_ARRAY, null); + sDefaults.putInt(KEY_PREF_NETWORK_NOTIFICATION_DELAY_INT, -1); + sDefaults.putInt(KEY_EMERGENCY_NOTIFICATION_DELAY_INT, -1); + sDefaults.putBoolean(KEY_ALLOW_USSD_REQUESTS_VIA_TELEPHONY_MANAGER_BOOL, true); + sDefaults.putBoolean(KEY_SUPPORT_3GPP_CALL_FORWARDING_WHILE_ROAMING_BOOL, true); + sDefaults.putBoolean(KEY_NOTIFY_INTERNATIONAL_CALL_ON_WFC_BOOL, false); + sDefaults.putStringArray(KEY_CALL_FORWARDING_BLOCKS_WHILE_ROAMING_STRING_ARRAY, + null); + sDefaults.putInt(KEY_LTE_EARFCNS_RSRP_BOOST_INT, 0); + sDefaults.putStringArray(KEY_BOOSTED_LTE_EARFCNS_STRING_ARRAY, null); + sDefaults.putBoolean(KEY_DISABLE_VOICE_BARRING_NOTIFICATION_BOOL, false); + sDefaults.putInt(IMSI_KEY_AVAILABILITY_INT, 0); + sDefaults.putString(IMSI_KEY_DOWNLOAD_URL_STRING, null); + sDefaults.putBoolean(KEY_CONVERT_CDMA_CALLER_ID_MMI_CODES_WHILE_ROAMING_ON_3GPP_BOOL, + false); + sDefaults.putStringArray(KEY_NON_ROAMING_OPERATOR_STRING_ARRAY, null); + sDefaults.putStringArray(KEY_ROAMING_OPERATOR_STRING_ARRAY, null); + sDefaults.putBoolean(KEY_SHOW_IMS_REGISTRATION_STATUS_BOOL, false); + } + + /** + * Gets the configuration values for a particular subscription, which is associated with a + * specific SIM card. If an invalid subId is used, the returned config will contain default + * values. + * + * <p>Requires Permission: + * {@link android.Manifest.permission#READ_PHONE_STATE READ_PHONE_STATE} + * + * @param subId the subscription ID, normally obtained from {@link SubscriptionManager}. + * @return A {@link PersistableBundle} containing the config for the given subId, or default + * values for an invalid subId. + */ + @Nullable + public PersistableBundle getConfigForSubId(int subId) { + try { + ICarrierConfigLoader loader = getICarrierConfigLoader(); + if (loader == null) { + Rlog.w(TAG, "Error getting config for subId " + subId + + " ICarrierConfigLoader is null"); + return null; + } + return loader.getConfigForSubId(subId); + } catch (RemoteException ex) { + Rlog.e(TAG, "Error getting config for subId " + subId + ": " + + ex.toString()); + } + return null; + } + + /** + * Gets the configuration values for the default subscription. + * + * <p>Requires Permission: + * {@link android.Manifest.permission#READ_PHONE_STATE READ_PHONE_STATE} + * + * @see #getConfigForSubId + */ + @Nullable + public PersistableBundle getConfig() { + return getConfigForSubId(SubscriptionManager.getDefaultSubscriptionId()); + } + + /** + * Calling this method triggers telephony services to fetch the current carrier configuration. + * <p> + * Normally this does not need to be called because the platform reloads config on its own. + * This should be called by a carrier service app if it wants to update config at an arbitrary + * moment. + * </p> + * <p>Requires that the calling app has carrier privileges. + * <p> + * This method returns before the reload has completed, and + * {@link android.service.carrier.CarrierService#onLoadConfig} will be called from an + * arbitrary thread. + * </p> + * @see #hasCarrierPrivileges + */ + public void notifyConfigChangedForSubId(int subId) { + try { + ICarrierConfigLoader loader = getICarrierConfigLoader(); + if (loader == null) { + Rlog.w(TAG, "Error reloading config for subId=" + subId + + " ICarrierConfigLoader is null"); + return; + } + loader.notifyConfigChangedForSubId(subId); + } catch (RemoteException ex) { + Rlog.e(TAG, "Error reloading config for subId=" + subId + ": " + ex.toString()); + } + } + + /** + * Request the carrier config loader to update the cofig for phoneId. + * <p> + * Depending on simState, the config may be cleared or loaded from config app. This is only used + * by SubscriptionInfoUpdater. + * </p> + * + * @hide + */ + @SystemApi + @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) + public void updateConfigForPhoneId(int phoneId, String simState) { + try { + ICarrierConfigLoader loader = getICarrierConfigLoader(); + if (loader == null) { + Rlog.w(TAG, "Error updating config for phoneId=" + phoneId + + " ICarrierConfigLoader is null"); + return; + } + loader.updateConfigForPhoneId(phoneId, simState); + } catch (RemoteException ex) { + Rlog.e(TAG, "Error updating config for phoneId=" + phoneId + ": " + ex.toString()); + } + } + + /** {@hide} */ + public String getDefaultCarrierServicePackageName() { + try { + return getICarrierConfigLoader().getDefaultCarrierServicePackageName(); + } catch (Throwable t) { + return null; + } + } + + /** + * Returns a new bundle with the default value for every supported configuration variable. + * + * @hide + */ + @NonNull + @SystemApi + @SuppressLint("Doclava125") + public static PersistableBundle getDefaultConfig() { + return new PersistableBundle(sDefaults); + } + + /** @hide */ + @Nullable + private ICarrierConfigLoader getICarrierConfigLoader() { + return ICarrierConfigLoader.Stub + .asInterface(ServiceManager.getService(Context.CARRIER_CONFIG_SERVICE)); + } +} diff --git a/android/telephony/CarrierMessagingServiceManager.java b/android/telephony/CarrierMessagingServiceManager.java new file mode 100644 index 00000000..768d97bc --- /dev/null +++ b/android/telephony/CarrierMessagingServiceManager.java @@ -0,0 +1,99 @@ +/* + * Copyright (C) 2014 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.telephony; + +import android.content.ComponentName; +import android.content.Context; +import android.content.Intent; +import android.content.ServiceConnection; +import android.os.IBinder; +import android.service.carrier.CarrierMessagingService; +import android.service.carrier.ICarrierMessagingService; + +import com.android.internal.util.Preconditions; + +/** + * Provides basic structure for platform to connect to the carrier messaging service. + * <p> + * <code> + * CarrierMessagingServiceManager carrierMessagingServiceManager = + * new CarrierMessagingServiceManagerImpl(); + * if (carrierMessagingServiceManager.bindToCarrierMessagingService(context, carrierPackageName)) { + * // wait for onServiceReady callback + * } else { + * // Unable to bind: handle error. + * } + * </code> + * <p> Upon completion {@link #disposeConnection} should be called to unbind the + * CarrierMessagingService. + * @hide + */ +public abstract class CarrierMessagingServiceManager { + // Populated by bindToCarrierMessagingService. bindToCarrierMessagingService must complete + // prior to calling disposeConnection so that mCarrierMessagingServiceConnection is initialized. + private volatile CarrierMessagingServiceConnection mCarrierMessagingServiceConnection; + + /** + * Binds to the carrier messaging service under package {@code carrierPackageName}. This method + * should be called exactly once. + * + * @param context the context + * @param carrierPackageName the carrier package name + * @return true upon successfully binding to a carrier messaging service, false otherwise + */ + public boolean bindToCarrierMessagingService(Context context, String carrierPackageName) { + Preconditions.checkState(mCarrierMessagingServiceConnection == null); + + Intent intent = new Intent(CarrierMessagingService.SERVICE_INTERFACE); + intent.setPackage(carrierPackageName); + mCarrierMessagingServiceConnection = new CarrierMessagingServiceConnection(); + return context.bindService(intent, mCarrierMessagingServiceConnection, + Context.BIND_AUTO_CREATE); + } + + /** + * Unbinds the carrier messaging service. This method should be called exactly once. + * + * @param context the context + */ + public void disposeConnection(Context context) { + Preconditions.checkNotNull(mCarrierMessagingServiceConnection); + context.unbindService(mCarrierMessagingServiceConnection); + mCarrierMessagingServiceConnection = null; + } + + /** + * Implemented by subclasses to use the carrier messaging service once it is ready. + * + * @param carrierMessagingService the carrier messaing service interface + */ + protected abstract void onServiceReady(ICarrierMessagingService carrierMessagingService); + + /** + * A basic {@link ServiceConnection}. + */ + private final class CarrierMessagingServiceConnection implements ServiceConnection { + @Override + public void onServiceConnected(ComponentName name, IBinder service) { + onServiceReady(ICarrierMessagingService.Stub.asInterface(service)); + } + + @Override + public void onServiceDisconnected(ComponentName name) { + } + } +} diff --git a/android/telephony/CellBroadcastMessage.java b/android/telephony/CellBroadcastMessage.java new file mode 100644 index 00000000..cbb9e342 --- /dev/null +++ b/android/telephony/CellBroadcastMessage.java @@ -0,0 +1,437 @@ +/* + * Copyright (C) 2011 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.telephony; + +import android.content.ContentValues; +import android.content.Context; +import android.database.Cursor; +import android.os.Parcel; +import android.os.Parcelable; +import android.provider.Telephony; +import android.text.format.DateUtils; + +/** + * Application wrapper for {@link SmsCbMessage}. This is Parcelable so that + * decoded broadcast message objects can be passed between running Services. + * New broadcasts are received by the CellBroadcastReceiver app, which exports + * the database of previously received broadcasts at "content://cellbroadcasts/". + * The "android.permission.READ_CELL_BROADCASTS" permission is required to read + * from the ContentProvider, and writes to the database are not allowed.<p> + * + * Use {@link #createFromCursor} to create CellBroadcastMessage objects from rows + * in the database cursor returned by the ContentProvider. + * + * {@hide} + */ +public class CellBroadcastMessage implements Parcelable { + + /** Identifier for getExtra() when adding this object to an Intent. */ + public static final String SMS_CB_MESSAGE_EXTRA = + "com.android.cellbroadcastreceiver.SMS_CB_MESSAGE"; + + /** SmsCbMessage. */ + private final SmsCbMessage mSmsCbMessage; + + private final long mDeliveryTime; + private boolean mIsRead; + + /** + * Indicates the subId + * + * @hide + */ + private int mSubId = 0; + + /** + * set Subscription information + * + * @hide + */ + public void setSubId(int subId) { + mSubId = subId; + } + + /** + * get Subscription information + * + * @hide + */ + public int getSubId() { + return mSubId; + } + + public CellBroadcastMessage(SmsCbMessage message) { + mSmsCbMessage = message; + mDeliveryTime = System.currentTimeMillis(); + mIsRead = false; + } + + private CellBroadcastMessage(SmsCbMessage message, long deliveryTime, boolean isRead) { + mSmsCbMessage = message; + mDeliveryTime = deliveryTime; + mIsRead = isRead; + } + + private CellBroadcastMessage(Parcel in) { + mSmsCbMessage = new SmsCbMessage(in); + mDeliveryTime = in.readLong(); + mIsRead = (in.readInt() != 0); + mSubId = in.readInt(); + } + + /** Parcelable: no special flags. */ + @Override + public int describeContents() { + return 0; + } + + @Override + public void writeToParcel(Parcel out, int flags) { + mSmsCbMessage.writeToParcel(out, flags); + out.writeLong(mDeliveryTime); + out.writeInt(mIsRead ? 1 : 0); + out.writeInt(mSubId); + } + + public static final Parcelable.Creator<CellBroadcastMessage> CREATOR + = new Parcelable.Creator<CellBroadcastMessage>() { + @Override + public CellBroadcastMessage createFromParcel(Parcel in) { + return new CellBroadcastMessage(in); + } + + @Override + public CellBroadcastMessage[] newArray(int size) { + return new CellBroadcastMessage[size]; + } + }; + + /** + * Create a CellBroadcastMessage from a row in the database. + * @param cursor an open SQLite cursor pointing to the row to read + * @return the new CellBroadcastMessage + * @throws IllegalArgumentException if one of the required columns is missing + */ + public static CellBroadcastMessage createFromCursor(Cursor cursor) { + int geoScope = cursor.getInt( + cursor.getColumnIndexOrThrow(Telephony.CellBroadcasts.GEOGRAPHICAL_SCOPE)); + int serialNum = cursor.getInt( + cursor.getColumnIndexOrThrow(Telephony.CellBroadcasts.SERIAL_NUMBER)); + int category = cursor.getInt( + cursor.getColumnIndexOrThrow(Telephony.CellBroadcasts.SERVICE_CATEGORY)); + String language = cursor.getString( + cursor.getColumnIndexOrThrow(Telephony.CellBroadcasts.LANGUAGE_CODE)); + String body = cursor.getString( + cursor.getColumnIndexOrThrow(Telephony.CellBroadcasts.MESSAGE_BODY)); + int format = cursor.getInt( + cursor.getColumnIndexOrThrow(Telephony.CellBroadcasts.MESSAGE_FORMAT)); + int priority = cursor.getInt( + cursor.getColumnIndexOrThrow(Telephony.CellBroadcasts.MESSAGE_PRIORITY)); + + String plmn; + int plmnColumn = cursor.getColumnIndex(Telephony.CellBroadcasts.PLMN); + if (plmnColumn != -1 && !cursor.isNull(plmnColumn)) { + plmn = cursor.getString(plmnColumn); + } else { + plmn = null; + } + + int lac; + int lacColumn = cursor.getColumnIndex(Telephony.CellBroadcasts.LAC); + if (lacColumn != -1 && !cursor.isNull(lacColumn)) { + lac = cursor.getInt(lacColumn); + } else { + lac = -1; + } + + int cid; + int cidColumn = cursor.getColumnIndex(Telephony.CellBroadcasts.CID); + if (cidColumn != -1 && !cursor.isNull(cidColumn)) { + cid = cursor.getInt(cidColumn); + } else { + cid = -1; + } + + SmsCbLocation location = new SmsCbLocation(plmn, lac, cid); + + SmsCbEtwsInfo etwsInfo; + int etwsWarningTypeColumn = cursor.getColumnIndex( + Telephony.CellBroadcasts.ETWS_WARNING_TYPE); + if (etwsWarningTypeColumn != -1 && !cursor.isNull(etwsWarningTypeColumn)) { + int warningType = cursor.getInt(etwsWarningTypeColumn); + etwsInfo = new SmsCbEtwsInfo(warningType, false, false, false, null); + } else { + etwsInfo = null; + } + + SmsCbCmasInfo cmasInfo; + int cmasMessageClassColumn = cursor.getColumnIndex( + Telephony.CellBroadcasts.CMAS_MESSAGE_CLASS); + if (cmasMessageClassColumn != -1 && !cursor.isNull(cmasMessageClassColumn)) { + int messageClass = cursor.getInt(cmasMessageClassColumn); + + int cmasCategory; + int cmasCategoryColumn = cursor.getColumnIndex( + Telephony.CellBroadcasts.CMAS_CATEGORY); + if (cmasCategoryColumn != -1 && !cursor.isNull(cmasCategoryColumn)) { + cmasCategory = cursor.getInt(cmasCategoryColumn); + } else { + cmasCategory = SmsCbCmasInfo.CMAS_CATEGORY_UNKNOWN; + } + + int responseType; + int cmasResponseTypeColumn = cursor.getColumnIndex( + Telephony.CellBroadcasts.CMAS_RESPONSE_TYPE); + if (cmasResponseTypeColumn != -1 && !cursor.isNull(cmasResponseTypeColumn)) { + responseType = cursor.getInt(cmasResponseTypeColumn); + } else { + responseType = SmsCbCmasInfo.CMAS_RESPONSE_TYPE_UNKNOWN; + } + + int severity; + int cmasSeverityColumn = cursor.getColumnIndex( + Telephony.CellBroadcasts.CMAS_SEVERITY); + if (cmasSeverityColumn != -1 && !cursor.isNull(cmasSeverityColumn)) { + severity = cursor.getInt(cmasSeverityColumn); + } else { + severity = SmsCbCmasInfo.CMAS_SEVERITY_UNKNOWN; + } + + int urgency; + int cmasUrgencyColumn = cursor.getColumnIndex( + Telephony.CellBroadcasts.CMAS_URGENCY); + if (cmasUrgencyColumn != -1 && !cursor.isNull(cmasUrgencyColumn)) { + urgency = cursor.getInt(cmasUrgencyColumn); + } else { + urgency = SmsCbCmasInfo.CMAS_URGENCY_UNKNOWN; + } + + int certainty; + int cmasCertaintyColumn = cursor.getColumnIndex( + Telephony.CellBroadcasts.CMAS_CERTAINTY); + if (cmasCertaintyColumn != -1 && !cursor.isNull(cmasCertaintyColumn)) { + certainty = cursor.getInt(cmasCertaintyColumn); + } else { + certainty = SmsCbCmasInfo.CMAS_CERTAINTY_UNKNOWN; + } + + cmasInfo = new SmsCbCmasInfo(messageClass, cmasCategory, responseType, severity, + urgency, certainty); + } else { + cmasInfo = null; + } + + SmsCbMessage msg = new SmsCbMessage(format, geoScope, serialNum, location, category, + language, body, priority, etwsInfo, cmasInfo); + + long deliveryTime = cursor.getLong(cursor.getColumnIndexOrThrow( + Telephony.CellBroadcasts.DELIVERY_TIME)); + boolean isRead = (cursor.getInt(cursor.getColumnIndexOrThrow( + Telephony.CellBroadcasts.MESSAGE_READ)) != 0); + + return new CellBroadcastMessage(msg, deliveryTime, isRead); + } + + /** + * Return a ContentValues object for insertion into the database. + * @return a new ContentValues object containing this object's data + */ + public ContentValues getContentValues() { + ContentValues cv = new ContentValues(16); + SmsCbMessage msg = mSmsCbMessage; + cv.put(Telephony.CellBroadcasts.GEOGRAPHICAL_SCOPE, msg.getGeographicalScope()); + SmsCbLocation location = msg.getLocation(); + if (location.getPlmn() != null) { + cv.put(Telephony.CellBroadcasts.PLMN, location.getPlmn()); + } + if (location.getLac() != -1) { + cv.put(Telephony.CellBroadcasts.LAC, location.getLac()); + } + if (location.getCid() != -1) { + cv.put(Telephony.CellBroadcasts.CID, location.getCid()); + } + cv.put(Telephony.CellBroadcasts.SERIAL_NUMBER, msg.getSerialNumber()); + cv.put(Telephony.CellBroadcasts.SERVICE_CATEGORY, msg.getServiceCategory()); + cv.put(Telephony.CellBroadcasts.LANGUAGE_CODE, msg.getLanguageCode()); + cv.put(Telephony.CellBroadcasts.MESSAGE_BODY, msg.getMessageBody()); + cv.put(Telephony.CellBroadcasts.DELIVERY_TIME, mDeliveryTime); + cv.put(Telephony.CellBroadcasts.MESSAGE_READ, mIsRead); + cv.put(Telephony.CellBroadcasts.MESSAGE_FORMAT, msg.getMessageFormat()); + cv.put(Telephony.CellBroadcasts.MESSAGE_PRIORITY, msg.getMessagePriority()); + + SmsCbEtwsInfo etwsInfo = mSmsCbMessage.getEtwsWarningInfo(); + if (etwsInfo != null) { + cv.put(Telephony.CellBroadcasts.ETWS_WARNING_TYPE, etwsInfo.getWarningType()); + } + + SmsCbCmasInfo cmasInfo = mSmsCbMessage.getCmasWarningInfo(); + if (cmasInfo != null) { + cv.put(Telephony.CellBroadcasts.CMAS_MESSAGE_CLASS, cmasInfo.getMessageClass()); + cv.put(Telephony.CellBroadcasts.CMAS_CATEGORY, cmasInfo.getCategory()); + cv.put(Telephony.CellBroadcasts.CMAS_RESPONSE_TYPE, cmasInfo.getResponseType()); + cv.put(Telephony.CellBroadcasts.CMAS_SEVERITY, cmasInfo.getSeverity()); + cv.put(Telephony.CellBroadcasts.CMAS_URGENCY, cmasInfo.getUrgency()); + cv.put(Telephony.CellBroadcasts.CMAS_CERTAINTY, cmasInfo.getCertainty()); + } + + return cv; + } + + /** + * Set or clear the "read message" flag. + * @param isRead true if the message has been read; false if not + */ + public void setIsRead(boolean isRead) { + mIsRead = isRead; + } + + public String getLanguageCode() { + return mSmsCbMessage.getLanguageCode(); + } + + public int getServiceCategory() { + return mSmsCbMessage.getServiceCategory(); + } + + public long getDeliveryTime() { + return mDeliveryTime; + } + + public String getMessageBody() { + return mSmsCbMessage.getMessageBody(); + } + + public boolean isRead() { + return mIsRead; + } + + public int getSerialNumber() { + return mSmsCbMessage.getSerialNumber(); + } + + public SmsCbCmasInfo getCmasWarningInfo() { + return mSmsCbMessage.getCmasWarningInfo(); + } + + public SmsCbEtwsInfo getEtwsWarningInfo() { + return mSmsCbMessage.getEtwsWarningInfo(); + } + + /** + * Return whether the broadcast is an emergency (PWS) message type. + * This includes lower priority test messages and Amber alerts. + * + * All public alerts show the flashing warning icon in the dialog, + * but only emergency alerts play the alert sound and speak the message. + * + * @return true if the message is PWS type; false otherwise + */ + public boolean isPublicAlertMessage() { + return mSmsCbMessage.isEmergencyMessage(); + } + + /** + * Returns whether the broadcast is an emergency (PWS) message type, + * including test messages and AMBER alerts. + * + * @return true if the message is PWS type (ETWS or CMAS) + */ + public boolean isEmergencyAlertMessage() { + return mSmsCbMessage.isEmergencyMessage(); + } + + /** + * Return whether the broadcast is an ETWS emergency message type. + * @return true if the message is ETWS emergency type; false otherwise + */ + public boolean isEtwsMessage() { + return mSmsCbMessage.isEtwsMessage(); + } + + /** + * Return whether the broadcast is a CMAS emergency message type. + * @return true if the message is CMAS emergency type; false otherwise + */ + public boolean isCmasMessage() { + return mSmsCbMessage.isCmasMessage(); + } + + /** + * Return the CMAS message class. + * @return the CMAS message class, e.g. {@link SmsCbCmasInfo#CMAS_CLASS_SEVERE_THREAT}, or + * {@link SmsCbCmasInfo#CMAS_CLASS_UNKNOWN} if this is not a CMAS alert + */ + public int getCmasMessageClass() { + if (mSmsCbMessage.isCmasMessage()) { + return mSmsCbMessage.getCmasWarningInfo().getMessageClass(); + } else { + return SmsCbCmasInfo.CMAS_CLASS_UNKNOWN; + } + } + + /** + * Return whether the broadcast is an ETWS popup alert. + * This method checks the message ID and the message code. + * @return true if the message indicates an ETWS popup alert + */ + public boolean isEtwsPopupAlert() { + SmsCbEtwsInfo etwsInfo = mSmsCbMessage.getEtwsWarningInfo(); + return etwsInfo != null && etwsInfo.isPopupAlert(); + } + + /** + * Return whether the broadcast is an ETWS emergency user alert. + * This method checks the message ID and the message code. + * @return true if the message indicates an ETWS emergency user alert + */ + public boolean isEtwsEmergencyUserAlert() { + SmsCbEtwsInfo etwsInfo = mSmsCbMessage.getEtwsWarningInfo(); + return etwsInfo != null && etwsInfo.isEmergencyUserAlert(); + } + + /** + * Return whether the broadcast is an ETWS test message. + * @return true if the message is an ETWS test message; false otherwise + */ + public boolean isEtwsTestMessage() { + SmsCbEtwsInfo etwsInfo = mSmsCbMessage.getEtwsWarningInfo(); + return etwsInfo != null && + etwsInfo.getWarningType() == SmsCbEtwsInfo.ETWS_WARNING_TYPE_TEST_MESSAGE; + } + + /** + * Return the abbreviated date string for the message delivery time. + * @param context the context object + * @return a String to use in the broadcast list UI + */ + public String getDateString(Context context) { + int flags = DateUtils.FORMAT_NO_NOON_MIDNIGHT | DateUtils.FORMAT_SHOW_TIME | + DateUtils.FORMAT_ABBREV_ALL | DateUtils.FORMAT_SHOW_DATE | + DateUtils.FORMAT_CAP_AMPM; + return DateUtils.formatDateTime(context, mDeliveryTime, flags); + } + + /** + * Return the date string for the message delivery time, suitable for text-to-speech. + * @param context the context object + * @return a String for populating the list item AccessibilityEvent for TTS + */ + public String getSpokenDateString(Context context) { + int flags = DateUtils.FORMAT_SHOW_TIME | DateUtils.FORMAT_SHOW_DATE; + return DateUtils.formatDateTime(context, mDeliveryTime, flags); + } +} diff --git a/android/telephony/CellIdentityCdma.java b/android/telephony/CellIdentityCdma.java new file mode 100644 index 00000000..b39b4c76 --- /dev/null +++ b/android/telephony/CellIdentityCdma.java @@ -0,0 +1,224 @@ +/* + * Copyright (C) 2012 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.telephony; + +import android.os.Parcel; +import android.os.Parcelable; +import android.telephony.Rlog; + +import java.util.Objects; + +/** + * CellIdentity is to represent a unique CDMA cell + */ +public final class CellIdentityCdma implements Parcelable { + + private static final String LOG_TAG = "CellSignalStrengthCdma"; + private static final boolean DBG = false; + + // Network Id 0..65535 + private final int mNetworkId; + // CDMA System Id 0..32767 + private final int mSystemId; + // Base Station Id 0..65535 + private final int mBasestationId; + /** + * Longitude is a decimal number as specified in 3GPP2 C.S0005-A v6.0. + * It is represented in units of 0.25 seconds and ranges from -2592000 + * to 2592000, both values inclusive (corresponding to a range of -180 + * to +180 degrees). + */ + private final int mLongitude; + /** + * Latitude is a decimal number as specified in 3GPP2 C.S0005-A v6.0. + * It is represented in units of 0.25 seconds and ranges from -1296000 + * to 1296000, both values inclusive (corresponding to a range of -90 + * to +90 degrees). + */ + private final int mLatitude; + + /** + * @hide + */ + public CellIdentityCdma() { + mNetworkId = Integer.MAX_VALUE; + mSystemId = Integer.MAX_VALUE; + mBasestationId = Integer.MAX_VALUE; + mLongitude = Integer.MAX_VALUE; + mLatitude = Integer.MAX_VALUE; + } + + /** + * public constructor + * @param nid Network Id 0..65535 + * @param sid CDMA System Id 0..32767 + * @param bid Base Station Id 0..65535 + * @param lon Longitude is a decimal number ranges from -2592000 + * to 2592000 + * @param lat Latitude is a decimal number ranges from -1296000 + * to 1296000 + * + * @hide + */ + public CellIdentityCdma (int nid, int sid, int bid, int lon, int lat) { + mNetworkId = nid; + mSystemId = sid; + mBasestationId = bid; + mLongitude = lon; + mLatitude = lat; + } + + private CellIdentityCdma(CellIdentityCdma cid) { + mNetworkId = cid.mNetworkId; + mSystemId = cid.mSystemId; + mBasestationId = cid.mBasestationId; + mLongitude = cid.mLongitude; + mLatitude = cid.mLatitude; + } + + CellIdentityCdma copy() { + return new CellIdentityCdma(this); + } + + /** + * @return Network Id 0..65535, Integer.MAX_VALUE if unknown + */ + public int getNetworkId() { + return mNetworkId; + } + + /** + * @return System Id 0..32767, Integer.MAX_VALUE if unknown + */ + public int getSystemId() { + return mSystemId; + } + + /** + * @return Base Station Id 0..65535, Integer.MAX_VALUE if unknown + */ + public int getBasestationId() { + return mBasestationId; + } + + /** + * @return Base station longitude, which is a decimal number as + * specified in 3GPP2 C.S0005-A v6.0. It is represented in units + * of 0.25 seconds and ranges from -2592000 to 2592000, both + * values inclusive (corresponding to a range of -180 + * to +180 degrees). Integer.MAX_VALUE if unknown. + */ + public int getLongitude() { + return mLongitude; + } + + /** + * @return Base station latitude, which is a decimal number as + * specified in 3GPP2 C.S0005-A v6.0. It is represented in units + * of 0.25 seconds and ranges from -1296000 to 1296000, both + * values inclusive (corresponding to a range of -90 + * to +90 degrees). Integer.MAX_VALUE if unknown. + */ + public int getLatitude() { + return mLatitude; + } + + @Override + public int hashCode() { + return Objects.hash(mNetworkId, mSystemId, mBasestationId, mLatitude, mLongitude); + } + + @Override + public boolean equals(Object other) { + if (this == other) { + return true; + } + + if (!(other instanceof CellIdentityCdma)) { + return false; + } + + CellIdentityCdma o = (CellIdentityCdma) other; + return mNetworkId == o.mNetworkId && + mSystemId == o.mSystemId && + mBasestationId == o.mBasestationId && + mLatitude == o.mLatitude && + mLongitude == o.mLongitude; + } + + @Override + public String toString() { + StringBuilder sb = new StringBuilder("CellIdentityCdma:{"); + sb.append(" mNetworkId="); sb.append(mNetworkId); + sb.append(" mSystemId="); sb.append(mSystemId); + sb.append(" mBasestationId="); sb.append(mBasestationId); + sb.append(" mLongitude="); sb.append(mLongitude); + sb.append(" mLatitude="); sb.append(mLatitude); + sb.append("}"); + + return sb.toString(); + } + + /** Implement the Parcelable interface */ + @Override + public int describeContents() { + return 0; + } + + /** Implement the Parcelable interface */ + @Override + public void writeToParcel(Parcel dest, int flags) { + if (DBG) log("writeToParcel(Parcel, int): " + toString()); + dest.writeInt(mNetworkId); + dest.writeInt(mSystemId); + dest.writeInt(mBasestationId); + dest.writeInt(mLongitude); + dest.writeInt(mLatitude); + } + + /** Construct from Parcel, type has already been processed */ + private CellIdentityCdma(Parcel in) { + mNetworkId = in.readInt(); + mSystemId = in.readInt(); + mBasestationId = in.readInt(); + mLongitude = in.readInt(); + mLatitude = in.readInt(); + if (DBG) log("CellIdentityCdma(Parcel): " + toString()); + } + + /** Implement the Parcelable interface */ + @SuppressWarnings("hiding") + public static final Creator<CellIdentityCdma> CREATOR = + new Creator<CellIdentityCdma>() { + @Override + public CellIdentityCdma createFromParcel(Parcel in) { + return new CellIdentityCdma(in); + } + + @Override + public CellIdentityCdma[] newArray(int size) { + return new CellIdentityCdma[size]; + } + }; + + /** + * log + */ + private static void log(String s) { + Rlog.w(LOG_TAG, s); + } +} diff --git a/android/telephony/CellIdentityGsm.java b/android/telephony/CellIdentityGsm.java new file mode 100644 index 00000000..ec008e28 --- /dev/null +++ b/android/telephony/CellIdentityGsm.java @@ -0,0 +1,249 @@ +/* + * Copyright (C) 2012 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.telephony; + +import android.os.Parcel; +import android.os.Parcelable; +import android.telephony.Rlog; + +import java.util.Objects; + +/** + * CellIdentity to represent a unique GSM cell + */ +public final class CellIdentityGsm implements Parcelable { + + private static final String LOG_TAG = "CellIdentityGsm"; + private static final boolean DBG = false; + + // 3-digit Mobile Country Code, 0..999 + private final int mMcc; + // 2 or 3-digit Mobile Network Code, 0..999 + private final int mMnc; + // 16-bit Location Area Code, 0..65535 + private final int mLac; + // 16-bit GSM Cell Identity described in TS 27.007, 0..65535 + private final int mCid; + // 16-bit GSM Absolute RF Channel Number + private final int mArfcn; + // 6-bit Base Station Identity Code + private final int mBsic; + + /** + * @hide + */ + public CellIdentityGsm() { + mMcc = Integer.MAX_VALUE; + mMnc = Integer.MAX_VALUE; + mLac = Integer.MAX_VALUE; + mCid = Integer.MAX_VALUE; + mArfcn = Integer.MAX_VALUE; + mBsic = Integer.MAX_VALUE; + } + /** + * public constructor + * @param mcc 3-digit Mobile Country Code, 0..999 + * @param mnc 2 or 3-digit Mobile Network Code, 0..999 + * @param lac 16-bit Location Area Code, 0..65535 + * @param cid 16-bit GSM Cell Identity or 28-bit UMTS Cell Identity + * + * @hide + */ + public CellIdentityGsm (int mcc, int mnc, int lac, int cid) { + this(mcc, mnc, lac, cid, Integer.MAX_VALUE, Integer.MAX_VALUE); + } + + /** + * public constructor + * @param mcc 3-digit Mobile Country Code, 0..999 + * @param mnc 2 or 3-digit Mobile Network Code, 0..999 + * @param lac 16-bit Location Area Code, 0..65535 + * @param cid 16-bit GSM Cell Identity or 28-bit UMTS Cell Identity + * @param arfcn 16-bit GSM Absolute RF Channel Number + * @param bsic 6-bit Base Station Identity Code + * + * @hide + */ + public CellIdentityGsm (int mcc, int mnc, int lac, int cid, int arfcn, int bsic) { + mMcc = mcc; + mMnc = mnc; + mLac = lac; + mCid = cid; + mArfcn = arfcn; + mBsic = bsic; + } + + private CellIdentityGsm(CellIdentityGsm cid) { + mMcc = cid.mMcc; + mMnc = cid.mMnc; + mLac = cid.mLac; + mCid = cid.mCid; + mArfcn = cid.mArfcn; + mBsic = cid.mBsic; + } + + CellIdentityGsm copy() { + return new CellIdentityGsm(this); + } + + /** + * @return 3-digit Mobile Country Code, 0..999, Integer.MAX_VALUE if unknown + */ + public int getMcc() { + return mMcc; + } + + /** + * @return 2 or 3-digit Mobile Network Code, 0..999, Integer.MAX_VALUE if unknown + */ + public int getMnc() { + return mMnc; + } + + /** + * @return 16-bit Location Area Code, 0..65535, Integer.MAX_VALUE if unknown + */ + public int getLac() { + return mLac; + } + + /** + * @return CID + * Either 16-bit GSM Cell Identity described + * in TS 27.007, 0..65535, Integer.MAX_VALUE if unknown + */ + public int getCid() { + return mCid; + } + + /** + * @return 16-bit GSM Absolute RF Channel Number, Integer.MAX_VALUE if unknown + */ + public int getArfcn() { + return mArfcn; + } + + /** + * @return 6-bit Base Station Identity Code, Integer.MAX_VALUE if unknown + */ + public int getBsic() { + return mBsic; + } + + + /** + * @return Integer.MAX_VALUE, undefined for GSM + */ + @Deprecated + public int getPsc() { + return Integer.MAX_VALUE; + } + + @Override + public int hashCode() { + return Objects.hash(mMcc, mMnc, mLac, mCid); + } + + @Override + public boolean equals(Object other) { + if (this == other) { + return true; + } + + if (!(other instanceof CellIdentityGsm)) { + return false; + } + + CellIdentityGsm o = (CellIdentityGsm) other; + return mMcc == o.mMcc && + mMnc == o.mMnc && + mLac == o.mLac && + mCid == o.mCid && + mArfcn == o.mArfcn && + mBsic == o.mBsic; + } + + @Override + public String toString() { + StringBuilder sb = new StringBuilder("CellIdentityGsm:{"); + sb.append(" mMcc=").append(mMcc); + sb.append(" mMnc=").append(mMnc); + sb.append(" mLac=").append(mLac); + sb.append(" mCid=").append(mCid); + sb.append(" mArfcn=").append(mArfcn); + sb.append(" mBsic=").append("0x").append(Integer.toHexString(mBsic)); + sb.append("}"); + + return sb.toString(); + } + + /** Implement the Parcelable interface */ + @Override + public int describeContents() { + return 0; + } + + /** Implement the Parcelable interface */ + @Override + public void writeToParcel(Parcel dest, int flags) { + if (DBG) log("writeToParcel(Parcel, int): " + toString()); + dest.writeInt(mMcc); + dest.writeInt(mMnc); + dest.writeInt(mLac); + dest.writeInt(mCid); + dest.writeInt(mArfcn); + dest.writeInt(mBsic); + } + + /** Construct from Parcel, type has already been processed */ + private CellIdentityGsm(Parcel in) { + mMcc = in.readInt(); + mMnc = in.readInt(); + mLac = in.readInt(); + mCid = in.readInt(); + mArfcn = in.readInt(); + int bsic = in.readInt(); + // In RIL BSIC is a UINT8, so 0xFF is the 'INVALID' designator + // for inbound parcels + if (bsic == 0xFF) bsic = Integer.MAX_VALUE; + mBsic = bsic; + + if (DBG) log("CellIdentityGsm(Parcel): " + toString()); + } + + /** Implement the Parcelable interface */ + @SuppressWarnings("hiding") + public static final Creator<CellIdentityGsm> CREATOR = + new Creator<CellIdentityGsm>() { + @Override + public CellIdentityGsm createFromParcel(Parcel in) { + return new CellIdentityGsm(in); + } + + @Override + public CellIdentityGsm[] newArray(int size) { + return new CellIdentityGsm[size]; + } + }; + + /** + * log + */ + private static void log(String s) { + Rlog.w(LOG_TAG, s); + } +} diff --git a/android/telephony/CellIdentityLte.java b/android/telephony/CellIdentityLte.java new file mode 100644 index 00000000..ce743835 --- /dev/null +++ b/android/telephony/CellIdentityLte.java @@ -0,0 +1,235 @@ +/* + * Copyright (C) 2012 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.telephony; + +import android.os.Parcel; +import android.os.Parcelable; +import android.telephony.Rlog; + +import java.util.Objects; + +/** + * CellIdentity is to represent a unique LTE cell + */ +public final class CellIdentityLte implements Parcelable { + + private static final String LOG_TAG = "CellIdentityLte"; + private static final boolean DBG = false; + + // 3-digit Mobile Country Code, 0..999 + private final int mMcc; + // 2 or 3-digit Mobile Network Code, 0..999 + private final int mMnc; + // 28-bit cell identity + private final int mCi; + // physical cell id 0..503 + private final int mPci; + // 16-bit tracking area code + private final int mTac; + // 18-bit Absolute RF Channel Number + private final int mEarfcn; + + /** + * @hide + */ + public CellIdentityLte() { + mMcc = Integer.MAX_VALUE; + mMnc = Integer.MAX_VALUE; + mCi = Integer.MAX_VALUE; + mPci = Integer.MAX_VALUE; + mTac = Integer.MAX_VALUE; + mEarfcn = Integer.MAX_VALUE; + } + + /** + * + * @param mcc 3-digit Mobile Country Code, 0..999 + * @param mnc 2 or 3-digit Mobile Network Code, 0..999 + * @param ci 28-bit Cell Identity + * @param pci Physical Cell Id 0..503 + * @param tac 16-bit Tracking Area Code + * + * @hide + */ + public CellIdentityLte (int mcc, int mnc, int ci, int pci, int tac) { + this(mcc, mnc, ci, pci, tac, Integer.MAX_VALUE); + } + + /** + * + * @param mcc 3-digit Mobile Country Code, 0..999 + * @param mnc 2 or 3-digit Mobile Network Code, 0..999 + * @param ci 28-bit Cell Identity + * @param pci Physical Cell Id 0..503 + * @param tac 16-bit Tracking Area Code + * @param earfcn 18-bit LTE Absolute RF Channel Number + * + * @hide + */ + public CellIdentityLte (int mcc, int mnc, int ci, int pci, int tac, int earfcn) { + mMcc = mcc; + mMnc = mnc; + mCi = ci; + mPci = pci; + mTac = tac; + mEarfcn = earfcn; + } + + private CellIdentityLte(CellIdentityLte cid) { + mMcc = cid.mMcc; + mMnc = cid.mMnc; + mCi = cid.mCi; + mPci = cid.mPci; + mTac = cid.mTac; + mEarfcn = cid.mEarfcn; + } + + CellIdentityLte copy() { + return new CellIdentityLte(this); + } + + /** + * @return 3-digit Mobile Country Code, 0..999, Integer.MAX_VALUE if unknown + */ + public int getMcc() { + return mMcc; + } + + /** + * @return 2 or 3-digit Mobile Network Code, 0..999, Integer.MAX_VALUE if unknown + */ + public int getMnc() { + return mMnc; + } + + /** + * @return 28-bit Cell Identity, Integer.MAX_VALUE if unknown + */ + public int getCi() { + return mCi; + } + + /** + * @return Physical Cell Id 0..503, Integer.MAX_VALUE if unknown + */ + public int getPci() { + return mPci; + } + + /** + * @return 16-bit Tracking Area Code, Integer.MAX_VALUE if unknown + */ + public int getTac() { + return mTac; + } + + /** + * @return 18-bit Absolute RF Channel Number, Integer.MAX_VALUE if unknown + */ + public int getEarfcn() { + return mEarfcn; + } + + @Override + public int hashCode() { + return Objects.hash(mMcc, mMnc, mCi, mPci, mTac); + } + + @Override + public boolean equals(Object other) { + if (this == other) { + return true; + } + + if (!(other instanceof CellIdentityLte)) { + return false; + } + + CellIdentityLte o = (CellIdentityLte) other; + return mMcc == o.mMcc && + mMnc == o.mMnc && + mCi == o.mCi && + mPci == o.mPci && + mTac == o.mTac && + mEarfcn == o.mEarfcn; + } + + @Override + public String toString() { + StringBuilder sb = new StringBuilder("CellIdentityLte:{"); + sb.append(" mMcc="); sb.append(mMcc); + sb.append(" mMnc="); sb.append(mMnc); + sb.append(" mCi="); sb.append(mCi); + sb.append(" mPci="); sb.append(mPci); + sb.append(" mTac="); sb.append(mTac); + sb.append(" mEarfcn="); sb.append(mEarfcn); + sb.append("}"); + + return sb.toString(); + } + + /** Implement the Parcelable interface */ + @Override + public int describeContents() { + return 0; + } + + /** Implement the Parcelable interface */ + @Override + public void writeToParcel(Parcel dest, int flags) { + if (DBG) log("writeToParcel(Parcel, int): " + toString()); + dest.writeInt(mMcc); + dest.writeInt(mMnc); + dest.writeInt(mCi); + dest.writeInt(mPci); + dest.writeInt(mTac); + dest.writeInt(mEarfcn); + } + + /** Construct from Parcel, type has already been processed */ + private CellIdentityLte(Parcel in) { + mMcc = in.readInt(); + mMnc = in.readInt(); + mCi = in.readInt(); + mPci = in.readInt(); + mTac = in.readInt(); + mEarfcn = in.readInt(); + if (DBG) log("CellIdentityLte(Parcel): " + toString()); + } + + /** Implement the Parcelable interface */ + @SuppressWarnings("hiding") + public static final Creator<CellIdentityLte> CREATOR = + new Creator<CellIdentityLte>() { + @Override + public CellIdentityLte createFromParcel(Parcel in) { + return new CellIdentityLte(in); + } + + @Override + public CellIdentityLte[] newArray(int size) { + return new CellIdentityLte[size]; + } + }; + + /** + * log + */ + private static void log(String s) { + Rlog.w(LOG_TAG, s); + } +} diff --git a/android/telephony/CellIdentityWcdma.java b/android/telephony/CellIdentityWcdma.java new file mode 100644 index 00000000..0d13efd2 --- /dev/null +++ b/android/telephony/CellIdentityWcdma.java @@ -0,0 +1,236 @@ +/* + * Copyright (C) 2013 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.telephony; + +import android.os.Parcel; +import android.os.Parcelable; +import android.telephony.Rlog; + +import java.util.Objects; + +/** + * CellIdentity to represent a unique UMTS cell + */ +public final class CellIdentityWcdma implements Parcelable { + + private static final String LOG_TAG = "CellIdentityWcdma"; + private static final boolean DBG = false; + + // 3-digit Mobile Country Code, 0..999 + private final int mMcc; + // 2 or 3-digit Mobile Network Code, 0..999 + private final int mMnc; + // 16-bit Location Area Code, 0..65535 + private final int mLac; + // 28-bit UMTS Cell Identity described in TS 25.331, 0..268435455 + private final int mCid; + // 9-bit UMTS Primary Scrambling Code described in TS 25.331, 0..511 + private final int mPsc; + // 16-bit UMTS Absolute RF Channel Number + private final int mUarfcn; + + /** + * @hide + */ + public CellIdentityWcdma() { + mMcc = Integer.MAX_VALUE; + mMnc = Integer.MAX_VALUE; + mLac = Integer.MAX_VALUE; + mCid = Integer.MAX_VALUE; + mPsc = Integer.MAX_VALUE; + mUarfcn = Integer.MAX_VALUE; + } + /** + * public constructor + * @param mcc 3-digit Mobile Country Code, 0..999 + * @param mnc 2 or 3-digit Mobile Network Code, 0..999 + * @param lac 16-bit Location Area Code, 0..65535 + * @param cid 28-bit UMTS Cell Identity + * @param psc 9-bit UMTS Primary Scrambling Code + * + * @hide + */ + public CellIdentityWcdma (int mcc, int mnc, int lac, int cid, int psc) { + this(mcc, mnc, lac, cid, psc, Integer.MAX_VALUE); + } + + /** + * public constructor + * @param mcc 3-digit Mobile Country Code, 0..999 + * @param mnc 2 or 3-digit Mobile Network Code, 0..999 + * @param lac 16-bit Location Area Code, 0..65535 + * @param cid 28-bit UMTS Cell Identity + * @param psc 9-bit UMTS Primary Scrambling Code + * @param uarfcn 16-bit UMTS Absolute RF Channel Number + * + * @hide + */ + public CellIdentityWcdma (int mcc, int mnc, int lac, int cid, int psc, int uarfcn) { + mMcc = mcc; + mMnc = mnc; + mLac = lac; + mCid = cid; + mPsc = psc; + mUarfcn = uarfcn; + } + + private CellIdentityWcdma(CellIdentityWcdma cid) { + mMcc = cid.mMcc; + mMnc = cid.mMnc; + mLac = cid.mLac; + mCid = cid.mCid; + mPsc = cid.mPsc; + mUarfcn = cid.mUarfcn; + } + + CellIdentityWcdma copy() { + return new CellIdentityWcdma(this); + } + + /** + * @return 3-digit Mobile Country Code, 0..999, Integer.MAX_VALUE if unknown + */ + public int getMcc() { + return mMcc; + } + + /** + * @return 2 or 3-digit Mobile Network Code, 0..999, Integer.MAX_VALUE if unknown + */ + public int getMnc() { + return mMnc; + } + + /** + * @return 16-bit Location Area Code, 0..65535, Integer.MAX_VALUE if unknown + */ + public int getLac() { + return mLac; + } + + /** + * @return CID + * 28-bit UMTS Cell Identity described in TS 25.331, 0..268435455, Integer.MAX_VALUE if unknown + */ + public int getCid() { + return mCid; + } + + /** + * @return 9-bit UMTS Primary Scrambling Code described in TS 25.331, 0..511, Integer.MAX_VALUE + * if unknown + */ + public int getPsc() { + return mPsc; + } + + @Override + public int hashCode() { + return Objects.hash(mMcc, mMnc, mLac, mCid, mPsc); + } + + /** + * @return 16-bit UMTS Absolute RF Channel Number, Integer.MAX_VALUE if unknown + */ + public int getUarfcn() { + return mUarfcn; + } + + @Override + public boolean equals(Object other) { + if (this == other) { + return true; + } + + if (!(other instanceof CellIdentityWcdma)) { + return false; + } + + CellIdentityWcdma o = (CellIdentityWcdma) other; + return mMcc == o.mMcc && + mMnc == o.mMnc && + mLac == o.mLac && + mCid == o.mCid && + mPsc == o.mPsc && + mUarfcn == o.mUarfcn; + } + + @Override + public String toString() { + StringBuilder sb = new StringBuilder("CellIdentityWcdma:{"); + sb.append(" mMcc=").append(mMcc); + sb.append(" mMnc=").append(mMnc); + sb.append(" mLac=").append(mLac); + sb.append(" mCid=").append(mCid); + sb.append(" mPsc=").append(mPsc); + sb.append(" mUarfcn=").append(mUarfcn); + sb.append("}"); + + return sb.toString(); + } + + /** Implement the Parcelable interface */ + @Override + public int describeContents() { + return 0; + } + + /** Implement the Parcelable interface */ + @Override + public void writeToParcel(Parcel dest, int flags) { + if (DBG) log("writeToParcel(Parcel, int): " + toString()); + dest.writeInt(mMcc); + dest.writeInt(mMnc); + dest.writeInt(mLac); + dest.writeInt(mCid); + dest.writeInt(mPsc); + dest.writeInt(mUarfcn); + } + + /** Construct from Parcel, type has already been processed */ + private CellIdentityWcdma(Parcel in) { + mMcc = in.readInt(); + mMnc = in.readInt(); + mLac = in.readInt(); + mCid = in.readInt(); + mPsc = in.readInt(); + mUarfcn = in.readInt(); + if (DBG) log("CellIdentityWcdma(Parcel): " + toString()); + } + + /** Implement the Parcelable interface */ + @SuppressWarnings("hiding") + public static final Creator<CellIdentityWcdma> CREATOR = + new Creator<CellIdentityWcdma>() { + @Override + public CellIdentityWcdma createFromParcel(Parcel in) { + return new CellIdentityWcdma(in); + } + + @Override + public CellIdentityWcdma[] newArray(int size) { + return new CellIdentityWcdma[size]; + } + }; + + /** + * log + */ + private static void log(String s) { + Rlog.w(LOG_TAG, s); + } +} diff --git a/android/telephony/CellInfo.java b/android/telephony/CellInfo.java new file mode 100644 index 00000000..b5e4eef8 --- /dev/null +++ b/android/telephony/CellInfo.java @@ -0,0 +1,216 @@ +/* + * Copyright (C) 2012 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.telephony; + +import android.os.Parcel; +import android.os.Parcelable; + +/** + * Immutable cell information from a point in time. + */ +public abstract class CellInfo implements Parcelable { + + // Type fields for parceling + /** @hide */ + protected static final int TYPE_GSM = 1; + /** @hide */ + protected static final int TYPE_CDMA = 2; + /** @hide */ + protected static final int TYPE_LTE = 3; + /** @hide */ + protected static final int TYPE_WCDMA = 4; + + // Type to distinguish where time stamp gets recorded. + + /** @hide */ + public static final int TIMESTAMP_TYPE_UNKNOWN = 0; + /** @hide */ + public static final int TIMESTAMP_TYPE_ANTENNA = 1; + /** @hide */ + public static final int TIMESTAMP_TYPE_MODEM = 2; + /** @hide */ + public static final int TIMESTAMP_TYPE_OEM_RIL = 3; + /** @hide */ + public static final int TIMESTAMP_TYPE_JAVA_RIL = 4; + + // True if device is mRegistered to the mobile network + private boolean mRegistered; + + // Observation time stamped as type in nanoseconds since boot + private long mTimeStamp; + + // Where time stamp gets recorded. + // Value of TIMESTAMP_TYPE_XXXX + private int mTimeStampType; + + /** @hide */ + protected CellInfo() { + this.mRegistered = false; + this.mTimeStampType = TIMESTAMP_TYPE_UNKNOWN; + this.mTimeStamp = Long.MAX_VALUE; + } + + /** @hide */ + protected CellInfo(CellInfo ci) { + this.mRegistered = ci.mRegistered; + this.mTimeStampType = ci.mTimeStampType; + this.mTimeStamp = ci.mTimeStamp; + } + + /** True if this cell is registered to the mobile network */ + public boolean isRegistered() { + return mRegistered; + } + /** @hide */ + public void setRegistered(boolean registered) { + mRegistered = registered; + } + + /** Approximate time of this cell information in nanos since boot */ + public long getTimeStamp() { + return mTimeStamp; + } + /** @hide */ + public void setTimeStamp(long timeStamp) { + mTimeStamp = timeStamp; + } + + /** + * Where time stamp gets recorded. + * @return one of TIMESTAMP_TYPE_XXXX + * + * @hide + */ + public int getTimeStampType() { + return mTimeStampType; + } + /** @hide */ + public void setTimeStampType(int timeStampType) { + if (timeStampType < TIMESTAMP_TYPE_UNKNOWN || timeStampType > TIMESTAMP_TYPE_JAVA_RIL) { + mTimeStampType = TIMESTAMP_TYPE_UNKNOWN; + } else { + mTimeStampType = timeStampType; + } + } + + @Override + public int hashCode() { + int primeNum = 31; + return ((mRegistered ? 0 : 1) * primeNum) + ((int)(mTimeStamp / 1000) * primeNum) + + (mTimeStampType * primeNum); + } + + @Override + public boolean equals(Object other) { + if (other == null) { + return false; + } + if (this == other) { + return true; + } + try { + CellInfo o = (CellInfo) other; + return mRegistered == o.mRegistered + && mTimeStamp == o.mTimeStamp && mTimeStampType == o.mTimeStampType; + } catch (ClassCastException e) { + return false; + } + } + + private static String timeStampTypeToString(int type) { + switch (type) { + case 1: + return "antenna"; + case 2: + return "modem"; + case 3: + return "oem_ril"; + case 4: + return "java_ril"; + default: + return "unknown"; + } + } + + @Override + public String toString() { + StringBuffer sb = new StringBuffer(); + String timeStampType; + + sb.append("mRegistered=").append(mRegistered ? "YES" : "NO"); + timeStampType = timeStampTypeToString(mTimeStampType); + sb.append(" mTimeStampType=").append(timeStampType); + sb.append(" mTimeStamp=").append(mTimeStamp).append("ns"); + + return sb.toString(); + } + + /** + * Implement the Parcelable interface + */ + @Override + public int describeContents() { + return 0; + } + + /** Implement the Parcelable interface */ + @Override + public abstract void writeToParcel(Parcel dest, int flags); + + /** + * Used by child classes for parceling. + * + * @hide + */ + protected void writeToParcel(Parcel dest, int flags, int type) { + dest.writeInt(type); + dest.writeInt(mRegistered ? 1 : 0); + dest.writeInt(mTimeStampType); + dest.writeLong(mTimeStamp); + } + + /** + * Used by child classes for parceling + * + * @hide + */ + protected CellInfo(Parcel in) { + mRegistered = (in.readInt() == 1) ? true : false; + mTimeStampType = in.readInt(); + mTimeStamp = in.readLong(); + } + + /** Implement the Parcelable interface */ + public static final Creator<CellInfo> CREATOR = new Creator<CellInfo>() { + @Override + public CellInfo createFromParcel(Parcel in) { + int type = in.readInt(); + switch (type) { + case TYPE_GSM: return CellInfoGsm.createFromParcelBody(in); + case TYPE_CDMA: return CellInfoCdma.createFromParcelBody(in); + case TYPE_LTE: return CellInfoLte.createFromParcelBody(in); + case TYPE_WCDMA: return CellInfoWcdma.createFromParcelBody(in); + default: throw new RuntimeException("Bad CellInfo Parcel"); + } + } + + @Override + public CellInfo[] newArray(int size) { + return new CellInfo[size]; + } + }; +} diff --git a/android/telephony/CellInfoCdma.java b/android/telephony/CellInfoCdma.java new file mode 100644 index 00000000..6f2f1f67 --- /dev/null +++ b/android/telephony/CellInfoCdma.java @@ -0,0 +1,149 @@ +/* + * Copyright (C) 2012 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.telephony; + +import android.os.Parcel; +import android.os.Parcelable; +import android.telephony.Rlog; + +/** + * Immutable cell information from a point in time. + */ +public final class CellInfoCdma extends CellInfo implements Parcelable { + + private static final String LOG_TAG = "CellInfoCdma"; + private static final boolean DBG = false; + + private CellIdentityCdma mCellIdentityCdma; + private CellSignalStrengthCdma mCellSignalStrengthCdma; + + /** @hide */ + public CellInfoCdma() { + super(); + mCellIdentityCdma = new CellIdentityCdma(); + mCellSignalStrengthCdma = new CellSignalStrengthCdma(); + } + + /** @hide */ + public CellInfoCdma(CellInfoCdma ci) { + super(ci); + this.mCellIdentityCdma = ci.mCellIdentityCdma.copy(); + this.mCellSignalStrengthCdma = ci.mCellSignalStrengthCdma.copy(); + } + + public CellIdentityCdma getCellIdentity() { + return mCellIdentityCdma; + } + /** @hide */ + public void setCellIdentity(CellIdentityCdma cid) { + mCellIdentityCdma = cid; + } + + public CellSignalStrengthCdma getCellSignalStrength() { + return mCellSignalStrengthCdma; + } + /** @hide */ + public void setCellSignalStrength(CellSignalStrengthCdma css) { + mCellSignalStrengthCdma = css; + } + + /** + * @return hash code + */ + @Override + public int hashCode() { + return super.hashCode() + mCellIdentityCdma.hashCode() + mCellSignalStrengthCdma.hashCode(); + } + + @Override + public boolean equals(Object other) { + if (!super.equals(other)) { + return false; + } + try { + CellInfoCdma o = (CellInfoCdma) other; + return mCellIdentityCdma.equals(o.mCellIdentityCdma) + && mCellSignalStrengthCdma.equals(o.mCellSignalStrengthCdma); + } catch (ClassCastException e) { + return false; + } + } + + @Override + public String toString() { + StringBuffer sb = new StringBuffer(); + + sb.append("CellInfoCdma:{"); + sb.append(super.toString()); + sb.append(" ").append(mCellIdentityCdma); + sb.append(" ").append(mCellSignalStrengthCdma); + sb.append("}"); + + return sb.toString(); + } + + /** Implement the Parcelable interface */ + @Override + public int describeContents() { + return 0; + } + + /** Implement the Parcelable interface */ + @Override + public void writeToParcel(Parcel dest, int flags) { + super.writeToParcel(dest, flags, TYPE_CDMA); + mCellIdentityCdma.writeToParcel(dest, flags); + mCellSignalStrengthCdma.writeToParcel(dest, flags); + } + + /** + * Construct a CellInfoCdma object from the given parcel + * where the token is already been processed. + */ + private CellInfoCdma(Parcel in) { + super(in); + mCellIdentityCdma = CellIdentityCdma.CREATOR.createFromParcel(in); + mCellSignalStrengthCdma = CellSignalStrengthCdma.CREATOR.createFromParcel(in); + if (DBG) log("CellInfoCdma(Parcel): " + toString()); + } + + /** Implement the Parcelable interface */ + public static final Creator<CellInfoCdma> CREATOR = new Creator<CellInfoCdma>() { + @Override + public CellInfoCdma createFromParcel(Parcel in) { + in.readInt(); // Skip past token, we know what it is + return createFromParcelBody(in); + } + + @Override + public CellInfoCdma[] newArray(int size) { + return new CellInfoCdma[size]; + } + }; + + /** @hide */ + protected static CellInfoCdma createFromParcelBody(Parcel in) { + return new CellInfoCdma(in); + } + + /** + * log + */ + private static void log(String s) { + Rlog.w(LOG_TAG, s); + } +} diff --git a/android/telephony/CellInfoGsm.java b/android/telephony/CellInfoGsm.java new file mode 100644 index 00000000..1bedddb6 --- /dev/null +++ b/android/telephony/CellInfoGsm.java @@ -0,0 +1,148 @@ +/* + * Copyright (C) 2012 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.telephony; + +import android.os.Parcel; +import android.os.Parcelable; +import android.telephony.Rlog; + +/** + * Immutable cell information from a point in time. + */ +public final class CellInfoGsm extends CellInfo implements Parcelable { + + private static final String LOG_TAG = "CellInfoGsm"; + private static final boolean DBG = false; + + private CellIdentityGsm mCellIdentityGsm; + private CellSignalStrengthGsm mCellSignalStrengthGsm; + + /** @hide */ + public CellInfoGsm() { + super(); + mCellIdentityGsm = new CellIdentityGsm(); + mCellSignalStrengthGsm = new CellSignalStrengthGsm(); + } + + /** @hide */ + public CellInfoGsm(CellInfoGsm ci) { + super(ci); + this.mCellIdentityGsm = ci.mCellIdentityGsm.copy(); + this.mCellSignalStrengthGsm = ci.mCellSignalStrengthGsm.copy(); + } + + public CellIdentityGsm getCellIdentity() { + return mCellIdentityGsm; + } + /** @hide */ + public void setCellIdentity(CellIdentityGsm cid) { + mCellIdentityGsm = cid; + } + + public CellSignalStrengthGsm getCellSignalStrength() { + return mCellSignalStrengthGsm; + } + /** @hide */ + public void setCellSignalStrength(CellSignalStrengthGsm css) { + mCellSignalStrengthGsm = css; + } + + /** + * @return hash code + */ + @Override + public int hashCode() { + return super.hashCode() + mCellIdentityGsm.hashCode() + mCellSignalStrengthGsm.hashCode(); + } + + @Override + public boolean equals(Object other) { + if (!super.equals(other)) { + return false; + } + try { + CellInfoGsm o = (CellInfoGsm) other; + return mCellIdentityGsm.equals(o.mCellIdentityGsm) + && mCellSignalStrengthGsm.equals(o.mCellSignalStrengthGsm); + } catch (ClassCastException e) { + return false; + } + } + + @Override + public String toString() { + StringBuffer sb = new StringBuffer(); + + sb.append("CellInfoGsm:{"); + sb.append(super.toString()); + sb.append(" ").append(mCellIdentityGsm); + sb.append(" ").append(mCellSignalStrengthGsm); + sb.append("}"); + + return sb.toString(); + } + + /** Implement the Parcelable interface */ + @Override + public int describeContents() { + return 0; + } + + /** Implement the Parcelable interface */ + @Override + public void writeToParcel(Parcel dest, int flags) { + super.writeToParcel(dest, flags, TYPE_GSM); + mCellIdentityGsm.writeToParcel(dest, flags); + mCellSignalStrengthGsm.writeToParcel(dest, flags); + } + + /** + * Construct a CellInfoGsm object from the given parcel + * where the token is already been processed. + */ + private CellInfoGsm(Parcel in) { + super(in); + mCellIdentityGsm = CellIdentityGsm.CREATOR.createFromParcel(in); + mCellSignalStrengthGsm = CellSignalStrengthGsm.CREATOR.createFromParcel(in); + } + + /** Implement the Parcelable interface */ + public static final Creator<CellInfoGsm> CREATOR = new Creator<CellInfoGsm>() { + @Override + public CellInfoGsm createFromParcel(Parcel in) { + in.readInt(); // Skip past token, we know what it is + return createFromParcelBody(in); + } + + @Override + public CellInfoGsm[] newArray(int size) { + return new CellInfoGsm[size]; + } + }; + + /** @hide */ + protected static CellInfoGsm createFromParcelBody(Parcel in) { + return new CellInfoGsm(in); + } + + /** + * log + */ + private static void log(String s) { + Rlog.w(LOG_TAG, s); + } +} diff --git a/android/telephony/CellInfoLte.java b/android/telephony/CellInfoLte.java new file mode 100644 index 00000000..287c9f04 --- /dev/null +++ b/android/telephony/CellInfoLte.java @@ -0,0 +1,154 @@ +/* + * Copyright (C) 2012 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.telephony; + +import android.os.Parcel; +import android.os.Parcelable; +import android.telephony.Rlog; + +/** + * Immutable cell information from a point in time. + */ +public final class CellInfoLte extends CellInfo implements Parcelable { + + private static final String LOG_TAG = "CellInfoLte"; + private static final boolean DBG = false; + + private CellIdentityLte mCellIdentityLte; + private CellSignalStrengthLte mCellSignalStrengthLte; + + /** @hide */ + public CellInfoLte() { + super(); + mCellIdentityLte = new CellIdentityLte(); + mCellSignalStrengthLte = new CellSignalStrengthLte(); + } + + /** @hide */ + public CellInfoLte(CellInfoLte ci) { + super(ci); + this.mCellIdentityLte = ci.mCellIdentityLte.copy(); + this.mCellSignalStrengthLte = ci.mCellSignalStrengthLte.copy(); + } + + public CellIdentityLte getCellIdentity() { + if (DBG) log("getCellIdentity: " + mCellIdentityLte); + return mCellIdentityLte; + } + /** @hide */ + public void setCellIdentity(CellIdentityLte cid) { + if (DBG) log("setCellIdentity: " + cid); + mCellIdentityLte = cid; + } + + public CellSignalStrengthLte getCellSignalStrength() { + if (DBG) log("getCellSignalStrength: " + mCellSignalStrengthLte); + return mCellSignalStrengthLte; + } + /** @hide */ + public void setCellSignalStrength(CellSignalStrengthLte css) { + if (DBG) log("setCellSignalStrength: " + css); + mCellSignalStrengthLte = css; + } + + /** + * @return hash code + */ + @Override + public int hashCode() { + return super.hashCode() + mCellIdentityLte.hashCode() + mCellSignalStrengthLte.hashCode(); + } + + @Override + public boolean equals(Object other) { + if (!super.equals(other)) { + return false; + } + try { + CellInfoLte o = (CellInfoLte) other; + return mCellIdentityLte.equals(o.mCellIdentityLte) + && mCellSignalStrengthLte.equals(o.mCellSignalStrengthLte); + } catch (ClassCastException e) { + return false; + } + } + + @Override + public String toString() { + StringBuffer sb = new StringBuffer(); + + sb.append("CellInfoLte:{"); + sb.append(super.toString()); + sb.append(" ").append(mCellIdentityLte); + sb.append(" ").append(mCellSignalStrengthLte); + sb.append("}"); + + return sb.toString(); + } + + /** Implement the Parcelable interface */ + @Override + public int describeContents() { + return 0; + } + + /** Implement the Parcelable interface */ + @Override + public void writeToParcel(Parcel dest, int flags) { + if (DBG) log("writeToParcel(Parcel, int): " + toString()); + super.writeToParcel(dest, flags, TYPE_LTE); + mCellIdentityLte.writeToParcel(dest, flags); + mCellSignalStrengthLte.writeToParcel(dest, flags); + } + + /** + * Construct a CellInfoLte object from the given parcel + * where the TYPE_LTE token is already been processed. + */ + private CellInfoLte(Parcel in) { + super(in); + mCellIdentityLte = CellIdentityLte.CREATOR.createFromParcel(in); + mCellSignalStrengthLte = CellSignalStrengthLte.CREATOR.createFromParcel(in); + if (DBG) log("CellInfoLte(Parcel): " + toString()); + } + + /** Implement the Parcelable interface */ + public static final Creator<CellInfoLte> CREATOR = new Creator<CellInfoLte>() { + @Override + public CellInfoLte createFromParcel(Parcel in) { + in.readInt(); // Skip past token, we know what it is + return createFromParcelBody(in); + } + + @Override + public CellInfoLte[] newArray(int size) { + return new CellInfoLte[size]; + } + }; + + /** @hide */ + protected static CellInfoLte createFromParcelBody(Parcel in) { + return new CellInfoLte(in); + } + + /** + * log + */ + private static void log(String s) { + Rlog.w(LOG_TAG, s); + } +} diff --git a/android/telephony/CellInfoWcdma.java b/android/telephony/CellInfoWcdma.java new file mode 100644 index 00000000..06157022 --- /dev/null +++ b/android/telephony/CellInfoWcdma.java @@ -0,0 +1,148 @@ +/* + * Copyright (C) 2012 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.telephony; + +import android.os.Parcel; +import android.os.Parcelable; +import android.telephony.Rlog; + +/** + * Immutable cell information from a point in time. + */ +public final class CellInfoWcdma extends CellInfo implements Parcelable { + + private static final String LOG_TAG = "CellInfoWcdma"; + private static final boolean DBG = false; + + private CellIdentityWcdma mCellIdentityWcdma; + private CellSignalStrengthWcdma mCellSignalStrengthWcdma; + + /** @hide */ + public CellInfoWcdma() { + super(); + mCellIdentityWcdma = new CellIdentityWcdma(); + mCellSignalStrengthWcdma = new CellSignalStrengthWcdma(); + } + + /** @hide */ + public CellInfoWcdma(CellInfoWcdma ci) { + super(ci); + this.mCellIdentityWcdma = ci.mCellIdentityWcdma.copy(); + this.mCellSignalStrengthWcdma = ci.mCellSignalStrengthWcdma.copy(); + } + + public CellIdentityWcdma getCellIdentity() { + return mCellIdentityWcdma; + } + /** @hide */ + public void setCellIdentity(CellIdentityWcdma cid) { + mCellIdentityWcdma = cid; + } + + public CellSignalStrengthWcdma getCellSignalStrength() { + return mCellSignalStrengthWcdma; + } + /** @hide */ + public void setCellSignalStrength(CellSignalStrengthWcdma css) { + mCellSignalStrengthWcdma = css; + } + + /** + * @return hash code + */ + @Override + public int hashCode() { + return super.hashCode() + mCellIdentityWcdma.hashCode() + mCellSignalStrengthWcdma.hashCode(); + } + + @Override + public boolean equals(Object other) { + if (!super.equals(other)) { + return false; + } + try { + CellInfoWcdma o = (CellInfoWcdma) other; + return mCellIdentityWcdma.equals(o.mCellIdentityWcdma) + && mCellSignalStrengthWcdma.equals(o.mCellSignalStrengthWcdma); + } catch (ClassCastException e) { + return false; + } + } + + @Override + public String toString() { + StringBuffer sb = new StringBuffer(); + + sb.append("CellInfoWcdma:{"); + sb.append(super.toString()); + sb.append(" ").append(mCellIdentityWcdma); + sb.append(" ").append(mCellSignalStrengthWcdma); + sb.append("}"); + + return sb.toString(); + } + + /** Implement the Parcelable interface */ + @Override + public int describeContents() { + return 0; + } + + /** Implement the Parcelable interface */ + @Override + public void writeToParcel(Parcel dest, int flags) { + super.writeToParcel(dest, flags, TYPE_WCDMA); + mCellIdentityWcdma.writeToParcel(dest, flags); + mCellSignalStrengthWcdma.writeToParcel(dest, flags); + } + + /** + * Construct a CellInfoWcdma object from the given parcel + * where the token is already been processed. + */ + private CellInfoWcdma(Parcel in) { + super(in); + mCellIdentityWcdma = CellIdentityWcdma.CREATOR.createFromParcel(in); + mCellSignalStrengthWcdma = CellSignalStrengthWcdma.CREATOR.createFromParcel(in); + } + + /** Implement the Parcelable interface */ + public static final Creator<CellInfoWcdma> CREATOR = new Creator<CellInfoWcdma>() { + @Override + public CellInfoWcdma createFromParcel(Parcel in) { + in.readInt(); // Skip past token, we know what it is + return createFromParcelBody(in); + } + + @Override + public CellInfoWcdma[] newArray(int size) { + return new CellInfoWcdma[size]; + } + }; + + /** @hide */ + protected static CellInfoWcdma createFromParcelBody(Parcel in) { + return new CellInfoWcdma(in); + } + + /** + * log + */ + private static void log(String s) { + Rlog.w(LOG_TAG, s); + } +} diff --git a/android/telephony/CellLocation.java b/android/telephony/CellLocation.java new file mode 100644 index 00000000..5bcaa6e7 --- /dev/null +++ b/android/telephony/CellLocation.java @@ -0,0 +1,106 @@ +/* + * Copyright (C) 2006 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.telephony; + +import android.os.Bundle; +import android.os.RemoteException; +import android.os.ServiceManager; + +import android.telephony.cdma.CdmaCellLocation; +import android.telephony.gsm.GsmCellLocation; +import com.android.internal.telephony.ITelephony; +import com.android.internal.telephony.PhoneConstants; + +/** + * Abstract class that represents the location of the device. {@more} + */ +public abstract class CellLocation { + + /** + * Request an update of the current location. If the location has changed, + * a broadcast will be sent to everyone registered with {@link + * PhoneStateListener#LISTEN_CELL_LOCATION}. + */ + public static void requestLocationUpdate() { + try { + ITelephony phone = ITelephony.Stub.asInterface(ServiceManager.getService("phone")); + if (phone != null) { + phone.updateServiceLocation(); + } + } catch (RemoteException ex) { + // ignore it + } + } + + /** + * Create a new CellLocation from a intent notifier Bundle + * + * This method is used by PhoneStateIntentReceiver and maybe by + * external applications. + * + * @param bundle Bundle from intent notifier + * @return newly created CellLocation + * + * @hide + */ + public static CellLocation newFromBundle(Bundle bundle) { + // TelephonyManager.getDefault().getCurrentPhoneType() handles the case when + // ITelephony interface is not up yet. + switch(TelephonyManager.getDefault().getCurrentPhoneType()) { + case PhoneConstants.PHONE_TYPE_CDMA: + return new CdmaCellLocation(bundle); + case PhoneConstants.PHONE_TYPE_GSM: + return new GsmCellLocation(bundle); + default: + return null; + } + } + + /** + * @hide + */ + public abstract void fillInNotifierBundle(Bundle bundle); + + /** + * @hide + */ + public abstract boolean isEmpty(); + + /** + * Invalidate this object. The location area code and the cell id are set to -1. + * @hide + */ + public abstract void setStateInvalid(); + + /** + * Return a new CellLocation object representing an unknown + * location, or null for unknown/none phone radio types. + * + */ + public static CellLocation getEmpty() { + // TelephonyManager.getDefault().getCurrentPhoneType() handles the case when + // ITelephony interface is not up yet. + switch(TelephonyManager.getDefault().getCurrentPhoneType()) { + case PhoneConstants.PHONE_TYPE_CDMA: + return new CdmaCellLocation(); + case PhoneConstants.PHONE_TYPE_GSM: + return new GsmCellLocation(); + default: + return null; + } + } +} diff --git a/android/telephony/CellSignalStrength.java b/android/telephony/CellSignalStrength.java new file mode 100644 index 00000000..2a3ef219 --- /dev/null +++ b/android/telephony/CellSignalStrength.java @@ -0,0 +1,83 @@ +/* + * Copyright (C) 2012 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.telephony; + +/** + * Abstract base class for cell phone signal strength related information. + */ +public abstract class CellSignalStrength { + + public static final int SIGNAL_STRENGTH_NONE_OR_UNKNOWN = 0; + + public static final int SIGNAL_STRENGTH_POOR = 1; + + public static final int SIGNAL_STRENGTH_MODERATE = 2; + + public static final int SIGNAL_STRENGTH_GOOD = 3; + + public static final int SIGNAL_STRENGTH_GREAT = 4; + + /** @hide */ + public static final int NUM_SIGNAL_STRENGTH_BINS = 5; + + /** @hide */ + public static final String[] SIGNAL_STRENGTH_NAMES = { + "none", "poor", "moderate", "good", "great" + }; + + /** @hide */ + protected CellSignalStrength() { + } + + /** @hide */ + public abstract void setDefaultValues(); + + /** + * Get signal level as an int from 0..4 + * <p> + * @see SIGNAL_STRENGTH_NONE_OR_UNKNOWN + * @see SIGNAL_STRENGTH_POOR + * @see SIGNAL_STRENGTH_MODERATE + * @see SIGNAL_STRENGTH_GOOD + * @see SIGNAL_STRENGTH_GREAT + */ + public abstract int getLevel(); + + /** + * Get the signal level as an asu value between 0..31, 99 is unknown + */ + public abstract int getAsuLevel(); + + /** + * Get the signal strength as dBm + */ + public abstract int getDbm(); + + /** + * Copies the CellSignalStrength. + * + * @return A deep copy of this class. + * @hide + */ + public abstract CellSignalStrength copy(); + + @Override + public abstract int hashCode(); + + @Override + public abstract boolean equals (Object o); +} diff --git a/android/telephony/CellSignalStrengthCdma.java b/android/telephony/CellSignalStrengthCdma.java new file mode 100644 index 00000000..04743620 --- /dev/null +++ b/android/telephony/CellSignalStrengthCdma.java @@ -0,0 +1,396 @@ +/* + * Copyright (C) 2012 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.telephony; + +import android.os.Parcel; +import android.os.Parcelable; +import android.telephony.Rlog; + +/** + * Signal strength related information. + */ +public final class CellSignalStrengthCdma extends CellSignalStrength implements Parcelable { + + private static final String LOG_TAG = "CellSignalStrengthCdma"; + private static final boolean DBG = false; + + private int mCdmaDbm; // This value is the RSSI value + private int mCdmaEcio; // This value is the Ec/Io + private int mEvdoDbm; // This value is the EVDO RSSI value + private int mEvdoEcio; // This value is the EVDO Ec/Io + private int mEvdoSnr; // Valid values are 0-8. 8 is the highest signal to noise ratio + + /** + * Empty constructor + * + * @hide + */ + public CellSignalStrengthCdma() { + setDefaultValues(); + } + + /** + * Constructor + * + * @hide + */ + public CellSignalStrengthCdma(int cdmaDbm, int cdmaEcio, int evdoDbm, int evdoEcio, + int evdoSnr) { + initialize(cdmaDbm, cdmaEcio, evdoDbm, evdoEcio, evdoSnr); + } + + /** + * Copy constructors + * + * @param s Source SignalStrength + * + * @hide + */ + public CellSignalStrengthCdma(CellSignalStrengthCdma s) { + copyFrom(s); + } + + /** + * Initialize all the values + * + * @param cdmaDbm + * @param cdmaEcio + * @param evdoDbm + * @param evdoEcio + * @param evdoSnr + * + * @hide + */ + public void initialize(int cdmaDbm, int cdmaEcio, int evdoDbm, int evdoEcio, int evdoSnr) { + mCdmaDbm = cdmaDbm; + mCdmaEcio = cdmaEcio; + mEvdoDbm = evdoDbm; + mEvdoEcio = evdoEcio; + mEvdoSnr = evdoSnr; + } + + /** + * @hide + */ + protected void copyFrom(CellSignalStrengthCdma s) { + mCdmaDbm = s.mCdmaDbm; + mCdmaEcio = s.mCdmaEcio; + mEvdoDbm = s.mEvdoDbm; + mEvdoEcio = s.mEvdoEcio; + mEvdoSnr = s.mEvdoSnr; + } + + /** + * @hide + */ + @Override + public CellSignalStrengthCdma copy() { + return new CellSignalStrengthCdma(this); + } + + /** @hide */ + @Override + public void setDefaultValues() { + mCdmaDbm = Integer.MAX_VALUE; + mCdmaEcio = Integer.MAX_VALUE; + mEvdoDbm = Integer.MAX_VALUE; + mEvdoEcio = Integer.MAX_VALUE; + mEvdoSnr = Integer.MAX_VALUE; + } + + /** + * Get signal level as an int from 0..4 + */ + @Override + public int getLevel() { + int level; + + int cdmaLevel = getCdmaLevel(); + int evdoLevel = getEvdoLevel(); + if (evdoLevel == SIGNAL_STRENGTH_NONE_OR_UNKNOWN) { + /* We don't know evdo, use cdma */ + level = getCdmaLevel(); + } else if (cdmaLevel == SIGNAL_STRENGTH_NONE_OR_UNKNOWN) { + /* We don't know cdma, use evdo */ + level = getEvdoLevel(); + } else { + /* We know both, use the lowest level */ + level = cdmaLevel < evdoLevel ? cdmaLevel : evdoLevel; + } + if (DBG) log("getLevel=" + level); + return level; + } + + /** + * Get the signal level as an asu value between 0..97, 99 is unknown + */ + @Override + public int getAsuLevel() { + final int cdmaDbm = getCdmaDbm(); + final int cdmaEcio = getCdmaEcio(); + int cdmaAsuLevel; + int ecioAsuLevel; + + if (cdmaDbm == Integer.MAX_VALUE) cdmaAsuLevel = 99; + else if (cdmaDbm >= -75) cdmaAsuLevel = 16; + else if (cdmaDbm >= -82) cdmaAsuLevel = 8; + else if (cdmaDbm >= -90) cdmaAsuLevel = 4; + else if (cdmaDbm >= -95) cdmaAsuLevel = 2; + else if (cdmaDbm >= -100) cdmaAsuLevel = 1; + else cdmaAsuLevel = 99; + + // Ec/Io are in dB*10 + if (cdmaEcio == Integer.MAX_VALUE) ecioAsuLevel = 99; + else if (cdmaEcio >= -90) ecioAsuLevel = 16; + else if (cdmaEcio >= -100) ecioAsuLevel = 8; + else if (cdmaEcio >= -115) ecioAsuLevel = 4; + else if (cdmaEcio >= -130) ecioAsuLevel = 2; + else if (cdmaEcio >= -150) ecioAsuLevel = 1; + else ecioAsuLevel = 99; + + int level = (cdmaAsuLevel < ecioAsuLevel) ? cdmaAsuLevel : ecioAsuLevel; + if (DBG) log("getAsuLevel=" + level); + return level; + } + + /** + * Get cdma as level 0..4 + */ + public int getCdmaLevel() { + final int cdmaDbm = getCdmaDbm(); + final int cdmaEcio = getCdmaEcio(); + int levelDbm; + int levelEcio; + + if (cdmaDbm == Integer.MAX_VALUE) levelDbm = SIGNAL_STRENGTH_NONE_OR_UNKNOWN; + else if (cdmaDbm >= -75) levelDbm = SIGNAL_STRENGTH_GREAT; + else if (cdmaDbm >= -85) levelDbm = SIGNAL_STRENGTH_GOOD; + else if (cdmaDbm >= -95) levelDbm = SIGNAL_STRENGTH_MODERATE; + else if (cdmaDbm >= -100) levelDbm = SIGNAL_STRENGTH_POOR; + else levelDbm = SIGNAL_STRENGTH_NONE_OR_UNKNOWN; + + // Ec/Io are in dB*10 + if (cdmaEcio == Integer.MAX_VALUE) levelEcio = SIGNAL_STRENGTH_NONE_OR_UNKNOWN; + else if (cdmaEcio >= -90) levelEcio = SIGNAL_STRENGTH_GREAT; + else if (cdmaEcio >= -110) levelEcio = SIGNAL_STRENGTH_GOOD; + else if (cdmaEcio >= -130) levelEcio = SIGNAL_STRENGTH_MODERATE; + else if (cdmaEcio >= -150) levelEcio = SIGNAL_STRENGTH_POOR; + else levelEcio = SIGNAL_STRENGTH_NONE_OR_UNKNOWN; + + int level = (levelDbm < levelEcio) ? levelDbm : levelEcio; + if (DBG) log("getCdmaLevel=" + level); + return level; + } + + /** + * Get Evdo as level 0..4 + */ + public int getEvdoLevel() { + int evdoDbm = getEvdoDbm(); + int evdoSnr = getEvdoSnr(); + int levelEvdoDbm; + int levelEvdoSnr; + + if (evdoDbm == Integer.MAX_VALUE) levelEvdoDbm = SIGNAL_STRENGTH_NONE_OR_UNKNOWN; + else if (evdoDbm >= -65) levelEvdoDbm = SIGNAL_STRENGTH_GREAT; + else if (evdoDbm >= -75) levelEvdoDbm = SIGNAL_STRENGTH_GOOD; + else if (evdoDbm >= -90) levelEvdoDbm = SIGNAL_STRENGTH_MODERATE; + else if (evdoDbm >= -105) levelEvdoDbm = SIGNAL_STRENGTH_POOR; + else levelEvdoDbm = SIGNAL_STRENGTH_NONE_OR_UNKNOWN; + + if (evdoSnr == Integer.MAX_VALUE) levelEvdoSnr = SIGNAL_STRENGTH_NONE_OR_UNKNOWN; + else if (evdoSnr >= 7) levelEvdoSnr = SIGNAL_STRENGTH_GREAT; + else if (evdoSnr >= 5) levelEvdoSnr = SIGNAL_STRENGTH_GOOD; + else if (evdoSnr >= 3) levelEvdoSnr = SIGNAL_STRENGTH_MODERATE; + else if (evdoSnr >= 1) levelEvdoSnr = SIGNAL_STRENGTH_POOR; + else levelEvdoSnr = SIGNAL_STRENGTH_NONE_OR_UNKNOWN; + + int level = (levelEvdoDbm < levelEvdoSnr) ? levelEvdoDbm : levelEvdoSnr; + if (DBG) log("getEvdoLevel=" + level); + return level; + } + + /** + * Get the signal strength as dBm + */ + @Override + public int getDbm() { + int cdmaDbm = getCdmaDbm(); + int evdoDbm = getEvdoDbm(); + + // Use the lower value to be conservative + return (cdmaDbm < evdoDbm) ? cdmaDbm : evdoDbm; + } + + /** + * Get the CDMA RSSI value in dBm + */ + public int getCdmaDbm() { + return mCdmaDbm; + } + /** @hide */ + public void setCdmaDbm(int cdmaDbm) { + mCdmaDbm = cdmaDbm; + } + + /** + * Get the CDMA Ec/Io value in dB*10 + */ + public int getCdmaEcio() { + return mCdmaEcio; + } + /** @hide */ + public void setCdmaEcio(int cdmaEcio) { + mCdmaEcio = cdmaEcio; + } + + /** + * Get the EVDO RSSI value in dBm + */ + public int getEvdoDbm() { + return mEvdoDbm; + } + /** @hide */ + public void setEvdoDbm(int evdoDbm) { + mEvdoDbm = evdoDbm; + } + + /** + * Get the EVDO Ec/Io value in dB*10 + */ + public int getEvdoEcio() { + return mEvdoEcio; + } + /** @hide */ + public void setEvdoEcio(int evdoEcio) { + mEvdoEcio = evdoEcio; + } + + /** + * Get the signal to noise ratio. Valid values are 0-8. 8 is the highest. + */ + public int getEvdoSnr() { + return mEvdoSnr; + } + /** @hide */ + public void setEvdoSnr(int evdoSnr) { + mEvdoSnr = evdoSnr; + } + + @Override + public int hashCode() { + int primeNum = 31; + return ((mCdmaDbm * primeNum) + (mCdmaEcio * primeNum) + + (mEvdoDbm * primeNum) + (mEvdoEcio * primeNum) + (mEvdoSnr * primeNum)); + } + + @Override + public boolean equals (Object o) { + CellSignalStrengthCdma s; + + try { + s = (CellSignalStrengthCdma) o; + } catch (ClassCastException ex) { + return false; + } + + if (o == null) { + return false; + } + + return mCdmaDbm == s.mCdmaDbm + && mCdmaEcio == s.mCdmaEcio + && mEvdoDbm == s.mEvdoDbm + && mEvdoEcio == s.mEvdoEcio + && mEvdoSnr == s.mEvdoSnr; + } + + /** + * @return string representation. + */ + @Override + public String toString() { + return "CellSignalStrengthCdma:" + + " cdmaDbm=" + mCdmaDbm + + " cdmaEcio=" + mCdmaEcio + + " evdoDbm=" + mEvdoDbm + + " evdoEcio=" + mEvdoEcio + + " evdoSnr=" + mEvdoSnr; + } + + /** Implement the Parcelable interface */ + @Override + public void writeToParcel(Parcel dest, int flags) { + if (DBG) log("writeToParcel(Parcel, int): " + toString()); + // Need to multiply CdmaDbm, CdmaEcio, EvdoDbm and EvdoEcio by -1 + // to ensure consistency when reading values written here + // unless the value is invalid + dest.writeInt(mCdmaDbm * (mCdmaDbm != Integer.MAX_VALUE ? -1 : 1)); + dest.writeInt(mCdmaEcio * (mCdmaEcio != Integer.MAX_VALUE ? -1 : 1)); + dest.writeInt(mEvdoDbm * (mEvdoDbm != Integer.MAX_VALUE ? -1 : 1)); + dest.writeInt(mEvdoEcio * (mEvdoEcio != Integer.MAX_VALUE ? -1 : 1)); + dest.writeInt(mEvdoSnr); + } + + /** + * Construct a SignalStrength object from the given parcel + * where the TYPE_CDMA token is already been processed. + */ + private CellSignalStrengthCdma(Parcel in) { + // CdmaDbm, CdmaEcio, EvdoDbm and EvdoEcio are written into + // the parcel as positive values. + // Need to convert into negative values unless the value is invalid + mCdmaDbm = in.readInt(); + if (mCdmaDbm != Integer.MAX_VALUE) mCdmaDbm *= -1; + mCdmaEcio = in.readInt(); + if (mCdmaEcio != Integer.MAX_VALUE) mCdmaEcio *= -1; + mEvdoDbm = in.readInt(); + if (mEvdoDbm != Integer.MAX_VALUE) mEvdoDbm *= -1; + mEvdoEcio = in.readInt(); + if (mEvdoEcio != Integer.MAX_VALUE) mEvdoEcio *= -1; + mEvdoSnr = in.readInt(); + if (DBG) log("CellSignalStrengthCdma(Parcel): " + toString()); + } + + /** Implement the Parcelable interface */ + @Override + public int describeContents() { + return 0; + } + + /** Implement the Parcelable interface */ + @SuppressWarnings("hiding") + public static final Parcelable.Creator<CellSignalStrengthCdma> CREATOR = + new Parcelable.Creator<CellSignalStrengthCdma>() { + @Override + public CellSignalStrengthCdma createFromParcel(Parcel in) { + return new CellSignalStrengthCdma(in); + } + + @Override + public CellSignalStrengthCdma[] newArray(int size) { + return new CellSignalStrengthCdma[size]; + } + }; + + /** + * log + */ + private static void log(String s) { + Rlog.w(LOG_TAG, s); + } +} diff --git a/android/telephony/CellSignalStrengthGsm.java b/android/telephony/CellSignalStrengthGsm.java new file mode 100644 index 00000000..4137853e --- /dev/null +++ b/android/telephony/CellSignalStrengthGsm.java @@ -0,0 +1,268 @@ +/* + * Copyright (C) 2012 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.telephony; + +import android.os.Parcel; +import android.os.Parcelable; +import android.telephony.Rlog; + +/** + * GSM signal strength related information. + */ +public final class CellSignalStrengthGsm extends CellSignalStrength implements Parcelable { + + private static final String LOG_TAG = "CellSignalStrengthGsm"; + private static final boolean DBG = false; + + private static final int GSM_SIGNAL_STRENGTH_GREAT = 12; + private static final int GSM_SIGNAL_STRENGTH_GOOD = 8; + private static final int GSM_SIGNAL_STRENGTH_MODERATE = 5; + + private int mSignalStrength; // Valid values are (0-31, 99) as defined in TS 27.007 8.5 + private int mBitErrorRate; // bit error rate (0-7, 99) as defined in TS 27.007 8.5 + private int mTimingAdvance; + + /** + * Empty constructor + * + * @hide + */ + public CellSignalStrengthGsm() { + setDefaultValues(); + } + + /** + * Constructor + * + * @hide + */ + public CellSignalStrengthGsm(int ss, int ber) { + initialize(ss, ber); + } + + /** + * Copy constructors + * + * @param s Source SignalStrength + * + * @hide + */ + public CellSignalStrengthGsm(CellSignalStrengthGsm s) { + copyFrom(s); + } + + /** + * Initialize all the values + * + * @param ss SignalStrength as ASU value + * @param ber is Bit Error Rate + * + * @hide + */ + public void initialize(int ss, int ber) { + mSignalStrength = ss; + mBitErrorRate = ber; + mTimingAdvance = Integer.MAX_VALUE; + } + + /** + * Initialize all the values + * + * @param ss SignalStrength as ASU value + * @param ber is Bit Error Rate + * @param ta timing advance + * + * @hide + */ + public void initialize(int ss, int ber, int ta) { + mSignalStrength = ss; + mBitErrorRate = ber; + mTimingAdvance = ta; + } + + /** + * @hide + */ + protected void copyFrom(CellSignalStrengthGsm s) { + mSignalStrength = s.mSignalStrength; + mBitErrorRate = s.mBitErrorRate; + mTimingAdvance = s.mTimingAdvance; + } + + /** + * @hide + */ + @Override + public CellSignalStrengthGsm copy() { + return new CellSignalStrengthGsm(this); + } + + /** @hide */ + @Override + public void setDefaultValues() { + mSignalStrength = Integer.MAX_VALUE; + mBitErrorRate = Integer.MAX_VALUE; + mTimingAdvance = Integer.MAX_VALUE; + } + + /** + * Get signal level as an int from 0..4 + */ + @Override + public int getLevel() { + int level; + + // ASU ranges from 0 to 31 - TS 27.007 Sec 8.5 + // asu = 0 (-113dB or less) is very weak + // signal, its better to show 0 bars to the user in such cases. + // asu = 99 is a special case, where the signal strength is unknown. + int asu = mSignalStrength; + if (asu <= 2 || asu == 99) level = SIGNAL_STRENGTH_NONE_OR_UNKNOWN; + else if (asu >= GSM_SIGNAL_STRENGTH_GREAT) level = SIGNAL_STRENGTH_GREAT; + else if (asu >= GSM_SIGNAL_STRENGTH_GOOD) level = SIGNAL_STRENGTH_GOOD; + else if (asu >= GSM_SIGNAL_STRENGTH_MODERATE) level = SIGNAL_STRENGTH_MODERATE; + else level = SIGNAL_STRENGTH_POOR; + if (DBG) log("getLevel=" + level); + return level; + } + + /** + * Get the GSM timing advance between 0..219 symbols (normally 0..63). + * Integer.MAX_VALUE is reported when there is no RR connection. + * Refer to 3GPP 45.010 Sec 5.8 + * @return the current GSM timing advance, if available. + */ + public int getTimingAdvance() { + return mTimingAdvance; + } + + /** + * Get the signal strength as dBm + */ + @Override + public int getDbm() { + int dBm; + + int level = mSignalStrength; + int asu = (level == 99 ? Integer.MAX_VALUE : level); + if (asu != Integer.MAX_VALUE) { + dBm = -113 + (2 * asu); + } else { + dBm = Integer.MAX_VALUE; + } + if (DBG) log("getDbm=" + dBm); + return dBm; + } + + /** + * Get the signal level as an asu value between 0..31, 99 is unknown + * Asu is calculated based on 3GPP RSRP. Refer to 3GPP 27.007 (Ver 10.3.0) Sec 8.69 + */ + @Override + public int getAsuLevel() { + // ASU ranges from 0 to 31 - TS 27.007 Sec 8.5 + // asu = 0 (-113dB or less) is very weak + // signal, its better to show 0 bars to the user in such cases. + // asu = 99 is a special case, where the signal strength is unknown. + int level = mSignalStrength; + if (DBG) log("getAsuLevel=" + level); + return level; + } + + @Override + public int hashCode() { + int primeNum = 31; + return (mSignalStrength * primeNum) + (mBitErrorRate * primeNum); + } + + @Override + public boolean equals (Object o) { + CellSignalStrengthGsm s; + + try { + s = (CellSignalStrengthGsm) o; + } catch (ClassCastException ex) { + return false; + } + + if (o == null) { + return false; + } + + return mSignalStrength == s.mSignalStrength && mBitErrorRate == s.mBitErrorRate && + s.mTimingAdvance == mTimingAdvance; + } + + /** + * @return string representation. + */ + @Override + public String toString() { + return "CellSignalStrengthGsm:" + + " ss=" + mSignalStrength + + " ber=" + mBitErrorRate + + " mTa=" + mTimingAdvance; + } + + /** Implement the Parcelable interface */ + @Override + public void writeToParcel(Parcel dest, int flags) { + if (DBG) log("writeToParcel(Parcel, int): " + toString()); + dest.writeInt(mSignalStrength); + dest.writeInt(mBitErrorRate); + dest.writeInt(mTimingAdvance); + } + + /** + * Construct a SignalStrength object from the given parcel + * where the token is already been processed. + */ + private CellSignalStrengthGsm(Parcel in) { + mSignalStrength = in.readInt(); + mBitErrorRate = in.readInt(); + mTimingAdvance = in.readInt(); + if (DBG) log("CellSignalStrengthGsm(Parcel): " + toString()); + } + + /** Implement the Parcelable interface */ + @Override + public int describeContents() { + return 0; + } + + /** Implement the Parcelable interface */ + @SuppressWarnings("hiding") + public static final Parcelable.Creator<CellSignalStrengthGsm> CREATOR = + new Parcelable.Creator<CellSignalStrengthGsm>() { + @Override + public CellSignalStrengthGsm createFromParcel(Parcel in) { + return new CellSignalStrengthGsm(in); + } + + @Override + public CellSignalStrengthGsm[] newArray(int size) { + return new CellSignalStrengthGsm[size]; + } + }; + + /** + * log + */ + private static void log(String s) { + Rlog.w(LOG_TAG, s); + } +} diff --git a/android/telephony/CellSignalStrengthLte.java b/android/telephony/CellSignalStrengthLte.java new file mode 100644 index 00000000..0d07a408 --- /dev/null +++ b/android/telephony/CellSignalStrengthLte.java @@ -0,0 +1,336 @@ +/* + * Copyright (C) 2012 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.telephony; + +import android.os.Parcel; +import android.os.Parcelable; +import android.telephony.Rlog; + +/** + * LTE signal strength related information. + */ +public final class CellSignalStrengthLte extends CellSignalStrength implements Parcelable { + + private static final String LOG_TAG = "CellSignalStrengthLte"; + private static final boolean DBG = false; + + private int mSignalStrength; + private int mRsrp; + private int mRsrq; + private int mRssnr; + private int mCqi; + private int mTimingAdvance; + + /** + * Empty constructor + * + * @hide + */ + public CellSignalStrengthLte() { + setDefaultValues(); + } + + /** + * Constructor + * + * @hide + */ + public CellSignalStrengthLte(int signalStrength, int rsrp, int rsrq, int rssnr, int cqi, + int timingAdvance) { + initialize(signalStrength, rsrp, rsrq, rssnr, cqi, timingAdvance); + } + + /** + * Copy constructors + * + * @param s Source SignalStrength + * + * @hide + */ + public CellSignalStrengthLte(CellSignalStrengthLte s) { + copyFrom(s); + } + + /** + * Initialize all the values + * + * @param lteSignalStrength + * @param rsrp + * @param rsrq + * @param rssnr + * @param cqi + * + * @hide + */ + public void initialize(int lteSignalStrength, int rsrp, int rsrq, int rssnr, int cqi, + int timingAdvance) { + mSignalStrength = lteSignalStrength; + mRsrp = rsrp; + mRsrq = rsrq; + mRssnr = rssnr; + mCqi = cqi; + mTimingAdvance = timingAdvance; + } + + /** + * Initialize from the SignalStrength structure. + * + * @param ss + * + * @hide + */ + public void initialize(SignalStrength ss, int timingAdvance) { + mSignalStrength = ss.getLteSignalStrength(); + mRsrp = ss.getLteRsrp(); + mRsrq = ss.getLteRsrq(); + mRssnr = ss.getLteRssnr(); + mCqi = ss.getLteCqi(); + mTimingAdvance = timingAdvance; + } + + /** + * @hide + */ + protected void copyFrom(CellSignalStrengthLte s) { + mSignalStrength = s.mSignalStrength; + mRsrp = s.mRsrp; + mRsrq = s.mRsrq; + mRssnr = s.mRssnr; + mCqi = s.mCqi; + mTimingAdvance = s.mTimingAdvance; + } + + /** + * @hide + */ + @Override + public CellSignalStrengthLte copy() { + return new CellSignalStrengthLte(this); + } + + /** @hide */ + @Override + public void setDefaultValues() { + mSignalStrength = Integer.MAX_VALUE; + mRsrp = Integer.MAX_VALUE; + mRsrq = Integer.MAX_VALUE; + mRssnr = Integer.MAX_VALUE; + mCqi = Integer.MAX_VALUE; + mTimingAdvance = Integer.MAX_VALUE; + } + + /** + * Get signal level as an int from 0..4 + */ + @Override + public int getLevel() { + int levelRsrp = 0; + int levelRssnr = 0; + + if (mRsrp == Integer.MAX_VALUE) levelRsrp = 0; + else if (mRsrp >= -95) levelRsrp = SIGNAL_STRENGTH_GREAT; + else if (mRsrp >= -105) levelRsrp = SIGNAL_STRENGTH_GOOD; + else if (mRsrp >= -115) levelRsrp = SIGNAL_STRENGTH_MODERATE; + else levelRsrp = SIGNAL_STRENGTH_POOR; + + // See RIL_LTE_SignalStrength in ril.h + if (mRssnr == Integer.MAX_VALUE) levelRssnr = 0; + else if (mRssnr >= 45) levelRssnr = SIGNAL_STRENGTH_GREAT; + else if (mRssnr >= 10) levelRssnr = SIGNAL_STRENGTH_GOOD; + else if (mRssnr >= -30) levelRssnr = SIGNAL_STRENGTH_MODERATE; + else levelRssnr = SIGNAL_STRENGTH_POOR; + + int level; + if (mRsrp == Integer.MAX_VALUE) + level = levelRssnr; + else if (mRssnr == Integer.MAX_VALUE) + level = levelRsrp; + else + level = (levelRssnr < levelRsrp) ? levelRssnr : levelRsrp; + + if (DBG) log("Lte rsrp level: " + levelRsrp + + " snr level: " + levelRssnr + " level: " + level); + return level; + } + + /** + * Get reference signal received quality + */ + public int getRsrq() { + return mRsrq; + } + + /** + * Get reference signal signal-to-noise ratio + */ + public int getRssnr() { + return mRssnr; + } + + /** + * Get reference signal received power + */ + public int getRsrp() { + return mRsrp; + } + + /** + * Get channel quality indicator + */ + public int getCqi() { + return mCqi; + } + + /** + * Get signal strength as dBm + */ + @Override + public int getDbm() { + return mRsrp; + } + + /** + * Get the LTE signal level as an asu value between 0..97, 99 is unknown + * Asu is calculated based on 3GPP RSRP. Refer to 3GPP 27.007 (Ver 10.3.0) Sec 8.69 + */ + @Override + public int getAsuLevel() { + int lteAsuLevel = 99; + int lteDbm = getDbm(); + if (lteDbm == Integer.MAX_VALUE) lteAsuLevel = 99; + else if (lteDbm <= -140) lteAsuLevel = 0; + else if (lteDbm >= -43) lteAsuLevel = 97; + else lteAsuLevel = lteDbm + 140; + if (DBG) log("Lte Asu level: "+lteAsuLevel); + return lteAsuLevel; + } + + /** + * Get the timing advance value for LTE, as a value between 0..63. + * Integer.MAX_VALUE is reported when there is no active RRC + * connection. Refer to 3GPP 36.213 Sec 4.2.3 + * @return the LTE timing advance, if available. + */ + public int getTimingAdvance() { + return mTimingAdvance; + } + + @Override + public int hashCode() { + int primeNum = 31; + return (mSignalStrength * primeNum) + (mRsrp * primeNum) + + (mRsrq * primeNum) + (mRssnr * primeNum) + (mCqi * primeNum) + + (mTimingAdvance * primeNum); + } + + @Override + public boolean equals (Object o) { + CellSignalStrengthLte s; + + try { + s = (CellSignalStrengthLte) o; + } catch (ClassCastException ex) { + return false; + } + + if (o == null) { + return false; + } + + return mSignalStrength == s.mSignalStrength + && mRsrp == s.mRsrp + && mRsrq == s.mRsrq + && mRssnr == s.mRssnr + && mCqi == s.mCqi + && mTimingAdvance == s.mTimingAdvance; + } + + /** + * @return string representation. + */ + @Override + public String toString() { + return "CellSignalStrengthLte:" + + " ss=" + mSignalStrength + + " rsrp=" + mRsrp + + " rsrq=" + mRsrq + + " rssnr=" + mRssnr + + " cqi=" + mCqi + + " ta=" + mTimingAdvance; + } + + /** Implement the Parcelable interface */ + @Override + public void writeToParcel(Parcel dest, int flags) { + if (DBG) log("writeToParcel(Parcel, int): " + toString()); + dest.writeInt(mSignalStrength); + // Need to multiply rsrp and rsrq by -1 + // to ensure consistency when reading values written here + // unless the values are invalid + dest.writeInt(mRsrp * (mRsrp != Integer.MAX_VALUE ? -1 : 1)); + dest.writeInt(mRsrq * (mRsrq != Integer.MAX_VALUE ? -1 : 1)); + dest.writeInt(mRssnr); + dest.writeInt(mCqi); + dest.writeInt(mTimingAdvance); + } + + /** + * Construct a SignalStrength object from the given parcel + * where the token is already been processed. + */ + private CellSignalStrengthLte(Parcel in) { + mSignalStrength = in.readInt(); + // rsrp and rsrq are written into the parcel as positive values. + // Need to convert into negative values unless the values are invalid + mRsrp = in.readInt(); + if (mRsrp != Integer.MAX_VALUE) mRsrp *= -1; + mRsrq = in.readInt(); + if (mRsrq != Integer.MAX_VALUE) mRsrq *= -1; + mRssnr = in.readInt(); + mCqi = in.readInt(); + mTimingAdvance = in.readInt(); + if (DBG) log("CellSignalStrengthLte(Parcel): " + toString()); + } + + /** Implement the Parcelable interface */ + @Override + public int describeContents() { + return 0; + } + + /** Implement the Parcelable interface */ + @SuppressWarnings("hiding") + public static final Parcelable.Creator<CellSignalStrengthLte> CREATOR = + new Parcelable.Creator<CellSignalStrengthLte>() { + @Override + public CellSignalStrengthLte createFromParcel(Parcel in) { + return new CellSignalStrengthLte(in); + } + + @Override + public CellSignalStrengthLte[] newArray(int size) { + return new CellSignalStrengthLte[size]; + } + }; + + /** + * log + */ + private static void log(String s) { + Rlog.w(LOG_TAG, s); + } +} diff --git a/android/telephony/CellSignalStrengthWcdma.java b/android/telephony/CellSignalStrengthWcdma.java new file mode 100644 index 00000000..b94b01da --- /dev/null +++ b/android/telephony/CellSignalStrengthWcdma.java @@ -0,0 +1,235 @@ +/* + * Copyright (C) 2012 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.telephony; + +import android.os.Parcel; +import android.os.Parcelable; +import android.telephony.Rlog; + +/** + * Wcdma signal strength related information. + */ +public final class CellSignalStrengthWcdma extends CellSignalStrength implements Parcelable { + + private static final String LOG_TAG = "CellSignalStrengthWcdma"; + private static final boolean DBG = false; + + private static final int WCDMA_SIGNAL_STRENGTH_GREAT = 12; + private static final int WCDMA_SIGNAL_STRENGTH_GOOD = 8; + private static final int WCDMA_SIGNAL_STRENGTH_MODERATE = 5; + + private int mSignalStrength; // Valid values are (0-31, 99) as defined in TS 27.007 8.5 + private int mBitErrorRate; // bit error rate (0-7, 99) as defined in TS 27.007 8.5 + + /** + * Empty constructor + * + * @hide + */ + public CellSignalStrengthWcdma() { + setDefaultValues(); + } + + /** + * Constructor + * + * @hide + */ + public CellSignalStrengthWcdma(int ss, int ber) { + initialize(ss, ber); + } + + /** + * Copy constructors + * + * @param s Source SignalStrength + * + * @hide + */ + public CellSignalStrengthWcdma(CellSignalStrengthWcdma s) { + copyFrom(s); + } + + /** + * Initialize all the values + * + * @param ss SignalStrength as ASU value + * @param ber is Bit Error Rate + * + * @hide + */ + public void initialize(int ss, int ber) { + mSignalStrength = ss; + mBitErrorRate = ber; + } + + /** + * @hide + */ + protected void copyFrom(CellSignalStrengthWcdma s) { + mSignalStrength = s.mSignalStrength; + mBitErrorRate = s.mBitErrorRate; + } + + /** + * @hide + */ + @Override + public CellSignalStrengthWcdma copy() { + return new CellSignalStrengthWcdma(this); + } + + /** @hide */ + @Override + public void setDefaultValues() { + mSignalStrength = Integer.MAX_VALUE; + mBitErrorRate = Integer.MAX_VALUE; + } + + /** + * Get signal level as an int from 0..4 + */ + @Override + public int getLevel() { + int level; + + // ASU ranges from 0 to 31 - TS 27.007 Sec 8.5 + // asu = 0 (-113dB or less) is very weak + // signal, its better to show 0 bars to the user in such cases. + // asu = 99 is a special case, where the signal strength is unknown. + int asu = mSignalStrength; + if (asu <= 2 || asu == 99) level = SIGNAL_STRENGTH_NONE_OR_UNKNOWN; + else if (asu >= WCDMA_SIGNAL_STRENGTH_GREAT) level = SIGNAL_STRENGTH_GREAT; + else if (asu >= WCDMA_SIGNAL_STRENGTH_GOOD) level = SIGNAL_STRENGTH_GOOD; + else if (asu >= WCDMA_SIGNAL_STRENGTH_MODERATE) level = SIGNAL_STRENGTH_MODERATE; + else level = SIGNAL_STRENGTH_POOR; + if (DBG) log("getLevel=" + level); + return level; + } + + /** + * Get the signal strength as dBm + */ + @Override + public int getDbm() { + int dBm; + + int level = mSignalStrength; + int asu = (level == 99 ? Integer.MAX_VALUE : level); + if (asu != Integer.MAX_VALUE) { + dBm = -113 + (2 * asu); + } else { + dBm = Integer.MAX_VALUE; + } + if (DBG) log("getDbm=" + dBm); + return dBm; + } + + /** + * Get the signal level as an asu value between 0..31, 99 is unknown + * Asu is calculated based on 3GPP RSRP. Refer to 3GPP 27.007 (Ver 10.3.0) Sec 8.69 + */ + @Override + public int getAsuLevel() { + // ASU ranges from 0 to 31 - TS 27.007 Sec 8.5 + // asu = 0 (-113dB or less) is very weak + // signal, its better to show 0 bars to the user in such cases. + // asu = 99 is a special case, where the signal strength is unknown. + int level = mSignalStrength; + if (DBG) log("getAsuLevel=" + level); + return level; + } + + @Override + public int hashCode() { + int primeNum = 31; + return (mSignalStrength * primeNum) + (mBitErrorRate * primeNum); + } + + @Override + public boolean equals (Object o) { + CellSignalStrengthWcdma s; + + try { + s = (CellSignalStrengthWcdma) o; + } catch (ClassCastException ex) { + return false; + } + + if (o == null) { + return false; + } + + return mSignalStrength == s.mSignalStrength && mBitErrorRate == s.mBitErrorRate; + } + + /** + * @return string representation. + */ + @Override + public String toString() { + return "CellSignalStrengthWcdma:" + + " ss=" + mSignalStrength + + " ber=" + mBitErrorRate; + } + + /** Implement the Parcelable interface */ + @Override + public void writeToParcel(Parcel dest, int flags) { + if (DBG) log("writeToParcel(Parcel, int): " + toString()); + dest.writeInt(mSignalStrength); + dest.writeInt(mBitErrorRate); + } + + /** + * Construct a SignalStrength object from the given parcel + * where the token is already been processed. + */ + private CellSignalStrengthWcdma(Parcel in) { + mSignalStrength = in.readInt(); + mBitErrorRate = in.readInt(); + if (DBG) log("CellSignalStrengthWcdma(Parcel): " + toString()); + } + + /** Implement the Parcelable interface */ + @Override + public int describeContents() { + return 0; + } + + /** Implement the Parcelable interface */ + @SuppressWarnings("hiding") + public static final Parcelable.Creator<CellSignalStrengthWcdma> CREATOR = + new Parcelable.Creator<CellSignalStrengthWcdma>() { + @Override + public CellSignalStrengthWcdma createFromParcel(Parcel in) { + return new CellSignalStrengthWcdma(in); + } + + @Override + public CellSignalStrengthWcdma[] newArray(int size) { + return new CellSignalStrengthWcdma[size]; + } + }; + + /** + * log + */ + private static void log(String s) { + Rlog.w(LOG_TAG, s); + } +} diff --git a/android/telephony/ClientRequestStats.java b/android/telephony/ClientRequestStats.java new file mode 100644 index 00000000..381c847d --- /dev/null +++ b/android/telephony/ClientRequestStats.java @@ -0,0 +1,175 @@ +/* + * 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.telephony; + +import android.os.Parcel; +import android.os.Parcelable; +import android.telephony.TelephonyHistogram; +import android.util.SparseArray; +import java.util.ArrayList; +import java.util.List; + +/** + * Parcelable class to store Client request statistics information. + * + * @hide + */ +public final class ClientRequestStats implements Parcelable { + public static final Parcelable.Creator<ClientRequestStats> CREATOR = + new Parcelable.Creator<ClientRequestStats>() { + + public ClientRequestStats createFromParcel(Parcel in) { + return new ClientRequestStats(in); + } + + public ClientRequestStats[] newArray(int size) { + return new ClientRequestStats[size]; + } + }; + private static final int REQUEST_HISTOGRAM_BUCKET_COUNT = 5; + private String mCallingPackage; + /* completed requests wake lock time in milli seconds */ + private long mCompletedRequestsWakelockTime = 0; + private long mCompletedRequestsCount = 0; + private long mPendingRequestsWakelockTime = 0; + private long mPendingRequestsCount = 0; + private SparseArray<TelephonyHistogram> mRequestHistograms = + new SparseArray<TelephonyHistogram>(); + + public ClientRequestStats(Parcel in) { + readFromParcel(in); + } + + public ClientRequestStats() { + } + + public ClientRequestStats(ClientRequestStats clientRequestStats) { + mCallingPackage = clientRequestStats.getCallingPackage(); + mCompletedRequestsCount = clientRequestStats.getCompletedRequestsCount(); + mCompletedRequestsWakelockTime = clientRequestStats.getCompletedRequestsWakelockTime(); + mPendingRequestsCount = clientRequestStats.getPendingRequestsCount(); + mPendingRequestsWakelockTime = clientRequestStats.getPendingRequestsWakelockTime(); + + List<TelephonyHistogram> list = clientRequestStats.getRequestHistograms(); + for (TelephonyHistogram entry : list) { + mRequestHistograms.put(entry.getId(), entry); + } + } + + public String getCallingPackage() { + return mCallingPackage; + } + + public void setCallingPackage(String mCallingPackage) { + this.mCallingPackage = mCallingPackage; + } + + public long getCompletedRequestsWakelockTime() { + return mCompletedRequestsWakelockTime; + } + + public void addCompletedWakelockTime(long completedRequestsWakelockTime) { + this.mCompletedRequestsWakelockTime += completedRequestsWakelockTime; + } + + public long getPendingRequestsWakelockTime() { + return mPendingRequestsWakelockTime; + } + + public void setPendingRequestsWakelockTime(long pendingRequestsWakelockTime) { + this.mPendingRequestsWakelockTime = pendingRequestsWakelockTime; + } + + public long getCompletedRequestsCount() { + return mCompletedRequestsCount; + } + + public void incrementCompletedRequestsCount() { + this.mCompletedRequestsCount++; + } + + public long getPendingRequestsCount() { + return mPendingRequestsCount; + } + + public void setPendingRequestsCount(long pendingRequestsCount) { + this.mPendingRequestsCount = pendingRequestsCount; + } + + public List<TelephonyHistogram> getRequestHistograms() { + List<TelephonyHistogram> list; + synchronized (mRequestHistograms) { + list = new ArrayList<>(mRequestHistograms.size()); + for (int i = 0; i < mRequestHistograms.size(); i++) { + TelephonyHistogram entry = new TelephonyHistogram(mRequestHistograms.valueAt(i)); + list.add(entry); + } + } + return list; + } + + public void updateRequestHistograms(int requestId, int time) { + synchronized (mRequestHistograms) { + TelephonyHistogram entry = mRequestHistograms.get(requestId); + if (entry == null) { + entry = new TelephonyHistogram(TelephonyHistogram.TELEPHONY_CATEGORY_RIL, + requestId, REQUEST_HISTOGRAM_BUCKET_COUNT); + mRequestHistograms.put(requestId, entry); + } + entry.addTimeTaken(time); + } + } + + @Override + public String toString() { + return "ClientRequestStats{" + + "mCallingPackage='" + mCallingPackage + '\'' + + ", mCompletedRequestsWakelockTime=" + mCompletedRequestsWakelockTime + + ", mCompletedRequestsCount=" + mCompletedRequestsCount + + ", mPendingRequestsWakelockTime=" + mPendingRequestsWakelockTime + + ", mPendingRequestsCount=" + mPendingRequestsCount + + '}'; + } + + @Override + public int describeContents() { + return 0; + } + + public void readFromParcel(Parcel in) { + mCallingPackage = in.readString(); + mCompletedRequestsWakelockTime = in.readLong(); + mCompletedRequestsCount = in.readLong(); + mPendingRequestsWakelockTime = in.readLong(); + mPendingRequestsCount = in.readLong(); + ArrayList<TelephonyHistogram> requestHistograms = new ArrayList<TelephonyHistogram>(); + in.readTypedList(requestHistograms, TelephonyHistogram.CREATOR); + for (TelephonyHistogram h : requestHistograms) { + mRequestHistograms.put(h.getId(), h); + } + } + + @Override + public void writeToParcel(Parcel dest, int flags) { + dest.writeString(mCallingPackage); + dest.writeLong(mCompletedRequestsWakelockTime); + dest.writeLong(mCompletedRequestsCount); + dest.writeLong(mPendingRequestsWakelockTime); + dest.writeLong(mPendingRequestsCount); + dest.writeTypedList(getRequestHistograms()); + } +} diff --git a/android/telephony/DataConnectionRealTimeInfo.java b/android/telephony/DataConnectionRealTimeInfo.java new file mode 100644 index 00000000..f71f58d0 --- /dev/null +++ b/android/telephony/DataConnectionRealTimeInfo.java @@ -0,0 +1,139 @@ +/* + * Copyright (C) 2014 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.telephony; + +import android.os.Parcel; +import android.os.Parcelable; + +/** + * Data connection real time information + * + * TODO: How to handle multiple subscriptions? + * @hide + */ +public class DataConnectionRealTimeInfo implements Parcelable { + private long mTime; // Time the info was collected since boot in nanos; + + public static final int DC_POWER_STATE_LOW = 1; + public static final int DC_POWER_STATE_MEDIUM = 2; + public static final int DC_POWER_STATE_HIGH = 3; + public static final int DC_POWER_STATE_UNKNOWN = Integer.MAX_VALUE; + + private int mDcPowerState; // DC_POWER_STATE_[LOW | MEDIUM | HIGH | UNKNOWN] + + /** + * Constructor + * + * @hide + */ + public DataConnectionRealTimeInfo(long time, int dcPowerState) { + mTime = time; + mDcPowerState = dcPowerState; + } + + /** + * Constructor + * + * @hide + */ + public DataConnectionRealTimeInfo() { + mTime = Long.MAX_VALUE; + mDcPowerState = DC_POWER_STATE_UNKNOWN; + } + + /** + * Construct a PreciseCallState object from the given parcel. + */ + private DataConnectionRealTimeInfo(Parcel in) { + mTime = in.readLong(); + mDcPowerState = in.readInt(); + } + + /** + * @return time the information was collected or Long.MAX_VALUE if unknown + */ + public long getTime() { + return mTime; + } + + /** + * @return DC_POWER_STATE_[LOW | MEDIUM | HIGH | UNKNOWN] + */ + public int getDcPowerState() { + return mDcPowerState; + } + + @Override + public int describeContents() { + return 0; + } + + @Override + public void writeToParcel(Parcel out, int flags) { + out.writeLong(mTime); + out.writeInt(mDcPowerState); + } + + public static final Parcelable.Creator<DataConnectionRealTimeInfo> CREATOR + = new Parcelable.Creator<DataConnectionRealTimeInfo>() { + + @Override + public DataConnectionRealTimeInfo createFromParcel(Parcel in) { + return new DataConnectionRealTimeInfo(in); + } + + @Override + public DataConnectionRealTimeInfo[] newArray(int size) { + return new DataConnectionRealTimeInfo[size]; + } + }; + + @Override + public int hashCode() { + final long prime = 17; + long result = 1; + result = (prime * result) + mTime; + result += (prime * result) + mDcPowerState; + return (int)result; + } + + @Override + public boolean equals(Object obj) { + if (this == obj) { + return true; + } + if (obj == null) { + return false; + } + if (getClass() != obj.getClass()) { + return false; + } + DataConnectionRealTimeInfo other = (DataConnectionRealTimeInfo) obj; + return (mTime == other.mTime) + && (mDcPowerState == other.mDcPowerState); + } + + @Override + public String toString() { + StringBuffer sb = new StringBuffer(); + + sb.append("mTime=").append(mTime); + sb.append(" mDcPowerState=").append(mDcPowerState); + + return sb.toString(); + } +} diff --git a/android/telephony/DisconnectCause.java b/android/telephony/DisconnectCause.java new file mode 100644 index 00000000..98fb6534 --- /dev/null +++ b/android/telephony/DisconnectCause.java @@ -0,0 +1,420 @@ +/* + * Copyright (C) 2014 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.telephony; + +/** + * Contains disconnect call causes generated by the framework and the RIL. + * @hide + */ +public class DisconnectCause { + + /** The disconnect cause is not valid (Not received a disconnect cause) */ + public static final int NOT_VALID = -1; + /** Has not yet disconnected */ + public static final int NOT_DISCONNECTED = 0; + /** An incoming call that was missed and never answered */ + public static final int INCOMING_MISSED = 1; + /** Normal; Remote hangup*/ + public static final int NORMAL = 2; + /** Normal; Local hangup */ + public static final int LOCAL = 3; + /** Outgoing call to busy line */ + public static final int BUSY = 4; + /** Outgoing call to congested network */ + public static final int CONGESTION = 5; + /** Not presently used */ + public static final int MMI = 6; + /** Invalid dial string */ + public static final int INVALID_NUMBER = 7; + /** Cannot reach the peer */ + public static final int NUMBER_UNREACHABLE = 8; + /** Cannot reach the server */ + public static final int SERVER_UNREACHABLE = 9; + /** Invalid credentials */ + public static final int INVALID_CREDENTIALS = 10; + /** Calling from out of network is not allowed */ + public static final int OUT_OF_NETWORK = 11; + /** Server error */ + public static final int SERVER_ERROR = 12; + /** Client timed out */ + public static final int TIMED_OUT = 13; + /** Client went out of network range */ + public static final int LOST_SIGNAL = 14; + /** GSM or CDMA ACM limit exceeded */ + public static final int LIMIT_EXCEEDED = 15; + /** An incoming call that was rejected */ + public static final int INCOMING_REJECTED = 16; + /** Radio is turned off explicitly */ + public static final int POWER_OFF = 17; + /** Out of service */ + public static final int OUT_OF_SERVICE = 18; + /** No ICC, ICC locked, or other ICC error */ + public static final int ICC_ERROR = 19; + /** Call was blocked by call barring */ + public static final int CALL_BARRED = 20; + /** Call was blocked by fixed dial number */ + public static final int FDN_BLOCKED = 21; + /** Call was blocked by restricted all voice access */ + public static final int CS_RESTRICTED = 22; + /** Call was blocked by restricted normal voice access */ + public static final int CS_RESTRICTED_NORMAL = 23; + /** Call was blocked by restricted emergency voice access */ + public static final int CS_RESTRICTED_EMERGENCY = 24; + /** Unassigned number */ + public static final int UNOBTAINABLE_NUMBER = 25; + /** MS is locked until next power cycle */ + public static final int CDMA_LOCKED_UNTIL_POWER_CYCLE = 26; + /** Drop call*/ + public static final int CDMA_DROP = 27; + /** INTERCEPT order received, MS state idle entered */ + public static final int CDMA_INTERCEPT = 28; + /** MS has been redirected, call is cancelled */ + public static final int CDMA_REORDER = 29; + /** Service option rejection */ + public static final int CDMA_SO_REJECT = 30; + /** Requested service is rejected, retry delay is set */ + public static final int CDMA_RETRY_ORDER = 31; + /** Unable to obtain access to the CDMA system */ + public static final int CDMA_ACCESS_FAILURE = 32; + /** Not a preempted call */ + public static final int CDMA_PREEMPTED = 33; + /** Not an emergency call */ + public static final int CDMA_NOT_EMERGENCY = 34; + /** Access Blocked by CDMA network */ + public static final int CDMA_ACCESS_BLOCKED = 35; + /** Unknown error or not specified */ + public static final int ERROR_UNSPECIFIED = 36; + /** + * Only emergency numbers are allowed, but we tried to dial + * a non-emergency number. + */ + // TODO: This should be the same as NOT_EMERGENCY + public static final int EMERGENCY_ONLY = 37; + /** + * The supplied CALL Intent didn't contain a valid phone number. + */ + public static final int NO_PHONE_NUMBER_SUPPLIED = 38; + /** + * Our initial phone number was actually an MMI sequence. + */ + public static final int DIALED_MMI = 39; + /** + * We tried to call a voicemail: URI but the device has no + * voicemail number configured. + */ + public static final int VOICEMAIL_NUMBER_MISSING = 40; + /** + * This status indicates that InCallScreen should display the + * CDMA-specific "call lost" dialog. (If an outgoing call fails, + * and the CDMA "auto-retry" feature is enabled, *and* the retried + * call fails too, we display this specific dialog.) + * + * TODO: this is currently unused, since the "call lost" dialog + * needs to be triggered by a *disconnect* event, rather than when + * the InCallScreen first comes to the foreground. For now we use + * the needToShowCallLostDialog field for this (see below.) + */ + public static final int CDMA_CALL_LOST = 41; + /** + * This status indicates that the call was placed successfully, + * but additionally, the InCallScreen needs to display the + * "Exiting ECM" dialog. + * + * (Details: "Emergency callback mode" is a CDMA-specific concept + * where the phone disallows data connections over the cell + * network for some period of time after you make an emergency + * call. If the phone is in ECM and you dial a non-emergency + * number, that automatically *cancels* ECM, but we additionally + * need to warn the user that ECM has been canceled (see bug + * 4207607.)) + * + * TODO: Rethink where the best place to put this is. It is not a notification + * of a failure of the connection -- it is an additional message that accompanies + * a successful connection giving the user important information about what happened. + * + * {@hide} + */ + public static final int EXITED_ECM = 42; + + /** + * The outgoing call failed with an unknown cause. + */ + public static final int OUTGOING_FAILURE = 43; + + /** + * The outgoing call was canceled by the {@link android.telecom.ConnectionService}. + */ + public static final int OUTGOING_CANCELED = 44; + + /** + * The call, which was an IMS call, disconnected because it merged with another call. + */ + public static final int IMS_MERGED_SUCCESSFULLY = 45; + + /** + * Stk Call Control modified DIAL request to USSD request. + * {@hide} + */ + public static final int DIAL_MODIFIED_TO_USSD = 46; + /** + * Stk Call Control modified DIAL request to SS request. + * {@hide} + */ + public static final int DIAL_MODIFIED_TO_SS = 47; + /** + * Stk Call Control modified DIAL request to DIAL with modified data. + * {@hide} + */ + public static final int DIAL_MODIFIED_TO_DIAL = 48; + + /** + * The call was terminated because CDMA phone service and roaming have already been activated. + * {@hide} + */ + public static final int CDMA_ALREADY_ACTIVATED = 49; + + /** + * The call was terminated because it is not possible to place a video call while TTY is + * enabled. + * {@hide} + */ + public static final int VIDEO_CALL_NOT_ALLOWED_WHILE_TTY_ENABLED = 50; + + /** + * The call was terminated because it was pulled to another device. + * {@hide} + */ + public static final int CALL_PULLED = 51; + + /** + * The call was terminated because it was answered on another device. + * {@hide} + */ + public static final int ANSWERED_ELSEWHERE = 52; + + /** + * The call was terminated because the maximum allowable number of calls has been reached. + * {@hide} + */ + public static final int MAXIMUM_NUMBER_OF_CALLS_REACHED = 53; + + /** + * The call was terminated because cellular data has been disabled. + * Used when in a video call and the user disables cellular data via the settings. + * {@hide} + */ + public static final int DATA_DISABLED = 54; + + /** + * The call was terminated because the data policy has disabled cellular data. + * Used when in a video call and the user has exceeded the device data limit. + * {@hide} + */ + public static final int DATA_LIMIT_REACHED = 55; + + /** + * The call being placed was detected as a call forwarding number and was being dialed while + * roaming on a carrier that does not allow this. + */ + public static final int DIALED_CALL_FORWARDING_WHILE_ROAMING = 57; + + /** + * The network does not accept the emergency call request because IMEI was used as + * identification and this cability is not supported by the network. + * {@hide} + */ + public static final int IMEI_NOT_ACCEPTED = 58; + + /** + * A call over WIFI was disconnected because the WIFI signal was lost or became too degraded to + * continue the call. + */ + public static final int WIFI_LOST = 59; + + /** + * The call has failed because of access class barring. + * {@hide} + */ + public static final int IMS_ACCESS_BLOCKED = 60; + + /** + * The call has ended (mid-call) because the device's battery is too low. + */ + public static final int LOW_BATTERY = 61; + + /** + * A call was not dialed because the device's battery is too low. + */ + public static final int DIAL_LOW_BATTERY = 62; + + /** + * Emergency call failed with a temporary fail cause and can be redialed on this slot. + * {@hide} + */ + public static final int EMERGENCY_TEMP_FAILURE = 63; + + /** + * Emergency call failed with a permanent fail cause and should not be redialed on this + * slot. + * {@hide} + */ + public static final int EMERGENCY_PERM_FAILURE = 64; + //********************************************************************************************* + // When adding a disconnect type: + // 1) Update toString() with the newly added disconnect type. + // 2) Update android.telecom.DisconnectCauseUtil with any mappings to a telecom.DisconnectCause. + //********************************************************************************************* + + /** Private constructor to avoid class instantiation. */ + private DisconnectCause() { + // Do nothing. + } + + /** Returns descriptive string for the specified disconnect cause. */ + public static String toString(int cause) { + switch (cause) { + case NOT_DISCONNECTED: + return "NOT_DISCONNECTED"; + case INCOMING_MISSED: + return "INCOMING_MISSED"; + case NORMAL: + return "NORMAL"; + case LOCAL: + return "LOCAL"; + case BUSY: + return "BUSY"; + case CONGESTION: + return "CONGESTION"; + case INVALID_NUMBER: + return "INVALID_NUMBER"; + case NUMBER_UNREACHABLE: + return "NUMBER_UNREACHABLE"; + case SERVER_UNREACHABLE: + return "SERVER_UNREACHABLE"; + case INVALID_CREDENTIALS: + return "INVALID_CREDENTIALS"; + case OUT_OF_NETWORK: + return "OUT_OF_NETWORK"; + case SERVER_ERROR: + return "SERVER_ERROR"; + case TIMED_OUT: + return "TIMED_OUT"; + case LOST_SIGNAL: + return "LOST_SIGNAL"; + case LIMIT_EXCEEDED: + return "LIMIT_EXCEEDED"; + case INCOMING_REJECTED: + return "INCOMING_REJECTED"; + case POWER_OFF: + return "POWER_OFF"; + case OUT_OF_SERVICE: + return "OUT_OF_SERVICE"; + case ICC_ERROR: + return "ICC_ERROR"; + case CALL_BARRED: + return "CALL_BARRED"; + case FDN_BLOCKED: + return "FDN_BLOCKED"; + case CS_RESTRICTED: + return "CS_RESTRICTED"; + case CS_RESTRICTED_NORMAL: + return "CS_RESTRICTED_NORMAL"; + case CS_RESTRICTED_EMERGENCY: + return "CS_RESTRICTED_EMERGENCY"; + case UNOBTAINABLE_NUMBER: + return "UNOBTAINABLE_NUMBER"; + case CDMA_LOCKED_UNTIL_POWER_CYCLE: + return "CDMA_LOCKED_UNTIL_POWER_CYCLE"; + case CDMA_DROP: + return "CDMA_DROP"; + case CDMA_INTERCEPT: + return "CDMA_INTERCEPT"; + case CDMA_REORDER: + return "CDMA_REORDER"; + case CDMA_SO_REJECT: + return "CDMA_SO_REJECT"; + case CDMA_RETRY_ORDER: + return "CDMA_RETRY_ORDER"; + case CDMA_ACCESS_FAILURE: + return "CDMA_ACCESS_FAILURE"; + case CDMA_PREEMPTED: + return "CDMA_PREEMPTED"; + case CDMA_NOT_EMERGENCY: + return "CDMA_NOT_EMERGENCY"; + case CDMA_ACCESS_BLOCKED: + return "CDMA_ACCESS_BLOCKED"; + case EMERGENCY_ONLY: + return "EMERGENCY_ONLY"; + case NO_PHONE_NUMBER_SUPPLIED: + return "NO_PHONE_NUMBER_SUPPLIED"; + case DIALED_MMI: + return "DIALED_MMI"; + case VOICEMAIL_NUMBER_MISSING: + return "VOICEMAIL_NUMBER_MISSING"; + case CDMA_CALL_LOST: + return "CDMA_CALL_LOST"; + case EXITED_ECM: + return "EXITED_ECM"; + case DIAL_MODIFIED_TO_USSD: + return "DIAL_MODIFIED_TO_USSD"; + case DIAL_MODIFIED_TO_SS: + return "DIAL_MODIFIED_TO_SS"; + case DIAL_MODIFIED_TO_DIAL: + return "DIAL_MODIFIED_TO_DIAL"; + case ERROR_UNSPECIFIED: + return "ERROR_UNSPECIFIED"; + case OUTGOING_FAILURE: + return "OUTGOING_FAILURE"; + case OUTGOING_CANCELED: + return "OUTGOING_CANCELED"; + case IMS_MERGED_SUCCESSFULLY: + return "IMS_MERGED_SUCCESSFULLY"; + case CDMA_ALREADY_ACTIVATED: + return "CDMA_ALREADY_ACTIVATED"; + case VIDEO_CALL_NOT_ALLOWED_WHILE_TTY_ENABLED: + return "VIDEO_CALL_NOT_ALLOWED_WHILE_TTY_ENABLED"; + case CALL_PULLED: + return "CALL_PULLED"; + case ANSWERED_ELSEWHERE: + return "ANSWERED_ELSEWHERE"; + case MAXIMUM_NUMBER_OF_CALLS_REACHED: + return "MAXIMUM_NUMER_OF_CALLS_REACHED"; + case DATA_DISABLED: + return "DATA_DISABLED"; + case DATA_LIMIT_REACHED: + return "DATA_LIMIT_REACHED"; + case DIALED_CALL_FORWARDING_WHILE_ROAMING: + return "DIALED_CALL_FORWARDING_WHILE_ROAMING"; + case IMEI_NOT_ACCEPTED: + return "IMEI_NOT_ACCEPTED"; + case WIFI_LOST: + return "WIFI_LOST"; + case IMS_ACCESS_BLOCKED: + return "IMS_ACCESS_BLOCKED"; + case LOW_BATTERY: + return "LOW_BATTERY"; + case DIAL_LOW_BATTERY: + return "DIAL_LOW_BATTERY"; + case EMERGENCY_TEMP_FAILURE: + return "EMERGENCY_TEMP_FAILURE"; + case EMERGENCY_PERM_FAILURE: + return "EMERGENCY_PERM_FAILURE"; + default: + return "INVALID: " + cause; + } + } +} diff --git a/android/telephony/IccOpenLogicalChannelResponse.java b/android/telephony/IccOpenLogicalChannelResponse.java new file mode 100644 index 00000000..4621f917 --- /dev/null +++ b/android/telephony/IccOpenLogicalChannelResponse.java @@ -0,0 +1,130 @@ +/* + * Copyright (C) 2014 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.telephony; + +import android.os.Parcel; +import android.os.Parcelable; + + +/** + * Response to the {@link TelephonyManager#iccOpenLogicalChannel} command. + */ +public class IccOpenLogicalChannelResponse implements Parcelable { + /** + * Indicates an invalid channel. + */ + public static final int INVALID_CHANNEL = -1; + + /** + * Possible status values returned by open channel command. + * + * STATUS_NO_ERROR: Open channel command returned successfully. + * STATUS_MISSING_RESOURCE: No logical channels available. + * STATUS_NO_SUCH_ELEMENT: AID not found on UICC. + * STATUS_UNKNOWN_ERROR: Unknown error in open channel command. + */ + public static final int STATUS_NO_ERROR = 1; + public static final int STATUS_MISSING_RESOURCE = 2; + public static final int STATUS_NO_SUCH_ELEMENT = 3; + public static final int STATUS_UNKNOWN_ERROR = 4; + + private final int mChannel; + private final int mStatus; + private final byte[] mSelectResponse; + + /** + * Constructor. + * + * @hide + */ + public IccOpenLogicalChannelResponse(int channel, int status, byte[] selectResponse) { + mChannel = channel; + mStatus = status; + mSelectResponse = selectResponse; + } + + /** + * Construct a IccOpenLogicalChannelResponse from a given parcel. + */ + private IccOpenLogicalChannelResponse(Parcel in) { + mChannel = in.readInt(); + mStatus = in.readInt(); + int arrayLength = in.readInt(); + if (arrayLength > 0) { + mSelectResponse = new byte[arrayLength]; + in.readByteArray(mSelectResponse); + } else { + mSelectResponse = null; + } + } + + /** + * @return the channel id. + */ + public int getChannel() { + return mChannel; + } + + /** + * @return the status of the command. + */ + public int getStatus() { + return mStatus; + } + + /** + * @return the select response. + */ + public byte[] getSelectResponse() { + return mSelectResponse; + } + + @Override + public int describeContents() { + return 0; + } + + @Override + public void writeToParcel(Parcel out, int flags) { + out.writeInt(mChannel); + out.writeInt(mStatus); + if (mSelectResponse != null && mSelectResponse.length > 0) { + out.writeInt(mSelectResponse.length); + out.writeByteArray(mSelectResponse); + } else { + out.writeInt(0); + } + } + + public static final Parcelable.Creator<IccOpenLogicalChannelResponse> CREATOR + = new Parcelable.Creator<IccOpenLogicalChannelResponse>() { + + @Override + public IccOpenLogicalChannelResponse createFromParcel(Parcel in) { + return new IccOpenLogicalChannelResponse(in); + } + + public IccOpenLogicalChannelResponse[] newArray(int size) { + return new IccOpenLogicalChannelResponse[size]; + } + }; + + @Override + public String toString() { + return "Channel: " + mChannel + " Status: " + mStatus; + } +} diff --git a/android/telephony/ImsiEncryptionInfo.java b/android/telephony/ImsiEncryptionInfo.java new file mode 100644 index 00000000..d2680ad6 --- /dev/null +++ b/android/telephony/ImsiEncryptionInfo.java @@ -0,0 +1,154 @@ +/* + * Copyright (C) 2017 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.telephony; + +import android.os.Parcel; +import android.os.Parcelable; +import java.util.Date; +import android.util.Log; + +import java.security.KeyFactory; +import java.security.NoSuchAlgorithmException; +import java.security.PublicKey; +import java.security.spec.InvalidKeySpecException; +import java.security.spec.X509EncodedKeySpec; + +/** + * Class to represent information sent by the carrier, which will be used to encrypt + * the IMSI + IMPI. The ecryption is being done by WLAN, and the modem. + * + * @hide + */ +public final class ImsiEncryptionInfo implements Parcelable { + + private static final String LOG_TAG = "ImsiEncryptionInfo"; + + + private final String mcc; + private final String mnc; + private final PublicKey publicKey; + private final String keyIdentifier; + private final int keyType; + //Date-Time in UTC when the key will expire. + private final Date expirationTime; + + public ImsiEncryptionInfo(String mcc, String mnc, int keyType, String keyIdentifier, + byte[] key, Date expirationTime) { + this(mcc, mnc, keyType, keyIdentifier, makeKeyObject(key), expirationTime); + } + + public ImsiEncryptionInfo(String mcc, String mnc, int keyType, String keyIdentifier, + PublicKey publicKey, Date expirationTime) { + // todo need to validate that ImsiEncryptionInfo is being created with the correct params. + // Including validating that the public key is in "X.509" format. This will be done in + // a subsequent CL. + this.mcc = mcc; + this.mnc = mnc; + this.keyType = keyType; + this.publicKey = publicKey; + this.keyIdentifier = keyIdentifier; + this.expirationTime = expirationTime; + } + + public ImsiEncryptionInfo(Parcel in) { + int length = in.readInt(); + byte b[] = new byte[length]; + in.readByteArray(b); + publicKey = makeKeyObject(b); + mcc = in.readString(); + mnc = in.readString(); + keyIdentifier = in.readString(); + keyType = in.readInt(); + expirationTime = new Date(in.readLong()); + } + + public String getMnc() { + return this.mnc; + } + + public String getMcc() { + return this.mcc; + } + + public String getKeyIdentifier() { + return this.keyIdentifier; + } + + public int getKeyType() { + return this.keyType; + } + + public PublicKey getPublicKey() { + return this.publicKey; + } + + public Date getExpirationTime() { + return this.expirationTime; + } + + private static PublicKey makeKeyObject(byte[] publicKeyBytes) { + try { + X509EncodedKeySpec pubKeySpec = new X509EncodedKeySpec(publicKeyBytes); + return KeyFactory.getInstance("RSA").generatePublic(pubKeySpec); + } catch (InvalidKeySpecException | NoSuchAlgorithmException ex) { + Log.e(LOG_TAG, "Error makeKeyObject: unable to convert into PublicKey", ex); + } + throw new IllegalArgumentException(); + } + + /** Implement the Parcelable interface */ + @Override + public int describeContents() { + return 0; + } + + public static final Parcelable.Creator<ImsiEncryptionInfo> CREATOR = + new Parcelable.Creator<ImsiEncryptionInfo>() { + @Override + public ImsiEncryptionInfo createFromParcel(Parcel in) { + return new ImsiEncryptionInfo(in); + } + + @Override + public ImsiEncryptionInfo[] newArray(int size) { + return new ImsiEncryptionInfo[size]; + } + }; + + @Override + public void writeToParcel(Parcel dest, int flags) { + byte[] b = publicKey.getEncoded(); + dest.writeInt(b.length); + dest.writeByteArray(b); + dest.writeString(mcc); + dest.writeString(mnc); + dest.writeString(keyIdentifier); + dest.writeInt(keyType); + dest.writeLong(expirationTime.getTime()); + } + + @Override + public String toString(){ + return "[ImsiEncryptionInfo " + + "mcc=" + mcc + + "mnc=" + mnc + + "publicKey=" + publicKey + + ", keyIdentifier=" + keyIdentifier + + ", keyType=" + keyType + + ", expirationTime=" + expirationTime + + "]"; + } +} diff --git a/android/telephony/JapanesePhoneNumberFormatter.java b/android/telephony/JapanesePhoneNumberFormatter.java new file mode 100644 index 00000000..f5e53ef1 --- /dev/null +++ b/android/telephony/JapanesePhoneNumberFormatter.java @@ -0,0 +1,218 @@ +/* + * Copyright (C) 2008 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.telephony; + +import android.text.Editable; + +/* + * Japanese Phone number formatting rule is a bit complicated. + * Here are some valid examples: + * + * 022-229-1234 0223-23-1234 022-301-9876 015-482-7849 0154-91-3478 + * 01547-5-4534 090-1234-1234 080-0123-6789 + * 050-0000-0000 060-0000-0000 + * 0800-000-9999 0570-000-000 0276-00-0000 + * + * As you can see, there is no straight-forward rule here. + * In order to handle this, a big array is prepared. + */ +/* package */ class JapanesePhoneNumberFormatter { + private static short FORMAT_MAP[] = { + -100, 10, 220, -15, 410, 530, 1200, 670, 780, 1060, + -100, -25, 20, 40, 70, 100, 150, 190, 200, 210, + -36, -100, -100, -35, -35, -35, 30, -100, -100, -100, + -35, -35, -35, -35, -35, -35, -35, -45, -35, -35, + -100, -100, -100, -35, -35, -35, -35, 50, -35, 60, + -35, -35, -45, -35, -45, -35, -35, -45, -35, -35, + -35, -35, -45, -35, -35, -35, -35, -45, -45, -35, + -100, -100, -35, -35, -35, 80, 90, -100, -100, -100, + -35, -35, -35, -35, -35, -35, -45, -45, -35, -35, + -35, -35, -35, -35, -35, -35, -45, -35, -35, -35, + -25, -25, -35, -35, 110, 120, 130, -35, 140, -25, + -35, -25, -35, -35, -35, -35, -35, -45, -25, -35, + -35, -25, -35, -35, -35, -35, -35, -25, -45, -35, + -35, -35, -35, -35, -45, -35, -35, -35, -35, -35, + -35, -35, -35, -35, -35, -35, -45, -45, -35, -35, + -100, -100, -35, 160, 170, 180, -35, -35, -100, -100, + -35, -35, -45, -35, -45, -45, -35, -35, -35, -35, + -35, -35, -35, -35, -35, -35, -35, -35, -45, -35, + -35, -35, -35, -35, -45, -45, -45, -35, -45, -35, + -25, -25, -35, -35, -35, -35, -35, -25, -35, -35, + -25, -25, -35, -35, -35, -35, -35, -35, -25, -25, + -25, -35, -35, -35, -35, -35, -25, -35, -35, -25, + -100, -100, 230, 250, 260, 270, 320, 340, 360, 390, + -35, -25, -25, 240, -35, -35, -35, -25, -35, -35, + -25, -35, -35, -35, -25, -25, -25, -25, -25, -25, + -25, -25, -25, -35, -35, -35, -25, -35, -35, -25, + -35, -35, -35, -35, -35, -25, -35, -35, -35, -25, + -35, -25, -25, -25, -35, 280, 290, 300, 310, -35, + -25, -25, -25, -25, -25, -25, -25, -35, -35, -25, + -25, -35, -35, -35, -35, -35, -35, -35, -35, -35, + -25, -25, -35, -35, -35, -25, -25, -25, -25, -25, + -25, -35, -35, -35, -35, -35, -35, -35, -35, -35, + -35, -35, -25, -35, 330, -35, -35, -35, -35, -35, + -25, -35, -35, -35, -35, -35, -25, -25, -25, -25, + -35, -25, -25, -25, -35, -25, -35, -35, 350, -35, + -25, -35, -35, -35, -35, -35, -35, -35, -25, -25, + -35, -25, -35, 370, -35, -35, -25, -35, -35, 380, + -25, -35, -35, -25, -25, -35, -35, -35, -35, -35, + -25, -35, -25, -25, -25, -25, -35, -35, -35, -35, + -25, -35, -25, 400, -35, -35, -35, -35, -25, -35, + -25, -35, -35, -35, -35, -25, -25, -25, -25, -25, + -15, -15, 420, 460, -25, -25, 470, 480, 500, 510, + -15, -25, 430, -25, -25, -25, -25, -25, 440, 450, + -25, -35, -35, -35, -35, -35, -35, -35, -35, -35, + -25, -25, -35, -35, -25, -25, -25, -35, -35, -35, + -15, -25, -15, -15, -15, -15, -15, -25, -25, -15, + -25, -25, -25, -25, -25, -25, -35, -25, -35, -35, + -35, -25, -25, -35, -25, -35, -35, -35, -25, -25, + 490, -15, -25, -25, -25, -35, -35, -25, -35, -35, + -15, -35, -35, -35, -35, -35, -35, -35, -35, -15, + -35, -25, -25, -25, -25, -25, -25, -25, -25, -25, + -25, -25, -25, -35, -35, -35, -25, -25, -25, 520, + -100, -100, -45, -100, -45, -100, -45, -100, -45, -100, + -26, -100, -25, 540, 580, 590, 600, 610, 630, 640, + -25, -35, -35, -35, -25, -25, -35, -35, -35, 550, + -35, -35, -25, -25, -25, -25, 560, 570, -25, -35, + -35, -35, -35, -35, -25, -25, -25, -25, -25, -25, + -25, -25, -25, -25, -35, -25, -25, -35, -25, -25, + -25, -25, -25, -25, -35, -35, -25, -35, -35, -25, + -35, -35, -25, -35, -35, -35, -35, -35, -35, -25, + -100, -35, -35, -35, -35, -35, -35, -35, -35, -35, + -36, -100, -35, -35, -35, -35, 620, -35, -35, -100, + -35, -35, -35, -35, -35, -35, -35, -35, -35, -45, + -25, -35, -25, -25, -35, -35, -35, -35, -25, -25, + -25, -25, -25, -25, -35, -35, -35, 650, -35, 660, + -35, -35, -35, -35, -45, -35, -35, -35, -35, -45, + -35, -35, -35, -35, -35, -35, -35, -35, -35, -25, + -26, -100, 680, 690, 700, -25, 720, 730, -25, 740, + -25, -35, -25, -25, -25, -35, -25, -25, -25, -25, + -25, -25, -25, -25, -25, -35, -35, -35, -35, -35, + -35, -100, -35, -35, -35, -35, 710, -35, -35, -35, + -35, -35, -35, -35, -35, -35, -35, -35, -45, -35, + -25, -35, -25, -35, -25, -35, -35, -35, -35, -25, + -35, -35, -35, -35, -35, -25, -35, -25, -35, -35, + -35, -35, -25, -25, 750, 760, 770, -35, -35, -35, + -25, -35, -25, -25, -25, -25, -35, -35, -35, -25, + -25, -35, -35, -35, -35, -25, -25, -35, -35, -25, + -25, -35, -35, -35, -35, -35, -25, -25, -35, -35, + 790, -100, 800, 850, 900, 920, 940, 1030, 1040, 1050, + -36, -26, -26, -26, -26, -26, -26, -26, -26, -26, + -35, -25, -25, -35, 810, -25, -35, -35, -25, 820, + -25, -35, -25, -25, -35, -35, -35, -35, -35, -25, + -25, -35, 830, -35, 840, -35, -25, -35, -35, -25, + -35, -25, -25, -25, -25, -25, -25, -25, -25, -25, + -100, -25, -25, -25, -100, -100, -100, -100, -100, -100, + -25, -25, -35, -35, -35, -35, 860, -35, 870, 880, + -25, -35, -35, -35, -35, -35, -35, -35, -35, -35, + -35, -35, -35, -35, -35, -35, -35, -45, -45, -35, + -100, -100, -100, -100, -100, -100, 890, -100, -100, -100, + -25, -45, -45, -25, -45, -45, -25, -45, -45, -45, + -25, -25, -25, -25, -25, -35, -35, 910, -35, -25, + -35, -35, -35, -35, -35, -35, -35, -45, -35, -35, + -100, 930, -35, -35, -35, -35, -35, -35, -35, -35, + -100, -100, -45, -100, -45, -100, -100, -100, -100, -100, + -25, -25, -25, 950, -25, 970, 990, -35, 1000, 1010, + -35, -35, -35, -35, -35, -35, 960, -35, -35, -35, + -45, -45, -45, -45, -45, -45, -35, -45, -45, -45, + -35, -35, -25, -35, -35, 980, -35, -35, -35, -35, + -100, -100, -25, -25, -100, -100, -100, -100, -100, -100, + -25, -35, -35, -35, -35, -35, -35, -35, -35, -35, + -25, -35, -35, -35, -35, -35, -35, -35, -35, -25, + -25, -35, -35, -35, -25, -25, -35, -35, -35, 1020, + -45, -45, -35, -35, -45, -45, -45, -45, -45, -45, + -25, -25, -25, -25, -25, -35, -25, -35, -25, -35, + -35, -25, -25, -35, -35, -35, -25, -35, -25, -35, + -25, -25, -35, -35, -35, -35, -35, -35, -35, -25, + -26, -100, 1070, 1080, 1090, 1110, 1120, 1130, 1140, 1160, + -35, -25, -25, -25, -25, -25, -25, -25, -25, -25, + -35, -25, -25, -25, -25, -25, -25, -25, -25, -25, + -35, -100, -35, -35, -35, -100, -35, -35, -35, 1100, + -35, -35, -35, -35, -35, -35, -45, -35, -35, -35, + -35, -25, -35, -25, -35, -35, -35, -35, -25, -35, + -25, -25, -25, -25, -35, -35, -35, -35, -35, -35, + -25, -25, -35, -35, -35, -25, -25, -35, -35, -35, + 1150, -25, -35, -35, -35, -35, -35, -35, -25, -25, + -35, -35, -45, -35, -35, -35, -35, -35, -35, -35, + -35, 1170, -25, -35, 1180, -35, 1190, -35, -25, -25, + -100, -100, -45, -45, -100, -100, -100, -100, -100, -100, + -25, -35, -35, -35, -35, -35, -35, -25, -25, -35, + -35, -35, -35, -35, -35, -35, -35, -35, -35, -45, + -26, -15, -15, -15, -15, -15, -15, -15, -15, -15}; + + public static void format(Editable text) { + // Here, "root" means the position of "'": + // 0'3, 0'90, and +81'-90 + // (dash will be deleted soon, so it is actually +81'90). + int rootIndex = 1; + int length = text.length(); + if (length > 3 + && text.subSequence(0, 3).toString().equals("+81")) { + rootIndex = 3; + } else if (length < 1 || text.charAt(0) != '0') { + return; + } + + CharSequence saved = text.subSequence(0, length); + + // Strip the dashes first, as we're going to add them back + int i = 0; + while (i < text.length()) { + if (text.charAt(i) == '-') { + text.delete(i, i + 1); + } else { + i++; + } + } + + length = text.length(); + int dashposition; + + i = rootIndex; + int base = 0; + while (i < length) { + char ch = text.charAt(i); + if (!Character.isDigit(ch)) { + text.replace(0, length, saved); + return; + } + short value = FORMAT_MAP[base + ch - '0']; + if (value < 0) { + if (value <= -100) { + text.replace(0, length, saved); + return; + } + int dashPos2 = rootIndex + (Math.abs(value) % 10); + if (length > dashPos2) { + text.insert(dashPos2, "-"); + } + int dashPos1 = rootIndex + (Math.abs(value) / 10); + if (length > dashPos1) { + text.insert(dashPos1, "-"); + } + break; + } else { + base = value; + i++; + } + } + + if (length > 3 && rootIndex == 3) { + text.insert(rootIndex, "-"); + } + } +}
\ No newline at end of file diff --git a/android/telephony/MbmsDownloadManager.java b/android/telephony/MbmsDownloadManager.java new file mode 100644 index 00000000..1e8cf185 --- /dev/null +++ b/android/telephony/MbmsDownloadManager.java @@ -0,0 +1,586 @@ +/* + * 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.telephony; + +import android.annotation.IntDef; +import android.annotation.NonNull; +import android.annotation.Nullable; +import android.annotation.SdkConstant; +import android.content.ComponentName; +import android.content.Context; +import android.content.Intent; +import android.content.ServiceConnection; +import android.content.SharedPreferences; +import android.net.Uri; +import android.os.IBinder; +import android.os.RemoteException; +import android.telephony.mbms.DownloadProgressListener; +import android.telephony.mbms.FileInfo; +import android.telephony.mbms.DownloadRequest; +import android.telephony.mbms.MbmsDownloadManagerCallback; +import android.telephony.mbms.MbmsDownloadReceiver; +import android.telephony.mbms.MbmsException; +import android.telephony.mbms.MbmsTempFileProvider; +import android.telephony.mbms.MbmsUtils; +import android.telephony.mbms.vendor.IMbmsDownloadService; +import android.util.Log; + +import java.io.File; +import java.io.IOException; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.util.List; +import java.util.concurrent.atomic.AtomicBoolean; +import java.util.concurrent.atomic.AtomicReference; + +import static android.telephony.SubscriptionManager.INVALID_SUBSCRIPTION_ID; + +/** @hide */ +public class MbmsDownloadManager { + private static final String LOG_TAG = MbmsDownloadManager.class.getSimpleName(); + + /** @hide */ + // TODO: systemapi + @SdkConstant(SdkConstant.SdkConstantType.SERVICE_ACTION) + public static final String MBMS_DOWNLOAD_SERVICE_ACTION = + "android.telephony.action.EmbmsDownload"; + + /** + * Integer extra indicating the result code of the download. One of + * {@link #RESULT_SUCCESSFUL}, {@link #RESULT_EXPIRED}, or {@link #RESULT_CANCELLED}. + */ + public static final String EXTRA_RESULT = "android.telephony.mbms.extra.RESULT"; + + /** + * Extra containing the {@link android.telephony.mbms.FileInfo} for which the download result + * is for. Must not be null. + */ + public static final String EXTRA_FILE_INFO = "android.telephony.mbms.extra.FILE_INFO"; + + /** + * Extra containing a single {@link Uri} indicating the location of the successfully + * downloaded file. Set on the intent provided via + * {@link android.telephony.mbms.DownloadRequest.Builder#setAppIntent(Intent)}. + * Will always be set to a non-null value if {@link #EXTRA_RESULT} is set to + * {@link #RESULT_SUCCESSFUL}. + */ + public static final String EXTRA_COMPLETED_FILE_URI = + "android.telephony.mbms.extra.COMPLETED_FILE_URI"; + + public static final int RESULT_SUCCESSFUL = 1; + public static final int RESULT_CANCELLED = 2; + public static final int RESULT_EXPIRED = 3; + public static final int RESULT_IO_ERROR = 4; + // TODO - more results! + + /** @hide */ + @Retention(RetentionPolicy.SOURCE) + @IntDef({STATUS_UNKNOWN, STATUS_ACTIVELY_DOWNLOADING, STATUS_PENDING_DOWNLOAD, + STATUS_PENDING_REPAIR, STATUS_PENDING_DOWNLOAD_WINDOW}) + public @interface DownloadStatus {} + + public static final int STATUS_UNKNOWN = 0; + public static final int STATUS_ACTIVELY_DOWNLOADING = 1; + public static final int STATUS_PENDING_DOWNLOAD = 2; + public static final int STATUS_PENDING_REPAIR = 3; + public static final int STATUS_PENDING_DOWNLOAD_WINDOW = 4; + + private static AtomicBoolean sIsInitialized = new AtomicBoolean(false); + + private final Context mContext; + private int mSubscriptionId = INVALID_SUBSCRIPTION_ID; + private IBinder.DeathRecipient mDeathRecipient = new IBinder.DeathRecipient() { + @Override + public void binderDied() { + sendErrorToApp(MbmsException.ERROR_MIDDLEWARE_LOST, "Received death notification"); + } + }; + + private AtomicReference<IMbmsDownloadService> mService = new AtomicReference<>(null); + private final MbmsDownloadManagerCallback mCallback; + + private MbmsDownloadManager(Context context, MbmsDownloadManagerCallback callback, int subId) { + mContext = context; + mCallback = callback; + mSubscriptionId = subId; + } + + /** + * Create a new MbmsDownloadManager using the system default data subscription ID. + * See {@link #create(Context, MbmsDownloadManagerCallback, int)} + * + * @hide + */ + public static MbmsDownloadManager create(Context context, + MbmsDownloadManagerCallback listener) + throws MbmsException { + return create(context, listener, SubscriptionManager.getDefaultSubscriptionId()); + } + + /** + * Create a new MbmsDownloadManager using the given subscription ID. + * + * Note that this call will bind a remote service and that may take a bit. The instance of + * {@link MbmsDownloadManager} that is returned will not be ready for use until + * {@link MbmsDownloadManagerCallback#middlewareReady()} is called on the provided callback. + * If you attempt to use the manager before it is ready, a {@link MbmsException} will be thrown. + * + * This also may throw an {@link IllegalArgumentException} or an {@link IllegalStateException}. + * + * You may only have one instance of {@link MbmsDownloadManager} per UID. If you call this + * method while there is an active instance of {@link MbmsDownloadManager} in your process + * (in other words, one that has not had {@link #dispose()} called on it), this method will + * throw an {@link MbmsException}. If you call this method in a different process + * running under the same UID, an error will be indicated via + * {@link MbmsDownloadManagerCallback#error(int, String)}. + * + * Note that initialization may fail asynchronously. If you wish to try again after you + * receive such an asynchronous error, you must call dispose() on the instance of + * {@link MbmsDownloadManager} that you received before calling this method again. + * + * @param context The instance of {@link Context} to use + * @param listener A callback to get asynchronous error messages and file service updates. + * @param subscriptionId The data subscription ID to use + * @hide + */ + public static MbmsDownloadManager create(Context context, + MbmsDownloadManagerCallback listener, int subscriptionId) + throws MbmsException { + if (!sIsInitialized.compareAndSet(false, true)) { + throw new MbmsException(MbmsException.InitializationErrors.ERROR_DUPLICATE_INITIALIZE); + } + MbmsDownloadManager mdm = new MbmsDownloadManager(context, listener, subscriptionId); + try { + mdm.bindAndInitialize(); + } catch (MbmsException e) { + sIsInitialized.set(false); + throw e; + } + return mdm; + } + + private void bindAndInitialize() throws MbmsException { + MbmsUtils.startBinding(mContext, MBMS_DOWNLOAD_SERVICE_ACTION, + new ServiceConnection() { + @Override + public void onServiceConnected(ComponentName name, IBinder service) { + IMbmsDownloadService downloadService = + IMbmsDownloadService.Stub.asInterface(service); + int result; + try { + result = downloadService.initialize(mSubscriptionId, mCallback); + } catch (RemoteException e) { + Log.e(LOG_TAG, "Service died before initialization"); + sIsInitialized.set(false); + return; + } catch (RuntimeException e) { + Log.e(LOG_TAG, "Runtime exception during initialization"); + sendErrorToApp( + MbmsException.InitializationErrors.ERROR_UNABLE_TO_INITIALIZE, + e.toString()); + sIsInitialized.set(false); + return; + } + if (result != MbmsException.SUCCESS) { + sendErrorToApp(result, "Error returned during initialization"); + sIsInitialized.set(false); + return; + } + try { + downloadService.asBinder().linkToDeath(mDeathRecipient, 0); + } catch (RemoteException e) { + sendErrorToApp(MbmsException.ERROR_MIDDLEWARE_LOST, + "Middleware lost during initialization"); + sIsInitialized.set(false); + return; + } + mService.set(downloadService); + } + + @Override + public void onServiceDisconnected(ComponentName name) { + sIsInitialized.set(false); + mService.set(null); + } + }); + } + + /** + * An inspection API to retrieve the list of available + * {@link android.telephony.mbms.FileServiceInfo}s currently being advertised. + * The results are returned asynchronously via a call to + * {@link MbmsDownloadManagerCallback#fileServicesUpdated(List)} + * + * The serviceClasses argument lets the app filter on types of programming and is opaque data + * negotiated beforehand between the app and the carrier. + * + * This may throw an {@link MbmsException} containing one of the following errors: + * {@link MbmsException#ERROR_MIDDLEWARE_NOT_BOUND} + * {@link MbmsException#ERROR_MIDDLEWARE_LOST} + * + * Asynchronous error codes via the {@link MbmsDownloadManagerCallback#error(int, String)} + * callback can include any of the errors except: + * {@link MbmsException.StreamingErrors#ERROR_UNABLE_TO_START_SERVICE} + * + * @param classList A list of service classes which the app wishes to receive + * {@link MbmsDownloadManagerCallback#fileServicesUpdated(List)} callbacks + * about. Subsequent calls to this method will replace this list of service + * classes (i.e. the middleware will no longer send updates for services + * matching classes only in the old list). + */ + public void getFileServices(List<String> classList) throws MbmsException { + IMbmsDownloadService downloadService = mService.get(); + if (downloadService == null) { + throw new MbmsException(MbmsException.ERROR_MIDDLEWARE_NOT_BOUND); + } + try { + int returnCode = downloadService.getFileServices(mSubscriptionId, classList); + if (returnCode != MbmsException.SUCCESS) { + throw new MbmsException(returnCode); + } + } catch (RemoteException e) { + Log.w(LOG_TAG, "Remote process died"); + mService.set(null); + throw new MbmsException(MbmsException.ERROR_MIDDLEWARE_LOST); + } + } + + /** + * Sets the temp file root for downloads. + * All temp files created for the middleware to write to will be contained in the specified + * directory. Applications that wish to specify a location only need to call this method once + * as long their data is persisted in storage -- the argument will be stored both in a + * local instance of {@link android.content.SharedPreferences} and by the middleware. + * + * If this method is not called at least once before calling + * {@link #download(DownloadRequest, DownloadProgressListener)}, the framework + * will default to a directory formed by the concatenation of the app's files directory and + * {@link android.telephony.mbms.MbmsTempFileProvider#DEFAULT_TOP_LEVEL_TEMP_DIRECTORY}. + * + * Before calling this method, the app must cancel all of its pending + * {@link DownloadRequest}s via {@link #cancelDownload(DownloadRequest)}. If this is not done, + * an {@link MbmsException} will be thrown with code + * {@link MbmsException.DownloadErrors#ERROR_CANNOT_CHANGE_TEMP_FILE_ROOT} unless the + * provided directory is the same as what has been previously configured. + * + * The {@link File} supplied as a root temp file directory must already exist. If not, an + * {@link IllegalArgumentException} will be thrown. + * @param tempFileRootDirectory A directory to place temp files in. + */ + public void setTempFileRootDirectory(@NonNull File tempFileRootDirectory) + throws MbmsException { + IMbmsDownloadService downloadService = mService.get(); + if (downloadService == null) { + throw new MbmsException(MbmsException.ERROR_MIDDLEWARE_NOT_BOUND); + } + if (!tempFileRootDirectory.exists()) { + throw new IllegalArgumentException("Provided directory does not exist"); + } + if (!tempFileRootDirectory.isDirectory()) { + throw new IllegalArgumentException("Provided File is not a directory"); + } + String filePath; + try { + filePath = tempFileRootDirectory.getCanonicalPath(); + } catch (IOException e) { + throw new IllegalArgumentException("Unable to canonicalize the provided path: " + e); + } + + try { + int result = downloadService.setTempFileRootDirectory(mSubscriptionId, filePath); + if (result != MbmsException.SUCCESS) { + throw new MbmsException(result); + } + } catch (RemoteException e) { + mService.set(null); + throw new MbmsException(MbmsException.ERROR_MIDDLEWARE_LOST); + } + + SharedPreferences prefs = mContext.getSharedPreferences( + MbmsTempFileProvider.TEMP_FILE_ROOT_PREF_FILE_NAME, 0); + prefs.edit().putString(MbmsTempFileProvider.TEMP_FILE_ROOT_PREF_NAME, filePath).apply(); + } + + /** + * Retrieves the currently configured temp file root directory. Returns the file that was + * configured via {@link #setTempFileRootDirectory(File)} or the default directory + * {@link #download(DownloadRequest, DownloadProgressListener)} was called without ever setting + * the temp file root. If neither method has been called since the last time the app's shared + * preferences were reset, returns null. + * + * @return A {@link File} pointing to the configured temp file directory, or null if not yet + * configured. + */ + public @Nullable File getTempFileRootDirectory() { + SharedPreferences prefs = mContext.getSharedPreferences( + MbmsTempFileProvider.TEMP_FILE_ROOT_PREF_FILE_NAME, 0); + String path = prefs.getString(MbmsTempFileProvider.TEMP_FILE_ROOT_PREF_NAME, null); + if (path != null) { + return new File(path); + } + return null; + } + + /** + * Requests a download of a file that is available via multicast. + * + * downloadListener is an optional callback object which can be used to get progress reports + * of a currently occuring download. Note this can only run while the calling app + * is running, so future downloads will simply result in resultIntents being sent + * for completed or errored-out downloads. A NULL indicates no callbacks are needed. + * + * May throw an {@link IllegalArgumentException} + * + * If {@link #setTempFileRootDirectory(File)} has not called after the app has been installed, + * this method will create a directory at the default location defined at + * {@link MbmsTempFileProvider#DEFAULT_TOP_LEVEL_TEMP_DIRECTORY} and store that as the temp + * file root directory. + * + * Asynchronous errors through the listener include any of the errors + * + * @param request The request that specifies what should be downloaded + * @param progressListener Optional listener that will be provided progress updates + * if the app is running. + */ + public void download(DownloadRequest request, DownloadProgressListener progressListener) + throws MbmsException { + IMbmsDownloadService downloadService = mService.get(); + if (downloadService == null) { + throw new MbmsException(MbmsException.ERROR_MIDDLEWARE_NOT_BOUND); + } + + // Check to see whether the app's set a temp root dir yet, and set it if not. + SharedPreferences prefs = mContext.getSharedPreferences( + MbmsTempFileProvider.TEMP_FILE_ROOT_PREF_FILE_NAME, 0); + if (prefs.getString(MbmsTempFileProvider.TEMP_FILE_ROOT_PREF_NAME, null) == null) { + File tempRootDirectory = new File(mContext.getFilesDir(), + MbmsTempFileProvider.DEFAULT_TOP_LEVEL_TEMP_DIRECTORY); + tempRootDirectory.mkdirs(); + setTempFileRootDirectory(tempRootDirectory); + } + + checkValidDownloadDestination(request); + writeDownloadRequestToken(request); + try { + downloadService.download(request, progressListener); + } catch (RemoteException e) { + mService.set(null); + throw new MbmsException(MbmsException.ERROR_MIDDLEWARE_LOST); + } + } + + /** + * Returns a list of pending {@link DownloadRequest}s that originated from this application. + * A pending request is one that was issued via + * {@link #download(DownloadRequest, DownloadProgressListener)} but not cancelled through + * {@link #cancelDownload(DownloadRequest)}. + * @return A list, possibly empty, of {@link DownloadRequest}s + */ + public @NonNull List<DownloadRequest> listPendingDownloads() throws MbmsException { + IMbmsDownloadService downloadService = mService.get(); + if (downloadService == null) { + throw new MbmsException(MbmsException.ERROR_MIDDLEWARE_NOT_BOUND); + } + + try { + return downloadService.listPendingDownloads(mSubscriptionId); + } catch (RemoteException e) { + mService.set(null); + throw new MbmsException(MbmsException.ERROR_MIDDLEWARE_LOST); + } + } + + /** + * Attempts to cancel the specified {@link DownloadRequest}. + * + * If the middleware is not aware of the specified download request, an MbmsException will be + * thrown with error code {@link MbmsException.DownloadErrors#ERROR_UNKNOWN_DOWNLOAD_REQUEST}. + * + * If this method returns without throwing an exception, you may assume that cancellation + * was successful. + * @param downloadRequest The download request that you wish to cancel. + */ + public void cancelDownload(DownloadRequest downloadRequest) throws MbmsException { + IMbmsDownloadService downloadService = mService.get(); + if (downloadService == null) { + throw new MbmsException(MbmsException.ERROR_MIDDLEWARE_NOT_BOUND); + } + + try { + int result = downloadService.cancelDownload(downloadRequest); + if (result != MbmsException.SUCCESS) { + throw new MbmsException(result); + } + } catch (RemoteException e) { + mService.set(null); + throw new MbmsException(MbmsException.ERROR_MIDDLEWARE_LOST); + } + deleteDownloadRequestToken(downloadRequest); + } + + /** + * Gets information about the status of a file pending download. + * + * If the middleware has not yet been properly initialized or if it has no records of the + * file indicated by {@code fileInfo} being associated with {@code downloadRequest}, + * {@link #STATUS_UNKNOWN} will be returned. + * + * @param downloadRequest The download request to query. + * @param fileInfo The particular file within the request to get information on. + * @return The status of the download. + */ + @DownloadStatus + public int getDownloadStatus(DownloadRequest downloadRequest, FileInfo fileInfo) + throws MbmsException { + IMbmsDownloadService downloadService = mService.get(); + if (downloadService == null) { + throw new MbmsException(MbmsException.ERROR_MIDDLEWARE_NOT_BOUND); + } + + try { + return downloadService.getDownloadStatus(downloadRequest, fileInfo); + } catch (RemoteException e) { + mService.set(null); + throw new MbmsException(MbmsException.ERROR_MIDDLEWARE_LOST); + } + } + + /** + * Resets the middleware's knowledge of previously-downloaded files in this download request. + * + * Normally, the middleware keeps track of the hashes of downloaded files and won't re-download + * files whose server-reported hash matches one of the already-downloaded files. This means + * that if the file is accidentally deleted by the user or by the app, the middleware will + * not try to download it again. + * This method will reset the middleware's cache of hashes for the provided + * {@link DownloadRequest}, so that previously downloaded content will be downloaded again + * when available. + * This will not interrupt in-progress downloads. + * + * If the middleware is not aware of the specified download request, an MbmsException will be + * thrown with error code {@link MbmsException.DownloadErrors#ERROR_UNKNOWN_DOWNLOAD_REQUEST}. + * + * May throw a {@link MbmsException} with error code + * @param downloadRequest The request to re-download files for. + */ + public void resetDownloadKnowledge(DownloadRequest downloadRequest) throws MbmsException { + IMbmsDownloadService downloadService = mService.get(); + if (downloadService == null) { + throw new MbmsException(MbmsException.ERROR_MIDDLEWARE_NOT_BOUND); + } + + try { + int result = downloadService.resetDownloadKnowledge(downloadRequest); + if (result != MbmsException.SUCCESS) { + throw new MbmsException(result); + } + } catch (RemoteException e) { + mService.set(null); + throw new MbmsException(MbmsException.ERROR_MIDDLEWARE_LOST); + } + } + + public void dispose() { + try { + IMbmsDownloadService downloadService = mService.get(); + if (downloadService == null) { + Log.i(LOG_TAG, "Service already dead"); + return; + } + downloadService.dispose(mSubscriptionId); + } catch (RemoteException e) { + // Ignore + Log.i(LOG_TAG, "Remote exception while disposing of service"); + } finally { + mService.set(null); + sIsInitialized.set(false); + } + } + + private void writeDownloadRequestToken(DownloadRequest request) { + File token = getDownloadRequestTokenPath(request); + if (!token.getParentFile().exists()) { + token.getParentFile().mkdirs(); + } + if (token.exists()) { + Log.w(LOG_TAG, "Download token " + token.getName() + " already exists"); + return; + } + try { + if (!token.createNewFile()) { + throw new RuntimeException("Failed to create download token for request " + + request); + } + } catch (IOException e) { + throw new RuntimeException("Failed to create download token for request " + request + + " due to IOException " + e); + } + } + + private void deleteDownloadRequestToken(DownloadRequest request) { + File token = getDownloadRequestTokenPath(request); + if (!token.isFile()) { + Log.w(LOG_TAG, "Attempting to delete non-existent download token at " + token); + return; + } + if (!token.delete()) { + Log.w(LOG_TAG, "Couldn't delete download token at " + token); + } + } + + private File getDownloadRequestTokenPath(DownloadRequest request) { + File tempFileLocation = MbmsUtils.getEmbmsTempFileDirForService(mContext, + request.getFileServiceId()); + String downloadTokenFileName = request.getHash() + + MbmsDownloadReceiver.DOWNLOAD_TOKEN_SUFFIX; + return new File(tempFileLocation, downloadTokenFileName); + } + + /** + * Verifies the following: + * If a request is multi-part, + * 1. Destination Uri must exist and be a directory + * 2. Directory specified must contain no files. + * Otherwise + * 1. The file specified by the destination Uri must not exist. + */ + private void checkValidDownloadDestination(DownloadRequest request) { + File toFile = new File(request.getDestinationUri().getSchemeSpecificPart()); + if (request.isMultipartDownload()) { + if (!toFile.isDirectory()) { + throw new IllegalArgumentException("Multipart download must specify valid " + + "destination directory."); + } + if (toFile.listFiles().length > 0) { + throw new IllegalArgumentException("Destination directory must be clear of all " + + "files."); + } + } else { + if (toFile.exists()) { + throw new IllegalArgumentException("Destination file must not exist."); + } + } + } + + private void sendErrorToApp(int errorCode, String message) { + try { + mCallback.error(errorCode, message); + } catch (RemoteException e) { + // Ignore, should not happen locally. + } + } +} diff --git a/android/telephony/MbmsStreamingManager.java b/android/telephony/MbmsStreamingManager.java new file mode 100644 index 00000000..b6b253ec --- /dev/null +++ b/android/telephony/MbmsStreamingManager.java @@ -0,0 +1,323 @@ +/* + * 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.telephony; + +import android.annotation.SdkConstant; +import android.annotation.SystemApi; +import android.content.ComponentName; +import android.content.Context; +import android.content.ServiceConnection; +import android.os.Handler; +import android.os.IBinder; +import android.os.Looper; +import android.os.RemoteException; +import android.telephony.mbms.InternalStreamingManagerCallback; +import android.telephony.mbms.InternalStreamingServiceCallback; +import android.telephony.mbms.MbmsException; +import android.telephony.mbms.MbmsStreamingManagerCallback; +import android.telephony.mbms.MbmsUtils; +import android.telephony.mbms.StreamingService; +import android.telephony.mbms.StreamingServiceCallback; +import android.telephony.mbms.StreamingServiceInfo; +import android.telephony.mbms.vendor.IMbmsStreamingService; +import android.util.Log; + +import java.util.List; +import java.util.concurrent.atomic.AtomicBoolean; +import java.util.concurrent.atomic.AtomicReference; + +import static android.telephony.SubscriptionManager.INVALID_SUBSCRIPTION_ID; + +/** + * This class provides functionality for streaming media over MBMS. + */ +public class MbmsStreamingManager { + private static final String LOG_TAG = "MbmsStreamingManager"; + + /** + * Service action which must be handled by the middleware implementing the MBMS streaming + * interface. + * @hide + */ + @SystemApi + @SdkConstant(SdkConstant.SdkConstantType.SERVICE_ACTION) + public static final String MBMS_STREAMING_SERVICE_ACTION = + "android.telephony.action.EmbmsStreaming"; + + private static AtomicBoolean sIsInitialized = new AtomicBoolean(false); + + private AtomicReference<IMbmsStreamingService> mService = new AtomicReference<>(null); + private IBinder.DeathRecipient mDeathRecipient = new IBinder.DeathRecipient() { + @Override + public void binderDied() { + sIsInitialized.set(false); + sendErrorToApp(MbmsException.ERROR_MIDDLEWARE_LOST, "Received death notification"); + } + }; + + private InternalStreamingManagerCallback mInternalCallback; + + private final Context mContext; + private int mSubscriptionId = INVALID_SUBSCRIPTION_ID; + + /** @hide */ + private MbmsStreamingManager(Context context, MbmsStreamingManagerCallback callback, + int subscriptionId, Handler handler) { + mContext = context; + mSubscriptionId = subscriptionId; + if (handler == null) { + handler = new Handler(Looper.getMainLooper()); + } + mInternalCallback = new InternalStreamingManagerCallback(callback, handler); + } + + /** + * Create a new MbmsStreamingManager using the given subscription ID. + * + * Note that this call will bind a remote service. You may not call this method on your app's + * main thread. This may throw an {@link MbmsException}, indicating errors that may happen + * during the initialization or binding process. + * + * + * You may only have one instance of {@link MbmsStreamingManager} per UID. If you call this + * method while there is an active instance of {@link MbmsStreamingManager} in your process + * (in other words, one that has not had {@link #dispose()} called on it), this method will + * throw an {@link MbmsException}. If you call this method in a different process + * running under the same UID, an error will be indicated via + * {@link MbmsStreamingManagerCallback#onError(int, String)}. + * + * Note that initialization may fail asynchronously. If you wish to try again after you + * receive such an asynchronous error, you must call dispose() on the instance of + * {@link MbmsStreamingManager} that you received before calling this method again. + * + * @param context The {@link Context} to use. + * @param callback A callback object on which you wish to receive results of asynchronous + * operations. + * @param subscriptionId The subscription ID to use. + * @param handler The handler you wish to receive callbacks on. If null, callbacks will be + * processed on the main looper (in other words, the looper returned from + * {@link Looper#getMainLooper()}). + */ + public static MbmsStreamingManager create(Context context, + MbmsStreamingManagerCallback callback, int subscriptionId, Handler handler) + throws MbmsException { + if (!sIsInitialized.compareAndSet(false, true)) { + throw new MbmsException(MbmsException.InitializationErrors.ERROR_DUPLICATE_INITIALIZE); + } + MbmsStreamingManager manager = new MbmsStreamingManager(context, callback, + subscriptionId, handler); + try { + manager.bindAndInitialize(); + } catch (MbmsException e) { + sIsInitialized.set(false); + throw e; + } + return manager; + } + + /** + * Create a new MbmsStreamingManager using the system default data subscription ID. + * See {@link #create(Context, MbmsStreamingManagerCallback, int, Handler)}. + */ + public static MbmsStreamingManager create(Context context, + MbmsStreamingManagerCallback callback, Handler handler) + throws MbmsException { + return create(context, callback, SubscriptionManager.getDefaultSubscriptionId(), handler); + } + + /** + * Create a new MbmsStreamingManager using the system default data subscription ID and + * default {@link Handler}. + * See {@link #create(Context, MbmsStreamingManagerCallback, int, Handler)}. + */ + public static MbmsStreamingManager create(Context context, + MbmsStreamingManagerCallback callback) + throws MbmsException { + return create(context, callback, SubscriptionManager.getDefaultSubscriptionId(), null); + } + + /** + * Terminates this instance, ending calls to the registered listener. Also terminates + * any streaming services spawned from this instance. + * + * May throw an {@link IllegalStateException} + */ + public void dispose() { + try { + IMbmsStreamingService streamingService = mService.get(); + if (streamingService == null) { + // Ignore and return, assume already disposed. + return; + } + streamingService.dispose(mSubscriptionId); + } catch (RemoteException e) { + // Ignore for now + } finally { + mService.set(null); + sIsInitialized.set(false); + } + } + + /** + * An inspection API to retrieve the list of streaming media currently be advertised. + * The results are returned asynchronously through the previously registered callback. + * serviceClasses lets the app filter on types of programming and is opaque data between + * the app and the carrier. + * + * Multiple calls replace the list of serviceClasses of interest. + * + * This may throw an {@link MbmsException} containing any error in + * {@link android.telephony.mbms.MbmsException.GeneralErrors}, + * {@link MbmsException#ERROR_MIDDLEWARE_NOT_BOUND}, or + * {@link MbmsException#ERROR_MIDDLEWARE_LOST}. + * + * May also throw an unchecked {@link IllegalArgumentException} or an + * {@link IllegalStateException} + * + * @param classList A list of streaming service classes that the app would like updates on. + */ + public void getStreamingServices(List<String> classList) throws MbmsException { + IMbmsStreamingService streamingService = mService.get(); + if (streamingService == null) { + throw new MbmsException(MbmsException.ERROR_MIDDLEWARE_NOT_BOUND); + } + try { + int returnCode = streamingService.getStreamingServices(mSubscriptionId, classList); + if (returnCode != MbmsException.SUCCESS) { + throw new MbmsException(returnCode); + } + } catch (RemoteException e) { + Log.w(LOG_TAG, "Remote process died"); + mService.set(null); + sIsInitialized.set(false); + throw new MbmsException(MbmsException.ERROR_MIDDLEWARE_LOST); + } + } + + /** + * Starts streaming a requested service, reporting status to the indicated callback. + * Returns an object used to control that stream. The stream may not be ready for consumption + * immediately upon return from this method -- wait until the streaming state has been + * reported via + * {@link android.telephony.mbms.StreamingServiceCallback#onStreamStateUpdated(int, int)} + * + * May throw an + * {@link MbmsException} containing any of the error codes in + * {@link android.telephony.mbms.MbmsException.GeneralErrors}, + * {@link MbmsException#ERROR_MIDDLEWARE_NOT_BOUND}, or + * {@link MbmsException#ERROR_MIDDLEWARE_LOST}. + * + * May also throw an {@link IllegalArgumentException} or an {@link IllegalStateException} + * + * Asynchronous errors through the callback include any of the errors in + * {@link android.telephony.mbms.MbmsException.GeneralErrors} or + * {@link android.telephony.mbms.MbmsException.StreamingErrors}. + * + * @param serviceInfo The information about the service to stream. + * @param callback A callback that'll be called when something about the stream changes. + * @param handler A handler that calls to {@code callback} should be called on. If null, + * defaults to the handler provided via + * {@link #create(Context, MbmsStreamingManagerCallback, int, Handler)}. + * @return An instance of {@link StreamingService} through which the stream can be controlled. + */ + public StreamingService startStreaming(StreamingServiceInfo serviceInfo, + StreamingServiceCallback callback, Handler handler) throws MbmsException { + IMbmsStreamingService streamingService = mService.get(); + if (streamingService == null) { + throw new MbmsException(MbmsException.ERROR_MIDDLEWARE_NOT_BOUND); + } + + InternalStreamingServiceCallback serviceCallback = new InternalStreamingServiceCallback( + callback, handler == null ? mInternalCallback.getHandler() : handler); + + StreamingService serviceForApp = new StreamingService( + mSubscriptionId, streamingService, serviceInfo, serviceCallback); + + try { + int returnCode = streamingService.startStreaming( + mSubscriptionId, serviceInfo.getServiceId(), serviceCallback); + if (returnCode != MbmsException.SUCCESS) { + throw new MbmsException(returnCode); + } + } catch (RemoteException e) { + Log.w(LOG_TAG, "Remote process died"); + mService.set(null); + sIsInitialized.set(false); + throw new MbmsException(MbmsException.ERROR_MIDDLEWARE_LOST); + } + + return serviceForApp; + } + + private void bindAndInitialize() throws MbmsException { + MbmsUtils.startBinding(mContext, MBMS_STREAMING_SERVICE_ACTION, + new ServiceConnection() { + @Override + public void onServiceConnected(ComponentName name, IBinder service) { + IMbmsStreamingService streamingService = + IMbmsStreamingService.Stub.asInterface(service); + int result; + try { + result = streamingService.initialize(mInternalCallback, + mSubscriptionId); + } catch (RemoteException e) { + Log.e(LOG_TAG, "Service died before initialization"); + sendErrorToApp( + MbmsException.InitializationErrors.ERROR_UNABLE_TO_INITIALIZE, + e.toString()); + sIsInitialized.set(false); + return; + } catch (RuntimeException e) { + Log.e(LOG_TAG, "Runtime exception during initialization"); + sendErrorToApp( + MbmsException.InitializationErrors.ERROR_UNABLE_TO_INITIALIZE, + e.toString()); + sIsInitialized.set(false); + return; + } + if (result != MbmsException.SUCCESS) { + sendErrorToApp(result, "Error returned during initialization"); + sIsInitialized.set(false); + return; + } + try { + streamingService.asBinder().linkToDeath(mDeathRecipient, 0); + } catch (RemoteException e) { + sendErrorToApp(MbmsException.ERROR_MIDDLEWARE_LOST, + "Middleware lost during initialization"); + sIsInitialized.set(false); + return; + } + mService.set(streamingService); + } + + @Override + public void onServiceDisconnected(ComponentName name) { + sIsInitialized.set(false); + mService.set(null); + } + }); + } + + private void sendErrorToApp(int errorCode, String message) { + try { + mInternalCallback.error(errorCode, message); + } catch (RemoteException e) { + // Ignore, should not happen locally. + } + } +} diff --git a/android/telephony/ModemActivityInfo.java b/android/telephony/ModemActivityInfo.java new file mode 100644 index 00000000..03ce2d8e --- /dev/null +++ b/android/telephony/ModemActivityInfo.java @@ -0,0 +1,174 @@ +/* + * Copyright (C) 2015 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.telephony; + +import android.os.Parcel; +import android.os.Parcelable; + +import java.util.Arrays; + +/** + * Reports modem activity information + * @hide + */ +public class ModemActivityInfo implements Parcelable { + /** + * Tx power index + * index 0 = tx_power < 0dBm + * index 1 = 0dBm < tx_power < 5dBm + * index 2 = 5dBm < tx_power < 15dBm + * index 3 = 15dBm < tx_power < 20dBm + * index 4 = tx_power > 20dBm + */ + public static final int TX_POWER_LEVELS = 5; + + private final long mTimestamp; + private final int mSleepTimeMs; + private final int mIdleTimeMs; + private final int [] mTxTimeMs = new int[TX_POWER_LEVELS]; + private final int mRxTimeMs; + private final int mEnergyUsed; + + public ModemActivityInfo(long timestamp, int sleepTimeMs, int idleTimeMs, + int[] txTimeMs, int rxTimeMs, int energyUsed) { + mTimestamp = timestamp; + mSleepTimeMs = sleepTimeMs; + mIdleTimeMs = idleTimeMs; + if (txTimeMs != null) { + System.arraycopy(txTimeMs, 0, mTxTimeMs, 0, Math.min(txTimeMs.length, TX_POWER_LEVELS)); + } + mRxTimeMs = rxTimeMs; + mEnergyUsed = energyUsed; + } + + @Override + public String toString() { + return "ModemActivityInfo{" + + " mTimestamp=" + mTimestamp + + " mSleepTimeMs=" + mSleepTimeMs + + " mIdleTimeMs=" + mIdleTimeMs + + " mTxTimeMs[]=" + Arrays.toString(mTxTimeMs) + + " mRxTimeMs=" + mRxTimeMs + + " mEnergyUsed=" + mEnergyUsed + + "}"; + } + + public int describeContents() { + return 0; + } + + public static final Parcelable.Creator<ModemActivityInfo> CREATOR = + new Parcelable.Creator<ModemActivityInfo>() { + public ModemActivityInfo createFromParcel(Parcel in) { + long timestamp = in.readLong(); + int sleepTimeMs = in.readInt(); + int idleTimeMs = in.readInt(); + int[] txTimeMs = new int[TX_POWER_LEVELS]; + for (int i = 0; i < TX_POWER_LEVELS; i++) { + txTimeMs[i] = in.readInt(); + } + int rxTimeMs = in.readInt(); + int energyUsed = in.readInt(); + return new ModemActivityInfo(timestamp, sleepTimeMs, idleTimeMs, + txTimeMs, rxTimeMs, energyUsed); + } + + public ModemActivityInfo[] newArray(int size) { + return new ModemActivityInfo[size]; + } + }; + + public void writeToParcel(Parcel dest, int flags) { + dest.writeLong(mTimestamp); + dest.writeInt(mSleepTimeMs); + dest.writeInt(mIdleTimeMs); + for (int i = 0; i < TX_POWER_LEVELS; i++) { + dest.writeInt(mTxTimeMs[i]); + } + dest.writeInt(mRxTimeMs); + dest.writeInt(mEnergyUsed); + } + + /** + * @return timestamp of record creation + */ + public long getTimestamp() { + return mTimestamp; + } + + /** + * @return tx time in ms. It's an array of tx times + * with each index... + */ + public int [] getTxTimeMillis() { + return mTxTimeMs; + } + + /** + * @return sleep time in ms. + */ + public int getSleepTimeMillis() { + return mSleepTimeMs; + } + + /** + * @return idle time in ms. + */ + public int getIdleTimeMillis() { + return mIdleTimeMs; + } + + /** + * @return rx time in ms. + */ + public int getRxTimeMillis() { + return mRxTimeMs; + } + + /** + * product of current(mA), voltage(V) and time(ms) + * @return energy used + */ + public int getEnergyUsed () { + return mEnergyUsed; + } + + /** + * @return if the record is valid + */ + public boolean isValid() { + for (int txVal : getTxTimeMillis()) { + if(txVal < 0) { + return false; + } + } + + return ((getIdleTimeMillis() >= 0) && (getSleepTimeMillis() >= 0) + && (getRxTimeMillis() >= 0) && (getEnergyUsed() >= 0) && !isEmpty()); + } + + private boolean isEmpty() { + for (int txVal : getTxTimeMillis()) { + if(txVal != 0) { + return false; + } + } + + return ((getIdleTimeMillis() == 0) && (getSleepTimeMillis() == 0) + && (getRxTimeMillis() == 0) && (getEnergyUsed() == 0)); + } +} diff --git a/android/telephony/NeighboringCellInfo.java b/android/telephony/NeighboringCellInfo.java new file mode 100644 index 00000000..25851e32 --- /dev/null +++ b/android/telephony/NeighboringCellInfo.java @@ -0,0 +1,299 @@ +/* + * Copyright (C) 2006 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.telephony; + +import android.os.Parcel; +import android.os.Parcelable; +import static android.telephony.TelephonyManager.NETWORK_TYPE_UNKNOWN; +import static android.telephony.TelephonyManager.NETWORK_TYPE_EDGE; +import static android.telephony.TelephonyManager.NETWORK_TYPE_GPRS; +import static android.telephony.TelephonyManager.NETWORK_TYPE_UMTS; +import static android.telephony.TelephonyManager.NETWORK_TYPE_HSDPA; +import static android.telephony.TelephonyManager.NETWORK_TYPE_HSUPA; +import static android.telephony.TelephonyManager.NETWORK_TYPE_HSPA; + + + +/** + * Represents the neighboring cell information, including + * Received Signal Strength and Cell ID location. + */ +public class NeighboringCellInfo implements Parcelable +{ + /** + * Signal strength is not available + */ + static final public int UNKNOWN_RSSI = 99; + /** + * Cell location is not available + */ + static final public int UNKNOWN_CID = -1; + + /** + * In GSM, mRssi is the Received RSSI; + * In UMTS, mRssi is the Level index of CPICH Received Signal Code Power + */ + private int mRssi; + /** + * CID in 16 bits format in GSM. Return UNKNOWN_CID in UMTS and CMDA. + */ + private int mCid; + /** + * LAC in 16 bits format in GSM. Return UNKNOWN_CID in UMTS and CMDA. + */ + private int mLac; + /** + * Primary Scrambling Code in 9 bits format in UMTS + * Return UNKNOWN_CID in GSM and CMDA. + */ + private int mPsc; + /** + * Radio network type, value is one of following + * TelephonyManager.NETWORK_TYPE_XXXXXX. + */ + private int mNetworkType; + + /** + * Empty constructor. Initializes the RSSI and CID. + * + * NeighboringCellInfo is one time shot for the neighboring cells based on + * the radio network type at that moment. Its constructor needs radio network + * type. + * + * @deprecated by {@link #NeighboringCellInfo(int, String, int)} + */ + @Deprecated + public NeighboringCellInfo() { + mRssi = UNKNOWN_RSSI; + mLac = UNKNOWN_CID; + mCid = UNKNOWN_CID; + mPsc = UNKNOWN_CID; + mNetworkType = NETWORK_TYPE_UNKNOWN; + } + + /** + * Initialize the object from rssi and cid. + * + * NeighboringCellInfo is one time shot for the neighboring cells based on + * the radio network type at that moment. Its constructor needs radio network + * type. + * + * @deprecated by {@link #NeighboringCellInfo(int, String, int)} + */ + @Deprecated + public NeighboringCellInfo(int rssi, int cid) { + mRssi = rssi; + mCid = cid; + } + + /** + * Initialize the object from rssi, location string, and radioType + * radioType is one of following + * {@link TelephonyManager#NETWORK_TYPE_GPRS TelephonyManager.NETWORK_TYPE_GPRS}, + * {@link TelephonyManager#NETWORK_TYPE_EDGE TelephonyManager.NETWORK_TYPE_EDGE}, + * {@link TelephonyManager#NETWORK_TYPE_UMTS TelephonyManager.NETWORK_TYPE_UMTS}, + * {@link TelephonyManager#NETWORK_TYPE_HSDPA TelephonyManager.NETWORK_TYPE_HSDPA}, + * {@link TelephonyManager#NETWORK_TYPE_HSUPA TelephonyManager.NETWORK_TYPE_HSUPA}, + * and {@link TelephonyManager#NETWORK_TYPE_HSPA TelephonyManager.NETWORK_TYPE_HSPA}. + */ + public NeighboringCellInfo(int rssi, String location, int radioType) { + // set default value + mRssi = rssi; + mNetworkType = NETWORK_TYPE_UNKNOWN; + mPsc = UNKNOWN_CID; + mLac = UNKNOWN_CID; + mCid = UNKNOWN_CID; + + + // pad location string with leading "0" + int l = location.length(); + if (l > 8) return; + if (l < 8) { + for (int i = 0; i < (8-l); i++) { + location = "0" + location; + } + } + // TODO - handle LTE and eHRPD (or find they can't be supported) + try {// set LAC/CID or PSC based on radioType + switch (radioType) { + case NETWORK_TYPE_GPRS: + case NETWORK_TYPE_EDGE: + mNetworkType = radioType; + // check if 0xFFFFFFFF for UNKNOWN_CID + if (!location.equalsIgnoreCase("FFFFFFFF")) { + mCid = Integer.parseInt(location.substring(4), 16); + mLac = Integer.parseInt(location.substring(0, 4), 16); + } + break; + case NETWORK_TYPE_UMTS: + case NETWORK_TYPE_HSDPA: + case NETWORK_TYPE_HSUPA: + case NETWORK_TYPE_HSPA: + mNetworkType = radioType; + mPsc = Integer.parseInt(location, 16); + break; + } + } catch (NumberFormatException e) { + // parsing location error + mPsc = UNKNOWN_CID; + mLac = UNKNOWN_CID; + mCid = UNKNOWN_CID; + mNetworkType = NETWORK_TYPE_UNKNOWN; + } + } + + /** + * Initialize the object from a parcel. + */ + public NeighboringCellInfo(Parcel in) { + mRssi = in.readInt(); + mLac = in.readInt(); + mCid = in.readInt(); + mPsc = in.readInt(); + mNetworkType = in.readInt(); + } + + /** + * @return received signal strength or UNKNOWN_RSSI if unknown + * + * For GSM, it is in "asu" ranging from 0 to 31 (dBm = -113 + 2*asu) + * 0 means "-113 dBm or less" and 31 means "-51 dBm or greater" + * For UMTS, it is the Level index of CPICH RSCP defined in TS 25.125 + */ + public int getRssi() { + return mRssi; + } + + /** + * @return LAC in GSM, 0xffff max legal value + * UNKNOWN_CID if in UMTS or CMDA or unknown + */ + public int getLac() { + return mLac; + } + + /** + * @return cell id in GSM, 0xffff max legal value + * UNKNOWN_CID if in UMTS or CDMA or unknown + */ + public int getCid() { + return mCid; + } + + /** + * @return Primary Scrambling Code in 9 bits format in UMTS, 0x1ff max value + * UNKNOWN_CID if in GSM or CMDA or unknown + */ + public int getPsc() { + return mPsc; + } + + /** + * @return Radio network type while neighboring cell location is stored. + * + * Return {@link TelephonyManager#NETWORK_TYPE_UNKNOWN TelephonyManager.NETWORK_TYPE_UNKNOWN} + * means that the location information is unavailable. + * + * Return {@link TelephonyManager#NETWORK_TYPE_GPRS TelephonyManager.NETWORK_TYPE_GPRS} or + * {@link TelephonyManager#NETWORK_TYPE_EDGE TelephonyManager.NETWORK_TYPE_EDGE} + * means that Neighboring Cell information is stored for GSM network, in + * which {@link NeighboringCellInfo#getLac NeighboringCellInfo.getLac} and + * {@link NeighboringCellInfo#getCid NeighboringCellInfo.getCid} should be + * called to access location. + * + * Return {@link TelephonyManager#NETWORK_TYPE_UMTS TelephonyManager.NETWORK_TYPE_UMTS}, + * {@link TelephonyManager#NETWORK_TYPE_HSDPA TelephonyManager.NETWORK_TYPE_HSDPA}, + * {@link TelephonyManager#NETWORK_TYPE_HSUPA TelephonyManager.NETWORK_TYPE_HSUPA}, + * or {@link TelephonyManager#NETWORK_TYPE_HSPA TelephonyManager.NETWORK_TYPE_HSPA} + * means that Neighboring Cell information is stored for UMTS network, in + * which {@link NeighboringCellInfo#getPsc NeighboringCellInfo.getPsc} + * should be called to access location. + */ + public int getNetworkType() { + return mNetworkType; + } + /** + * Set the cell id. + * + * NeighboringCellInfo is a one time shot for the neighboring cells based on + * the radio network type at that moment. It shouldn't be changed after + * creation. + * + * @deprecated cid value passed as in location parameter passed to constructor + * {@link #NeighboringCellInfo(int, String, int)} + */ + @Deprecated + public void setCid(int cid) { + mCid = cid; + } + + /** + * Set the signal strength of the cell. + * + * NeighboringCellInfo is a one time shot for the neighboring cells based on + * the radio network type at that moment. It shouldn't be changed after + * creation. + * + * @deprecated initial rssi value passed as parameter to constructor + * {@link #NeighboringCellInfo(int, String, int)} + */ + @Deprecated + public void setRssi(int rssi) { + mRssi = rssi; + } + + @Override + public String toString() { + StringBuilder sb = new StringBuilder(); + + sb.append("["); + if (mPsc != UNKNOWN_CID) { + sb.append(Integer.toHexString(mPsc)) + .append("@").append(((mRssi == UNKNOWN_RSSI)? "-" : mRssi)); + } else if(mLac != UNKNOWN_CID && mCid != UNKNOWN_CID) { + sb.append(Integer.toHexString(mLac)) + .append(Integer.toHexString(mCid)) + .append("@").append(((mRssi == UNKNOWN_RSSI)? "-" : mRssi)); + } + sb.append("]"); + + return sb.toString(); + } + + public int describeContents() { + return 0; + } + + public void writeToParcel(Parcel dest, int flags) { + dest.writeInt(mRssi); + dest.writeInt(mLac); + dest.writeInt(mCid); + dest.writeInt(mPsc); + dest.writeInt(mNetworkType); + } + + public static final Parcelable.Creator<NeighboringCellInfo> CREATOR + = new Parcelable.Creator<NeighboringCellInfo>() { + public NeighboringCellInfo createFromParcel(Parcel in) { + return new NeighboringCellInfo(in); + } + + public NeighboringCellInfo[] newArray(int size) { + return new NeighboringCellInfo[size]; + } + }; +} diff --git a/android/telephony/NetworkScan.java b/android/telephony/NetworkScan.java new file mode 100644 index 00000000..f15fde8f --- /dev/null +++ b/android/telephony/NetworkScan.java @@ -0,0 +1,97 @@ +/* + * Copyright (C) 2017 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.telephony; + +import android.content.Context; +import android.os.RemoteException; +import android.os.ServiceManager; +import android.util.Log; + +import com.android.internal.telephony.ITelephony; + +/** + * Allows applications to request the system to perform a network scan. + * + * The caller of {@link #requestNetworkScan(NetworkScanRequest, NetworkScanCallback)} will + * receive a NetworkScan which contains the callback method to stop the scan requested. + * @hide + */ +public class NetworkScan { + + public static final String TAG = "NetworkScan"; + + // Below errors are mapped from RadioError which is returned from RIL. We will consolidate + // RadioErrors during the mapping if those RadioErrors mean no difference to the users. + public static final int SUCCESS = 0; // RadioError:NONE + public static final int ERROR_MODEM_ERROR = 1; // RadioError:RADIO_NOT_AVAILABLE + // RadioError:NO_MEMORY + // RadioError:INTERNAL_ERR + // RadioError:MODEM_ERR + // RadioError:OPERATION_NOT_ALLOWED + public static final int ERROR_INVALID_SCAN = 2; // RadioError:INVALID_ARGUMENTS + public static final int ERROR_MODEM_BUSY = 3; // RadioError:DEVICE_IN_USE + public static final int ERROR_UNSUPPORTED = 4; // RadioError:REQUEST_NOT_SUPPORTED + + // Below errors are generated at the Telephony. + public static final int ERROR_RIL_ERROR = 10000; // Nothing or only exception is + // returned from RIL. + public static final int ERROR_INVALID_SCANID = 10001; // The scanId is invalid. The user is + // either trying to stop a scan which + // does not exist or started by others. + public static final int ERROR_INTERRUPTED = 10002; // Scan was interrupted by another scan + // with higher priority. + private final int mScanId; + private final int mSubId; + + /** + * Stops the network scan + * + * This is the callback method to stop an ongoing scan. When user requests a new scan, + * a NetworkScan object will be returned, and the user can stop the scan by calling this + * method. + */ + public void stop() throws RemoteException { + try { + ITelephony telephony = getITelephony(); + if (telephony != null) { + telephony.stopNetworkScan(mSubId, mScanId); + } else { + throw new RemoteException("Failed to get the ITelephony instance."); + } + } catch (RemoteException ex) { + Rlog.e(TAG, "stopNetworkScan RemoteException", ex); + throw new RemoteException("Failed to stop the network scan with id " + mScanId); + } + } + + /** + * Creates a new NetworkScan with scanId + * + * @param scanId The id of the scan + * @param subId the id of the subscription + * @hide + */ + public NetworkScan(int scanId, int subId) { + mScanId = scanId; + mSubId = subId; + } + + private ITelephony getITelephony() { + return ITelephony.Stub.asInterface( + ServiceManager.getService(Context.TELEPHONY_SERVICE)); + } +} diff --git a/android/telephony/NetworkScanRequest.java b/android/telephony/NetworkScanRequest.java new file mode 100644 index 00000000..d2aef200 --- /dev/null +++ b/android/telephony/NetworkScanRequest.java @@ -0,0 +1,123 @@ +/* + * Copyright (C) 2017 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.telephony; + +import android.os.Parcel; +import android.os.Parcelable; + +import java.util.Arrays; + +/** + * Defines a request to peform a network scan. + * + * This class defines whether the network scan will be performed only once or periodically until + * cancelled, when the scan is performed periodically, the time interval is not controlled by the + * user but defined by the modem vendor. + * @hide + */ +public final class NetworkScanRequest implements Parcelable { + + // Below size limits for RAN/Band/Channel are for pre-treble modems and will be removed later. + /** @hide */ + public static final int MAX_RADIO_ACCESS_NETWORKS = 8; + /** @hide */ + public static final int MAX_BANDS = 8; + /** @hide */ + public static final int MAX_CHANNELS = 32; + + /** Performs the scan only once */ + public static final int SCAN_TYPE_ONE_SHOT = 0; + /** + * Performs the scan periodically until cancelled + * + * The modem will start new scans periodically, and the interval between two scans is usually + * multiple minutes. + * */ + public static final int SCAN_TYPE_PERIODIC = 1; + + /** Defines the type of the scan. */ + public int scanType; + + /** Describes the radio access technologies with bands or channels that need to be scanned. */ + public RadioAccessSpecifier[] specifiers; + + /** + * Creates a new NetworkScanRequest with scanType and network specifiers + * + * @param scanType The type of the scan + * @param specifiers the radio network with bands / channels to be scanned + */ + public NetworkScanRequest(int scanType, RadioAccessSpecifier[] specifiers) { + this.scanType = scanType; + this.specifiers = specifiers; + } + + @Override + public int describeContents() { + return 0; + } + + @Override + public void writeToParcel(Parcel dest, int flags) { + dest.writeInt(scanType); + dest.writeParcelableArray(specifiers, flags); + } + + private NetworkScanRequest(Parcel in) { + scanType = in.readInt(); + specifiers = (RadioAccessSpecifier[]) in.readParcelableArray( + Object.class.getClassLoader(), + RadioAccessSpecifier.class); + } + + @Override + public boolean equals (Object o) { + NetworkScanRequest nsr; + + try { + nsr = (NetworkScanRequest) o; + } catch (ClassCastException ex) { + return false; + } + + if (o == null) { + return false; + } + + return (scanType == nsr.scanType + && Arrays.equals(specifiers, nsr.specifiers)); + } + + @Override + public int hashCode () { + return ((scanType * 31) + + (Arrays.hashCode(specifiers)) * 37); + } + + public static final Creator<NetworkScanRequest> CREATOR = + new Creator<NetworkScanRequest>() { + @Override + public NetworkScanRequest createFromParcel(Parcel in) { + return new NetworkScanRequest(in); + } + + @Override + public NetworkScanRequest[] newArray(int size) { + return new NetworkScanRequest[size]; + } + }; +} diff --git a/android/telephony/PcoData.java b/android/telephony/PcoData.java new file mode 100644 index 00000000..3e735e74 --- /dev/null +++ b/android/telephony/PcoData.java @@ -0,0 +1,87 @@ +/* + * 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.telephony; + +import android.os.Parcel; +import android.os.Parcelable; + +/** + * Contains Carrier-specific (and opaque) Protocol configuration Option + * Data. In general this is only passed on to carrier-specific applications + * for interpretation. + * + * @hide + */ +public class PcoData implements Parcelable { + + public final int cid; + public final String bearerProto; + public final int pcoId; + public final byte[] contents; + + public PcoData(int cid, String bearerProto, int pcoId, byte[]contents) { + this.cid = cid; + this.bearerProto = bearerProto; + this.pcoId = pcoId; + this.contents = contents; + } + + public PcoData(Parcel in) { + cid = in.readInt(); + bearerProto = in.readString(); + pcoId = in.readInt(); + contents = in.createByteArray(); + } + + /** + * {@link Parcelable#writeToParcel} + */ + public void writeToParcel(Parcel out, int flags) { + out.writeInt(cid); + out.writeString(bearerProto); + out.writeInt(pcoId); + out.writeByteArray(contents); + } + + /** + * {@link Parcelable#describeContents} + */ + public int describeContents() { + return 0; + } + + /** + * {@link Parcelable.Creator} + * + * @hide + */ + public static final Parcelable.Creator<PcoData> CREATOR = new Parcelable.Creator() { + public PcoData createFromParcel(Parcel in) { + return new PcoData(in); + } + + public PcoData[] newArray(int size) { + return new PcoData[size]; + } + }; + + @Override + public String toString() { + return "PcoData(" + cid + ", " + bearerProto + ", " + pcoId + ", contents[" + + contents.length + "])"; + } +} diff --git a/android/telephony/PhoneNumberFormattingTextWatcher.java b/android/telephony/PhoneNumberFormattingTextWatcher.java new file mode 100644 index 00000000..f7dee99f --- /dev/null +++ b/android/telephony/PhoneNumberFormattingTextWatcher.java @@ -0,0 +1,174 @@ +/* + * Copyright (C) 2008 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.telephony; + +import com.android.i18n.phonenumbers.AsYouTypeFormatter; +import com.android.i18n.phonenumbers.PhoneNumberUtil; + +import android.telephony.PhoneNumberUtils; +import android.text.Editable; +import android.text.Selection; +import android.text.TextWatcher; + +import java.util.Locale; + +/** + * Watches a {@link android.widget.TextView} and if a phone number is entered + * will format it. + * <p> + * Stop formatting when the user + * <ul> + * <li>Inputs non-dialable characters</li> + * <li>Removes the separator in the middle of string.</li> + * </ul> + * <p> + * The formatting will be restarted once the text is cleared. + */ +public class PhoneNumberFormattingTextWatcher implements TextWatcher { + + /** + * Indicates the change was caused by ourselves. + */ + private boolean mSelfChange = false; + + /** + * Indicates the formatting has been stopped. + */ + private boolean mStopFormatting; + + private AsYouTypeFormatter mFormatter; + + /** + * The formatting is based on the current system locale and future locale changes + * may not take effect on this instance. + */ + public PhoneNumberFormattingTextWatcher() { + this(Locale.getDefault().getCountry()); + } + + /** + * The formatting is based on the given <code>countryCode</code>. + * + * @param countryCode the ISO 3166-1 two-letter country code that indicates the country/region + * where the phone number is being entered. + */ + public PhoneNumberFormattingTextWatcher(String countryCode) { + if (countryCode == null) throw new IllegalArgumentException(); + mFormatter = PhoneNumberUtil.getInstance().getAsYouTypeFormatter(countryCode); + } + + @Override + public void beforeTextChanged(CharSequence s, int start, int count, + int after) { + if (mSelfChange || mStopFormatting) { + return; + } + // If the user manually deleted any non-dialable characters, stop formatting + if (count > 0 && hasSeparator(s, start, count)) { + stopFormatting(); + } + } + + @Override + public void onTextChanged(CharSequence s, int start, int before, int count) { + if (mSelfChange || mStopFormatting) { + return; + } + // If the user inserted any non-dialable characters, stop formatting + if (count > 0 && hasSeparator(s, start, count)) { + stopFormatting(); + } + } + + @Override + public synchronized void afterTextChanged(Editable s) { + if (mStopFormatting) { + // Restart the formatting when all texts were clear. + mStopFormatting = !(s.length() == 0); + return; + } + if (mSelfChange) { + // Ignore the change caused by s.replace(). + return; + } + String formatted = reformat(s, Selection.getSelectionEnd(s)); + if (formatted != null) { + int rememberedPos = mFormatter.getRememberedPosition(); + mSelfChange = true; + s.replace(0, s.length(), formatted, 0, formatted.length()); + // The text could be changed by other TextWatcher after we changed it. If we found the + // text is not the one we were expecting, just give up calling setSelection(). + if (formatted.equals(s.toString())) { + Selection.setSelection(s, rememberedPos); + } + mSelfChange = false; + } + PhoneNumberUtils.ttsSpanAsPhoneNumber(s, 0, s.length()); + } + + /** + * Generate the formatted number by ignoring all non-dialable chars and stick the cursor to the + * nearest dialable char to the left. For instance, if the number is (650) 123-45678 and '4' is + * removed then the cursor should be behind '3' instead of '-'. + */ + private String reformat(CharSequence s, int cursor) { + // The index of char to the leftward of the cursor. + int curIndex = cursor - 1; + String formatted = null; + mFormatter.clear(); + char lastNonSeparator = 0; + boolean hasCursor = false; + int len = s.length(); + for (int i = 0; i < len; i++) { + char c = s.charAt(i); + if (PhoneNumberUtils.isNonSeparator(c)) { + if (lastNonSeparator != 0) { + formatted = getFormattedNumber(lastNonSeparator, hasCursor); + hasCursor = false; + } + lastNonSeparator = c; + } + if (i == curIndex) { + hasCursor = true; + } + } + if (lastNonSeparator != 0) { + formatted = getFormattedNumber(lastNonSeparator, hasCursor); + } + return formatted; + } + + private String getFormattedNumber(char lastNonSeparator, boolean hasCursor) { + return hasCursor ? mFormatter.inputDigitAndRememberPosition(lastNonSeparator) + : mFormatter.inputDigit(lastNonSeparator); + } + + private void stopFormatting() { + mStopFormatting = true; + mFormatter.clear(); + } + + private boolean hasSeparator(final CharSequence s, final int start, final int count) { + for (int i = start; i < start + count; i++) { + char c = s.charAt(i); + if (!PhoneNumberUtils.isNonSeparator(c)) { + return true; + } + } + return false; + } +} diff --git a/android/telephony/PhoneNumberUtils.java b/android/telephony/PhoneNumberUtils.java new file mode 100644 index 00000000..d5ff1adb --- /dev/null +++ b/android/telephony/PhoneNumberUtils.java @@ -0,0 +1,3167 @@ +/* + * Copyright (C) 2006 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.telephony; + +import com.android.i18n.phonenumbers.NumberParseException; +import com.android.i18n.phonenumbers.PhoneNumberUtil; +import com.android.i18n.phonenumbers.PhoneNumberUtil.PhoneNumberFormat; +import com.android.i18n.phonenumbers.Phonenumber.PhoneNumber; +import com.android.i18n.phonenumbers.ShortNumberInfo; + +import android.content.Context; +import android.content.Intent; +import android.content.res.Resources; +import android.database.Cursor; +import android.location.CountryDetector; +import android.net.Uri; +import android.os.SystemProperties; +import android.os.PersistableBundle; +import android.provider.Contacts; +import android.provider.ContactsContract; +import android.telecom.PhoneAccount; +import android.text.Editable; +import android.text.Spannable; +import android.text.SpannableStringBuilder; +import android.text.TextUtils; +import android.text.style.TtsSpan; +import android.util.SparseIntArray; + +import static com.android.internal.telephony.TelephonyProperties.PROPERTY_OPERATOR_IDP_STRING; + +import java.util.Locale; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +/** + * Various utilities for dealing with phone number strings. + */ +public class PhoneNumberUtils +{ + /* + * Special characters + * + * (See "What is a phone number?" doc) + * 'p' --- GSM pause character, same as comma + * 'n' --- GSM wild character + * 'w' --- GSM wait character + */ + public static final char PAUSE = ','; + public static final char WAIT = ';'; + public static final char WILD = 'N'; + + /* + * Calling Line Identification Restriction (CLIR) + */ + private static final String CLIR_ON = "*31#"; + private static final String CLIR_OFF = "#31#"; + + /* + * TOA = TON + NPI + * See TS 24.008 section 10.5.4.7 for details. + * These are the only really useful TOA values + */ + public static final int TOA_International = 0x91; + public static final int TOA_Unknown = 0x81; + + static final String LOG_TAG = "PhoneNumberUtils"; + private static final boolean DBG = false; + + /* + * global-phone-number = ["+"] 1*( DIGIT / written-sep ) + * written-sep = ("-"/".") + */ + private static final Pattern GLOBAL_PHONE_NUMBER_PATTERN = + Pattern.compile("[\\+]?[0-9.-]+"); + + /** True if c is ISO-LATIN characters 0-9 */ + public static boolean + isISODigit (char c) { + return c >= '0' && c <= '9'; + } + + /** True if c is ISO-LATIN characters 0-9, *, # */ + public final static boolean + is12Key(char c) { + return (c >= '0' && c <= '9') || c == '*' || c == '#'; + } + + /** True if c is ISO-LATIN characters 0-9, *, # , +, WILD */ + public final static boolean + isDialable(char c) { + return (c >= '0' && c <= '9') || c == '*' || c == '#' || c == '+' || c == WILD; + } + + /** True if c is ISO-LATIN characters 0-9, *, # , + (no WILD) */ + public final static boolean + isReallyDialable(char c) { + return (c >= '0' && c <= '9') || c == '*' || c == '#' || c == '+'; + } + + /** True if c is ISO-LATIN characters 0-9, *, # , +, WILD, WAIT, PAUSE */ + public final static boolean + isNonSeparator(char c) { + return (c >= '0' && c <= '9') || c == '*' || c == '#' || c == '+' + || c == WILD || c == WAIT || c == PAUSE; + } + + /** This any anything to the right of this char is part of the + * post-dial string (eg this is PAUSE or WAIT) + */ + public final static boolean + isStartsPostDial (char c) { + return c == PAUSE || c == WAIT; + } + + private static boolean + isPause (char c){ + return c == 'p'||c == 'P'; + } + + private static boolean + isToneWait (char c){ + return c == 'w'||c == 'W'; + } + + + /** Returns true if ch is not dialable or alpha char */ + private static boolean isSeparator(char ch) { + return !isDialable(ch) && !(('a' <= ch && ch <= 'z') || ('A' <= ch && ch <= 'Z')); + } + + /** Extracts the phone number from an Intent. + * + * @param intent the intent to get the number of + * @param context a context to use for database access + * + * @return the phone number that would be called by the intent, or + * <code>null</code> if the number cannot be found. + */ + public static String getNumberFromIntent(Intent intent, Context context) { + String number = null; + + Uri uri = intent.getData(); + + if (uri == null) { + return null; + } + + String scheme = uri.getScheme(); + + if (scheme.equals("tel") || scheme.equals("sip")) { + return uri.getSchemeSpecificPart(); + } + + if (context == null) { + return null; + } + + String type = intent.resolveType(context); + String phoneColumn = null; + + // Correctly read out the phone entry based on requested provider + final String authority = uri.getAuthority(); + if (Contacts.AUTHORITY.equals(authority)) { + phoneColumn = Contacts.People.Phones.NUMBER; + } else if (ContactsContract.AUTHORITY.equals(authority)) { + phoneColumn = ContactsContract.CommonDataKinds.Phone.NUMBER; + } + + Cursor c = null; + try { + c = context.getContentResolver().query(uri, new String[] { phoneColumn }, + null, null, null); + if (c != null) { + if (c.moveToFirst()) { + number = c.getString(c.getColumnIndex(phoneColumn)); + } + } + } catch (RuntimeException e) { + Rlog.e(LOG_TAG, "Error getting phone number.", e); + } finally { + if (c != null) { + c.close(); + } + } + + return number; + } + + /** Extracts the network address portion and canonicalizes + * (filters out separators.) + * Network address portion is everything up to DTMF control digit + * separators (pause or wait), but without non-dialable characters. + * + * Please note that the GSM wild character is allowed in the result. + * This must be resolved before dialing. + * + * Returns null if phoneNumber == null + */ + public static String + extractNetworkPortion(String phoneNumber) { + if (phoneNumber == null) { + return null; + } + + int len = phoneNumber.length(); + StringBuilder ret = new StringBuilder(len); + + for (int i = 0; i < len; i++) { + char c = phoneNumber.charAt(i); + // Character.digit() supports ASCII and Unicode digits (fullwidth, Arabic-Indic, etc.) + int digit = Character.digit(c, 10); + if (digit != -1) { + ret.append(digit); + } else if (c == '+') { + // Allow '+' as first character or after CLIR MMI prefix + String prefix = ret.toString(); + if (prefix.length() == 0 || prefix.equals(CLIR_ON) || prefix.equals(CLIR_OFF)) { + ret.append(c); + } + } else if (isDialable(c)) { + ret.append(c); + } else if (isStartsPostDial (c)) { + break; + } + } + + return ret.toString(); + } + + /** + * Extracts the network address portion and canonicalize. + * + * This function is equivalent to extractNetworkPortion(), except + * for allowing the PLUS character to occur at arbitrary positions + * in the address portion, not just the first position. + * + * @hide + */ + public static String extractNetworkPortionAlt(String phoneNumber) { + if (phoneNumber == null) { + return null; + } + + int len = phoneNumber.length(); + StringBuilder ret = new StringBuilder(len); + boolean haveSeenPlus = false; + + for (int i = 0; i < len; i++) { + char c = phoneNumber.charAt(i); + if (c == '+') { + if (haveSeenPlus) { + continue; + } + haveSeenPlus = true; + } + if (isDialable(c)) { + ret.append(c); + } else if (isStartsPostDial (c)) { + break; + } + } + + return ret.toString(); + } + + /** + * Strips separators from a phone number string. + * @param phoneNumber phone number to strip. + * @return phone string stripped of separators. + */ + public static String stripSeparators(String phoneNumber) { + if (phoneNumber == null) { + return null; + } + int len = phoneNumber.length(); + StringBuilder ret = new StringBuilder(len); + + for (int i = 0; i < len; i++) { + char c = phoneNumber.charAt(i); + // Character.digit() supports ASCII and Unicode digits (fullwidth, Arabic-Indic, etc.) + int digit = Character.digit(c, 10); + if (digit != -1) { + ret.append(digit); + } else if (isNonSeparator(c)) { + ret.append(c); + } + } + + return ret.toString(); + } + + /** + * Translates keypad letters to actual digits (e.g. 1-800-GOOG-411 will + * become 1-800-4664-411), and then strips all separators (e.g. 1-800-4664-411 will become + * 18004664411). + * + * @see #convertKeypadLettersToDigits(String) + * @see #stripSeparators(String) + * + * @hide + */ + public static String convertAndStrip(String phoneNumber) { + return stripSeparators(convertKeypadLettersToDigits(phoneNumber)); + } + + /** + * Converts pause and tonewait pause characters + * to Android representation. + * RFC 3601 says pause is 'p' and tonewait is 'w'. + * @hide + */ + public static String convertPreDial(String phoneNumber) { + if (phoneNumber == null) { + return null; + } + int len = phoneNumber.length(); + StringBuilder ret = new StringBuilder(len); + + for (int i = 0; i < len; i++) { + char c = phoneNumber.charAt(i); + + if (isPause(c)) { + c = PAUSE; + } else if (isToneWait(c)) { + c = WAIT; + } + ret.append(c); + } + return ret.toString(); + } + + /** or -1 if both are negative */ + static private int + minPositive (int a, int b) { + if (a >= 0 && b >= 0) { + return (a < b) ? a : b; + } else if (a >= 0) { /* && b < 0 */ + return a; + } else if (b >= 0) { /* && a < 0 */ + return b; + } else { /* a < 0 && b < 0 */ + return -1; + } + } + + private static void log(String msg) { + Rlog.d(LOG_TAG, msg); + } + /** index of the last character of the network portion + * (eg anything after is a post-dial string) + */ + static private int + indexOfLastNetworkChar(String a) { + int pIndex, wIndex; + int origLength; + int trimIndex; + + origLength = a.length(); + + pIndex = a.indexOf(PAUSE); + wIndex = a.indexOf(WAIT); + + trimIndex = minPositive(pIndex, wIndex); + + if (trimIndex < 0) { + return origLength - 1; + } else { + return trimIndex - 1; + } + } + + /** + * Extracts the post-dial sequence of DTMF control digits, pauses, and + * waits. Strips separators. This string may be empty, but will not be null + * unless phoneNumber == null. + * + * Returns null if phoneNumber == null + */ + + public static String + extractPostDialPortion(String phoneNumber) { + if (phoneNumber == null) return null; + + int trimIndex; + StringBuilder ret = new StringBuilder(); + + trimIndex = indexOfLastNetworkChar (phoneNumber); + + for (int i = trimIndex + 1, s = phoneNumber.length() + ; i < s; i++ + ) { + char c = phoneNumber.charAt(i); + if (isNonSeparator(c)) { + ret.append(c); + } + } + + return ret.toString(); + } + + /** + * Compare phone numbers a and b, return true if they're identical enough for caller ID purposes. + */ + public static boolean compare(String a, String b) { + // We've used loose comparation at least Eclair, which may change in the future. + + return compare(a, b, false); + } + + /** + * Compare phone numbers a and b, and return true if they're identical + * enough for caller ID purposes. Checks a resource to determine whether + * to use a strict or loose comparison algorithm. + */ + public static boolean compare(Context context, String a, String b) { + boolean useStrict = context.getResources().getBoolean( + com.android.internal.R.bool.config_use_strict_phone_number_comparation); + return compare(a, b, useStrict); + } + + /** + * @hide only for testing. + */ + public static boolean compare(String a, String b, boolean useStrictComparation) { + return (useStrictComparation ? compareStrictly(a, b) : compareLoosely(a, b)); + } + + /** + * Compare phone numbers a and b, return true if they're identical + * enough for caller ID purposes. + * + * - Compares from right to left + * - requires MIN_MATCH (7) characters to match + * - handles common trunk prefixes and international prefixes + * (basically, everything except the Russian trunk prefix) + * + * Note that this method does not return false even when the two phone numbers + * are not exactly same; rather; we can call this method "similar()", not "equals()". + * + * @hide + */ + public static boolean + compareLoosely(String a, String b) { + int ia, ib; + int matched; + int numNonDialableCharsInA = 0; + int numNonDialableCharsInB = 0; + + if (a == null || b == null) return a == b; + + if (a.length() == 0 || b.length() == 0) { + return false; + } + + ia = indexOfLastNetworkChar (a); + ib = indexOfLastNetworkChar (b); + matched = 0; + + while (ia >= 0 && ib >=0) { + char ca, cb; + boolean skipCmp = false; + + ca = a.charAt(ia); + + if (!isDialable(ca)) { + ia--; + skipCmp = true; + numNonDialableCharsInA++; + } + + cb = b.charAt(ib); + + if (!isDialable(cb)) { + ib--; + skipCmp = true; + numNonDialableCharsInB++; + } + + if (!skipCmp) { + if (cb != ca && ca != WILD && cb != WILD) { + break; + } + ia--; ib--; matched++; + } + } + + if (matched < MIN_MATCH) { + int effectiveALen = a.length() - numNonDialableCharsInA; + int effectiveBLen = b.length() - numNonDialableCharsInB; + + + // if the number of dialable chars in a and b match, but the matched chars < MIN_MATCH, + // treat them as equal (i.e. 404-04 and 40404) + if (effectiveALen == effectiveBLen && effectiveALen == matched) { + return true; + } + + return false; + } + + // At least one string has matched completely; + if (matched >= MIN_MATCH && (ia < 0 || ib < 0)) { + return true; + } + + /* + * Now, what remains must be one of the following for a + * match: + * + * - a '+' on one and a '00' or a '011' on the other + * - a '0' on one and a (+,00)<country code> on the other + * (for this, a '0' and a '00' prefix would have succeeded above) + */ + + if (matchIntlPrefix(a, ia + 1) + && matchIntlPrefix (b, ib +1) + ) { + return true; + } + + if (matchTrunkPrefix(a, ia + 1) + && matchIntlPrefixAndCC(b, ib +1) + ) { + return true; + } + + if (matchTrunkPrefix(b, ib + 1) + && matchIntlPrefixAndCC(a, ia +1) + ) { + return true; + } + + return false; + } + + /** + * @hide + */ + public static boolean + compareStrictly(String a, String b) { + return compareStrictly(a, b, true); + } + + /** + * @hide + */ + public static boolean + compareStrictly(String a, String b, boolean acceptInvalidCCCPrefix) { + if (a == null || b == null) { + return a == b; + } else if (a.length() == 0 && b.length() == 0) { + return false; + } + + int forwardIndexA = 0; + int forwardIndexB = 0; + + CountryCallingCodeAndNewIndex cccA = + tryGetCountryCallingCodeAndNewIndex(a, acceptInvalidCCCPrefix); + CountryCallingCodeAndNewIndex cccB = + tryGetCountryCallingCodeAndNewIndex(b, acceptInvalidCCCPrefix); + boolean bothHasCountryCallingCode = false; + boolean okToIgnorePrefix = true; + boolean trunkPrefixIsOmittedA = false; + boolean trunkPrefixIsOmittedB = false; + if (cccA != null && cccB != null) { + if (cccA.countryCallingCode != cccB.countryCallingCode) { + // Different Country Calling Code. Must be different phone number. + return false; + } + // When both have ccc, do not ignore trunk prefix. Without this, + // "+81123123" becomes same as "+810123123" (+81 == Japan) + okToIgnorePrefix = false; + bothHasCountryCallingCode = true; + forwardIndexA = cccA.newIndex; + forwardIndexB = cccB.newIndex; + } else if (cccA == null && cccB == null) { + // When both do not have ccc, do not ignore trunk prefix. Without this, + // "123123" becomes same as "0123123" + okToIgnorePrefix = false; + } else { + if (cccA != null) { + forwardIndexA = cccA.newIndex; + } else { + int tmp = tryGetTrunkPrefixOmittedIndex(b, 0); + if (tmp >= 0) { + forwardIndexA = tmp; + trunkPrefixIsOmittedA = true; + } + } + if (cccB != null) { + forwardIndexB = cccB.newIndex; + } else { + int tmp = tryGetTrunkPrefixOmittedIndex(b, 0); + if (tmp >= 0) { + forwardIndexB = tmp; + trunkPrefixIsOmittedB = true; + } + } + } + + int backwardIndexA = a.length() - 1; + int backwardIndexB = b.length() - 1; + while (backwardIndexA >= forwardIndexA && backwardIndexB >= forwardIndexB) { + boolean skip_compare = false; + final char chA = a.charAt(backwardIndexA); + final char chB = b.charAt(backwardIndexB); + if (isSeparator(chA)) { + backwardIndexA--; + skip_compare = true; + } + if (isSeparator(chB)) { + backwardIndexB--; + skip_compare = true; + } + + if (!skip_compare) { + if (chA != chB) { + return false; + } + backwardIndexA--; + backwardIndexB--; + } + } + + if (okToIgnorePrefix) { + if ((trunkPrefixIsOmittedA && forwardIndexA <= backwardIndexA) || + !checkPrefixIsIgnorable(a, forwardIndexA, backwardIndexA)) { + if (acceptInvalidCCCPrefix) { + // Maybe the code handling the special case for Thailand makes the + // result garbled, so disable the code and try again. + // e.g. "16610001234" must equal to "6610001234", but with + // Thailand-case handling code, they become equal to each other. + // + // Note: we select simplicity rather than adding some complicated + // logic here for performance(like "checking whether remaining + // numbers are just 66 or not"), assuming inputs are small + // enough. + return compare(a, b, false); + } else { + return false; + } + } + if ((trunkPrefixIsOmittedB && forwardIndexB <= backwardIndexB) || + !checkPrefixIsIgnorable(b, forwardIndexA, backwardIndexB)) { + if (acceptInvalidCCCPrefix) { + return compare(a, b, false); + } else { + return false; + } + } + } else { + // In the US, 1-650-555-1234 must be equal to 650-555-1234, + // while 090-1234-1234 must not be equal to 90-1234-1234 in Japan. + // This request exists just in US (with 1 trunk (NDD) prefix). + // In addition, "011 11 7005554141" must not equal to "+17005554141", + // while "011 1 7005554141" must equal to "+17005554141" + // + // In this comparison, we ignore the prefix '1' just once, when + // - at least either does not have CCC, or + // - the remaining non-separator number is 1 + boolean maybeNamp = !bothHasCountryCallingCode; + while (backwardIndexA >= forwardIndexA) { + final char chA = a.charAt(backwardIndexA); + if (isDialable(chA)) { + if (maybeNamp && tryGetISODigit(chA) == 1) { + maybeNamp = false; + } else { + return false; + } + } + backwardIndexA--; + } + while (backwardIndexB >= forwardIndexB) { + final char chB = b.charAt(backwardIndexB); + if (isDialable(chB)) { + if (maybeNamp && tryGetISODigit(chB) == 1) { + maybeNamp = false; + } else { + return false; + } + } + backwardIndexB--; + } + } + + return true; + } + + /** + * Returns the rightmost MIN_MATCH (5) characters in the network portion + * in *reversed* order + * + * This can be used to do a database lookup against the column + * that stores getStrippedReversed() + * + * Returns null if phoneNumber == null + */ + public static String + toCallerIDMinMatch(String phoneNumber) { + String np = extractNetworkPortionAlt(phoneNumber); + return internalGetStrippedReversed(np, MIN_MATCH); + } + + /** + * Returns the network portion reversed. + * This string is intended to go into an index column for a + * database lookup. + * + * Returns null if phoneNumber == null + */ + public static String + getStrippedReversed(String phoneNumber) { + String np = extractNetworkPortionAlt(phoneNumber); + + if (np == null) return null; + + return internalGetStrippedReversed(np, np.length()); + } + + /** + * Returns the last numDigits of the reversed phone number + * Returns null if np == null + */ + private static String + internalGetStrippedReversed(String np, int numDigits) { + if (np == null) return null; + + StringBuilder ret = new StringBuilder(numDigits); + int length = np.length(); + + for (int i = length - 1, s = length + ; i >= 0 && (s - i) <= numDigits ; i-- + ) { + char c = np.charAt(i); + + ret.append(c); + } + + return ret.toString(); + } + + /** + * Basically: makes sure there's a + in front of a + * TOA_International number + * + * Returns null if s == null + */ + public static String + stringFromStringAndTOA(String s, int TOA) { + if (s == null) return null; + + if (TOA == TOA_International && s.length() > 0 && s.charAt(0) != '+') { + return "+" + s; + } + + return s; + } + + /** + * Returns the TOA for the given dial string + * Basically, returns TOA_International if there's a + prefix + */ + + public static int + toaFromString(String s) { + if (s != null && s.length() > 0 && s.charAt(0) == '+') { + return TOA_International; + } + + return TOA_Unknown; + } + + /** + * 3GPP TS 24.008 10.5.4.7 + * Called Party BCD Number + * + * See Also TS 51.011 10.5.1 "dialing number/ssc string" + * and TS 11.11 "10.3.1 EF adn (Abbreviated dialing numbers)" + * + * @param bytes the data buffer + * @param offset should point to the TOA (aka. TON/NPI) octet after the length byte + * @param length is the number of bytes including TOA byte + * and must be at least 2 + * + * @return partial string on invalid decode + * + * FIXME(mkf) support alphanumeric address type + * currently implemented in SMSMessage.getAddress() + */ + public static String + calledPartyBCDToString (byte[] bytes, int offset, int length) { + boolean prependPlus = false; + StringBuilder ret = new StringBuilder(1 + length * 2); + + if (length < 2) { + return ""; + } + + //Only TON field should be taken in consideration + if ((bytes[offset] & 0xf0) == (TOA_International & 0xf0)) { + prependPlus = true; + } + + internalCalledPartyBCDFragmentToString( + ret, bytes, offset + 1, length - 1); + + if (prependPlus && ret.length() == 0) { + // If the only thing there is a prepended plus, return "" + return ""; + } + + if (prependPlus) { + // This is an "international number" and should have + // a plus prepended to the dialing number. But there + // can also be GSM MMI codes as defined in TS 22.030 6.5.2 + // so we need to handle those also. + // + // http://web.telia.com/~u47904776/gsmkode.htm + // has a nice list of some of these GSM codes. + // + // Examples are: + // **21*+886988171479# + // **21*8311234567# + // *21# + // #21# + // *#21# + // *31#+11234567890 + // #31#+18311234567 + // #31#8311234567 + // 18311234567 + // +18311234567# + // +18311234567 + // Odd ball cases that some phones handled + // where there is no dialing number so they + // append the "+" + // *21#+ + // **21#+ + String retString = ret.toString(); + Pattern p = Pattern.compile("(^[#*])(.*)([#*])(.*)(#)$"); + Matcher m = p.matcher(retString); + if (m.matches()) { + if ("".equals(m.group(2))) { + // Started with two [#*] ends with # + // So no dialing number and we'll just + // append a +, this handles **21#+ + ret = new StringBuilder(); + ret.append(m.group(1)); + ret.append(m.group(3)); + ret.append(m.group(4)); + ret.append(m.group(5)); + ret.append("+"); + } else { + // Starts with [#*] and ends with # + // Assume group 4 is a dialing number + // such as *21*+1234554# + ret = new StringBuilder(); + ret.append(m.group(1)); + ret.append(m.group(2)); + ret.append(m.group(3)); + ret.append("+"); + ret.append(m.group(4)); + ret.append(m.group(5)); + } + } else { + p = Pattern.compile("(^[#*])(.*)([#*])(.*)"); + m = p.matcher(retString); + if (m.matches()) { + // Starts with [#*] and only one other [#*] + // Assume the data after last [#*] is dialing + // number (i.e. group 4) such as *31#+11234567890. + // This also includes the odd ball *21#+ + ret = new StringBuilder(); + ret.append(m.group(1)); + ret.append(m.group(2)); + ret.append(m.group(3)); + ret.append("+"); + ret.append(m.group(4)); + } else { + // Does NOT start with [#*] just prepend '+' + ret = new StringBuilder(); + ret.append('+'); + ret.append(retString); + } + } + } + + return ret.toString(); + } + + private static void + internalCalledPartyBCDFragmentToString( + StringBuilder sb, byte [] bytes, int offset, int length) { + for (int i = offset ; i < length + offset ; i++) { + byte b; + char c; + + c = bcdToChar((byte)(bytes[i] & 0xf)); + + if (c == 0) { + return; + } + sb.append(c); + + // FIXME(mkf) TS 23.040 9.1.2.3 says + // "if a mobile receives 1111 in a position prior to + // the last semi-octet then processing shall commence with + // the next semi-octet and the intervening + // semi-octet shall be ignored" + // How does this jive with 24.008 10.5.4.7 + + b = (byte)((bytes[i] >> 4) & 0xf); + + if (b == 0xf && i + 1 == length + offset) { + //ignore final 0xf + break; + } + + c = bcdToChar(b); + if (c == 0) { + return; + } + + sb.append(c); + } + + } + + /** + * Like calledPartyBCDToString, but field does not start with a + * TOA byte. For example: SIM ADN extension fields + */ + + public static String + calledPartyBCDFragmentToString(byte [] bytes, int offset, int length) { + StringBuilder ret = new StringBuilder(length * 2); + + internalCalledPartyBCDFragmentToString(ret, bytes, offset, length); + + return ret.toString(); + } + + /** returns 0 on invalid value */ + private static char + bcdToChar(byte b) { + if (b < 0xa) { + return (char)('0' + b); + } else switch (b) { + case 0xa: return '*'; + case 0xb: return '#'; + case 0xc: return PAUSE; + case 0xd: return WILD; + + default: return 0; + } + } + + private static int + charToBCD(char c) { + if (c >= '0' && c <= '9') { + return c - '0'; + } else if (c == '*') { + return 0xa; + } else if (c == '#') { + return 0xb; + } else if (c == PAUSE) { + return 0xc; + } else if (c == WILD) { + return 0xd; + } else if (c == WAIT) { + return 0xe; + } else { + throw new RuntimeException ("invalid char for BCD " + c); + } + } + + /** + * Return true iff the network portion of <code>address</code> is, + * as far as we can tell on the device, suitable for use as an SMS + * destination address. + */ + public static boolean isWellFormedSmsAddress(String address) { + String networkPortion = + PhoneNumberUtils.extractNetworkPortion(address); + + return (!(networkPortion.equals("+") + || TextUtils.isEmpty(networkPortion))) + && isDialable(networkPortion); + } + + public static boolean isGlobalPhoneNumber(String phoneNumber) { + if (TextUtils.isEmpty(phoneNumber)) { + return false; + } + + Matcher match = GLOBAL_PHONE_NUMBER_PATTERN.matcher(phoneNumber); + return match.matches(); + } + + private static boolean isDialable(String address) { + for (int i = 0, count = address.length(); i < count; i++) { + if (!isDialable(address.charAt(i))) { + return false; + } + } + return true; + } + + private static boolean isNonSeparator(String address) { + for (int i = 0, count = address.length(); i < count; i++) { + if (!isNonSeparator(address.charAt(i))) { + return false; + } + } + return true; + } + /** + * Note: calls extractNetworkPortion(), so do not use for + * SIM EF[ADN] style records + * + * Returns null if network portion is empty. + */ + public static byte[] + networkPortionToCalledPartyBCD(String s) { + String networkPortion = extractNetworkPortion(s); + return numberToCalledPartyBCDHelper(networkPortion, false); + } + + /** + * Same as {@link #networkPortionToCalledPartyBCD}, but includes a + * one-byte length prefix. + */ + public static byte[] + networkPortionToCalledPartyBCDWithLength(String s) { + String networkPortion = extractNetworkPortion(s); + return numberToCalledPartyBCDHelper(networkPortion, true); + } + + /** + * Convert a dialing number to BCD byte array + * + * @param number dialing number string + * if the dialing number starts with '+', set to international TOA + * @return BCD byte array + */ + public static byte[] + numberToCalledPartyBCD(String number) { + return numberToCalledPartyBCDHelper(number, false); + } + + /** + * If includeLength is true, prepend a one-byte length value to + * the return array. + */ + private static byte[] + numberToCalledPartyBCDHelper(String number, boolean includeLength) { + int numberLenReal = number.length(); + int numberLenEffective = numberLenReal; + boolean hasPlus = number.indexOf('+') != -1; + if (hasPlus) numberLenEffective--; + + if (numberLenEffective == 0) return null; + + int resultLen = (numberLenEffective + 1) / 2; // Encoded numbers require only 4 bits each. + int extraBytes = 1; // Prepended TOA byte. + if (includeLength) extraBytes++; // Optional prepended length byte. + resultLen += extraBytes; + + byte[] result = new byte[resultLen]; + + int digitCount = 0; + for (int i = 0; i < numberLenReal; i++) { + char c = number.charAt(i); + if (c == '+') continue; + int shift = ((digitCount & 0x01) == 1) ? 4 : 0; + result[extraBytes + (digitCount >> 1)] |= (byte)((charToBCD(c) & 0x0F) << shift); + digitCount++; + } + + // 1-fill any trailing odd nibble/quartet. + if ((digitCount & 0x01) == 1) result[extraBytes + (digitCount >> 1)] |= 0xF0; + + int offset = 0; + if (includeLength) result[offset++] = (byte)(resultLen - 1); + result[offset] = (byte)(hasPlus ? TOA_International : TOA_Unknown); + + return result; + } + + //================ Number formatting ========================= + + /** The current locale is unknown, look for a country code or don't format */ + public static final int FORMAT_UNKNOWN = 0; + /** NANP formatting */ + public static final int FORMAT_NANP = 1; + /** Japanese formatting */ + public static final int FORMAT_JAPAN = 2; + + /** List of country codes for countries that use the NANP */ + private static final String[] NANP_COUNTRIES = new String[] { + "US", // United States + "CA", // Canada + "AS", // American Samoa + "AI", // Anguilla + "AG", // Antigua and Barbuda + "BS", // Bahamas + "BB", // Barbados + "BM", // Bermuda + "VG", // British Virgin Islands + "KY", // Cayman Islands + "DM", // Dominica + "DO", // Dominican Republic + "GD", // Grenada + "GU", // Guam + "JM", // Jamaica + "PR", // Puerto Rico + "MS", // Montserrat + "MP", // Northern Mariana Islands + "KN", // Saint Kitts and Nevis + "LC", // Saint Lucia + "VC", // Saint Vincent and the Grenadines + "TT", // Trinidad and Tobago + "TC", // Turks and Caicos Islands + "VI", // U.S. Virgin Islands + }; + + private static final String KOREA_ISO_COUNTRY_CODE = "KR"; + + private static final String JAPAN_ISO_COUNTRY_CODE = "JP"; + + /** + * Breaks the given number down and formats it according to the rules + * for the country the number is from. + * + * @param source The phone number to format + * @return A locally acceptable formatting of the input, or the raw input if + * formatting rules aren't known for the number + * + * @deprecated Use link #formatNumber(String phoneNumber, String defaultCountryIso) instead + */ + @Deprecated + public static String formatNumber(String source) { + SpannableStringBuilder text = new SpannableStringBuilder(source); + formatNumber(text, getFormatTypeForLocale(Locale.getDefault())); + return text.toString(); + } + + /** + * Formats the given number with the given formatting type. Currently + * {@link #FORMAT_NANP} and {@link #FORMAT_JAPAN} are supported as a formating type. + * + * @param source the phone number to format + * @param defaultFormattingType The default formatting rules to apply if the number does + * not begin with +[country_code] + * @return The phone number formatted with the given formatting type. + * + * @hide + * @deprecated Use link #formatNumber(String phoneNumber, String defaultCountryIso) instead + */ + @Deprecated + public static String formatNumber(String source, int defaultFormattingType) { + SpannableStringBuilder text = new SpannableStringBuilder(source); + formatNumber(text, defaultFormattingType); + return text.toString(); + } + + /** + * Returns the phone number formatting type for the given locale. + * + * @param locale The locale of interest, usually {@link Locale#getDefault()} + * @return The formatting type for the given locale, or FORMAT_UNKNOWN if the formatting + * rules are not known for the given locale + * + * @deprecated Use link #formatNumber(String phoneNumber, String defaultCountryIso) instead + */ + @Deprecated + public static int getFormatTypeForLocale(Locale locale) { + String country = locale.getCountry(); + + return getFormatTypeFromCountryCode(country); + } + + /** + * Formats a phone number in-place. Currently {@link #FORMAT_JAPAN} and {@link #FORMAT_NANP} + * is supported as a second argument. + * + * @param text The number to be formatted, will be modified with the formatting + * @param defaultFormattingType The default formatting rules to apply if the number does + * not begin with +[country_code] + * + * @deprecated Use link #formatNumber(String phoneNumber, String defaultCountryIso) instead + */ + @Deprecated + public static void formatNumber(Editable text, int defaultFormattingType) { + int formatType = defaultFormattingType; + + if (text.length() > 2 && text.charAt(0) == '+') { + if (text.charAt(1) == '1') { + formatType = FORMAT_NANP; + } else if (text.length() >= 3 && text.charAt(1) == '8' + && text.charAt(2) == '1') { + formatType = FORMAT_JAPAN; + } else { + formatType = FORMAT_UNKNOWN; + } + } + + switch (formatType) { + case FORMAT_NANP: + formatNanpNumber(text); + return; + case FORMAT_JAPAN: + formatJapaneseNumber(text); + return; + case FORMAT_UNKNOWN: + removeDashes(text); + return; + } + } + + private static final int NANP_STATE_DIGIT = 1; + private static final int NANP_STATE_PLUS = 2; + private static final int NANP_STATE_ONE = 3; + private static final int NANP_STATE_DASH = 4; + + /** + * Formats a phone number in-place using the NANP formatting rules. Numbers will be formatted + * as: + * + * <p><code> + * xxxxx + * xxx-xxxx + * xxx-xxx-xxxx + * 1-xxx-xxx-xxxx + * +1-xxx-xxx-xxxx + * </code></p> + * + * @param text the number to be formatted, will be modified with the formatting + * + * @deprecated Use link #formatNumber(String phoneNumber, String defaultCountryIso) instead + */ + @Deprecated + public static void formatNanpNumber(Editable text) { + int length = text.length(); + if (length > "+1-nnn-nnn-nnnn".length()) { + // The string is too long to be formatted + return; + } else if (length <= 5) { + // The string is either a shortcode or too short to be formatted + return; + } + + CharSequence saved = text.subSequence(0, length); + + // Strip the dashes first, as we're going to add them back + removeDashes(text); + length = text.length(); + + // When scanning the number we record where dashes need to be added, + // if they're non-0 at the end of the scan the dashes will be added in + // the proper places. + int dashPositions[] = new int[3]; + int numDashes = 0; + + int state = NANP_STATE_DIGIT; + int numDigits = 0; + for (int i = 0; i < length; i++) { + char c = text.charAt(i); + switch (c) { + case '1': + if (numDigits == 0 || state == NANP_STATE_PLUS) { + state = NANP_STATE_ONE; + break; + } + // fall through + case '2': + case '3': + case '4': + case '5': + case '6': + case '7': + case '8': + case '9': + case '0': + if (state == NANP_STATE_PLUS) { + // Only NANP number supported for now + text.replace(0, length, saved); + return; + } else if (state == NANP_STATE_ONE) { + // Found either +1 or 1, follow it up with a dash + dashPositions[numDashes++] = i; + } else if (state != NANP_STATE_DASH && (numDigits == 3 || numDigits == 6)) { + // Found a digit that should be after a dash that isn't + dashPositions[numDashes++] = i; + } + state = NANP_STATE_DIGIT; + numDigits++; + break; + + case '-': + state = NANP_STATE_DASH; + break; + + case '+': + if (i == 0) { + // Plus is only allowed as the first character + state = NANP_STATE_PLUS; + break; + } + // Fall through + default: + // Unknown character, bail on formatting + text.replace(0, length, saved); + return; + } + } + + if (numDigits == 7) { + // With 7 digits we want xxx-xxxx, not xxx-xxx-x + numDashes--; + } + + // Actually put the dashes in place + for (int i = 0; i < numDashes; i++) { + int pos = dashPositions[i]; + text.replace(pos + i, pos + i, "-"); + } + + // Remove trailing dashes + int len = text.length(); + while (len > 0) { + if (text.charAt(len - 1) == '-') { + text.delete(len - 1, len); + len--; + } else { + break; + } + } + } + + /** + * Formats a phone number in-place using the Japanese formatting rules. + * Numbers will be formatted as: + * + * <p><code> + * 03-xxxx-xxxx + * 090-xxxx-xxxx + * 0120-xxx-xxx + * +81-3-xxxx-xxxx + * +81-90-xxxx-xxxx + * </code></p> + * + * @param text the number to be formatted, will be modified with + * the formatting + * + * @deprecated Use link #formatNumber(String phoneNumber, String defaultCountryIso) instead + */ + @Deprecated + public static void formatJapaneseNumber(Editable text) { + JapanesePhoneNumberFormatter.format(text); + } + + /** + * Removes all dashes from the number. + * + * @param text the number to clear from dashes + */ + private static void removeDashes(Editable text) { + int p = 0; + while (p < text.length()) { + if (text.charAt(p) == '-') { + text.delete(p, p + 1); + } else { + p++; + } + } + } + + /** + * Formats the specified {@code phoneNumber} to the E.164 representation. + * + * @param phoneNumber the phone number to format. + * @param defaultCountryIso the ISO 3166-1 two letters country code. + * @return the E.164 representation, or null if the given phone number is not valid. + */ + public static String formatNumberToE164(String phoneNumber, String defaultCountryIso) { + return formatNumberInternal(phoneNumber, defaultCountryIso, PhoneNumberFormat.E164); + } + + /** + * Formats the specified {@code phoneNumber} to the RFC3966 representation. + * + * @param phoneNumber the phone number to format. + * @param defaultCountryIso the ISO 3166-1 two letters country code. + * @return the RFC3966 representation, or null if the given phone number is not valid. + */ + public static String formatNumberToRFC3966(String phoneNumber, String defaultCountryIso) { + return formatNumberInternal(phoneNumber, defaultCountryIso, PhoneNumberFormat.RFC3966); + } + + /** + * Formats the raw phone number (string) using the specified {@code formatIdentifier}. + * <p> + * The given phone number must have an area code and could have a country code. + * <p> + * The defaultCountryIso is used to validate the given number and generate the formatted number + * if the specified number doesn't have a country code. + * + * @param rawPhoneNumber The phone number to format. + * @param defaultCountryIso The ISO 3166-1 two letters country code. + * @param formatIdentifier The (enum) identifier of the desired format. + * @return the formatted representation, or null if the specified number is not valid. + */ + private static String formatNumberInternal( + String rawPhoneNumber, String defaultCountryIso, PhoneNumberFormat formatIdentifier) { + + PhoneNumberUtil util = PhoneNumberUtil.getInstance(); + try { + PhoneNumber phoneNumber = util.parse(rawPhoneNumber, defaultCountryIso); + if (util.isValidNumber(phoneNumber)) { + return util.format(phoneNumber, formatIdentifier); + } + } catch (NumberParseException ignored) { } + + return null; + } + + /** + * Determines if a {@param phoneNumber} is international if dialed from + * {@param defaultCountryIso}. + * + * @param phoneNumber The phone number. + * @param defaultCountryIso The current country ISO. + * @return {@code true} if the number is international, {@code false} otherwise. + * @hide + */ + public static boolean isInternationalNumber(String phoneNumber, String defaultCountryIso) { + // If no phone number is provided, it can't be international. + if (TextUtils.isEmpty(phoneNumber)) { + return false; + } + + // If it starts with # or * its not international. + if (phoneNumber.startsWith("#") || phoneNumber.startsWith("*")) { + return false; + } + + PhoneNumberUtil util = PhoneNumberUtil.getInstance(); + try { + PhoneNumber pn = util.parseAndKeepRawInput(phoneNumber, defaultCountryIso); + return pn.getCountryCode() != util.getCountryCodeForRegion(defaultCountryIso); + } catch (NumberParseException e) { + return false; + } + } + + /** + * Format a phone number. + * <p> + * If the given number doesn't have the country code, the phone will be + * formatted to the default country's convention. + * + * @param phoneNumber + * the number to be formatted. + * @param defaultCountryIso + * the ISO 3166-1 two letters country code whose convention will + * be used if the given number doesn't have the country code. + * @return the formatted number, or null if the given number is not valid. + */ + public static String formatNumber(String phoneNumber, String defaultCountryIso) { + // Do not attempt to format numbers that start with a hash or star symbol. + if (phoneNumber.startsWith("#") || phoneNumber.startsWith("*")) { + return phoneNumber; + } + + PhoneNumberUtil util = PhoneNumberUtil.getInstance(); + String result = null; + try { + PhoneNumber pn = util.parseAndKeepRawInput(phoneNumber, defaultCountryIso); + + if (KOREA_ISO_COUNTRY_CODE.equalsIgnoreCase(defaultCountryIso) && + (pn.getCountryCode() == util.getCountryCodeForRegion(KOREA_ISO_COUNTRY_CODE)) && + (pn.getCountryCodeSource() == + PhoneNumber.CountryCodeSource.FROM_NUMBER_WITH_PLUS_SIGN)) { + /** + * Need to reformat any local Korean phone numbers (when the user is in Korea) with + * country code to corresponding national format which would replace the leading + * +82 with 0. + */ + result = util.format(pn, PhoneNumberUtil.PhoneNumberFormat.NATIONAL); + } else if (JAPAN_ISO_COUNTRY_CODE.equalsIgnoreCase(defaultCountryIso) && + pn.getCountryCode() == util.getCountryCodeForRegion(JAPAN_ISO_COUNTRY_CODE) && + (pn.getCountryCodeSource() == + PhoneNumber.CountryCodeSource.FROM_NUMBER_WITH_PLUS_SIGN)) { + /** + * Need to reformat Japanese phone numbers (when user is in Japan) with the national + * dialing format. + */ + result = util.format(pn, PhoneNumberUtil.PhoneNumberFormat.NATIONAL); + } else { + result = util.formatInOriginalFormat(pn, defaultCountryIso); + } + } catch (NumberParseException e) { + } + return result; + } + + /** + * Format the phone number only if the given number hasn't been formatted. + * <p> + * The number which has only dailable character is treated as not being + * formatted. + * + * @param phoneNumber + * the number to be formatted. + * @param phoneNumberE164 + * the E164 format number whose country code is used if the given + * phoneNumber doesn't have the country code. + * @param defaultCountryIso + * the ISO 3166-1 two letters country code whose convention will + * be used if the phoneNumberE164 is null or invalid, or if phoneNumber + * contains IDD. + * @return the formatted number if the given number has been formatted, + * otherwise, return the given number. + */ + public static String formatNumber( + String phoneNumber, String phoneNumberE164, String defaultCountryIso) { + int len = phoneNumber.length(); + for (int i = 0; i < len; i++) { + if (!isDialable(phoneNumber.charAt(i))) { + return phoneNumber; + } + } + PhoneNumberUtil util = PhoneNumberUtil.getInstance(); + // Get the country code from phoneNumberE164 + if (phoneNumberE164 != null && phoneNumberE164.length() >= 2 + && phoneNumberE164.charAt(0) == '+') { + try { + // The number to be parsed is in E164 format, so the default region used doesn't + // matter. + PhoneNumber pn = util.parse(phoneNumberE164, "ZZ"); + String regionCode = util.getRegionCodeForNumber(pn); + if (!TextUtils.isEmpty(regionCode) && + // This makes sure phoneNumber doesn't contain an IDD + normalizeNumber(phoneNumber).indexOf(phoneNumberE164.substring(1)) <= 0) { + defaultCountryIso = regionCode; + } + } catch (NumberParseException e) { + } + } + String result = formatNumber(phoneNumber, defaultCountryIso); + return result != null ? result : phoneNumber; + } + + /** + * Normalize a phone number by removing the characters other than digits. If + * the given number has keypad letters, the letters will be converted to + * digits first. + * + * @param phoneNumber the number to be normalized. + * @return the normalized number. + */ + public static String normalizeNumber(String phoneNumber) { + if (TextUtils.isEmpty(phoneNumber)) { + return ""; + } + + StringBuilder sb = new StringBuilder(); + int len = phoneNumber.length(); + for (int i = 0; i < len; i++) { + char c = phoneNumber.charAt(i); + // Character.digit() supports ASCII and Unicode digits (fullwidth, Arabic-Indic, etc.) + int digit = Character.digit(c, 10); + if (digit != -1) { + sb.append(digit); + } else if (sb.length() == 0 && c == '+') { + sb.append(c); + } else if ((c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z')) { + return normalizeNumber(PhoneNumberUtils.convertKeypadLettersToDigits(phoneNumber)); + } + } + return sb.toString(); + } + + /** + * Replaces all unicode(e.g. Arabic, Persian) digits with their decimal digit equivalents. + * + * @param number the number to perform the replacement on. + * @return the replaced number. + */ + public static String replaceUnicodeDigits(String number) { + StringBuilder normalizedDigits = new StringBuilder(number.length()); + for (char c : number.toCharArray()) { + int digit = Character.digit(c, 10); + if (digit != -1) { + normalizedDigits.append(digit); + } else { + normalizedDigits.append(c); + } + } + return normalizedDigits.toString(); + } + + // Three and four digit phone numbers for either special services, + // or 3-6 digit addresses from the network (eg carrier-originated SMS messages) should + // not match. + // + // This constant used to be 5, but SMS short codes has increased in length and + // can be easily 6 digits now days. Most countries have SMS short code length between + // 3 to 6 digits. The exceptions are + // + // Australia: Short codes are six or eight digits in length, starting with the prefix "19" + // followed by an additional four or six digits and two. + // Czechia: Codes are seven digits in length for MO and five (not billed) or + // eight (billed) for MT direction + // + // see http://en.wikipedia.org/wiki/Short_code#Regional_differences for reference + // + // However, in order to loose match 650-555-1212 and 555-1212, we need to set the min match + // to 7. + static final int MIN_MATCH = 7; + + /** + * Checks a given number against the list of + * emergency numbers provided by the RIL and SIM card. + * + * @param number the number to look up. + * @return true if the number is in the list of emergency numbers + * listed in the RIL / SIM, otherwise return false. + */ + public static boolean isEmergencyNumber(String number) { + return isEmergencyNumber(getDefaultVoiceSubId(), number); + } + + /** + * Checks a given number against the list of + * emergency numbers provided by the RIL and SIM card. + * + * @param subId the subscription id of the SIM. + * @param number the number to look up. + * @return true if the number is in the list of emergency numbers + * listed in the RIL / SIM, otherwise return false. + * @hide + */ + public static boolean isEmergencyNumber(int subId, String number) { + // Return true only if the specified number *exactly* matches + // one of the emergency numbers listed by the RIL / SIM. + return isEmergencyNumberInternal(subId, number, true /* useExactMatch */); + } + + /** + * Checks if given number might *potentially* result in + * a call to an emergency service on the current network. + * + * Specifically, this method will return true if the specified number + * is an emergency number according to the list managed by the RIL or + * SIM, *or* if the specified number simply starts with the same + * digits as any of the emergency numbers listed in the RIL / SIM. + * + * This method is intended for internal use by the phone app when + * deciding whether to allow ACTION_CALL intents from 3rd party apps + * (where we're required to *not* allow emergency calls to be placed.) + * + * @param number the number to look up. + * @return true if the number is in the list of emergency numbers + * listed in the RIL / SIM, *or* if the number starts with the + * same digits as any of those emergency numbers. + * + * @hide + */ + public static boolean isPotentialEmergencyNumber(String number) { + return isPotentialEmergencyNumber(getDefaultVoiceSubId(), number); + } + + /** + * Checks if given number might *potentially* result in + * a call to an emergency service on the current network. + * + * Specifically, this method will return true if the specified number + * is an emergency number according to the list managed by the RIL or + * SIM, *or* if the specified number simply starts with the same + * digits as any of the emergency numbers listed in the RIL / SIM. + * + * This method is intended for internal use by the phone app when + * deciding whether to allow ACTION_CALL intents from 3rd party apps + * (where we're required to *not* allow emergency calls to be placed.) + * + * @param subId the subscription id of the SIM. + * @param number the number to look up. + * @return true if the number is in the list of emergency numbers + * listed in the RIL / SIM, *or* if the number starts with the + * same digits as any of those emergency numbers. + * @hide + */ + public static boolean isPotentialEmergencyNumber(int subId, String number) { + // Check against the emergency numbers listed by the RIL / SIM, + // and *don't* require an exact match. + return isEmergencyNumberInternal(subId, number, false /* useExactMatch */); + } + + /** + * Helper function for isEmergencyNumber(String) and + * isPotentialEmergencyNumber(String). + * + * @param number the number to look up. + * + * @param useExactMatch if true, consider a number to be an emergency + * number only if it *exactly* matches a number listed in + * the RIL / SIM. If false, a number is considered to be an + * emergency number if it simply starts with the same digits + * as any of the emergency numbers listed in the RIL / SIM. + * (Setting useExactMatch to false allows you to identify + * number that could *potentially* result in emergency calls + * since many networks will actually ignore trailing digits + * after a valid emergency number.) + * + * @return true if the number is in the list of emergency numbers + * listed in the RIL / sim, otherwise return false. + */ + private static boolean isEmergencyNumberInternal(String number, boolean useExactMatch) { + return isEmergencyNumberInternal(getDefaultVoiceSubId(), number, useExactMatch); + } + + /** + * Helper function for isEmergencyNumber(String) and + * isPotentialEmergencyNumber(String). + * + * @param subId the subscription id of the SIM. + * @param number the number to look up. + * + * @param useExactMatch if true, consider a number to be an emergency + * number only if it *exactly* matches a number listed in + * the RIL / SIM. If false, a number is considered to be an + * emergency number if it simply starts with the same digits + * as any of the emergency numbers listed in the RIL / SIM. + * (Setting useExactMatch to false allows you to identify + * number that could *potentially* result in emergency calls + * since many networks will actually ignore trailing digits + * after a valid emergency number.) + * + * @return true if the number is in the list of emergency numbers + * listed in the RIL / sim, otherwise return false. + */ + private static boolean isEmergencyNumberInternal(int subId, String number, + boolean useExactMatch) { + return isEmergencyNumberInternal(subId, number, null, useExactMatch); + } + + /** + * Checks if a given number is an emergency number for a specific country. + * + * @param number the number to look up. + * @param defaultCountryIso the specific country which the number should be checked against + * @return if the number is an emergency number for the specific country, then return true, + * otherwise false + * + * @hide + */ + public static boolean isEmergencyNumber(String number, String defaultCountryIso) { + return isEmergencyNumber(getDefaultVoiceSubId(), number, defaultCountryIso); + } + + /** + * Checks if a given number is an emergency number for a specific country. + * + * @param subId the subscription id of the SIM. + * @param number the number to look up. + * @param defaultCountryIso the specific country which the number should be checked against + * @return if the number is an emergency number for the specific country, then return true, + * otherwise false + * @hide + */ + public static boolean isEmergencyNumber(int subId, String number, String defaultCountryIso) { + return isEmergencyNumberInternal(subId, number, + defaultCountryIso, + true /* useExactMatch */); + } + + /** + * Checks if a given number might *potentially* result in a call to an + * emergency service, for a specific country. + * + * Specifically, this method will return true if the specified number + * is an emergency number in the specified country, *or* if the number + * simply starts with the same digits as any emergency number for that + * country. + * + * This method is intended for internal use by the phone app when + * deciding whether to allow ACTION_CALL intents from 3rd party apps + * (where we're required to *not* allow emergency calls to be placed.) + * + * @param number the number to look up. + * @param defaultCountryIso the specific country which the number should be checked against + * @return true if the number is an emergency number for the specific + * country, *or* if the number starts with the same digits as + * any of those emergency numbers. + * + * @hide + */ + public static boolean isPotentialEmergencyNumber(String number, String defaultCountryIso) { + return isPotentialEmergencyNumber(getDefaultVoiceSubId(), number, defaultCountryIso); + } + + /** + * Checks if a given number might *potentially* result in a call to an + * emergency service, for a specific country. + * + * Specifically, this method will return true if the specified number + * is an emergency number in the specified country, *or* if the number + * simply starts with the same digits as any emergency number for that + * country. + * + * This method is intended for internal use by the phone app when + * deciding whether to allow ACTION_CALL intents from 3rd party apps + * (where we're required to *not* allow emergency calls to be placed.) + * + * @param subId the subscription id of the SIM. + * @param number the number to look up. + * @param defaultCountryIso the specific country which the number should be checked against + * @return true if the number is an emergency number for the specific + * country, *or* if the number starts with the same digits as + * any of those emergency numbers. + * @hide + */ + public static boolean isPotentialEmergencyNumber(int subId, String number, + String defaultCountryIso) { + return isEmergencyNumberInternal(subId, number, + defaultCountryIso, + false /* useExactMatch */); + } + + /** + * Helper function for isEmergencyNumber(String, String) and + * isPotentialEmergencyNumber(String, String). + * + * @param number the number to look up. + * @param defaultCountryIso the specific country which the number should be checked against + * @param useExactMatch if true, consider a number to be an emergency + * number only if it *exactly* matches a number listed in + * the RIL / SIM. If false, a number is considered to be an + * emergency number if it simply starts with the same digits + * as any of the emergency numbers listed in the RIL / SIM. + * + * @return true if the number is an emergency number for the specified country. + */ + private static boolean isEmergencyNumberInternal(String number, + String defaultCountryIso, + boolean useExactMatch) { + return isEmergencyNumberInternal(getDefaultVoiceSubId(), number, defaultCountryIso, + useExactMatch); + } + + /** + * Helper function for isEmergencyNumber(String, String) and + * isPotentialEmergencyNumber(String, String). + * + * @param subId the subscription id of the SIM. + * @param number the number to look up. + * @param defaultCountryIso the specific country which the number should be checked against + * @param useExactMatch if true, consider a number to be an emergency + * number only if it *exactly* matches a number listed in + * the RIL / SIM. If false, a number is considered to be an + * emergency number if it simply starts with the same digits + * as any of the emergency numbers listed in the RIL / SIM. + * + * @return true if the number is an emergency number for the specified country. + * @hide + */ + private static boolean isEmergencyNumberInternal(int subId, String number, + String defaultCountryIso, + boolean useExactMatch) { + // If the number passed in is null, just return false: + if (number == null) return false; + + // If the number passed in is a SIP address, return false, since the + // concept of "emergency numbers" is only meaningful for calls placed + // over the cell network. + // (Be sure to do this check *before* calling extractNetworkPortionAlt(), + // since the whole point of extractNetworkPortionAlt() is to filter out + // any non-dialable characters (which would turn 'abc911def@example.com' + // into '911', for example.)) + if (isUriNumber(number)) { + return false; + } + + // Strip the separators from the number before comparing it + // to the list. + number = extractNetworkPortionAlt(number); + + String emergencyNumbers = ""; + int slotId = SubscriptionManager.getSlotIndex(subId); + + // retrieve the list of emergency numbers + // check read-write ecclist property first + String ecclist = (slotId <= 0) ? "ril.ecclist" : ("ril.ecclist" + slotId); + + emergencyNumbers = SystemProperties.get(ecclist, ""); + + Rlog.d(LOG_TAG, "slotId:" + slotId + " subId:" + subId + " country:" + + defaultCountryIso + " emergencyNumbers: " + emergencyNumbers); + + if (TextUtils.isEmpty(emergencyNumbers)) { + // then read-only ecclist property since old RIL only uses this + emergencyNumbers = SystemProperties.get("ro.ril.ecclist"); + } + + if (!TextUtils.isEmpty(emergencyNumbers)) { + // searches through the comma-separated list for a match, + // return true if one is found. + for (String emergencyNum : emergencyNumbers.split(",")) { + // It is not possible to append additional digits to an emergency number to dial + // the number in Brazil - it won't connect. + if (useExactMatch || "BR".equalsIgnoreCase(defaultCountryIso)) { + if (number.equals(emergencyNum)) { + return true; + } + } else { + if (number.startsWith(emergencyNum)) { + return true; + } + } + } + // no matches found against the list! + return false; + } + + Rlog.d(LOG_TAG, "System property doesn't provide any emergency numbers." + + " Use embedded logic for determining ones."); + + // If slot id is invalid, means that there is no sim card. + // According spec 3GPP TS22.101, the following numbers should be + // ECC numbers when SIM/USIM is not present. + emergencyNumbers = ((slotId < 0) ? "112,911,000,08,110,118,119,999" : "112,911"); + + for (String emergencyNum : emergencyNumbers.split(",")) { + if (useExactMatch) { + if (number.equals(emergencyNum)) { + return true; + } + } else { + if (number.startsWith(emergencyNum)) { + return true; + } + } + } + + // No ecclist system property, so use our own list. + if (defaultCountryIso != null) { + ShortNumberInfo info = ShortNumberInfo.getInstance(); + if (useExactMatch) { + return info.isEmergencyNumber(number, defaultCountryIso); + } else { + return info.connectsToEmergencyNumber(number, defaultCountryIso); + } + } + + return false; + } + + /** + * Checks if a given number is an emergency number for the country that the user is in. + * + * @param number the number to look up. + * @param context the specific context which the number should be checked against + * @return true if the specified number is an emergency number for the country the user + * is currently in. + */ + public static boolean isLocalEmergencyNumber(Context context, String number) { + return isLocalEmergencyNumber(context, getDefaultVoiceSubId(), number); + } + + /** + * Checks if a given number is an emergency number for the country that the user is in. + * + * @param subId the subscription id of the SIM. + * @param number the number to look up. + * @param context the specific context which the number should be checked against + * @return true if the specified number is an emergency number for the country the user + * is currently in. + * @hide + */ + public static boolean isLocalEmergencyNumber(Context context, int subId, String number) { + return isLocalEmergencyNumberInternal(subId, number, + context, + true /* useExactMatch */); + } + + /** + * Checks if a given number might *potentially* result in a call to an + * emergency service, for the country that the user is in. The current + * country is determined using the CountryDetector. + * + * Specifically, this method will return true if the specified number + * is an emergency number in the current country, *or* if the number + * simply starts with the same digits as any emergency number for the + * current country. + * + * This method is intended for internal use by the phone app when + * deciding whether to allow ACTION_CALL intents from 3rd party apps + * (where we're required to *not* allow emergency calls to be placed.) + * + * @param number the number to look up. + * @param context the specific context which the number should be checked against + * @return true if the specified number is an emergency number for a local country, based on the + * CountryDetector. + * + * @see android.location.CountryDetector + * @hide + */ + public static boolean isPotentialLocalEmergencyNumber(Context context, String number) { + return isPotentialLocalEmergencyNumber(context, getDefaultVoiceSubId(), number); + } + + /** + * Checks if a given number might *potentially* result in a call to an + * emergency service, for the country that the user is in. The current + * country is determined using the CountryDetector. + * + * Specifically, this method will return true if the specified number + * is an emergency number in the current country, *or* if the number + * simply starts with the same digits as any emergency number for the + * current country. + * + * This method is intended for internal use by the phone app when + * deciding whether to allow ACTION_CALL intents from 3rd party apps + * (where we're required to *not* allow emergency calls to be placed.) + * + * @param subId the subscription id of the SIM. + * @param number the number to look up. + * @param context the specific context which the number should be checked against + * @return true if the specified number is an emergency number for a local country, based on the + * CountryDetector. + * + * @hide + */ + public static boolean isPotentialLocalEmergencyNumber(Context context, int subId, + String number) { + return isLocalEmergencyNumberInternal(subId, number, + context, + false /* useExactMatch */); + } + + /** + * Helper function for isLocalEmergencyNumber() and + * isPotentialLocalEmergencyNumber(). + * + * @param number the number to look up. + * @param context the specific context which the number should be checked against + * @param useExactMatch if true, consider a number to be an emergency + * number only if it *exactly* matches a number listed in + * the RIL / SIM. If false, a number is considered to be an + * emergency number if it simply starts with the same digits + * as any of the emergency numbers listed in the RIL / SIM. + * + * @return true if the specified number is an emergency number for a + * local country, based on the CountryDetector. + * + * @see android.location.CountryDetector + * @hide + */ + private static boolean isLocalEmergencyNumberInternal(String number, + Context context, + boolean useExactMatch) { + return isLocalEmergencyNumberInternal(getDefaultVoiceSubId(), number, context, + useExactMatch); + } + + /** + * Helper function for isLocalEmergencyNumber() and + * isPotentialLocalEmergencyNumber(). + * + * @param subId the subscription id of the SIM. + * @param number the number to look up. + * @param context the specific context which the number should be checked against + * @param useExactMatch if true, consider a number to be an emergency + * number only if it *exactly* matches a number listed in + * the RIL / SIM. If false, a number is considered to be an + * emergency number if it simply starts with the same digits + * as any of the emergency numbers listed in the RIL / SIM. + * + * @return true if the specified number is an emergency number for a + * local country, based on the CountryDetector. + * @hide + */ + private static boolean isLocalEmergencyNumberInternal(int subId, String number, + Context context, + boolean useExactMatch) { + String countryIso; + CountryDetector detector = (CountryDetector) context.getSystemService( + Context.COUNTRY_DETECTOR); + if (detector != null && detector.detectCountry() != null) { + countryIso = detector.detectCountry().getCountryIso(); + } else { + Locale locale = context.getResources().getConfiguration().locale; + countryIso = locale.getCountry(); + Rlog.w(LOG_TAG, "No CountryDetector; falling back to countryIso based on locale: " + + countryIso); + } + return isEmergencyNumberInternal(subId, number, countryIso, useExactMatch); + } + + /** + * isVoiceMailNumber: checks a given number against the voicemail + * number provided by the RIL and SIM card. The caller must have + * the READ_PHONE_STATE credential. + * + * @param number the number to look up. + * @return true if the number is in the list of voicemail. False + * otherwise, including if the caller does not have the permission + * to read the VM number. + */ + public static boolean isVoiceMailNumber(String number) { + return isVoiceMailNumber(SubscriptionManager.getDefaultSubscriptionId(), number); + } + + /** + * isVoiceMailNumber: checks a given number against the voicemail + * number provided by the RIL and SIM card. The caller must have + * the READ_PHONE_STATE credential. + * + * @param subId the subscription id of the SIM. + * @param number the number to look up. + * @return true if the number is in the list of voicemail. False + * otherwise, including if the caller does not have the permission + * to read the VM number. + * @hide + */ + public static boolean isVoiceMailNumber(int subId, String number) { + return isVoiceMailNumber(null, subId, number); + } + + /** + * isVoiceMailNumber: checks a given number against the voicemail + * number provided by the RIL and SIM card. The caller must have + * the READ_PHONE_STATE credential. + * + * @param context {@link Context}. + * @param subId the subscription id of the SIM. + * @param number the number to look up. + * @return true if the number is in the list of voicemail. False + * otherwise, including if the caller does not have the permission + * to read the VM number. + * @hide + */ + public static boolean isVoiceMailNumber(Context context, int subId, String number) { + String vmNumber, mdn; + try { + final TelephonyManager tm; + if (context == null) { + tm = TelephonyManager.getDefault(); + if (DBG) log("isVoiceMailNumber: default tm"); + } else { + tm = TelephonyManager.from(context); + if (DBG) log("isVoiceMailNumber: tm from context"); + } + vmNumber = tm.getVoiceMailNumber(subId); + mdn = tm.getLine1Number(subId); + if (DBG) log("isVoiceMailNumber: mdn=" + mdn + ", vmNumber=" + vmNumber + + ", number=" + number); + } catch (SecurityException ex) { + if (DBG) log("isVoiceMailNumber: SecurityExcpetion caught"); + return false; + } + // Strip the separators from the number before comparing it + // to the list. + number = extractNetworkPortionAlt(number); + if (TextUtils.isEmpty(number)) { + if (DBG) log("isVoiceMailNumber: number is empty after stripping"); + return false; + } + + // check if the carrier considers MDN to be an additional voicemail number + boolean compareWithMdn = false; + if (context != null) { + CarrierConfigManager configManager = (CarrierConfigManager) + context.getSystemService(Context.CARRIER_CONFIG_SERVICE); + if (configManager != null) { + PersistableBundle b = configManager.getConfigForSubId(subId); + if (b != null) { + compareWithMdn = b.getBoolean(CarrierConfigManager. + KEY_MDN_IS_ADDITIONAL_VOICEMAIL_NUMBER_BOOL); + if (DBG) log("isVoiceMailNumber: compareWithMdn=" + compareWithMdn); + } + } + } + + if (compareWithMdn) { + if (DBG) log("isVoiceMailNumber: treating mdn as additional vm number"); + return compare(number, vmNumber) || compare(number, mdn); + } else { + if (DBG) log("isVoiceMailNumber: returning regular compare"); + return compare(number, vmNumber); + } + } + + /** + * Translates any alphabetic letters (i.e. [A-Za-z]) in the + * specified phone number into the equivalent numeric digits, + * according to the phone keypad letter mapping described in + * ITU E.161 and ISO/IEC 9995-8. + * + * @return the input string, with alpha letters converted to numeric + * digits using the phone keypad letter mapping. For example, + * an input of "1-800-GOOG-411" will return "1-800-4664-411". + */ + public static String convertKeypadLettersToDigits(String input) { + if (input == null) { + return input; + } + int len = input.length(); + if (len == 0) { + return input; + } + + char[] out = input.toCharArray(); + + for (int i = 0; i < len; i++) { + char c = out[i]; + // If this char isn't in KEYPAD_MAP at all, just leave it alone. + out[i] = (char) KEYPAD_MAP.get(c, c); + } + + return new String(out); + } + + /** + * The phone keypad letter mapping (see ITU E.161 or ISO/IEC 9995-8.) + * TODO: This should come from a resource. + */ + private static final SparseIntArray KEYPAD_MAP = new SparseIntArray(); + static { + KEYPAD_MAP.put('a', '2'); KEYPAD_MAP.put('b', '2'); KEYPAD_MAP.put('c', '2'); + KEYPAD_MAP.put('A', '2'); KEYPAD_MAP.put('B', '2'); KEYPAD_MAP.put('C', '2'); + + KEYPAD_MAP.put('d', '3'); KEYPAD_MAP.put('e', '3'); KEYPAD_MAP.put('f', '3'); + KEYPAD_MAP.put('D', '3'); KEYPAD_MAP.put('E', '3'); KEYPAD_MAP.put('F', '3'); + + KEYPAD_MAP.put('g', '4'); KEYPAD_MAP.put('h', '4'); KEYPAD_MAP.put('i', '4'); + KEYPAD_MAP.put('G', '4'); KEYPAD_MAP.put('H', '4'); KEYPAD_MAP.put('I', '4'); + + KEYPAD_MAP.put('j', '5'); KEYPAD_MAP.put('k', '5'); KEYPAD_MAP.put('l', '5'); + KEYPAD_MAP.put('J', '5'); KEYPAD_MAP.put('K', '5'); KEYPAD_MAP.put('L', '5'); + + KEYPAD_MAP.put('m', '6'); KEYPAD_MAP.put('n', '6'); KEYPAD_MAP.put('o', '6'); + KEYPAD_MAP.put('M', '6'); KEYPAD_MAP.put('N', '6'); KEYPAD_MAP.put('O', '6'); + + KEYPAD_MAP.put('p', '7'); KEYPAD_MAP.put('q', '7'); KEYPAD_MAP.put('r', '7'); KEYPAD_MAP.put('s', '7'); + KEYPAD_MAP.put('P', '7'); KEYPAD_MAP.put('Q', '7'); KEYPAD_MAP.put('R', '7'); KEYPAD_MAP.put('S', '7'); + + KEYPAD_MAP.put('t', '8'); KEYPAD_MAP.put('u', '8'); KEYPAD_MAP.put('v', '8'); + KEYPAD_MAP.put('T', '8'); KEYPAD_MAP.put('U', '8'); KEYPAD_MAP.put('V', '8'); + + KEYPAD_MAP.put('w', '9'); KEYPAD_MAP.put('x', '9'); KEYPAD_MAP.put('y', '9'); KEYPAD_MAP.put('z', '9'); + KEYPAD_MAP.put('W', '9'); KEYPAD_MAP.put('X', '9'); KEYPAD_MAP.put('Y', '9'); KEYPAD_MAP.put('Z', '9'); + } + + //================ Plus Code formatting ========================= + private static final char PLUS_SIGN_CHAR = '+'; + private static final String PLUS_SIGN_STRING = "+"; + private static final String NANP_IDP_STRING = "011"; + private static final int NANP_LENGTH = 10; + + /** + * This function checks if there is a plus sign (+) in the passed-in dialing number. + * If there is, it processes the plus sign based on the default telephone + * numbering plan of the system when the phone is activated and the current + * telephone numbering plan of the system that the phone is camped on. + * Currently, we only support the case that the default and current telephone + * numbering plans are North American Numbering Plan(NANP). + * + * The passed-in dialStr should only contain the valid format as described below, + * 1) the 1st character in the dialStr should be one of the really dialable + * characters listed below + * ISO-LATIN characters 0-9, *, # , + + * 2) the dialStr should already strip out the separator characters, + * every character in the dialStr should be one of the non separator characters + * listed below + * ISO-LATIN characters 0-9, *, # , +, WILD, WAIT, PAUSE + * + * Otherwise, this function returns the dial string passed in + * + * @param dialStr the original dial string + * @return the converted dial string if the current/default countries belong to NANP, + * and if there is the "+" in the original dial string. Otherwise, the original dial + * string returns. + * + * This API is for CDMA only + * + * @hide TODO: pending API Council approval + */ + public static String cdmaCheckAndProcessPlusCode(String dialStr) { + if (!TextUtils.isEmpty(dialStr)) { + if (isReallyDialable(dialStr.charAt(0)) && + isNonSeparator(dialStr)) { + String currIso = TelephonyManager.getDefault().getNetworkCountryIso(); + String defaultIso = TelephonyManager.getDefault().getSimCountryIso(); + if (!TextUtils.isEmpty(currIso) && !TextUtils.isEmpty(defaultIso)) { + return cdmaCheckAndProcessPlusCodeByNumberFormat(dialStr, + getFormatTypeFromCountryCode(currIso), + getFormatTypeFromCountryCode(defaultIso)); + } + } + } + return dialStr; + } + + /** + * Process phone number for CDMA, converting plus code using the home network number format. + * This is used for outgoing SMS messages. + * + * @param dialStr the original dial string + * @return the converted dial string + * @hide for internal use + */ + public static String cdmaCheckAndProcessPlusCodeForSms(String dialStr) { + if (!TextUtils.isEmpty(dialStr)) { + if (isReallyDialable(dialStr.charAt(0)) && isNonSeparator(dialStr)) { + String defaultIso = TelephonyManager.getDefault().getSimCountryIso(); + if (!TextUtils.isEmpty(defaultIso)) { + int format = getFormatTypeFromCountryCode(defaultIso); + return cdmaCheckAndProcessPlusCodeByNumberFormat(dialStr, format, format); + } + } + } + return dialStr; + } + + /** + * This function should be called from checkAndProcessPlusCode only + * And it is used for test purpose also. + * + * It checks the dial string by looping through the network portion, + * post dial portion 1, post dial porting 2, etc. If there is any + * plus sign, then process the plus sign. + * Currently, this function supports the plus sign conversion within NANP only. + * Specifically, it handles the plus sign in the following ways: + * 1)+1NANP,remove +, e.g. + * +18475797000 is converted to 18475797000, + * 2)+NANP or +non-NANP Numbers,replace + with the current NANP IDP, e.g, + * +8475797000 is converted to 0118475797000, + * +11875767800 is converted to 01111875767800 + * 3)+1NANP in post dial string(s), e.g. + * 8475797000;+18475231753 is converted to 8475797000;18475231753 + * + * + * @param dialStr the original dial string + * @param currFormat the numbering system of the current country that the phone is camped on + * @param defaultFormat the numbering system of the country that the phone is activated on + * @return the converted dial string if the current/default countries belong to NANP, + * and if there is the "+" in the original dial string. Otherwise, the original dial + * string returns. + * + * @hide + */ + public static String + cdmaCheckAndProcessPlusCodeByNumberFormat(String dialStr,int currFormat,int defaultFormat) { + String retStr = dialStr; + + boolean useNanp = (currFormat == defaultFormat) && (currFormat == FORMAT_NANP); + + // Checks if the plus sign character is in the passed-in dial string + if (dialStr != null && + dialStr.lastIndexOf(PLUS_SIGN_STRING) != -1) { + + // Handle case where default and current telephone numbering plans are NANP. + String postDialStr = null; + String tempDialStr = dialStr; + + // Sets the retStr to null since the conversion will be performed below. + retStr = null; + if (DBG) log("checkAndProcessPlusCode,dialStr=" + dialStr); + // This routine is to process the plus sign in the dial string by loop through + // the network portion, post dial portion 1, post dial portion 2... etc. if + // applied + do { + String networkDialStr; + // Format the string based on the rules for the country the number is from, + // and the current country the phone is camped + if (useNanp) { + networkDialStr = extractNetworkPortion(tempDialStr); + } else { + networkDialStr = extractNetworkPortionAlt(tempDialStr); + + } + + networkDialStr = processPlusCode(networkDialStr, useNanp); + + // Concatenates the string that is converted from network portion + if (!TextUtils.isEmpty(networkDialStr)) { + if (retStr == null) { + retStr = networkDialStr; + } else { + retStr = retStr.concat(networkDialStr); + } + } else { + // This should never happen since we checked the if dialStr is null + // and if it contains the plus sign in the beginning of this function. + // The plus sign is part of the network portion. + Rlog.e("checkAndProcessPlusCode: null newDialStr", networkDialStr); + return dialStr; + } + postDialStr = extractPostDialPortion(tempDialStr); + if (!TextUtils.isEmpty(postDialStr)) { + int dialableIndex = findDialableIndexFromPostDialStr(postDialStr); + + // dialableIndex should always be greater than 0 + if (dialableIndex >= 1) { + retStr = appendPwCharBackToOrigDialStr(dialableIndex, + retStr,postDialStr); + // Skips the P/W character, extracts the dialable portion + tempDialStr = postDialStr.substring(dialableIndex); + } else { + // Non-dialable character such as P/W should not be at the end of + // the dial string after P/W processing in GsmCdmaConnection.java + // Set the postDialStr to "" to break out of the loop + if (dialableIndex < 0) { + postDialStr = ""; + } + Rlog.e("wrong postDialStr=", postDialStr); + } + } + if (DBG) log("checkAndProcessPlusCode,postDialStr=" + postDialStr); + } while (!TextUtils.isEmpty(postDialStr) && !TextUtils.isEmpty(tempDialStr)); + } + return retStr; + } + + /** + * Wrap the supplied {@code CharSequence} with a {@code TtsSpan}, annotating it as + * containing a phone number in its entirety. + * + * @param phoneNumber A {@code CharSequence} the entirety of which represents a phone number. + * @return A {@code CharSequence} with appropriate annotations. + */ + public static CharSequence createTtsSpannable(CharSequence phoneNumber) { + if (phoneNumber == null) { + return null; + } + Spannable spannable = Spannable.Factory.getInstance().newSpannable(phoneNumber); + PhoneNumberUtils.addTtsSpan(spannable, 0, spannable.length()); + return spannable; + } + + /** + * Attach a {@link TtsSpan} to the supplied {@code Spannable} at the indicated location, + * annotating that location as containing a phone number. + * + * @param s A {@code Spannable} to annotate. + * @param start The starting character position of the phone number in {@code s}. + * @param endExclusive The position after the ending character in the phone number {@code s}. + */ + public static void addTtsSpan(Spannable s, int start, int endExclusive) { + s.setSpan(createTtsSpan(s.subSequence(start, endExclusive).toString()), + start, + endExclusive, + Spannable.SPAN_EXCLUSIVE_EXCLUSIVE); + } + + /** + * Wrap the supplied {@code CharSequence} with a {@code TtsSpan}, annotating it as + * containing a phone number in its entirety. + * + * @param phoneNumber A {@code CharSequence} the entirety of which represents a phone number. + * @return A {@code CharSequence} with appropriate annotations. + * @deprecated Renamed {@link #createTtsSpannable}. + * + * @hide + */ + @Deprecated + public static CharSequence ttsSpanAsPhoneNumber(CharSequence phoneNumber) { + return createTtsSpannable(phoneNumber); + } + + /** + * Attach a {@link TtsSpan} to the supplied {@code Spannable} at the indicated location, + * annotating that location as containing a phone number. + * + * @param s A {@code Spannable} to annotate. + * @param start The starting character position of the phone number in {@code s}. + * @param end The ending character position of the phone number in {@code s}. + * + * @deprecated Renamed {@link #addTtsSpan}. + * + * @hide + */ + @Deprecated + public static void ttsSpanAsPhoneNumber(Spannable s, int start, int end) { + addTtsSpan(s, start, end); + } + + /** + * Create a {@code TtsSpan} for the supplied {@code String}. + * + * @param phoneNumberString A {@code String} the entirety of which represents a phone number. + * @return A {@code TtsSpan} for {@param phoneNumberString}. + */ + public static TtsSpan createTtsSpan(String phoneNumberString) { + if (phoneNumberString == null) { + return null; + } + + // Parse the phone number + final PhoneNumberUtil phoneNumberUtil = PhoneNumberUtil.getInstance(); + PhoneNumber phoneNumber = null; + try { + // Don't supply a defaultRegion so this fails for non-international numbers because + // we don't want to TalkBalk to read a country code (e.g. +1) if it is not already + // present + phoneNumber = phoneNumberUtil.parse(phoneNumberString, /* defaultRegion */ null); + } catch (NumberParseException ignored) { + } + + // Build a telephone tts span + final TtsSpan.TelephoneBuilder builder = new TtsSpan.TelephoneBuilder(); + if (phoneNumber == null) { + // Strip separators otherwise TalkBack will be silent + // (this behavior was observed with TalkBalk 4.0.2 from their alpha channel) + builder.setNumberParts(splitAtNonNumerics(phoneNumberString)); + } else { + if (phoneNumber.hasCountryCode()) { + builder.setCountryCode(Integer.toString(phoneNumber.getCountryCode())); + } + builder.setNumberParts(Long.toString(phoneNumber.getNationalNumber())); + } + return builder.build(); + } + + // Split a phone number like "+20(123)-456#" using spaces, ignoring anything that is not + // a digit or the characters * and #, to produce a result like "20 123 456#". + private static String splitAtNonNumerics(CharSequence number) { + StringBuilder sb = new StringBuilder(number.length()); + for (int i = 0; i < number.length(); i++) { + sb.append(PhoneNumberUtils.is12Key(number.charAt(i)) + ? number.charAt(i) + : " "); + } + // It is very important to remove extra spaces. At time of writing, any leading or trailing + // spaces, or any sequence of more than one space, will confuse TalkBack and cause the TTS + // span to be non-functional! + return sb.toString().replaceAll(" +", " ").trim(); + } + + private static String getCurrentIdp(boolean useNanp) { + String ps = null; + if (useNanp) { + ps = NANP_IDP_STRING; + } else { + // in case, there is no IDD is found, we shouldn't convert it. + ps = SystemProperties.get(PROPERTY_OPERATOR_IDP_STRING, PLUS_SIGN_STRING); + } + return ps; + } + + private static boolean isTwoToNine (char c) { + if (c >= '2' && c <= '9') { + return true; + } else { + return false; + } + } + + private static int getFormatTypeFromCountryCode (String country) { + // Check for the NANP countries + int length = NANP_COUNTRIES.length; + for (int i = 0; i < length; i++) { + if (NANP_COUNTRIES[i].compareToIgnoreCase(country) == 0) { + return FORMAT_NANP; + } + } + if ("jp".compareToIgnoreCase(country) == 0) { + return FORMAT_JAPAN; + } + return FORMAT_UNKNOWN; + } + + /** + * This function checks if the passed in string conforms to the NANP format + * i.e. NXX-NXX-XXXX, N is any digit 2-9 and X is any digit 0-9 + * @hide + */ + public static boolean isNanp (String dialStr) { + boolean retVal = false; + if (dialStr != null) { + if (dialStr.length() == NANP_LENGTH) { + if (isTwoToNine(dialStr.charAt(0)) && + isTwoToNine(dialStr.charAt(3))) { + retVal = true; + for (int i=1; i<NANP_LENGTH; i++ ) { + char c=dialStr.charAt(i); + if (!PhoneNumberUtils.isISODigit(c)) { + retVal = false; + break; + } + } + } + } + } else { + Rlog.e("isNanp: null dialStr passed in", dialStr); + } + return retVal; + } + + /** + * This function checks if the passed in string conforms to 1-NANP format + */ + private static boolean isOneNanp(String dialStr) { + boolean retVal = false; + if (dialStr != null) { + String newDialStr = dialStr.substring(1); + if ((dialStr.charAt(0) == '1') && isNanp(newDialStr)) { + retVal = true; + } + } else { + Rlog.e("isOneNanp: null dialStr passed in", dialStr); + } + return retVal; + } + + /** + * Determines if the specified number is actually a URI + * (i.e. a SIP address) rather than a regular PSTN phone number, + * based on whether or not the number contains an "@" character. + * + * @hide + * @param number + * @return true if number contains @ + */ + public static boolean isUriNumber(String number) { + // Note we allow either "@" or "%40" to indicate a URI, in case + // the passed-in string is URI-escaped. (Neither "@" nor "%40" + // will ever be found in a legal PSTN number.) + return number != null && (number.contains("@") || number.contains("%40")); + } + + /** + * @return the "username" part of the specified SIP address, + * i.e. the part before the "@" character (or "%40"). + * + * @param number SIP address of the form "username@domainname" + * (or the URI-escaped equivalent "username%40domainname") + * @see #isUriNumber + * + * @hide + */ + public static String getUsernameFromUriNumber(String number) { + // The delimiter between username and domain name can be + // either "@" or "%40" (the URI-escaped equivalent.) + int delimiterIndex = number.indexOf('@'); + if (delimiterIndex < 0) { + delimiterIndex = number.indexOf("%40"); + } + if (delimiterIndex < 0) { + Rlog.w(LOG_TAG, + "getUsernameFromUriNumber: no delimiter found in SIP addr '" + number + "'"); + delimiterIndex = number.length(); + } + return number.substring(0, delimiterIndex); + } + + /** + * Given a {@link Uri} with a {@code sip} scheme, attempts to build an equivalent {@code tel} + * scheme {@link Uri}. If the source {@link Uri} does not contain a valid number, or is not + * using the {@code sip} scheme, the original {@link Uri} is returned. + * + * @param source The {@link Uri} to convert. + * @return The equivalent {@code tel} scheme {@link Uri}. + * + * @hide + */ + public static Uri convertSipUriToTelUri(Uri source) { + // A valid SIP uri has the format: sip:user:password@host:port;uri-parameters?headers + // Per RFC3261, the "user" can be a telephone number. + // For example: sip:1650555121;phone-context=blah.com@host.com + // In this case, the phone number is in the user field of the URI, and the parameters can be + // ignored. + // + // A SIP URI can also specify a phone number in a format similar to: + // sip:+1-212-555-1212@something.com;user=phone + // In this case, the phone number is again in user field and the parameters can be ignored. + // We can get the user field in these instances by splitting the string on the @, ;, or : + // and looking at the first found item. + + String scheme = source.getScheme(); + + if (!PhoneAccount.SCHEME_SIP.equals(scheme)) { + // Not a sip URI, bail. + return source; + } + + String number = source.getSchemeSpecificPart(); + String numberParts[] = number.split("[@;:]"); + + if (numberParts.length == 0) { + // Number not found, bail. + return source; + } + number = numberParts[0]; + + return Uri.fromParts(PhoneAccount.SCHEME_TEL, number, null); + } + + /** + * This function handles the plus code conversion + * If the number format is + * 1)+1NANP,remove +, + * 2)other than +1NANP, any + numbers,replace + with the current IDP + */ + private static String processPlusCode(String networkDialStr, boolean useNanp) { + String retStr = networkDialStr; + + if (DBG) log("processPlusCode, networkDialStr = " + networkDialStr + + "for NANP = " + useNanp); + // If there is a plus sign at the beginning of the dial string, + // Convert the plus sign to the default IDP since it's an international number + if (networkDialStr != null && + networkDialStr.charAt(0) == PLUS_SIGN_CHAR && + networkDialStr.length() > 1) { + String newStr = networkDialStr.substring(1); + // TODO: for nonNanp, should the '+' be removed if following number is country code + if (useNanp && isOneNanp(newStr)) { + // Remove the leading plus sign + retStr = newStr; + } else { + // Replaces the plus sign with the default IDP + retStr = networkDialStr.replaceFirst("[+]", getCurrentIdp(useNanp)); + } + } + if (DBG) log("processPlusCode, retStr=" + retStr); + return retStr; + } + + // This function finds the index of the dialable character(s) + // in the post dial string + private static int findDialableIndexFromPostDialStr(String postDialStr) { + for (int index = 0;index < postDialStr.length();index++) { + char c = postDialStr.charAt(index); + if (isReallyDialable(c)) { + return index; + } + } + return -1; + } + + // This function appends the non-dialable P/W character to the original + // dial string based on the dialable index passed in + private static String + appendPwCharBackToOrigDialStr(int dialableIndex,String origStr, String dialStr) { + String retStr; + + // There is only 1 P/W character before the dialable characters + if (dialableIndex == 1) { + StringBuilder ret = new StringBuilder(origStr); + ret = ret.append(dialStr.charAt(0)); + retStr = ret.toString(); + } else { + // It means more than 1 P/W characters in the post dial string, + // appends to retStr + String nonDigitStr = dialStr.substring(0,dialableIndex); + retStr = origStr.concat(nonDigitStr); + } + return retStr; + } + + //===== Beginning of utility methods used in compareLoosely() ===== + + /** + * Phone numbers are stored in "lookup" form in the database + * as reversed strings to allow for caller ID lookup + * + * This method takes a phone number and makes a valid SQL "LIKE" + * string that will match the lookup form + * + */ + /** all of a up to len must be an international prefix or + * separators/non-dialing digits + */ + private static boolean + matchIntlPrefix(String a, int len) { + /* '([^0-9*#+pwn]\+[^0-9*#+pwn] | [^0-9*#+pwn]0(0|11)[^0-9*#+pwn] )$' */ + /* 0 1 2 3 45 */ + + int state = 0; + for (int i = 0 ; i < len ; i++) { + char c = a.charAt(i); + + switch (state) { + case 0: + if (c == '+') state = 1; + else if (c == '0') state = 2; + else if (isNonSeparator(c)) return false; + break; + + case 2: + if (c == '0') state = 3; + else if (c == '1') state = 4; + else if (isNonSeparator(c)) return false; + break; + + case 4: + if (c == '1') state = 5; + else if (isNonSeparator(c)) return false; + break; + + default: + if (isNonSeparator(c)) return false; + break; + + } + } + + return state == 1 || state == 3 || state == 5; + } + + /** all of 'a' up to len must be a (+|00|011)country code) + * We're fast and loose with the country code. Any \d{1,3} matches */ + private static boolean + matchIntlPrefixAndCC(String a, int len) { + /* [^0-9*#+pwn]*(\+|0(0|11)\d\d?\d? [^0-9*#+pwn] $ */ + /* 0 1 2 3 45 6 7 8 */ + + int state = 0; + for (int i = 0 ; i < len ; i++ ) { + char c = a.charAt(i); + + switch (state) { + case 0: + if (c == '+') state = 1; + else if (c == '0') state = 2; + else if (isNonSeparator(c)) return false; + break; + + case 2: + if (c == '0') state = 3; + else if (c == '1') state = 4; + else if (isNonSeparator(c)) return false; + break; + + case 4: + if (c == '1') state = 5; + else if (isNonSeparator(c)) return false; + break; + + case 1: + case 3: + case 5: + if (isISODigit(c)) state = 6; + else if (isNonSeparator(c)) return false; + break; + + case 6: + case 7: + if (isISODigit(c)) state++; + else if (isNonSeparator(c)) return false; + break; + + default: + if (isNonSeparator(c)) return false; + } + } + + return state == 6 || state == 7 || state == 8; + } + + /** all of 'a' up to len must match non-US trunk prefix ('0') */ + private static boolean + matchTrunkPrefix(String a, int len) { + boolean found; + + found = false; + + for (int i = 0 ; i < len ; i++) { + char c = a.charAt(i); + + if (c == '0' && !found) { + found = true; + } else if (isNonSeparator(c)) { + return false; + } + } + + return found; + } + + //===== End of utility methods used only in compareLoosely() ===== + + //===== Beginning of utility methods used only in compareStrictly() ==== + + /* + * If true, the number is country calling code. + */ + private static final boolean COUNTRY_CALLING_CALL[] = { + true, true, false, false, false, false, false, true, false, false, + false, false, false, false, false, false, false, false, false, false, + true, false, false, false, false, false, false, true, true, false, + true, true, true, true, true, false, true, false, false, true, + true, false, false, true, true, true, true, true, true, true, + false, true, true, true, true, true, true, true, true, false, + true, true, true, true, true, true, true, false, false, false, + false, false, false, false, false, false, false, false, false, false, + false, true, true, true, true, false, true, false, false, true, + true, true, true, true, true, true, false, false, true, false, + }; + private static final int CCC_LENGTH = COUNTRY_CALLING_CALL.length; + + /** + * @return true when input is valid Country Calling Code. + */ + private static boolean isCountryCallingCode(int countryCallingCodeCandidate) { + return countryCallingCodeCandidate > 0 && countryCallingCodeCandidate < CCC_LENGTH && + COUNTRY_CALLING_CALL[countryCallingCodeCandidate]; + } + + /** + * Returns integer corresponding to the input if input "ch" is + * ISO-LATIN characters 0-9. + * Returns -1 otherwise + */ + private static int tryGetISODigit(char ch) { + if ('0' <= ch && ch <= '9') { + return ch - '0'; + } else { + return -1; + } + } + + private static class CountryCallingCodeAndNewIndex { + public final int countryCallingCode; + public final int newIndex; + public CountryCallingCodeAndNewIndex(int countryCode, int newIndex) { + this.countryCallingCode = countryCode; + this.newIndex = newIndex; + } + } + + /* + * Note that this function does not strictly care the country calling code with + * 3 length (like Morocco: +212), assuming it is enough to use the first two + * digit to compare two phone numbers. + */ + private static CountryCallingCodeAndNewIndex tryGetCountryCallingCodeAndNewIndex( + String str, boolean acceptThailandCase) { + // Rough regexp: + // ^[^0-9*#+]*((\+|0(0|11)\d\d?|166) [^0-9*#+] $ + // 0 1 2 3 45 6 7 89 + // + // In all the states, this function ignores separator characters. + // "166" is the special case for the call from Thailand to the US. Uguu! + int state = 0; + int ccc = 0; + final int length = str.length(); + for (int i = 0 ; i < length ; i++ ) { + char ch = str.charAt(i); + switch (state) { + case 0: + if (ch == '+') state = 1; + else if (ch == '0') state = 2; + else if (ch == '1') { + if (acceptThailandCase) { + state = 8; + } else { + return null; + } + } else if (isDialable(ch)) { + return null; + } + break; + + case 2: + if (ch == '0') state = 3; + else if (ch == '1') state = 4; + else if (isDialable(ch)) { + return null; + } + break; + + case 4: + if (ch == '1') state = 5; + else if (isDialable(ch)) { + return null; + } + break; + + case 1: + case 3: + case 5: + case 6: + case 7: + { + int ret = tryGetISODigit(ch); + if (ret > 0) { + ccc = ccc * 10 + ret; + if (ccc >= 100 || isCountryCallingCode(ccc)) { + return new CountryCallingCodeAndNewIndex(ccc, i + 1); + } + if (state == 1 || state == 3 || state == 5) { + state = 6; + } else { + state++; + } + } else if (isDialable(ch)) { + return null; + } + } + break; + case 8: + if (ch == '6') state = 9; + else if (isDialable(ch)) { + return null; + } + break; + case 9: + if (ch == '6') { + return new CountryCallingCodeAndNewIndex(66, i + 1); + } else { + return null; + } + default: + return null; + } + } + + return null; + } + + /** + * Currently this function simply ignore the first digit assuming it is + * trunk prefix. Actually trunk prefix is different in each country. + * + * e.g. + * "+79161234567" equals "89161234567" (Russian trunk digit is 8) + * "+33123456789" equals "0123456789" (French trunk digit is 0) + * + */ + private static int tryGetTrunkPrefixOmittedIndex(String str, int currentIndex) { + int length = str.length(); + for (int i = currentIndex ; i < length ; i++) { + final char ch = str.charAt(i); + if (tryGetISODigit(ch) >= 0) { + return i + 1; + } else if (isDialable(ch)) { + return -1; + } + } + return -1; + } + + /** + * Return true if the prefix of "str" is "ignorable". Here, "ignorable" means + * that "str" has only one digit and separator characters. The one digit is + * assumed to be trunk prefix. + */ + private static boolean checkPrefixIsIgnorable(final String str, + int forwardIndex, int backwardIndex) { + boolean trunk_prefix_was_read = false; + while (backwardIndex >= forwardIndex) { + if (tryGetISODigit(str.charAt(backwardIndex)) >= 0) { + if (trunk_prefix_was_read) { + // More than one digit appeared, meaning that "a" and "b" + // is different. + return false; + } else { + // Ignore just one digit, assuming it is trunk prefix. + trunk_prefix_was_read = true; + } + } else if (isDialable(str.charAt(backwardIndex))) { + // Trunk prefix is a digit, not "*", "#"... + return false; + } + backwardIndex--; + } + + return true; + } + + /** + * Returns Default voice subscription Id. + */ + private static int getDefaultVoiceSubId() { + return SubscriptionManager.getDefaultVoiceSubscriptionId(); + } + //==== End of utility methods used only in compareStrictly() ===== + + + /* + * The config held calling number conversion map, expected to convert to emergency number. + */ + private static String[] sConvertToEmergencyMap = null; + + /** + * Converts to emergency number based on the conversion map. + * The conversion map is declared as config_convert_to_emergency_number_map. + * + * @param context a context to use for accessing resources + * @return The converted emergency number if the number matches conversion map, + * otherwise original number. + * + * @hide + */ + public static String convertToEmergencyNumber(Context context, String number) { + if (context == null || TextUtils.isEmpty(number)) { + return number; + } + + String normalizedNumber = normalizeNumber(number); + + // The number is already emergency number. Skip conversion. + if (isEmergencyNumber(normalizedNumber)) { + return number; + } + + if (sConvertToEmergencyMap == null) { + sConvertToEmergencyMap = context.getResources().getStringArray( + com.android.internal.R.array.config_convert_to_emergency_number_map); + } + + // The conversion map is not defined (this is default). Skip conversion. + if (sConvertToEmergencyMap == null || sConvertToEmergencyMap.length == 0 ) { + return number; + } + + for (String convertMap : sConvertToEmergencyMap) { + if (DBG) log("convertToEmergencyNumber: " + convertMap); + String[] entry = null; + String[] filterNumbers = null; + String convertedNumber = null; + if (!TextUtils.isEmpty(convertMap)) { + entry = convertMap.split(":"); + } + if (entry != null && entry.length == 2) { + convertedNumber = entry[1]; + if (!TextUtils.isEmpty(entry[0])) { + filterNumbers = entry[0].split(","); + } + } + // Skip if the format of entry is invalid + if (TextUtils.isEmpty(convertedNumber) || filterNumbers == null + || filterNumbers.length == 0) { + continue; + } + + for (String filterNumber : filterNumbers) { + if (DBG) log("convertToEmergencyNumber: filterNumber = " + filterNumber + + ", convertedNumber = " + convertedNumber); + if (!TextUtils.isEmpty(filterNumber) && filterNumber.equals(normalizedNumber)) { + if (DBG) log("convertToEmergencyNumber: Matched. Successfully converted to: " + + convertedNumber); + return convertedNumber; + } + } + } + return number; + } +} diff --git a/android/telephony/PhoneStateListener.java b/android/telephony/PhoneStateListener.java new file mode 100644 index 00000000..afff6d54 --- /dev/null +++ b/android/telephony/PhoneStateListener.java @@ -0,0 +1,694 @@ +/* + * Copyright (C) 2008 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.telephony; + +import android.os.Bundle; +import android.os.Handler; +import android.os.Looper; +import android.os.Message; + +import com.android.internal.telephony.IPhoneStateListener; + +import java.util.List; +import java.lang.ref.WeakReference; + +/** + * A listener class for monitoring changes in specific telephony states + * on the device, including service state, signal strength, message + * waiting indicator (voicemail), and others. + * <p> + * Override the methods for the state that you wish to receive updates for, and + * pass your PhoneStateListener object, along with bitwise-or of the LISTEN_ + * flags to {@link TelephonyManager#listen TelephonyManager.listen()}. + * <p> + * Note that access to some telephony information is + * permission-protected. Your application won't receive updates for protected + * information unless it has the appropriate permissions declared in + * its manifest file. Where permissions apply, they are noted in the + * appropriate LISTEN_ flags. + */ +public class PhoneStateListener { + private static final String LOG_TAG = "PhoneStateListener"; + private static final boolean DBG = false; // STOPSHIP if true + + /** + * Stop listening for updates. + */ + public static final int LISTEN_NONE = 0; + + /** + * Listen for changes to the network service state (cellular). + * + * @see #onServiceStateChanged + * @see ServiceState + */ + public static final int LISTEN_SERVICE_STATE = 0x00000001; + + /** + * Listen for changes to the network signal strength (cellular). + * {@more} + * Requires Permission: {@link android.Manifest.permission#READ_PHONE_STATE + * READ_PHONE_STATE} + * <p> + * + * @see #onSignalStrengthChanged + * + * @deprecated by {@link #LISTEN_SIGNAL_STRENGTHS} + */ + @Deprecated + public static final int LISTEN_SIGNAL_STRENGTH = 0x00000002; + + /** + * Listen for changes to the message-waiting indicator. + * {@more} + * Requires Permission: {@link android.Manifest.permission#READ_PHONE_STATE + * READ_PHONE_STATE} + * <p> + * Example: The status bar uses this to determine when to display the + * voicemail icon. + * + * @see #onMessageWaitingIndicatorChanged + */ + public static final int LISTEN_MESSAGE_WAITING_INDICATOR = 0x00000004; + + /** + * Listen for changes to the call-forwarding indicator. + * {@more} + * Requires Permission: {@link android.Manifest.permission#READ_PHONE_STATE + * READ_PHONE_STATE} + * @see #onCallForwardingIndicatorChanged + */ + public static final int LISTEN_CALL_FORWARDING_INDICATOR = 0x00000008; + + /** + * Listen for changes to the device's cell location. Note that + * this will result in frequent callbacks to the listener. + * {@more} + * Requires Permission: {@link android.Manifest.permission#ACCESS_COARSE_LOCATION + * ACCESS_COARSE_LOCATION} + * <p> + * If you need regular location updates but want more control over + * the update interval or location precision, you can set up a listener + * through the {@link android.location.LocationManager location manager} + * instead. + * + * @see #onCellLocationChanged + */ + public static final int LISTEN_CELL_LOCATION = 0x00000010; + + /** + * Listen for changes to the device call state. + * {@more} + * + * @see #onCallStateChanged + */ + public static final int LISTEN_CALL_STATE = 0x00000020; + + /** + * Listen for changes to the data connection state (cellular). + * + * @see #onDataConnectionStateChanged + */ + public static final int LISTEN_DATA_CONNECTION_STATE = 0x00000040; + + /** + * Listen for changes to the direction of data traffic on the data + * connection (cellular). + * {@more} + * Example: The status bar uses this to display the appropriate + * data-traffic icon. + * + * @see #onDataActivity + */ + public static final int LISTEN_DATA_ACTIVITY = 0x00000080; + + /** + * Listen for changes to the network signal strengths (cellular). + * <p> + * Example: The status bar uses this to control the signal-strength + * icon. + * + * @see #onSignalStrengthsChanged + */ + public static final int LISTEN_SIGNAL_STRENGTHS = 0x00000100; + + /** + * Listen for changes to OTASP mode. + * + * @see #onOtaspChanged + * @hide + */ + public static final int LISTEN_OTASP_CHANGED = 0x00000200; + + /** + * Listen for changes to observed cell info. + * + * @see #onCellInfoChanged + */ + public static final int LISTEN_CELL_INFO = 0x00000400; + + /** + * Listen for precise changes and fails to the device calls (cellular). + * {@more} + * Requires Permission: {@link android.Manifest.permission#READ_PRECISE_PHONE_STATE + * READ_PRECISE_PHONE_STATE} + * + * @hide + */ + public static final int LISTEN_PRECISE_CALL_STATE = 0x00000800; + + /** + * Listen for precise changes and fails on the data connection (cellular). + * {@more} + * Requires Permission: {@link android.Manifest.permission#READ_PRECISE_PHONE_STATE + * READ_PRECISE_PHONE_STATE} + * + * @see #onPreciseDataConnectionStateChanged + * @hide + */ + public static final int LISTEN_PRECISE_DATA_CONNECTION_STATE = 0x00001000; + + /** + * Listen for real time info for all data connections (cellular)). + * {@more} + * Requires Permission: {@link android.Manifest.permission#READ_PRECISE_PHONE_STATE + * READ_PRECISE_PHONE_STATE} + * @see #onDataConnectionRealTimeInfoChanged(DataConnectionRealTimeInfo) + * + * @deprecated Use {@link TelephonyManager#getModemActivityInfo()} + * @hide + */ + @Deprecated + public static final int LISTEN_DATA_CONNECTION_REAL_TIME_INFO = 0x00002000; + + /** + * Listen for changes to LTE network state + * + * @see #onLteNetworkStateChanged + * @hide + */ + public static final int LISTEN_VOLTE_STATE = 0x00004000; + + /** + * Listen for OEM hook raw event + * + * @see #onOemHookRawEvent + * @hide + * @deprecated OEM needs a vendor-extension hal and their apps should use that instead + */ + @Deprecated + public static final int LISTEN_OEM_HOOK_RAW_EVENT = 0x00008000; + + /** + * Listen for carrier network changes indicated by a carrier app. + * + * @see #onCarrierNetworkRequest + * @see TelephonyManager#notifyCarrierNetworkChange(boolean) + * @hide + */ + public static final int LISTEN_CARRIER_NETWORK_CHANGE = 0x00010000; + + /** + * Listen for changes to the sim voice activation state + * @see TelephonyManager#SIM_ACTIVATION_STATE_ACTIVATING + * @see TelephonyManager#SIM_ACTIVATION_STATE_ACTIVATED + * @see TelephonyManager#SIM_ACTIVATION_STATE_DEACTIVATED + * @see TelephonyManager#SIM_ACTIVATION_STATE_RESTRICTED + * @see TelephonyManager#SIM_ACTIVATION_STATE_UNKNOWN + * {@more} + * Example: TelephonyManager#SIM_ACTIVATION_STATE_ACTIVATED indicates voice service has been + * fully activated + * + * @see #onVoiceActivationStateChanged + * @hide + */ + public static final int LISTEN_VOICE_ACTIVATION_STATE = 0x00020000; + + /** + * Listen for changes to the sim data activation state + * @see TelephonyManager#SIM_ACTIVATION_STATE_ACTIVATING + * @see TelephonyManager#SIM_ACTIVATION_STATE_ACTIVATED + * @see TelephonyManager#SIM_ACTIVATION_STATE_DEACTIVATED + * @see TelephonyManager#SIM_ACTIVATION_STATE_RESTRICTED + * @see TelephonyManager#SIM_ACTIVATION_STATE_UNKNOWN + * {@more} + * Example: TelephonyManager#SIM_ACTIVATION_STATE_ACTIVATED indicates data service has been + * fully activated + * + * @see #onDataActivationStateChanged + * @hide + */ + public static final int LISTEN_DATA_ACTIVATION_STATE = 0x00040000; + + /* + * Subscription used to listen to the phone state changes + * @hide + */ + /** @hide */ + protected Integer mSubId; + + private final Handler mHandler; + + /** + * Create a PhoneStateListener for the Phone with the default subscription. + * This class requires Looper.myLooper() not return null. + */ + public PhoneStateListener() { + this(null, Looper.myLooper()); + } + + /** + * Create a PhoneStateListener for the Phone with the default subscription + * using a particular non-null Looper. + * @hide + */ + public PhoneStateListener(Looper looper) { + this(null, looper); + } + + /** + * Create a PhoneStateListener for the Phone using the specified subscription. + * This class requires Looper.myLooper() not return null. To supply your + * own non-null Looper use PhoneStateListener(int subId, Looper looper) below. + * @hide + */ + public PhoneStateListener(Integer subId) { + this(subId, Looper.myLooper()); + } + + /** + * Create a PhoneStateListener for the Phone using the specified subscription + * and non-null Looper. + * @hide + */ + public PhoneStateListener(Integer subId, Looper looper) { + if (DBG) log("ctor: subId=" + subId + " looper=" + looper); + mSubId = subId; + mHandler = new Handler(looper) { + public void handleMessage(Message msg) { + if (DBG) { + log("mSubId=" + mSubId + " what=0x" + Integer.toHexString(msg.what) + + " msg=" + msg); + } + switch (msg.what) { + case LISTEN_SERVICE_STATE: + PhoneStateListener.this.onServiceStateChanged((ServiceState)msg.obj); + break; + case LISTEN_SIGNAL_STRENGTH: + PhoneStateListener.this.onSignalStrengthChanged(msg.arg1); + break; + case LISTEN_MESSAGE_WAITING_INDICATOR: + PhoneStateListener.this.onMessageWaitingIndicatorChanged(msg.arg1 != 0); + break; + case LISTEN_CALL_FORWARDING_INDICATOR: + PhoneStateListener.this.onCallForwardingIndicatorChanged(msg.arg1 != 0); + break; + case LISTEN_CELL_LOCATION: + PhoneStateListener.this.onCellLocationChanged((CellLocation)msg.obj); + break; + case LISTEN_CALL_STATE: + PhoneStateListener.this.onCallStateChanged(msg.arg1, (String)msg.obj); + break; + case LISTEN_DATA_CONNECTION_STATE: + PhoneStateListener.this.onDataConnectionStateChanged(msg.arg1, msg.arg2); + PhoneStateListener.this.onDataConnectionStateChanged(msg.arg1); + break; + case LISTEN_DATA_ACTIVITY: + PhoneStateListener.this.onDataActivity(msg.arg1); + break; + case LISTEN_SIGNAL_STRENGTHS: + PhoneStateListener.this.onSignalStrengthsChanged((SignalStrength)msg.obj); + break; + case LISTEN_OTASP_CHANGED: + PhoneStateListener.this.onOtaspChanged(msg.arg1); + break; + case LISTEN_CELL_INFO: + PhoneStateListener.this.onCellInfoChanged((List<CellInfo>)msg.obj); + break; + case LISTEN_PRECISE_CALL_STATE: + PhoneStateListener.this.onPreciseCallStateChanged((PreciseCallState)msg.obj); + break; + case LISTEN_PRECISE_DATA_CONNECTION_STATE: + PhoneStateListener.this.onPreciseDataConnectionStateChanged( + (PreciseDataConnectionState)msg.obj); + break; + case LISTEN_DATA_CONNECTION_REAL_TIME_INFO: + PhoneStateListener.this.onDataConnectionRealTimeInfoChanged( + (DataConnectionRealTimeInfo)msg.obj); + break; + case LISTEN_VOLTE_STATE: + PhoneStateListener.this.onVoLteServiceStateChanged((VoLteServiceState)msg.obj); + break; + case LISTEN_VOICE_ACTIVATION_STATE: + PhoneStateListener.this.onVoiceActivationStateChanged((int)msg.obj); + break; + case LISTEN_DATA_ACTIVATION_STATE: + PhoneStateListener.this.onDataActivationStateChanged((int)msg.obj); + break; + case LISTEN_OEM_HOOK_RAW_EVENT: + PhoneStateListener.this.onOemHookRawEvent((byte[])msg.obj); + break; + case LISTEN_CARRIER_NETWORK_CHANGE: + PhoneStateListener.this.onCarrierNetworkChange((boolean)msg.obj); + break; + + } + } + }; + } + + /** + * Callback invoked when device service state changes. + * + * @see ServiceState#STATE_EMERGENCY_ONLY + * @see ServiceState#STATE_IN_SERVICE + * @see ServiceState#STATE_OUT_OF_SERVICE + * @see ServiceState#STATE_POWER_OFF + */ + public void onServiceStateChanged(ServiceState serviceState) { + // default implementation empty + } + + /** + * Callback invoked when network signal strength changes. + * + * @see ServiceState#STATE_EMERGENCY_ONLY + * @see ServiceState#STATE_IN_SERVICE + * @see ServiceState#STATE_OUT_OF_SERVICE + * @see ServiceState#STATE_POWER_OFF + * @deprecated Use {@link #onSignalStrengthsChanged(SignalStrength)} + */ + @Deprecated + public void onSignalStrengthChanged(int asu) { + // default implementation empty + } + + /** + * Callback invoked when the message-waiting indicator changes. + */ + public void onMessageWaitingIndicatorChanged(boolean mwi) { + // default implementation empty + } + + /** + * Callback invoked when the call-forwarding indicator changes. + */ + public void onCallForwardingIndicatorChanged(boolean cfi) { + // default implementation empty + } + + /** + * Callback invoked when device cell location changes. + */ + public void onCellLocationChanged(CellLocation location) { + // default implementation empty + } + + /** + * Callback invoked when device call state changes. + * @param state call state + * @param incomingNumber incoming call phone number. If application does not have + * {@link android.Manifest.permission#READ_PHONE_STATE READ_PHONE_STATE} permission, an empty + * string will be passed as an argument. + * + * @see TelephonyManager#CALL_STATE_IDLE + * @see TelephonyManager#CALL_STATE_RINGING + * @see TelephonyManager#CALL_STATE_OFFHOOK + */ + public void onCallStateChanged(int state, String incomingNumber) { + // default implementation empty + } + + /** + * Callback invoked when connection state changes. + * + * @see TelephonyManager#DATA_DISCONNECTED + * @see TelephonyManager#DATA_CONNECTING + * @see TelephonyManager#DATA_CONNECTED + * @see TelephonyManager#DATA_SUSPENDED + */ + public void onDataConnectionStateChanged(int state) { + // default implementation empty + } + + /** + * same as above, but with the network type. Both called. + */ + public void onDataConnectionStateChanged(int state, int networkType) { + } + + /** + * Callback invoked when data activity state changes. + * + * @see TelephonyManager#DATA_ACTIVITY_NONE + * @see TelephonyManager#DATA_ACTIVITY_IN + * @see TelephonyManager#DATA_ACTIVITY_OUT + * @see TelephonyManager#DATA_ACTIVITY_INOUT + * @see TelephonyManager#DATA_ACTIVITY_DORMANT + */ + public void onDataActivity(int direction) { + // default implementation empty + } + + /** + * Callback invoked when network signal strengths changes. + * + * @see ServiceState#STATE_EMERGENCY_ONLY + * @see ServiceState#STATE_IN_SERVICE + * @see ServiceState#STATE_OUT_OF_SERVICE + * @see ServiceState#STATE_POWER_OFF + */ + public void onSignalStrengthsChanged(SignalStrength signalStrength) { + // default implementation empty + } + + + /** + * The Over The Air Service Provisioning (OTASP) has changed. Requires + * the READ_PHONE_STATE permission. + * @param otaspMode is integer <code>OTASP_UNKNOWN=1<code> + * means the value is currently unknown and the system should wait until + * <code>OTASP_NEEDED=2<code> or <code>OTASP_NOT_NEEDED=3<code> is received before + * making the decision to perform OTASP or not. + * + * @hide + */ + public void onOtaspChanged(int otaspMode) { + // default implementation empty + } + + /** + * Callback invoked when a observed cell info has changed, + * or new cells have been added or removed. + * @param cellInfo is the list of currently visible cells. + */ + public void onCellInfoChanged(List<CellInfo> cellInfo) { + } + + /** + * Callback invoked when precise device call state changes. + * + * @hide + */ + public void onPreciseCallStateChanged(PreciseCallState callState) { + // default implementation empty + } + + /** + * Callback invoked when data connection state changes with precise information. + * + * @hide + */ + public void onPreciseDataConnectionStateChanged( + PreciseDataConnectionState dataConnectionState) { + // default implementation empty + } + + /** + * Callback invoked when data connection state changes with precise information. + * + * @hide + */ + public void onDataConnectionRealTimeInfoChanged( + DataConnectionRealTimeInfo dcRtInfo) { + // default implementation empty + } + + /** + * Callback invoked when the service state of LTE network + * related to the VoLTE service has changed. + * @param stateInfo is the current LTE network information + * @hide + */ + public void onVoLteServiceStateChanged(VoLteServiceState stateInfo) { + } + + /** + * Callback invoked when the SIM voice activation state has changed + * @param state is the current SIM voice activation state + * @hide + */ + public void onVoiceActivationStateChanged(int state) { + + } + + /** + * Callback invoked when the SIM data activation state has changed + * @param state is the current SIM data activation state + * @hide + */ + public void onDataActivationStateChanged(int state) { + + } + + /** + * Callback invoked when OEM hook raw event is received. Requires + * the READ_PRIVILEGED_PHONE_STATE permission. + * @param rawData is the byte array of the OEM hook raw data. + * @hide + */ + public void onOemHookRawEvent(byte[] rawData) { + // default implementation empty + } + + /** + * Callback invoked when telephony has received notice from a carrier + * app that a network action that could result in connectivity loss + * has been requested by an app using + * {@link android.telephony.TelephonyManager#notifyCarrierNetworkChange(boolean)} + * + * @param active Whether the carrier network change is or shortly + * will be active. This value is true to indicate + * showing alternative UI and false to stop. + * + * @hide + */ + public void onCarrierNetworkChange(boolean active) { + // default implementation empty + } + + /** + * The callback methods need to be called on the handler thread where + * this object was created. If the binder did that for us it'd be nice. + * + * Using a static class and weak reference here to avoid memory leak caused by the + * IPhoneStateListener.Stub callback retaining references to the outside PhoneStateListeners: + * even caller has been destroyed and "un-registered" the PhoneStateListener, it is still not + * eligible for GC given the references coming from: + * Native Stack --> PhoneStateListener --> Context (Activity). + * memory of caller's context will be collected after GC from service side get triggered + */ + private static class IPhoneStateListenerStub extends IPhoneStateListener.Stub { + private WeakReference<PhoneStateListener> mPhoneStateListenerWeakRef; + + public IPhoneStateListenerStub(PhoneStateListener phoneStateListener) { + mPhoneStateListenerWeakRef = new WeakReference<PhoneStateListener>(phoneStateListener); + } + + private void send(int what, int arg1, int arg2, Object obj) { + PhoneStateListener listener = mPhoneStateListenerWeakRef.get(); + if (listener != null) { + Message.obtain(listener.mHandler, what, arg1, arg2, obj).sendToTarget(); + } + } + + public void onServiceStateChanged(ServiceState serviceState) { + send(LISTEN_SERVICE_STATE, 0, 0, serviceState); + } + + public void onSignalStrengthChanged(int asu) { + send(LISTEN_SIGNAL_STRENGTH, asu, 0, null); + } + + public void onMessageWaitingIndicatorChanged(boolean mwi) { + send(LISTEN_MESSAGE_WAITING_INDICATOR, mwi ? 1 : 0, 0, null); + } + + public void onCallForwardingIndicatorChanged(boolean cfi) { + send(LISTEN_CALL_FORWARDING_INDICATOR, cfi ? 1 : 0, 0, null); + } + + public void onCellLocationChanged(Bundle bundle) { + CellLocation location = CellLocation.newFromBundle(bundle); + send(LISTEN_CELL_LOCATION, 0, 0, location); + } + + public void onCallStateChanged(int state, String incomingNumber) { + send(LISTEN_CALL_STATE, state, 0, incomingNumber); + } + + public void onDataConnectionStateChanged(int state, int networkType) { + send(LISTEN_DATA_CONNECTION_STATE, state, networkType, null); + } + + public void onDataActivity(int direction) { + send(LISTEN_DATA_ACTIVITY, direction, 0, null); + } + + public void onSignalStrengthsChanged(SignalStrength signalStrength) { + send(LISTEN_SIGNAL_STRENGTHS, 0, 0, signalStrength); + } + + public void onOtaspChanged(int otaspMode) { + send(LISTEN_OTASP_CHANGED, otaspMode, 0, null); + } + + public void onCellInfoChanged(List<CellInfo> cellInfo) { + send(LISTEN_CELL_INFO, 0, 0, cellInfo); + } + + public void onPreciseCallStateChanged(PreciseCallState callState) { + send(LISTEN_PRECISE_CALL_STATE, 0, 0, callState); + } + + public void onPreciseDataConnectionStateChanged( + PreciseDataConnectionState dataConnectionState) { + send(LISTEN_PRECISE_DATA_CONNECTION_STATE, 0, 0, dataConnectionState); + } + + public void onDataConnectionRealTimeInfoChanged( + DataConnectionRealTimeInfo dcRtInfo) { + send(LISTEN_DATA_CONNECTION_REAL_TIME_INFO, 0, 0, dcRtInfo); + } + + public void onVoLteServiceStateChanged(VoLteServiceState lteState) { + send(LISTEN_VOLTE_STATE, 0, 0, lteState); + } + + public void onVoiceActivationStateChanged(int activationState) { + send(LISTEN_VOICE_ACTIVATION_STATE, 0, 0, activationState); + } + + public void onDataActivationStateChanged(int activationState) { + send(LISTEN_DATA_ACTIVATION_STATE, 0, 0, activationState); + } + + public void onOemHookRawEvent(byte[] rawData) { + send(LISTEN_OEM_HOOK_RAW_EVENT, 0, 0, rawData); + } + + public void onCarrierNetworkChange(boolean active) { + send(LISTEN_CARRIER_NETWORK_CHANGE, 0, 0, active); + } + } + + IPhoneStateListener callback = new IPhoneStateListenerStub(this); + + private void log(String s) { + Rlog.d(LOG_TAG, s); + } +} diff --git a/android/telephony/PreciseCallState.java b/android/telephony/PreciseCallState.java new file mode 100644 index 00000000..f246416d --- /dev/null +++ b/android/telephony/PreciseCallState.java @@ -0,0 +1,309 @@ +/* + * Copyright (C) 2014 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.telephony; + +import android.os.Parcel; +import android.os.Parcelable; +import android.telephony.DisconnectCause; +import android.telephony.PreciseDisconnectCause; + +/** + * Contains precise call state and call fail causes generated by the + * framework and the RIL. + * + * The following call information is included in returned PreciseCallState: + * + * <ul> + * <li>Ringing call state. + * <li>Foreground call state. + * <li>Background call state. + * <li>Disconnect cause; generated by the framework. + * <li>Precise disconnect cause; generated by the RIL. + * </ul> + * + * @hide + */ +public class PreciseCallState implements Parcelable { + + /** Call state is not valid (Not received a call state). */ + public static final int PRECISE_CALL_STATE_NOT_VALID = -1; + /** Call state: No activity. */ + public static final int PRECISE_CALL_STATE_IDLE = 0; + /** Call state: Active. */ + public static final int PRECISE_CALL_STATE_ACTIVE = 1; + /** Call state: On hold. */ + public static final int PRECISE_CALL_STATE_HOLDING = 2; + /** Call state: Dialing. */ + public static final int PRECISE_CALL_STATE_DIALING = 3; + /** Call state: Alerting. */ + public static final int PRECISE_CALL_STATE_ALERTING = 4; + /** Call state: Incoming. */ + public static final int PRECISE_CALL_STATE_INCOMING = 5; + /** Call state: Waiting. */ + public static final int PRECISE_CALL_STATE_WAITING = 6; + /** Call state: Disconnected. */ + public static final int PRECISE_CALL_STATE_DISCONNECTED = 7; + /** Call state: Disconnecting. */ + public static final int PRECISE_CALL_STATE_DISCONNECTING = 8; + + private int mRingingCallState = PRECISE_CALL_STATE_NOT_VALID; + private int mForegroundCallState = PRECISE_CALL_STATE_NOT_VALID; + private int mBackgroundCallState = PRECISE_CALL_STATE_NOT_VALID; + private int mDisconnectCause = DisconnectCause.NOT_VALID; + private int mPreciseDisconnectCause = PreciseDisconnectCause.NOT_VALID; + + /** + * Constructor + * + * @hide + */ + public PreciseCallState(int ringingCall, int foregroundCall, int backgroundCall, + int disconnectCause, int preciseDisconnectCause) { + mRingingCallState = ringingCall; + mForegroundCallState = foregroundCall; + mBackgroundCallState = backgroundCall; + mDisconnectCause = disconnectCause; + mPreciseDisconnectCause = preciseDisconnectCause; + } + + /** + * Empty Constructor + * + * @hide + */ + public PreciseCallState() { + } + + /** + * Construct a PreciseCallState object from the given parcel. + */ + private PreciseCallState(Parcel in) { + mRingingCallState = in.readInt(); + mForegroundCallState = in.readInt(); + mBackgroundCallState = in.readInt(); + mDisconnectCause = in.readInt(); + mPreciseDisconnectCause = in.readInt(); + } + + /** + * Get precise ringing call state + * + * @see PreciseCallState#PRECISE_CALL_STATE_NOT_VALID + * @see PreciseCallState#PRECISE_CALL_STATE_IDLE + * @see PreciseCallState#PRECISE_CALL_STATE_ACTIVE + * @see PreciseCallState#PRECISE_CALL_STATE_HOLDING + * @see PreciseCallState#PRECISE_CALL_STATE_DIALING + * @see PreciseCallState#PRECISE_CALL_STATE_ALERTING + * @see PreciseCallState#PRECISE_CALL_STATE_INCOMING + * @see PreciseCallState#PRECISE_CALL_STATE_WAITING + * @see PreciseCallState#PRECISE_CALL_STATE_DISCONNECTED + * @see PreciseCallState#PRECISE_CALL_STATE_DISCONNECTING + */ + public int getRingingCallState() { + return mRingingCallState; + } + + /** + * Get precise foreground call state + * + * @see PreciseCallState#PRECISE_CALL_STATE_NOT_VALID + * @see PreciseCallState#PRECISE_CALL_STATE_IDLE + * @see PreciseCallState#PRECISE_CALL_STATE_ACTIVE + * @see PreciseCallState#PRECISE_CALL_STATE_HOLDING + * @see PreciseCallState#PRECISE_CALL_STATE_DIALING + * @see PreciseCallState#PRECISE_CALL_STATE_ALERTING + * @see PreciseCallState#PRECISE_CALL_STATE_INCOMING + * @see PreciseCallState#PRECISE_CALL_STATE_WAITING + * @see PreciseCallState#PRECISE_CALL_STATE_DISCONNECTED + * @see PreciseCallState#PRECISE_CALL_STATE_DISCONNECTING + */ + public int getForegroundCallState() { + return mForegroundCallState; + } + + /** + * Get precise background call state + * + * @see PreciseCallState#PRECISE_CALL_STATE_NOT_VALID + * @see PreciseCallState#PRECISE_CALL_STATE_IDLE + * @see PreciseCallState#PRECISE_CALL_STATE_ACTIVE + * @see PreciseCallState#PRECISE_CALL_STATE_HOLDING + * @see PreciseCallState#PRECISE_CALL_STATE_DIALING + * @see PreciseCallState#PRECISE_CALL_STATE_ALERTING + * @see PreciseCallState#PRECISE_CALL_STATE_INCOMING + * @see PreciseCallState#PRECISE_CALL_STATE_WAITING + * @see PreciseCallState#PRECISE_CALL_STATE_DISCONNECTED + * @see PreciseCallState#PRECISE_CALL_STATE_DISCONNECTING + */ + public int getBackgroundCallState() { + return mBackgroundCallState; + } + + /** + * Get disconnect cause generated by the framework + * + * @see DisconnectCause#NOT_VALID + * @see DisconnectCause#NOT_DISCONNECTED + * @see DisconnectCause#INCOMING_MISSED + * @see DisconnectCause#NORMAL + * @see DisconnectCause#LOCAL + * @see DisconnectCause#BUSY + * @see DisconnectCause#CONGESTION + * @see DisconnectCause#MMI + * @see DisconnectCause#INVALID_NUMBER + * @see DisconnectCause#NUMBER_UNREACHABLE + * @see DisconnectCause#SERVER_UNREACHABLE + * @see DisconnectCause#INVALID_CREDENTIALS + * @see DisconnectCause#OUT_OF_NETWORK + * @see DisconnectCause#SERVER_ERROR + * @see DisconnectCause#TIMED_OUT + * @see DisconnectCause#LOST_SIGNAL + * @see DisconnectCause#LIMIT_EXCEEDED + * @see DisconnectCause#INCOMING_REJECTED + * @see DisconnectCause#POWER_OFF + * @see DisconnectCause#OUT_OF_SERVICE + * @see DisconnectCause#ICC_ERROR + * @see DisconnectCause#CALL_BARRED + * @see DisconnectCause#FDN_BLOCKED + * @see DisconnectCause#CS_RESTRICTED + * @see DisconnectCause#CS_RESTRICTED_NORMAL + * @see DisconnectCause#CS_RESTRICTED_EMERGENCY + * @see DisconnectCause#UNOBTAINABLE_NUMBER + * @see DisconnectCause#CDMA_LOCKED_UNTIL_POWER_CYCLE + * @see DisconnectCause#CDMA_DROP + * @see DisconnectCause#CDMA_INTERCEPT + * @see DisconnectCause#CDMA_REORDER + * @see DisconnectCause#CDMA_SO_REJECT + * @see DisconnectCause#CDMA_RETRY_ORDER + * @see DisconnectCause#CDMA_ACCESS_FAILURE + * @see DisconnectCause#CDMA_PREEMPTED + * @see DisconnectCause#CDMA_NOT_EMERGENCY + * @see DisconnectCause#CDMA_ACCESS_BLOCKED + * @see DisconnectCause#ERROR_UNSPECIFIED + */ + public int getDisconnectCause() { + return mDisconnectCause; + } + + /** + * Get disconnect cause generated by the RIL + * + * @see PreciseDisconnectCause#NOT_VALID + * @see PreciseDisconnectCause#NO_DISCONNECT_CAUSE_AVAILABLE + * @see PreciseDisconnectCause#UNOBTAINABLE_NUMBER + * @see PreciseDisconnectCause#NORMAL + * @see PreciseDisconnectCause#BUSY + * @see PreciseDisconnectCause#NUMBER_CHANGED + * @see PreciseDisconnectCause#STATUS_ENQUIRY + * @see PreciseDisconnectCause#NORMAL_UNSPECIFIED + * @see PreciseDisconnectCause#NO_CIRCUIT_AVAIL + * @see PreciseDisconnectCause#TEMPORARY_FAILURE + * @see PreciseDisconnectCause#SWITCHING_CONGESTION + * @see PreciseDisconnectCause#CHANNEL_NOT_AVAIL + * @see PreciseDisconnectCause#QOS_NOT_AVAIL + * @see PreciseDisconnectCause#BEARER_NOT_AVAIL + * @see PreciseDisconnectCause#ACM_LIMIT_EXCEEDED + * @see PreciseDisconnectCause#CALL_BARRED + * @see PreciseDisconnectCause#FDN_BLOCKED + * @see PreciseDisconnectCause#IMSI_UNKNOWN_IN_VLR + * @see PreciseDisconnectCause#IMEI_NOT_ACCEPTED + * @see PreciseDisconnectCause#CDMA_LOCKED_UNTIL_POWER_CYCLE + * @see PreciseDisconnectCause#CDMA_DROP + * @see PreciseDisconnectCause#CDMA_INTERCEPT + * @see PreciseDisconnectCause#CDMA_REORDER + * @see PreciseDisconnectCause#CDMA_SO_REJECT + * @see PreciseDisconnectCause#CDMA_RETRY_ORDER + * @see PreciseDisconnectCause#CDMA_ACCESS_FAILURE + * @see PreciseDisconnectCause#CDMA_PREEMPTED + * @see PreciseDisconnectCause#CDMA_NOT_EMERGENCY + * @see PreciseDisconnectCause#CDMA_ACCESS_BLOCKED + * @see PreciseDisconnectCause#ERROR_UNSPECIFIED + */ + public int getPreciseDisconnectCause() { + return mPreciseDisconnectCause; + } + + @Override + public int describeContents() { + return 0; + } + + @Override + public void writeToParcel(Parcel out, int flags) { + out.writeInt(mRingingCallState); + out.writeInt(mForegroundCallState); + out.writeInt(mBackgroundCallState); + out.writeInt(mDisconnectCause); + out.writeInt(mPreciseDisconnectCause); + } + + public static final Parcelable.Creator<PreciseCallState> CREATOR + = new Parcelable.Creator<PreciseCallState>() { + + public PreciseCallState createFromParcel(Parcel in) { + return new PreciseCallState(in); + } + + public PreciseCallState[] newArray(int size) { + return new PreciseCallState[size]; + } + }; + + @Override + public int hashCode() { + final int prime = 31; + int result = 1; + result = prime * result + mRingingCallState; + result = prime * result + mForegroundCallState; + result = prime * result + mBackgroundCallState; + result = prime * result + mDisconnectCause; + result = prime * result + mPreciseDisconnectCause; + return result; + } + + @Override + public boolean equals(Object obj) { + if (this == obj) { + return true; + } + if (obj == null) { + return false; + } + if (getClass() != obj.getClass()) { + return false; + } + PreciseCallState other = (PreciseCallState) obj; + return (mRingingCallState != other.mRingingCallState && + mForegroundCallState != other.mForegroundCallState && + mBackgroundCallState != other.mBackgroundCallState && + mDisconnectCause != other.mDisconnectCause && + mPreciseDisconnectCause != other.mPreciseDisconnectCause); + } + + @Override + public String toString() { + StringBuffer sb = new StringBuffer(); + + sb.append("Ringing call state: " + mRingingCallState); + sb.append(", Foreground call state: " + mForegroundCallState); + sb.append(", Background call state: " + mBackgroundCallState); + sb.append(", Disconnect cause: " + mDisconnectCause); + sb.append(", Precise disconnect cause: " + mPreciseDisconnectCause); + + return sb.toString(); + } +} diff --git a/android/telephony/PreciseDataConnectionState.java b/android/telephony/PreciseDataConnectionState.java new file mode 100644 index 00000000..31c9a9e5 --- /dev/null +++ b/android/telephony/PreciseDataConnectionState.java @@ -0,0 +1,273 @@ +/* + * Copyright (C) 2014 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.telephony; + +import android.os.Parcel; +import android.os.Parcelable; +import android.telephony.TelephonyManager; +import android.net.LinkProperties; + +/** + * Contains precise data connection state. + * + * The following data connection information is included in returned PreciseDataConnectionState: + * + * <ul> + * <li>Data connection state. + * <li>Network type of the connection. + * <li>APN type. + * <li>APN. + * <li>Data connection change reason. + * <li>The properties of the network link. + * <li>Data connection fail cause. + * </ul> + * + * @hide + */ +public class PreciseDataConnectionState implements Parcelable { + + private int mState = TelephonyManager.DATA_UNKNOWN; + private int mNetworkType = TelephonyManager.NETWORK_TYPE_UNKNOWN; + private String mAPNType = ""; + private String mAPN = ""; + private String mReason = ""; + private LinkProperties mLinkProperties = null; + private String mFailCause = ""; + + /** + * Constructor + * + * @hide + */ + public PreciseDataConnectionState(int state, int networkType, + String apnType, String apn, String reason, + LinkProperties linkProperties, String failCause) { + mState = state; + mNetworkType = networkType; + mAPNType = apnType; + mAPN = apn; + mReason = reason; + mLinkProperties = linkProperties; + mFailCause = failCause; + } + + /** + * Empty Constructor + * + * @hide + */ + public PreciseDataConnectionState() { + } + + /** + * Construct a PreciseDataConnectionState object from the given parcel. + */ + private PreciseDataConnectionState(Parcel in) { + mState = in.readInt(); + mNetworkType = in.readInt(); + mAPNType = in.readString(); + mAPN = in.readString(); + mReason = in.readString(); + mLinkProperties = (LinkProperties)in.readParcelable(null); + mFailCause = in.readString(); + } + + /** + * Get data connection state + * + * @see TelephonyManager#DATA_UNKNOWN + * @see TelephonyManager#DATA_DISCONNECTED + * @see TelephonyManager#DATA_CONNECTING + * @see TelephonyManager#DATA_CONNECTED + * @see TelephonyManager#DATA_SUSPENDED + */ + public int getDataConnectionState() { + return mState; + } + + /** + * Get data connection network type + * + * @see TelephonyManager#NETWORK_TYPE_UNKNOWN + * @see TelephonyManager#NETWORK_TYPE_GPRS + * @see TelephonyManager#NETWORK_TYPE_EDGE + * @see TelephonyManager#NETWORK_TYPE_UMTS + * @see TelephonyManager#NETWORK_TYPE_CDMA + * @see TelephonyManager#NETWORK_TYPE_EVDO_0 + * @see TelephonyManager#NETWORK_TYPE_EVDO_A + * @see TelephonyManager#NETWORK_TYPE_1xRTT + * @see TelephonyManager#NETWORK_TYPE_HSDPA + * @see TelephonyManager#NETWORK_TYPE_HSUPA + * @see TelephonyManager#NETWORK_TYPE_HSPA + * @see TelephonyManager#NETWORK_TYPE_IDEN + * @see TelephonyManager#NETWORK_TYPE_EVDO_B + * @see TelephonyManager#NETWORK_TYPE_LTE + * @see TelephonyManager#NETWORK_TYPE_EHRPD + * @see TelephonyManager#NETWORK_TYPE_HSPAP + */ + public int getDataConnectionNetworkType() { + return mNetworkType; + } + + /** + * Get data connection APN type + */ + public String getDataConnectionAPNType() { + return mAPNType; + } + + /** + * Get data connection APN. + */ + public String getDataConnectionAPN() { + return mAPN; + } + + /** + * Get data connection change reason. + */ + public String getDataConnectionChangeReason() { + return mReason; + } + + /** + * Get the properties of the network link. + */ + public LinkProperties getDataConnectionLinkProperties() { + return mLinkProperties; + } + + /** + * Get data connection fail cause, in case there was a failure. + */ + public String getDataConnectionFailCause() { + return mFailCause; + } + + @Override + public int describeContents() { + return 0; + } + + @Override + public void writeToParcel(Parcel out, int flags) { + out.writeInt(mState); + out.writeInt(mNetworkType); + out.writeString(mAPNType); + out.writeString(mAPN); + out.writeString(mReason); + out.writeParcelable(mLinkProperties, flags); + out.writeString(mFailCause); + } + + public static final Parcelable.Creator<PreciseDataConnectionState> CREATOR + = new Parcelable.Creator<PreciseDataConnectionState>() { + + public PreciseDataConnectionState createFromParcel(Parcel in) { + return new PreciseDataConnectionState(in); + } + + public PreciseDataConnectionState[] newArray(int size) { + return new PreciseDataConnectionState[size]; + } + }; + + @Override + public int hashCode() { + final int prime = 31; + int result = 1; + result = prime * result + mState; + result = prime * result + mNetworkType; + result = prime * result + ((mAPNType == null) ? 0 : mAPNType.hashCode()); + result = prime * result + ((mAPN == null) ? 0 : mAPN.hashCode()); + result = prime * result + ((mReason == null) ? 0 : mReason.hashCode()); + result = prime * result + ((mLinkProperties == null) ? 0 : mLinkProperties.hashCode()); + result = prime * result + ((mFailCause == null) ? 0 : mFailCause.hashCode()); + return result; + } + + @Override + public boolean equals(Object obj) { + if (this == obj) { + return true; + } + if (obj == null) { + return false; + } + if (getClass() != obj.getClass()) { + return false; + } + PreciseDataConnectionState other = (PreciseDataConnectionState) obj; + if (mAPN == null) { + if (other.mAPN != null) { + return false; + } + } else if (!mAPN.equals(other.mAPN)) { + return false; + } + if (mAPNType == null) { + if (other.mAPNType != null) { + return false; + } + } else if (!mAPNType.equals(other.mAPNType)) { + return false; + } + if (mFailCause == null) { + if (other.mFailCause != null) { + return false; + } + } else if (!mFailCause.equals(other.mFailCause)) { + return false; + } + if (mLinkProperties == null) { + if (other.mLinkProperties != null) { + return false; + } + } else if (!mLinkProperties.equals(other.mLinkProperties)) { + return false; + } + if (mNetworkType != other.mNetworkType) { + return false; + } + if (mReason == null) { + if (other.mReason != null) { + return false; + } + } else if (!mReason.equals(other.mReason)) { + return false; + } + if (mState != other.mState) { + return false; + } + return true; + } + + @Override + public String toString() { + StringBuilder sb = new StringBuilder(); + + sb.append("Data Connection state: " + mState); + sb.append(", Network type: " + mNetworkType); + sb.append(", APN type: " + mAPNType); + sb.append(", APN: " + mAPN); + sb.append(", Change reason: " + mReason); + sb.append(", Link properties: " + mLinkProperties); + sb.append(", Fail cause: " + mFailCause); + + return sb.toString(); + } +} diff --git a/android/telephony/PreciseDisconnectCause.java b/android/telephony/PreciseDisconnectCause.java new file mode 100644 index 00000000..2516d512 --- /dev/null +++ b/android/telephony/PreciseDisconnectCause.java @@ -0,0 +1,507 @@ +/* + * Copyright (C) 2014 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.telephony; + +/** + * Contains precise disconnect call causes generated by the + * framework and the RIL. + * + * @hide + */ +public class PreciseDisconnectCause { + + /** The disconnect cause is not valid (Not received a disconnect cause)*/ + public static final int NOT_VALID = -1; + /** No disconnect cause provided. Generally a local disconnect or an incoming missed call */ + public static final int NO_DISCONNECT_CAUSE_AVAILABLE = 0; + /** + * The destination cannot be reached because the number, although valid, + * is not currently assigned + */ + public static final int UNOBTAINABLE_NUMBER = 1; + /** The user cannot be reached because the network through which the call has been + * routed does not serve the destination desired + */ + public static final int NO_ROUTE_TO_DESTINATION = 3; + /** The channel most recently identified is not acceptable to the sending entity for + * use in this call + */ + public static final int CHANNEL_UNACCEPTABLE = 6; + /** The MS has tried to access a service that the MS's network operator or service + * provider is not prepared to allow + */ + public static final int OPERATOR_DETERMINED_BARRING = 8; + /** One of the users involved in the call has requested that the call is cleared */ + public static final int NORMAL = 16; + /** The called user is unable to accept another call */ + public static final int BUSY = 17; + /** The user does not respond to a call establishment message with either an alerting + * or connect indication within the prescribed period of time allocated + */ + public static final int NO_USER_RESPONDING = 18; + /** The user has provided an alerting indication but has not provided a connect + * indication within a prescribed period of time + */ + public static final int NO_ANSWER_FROM_USER = 19; + /** The equipment sending this cause does not wish to accept this call */ + public static final int CALL_REJECTED = 21; + /** The called number is no longer assigned */ + public static final int NUMBER_CHANGED = 22; + /** This cause is returned to the network when a mobile station clears an active + * call which is being pre-empted by another call with higher precedence + */ + public static final int PREEMPTION = 25; + /** The destination indicated by the mobile station cannot be reached because + * the interface to the destination is not functioning correctly + */ + public static final int DESTINATION_OUT_OF_ORDER = 27; + /** The called party number is not a valid format or is not complete */ + public static final int INVALID_NUMBER_FORMAT = 28; + /** The facility requested by user can not be provided by the network */ + public static final int FACILITY_REJECTED = 29; + /** Provided in response to a STATUS ENQUIRY message */ + public static final int STATUS_ENQUIRY = 30; + /** Reports a normal disconnect only when no other normal cause applies */ + public static final int NORMAL_UNSPECIFIED = 31; + /** There is no channel presently available to handle the call */ + public static final int NO_CIRCUIT_AVAIL = 34; + /** The network is not functioning correctly and that the condition is likely + * to last a relatively long period of time + */ + public static final int NETWORK_OUT_OF_ORDER = 38; + /** + * The network is not functioning correctly and the condition is not likely to last + * a long period of time + */ + public static final int TEMPORARY_FAILURE = 41; + /** The switching equipment is experiencing a period of high traffic */ + public static final int SWITCHING_CONGESTION = 42; + /** The network could not deliver access information to the remote user as requested */ + public static final int ACCESS_INFORMATION_DISCARDED = 43; + /** The channel cannot be provided */ + public static final int CHANNEL_NOT_AVAIL = 44; + /** This cause is used to report a resource unavailable event only when no other + * cause in the resource unavailable class applies + */ + public static final int RESOURCES_UNAVAILABLE_OR_UNSPECIFIED = 44; + /** The requested quality of service (ITU-T X.213) cannot be provided */ + public static final int QOS_NOT_AVAIL = 49; + /** The facility could not be provided by the network because the user has no + * complete subscription + */ + public static final int REQUESTED_FACILITY_NOT_SUBSCRIBED = 50; + /** Incoming calls are not allowed within this CUG */ + public static final int INCOMING_CALLS_BARRED_WITHIN_CUG = 55; + /** The mobile station is not authorized to use bearer capability requested */ + public static final int BEARER_CAPABILITY_NOT_AUTHORIZED = 57; + /** The requested bearer capability is not available at this time */ + public static final int BEARER_NOT_AVAIL = 58; + /** The service option is not availble at this time */ + public static final int SERVICE_OPTION_NOT_AVAILABLE = 63; + /** The equipment sending this cause does not support the bearer capability requested */ + public static final int BEARER_SERVICE_NOT_IMPLEMENTED = 65; + /** The call clearing is due to ACM being greater than or equal to ACMmax */ + public static final int ACM_LIMIT_EXCEEDED = 68; + /** The equipment sending this cause does not support the requested facility */ + public static final int REQUESTED_FACILITY_NOT_IMPLEMENTED = 69; + /** The equipment sending this cause only supports the restricted version of + * the requested bearer capability + */ + public static final int ONLY_DIGITAL_INFORMATION_BEARER_AVAILABLE = 70; + /** The service requested is not implemented at network */ + public static final int SERVICE_OR_OPTION_NOT_IMPLEMENTED = 79; + /** The equipment sending this cause has received a message with a transaction identifier + * which is not currently in use on the MS-network interface + */ + public static final int INVALID_TRANSACTION_IDENTIFIER = 81; + /** The called user for the incoming CUG call is not a member of the specified CUG */ + public static final int USER_NOT_MEMBER_OF_CUG = 87; + /** The equipment sending this cause has received a request which can't be accomodated */ + public static final int INCOMPATIBLE_DESTINATION = 88; + /** This cause is used to report receipt of a message with semantically incorrect contents */ + public static final int SEMANTICALLY_INCORRECT_MESSAGE = 95; + /** The equipment sending this cause has received a message with a non-semantical + * mandatory IE error + */ + public static final int INVALID_MANDATORY_INFORMATION = 96; + /** This is sent in response to a message which is not defined, or defined but not + * implemented by the equipment sending this cause + */ + public static final int MESSAGE_TYPE_NON_IMPLEMENTED = 97; + /** The equipment sending this cause has received a message not compatible with the + * protocol state + */ + public static final int MESSAGE_TYPE_NOT_COMPATIBLE_WITH_PROTOCOL_STATE = 98; + /** The equipment sending this cause has received a message which includes information + * elements not recognized because its identifier is not defined or it is defined but not + * implemented by the equipment sending the cause + */ + public static final int INFORMATION_ELEMENT_NON_EXISTENT = 99; + /** The equipment sending this cause has received a message with conditional IE errors */ + public static final int CONDITIONAL_IE_ERROR = 100; + /** The message has been received which is incompatible with the protocol state */ + public static final int MESSAGE_NOT_COMPATIBLE_WITH_PROTOCOL_STATE = 101; + /** The procedure has been initiated by the expiry of a timer in association with + * 3GPP TS 24.008 error handling procedures + */ + public static final int RECOVERY_ON_TIMER_EXPIRED = 102; + /** This protocol error event is reported only when no other cause in the protocol + * error class applies + */ + public static final int PROTOCOL_ERROR_UNSPECIFIED = 111; + /** interworking with a network which does not provide causes for actions it takes + * thus, the precise cause for a message which is being sent cannot be ascertained + */ + public static final int INTERWORKING_UNSPECIFIED = 127; + /** The call is restricted */ + public static final int CALL_BARRED = 240; + /** The call is blocked by the Fixed Dialing Number list */ + public static final int FDN_BLOCKED = 241; + /** The given IMSI is not known at the VLR */ + /** TS 24.008 cause 4 */ + public static final int IMSI_UNKNOWN_IN_VLR = 242; + /** + * The network does not accept emergency call establishment using an IMEI or not accept attach + * procedure for emergency services using an IMEI + */ + public static final int IMEI_NOT_ACCEPTED = 243; + /** The call cannot be established because RADIO is OFF */ + public static final int RADIO_OFF = 247; + /** The call cannot be established because of no cell coverage */ + public static final int OUT_OF_SRV = 248; + /** The call cannot be established because of no valid SIM */ + public static final int NO_VALID_SIM = 249; + /** The call is dropped or failed internally by modem */ + public static final int RADIO_INTERNAL_ERROR = 250; + /** Call failed because of UE timer expired while waiting for a response from network */ + public static final int NETWORK_RESP_TIMEOUT = 251; + /** Call failed because of a network reject */ + public static final int NETWORK_REJECT = 252; + /** Call failed because of radio access failure. ex. RACH failure */ + public static final int RADIO_ACCESS_FAILURE = 253; + /** Call failed/dropped because of a RLF */ + public static final int RADIO_LINK_FAILURE = 254; + /** Call failed/dropped because of radio link lost */ + public static final int RADIO_LINK_LOST = 255; + /** Call failed because of a radio uplink issue */ + public static final int RADIO_UPLINK_FAILURE = 256; + /** Call failed because of a RRC connection setup failure */ + public static final int RADIO_SETUP_FAILURE = 257; + /** Call failed/dropped because of RRC connection release from NW */ + public static final int RADIO_RELEASE_NORMAL = 258; + /** Call failed/dropped because of RRC abnormally released by modem/network */ + public static final int RADIO_RELEASE_ABNORMAL = 259; + /** Call setup failed because of access class barring */ + public static final int ACCESS_CLASS_BLOCKED = 260; + /** Call failed/dropped because of a network detach */ + public static final int NETWORK_DETACH = 261; + + /** MS is locked until next power cycle */ + public static final int CDMA_LOCKED_UNTIL_POWER_CYCLE = 1000; + /** Drop call*/ + public static final int CDMA_DROP = 1001; + /** INTERCEPT order received, MS state idle entered */ + public static final int CDMA_INTERCEPT = 1002; + /** MS has been redirected, call is cancelled */ + public static final int CDMA_REORDER = 1003; + /** Service option rejection */ + public static final int CDMA_SO_REJECT = 1004; + /** Requested service is rejected, retry delay is set */ + public static final int CDMA_RETRY_ORDER = 1005; + /** Unable to obtain access to the CDMA system */ + public static final int CDMA_ACCESS_FAILURE = 1006; + /** Not a preempted call */ + public static final int CDMA_PREEMPTED = 1007; + /** Not an emergency call */ + public static final int CDMA_NOT_EMERGENCY = 1008; + /** Access Blocked by CDMA network */ + public static final int CDMA_ACCESS_BLOCKED = 1009; + + /** Mapped from ImsReasonInfo */ + /* The passed argument is an invalid */ + public static final int LOCAL_ILLEGAL_ARGUMENT = 1200; + // The operation is invoked in invalid call state + public static final int LOCAL_ILLEGAL_STATE = 1201; + // IMS service internal error + public static final int LOCAL_INTERNAL_ERROR = 1202; + // IMS service goes down (service connection is lost) + public static final int LOCAL_IMS_SERVICE_DOWN = 1203; + // No pending incoming call exists + public static final int LOCAL_NO_PENDING_CALL = 1204; + // Service unavailable; by power off + public static final int LOCAL_POWER_OFF = 1205; + // Service unavailable; by low battery + public static final int LOCAL_LOW_BATTERY = 1206; + // Service unavailable; by out of service (data service state) + public static final int LOCAL_NETWORK_NO_SERVICE = 1207; + /* Service unavailable; by no LTE coverage + * (VoLTE is not supported even though IMS is registered) + */ + public static final int LOCAL_NETWORK_NO_LTE_COVERAGE = 1208; + /** Service unavailable; by located in roaming area */ + public static final int LOCAL_NETWORK_ROAMING = 1209; + /** Service unavailable; by IP changed */ + public static final int LOCAL_NETWORK_IP_CHANGED = 1210; + /** Service unavailable; other */ + public static final int LOCAL_SERVICE_UNAVAILABLE = 1211; + /* Service unavailable; IMS connection is lost (IMS is not registered) */ + public static final int LOCAL_NOT_REGISTERED = 1212; + /** Max call exceeded */ + public static final int LOCAL_MAX_CALL_EXCEEDED = 1213; + /** Call decline */ + public static final int LOCAL_CALL_DECLINE = 1214; + /** SRVCC is in progress */ + public static final int LOCAL_CALL_VCC_ON_PROGRESSING = 1215; + /** Resource reservation is failed (QoS precondition) */ + public static final int LOCAL_CALL_RESOURCE_RESERVATION_FAILED = 1216; + /** Retry CS call; VoLTE service can't be provided by the network or remote end + * Resolve the extra code(EXTRA_CODE_CALL_RETRY_*) if the below code is set + */ + public static final int LOCAL_CALL_CS_RETRY_REQUIRED = 1217; + /** Retry VoLTE call; VoLTE service can't be provided by the network temporarily */ + public static final int LOCAL_CALL_VOLTE_RETRY_REQUIRED = 1218; + /** IMS call is already terminated (in TERMINATED state) */ + public static final int LOCAL_CALL_TERMINATED = 1219; + /** Handover not feasible */ + public static final int LOCAL_HO_NOT_FEASIBLE = 1220; + + /** 1xx waiting timer is expired after sending INVITE request (MO only) */ + public static final int TIMEOUT_1XX_WAITING = 1221; + /** User no answer during call setup operation (MO/MT) + * MO : 200 OK to INVITE request is not received, + * MT : No action from user after alerting the call + */ + public static final int TIMEOUT_NO_ANSWER = 1222; + /** User no answer during call update operation (MO/MT) + * MO : 200 OK to re-INVITE request is not received, + * MT : No action from user after alerting the call + */ + public static final int TIMEOUT_NO_ANSWER_CALL_UPDATE = 1223; + + /** + * STATUSCODE (SIP response code) (IMS -> Telephony) + */ + /** SIP request is redirected */ + public static final int SIP_REDIRECTED = 1300; + /** 4xx responses */ + /** 400 : Bad Request */ + public static final int SIP_BAD_REQUEST = 1310; + /** 403 : Forbidden */ + public static final int SIP_FORBIDDEN = 1311; + /** 404 : Not Found */ + public static final int SIP_NOT_FOUND = 1312; + /** 415 : Unsupported Media Type + * 416 : Unsupported URI Scheme + * 420 : Bad Extension + */ + public static final int SIP_NOT_SUPPORTED = 1313; + /** 408 : Request Timeout */ + public static final int SIP_REQUEST_TIMEOUT = 1314; + /** 480 : Temporarily Unavailable */ + public static final int SIP_TEMPRARILY_UNAVAILABLE = 1315; + /** 484 : Address Incomplete */ + public static final int SIP_BAD_ADDRESS = 1316; + /** 486 : Busy Here + * 600 : Busy Everywhere + */ + public static final int SIP_BUSY = 1317; + /** 487 : Request Terminated */ + public static final int SIP_REQUEST_CANCELLED = 1318; + /** 406 : Not Acceptable + * 488 : Not Acceptable Here + * 606 : Not Acceptable + */ + public static final int SIP_NOT_ACCEPTABLE = 1319; + /** 410 : Gone + * 604 : Does Not Exist Anywhere + */ + public static final int SIP_NOT_REACHABLE = 1320; + /** Others */ + public static final int SIP_CLIENT_ERROR = 1321; + /** 5xx responses + * 501 : Server Internal Error + */ + public static final int SIP_SERVER_INTERNAL_ERROR = 1330; + /** 503 : Service Unavailable */ + public static final int SIP_SERVICE_UNAVAILABLE = 1331; + /** 504 : Server Time-out */ + public static final int SIP_SERVER_TIMEOUT = 1332; + /** Others */ + public static final int SIP_SERVER_ERROR = 1333; + /** 6xx responses + * 603 : Decline + */ + public static final int SIP_USER_REJECTED = 1340; + /** Others */ + public static final int SIP_GLOBAL_ERROR = 1341; + /** Emergency failure */ + public static final int EMERGENCY_TEMP_FAILURE = 1342; + public static final int EMERGENCY_PERM_FAILURE = 1343; + /** Media resource initialization failed */ + public static final int MEDIA_INIT_FAILED = 1400; + /** RTP timeout (no audio / video traffic in the session) */ + public static final int MEDIA_NO_DATA = 1401; + /** Media is not supported; so dropped the call */ + public static final int MEDIA_NOT_ACCEPTABLE = 1402; + /** Unknown media related errors */ + public static final int MEDIA_UNSPECIFIED = 1403; + /** User triggers the call end */ + public static final int USER_TERMINATED = 1500; + /** No action while an incoming call is ringing */ + public static final int USER_NOANSWER = 1501; + /** User ignores an incoming call */ + public static final int USER_IGNORE = 1502; + /** User declines an incoming call */ + public static final int USER_DECLINE = 1503; + /** Device declines/ends a call due to low battery */ + public static final int LOW_BATTERY = 1504; + /** Device declines call due to blacklisted call ID */ + public static final int BLACKLISTED_CALL_ID = 1505; + /** The call is terminated by the network or remote user */ + public static final int USER_TERMINATED_BY_REMOTE = 1510; + + /** + * UT + */ + public static final int UT_NOT_SUPPORTED = 1800; + public static final int UT_SERVICE_UNAVAILABLE = 1801; + public static final int UT_OPERATION_NOT_ALLOWED = 1802; + public static final int UT_NETWORK_ERROR = 1803; + public static final int UT_CB_PASSWORD_MISMATCH = 1804; + + /** + * ECBM + */ + public static final int ECBM_NOT_SUPPORTED = 1900; + + /** + * Fail code used to indicate that Multi-endpoint is not supported by the Ims framework. + */ + public static final int MULTIENDPOINT_NOT_SUPPORTED = 1901; + + /** + * CALL DROP error codes (Call could drop because of many reasons like Network not available, + * handover, failed, etc) + */ + + /** + * CALL DROP error code for the case when a device is ePDG capable and when the user is on an + * active wifi call and at the edge of coverage and there is no qualified LTE network available + * to handover the call to. We get a handover NOT_TRIGERRED message from the modem. This error + * code is received as part of the handover message. + */ + public static final int CALL_DROP_IWLAN_TO_LTE_UNAVAILABLE = 2000; + + /** + * MT call has ended due to a release from the network + * because the call was answered elsewhere + */ + public static final int ANSWERED_ELSEWHERE = 2100; + + /** + * For MultiEndpoint - Call Pull request has failed + */ + public static final int CALL_PULL_OUT_OF_SYNC = 2101; + + /** + * For MultiEndpoint - Call has been pulled from primary to secondary + */ + public static final int CALL_PULLED = 2102; + + /** + * Supplementary services (HOLD/RESUME) failure error codes. + * Values for Supplemetary services failure - Failed, Cancelled and Re-Invite collision. + */ + public static final int SUPP_SVC_FAILED = 2300; + public static final int SUPP_SVC_CANCELLED = 2301; + public static final int SUPP_SVC_REINVITE_COLLISION = 2302; + + /** + * DPD Procedure received no response or send failed + */ + public static final int IWLAN_DPD_FAILURE = 2400; + + /** + * Establishment of the ePDG Tunnel Failed + */ + public static final int EPDG_TUNNEL_ESTABLISH_FAILURE = 2500; + + /** + * Re-keying of the ePDG Tunnel Failed; may not always result in teardown + */ + public static final int EPDG_TUNNEL_REKEY_FAILURE = 2501; + + /** + * Connection to the packet gateway is lost + */ + public static final int EPDG_TUNNEL_LOST_CONNECTION = 2502; + + /** + * The maximum number of calls allowed has been reached. Used in a multi-endpoint scenario + * where the number of calls across all connected devices has reached the maximum. + */ + public static final int MAXIMUM_NUMBER_OF_CALLS_REACHED = 2503; + + /** + * Similar to {@link #CODE_LOCAL_CALL_DECLINE}, except indicates that a remote device has + * declined the call. Used in a multi-endpoint scenario where a remote device declined an + * incoming call. + */ + public static final int REMOTE_CALL_DECLINE = 2504; + + /** + * Indicates the call was disconnected due to the user reaching their data limit. + */ + public static final int DATA_LIMIT_REACHED = 2505; + + /** + * Indicates the call was disconnected due to the user disabling cellular data. + */ + public static final int DATA_DISABLED = 2506; + + /** + * Indicates a call was disconnected due to loss of wifi signal. + */ + public static final int WIFI_LOST = 2507; + + + /* OEM specific error codes. To be used by OEMs when they don't want to + reveal error code which would be replaced by ERROR_UNSPECIFIED */ + public static final int OEM_CAUSE_1 = 0xf001; + public static final int OEM_CAUSE_2 = 0xf002; + public static final int OEM_CAUSE_3 = 0xf003; + public static final int OEM_CAUSE_4 = 0xf004; + public static final int OEM_CAUSE_5 = 0xf005; + public static final int OEM_CAUSE_6 = 0xf006; + public static final int OEM_CAUSE_7 = 0xf007; + public static final int OEM_CAUSE_8 = 0xf008; + public static final int OEM_CAUSE_9 = 0xf009; + public static final int OEM_CAUSE_10 = 0xf00a; + public static final int OEM_CAUSE_11 = 0xf00b; + public static final int OEM_CAUSE_12 = 0xf00c; + public static final int OEM_CAUSE_13 = 0xf00d; + public static final int OEM_CAUSE_14 = 0xf00e; + public static final int OEM_CAUSE_15 = 0xf00f; + + /** Disconnected due to unspecified reasons */ + public static final int ERROR_UNSPECIFIED = 0xffff; + + /** Private constructor to avoid class instantiation. */ + private PreciseDisconnectCause() { + // Do nothing. + } +} diff --git a/android/telephony/RadioAccessFamily.java b/android/telephony/RadioAccessFamily.java new file mode 100644 index 00000000..d657bae0 --- /dev/null +++ b/android/telephony/RadioAccessFamily.java @@ -0,0 +1,385 @@ +/* +* Copyright (C) 2014 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.telephony; + +import android.os.Parcel; +import android.os.Parcelable; + +import com.android.internal.telephony.RILConstants; + +/** + * Object to indicate the phone radio type and access technology. + * + * @hide + */ +public class RadioAccessFamily implements Parcelable { + + // Radio Access Family + // 2G + public static final int RAF_UNKNOWN = (1 << ServiceState.RIL_RADIO_TECHNOLOGY_UNKNOWN); + public static final int RAF_GSM = (1 << ServiceState.RIL_RADIO_TECHNOLOGY_GSM); + public static final int RAF_GPRS = (1 << ServiceState.RIL_RADIO_TECHNOLOGY_GPRS); + public static final int RAF_EDGE = (1 << ServiceState.RIL_RADIO_TECHNOLOGY_EDGE); + public static final int RAF_IS95A = (1 << ServiceState.RIL_RADIO_TECHNOLOGY_IS95A); + public static final int RAF_IS95B = (1 << ServiceState.RIL_RADIO_TECHNOLOGY_IS95B); + public static final int RAF_1xRTT = (1 << ServiceState.RIL_RADIO_TECHNOLOGY_1xRTT); + // 3G + public static final int RAF_EVDO_0 = (1 << ServiceState.RIL_RADIO_TECHNOLOGY_EVDO_0); + public static final int RAF_EVDO_A = (1 << ServiceState.RIL_RADIO_TECHNOLOGY_EVDO_A); + public static final int RAF_EVDO_B = (1 << ServiceState.RIL_RADIO_TECHNOLOGY_EVDO_B); + public static final int RAF_EHRPD = (1 << ServiceState.RIL_RADIO_TECHNOLOGY_EHRPD); + public static final int RAF_HSUPA = (1 << ServiceState.RIL_RADIO_TECHNOLOGY_HSUPA); + public static final int RAF_HSDPA = (1 << ServiceState.RIL_RADIO_TECHNOLOGY_HSDPA); + public static final int RAF_HSPA = (1 << ServiceState.RIL_RADIO_TECHNOLOGY_HSPA); + public static final int RAF_HSPAP = (1 << ServiceState.RIL_RADIO_TECHNOLOGY_HSPAP); + public static final int RAF_UMTS = (1 << ServiceState.RIL_RADIO_TECHNOLOGY_UMTS); + public static final int RAF_TD_SCDMA = (1 << ServiceState.RIL_RADIO_TECHNOLOGY_TD_SCDMA); + // 4G + public static final int RAF_LTE = (1 << ServiceState.RIL_RADIO_TECHNOLOGY_LTE); + public static final int RAF_LTE_CA = (1 << ServiceState.RIL_RADIO_TECHNOLOGY_LTE_CA); + + // Grouping of RAFs + // 2G + private static final int GSM = RAF_GSM | RAF_GPRS | RAF_EDGE; + private static final int CDMA = RAF_IS95A | RAF_IS95B | RAF_1xRTT; + // 3G + private static final int EVDO = RAF_EVDO_0 | RAF_EVDO_A | RAF_EVDO_B | RAF_EHRPD; + private static final int HS = RAF_HSUPA | RAF_HSDPA | RAF_HSPA | RAF_HSPAP; + private static final int WCDMA = HS | RAF_UMTS; + // 4G + private static final int LTE = RAF_LTE | RAF_LTE_CA; + + /* Phone ID of phone */ + private int mPhoneId; + + /* Radio Access Family */ + private int mRadioAccessFamily; + + /** + * Constructor. + * + * @param phoneId the phone ID + * @param radioAccessFamily the phone radio access family defined + * in RadioAccessFamily. It's a bit mask value to represent + * the support type. + */ + public RadioAccessFamily(int phoneId, int radioAccessFamily) { + mPhoneId = phoneId; + mRadioAccessFamily = radioAccessFamily; + } + + /** + * Get phone ID. + * + * @return phone ID + */ + public int getPhoneId() { + return mPhoneId; + } + + /** + * get radio access family. + * + * @return radio access family + */ + public int getRadioAccessFamily() { + return mRadioAccessFamily; + } + + @Override + public String toString() { + String ret = "{ mPhoneId = " + mPhoneId + + ", mRadioAccessFamily = " + mRadioAccessFamily + + "}"; + return ret; + } + + /** + * Implement the Parcelable interface. + * + * @return describe content + */ + @Override + public int describeContents() { + return 0; + } + + /** + * Implement the Parcelable interface. + * + * @param outParcel The Parcel in which the object should be written. + * @param flags Additional flags about how the object should be written. + */ + @Override + public void writeToParcel(Parcel outParcel, int flags) { + outParcel.writeInt(mPhoneId); + outParcel.writeInt(mRadioAccessFamily); + } + + /** + * Implement the Parcelable interface. + */ + public static final Creator<RadioAccessFamily> CREATOR = + new Creator<RadioAccessFamily>() { + + @Override + public RadioAccessFamily createFromParcel(Parcel in) { + int phoneId = in.readInt(); + int radioAccessFamily = in.readInt(); + + return new RadioAccessFamily(phoneId, radioAccessFamily); + } + + @Override + public RadioAccessFamily[] newArray(int size) { + return new RadioAccessFamily[size]; + } + }; + + public static int getRafFromNetworkType(int type) { + int raf; + + switch (type) { + case RILConstants.NETWORK_MODE_WCDMA_PREF: + raf = GSM | WCDMA; + break; + case RILConstants.NETWORK_MODE_GSM_ONLY: + raf = GSM; + break; + case RILConstants.NETWORK_MODE_WCDMA_ONLY: + raf = WCDMA; + break; + case RILConstants.NETWORK_MODE_GSM_UMTS: + raf = GSM | WCDMA; + break; + case RILConstants.NETWORK_MODE_CDMA: + raf = CDMA | EVDO; + break; + case RILConstants.NETWORK_MODE_LTE_CDMA_EVDO: + raf = LTE | CDMA | EVDO; + break; + case RILConstants.NETWORK_MODE_LTE_GSM_WCDMA: + raf = LTE | GSM | WCDMA; + break; + case RILConstants.NETWORK_MODE_LTE_CDMA_EVDO_GSM_WCDMA: + raf = LTE | CDMA | EVDO | GSM | WCDMA; + break; + case RILConstants.NETWORK_MODE_LTE_ONLY: + raf = LTE; + break; + case RILConstants.NETWORK_MODE_LTE_WCDMA: + raf = LTE | WCDMA; + break; + case RILConstants.NETWORK_MODE_CDMA_NO_EVDO: + raf = CDMA; + break; + case RILConstants.NETWORK_MODE_EVDO_NO_CDMA: + raf = EVDO; + break; + case RILConstants.NETWORK_MODE_GLOBAL: + raf = GSM | WCDMA | CDMA | EVDO; + break; + case RILConstants.NETWORK_MODE_TDSCDMA_ONLY: + raf = RAF_TD_SCDMA; + break; + case RILConstants.NETWORK_MODE_TDSCDMA_WCDMA: + raf = RAF_TD_SCDMA | WCDMA; + break; + case RILConstants.NETWORK_MODE_LTE_TDSCDMA: + raf = LTE | RAF_TD_SCDMA; + break; + case RILConstants.NETWORK_MODE_TDSCDMA_GSM: + raf = RAF_TD_SCDMA | GSM; + break; + case RILConstants.NETWORK_MODE_LTE_TDSCDMA_GSM: + raf = LTE | RAF_TD_SCDMA | GSM; + break; + case RILConstants.NETWORK_MODE_TDSCDMA_GSM_WCDMA: + raf = RAF_TD_SCDMA | GSM | WCDMA; + break; + case RILConstants.NETWORK_MODE_LTE_TDSCDMA_WCDMA: + raf = LTE | RAF_TD_SCDMA | WCDMA; + break; + case RILConstants.NETWORK_MODE_LTE_TDSCDMA_GSM_WCDMA: + raf = LTE | RAF_TD_SCDMA | GSM | WCDMA; + break; + case RILConstants.NETWORK_MODE_TDSCDMA_CDMA_EVDO_GSM_WCDMA: + raf = RAF_TD_SCDMA | CDMA | EVDO | GSM | WCDMA; + break; + case RILConstants.NETWORK_MODE_LTE_TDSCDMA_CDMA_EVDO_GSM_WCDMA: + raf = LTE | RAF_TD_SCDMA | CDMA | EVDO | GSM | WCDMA; + break; + default: + raf = RAF_UNKNOWN; + break; + } + + return raf; + } + + /** + * if the raf includes ANY bit set for a group + * adjust it to contain ALL the bits for that group + */ + private static int getAdjustedRaf(int raf) { + raf = ((GSM & raf) > 0) ? (GSM | raf) : raf; + raf = ((WCDMA & raf) > 0) ? (WCDMA | raf) : raf; + raf = ((CDMA & raf) > 0) ? (CDMA | raf) : raf; + raf = ((EVDO & raf) > 0) ? (EVDO | raf) : raf; + raf = ((LTE & raf) > 0) ? (LTE | raf) : raf; + + return raf; + } + + /** + * Returns the highest capability of the RadioAccessFamily (4G > 3G > 2G). + * @param raf The RadioAccessFamily that we wish to filter + * @return The highest radio capability + */ + public static int getHighestRafCapability(int raf) { + if ((LTE & raf) > 0) { + return TelephonyManager.NETWORK_CLASS_4_G; + } + if ((EVDO|HS|WCDMA & raf) > 0) { + return TelephonyManager.NETWORK_CLASS_3_G; + } + if((GSM|CDMA & raf) > 0) { + return TelephonyManager.NETWORK_CLASS_2_G; + } + return TelephonyManager.NETWORK_CLASS_UNKNOWN; + } + + public static int getNetworkTypeFromRaf(int raf) { + int type; + + raf = getAdjustedRaf(raf); + + switch (raf) { + case (GSM | WCDMA): + type = RILConstants.NETWORK_MODE_WCDMA_PREF; + break; + case GSM: + type = RILConstants.NETWORK_MODE_GSM_ONLY; + break; + case WCDMA: + type = RILConstants.NETWORK_MODE_WCDMA_ONLY; + break; + case (CDMA | EVDO): + type = RILConstants.NETWORK_MODE_CDMA; + break; + case (LTE | CDMA | EVDO): + type = RILConstants.NETWORK_MODE_LTE_CDMA_EVDO; + break; + case (LTE | GSM | WCDMA): + type = RILConstants.NETWORK_MODE_LTE_GSM_WCDMA; + break; + case (LTE | CDMA | EVDO | GSM | WCDMA): + type = RILConstants.NETWORK_MODE_LTE_CDMA_EVDO_GSM_WCDMA; + break; + case LTE: + type = RILConstants.NETWORK_MODE_LTE_ONLY; + break; + case (LTE | WCDMA): + type = RILConstants.NETWORK_MODE_LTE_WCDMA; + break; + case CDMA: + type = RILConstants.NETWORK_MODE_CDMA_NO_EVDO; + break; + case EVDO: + type = RILConstants.NETWORK_MODE_EVDO_NO_CDMA; + break; + case (GSM | WCDMA | CDMA | EVDO): + type = RILConstants.NETWORK_MODE_GLOBAL; + break; + case RAF_TD_SCDMA: + type = RILConstants.NETWORK_MODE_TDSCDMA_ONLY; + break; + case (RAF_TD_SCDMA | WCDMA): + type = RILConstants.NETWORK_MODE_TDSCDMA_WCDMA; + break; + case (LTE | RAF_TD_SCDMA): + type = RILConstants.NETWORK_MODE_LTE_TDSCDMA; + break; + case (RAF_TD_SCDMA | GSM): + type = RILConstants.NETWORK_MODE_TDSCDMA_GSM; + break; + case (LTE | RAF_TD_SCDMA | GSM): + type = RILConstants.NETWORK_MODE_LTE_TDSCDMA_GSM; + break; + case (RAF_TD_SCDMA | GSM | WCDMA): + type = RILConstants.NETWORK_MODE_TDSCDMA_GSM_WCDMA; + break; + case (LTE | RAF_TD_SCDMA | WCDMA): + type = RILConstants.NETWORK_MODE_LTE_TDSCDMA_WCDMA; + break; + case (LTE | RAF_TD_SCDMA | GSM | WCDMA): + type = RILConstants.NETWORK_MODE_LTE_TDSCDMA_GSM_WCDMA; + break; + case (RAF_TD_SCDMA | CDMA | EVDO | GSM | WCDMA): + type = RILConstants.NETWORK_MODE_TDSCDMA_CDMA_EVDO_GSM_WCDMA; + break; + case (LTE | RAF_TD_SCDMA | CDMA | EVDO | GSM | WCDMA): + type = RILConstants.NETWORK_MODE_LTE_TDSCDMA_CDMA_EVDO_GSM_WCDMA; + break; + default: + type = RILConstants.PREFERRED_NETWORK_MODE ; + break; + } + + return type; + } + + public static int singleRafTypeFromString(String rafString) { + switch (rafString) { + case "GPRS": return RAF_GPRS; + case "EDGE": return RAF_EDGE; + case "UMTS": return RAF_UMTS; + case "IS95A": return RAF_IS95A; + case "IS95B": return RAF_IS95B; + case "1XRTT": return RAF_1xRTT; + case "EVDO_0": return RAF_EVDO_0; + case "EVDO_A": return RAF_EVDO_A; + case "HSDPA": return RAF_HSDPA; + case "HSUPA": return RAF_HSUPA; + case "HSPA": return RAF_HSPA; + case "EVDO_B": return RAF_EVDO_B; + case "EHRPD": return RAF_EHRPD; + case "LTE": return RAF_LTE; + case "HSPAP": return RAF_HSPAP; + case "GSM": return RAF_GSM; + case "TD_SCDMA":return RAF_TD_SCDMA; + case "HS": return HS; + case "CDMA": return CDMA; + case "EVDO": return EVDO; + case "WCDMA": return WCDMA; + case "LTE_CA": return RAF_LTE_CA; + default: return RAF_UNKNOWN; + } + } + + public static int rafTypeFromString(String rafList) { + rafList = rafList.toUpperCase(); + String[] rafs = rafList.split("\\|"); + int result = 0; + for(String raf : rafs) { + int rafType = singleRafTypeFromString(raf.trim()); + if (rafType == RAF_UNKNOWN) return rafType; + result |= rafType; + } + return result; + } +} diff --git a/android/telephony/RadioAccessSpecifier.java b/android/telephony/RadioAccessSpecifier.java new file mode 100644 index 00000000..33ce8b42 --- /dev/null +++ b/android/telephony/RadioAccessSpecifier.java @@ -0,0 +1,129 @@ +/* + * Copyright (C) 2017 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.telephony; + +import android.os.Parcel; +import android.os.Parcelable; + +import java.util.Arrays; + +/** + * Describes a particular radio access network to be scanned. + * + * The scan can be performed on either bands or channels for a specific radio access network type. + * @hide + */ +public final class RadioAccessSpecifier implements Parcelable { + + /** + * The radio access network that needs to be scanned + * + * See {@link RadioNetworkConstants.RadioAccessNetworks} for details. + */ + public int radioAccessNetwork; + + /** + * The frequency bands that need to be scanned + * + * bands must be used together with radioAccessNetwork + * + * See {@link RadioNetworkConstants} for details. + */ + public int[] bands; + + /** + * The frequency channels that need to be scanned + * + * channels must be used together with radioAccessNetwork + * + * See {@link RadioNetworkConstants.RadioAccessNetworks} for details. + */ + public int[] channels; + + /** + * Creates a new RadioAccessSpecifier with radio network, bands and channels + * + * The user must specify the radio network type, and at least specify either of frequency + * bands or channels. + * + * @param ran The type of the radio access network + * @param bands the frequency bands to be scanned + * @param channels the frequency bands to be scanned + */ + public RadioAccessSpecifier(int ran, int[] bands, int[] channels) { + this.radioAccessNetwork = ran; + this.bands = bands; + this.channels = channels; + } + + public static final Parcelable.Creator<RadioAccessSpecifier> CREATOR = + new Parcelable.Creator<RadioAccessSpecifier> (){ + @Override + public RadioAccessSpecifier createFromParcel(Parcel in) { + return new RadioAccessSpecifier(in); + } + + @Override + public RadioAccessSpecifier[] newArray(int size) { + return new RadioAccessSpecifier[size]; + } + }; + + @Override + public int describeContents() { + return 0; + } + + @Override + public void writeToParcel(Parcel dest, int flags) { + dest.writeInt(radioAccessNetwork); + dest.writeIntArray(bands); + dest.writeIntArray(channels); + } + + private RadioAccessSpecifier(Parcel in) { + radioAccessNetwork = in.readInt(); + bands = in.createIntArray(); + channels = in.createIntArray(); + } + + @Override + public boolean equals (Object o) { + RadioAccessSpecifier ras; + + try { + ras = (RadioAccessSpecifier) o; + } catch (ClassCastException ex) { + return false; + } + + if (o == null) { + return false; + } + + return (radioAccessNetwork == ras.radioAccessNetwork + && Arrays.equals(bands, ras.bands) + && Arrays.equals(channels, ras.channels)); + } + + @Override + public int hashCode () { + return ((radioAccessNetwork * 31) + + (Arrays.hashCode(bands) * 37) + + (Arrays.hashCode(channels)) * 39); + } +} diff --git a/android/telephony/RadioNetworkConstants.java b/android/telephony/RadioNetworkConstants.java new file mode 100644 index 00000000..1a9072d3 --- /dev/null +++ b/android/telephony/RadioNetworkConstants.java @@ -0,0 +1,169 @@ +/* + * Copyright (C) 2017 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.telephony; + +/** + * Contains radio access network related constants. + * @hide + */ +public final class RadioNetworkConstants { + + public static final class RadioAccessNetworks { + public static final int GERAN = 1; + public static final int UTRAN = 2; + public static final int EUTRAN = 3; + /** @hide */ + public static final int CDMA2000 = 4; + } + + /** + * Frenquency bands for GERAN. + * http://www.etsi.org/deliver/etsi_ts/145000_145099/145005/14.00.00_60/ts_145005v140000p.pdf + */ + public static final class GeranBands { + public static final int BAND_T380 = 1; + public static final int BAND_T410 = 2; + public static final int BAND_450 = 3; + public static final int BAND_480 = 4; + public static final int BAND_710 = 5; + public static final int BAND_750 = 6; + public static final int BAND_T810 = 7; + public static final int BAND_850 = 8; + public static final int BAND_P900 = 9; + public static final int BAND_E900 = 10; + public static final int BAND_R900 = 11; + public static final int BAND_DCS1800 = 12; + public static final int BAND_PCS1900 = 13; + public static final int BAND_ER900 = 14; + } + + /** + * Frenquency bands for UTRAN. + * http://www.etsi.org/deliver/etsi_ts/125100_125199/125104/13.03.00_60/ts_125104v130p.pdf + */ + public static final class UtranBands { + public static final int BAND_1 = 1; + public static final int BAND_2 = 2; + public static final int BAND_3 = 3; + public static final int BAND_4 = 4; + public static final int BAND_5 = 5; + public static final int BAND_6 = 6; + public static final int BAND_7 = 7; + public static final int BAND_8 = 8; + public static final int BAND_9 = 9; + public static final int BAND_10 = 10; + public static final int BAND_11 = 11; + public static final int BAND_12 = 12; + public static final int BAND_13 = 13; + public static final int BAND_14 = 14; + /** band 15, 16, 17, 18 are reserved */ + public static final int BAND_19 = 19; + public static final int BAND_20 = 20; + public static final int BAND_21 = 21; + public static final int BAND_22 = 22; + /** band 23, 24 are reserved */ + public static final int BAND_25 = 25; + public static final int BAND_26 = 26; + } + + /** + * Frenquency bands for EUTRAN. + * http://www.etsi.org/deliver/etsi_ts/136100_136199/136101/14.03.00_60/ts_136101v140p.pdf + */ + public static final class EutranBands { + public static final int BAND_1 = 1; + public static final int BAND_2 = 2; + public static final int BAND_3 = 3; + public static final int BAND_4 = 4; + public static final int BAND_5 = 5; + public static final int BAND_6 = 6; + public static final int BAND_7 = 7; + public static final int BAND_8 = 8; + public static final int BAND_9 = 9; + public static final int BAND_10 = 10; + public static final int BAND_11 = 11; + public static final int BAND_12 = 12; + public static final int BAND_13 = 13; + public static final int BAND_14 = 14; + public static final int BAND_17 = 17; + public static final int BAND_18 = 18; + public static final int BAND_19 = 19; + public static final int BAND_20 = 20; + public static final int BAND_21 = 21; + public static final int BAND_22 = 22; + public static final int BAND_23 = 23; + public static final int BAND_24 = 24; + public static final int BAND_25 = 25; + public static final int BAND_26 = 26; + public static final int BAND_27 = 27; + public static final int BAND_28 = 28; + public static final int BAND_30 = 30; + public static final int BAND_31 = 31; + public static final int BAND_33 = 33; + public static final int BAND_34 = 34; + public static final int BAND_35 = 35; + public static final int BAND_36 = 36; + public static final int BAND_37 = 37; + public static final int BAND_38 = 38; + public static final int BAND_39 = 39; + public static final int BAND_40 = 40; + public static final int BAND_41 = 41; + public static final int BAND_42 = 42; + public static final int BAND_43 = 43; + public static final int BAND_44 = 44; + public static final int BAND_45 = 45; + public static final int BAND_46 = 46; + public static final int BAND_47 = 47; + public static final int BAND_48 = 48; + public static final int BAND_65 = 65; + public static final int BAND_66 = 66; + public static final int BAND_68 = 68; + public static final int BAND_70 = 70; + } + + /** + * Frenquency bands for CDMA2000. + * http://www.3gpp2.org/Public_html/Specs/C.S0057-E_v1.0_Bandclass_Specification.pdf + * @hide + * + * TODO(yinxu): Check with the nexus team about the definition of CDMA bands. + */ + public static final class CdmaBands { + public static final int BAND_0 = 1; + public static final int BAND_1 = 2; + public static final int BAND_2 = 3; + public static final int BAND_3 = 4; + public static final int BAND_4 = 5; + public static final int BAND_5 = 6; + public static final int BAND_6 = 7; + public static final int BAND_7 = 8; + public static final int BAND_8 = 9; + public static final int BAND_9 = 10; + public static final int BAND_10 = 11; + public static final int BAND_11 = 12; + public static final int BAND_12 = 13; + public static final int BAND_13 = 14; + public static final int BAND_14 = 15; + public static final int BAND_15 = 16; + public static final int BAND_16 = 17; + public static final int BAND_17 = 18; + public static final int BAND_18 = 19; + public static final int BAND_19 = 20; + public static final int BAND_20 = 21; + public static final int BAND_21 = 22; + } +} diff --git a/android/telephony/Rlog.java b/android/telephony/Rlog.java new file mode 100644 index 00000000..e0b46e10 --- /dev/null +++ b/android/telephony/Rlog.java @@ -0,0 +1,153 @@ +/* + * Copyright (C) 2012 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.telephony; + +import android.os.Build; +import android.text.TextUtils; +import android.util.Log; + +import android.util.Base64; + +import java.security.MessageDigest; +import java.security.NoSuchAlgorithmException; + + +/** + * A class to log strings to the RADIO LOG. + * + * @hide + */ +public final class Rlog { + + private static final boolean USER_BUILD = Build.IS_USER; + + private Rlog() { + } + + public static int v(String tag, String msg) { + return Log.println_native(Log.LOG_ID_RADIO, Log.VERBOSE, tag, msg); + } + + public static int v(String tag, String msg, Throwable tr) { + return Log.println_native(Log.LOG_ID_RADIO, Log.VERBOSE, tag, + msg + '\n' + Log.getStackTraceString(tr)); + } + + public static int d(String tag, String msg) { + return Log.println_native(Log.LOG_ID_RADIO, Log.DEBUG, tag, msg); + } + + public static int d(String tag, String msg, Throwable tr) { + return Log.println_native(Log.LOG_ID_RADIO, Log.DEBUG, tag, + msg + '\n' + Log.getStackTraceString(tr)); + } + + public static int i(String tag, String msg) { + return Log.println_native(Log.LOG_ID_RADIO, Log.INFO, tag, msg); + } + + public static int i(String tag, String msg, Throwable tr) { + return Log.println_native(Log.LOG_ID_RADIO, Log.INFO, tag, + msg + '\n' + Log.getStackTraceString(tr)); + } + + public static int w(String tag, String msg) { + return Log.println_native(Log.LOG_ID_RADIO, Log.WARN, tag, msg); + } + + public static int w(String tag, String msg, Throwable tr) { + return Log.println_native(Log.LOG_ID_RADIO, Log.WARN, tag, + msg + '\n' + Log.getStackTraceString(tr)); + } + + public static int w(String tag, Throwable tr) { + return Log.println_native(Log.LOG_ID_RADIO, Log.WARN, tag, Log.getStackTraceString(tr)); + } + + public static int e(String tag, String msg) { + return Log.println_native(Log.LOG_ID_RADIO, Log.ERROR, tag, msg); + } + + public static int e(String tag, String msg, Throwable tr) { + return Log.println_native(Log.LOG_ID_RADIO, Log.ERROR, tag, + msg + '\n' + Log.getStackTraceString(tr)); + } + + public static int println(int priority, String tag, String msg) { + return Log.println_native(Log.LOG_ID_RADIO, priority, tag, msg); + } + + public static boolean isLoggable(String tag, int level) { + return Log.isLoggable(tag, level); + } + + /** + * Redact personally identifiable information for production users. + * @param tag used to identify the source of a log message + * @param pii the personally identifiable information we want to apply secure hash on. + * @return If tag is loggable in verbose mode or pii is null, return the original input. + * otherwise return a secure Hash of input pii + */ + public static String pii(String tag, Object pii) { + String val = String.valueOf(pii); + if (pii == null || TextUtils.isEmpty(val) || isLoggable(tag, Log.VERBOSE)) { + return val; + } + return "[" + secureHash(val.getBytes()) + "]"; + } + + /** + * Redact personally identifiable information for production users. + * @param enablePiiLogging set when caller explicitly want to enable sensitive logging. + * @param pii the personally identifiable information we want to apply secure hash on. + * @return If enablePiiLogging is set to true or pii is null, return the original input. + * otherwise return a secure Hash of input pii + */ + public static String pii(boolean enablePiiLogging, Object pii) { + String val = String.valueOf(pii); + if (pii == null || TextUtils.isEmpty(val) || enablePiiLogging) { + return val; + } + return "[" + secureHash(val.getBytes()) + "]"; + } + + /** + * Returns a secure hash (using the SHA1 algorithm) of the provided input. + * + * @return "****" if the build type is user, otherwise the hash + * @param input the bytes for which the secure hash should be computed. + */ + private static String secureHash(byte[] input) { + // Refrain from logging user personal information in user build. + if (USER_BUILD) { + return "****"; + } + + MessageDigest messageDigest; + + try { + messageDigest = MessageDigest.getInstance("SHA-1"); + } catch (NoSuchAlgorithmException e) { + return "####"; + } + + byte[] result = messageDigest.digest(input); + return Base64.encodeToString( + result, Base64.URL_SAFE | Base64.NO_PADDING | Base64.NO_WRAP); + } +} + diff --git a/android/telephony/ServiceState.java b/android/telephony/ServiceState.java new file mode 100644 index 00000000..e448fb2a --- /dev/null +++ b/android/telephony/ServiceState.java @@ -0,0 +1,1330 @@ +/* + * Copyright (C) 2006 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.telephony; + +import android.os.Bundle; +import android.os.Parcel; +import android.os.Parcelable; +import android.telephony.Rlog; +import android.text.TextUtils; + +/** + * Contains phone state and service related information. + * + * The following phone information is included in returned ServiceState: + * + * <ul> + * <li>Service state: IN_SERVICE, OUT_OF_SERVICE, EMERGENCY_ONLY, POWER_OFF + * <li>Roaming indicator + * <li>Operator name, short name and numeric id + * <li>Network selection mode + * </ul> + */ +public class ServiceState implements Parcelable { + + static final String LOG_TAG = "PHONE"; + static final boolean DBG = false; + static final boolean VDBG = false; // STOPSHIP if true + + /** + * Normal operation condition, the phone is registered + * with an operator either in home network or in roaming. + */ + public static final int STATE_IN_SERVICE = 0; + + /** + * Phone is not registered with any operator, the phone + * can be currently searching a new operator to register to, or not + * searching to registration at all, or registration is denied, or radio + * signal is not available. + */ + public static final int STATE_OUT_OF_SERVICE = 1; + + /** + * The phone is registered and locked. Only emergency numbers are allowed. {@more} + */ + public static final int STATE_EMERGENCY_ONLY = 2; + + /** + * Radio of telephony is explicitly powered off. + */ + public static final int STATE_POWER_OFF = 3; + + /** + * RIL level registration state values from ril.h + * ((const char **)response)[0] is registration state 0-6, + * 0 - Not registered, MT is not currently searching + * a new operator to register + * 1 - Registered, home network + * 2 - Not registered, but MT is currently searching + * a new operator to register + * 3 - Registration denied + * 4 - Unknown + * 5 - Registered, roaming + * 10 - Same as 0, but indicates that emergency calls + * are enabled. + * 12 - Same as 2, but indicates that emergency calls + * are enabled. + * 13 - Same as 3, but indicates that emergency calls + * are enabled. + * 14 - Same as 4, but indicates that emergency calls + * are enabled. + * @hide + */ + public static final int RIL_REG_STATE_NOT_REG = 0; + /** @hide */ + public static final int RIL_REG_STATE_HOME = 1; + /** @hide */ + public static final int RIL_REG_STATE_SEARCHING = 2; + /** @hide */ + public static final int RIL_REG_STATE_DENIED = 3; + /** @hide */ + public static final int RIL_REG_STATE_UNKNOWN = 4; + /** @hide */ + public static final int RIL_REG_STATE_ROAMING = 5; + /** @hide */ + public static final int RIL_REG_STATE_NOT_REG_EMERGENCY_CALL_ENABLED = 10; + /** @hide */ + public static final int RIL_REG_STATE_SEARCHING_EMERGENCY_CALL_ENABLED = 12; + /** @hide */ + public static final int RIL_REG_STATE_DENIED_EMERGENCY_CALL_ENABLED = 13; + /** @hide */ + public static final int RIL_REG_STATE_UNKNOWN_EMERGENCY_CALL_ENABLED = 14; + + /** + * Available radio technologies for GSM, UMTS and CDMA. + * Duplicates the constants from hardware/radio/include/ril.h + * This should only be used by agents working with the ril. Others + * should use the equivalent TelephonyManager.NETWORK_TYPE_* + */ + /** @hide */ + public static final int RIL_RADIO_TECHNOLOGY_UNKNOWN = 0; + /** @hide */ + public static final int RIL_RADIO_TECHNOLOGY_GPRS = 1; + /** @hide */ + public static final int RIL_RADIO_TECHNOLOGY_EDGE = 2; + /** @hide */ + public static final int RIL_RADIO_TECHNOLOGY_UMTS = 3; + /** @hide */ + public static final int RIL_RADIO_TECHNOLOGY_IS95A = 4; + /** @hide */ + public static final int RIL_RADIO_TECHNOLOGY_IS95B = 5; + /** @hide */ + public static final int RIL_RADIO_TECHNOLOGY_1xRTT = 6; + /** @hide */ + public static final int RIL_RADIO_TECHNOLOGY_EVDO_0 = 7; + /** @hide */ + public static final int RIL_RADIO_TECHNOLOGY_EVDO_A = 8; + /** @hide */ + public static final int RIL_RADIO_TECHNOLOGY_HSDPA = 9; + /** @hide */ + public static final int RIL_RADIO_TECHNOLOGY_HSUPA = 10; + /** @hide */ + public static final int RIL_RADIO_TECHNOLOGY_HSPA = 11; + /** @hide */ + public static final int RIL_RADIO_TECHNOLOGY_EVDO_B = 12; + /** @hide */ + public static final int RIL_RADIO_TECHNOLOGY_EHRPD = 13; + /** @hide */ + public static final int RIL_RADIO_TECHNOLOGY_LTE = 14; + /** @hide */ + public static final int RIL_RADIO_TECHNOLOGY_HSPAP = 15; + /** + * GSM radio technology only supports voice. It does not support data. + * @hide + */ + public static final int RIL_RADIO_TECHNOLOGY_GSM = 16; + /** @hide */ + public static final int RIL_RADIO_TECHNOLOGY_TD_SCDMA = 17; + /** + * IWLAN + * @hide + */ + public static final int RIL_RADIO_TECHNOLOGY_IWLAN = 18; + + /** + * LTE_CA + * @hide + */ + public static final int RIL_RADIO_TECHNOLOGY_LTE_CA = 19; + + /** @hide */ + public static final int RIL_RADIO_CDMA_TECHNOLOGY_BITMASK = + (1 << (RIL_RADIO_TECHNOLOGY_IS95A - 1)) + | (1 << (RIL_RADIO_TECHNOLOGY_IS95B - 1)) + | (1 << (RIL_RADIO_TECHNOLOGY_1xRTT - 1)) + | (1 << (RIL_RADIO_TECHNOLOGY_EVDO_0 - 1)) + | (1 << (RIL_RADIO_TECHNOLOGY_EVDO_A - 1)) + | (1 << (RIL_RADIO_TECHNOLOGY_EVDO_B - 1)) + | (1 << (RIL_RADIO_TECHNOLOGY_EHRPD - 1)); + + /** + * Available registration states for GSM, UMTS and CDMA. + */ + /** @hide */ + public static final int REGISTRATION_STATE_NOT_REGISTERED_AND_NOT_SEARCHING = 0; + /** @hide */ + public static final int REGISTRATION_STATE_HOME_NETWORK = 1; + /** @hide */ + public static final int REGISTRATION_STATE_NOT_REGISTERED_AND_SEARCHING = 2; + /** @hide */ + public static final int REGISTRATION_STATE_REGISTRATION_DENIED = 3; + /** @hide */ + public static final int REGISTRATION_STATE_UNKNOWN = 4; + /** @hide */ + public static final int REGISTRATION_STATE_ROAMING = 5; + + private int mVoiceRegState = STATE_OUT_OF_SERVICE; + private int mDataRegState = STATE_OUT_OF_SERVICE; + + /** + * Roaming type + * HOME : in home network + * @hide + */ + public static final int ROAMING_TYPE_NOT_ROAMING = 0; + /** + * Roaming type + * UNKNOWN : in a roaming network, but we can not tell if it's domestic or international + * @hide + */ + public static final int ROAMING_TYPE_UNKNOWN = 1; + /** + * Roaming type + * DOMESTIC : in domestic roaming network + * @hide + */ + public static final int ROAMING_TYPE_DOMESTIC = 2; + /** + * Roaming type + * INTERNATIONAL : in international roaming network + * @hide + */ + public static final int ROAMING_TYPE_INTERNATIONAL = 3; + + private int mVoiceRoamingType; + private int mDataRoamingType; + private String mVoiceOperatorAlphaLong; + private String mVoiceOperatorAlphaShort; + private String mVoiceOperatorNumeric; + private String mDataOperatorAlphaLong; + private String mDataOperatorAlphaShort; + private String mDataOperatorNumeric; + private boolean mIsManualNetworkSelection; + + private boolean mIsEmergencyOnly; + + private int mRilVoiceRadioTechnology; + private int mRilDataRadioTechnology; + + private boolean mCssIndicator; + private int mNetworkId; + private int mSystemId; + private int mCdmaRoamingIndicator; + private int mCdmaDefaultRoamingIndicator; + private int mCdmaEriIconIndex; + private int mCdmaEriIconMode; + + private boolean mIsDataRoamingFromRegistration; + + private boolean mIsUsingCarrierAggregation; + + /* EARFCN stands for E-UTRA Absolute Radio Frequency Channel Number, + * Reference: 3GPP TS 36.104 5.4.3 */ + private int mLteEarfcnRsrpBoost = 0; + + /** + * get String description of roaming type + * @hide + */ + public static final String getRoamingLogString(int roamingType) { + switch (roamingType) { + case ROAMING_TYPE_NOT_ROAMING: + return "home"; + + case ROAMING_TYPE_UNKNOWN: + return "roaming"; + + case ROAMING_TYPE_DOMESTIC: + return "Domestic Roaming"; + + case ROAMING_TYPE_INTERNATIONAL: + return "International Roaming"; + + default: + return "UNKNOWN"; + } + } + + /** + * Create a new ServiceState from a intent notifier Bundle + * + * This method is used by PhoneStateIntentReceiver and maybe by + * external applications. + * + * @param m Bundle from intent notifier + * @return newly created ServiceState + * @hide + */ + public static ServiceState newFromBundle(Bundle m) { + ServiceState ret; + ret = new ServiceState(); + ret.setFromNotifierBundle(m); + return ret; + } + + /** + * Empty constructor + */ + public ServiceState() { + } + + /** + * Copy constructors + * + * @param s Source service state + */ + public ServiceState(ServiceState s) { + copyFrom(s); + } + + protected void copyFrom(ServiceState s) { + mVoiceRegState = s.mVoiceRegState; + mDataRegState = s.mDataRegState; + mVoiceRoamingType = s.mVoiceRoamingType; + mDataRoamingType = s.mDataRoamingType; + mVoiceOperatorAlphaLong = s.mVoiceOperatorAlphaLong; + mVoiceOperatorAlphaShort = s.mVoiceOperatorAlphaShort; + mVoiceOperatorNumeric = s.mVoiceOperatorNumeric; + mDataOperatorAlphaLong = s.mDataOperatorAlphaLong; + mDataOperatorAlphaShort = s.mDataOperatorAlphaShort; + mDataOperatorNumeric = s.mDataOperatorNumeric; + mIsManualNetworkSelection = s.mIsManualNetworkSelection; + mRilVoiceRadioTechnology = s.mRilVoiceRadioTechnology; + mRilDataRadioTechnology = s.mRilDataRadioTechnology; + mCssIndicator = s.mCssIndicator; + mNetworkId = s.mNetworkId; + mSystemId = s.mSystemId; + mCdmaRoamingIndicator = s.mCdmaRoamingIndicator; + mCdmaDefaultRoamingIndicator = s.mCdmaDefaultRoamingIndicator; + mCdmaEriIconIndex = s.mCdmaEriIconIndex; + mCdmaEriIconMode = s.mCdmaEriIconMode; + mIsEmergencyOnly = s.mIsEmergencyOnly; + mIsDataRoamingFromRegistration = s.mIsDataRoamingFromRegistration; + mIsUsingCarrierAggregation = s.mIsUsingCarrierAggregation; + mLteEarfcnRsrpBoost = s.mLteEarfcnRsrpBoost; + } + + /** + * Construct a ServiceState object from the given parcel. + */ + public ServiceState(Parcel in) { + mVoiceRegState = in.readInt(); + mDataRegState = in.readInt(); + mVoiceRoamingType = in.readInt(); + mDataRoamingType = in.readInt(); + mVoiceOperatorAlphaLong = in.readString(); + mVoiceOperatorAlphaShort = in.readString(); + mVoiceOperatorNumeric = in.readString(); + mDataOperatorAlphaLong = in.readString(); + mDataOperatorAlphaShort = in.readString(); + mDataOperatorNumeric = in.readString(); + mIsManualNetworkSelection = in.readInt() != 0; + mRilVoiceRadioTechnology = in.readInt(); + mRilDataRadioTechnology = in.readInt(); + mCssIndicator = (in.readInt() != 0); + mNetworkId = in.readInt(); + mSystemId = in.readInt(); + mCdmaRoamingIndicator = in.readInt(); + mCdmaDefaultRoamingIndicator = in.readInt(); + mCdmaEriIconIndex = in.readInt(); + mCdmaEriIconMode = in.readInt(); + mIsEmergencyOnly = in.readInt() != 0; + mIsDataRoamingFromRegistration = in.readInt() != 0; + mIsUsingCarrierAggregation = in.readInt() != 0; + mLteEarfcnRsrpBoost = in.readInt(); + } + + public void writeToParcel(Parcel out, int flags) { + out.writeInt(mVoiceRegState); + out.writeInt(mDataRegState); + out.writeInt(mVoiceRoamingType); + out.writeInt(mDataRoamingType); + out.writeString(mVoiceOperatorAlphaLong); + out.writeString(mVoiceOperatorAlphaShort); + out.writeString(mVoiceOperatorNumeric); + out.writeString(mDataOperatorAlphaLong); + out.writeString(mDataOperatorAlphaShort); + out.writeString(mDataOperatorNumeric); + out.writeInt(mIsManualNetworkSelection ? 1 : 0); + out.writeInt(mRilVoiceRadioTechnology); + out.writeInt(mRilDataRadioTechnology); + out.writeInt(mCssIndicator ? 1 : 0); + out.writeInt(mNetworkId); + out.writeInt(mSystemId); + out.writeInt(mCdmaRoamingIndicator); + out.writeInt(mCdmaDefaultRoamingIndicator); + out.writeInt(mCdmaEriIconIndex); + out.writeInt(mCdmaEriIconMode); + out.writeInt(mIsEmergencyOnly ? 1 : 0); + out.writeInt(mIsDataRoamingFromRegistration ? 1 : 0); + out.writeInt(mIsUsingCarrierAggregation ? 1 : 0); + out.writeInt(mLteEarfcnRsrpBoost); + } + + public int describeContents() { + return 0; + } + + public static final Parcelable.Creator<ServiceState> CREATOR = + new Parcelable.Creator<ServiceState>() { + public ServiceState createFromParcel(Parcel in) { + return new ServiceState(in); + } + + public ServiceState[] newArray(int size) { + return new ServiceState[size]; + } + }; + + /** + * Get current voice service state + */ + public int getState() { + return getVoiceRegState(); + } + + /** + * Get current voice service state + * + * @see #STATE_IN_SERVICE + * @see #STATE_OUT_OF_SERVICE + * @see #STATE_EMERGENCY_ONLY + * @see #STATE_POWER_OFF + * + * @hide + */ + public int getVoiceRegState() { + return mVoiceRegState; + } + + /** + * Get current data service state + * + * @see #STATE_IN_SERVICE + * @see #STATE_OUT_OF_SERVICE + * @see #STATE_EMERGENCY_ONLY + * @see #STATE_POWER_OFF + * + * @hide + */ + public int getDataRegState() { + return mDataRegState; + } + + /** + * Get current roaming indicator of phone + * (note: not just decoding from TS 27.007 7.2) + * + * @return true if TS 27.007 7.2 roaming is true + * and ONS is different from SPN + */ + public boolean getRoaming() { + return getVoiceRoaming() || getDataRoaming(); + } + + /** + * Get current voice network roaming status + * @return roaming status + * @hide + */ + public boolean getVoiceRoaming() { + return mVoiceRoamingType != ROAMING_TYPE_NOT_ROAMING; + } + + /** + * Get current voice network roaming type + * @return roaming type + * @hide + */ + public int getVoiceRoamingType() { + return mVoiceRoamingType; + } + + /** + * Get current data network roaming type + * @return roaming type + * @hide + */ + public boolean getDataRoaming() { + return mDataRoamingType != ROAMING_TYPE_NOT_ROAMING; + } + + /** + * Set whether data network registration state is roaming + * + * This should only be set to the roaming value received + * once the data registration phase has completed. + * @hide + */ + public void setDataRoamingFromRegistration(boolean dataRoaming) { + mIsDataRoamingFromRegistration = dataRoaming; + } + + /** + * Get whether data network registration state is roaming + * @return true if registration indicates roaming, false otherwise + * @hide + */ + public boolean getDataRoamingFromRegistration() { + return mIsDataRoamingFromRegistration; + } + + /** + * Get current data network roaming type + * @return roaming type + * @hide + */ + public int getDataRoamingType() { + return mDataRoamingType; + } + + /** + * @hide + */ + public boolean isEmergencyOnly() { + return mIsEmergencyOnly; + } + + /** + * @hide + */ + public int getCdmaRoamingIndicator(){ + return this.mCdmaRoamingIndicator; + } + + /** + * @hide + */ + public int getCdmaDefaultRoamingIndicator(){ + return this.mCdmaDefaultRoamingIndicator; + } + + /** + * @hide + */ + public int getCdmaEriIconIndex() { + return this.mCdmaEriIconIndex; + } + + /** + * @hide + */ + public int getCdmaEriIconMode() { + return this.mCdmaEriIconMode; + } + + /** + * Get current registered operator name in long alphanumeric format. + * + * In GSM/UMTS, long format can be up to 16 characters long. + * In CDMA, returns the ERI text, if set. Otherwise, returns the ONS. + * + * @return long name of operator, null if unregistered or unknown + */ + public String getOperatorAlphaLong() { + return mVoiceOperatorAlphaLong; + } + + /** + * Get current registered voice network operator name in long alphanumeric format. + * @return long name of operator + * @hide + */ + public String getVoiceOperatorAlphaLong() { + return mVoiceOperatorAlphaLong; + } + + /** + * Get current registered data network operator name in long alphanumeric format. + * @return long name of voice operator + * @hide + */ + public String getDataOperatorAlphaLong() { + return mDataOperatorAlphaLong; + } + + /** + * Get current registered operator name in short alphanumeric format. + * + * In GSM/UMTS, short format can be up to 8 characters long. + * + * @return short name of operator, null if unregistered or unknown + */ + public String getOperatorAlphaShort() { + return mVoiceOperatorAlphaShort; + } + + /** + * Get current registered voice network operator name in short alphanumeric format. + * @return short name of operator, null if unregistered or unknown + * @hide + */ + public String getVoiceOperatorAlphaShort() { + return mVoiceOperatorAlphaShort; + } + + /** + * Get current registered data network operator name in short alphanumeric format. + * @return short name of operator, null if unregistered or unknown + * @hide + */ + public String getDataOperatorAlphaShort() { + return mDataOperatorAlphaShort; + } + + /** + * Get current registered operator name in long alphanumeric format if + * available or short otherwise. + * + * @see #getOperatorAlphaLong + * @see #getOperatorAlphaShort + * + * @return name of operator, null if unregistered or unknown + * @hide + */ + public String getOperatorAlpha() { + if (TextUtils.isEmpty(mVoiceOperatorAlphaLong)) { + return mVoiceOperatorAlphaShort; + } + + return mVoiceOperatorAlphaLong; + } + + /** + * Get current registered operator numeric id. + * + * In GSM/UMTS, numeric format is 3 digit country code plus 2 or 3 digit + * network code. + * + * @return numeric format of operator, null if unregistered or unknown + */ + /* + * The country code can be decoded using + * {@link com.android.internal.telephony.MccTable#countryCodeForMcc(int)}. + */ + public String getOperatorNumeric() { + return mVoiceOperatorNumeric; + } + + /** + * Get current registered voice network operator numeric id. + * @return numeric format of operator, null if unregistered or unknown + * @hide + */ + public String getVoiceOperatorNumeric() { + return mVoiceOperatorNumeric; + } + + /** + * Get current registered data network operator numeric id. + * @return numeric format of operator, null if unregistered or unknown + * @hide + */ + public String getDataOperatorNumeric() { + return mDataOperatorNumeric; + } + + /** + * Get current network selection mode. + * + * @return true if manual mode, false if automatic mode + */ + public boolean getIsManualSelection() { + return mIsManualNetworkSelection; + } + + @Override + public int hashCode() { + return ((mVoiceRegState * 31) + + (mDataRegState * 37) + + mVoiceRoamingType + + mDataRoamingType + + (mIsManualNetworkSelection ? 1 : 0) + + ((null == mVoiceOperatorAlphaLong) ? 0 : mVoiceOperatorAlphaLong.hashCode()) + + ((null == mVoiceOperatorAlphaShort) ? 0 : mVoiceOperatorAlphaShort.hashCode()) + + ((null == mVoiceOperatorNumeric) ? 0 : mVoiceOperatorNumeric.hashCode()) + + ((null == mDataOperatorAlphaLong) ? 0 : mDataOperatorAlphaLong.hashCode()) + + ((null == mDataOperatorAlphaShort) ? 0 : mDataOperatorAlphaShort.hashCode()) + + ((null == mDataOperatorNumeric) ? 0 : mDataOperatorNumeric.hashCode()) + + mCdmaRoamingIndicator + + mCdmaDefaultRoamingIndicator + + (mIsEmergencyOnly ? 1 : 0) + + (mIsDataRoamingFromRegistration ? 1 : 0)); + } + + @Override + public boolean equals (Object o) { + ServiceState s; + + try { + s = (ServiceState) o; + } catch (ClassCastException ex) { + return false; + } + + if (o == null) { + return false; + } + + return (mVoiceRegState == s.mVoiceRegState + && mDataRegState == s.mDataRegState + && mIsManualNetworkSelection == s.mIsManualNetworkSelection + && mVoiceRoamingType == s.mVoiceRoamingType + && mDataRoamingType == s.mDataRoamingType + && equalsHandlesNulls(mVoiceOperatorAlphaLong, s.mVoiceOperatorAlphaLong) + && equalsHandlesNulls(mVoiceOperatorAlphaShort, s.mVoiceOperatorAlphaShort) + && equalsHandlesNulls(mVoiceOperatorNumeric, s.mVoiceOperatorNumeric) + && equalsHandlesNulls(mDataOperatorAlphaLong, s.mDataOperatorAlphaLong) + && equalsHandlesNulls(mDataOperatorAlphaShort, s.mDataOperatorAlphaShort) + && equalsHandlesNulls(mDataOperatorNumeric, s.mDataOperatorNumeric) + && equalsHandlesNulls(mRilVoiceRadioTechnology, s.mRilVoiceRadioTechnology) + && equalsHandlesNulls(mRilDataRadioTechnology, s.mRilDataRadioTechnology) + && equalsHandlesNulls(mCssIndicator, s.mCssIndicator) + && equalsHandlesNulls(mNetworkId, s.mNetworkId) + && equalsHandlesNulls(mSystemId, s.mSystemId) + && equalsHandlesNulls(mCdmaRoamingIndicator, s.mCdmaRoamingIndicator) + && equalsHandlesNulls(mCdmaDefaultRoamingIndicator, + s.mCdmaDefaultRoamingIndicator) + && mIsEmergencyOnly == s.mIsEmergencyOnly + && mIsDataRoamingFromRegistration == s.mIsDataRoamingFromRegistration + && mIsUsingCarrierAggregation == s.mIsUsingCarrierAggregation); + } + + /** + * Convert radio technology to String + * + * @param radioTechnology + * @return String representation of the RAT + * + * @hide + */ + public static String rilRadioTechnologyToString(int rt) { + String rtString; + + switch(rt) { + case RIL_RADIO_TECHNOLOGY_UNKNOWN: + rtString = "Unknown"; + break; + case RIL_RADIO_TECHNOLOGY_GPRS: + rtString = "GPRS"; + break; + case RIL_RADIO_TECHNOLOGY_EDGE: + rtString = "EDGE"; + break; + case RIL_RADIO_TECHNOLOGY_UMTS: + rtString = "UMTS"; + break; + case RIL_RADIO_TECHNOLOGY_IS95A: + rtString = "CDMA-IS95A"; + break; + case RIL_RADIO_TECHNOLOGY_IS95B: + rtString = "CDMA-IS95B"; + break; + case RIL_RADIO_TECHNOLOGY_1xRTT: + rtString = "1xRTT"; + break; + case RIL_RADIO_TECHNOLOGY_EVDO_0: + rtString = "EvDo-rev.0"; + break; + case RIL_RADIO_TECHNOLOGY_EVDO_A: + rtString = "EvDo-rev.A"; + break; + case RIL_RADIO_TECHNOLOGY_HSDPA: + rtString = "HSDPA"; + break; + case RIL_RADIO_TECHNOLOGY_HSUPA: + rtString = "HSUPA"; + break; + case RIL_RADIO_TECHNOLOGY_HSPA: + rtString = "HSPA"; + break; + case RIL_RADIO_TECHNOLOGY_EVDO_B: + rtString = "EvDo-rev.B"; + break; + case RIL_RADIO_TECHNOLOGY_EHRPD: + rtString = "eHRPD"; + break; + case RIL_RADIO_TECHNOLOGY_LTE: + rtString = "LTE"; + break; + case RIL_RADIO_TECHNOLOGY_HSPAP: + rtString = "HSPAP"; + break; + case RIL_RADIO_TECHNOLOGY_GSM: + rtString = "GSM"; + break; + case RIL_RADIO_TECHNOLOGY_IWLAN: + rtString = "IWLAN"; + break; + case RIL_RADIO_TECHNOLOGY_TD_SCDMA: + rtString = "TD-SCDMA"; + break; + case RIL_RADIO_TECHNOLOGY_LTE_CA: + rtString = "LTE_CA"; + break; + default: + rtString = "Unexpected"; + Rlog.w(LOG_TAG, "Unexpected radioTechnology=" + rt); + break; + } + return rtString; + } + + /** + * Convert RIL Service State to String + * + * @param serviceState + * @return String representation of the ServiceState + * + * @hide + */ + public static String rilServiceStateToString(int serviceState) { + switch(serviceState) { + case STATE_IN_SERVICE: + return "IN_SERVICE"; + case STATE_OUT_OF_SERVICE: + return "OUT_OF_SERVICE"; + case STATE_EMERGENCY_ONLY: + return "EMERGENCY_ONLY"; + case STATE_POWER_OFF: + return "POWER_OFF"; + default: + return "UNKNOWN"; + } + } + + @Override + public String toString() { + return new StringBuilder().append("{mVoiceRegState=").append(mVoiceRegState) + .append("(" + rilServiceStateToString(mVoiceRegState) + ")") + .append(", mDataRegState=").append(mDataRegState) + .append("(" + rilServiceStateToString(mDataRegState) + ")") + .append(", mVoiceRoamingType=").append(getRoamingLogString(mVoiceRoamingType)) + .append(", mDataRoamingType=").append(getRoamingLogString(mDataRoamingType)) + .append(", mVoiceOperatorAlphaLong=").append(mVoiceOperatorAlphaLong) + .append(", mVoiceOperatorAlphaShort=").append(mVoiceOperatorAlphaShort) + .append(", mDataOperatorAlphaLong=").append(mDataOperatorAlphaLong) + .append(", mDataOperatorAlphaShort=").append(mDataOperatorAlphaShort) + .append(", isManualNetworkSelection=").append(mIsManualNetworkSelection) + .append(mIsManualNetworkSelection ? "(manual)" : "(automatic)") + .append(", mRilVoiceRadioTechnology=").append(mRilVoiceRadioTechnology) + .append("(" + rilRadioTechnologyToString(mRilVoiceRadioTechnology) + ")") + .append(", mRilDataRadioTechnology=").append(mRilDataRadioTechnology) + .append("(" + rilRadioTechnologyToString(mRilDataRadioTechnology) + ")") + .append(", mCssIndicator=").append(mCssIndicator ? "supported" : "unsupported") + .append(", mNetworkId=").append(mNetworkId) + .append(", mSystemId=").append(mSystemId) + .append(", mCdmaRoamingIndicator=").append(mCdmaRoamingIndicator) + .append(", mCdmaDefaultRoamingIndicator=").append(mCdmaDefaultRoamingIndicator) + .append(", mIsEmergencyOnly=").append(mIsEmergencyOnly) + .append(", mIsDataRoamingFromRegistration=").append(mIsDataRoamingFromRegistration) + .append(", mIsUsingCarrierAggregation=").append(mIsUsingCarrierAggregation) + .append(", mLteEarfcnRsrpBoost=").append(mLteEarfcnRsrpBoost) + .append("}").toString(); + } + + private void setNullState(int state) { + if (DBG) Rlog.d(LOG_TAG, "[ServiceState] setNullState=" + state); + mVoiceRegState = state; + mDataRegState = state; + mVoiceRoamingType = ROAMING_TYPE_NOT_ROAMING; + mDataRoamingType = ROAMING_TYPE_NOT_ROAMING; + mVoiceOperatorAlphaLong = null; + mVoiceOperatorAlphaShort = null; + mVoiceOperatorNumeric = null; + mDataOperatorAlphaLong = null; + mDataOperatorAlphaShort = null; + mDataOperatorNumeric = null; + mIsManualNetworkSelection = false; + mRilVoiceRadioTechnology = 0; + mRilDataRadioTechnology = 0; + mCssIndicator = false; + mNetworkId = -1; + mSystemId = -1; + mCdmaRoamingIndicator = -1; + mCdmaDefaultRoamingIndicator = -1; + mCdmaEriIconIndex = -1; + mCdmaEriIconMode = -1; + mIsEmergencyOnly = false; + mIsDataRoamingFromRegistration = false; + mIsUsingCarrierAggregation = false; + mLteEarfcnRsrpBoost = 0; + } + + public void setStateOutOfService() { + setNullState(STATE_OUT_OF_SERVICE); + } + + public void setStateOff() { + setNullState(STATE_POWER_OFF); + } + + public void setState(int state) { + setVoiceRegState(state); + if (DBG) Rlog.e(LOG_TAG, "[ServiceState] setState deprecated use setVoiceRegState()"); + } + + /** @hide */ + public void setVoiceRegState(int state) { + mVoiceRegState = state; + if (DBG) Rlog.d(LOG_TAG, "[ServiceState] setVoiceRegState=" + mVoiceRegState); + } + + /** @hide */ + public void setDataRegState(int state) { + mDataRegState = state; + if (VDBG) Rlog.d(LOG_TAG, "[ServiceState] setDataRegState=" + mDataRegState); + } + + public void setRoaming(boolean roaming) { + mVoiceRoamingType = (roaming ? ROAMING_TYPE_UNKNOWN : ROAMING_TYPE_NOT_ROAMING); + mDataRoamingType = mVoiceRoamingType; + } + + /** @hide */ + public void setVoiceRoaming(boolean roaming) { + mVoiceRoamingType = (roaming ? ROAMING_TYPE_UNKNOWN : ROAMING_TYPE_NOT_ROAMING); + } + + /** @hide */ + public void setVoiceRoamingType(int type) { + mVoiceRoamingType = type; + } + + /** @hide */ + public void setDataRoaming(boolean dataRoaming) { + mDataRoamingType = (dataRoaming ? ROAMING_TYPE_UNKNOWN : ROAMING_TYPE_NOT_ROAMING); + } + + /** @hide */ + public void setDataRoamingType(int type) { + mDataRoamingType = type; + } + + /** + * @hide + */ + public void setEmergencyOnly(boolean emergencyOnly) { + mIsEmergencyOnly = emergencyOnly; + } + + /** + * @hide + */ + public void setCdmaRoamingIndicator(int roaming) { + this.mCdmaRoamingIndicator = roaming; + } + + /** + * @hide + */ + public void setCdmaDefaultRoamingIndicator (int roaming) { + this.mCdmaDefaultRoamingIndicator = roaming; + } + + /** + * @hide + */ + public void setCdmaEriIconIndex(int index) { + this.mCdmaEriIconIndex = index; + } + + /** + * @hide + */ + public void setCdmaEriIconMode(int mode) { + this.mCdmaEriIconMode = mode; + } + + public void setOperatorName(String longName, String shortName, String numeric) { + mVoiceOperatorAlphaLong = longName; + mVoiceOperatorAlphaShort = shortName; + mVoiceOperatorNumeric = numeric; + mDataOperatorAlphaLong = longName; + mDataOperatorAlphaShort = shortName; + mDataOperatorNumeric = numeric; + } + + /** @hide */ + public void setVoiceOperatorName(String longName, String shortName, String numeric) { + mVoiceOperatorAlphaLong = longName; + mVoiceOperatorAlphaShort = shortName; + mVoiceOperatorNumeric = numeric; + } + + /** @hide */ + public void setDataOperatorName(String longName, String shortName, String numeric) { + mDataOperatorAlphaLong = longName; + mDataOperatorAlphaShort = shortName; + mDataOperatorNumeric = numeric; + } + + /** + * In CDMA, mOperatorAlphaLong can be set from the ERI text. + * This is done from the GsmCdmaPhone and not from the ServiceStateTracker. + * + * @hide + */ + public void setOperatorAlphaLong(String longName) { + mVoiceOperatorAlphaLong = longName; + mDataOperatorAlphaLong = longName; + } + + /** @hide */ + public void setVoiceOperatorAlphaLong(String longName) { + mVoiceOperatorAlphaLong = longName; + } + + /** @hide */ + public void setDataOperatorAlphaLong(String longName) { + mDataOperatorAlphaLong = longName; + } + + public void setIsManualSelection(boolean isManual) { + mIsManualNetworkSelection = isManual; + } + + /** + * Test whether two objects hold the same data values or both are null. + * + * @param a first obj + * @param b second obj + * @return true if two objects equal or both are null + */ + private static boolean equalsHandlesNulls (Object a, Object b) { + return (a == null) ? (b == null) : a.equals (b); + } + + /** + * Set ServiceState based on intent notifier map. + * + * @param m intent notifier map + * @hide + */ + private void setFromNotifierBundle(Bundle m) { + mVoiceRegState = m.getInt("voiceRegState"); + mDataRegState = m.getInt("dataRegState"); + mVoiceRoamingType = m.getInt("voiceRoamingType"); + mDataRoamingType = m.getInt("dataRoamingType"); + mVoiceOperatorAlphaLong = m.getString("operator-alpha-long"); + mVoiceOperatorAlphaShort = m.getString("operator-alpha-short"); + mVoiceOperatorNumeric = m.getString("operator-numeric"); + mDataOperatorAlphaLong = m.getString("data-operator-alpha-long"); + mDataOperatorAlphaShort = m.getString("data-operator-alpha-short"); + mDataOperatorNumeric = m.getString("data-operator-numeric"); + mIsManualNetworkSelection = m.getBoolean("manual"); + mRilVoiceRadioTechnology = m.getInt("radioTechnology"); + mRilDataRadioTechnology = m.getInt("dataRadioTechnology"); + mCssIndicator = m.getBoolean("cssIndicator"); + mNetworkId = m.getInt("networkId"); + mSystemId = m.getInt("systemId"); + mCdmaRoamingIndicator = m.getInt("cdmaRoamingIndicator"); + mCdmaDefaultRoamingIndicator = m.getInt("cdmaDefaultRoamingIndicator"); + mIsEmergencyOnly = m.getBoolean("emergencyOnly"); + mIsDataRoamingFromRegistration = m.getBoolean("isDataRoamingFromRegistration"); + mIsUsingCarrierAggregation = m.getBoolean("isUsingCarrierAggregation"); + mLteEarfcnRsrpBoost = m.getInt("LteEarfcnRsrpBoost"); + } + + /** + * Set intent notifier Bundle based on service state. + * + * @param m intent notifier Bundle + * @hide + */ + public void fillInNotifierBundle(Bundle m) { + m.putInt("voiceRegState", mVoiceRegState); + m.putInt("dataRegState", mDataRegState); + m.putInt("voiceRoamingType", mVoiceRoamingType); + m.putInt("dataRoamingType", mDataRoamingType); + m.putString("operator-alpha-long", mVoiceOperatorAlphaLong); + m.putString("operator-alpha-short", mVoiceOperatorAlphaShort); + m.putString("operator-numeric", mVoiceOperatorNumeric); + m.putString("data-operator-alpha-long", mDataOperatorAlphaLong); + m.putString("data-operator-alpha-short", mDataOperatorAlphaShort); + m.putString("data-operator-numeric", mDataOperatorNumeric); + m.putBoolean("manual", mIsManualNetworkSelection); + m.putInt("radioTechnology", mRilVoiceRadioTechnology); + m.putInt("dataRadioTechnology", mRilDataRadioTechnology); + m.putBoolean("cssIndicator", mCssIndicator); + m.putInt("networkId", mNetworkId); + m.putInt("systemId", mSystemId); + m.putInt("cdmaRoamingIndicator", mCdmaRoamingIndicator); + m.putInt("cdmaDefaultRoamingIndicator", mCdmaDefaultRoamingIndicator); + m.putBoolean("emergencyOnly", mIsEmergencyOnly); + m.putBoolean("isDataRoamingFromRegistration", mIsDataRoamingFromRegistration); + m.putBoolean("isUsingCarrierAggregation", mIsUsingCarrierAggregation); + m.putInt("LteEarfcnRsrpBoost", mLteEarfcnRsrpBoost); + } + + /** @hide */ + public void setRilVoiceRadioTechnology(int rt) { + if (rt == RIL_RADIO_TECHNOLOGY_LTE_CA) { + rt = RIL_RADIO_TECHNOLOGY_LTE; + } + + this.mRilVoiceRadioTechnology = rt; + } + + /** @hide */ + public void setRilDataRadioTechnology(int rt) { + if (rt == RIL_RADIO_TECHNOLOGY_LTE_CA) { + rt = RIL_RADIO_TECHNOLOGY_LTE; + this.mIsUsingCarrierAggregation = true; + } else { + this.mIsUsingCarrierAggregation = false; + } + this.mRilDataRadioTechnology = rt; + if (VDBG) Rlog.d(LOG_TAG, "[ServiceState] setRilDataRadioTechnology=" + + mRilDataRadioTechnology); + } + + /** @hide */ + public boolean isUsingCarrierAggregation() { + return mIsUsingCarrierAggregation; + } + + /** @hide */ + public void setIsUsingCarrierAggregation(boolean ca) { + mIsUsingCarrierAggregation = ca; + } + + /** @hide */ + public int getLteEarfcnRsrpBoost() { + return mLteEarfcnRsrpBoost; + } + + /** @hide */ + public void setLteEarfcnRsrpBoost(int LteEarfcnRsrpBoost) { + mLteEarfcnRsrpBoost = LteEarfcnRsrpBoost; + } + + /** @hide */ + public void setCssIndicator(int css) { + this.mCssIndicator = (css != 0); + } + + /** @hide */ + public void setSystemAndNetworkId(int systemId, int networkId) { + this.mSystemId = systemId; + this.mNetworkId = networkId; + } + + /** @hide */ + public int getRilVoiceRadioTechnology() { + return this.mRilVoiceRadioTechnology; + } + /** @hide */ + public int getRilDataRadioTechnology() { + return this.mRilDataRadioTechnology; + } + /** + * @hide + * @Deprecated to be removed Q3 2013 use {@link #getRilDataRadioTechnology} or + * {@link #getRilVoiceRadioTechnology} + */ + public int getRadioTechnology() { + Rlog.e(LOG_TAG, "ServiceState.getRadioTechnology() DEPRECATED will be removed *******"); + return getRilDataRadioTechnology(); + } + + private int rilRadioTechnologyToNetworkType(int rt) { + switch(rt) { + case ServiceState.RIL_RADIO_TECHNOLOGY_GPRS: + return TelephonyManager.NETWORK_TYPE_GPRS; + case ServiceState.RIL_RADIO_TECHNOLOGY_EDGE: + return TelephonyManager.NETWORK_TYPE_EDGE; + case ServiceState.RIL_RADIO_TECHNOLOGY_UMTS: + return TelephonyManager.NETWORK_TYPE_UMTS; + case ServiceState.RIL_RADIO_TECHNOLOGY_HSDPA: + return TelephonyManager.NETWORK_TYPE_HSDPA; + case ServiceState.RIL_RADIO_TECHNOLOGY_HSUPA: + return TelephonyManager.NETWORK_TYPE_HSUPA; + case ServiceState.RIL_RADIO_TECHNOLOGY_HSPA: + return TelephonyManager.NETWORK_TYPE_HSPA; + case ServiceState.RIL_RADIO_TECHNOLOGY_IS95A: + case ServiceState.RIL_RADIO_TECHNOLOGY_IS95B: + return TelephonyManager.NETWORK_TYPE_CDMA; + case ServiceState.RIL_RADIO_TECHNOLOGY_1xRTT: + return TelephonyManager.NETWORK_TYPE_1xRTT; + case ServiceState.RIL_RADIO_TECHNOLOGY_EVDO_0: + return TelephonyManager.NETWORK_TYPE_EVDO_0; + case ServiceState.RIL_RADIO_TECHNOLOGY_EVDO_A: + return TelephonyManager.NETWORK_TYPE_EVDO_A; + case ServiceState.RIL_RADIO_TECHNOLOGY_EVDO_B: + return TelephonyManager.NETWORK_TYPE_EVDO_B; + case ServiceState.RIL_RADIO_TECHNOLOGY_EHRPD: + return TelephonyManager.NETWORK_TYPE_EHRPD; + case ServiceState.RIL_RADIO_TECHNOLOGY_LTE: + return TelephonyManager.NETWORK_TYPE_LTE; + case ServiceState.RIL_RADIO_TECHNOLOGY_HSPAP: + return TelephonyManager.NETWORK_TYPE_HSPAP; + case ServiceState.RIL_RADIO_TECHNOLOGY_GSM: + return TelephonyManager.NETWORK_TYPE_GSM; + case ServiceState.RIL_RADIO_TECHNOLOGY_TD_SCDMA: + return TelephonyManager.NETWORK_TYPE_TD_SCDMA; + case ServiceState.RIL_RADIO_TECHNOLOGY_IWLAN: + return TelephonyManager.NETWORK_TYPE_IWLAN; + case ServiceState.RIL_RADIO_TECHNOLOGY_LTE_CA: + return TelephonyManager.NETWORK_TYPE_LTE_CA; + default: + return TelephonyManager.NETWORK_TYPE_UNKNOWN; + } + } + + /** + * @Deprecated to be removed Q3 2013 use {@link #getVoiceNetworkType} + * @hide + */ + public int getNetworkType() { + Rlog.e(LOG_TAG, "ServiceState.getNetworkType() DEPRECATED will be removed *******"); + return rilRadioTechnologyToNetworkType(mRilVoiceRadioTechnology); + } + + /** @hide */ + public int getDataNetworkType() { + return rilRadioTechnologyToNetworkType(mRilDataRadioTechnology); + } + + /** @hide */ + public int getVoiceNetworkType() { + return rilRadioTechnologyToNetworkType(mRilVoiceRadioTechnology); + } + + /** @hide */ + public int getCssIndicator() { + return this.mCssIndicator ? 1 : 0; + } + + /** @hide */ + public int getNetworkId() { + return this.mNetworkId; + } + + /** @hide */ + public int getSystemId() { + return this.mSystemId; + } + + /** @hide */ + public static boolean isGsm(int radioTechnology) { + return radioTechnology == RIL_RADIO_TECHNOLOGY_GPRS + || radioTechnology == RIL_RADIO_TECHNOLOGY_EDGE + || radioTechnology == RIL_RADIO_TECHNOLOGY_UMTS + || radioTechnology == RIL_RADIO_TECHNOLOGY_HSDPA + || radioTechnology == RIL_RADIO_TECHNOLOGY_HSUPA + || radioTechnology == RIL_RADIO_TECHNOLOGY_HSPA + || radioTechnology == RIL_RADIO_TECHNOLOGY_LTE + || radioTechnology == RIL_RADIO_TECHNOLOGY_HSPAP + || radioTechnology == RIL_RADIO_TECHNOLOGY_GSM + || radioTechnology == RIL_RADIO_TECHNOLOGY_TD_SCDMA + || radioTechnology == RIL_RADIO_TECHNOLOGY_IWLAN + || radioTechnology == RIL_RADIO_TECHNOLOGY_LTE_CA; + + } + + /** @hide */ + public static boolean isCdma(int radioTechnology) { + return radioTechnology == RIL_RADIO_TECHNOLOGY_IS95A + || radioTechnology == RIL_RADIO_TECHNOLOGY_IS95B + || radioTechnology == RIL_RADIO_TECHNOLOGY_1xRTT + || radioTechnology == RIL_RADIO_TECHNOLOGY_EVDO_0 + || radioTechnology == RIL_RADIO_TECHNOLOGY_EVDO_A + || radioTechnology == RIL_RADIO_TECHNOLOGY_EVDO_B + || radioTechnology == RIL_RADIO_TECHNOLOGY_EHRPD; + } + + /** @hide */ + public static boolean isLte(int radioTechnology) { + return radioTechnology == RIL_RADIO_TECHNOLOGY_LTE || + radioTechnology == RIL_RADIO_TECHNOLOGY_LTE_CA; + } + + /** @hide */ + public static boolean bearerBitmapHasCdma(int radioTechnologyBitmap) { + return (RIL_RADIO_CDMA_TECHNOLOGY_BITMASK & radioTechnologyBitmap) != 0; + } + + /** @hide */ + public static boolean bitmaskHasTech(int bearerBitmask, int radioTech) { + if (bearerBitmask == 0) { + return true; + } else if (radioTech >= 1) { + return ((bearerBitmask & (1 << (radioTech - 1))) != 0); + } + return false; + } + + /** @hide */ + public static int getBitmaskForTech(int radioTech) { + if (radioTech >= 1) { + return (1 << (radioTech - 1)); + } + return 0; + } + + /** @hide */ + public static int getBitmaskFromString(String bearerList) { + String[] bearers = bearerList.split("\\|"); + int bearerBitmask = 0; + for (String bearer : bearers) { + int bearerInt = 0; + try { + bearerInt = Integer.parseInt(bearer.trim()); + } catch (NumberFormatException nfe) { + return 0; + } + + if (bearerInt == 0) { + return 0; + } + + bearerBitmask |= getBitmaskForTech(bearerInt); + } + return bearerBitmask; + } + + /** + * Returns a merged ServiceState consisting of the base SS with voice settings from the + * voice SS. The voice SS is only used if it is IN_SERVICE (otherwise the base SS is returned). + * @hide + * */ + public static ServiceState mergeServiceStates(ServiceState baseSs, ServiceState voiceSs) { + if (voiceSs.mVoiceRegState != STATE_IN_SERVICE) { + return baseSs; + } + + ServiceState newSs = new ServiceState(baseSs); + + // voice overrides + newSs.mVoiceRegState = voiceSs.mVoiceRegState; + newSs.mIsEmergencyOnly = false; // only get here if voice is IN_SERVICE + + return newSs; + } +} diff --git a/android/telephony/SignalStrength.java b/android/telephony/SignalStrength.java new file mode 100644 index 00000000..9e023993 --- /dev/null +++ b/android/telephony/SignalStrength.java @@ -0,0 +1,1106 @@ +/* + * Copyright (C) 2012 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.telephony; + +import android.os.Bundle; +import android.os.Parcel; +import android.os.Parcelable; +import android.telephony.Rlog; +import android.util.Log; +import android.content.res.Resources; + +/** + * Contains phone signal strength related information. + */ +public class SignalStrength implements Parcelable { + + private static final String LOG_TAG = "SignalStrength"; + private static final boolean DBG = false; + + /** @hide */ + public static final int SIGNAL_STRENGTH_NONE_OR_UNKNOWN = 0; + /** @hide */ + public static final int SIGNAL_STRENGTH_POOR = 1; + /** @hide */ + public static final int SIGNAL_STRENGTH_MODERATE = 2; + /** @hide */ + public static final int SIGNAL_STRENGTH_GOOD = 3; + /** @hide */ + public static final int SIGNAL_STRENGTH_GREAT = 4; + /** @hide */ + public static final int NUM_SIGNAL_STRENGTH_BINS = 5; + /** @hide */ + public static final String[] SIGNAL_STRENGTH_NAMES = { + "none", "poor", "moderate", "good", "great" + }; + + /** @hide */ + //Use int max, as -1 is a valid value in signal strength + public static final int INVALID = 0x7FFFFFFF; + + private int mGsmSignalStrength; // Valid values are (0-31, 99) as defined in TS 27.007 8.5 + private int mGsmBitErrorRate; // bit error rate (0-7, 99) as defined in TS 27.007 8.5 + private int mCdmaDbm; // This value is the RSSI value + private int mCdmaEcio; // This value is the Ec/Io + private int mEvdoDbm; // This value is the EVDO RSSI value + private int mEvdoEcio; // This value is the EVDO Ec/Io + private int mEvdoSnr; // Valid values are 0-8. 8 is the highest signal to noise ratio + private int mLteSignalStrength; + private int mLteRsrp; + private int mLteRsrq; + private int mLteRssnr; + private int mLteCqi; + private int mLteRsrpBoost; // offset to be reduced from the rsrp threshold while calculating + // signal strength level + private int mTdScdmaRscp; + + private boolean isGsm; // This value is set by the ServiceStateTracker onSignalStrengthResult + + /** + * Create a new SignalStrength from a intent notifier Bundle + * + * This method is used by PhoneStateIntentReceiver and maybe by + * external applications. + * + * @param m Bundle from intent notifier + * @return newly created SignalStrength + * + * @hide + */ + public static SignalStrength newFromBundle(Bundle m) { + SignalStrength ret; + ret = new SignalStrength(); + ret.setFromNotifierBundle(m); + return ret; + } + + /** + * Empty constructor + * + * @hide + */ + public SignalStrength() { + mGsmSignalStrength = 99; + mGsmBitErrorRate = -1; + mCdmaDbm = -1; + mCdmaEcio = -1; + mEvdoDbm = -1; + mEvdoEcio = -1; + mEvdoSnr = -1; + mLteSignalStrength = 99; + mLteRsrp = INVALID; + mLteRsrq = INVALID; + mLteRssnr = INVALID; + mLteCqi = INVALID; + mLteRsrpBoost = 0; + mTdScdmaRscp = INVALID; + isGsm = true; + } + + /** + * This constructor is used to create SignalStrength with default + * values and set the isGsmFlag with the value passed in the input + * + * @param gsmFlag true if Gsm Phone,false if Cdma phone + * @return newly created SignalStrength + * @hide + */ + public SignalStrength(boolean gsmFlag) { + mGsmSignalStrength = 99; + mGsmBitErrorRate = -1; + mCdmaDbm = -1; + mCdmaEcio = -1; + mEvdoDbm = -1; + mEvdoEcio = -1; + mEvdoSnr = -1; + mLteSignalStrength = 99; + mLteRsrp = INVALID; + mLteRsrq = INVALID; + mLteRssnr = INVALID; + mLteCqi = INVALID; + mLteRsrpBoost = 0; + mTdScdmaRscp = INVALID; + isGsm = gsmFlag; + } + + /** + * Constructor + * + * @hide + */ + public SignalStrength(int gsmSignalStrength, int gsmBitErrorRate, + int cdmaDbm, int cdmaEcio, + int evdoDbm, int evdoEcio, int evdoSnr, + int lteSignalStrength, int lteRsrp, int lteRsrq, int lteRssnr, int lteCqi, + int lteRsrpBoost, int tdScdmaRscp, boolean gsmFlag) { + initialize(gsmSignalStrength, gsmBitErrorRate, cdmaDbm, cdmaEcio, + evdoDbm, evdoEcio, evdoSnr, lteSignalStrength, lteRsrp, + lteRsrq, lteRssnr, lteCqi, lteRsrpBoost, gsmFlag); + mTdScdmaRscp = tdScdmaRscp; + } + + /** + * Constructor + * + * @hide + */ + public SignalStrength(int gsmSignalStrength, int gsmBitErrorRate, + int cdmaDbm, int cdmaEcio, + int evdoDbm, int evdoEcio, int evdoSnr, + int lteSignalStrength, int lteRsrp, int lteRsrq, int lteRssnr, int lteCqi, + int tdScdmaRscp, boolean gsmFlag) { + initialize(gsmSignalStrength, gsmBitErrorRate, cdmaDbm, cdmaEcio, + evdoDbm, evdoEcio, evdoSnr, lteSignalStrength, lteRsrp, + lteRsrq, lteRssnr, lteCqi, 0, gsmFlag); + mTdScdmaRscp = tdScdmaRscp; + } + + /** + * Constructor + * + * @hide + */ + public SignalStrength(int gsmSignalStrength, int gsmBitErrorRate, + int cdmaDbm, int cdmaEcio, + int evdoDbm, int evdoEcio, int evdoSnr, + int lteSignalStrength, int lteRsrp, int lteRsrq, int lteRssnr, int lteCqi, + boolean gsmFlag) { + initialize(gsmSignalStrength, gsmBitErrorRate, cdmaDbm, cdmaEcio, + evdoDbm, evdoEcio, evdoSnr, lteSignalStrength, lteRsrp, + lteRsrq, lteRssnr, lteCqi, 0, gsmFlag); + } + + /** + * Constructor + * + * @hide + */ + public SignalStrength(int gsmSignalStrength, int gsmBitErrorRate, + int cdmaDbm, int cdmaEcio, + int evdoDbm, int evdoEcio, int evdoSnr, + boolean gsmFlag) { + initialize(gsmSignalStrength, gsmBitErrorRate, cdmaDbm, cdmaEcio, + evdoDbm, evdoEcio, evdoSnr, 99, INVALID, + INVALID, INVALID, INVALID, 0, gsmFlag); + } + + /** + * Copy constructors + * + * @param s Source SignalStrength + * + * @hide + */ + public SignalStrength(SignalStrength s) { + copyFrom(s); + } + + /** + * Initialize gsm/cdma values, sets lte values to defaults. + * + * @param gsmSignalStrength + * @param gsmBitErrorRate + * @param cdmaDbm + * @param cdmaEcio + * @param evdoDbm + * @param evdoEcio + * @param evdoSnr + * @param gsm + * + * @hide + */ + public void initialize(int gsmSignalStrength, int gsmBitErrorRate, + int cdmaDbm, int cdmaEcio, + int evdoDbm, int evdoEcio, int evdoSnr, + boolean gsm) { + initialize(gsmSignalStrength, gsmBitErrorRate, cdmaDbm, cdmaEcio, + evdoDbm, evdoEcio, evdoSnr, 99, INVALID, + INVALID, INVALID, INVALID, 0, gsm); + } + + /** + * Initialize all the values + * + * @param gsmSignalStrength + * @param gsmBitErrorRate + * @param cdmaDbm + * @param cdmaEcio + * @param evdoDbm + * @param evdoEcio + * @param evdoSnr + * @param lteSignalStrength + * @param lteRsrp + * @param lteRsrq + * @param lteRssnr + * @param lteCqi + * @param lteRsrpBoost + * @param gsm + * + * @hide + */ + public void initialize(int gsmSignalStrength, int gsmBitErrorRate, + int cdmaDbm, int cdmaEcio, + int evdoDbm, int evdoEcio, int evdoSnr, + int lteSignalStrength, int lteRsrp, int lteRsrq, int lteRssnr, int lteCqi, + int lteRsrpBoost, boolean gsm) { + mGsmSignalStrength = gsmSignalStrength; + mGsmBitErrorRate = gsmBitErrorRate; + mCdmaDbm = cdmaDbm; + mCdmaEcio = cdmaEcio; + mEvdoDbm = evdoDbm; + mEvdoEcio = evdoEcio; + mEvdoSnr = evdoSnr; + mLteSignalStrength = lteSignalStrength; + mLteRsrp = lteRsrp; + mLteRsrq = lteRsrq; + mLteRssnr = lteRssnr; + mLteCqi = lteCqi; + mLteRsrpBoost = lteRsrpBoost; + mTdScdmaRscp = INVALID; + isGsm = gsm; + if (DBG) log("initialize: " + toString()); + } + + /** + * @hide + */ + protected void copyFrom(SignalStrength s) { + mGsmSignalStrength = s.mGsmSignalStrength; + mGsmBitErrorRate = s.mGsmBitErrorRate; + mCdmaDbm = s.mCdmaDbm; + mCdmaEcio = s.mCdmaEcio; + mEvdoDbm = s.mEvdoDbm; + mEvdoEcio = s.mEvdoEcio; + mEvdoSnr = s.mEvdoSnr; + mLteSignalStrength = s.mLteSignalStrength; + mLteRsrp = s.mLteRsrp; + mLteRsrq = s.mLteRsrq; + mLteRssnr = s.mLteRssnr; + mLteCqi = s.mLteCqi; + mLteRsrpBoost = s.mLteRsrpBoost; + mTdScdmaRscp = s.mTdScdmaRscp; + isGsm = s.isGsm; + } + + /** + * Construct a SignalStrength object from the given parcel. + * + * @hide + */ + public SignalStrength(Parcel in) { + if (DBG) log("Size of signalstrength parcel:" + in.dataSize()); + + mGsmSignalStrength = in.readInt(); + mGsmBitErrorRate = in.readInt(); + mCdmaDbm = in.readInt(); + mCdmaEcio = in.readInt(); + mEvdoDbm = in.readInt(); + mEvdoEcio = in.readInt(); + mEvdoSnr = in.readInt(); + mLteSignalStrength = in.readInt(); + mLteRsrp = in.readInt(); + mLteRsrq = in.readInt(); + mLteRssnr = in.readInt(); + mLteCqi = in.readInt(); + mLteRsrpBoost = in.readInt(); + mTdScdmaRscp = in.readInt(); + isGsm = (in.readInt() != 0); + } + + /** + * Make a SignalStrength object from the given parcel as passed up by + * the ril which does not have isGsm. isGsm will be changed by ServiceStateTracker + * so the default is a don't care. + * + * @hide + */ + public static SignalStrength makeSignalStrengthFromRilParcel(Parcel in) { + if (DBG) log("Size of signalstrength parcel:" + in.dataSize()); + + SignalStrength ss = new SignalStrength(); + ss.mGsmSignalStrength = in.readInt(); + ss.mGsmBitErrorRate = in.readInt(); + ss.mCdmaDbm = in.readInt(); + ss.mCdmaEcio = in.readInt(); + ss.mEvdoDbm = in.readInt(); + ss.mEvdoEcio = in.readInt(); + ss.mEvdoSnr = in.readInt(); + ss.mLteSignalStrength = in.readInt(); + ss.mLteRsrp = in.readInt(); + ss.mLteRsrq = in.readInt(); + ss.mLteRssnr = in.readInt(); + ss.mLteCqi = in.readInt(); + ss.mTdScdmaRscp = in.readInt(); + return ss; + } + + /** + * {@link Parcelable#writeToParcel} + */ + public void writeToParcel(Parcel out, int flags) { + out.writeInt(mGsmSignalStrength); + out.writeInt(mGsmBitErrorRate); + out.writeInt(mCdmaDbm); + out.writeInt(mCdmaEcio); + out.writeInt(mEvdoDbm); + out.writeInt(mEvdoEcio); + out.writeInt(mEvdoSnr); + out.writeInt(mLteSignalStrength); + out.writeInt(mLteRsrp); + out.writeInt(mLteRsrq); + out.writeInt(mLteRssnr); + out.writeInt(mLteCqi); + out.writeInt(mLteRsrpBoost); + out.writeInt(mTdScdmaRscp); + out.writeInt(isGsm ? 1 : 0); + } + + /** + * {@link Parcelable#describeContents} + */ + public int describeContents() { + return 0; + } + + /** + * {@link Parcelable.Creator} + * + * @hide + */ + public static final Parcelable.Creator<SignalStrength> CREATOR = new Parcelable.Creator() { + public SignalStrength createFromParcel(Parcel in) { + return new SignalStrength(in); + } + + public SignalStrength[] newArray(int size) { + return new SignalStrength[size]; + } + }; + + /** + * Validate the individual signal strength fields as per the range + * specified in ril.h + * Set to invalid any field that is not in the valid range + * Cdma, evdo, lte rsrp & rsrq values are sign converted + * when received from ril interface + * + * @return + * Valid values for all signalstrength fields + * @hide + */ + public void validateInput() { + if (DBG) log("Signal before validate=" + this); + // TS 27.007 8.5 + mGsmSignalStrength = mGsmSignalStrength >= 0 ? mGsmSignalStrength : 99; + // BER no change; + + mCdmaDbm = mCdmaDbm > 0 ? -mCdmaDbm : -120; + mCdmaEcio = (mCdmaEcio > 0) ? -mCdmaEcio : -160; + + mEvdoDbm = (mEvdoDbm > 0) ? -mEvdoDbm : -120; + mEvdoEcio = (mEvdoEcio >= 0) ? -mEvdoEcio : -1; + mEvdoSnr = ((mEvdoSnr > 0) && (mEvdoSnr <= 8)) ? mEvdoSnr : -1; + + // TS 36.214 Physical Layer Section 5.1.3, TS 36.331 RRC + mLteSignalStrength = (mLteSignalStrength >= 0) ? mLteSignalStrength : 99; + mLteRsrp = ((mLteRsrp >= 44) && (mLteRsrp <= 140)) ? -mLteRsrp : SignalStrength.INVALID; + mLteRsrq = ((mLteRsrq >= 3) && (mLteRsrq <= 20)) ? -mLteRsrq : SignalStrength.INVALID; + mLteRssnr = ((mLteRssnr >= -200) && (mLteRssnr <= 300)) ? mLteRssnr + : SignalStrength.INVALID; + + mTdScdmaRscp = ((mTdScdmaRscp >= 25) && (mTdScdmaRscp <= 120)) + ? -mTdScdmaRscp : SignalStrength.INVALID; + // Cqi no change + if (DBG) log("Signal after validate=" + this); + } + + /** + * @param true - Gsm, Lte phones + * false - Cdma phones + * + * Used by voice phone to set the isGsm + * flag + * @hide + */ + public void setGsm(boolean gsmFlag) { + isGsm = gsmFlag; + } + + /** + * @param lteRsrpBoost - signal strength offset + * + * Used by phone to set the lte signal strength offset which will be + * reduced from rsrp threshold while calculating signal strength level + * + * @hide + */ + public void setLteRsrpBoost(int lteRsrpBoost) { + mLteRsrpBoost = lteRsrpBoost; + } + + /** + * Get the GSM Signal Strength, valid values are (0-31, 99) as defined in TS + * 27.007 8.5 + */ + public int getGsmSignalStrength() { + return this.mGsmSignalStrength; + } + + /** + * Get the GSM bit error rate (0-7, 99) as defined in TS 27.007 8.5 + */ + public int getGsmBitErrorRate() { + return this.mGsmBitErrorRate; + } + + /** + * Get the CDMA RSSI value in dBm + */ + public int getCdmaDbm() { + return this.mCdmaDbm; + } + + /** + * Get the CDMA Ec/Io value in dB*10 + */ + public int getCdmaEcio() { + return this.mCdmaEcio; + } + + /** + * Get the EVDO RSSI value in dBm + */ + public int getEvdoDbm() { + return this.mEvdoDbm; + } + + /** + * Get the EVDO Ec/Io value in dB*10 + */ + public int getEvdoEcio() { + return this.mEvdoEcio; + } + + /** + * Get the signal to noise ratio. Valid values are 0-8. 8 is the highest. + */ + public int getEvdoSnr() { + return this.mEvdoSnr; + } + + /** @hide */ + public int getLteSignalStrength() { + return mLteSignalStrength; + } + + /** @hide */ + public int getLteRsrp() { + return mLteRsrp; + } + + /** @hide */ + public int getLteRsrq() { + return mLteRsrq; + } + + /** @hide */ + public int getLteRssnr() { + return mLteRssnr; + } + + /** @hide */ + public int getLteCqi() { + return mLteCqi; + } + + /** @hide */ + public int getLteRsrpBoost() { + return mLteRsrpBoost; + } + + /** + * Retrieve an abstract level value for the overall signal strength. + * + * @return a single integer from 0 to 4 representing the general signal quality. + * This may take into account many different radio technology inputs. + * 0 represents very poor signal strength + * while 4 represents a very strong signal strength. + */ + public int getLevel() { + int level = 0; + + if (isGsm) { + level = getLteLevel(); + if (level == SIGNAL_STRENGTH_NONE_OR_UNKNOWN) { + level = getTdScdmaLevel(); + if (level == SIGNAL_STRENGTH_NONE_OR_UNKNOWN) { + level = getGsmLevel(); + } + } + } else { + int cdmaLevel = getCdmaLevel(); + int evdoLevel = getEvdoLevel(); + if (evdoLevel == SIGNAL_STRENGTH_NONE_OR_UNKNOWN) { + /* We don't know evdo, use cdma */ + level = cdmaLevel; + } else if (cdmaLevel == SIGNAL_STRENGTH_NONE_OR_UNKNOWN) { + /* We don't know cdma, use evdo */ + level = evdoLevel; + } else { + /* We know both, use the lowest level */ + level = cdmaLevel < evdoLevel ? cdmaLevel : evdoLevel; + } + } + if (DBG) log("getLevel=" + level); + return level; + } + + /** + * Get the signal level as an asu value between 0..31, 99 is unknown + * + * @hide + */ + public int getAsuLevel() { + int asuLevel = 0; + if (isGsm) { + if (getLteLevel() == SIGNAL_STRENGTH_NONE_OR_UNKNOWN) { + if (getTdScdmaLevel() == SIGNAL_STRENGTH_NONE_OR_UNKNOWN) { + asuLevel = getGsmAsuLevel(); + } else { + asuLevel = getTdScdmaAsuLevel(); + } + } else { + asuLevel = getLteAsuLevel(); + } + } else { + int cdmaAsuLevel = getCdmaAsuLevel(); + int evdoAsuLevel = getEvdoAsuLevel(); + if (evdoAsuLevel == 0) { + /* We don't know evdo use, cdma */ + asuLevel = cdmaAsuLevel; + } else if (cdmaAsuLevel == 0) { + /* We don't know cdma use, evdo */ + asuLevel = evdoAsuLevel; + } else { + /* We know both, use the lowest level */ + asuLevel = cdmaAsuLevel < evdoAsuLevel ? cdmaAsuLevel : evdoAsuLevel; + } + } + if (DBG) log("getAsuLevel=" + asuLevel); + return asuLevel; + } + + /** + * Get the signal strength as dBm + * + * @hide + */ + public int getDbm() { + int dBm = INVALID; + + if(isGsm()) { + dBm = getLteDbm(); + if (dBm == INVALID) { + if (getTdScdmaLevel() == SIGNAL_STRENGTH_NONE_OR_UNKNOWN) { + dBm = getGsmDbm(); + } else { + dBm = getTdScdmaDbm(); + } + } + } else { + int cdmaDbm = getCdmaDbm(); + int evdoDbm = getEvdoDbm(); + + return (evdoDbm == -120) ? cdmaDbm : ((cdmaDbm == -120) ? evdoDbm + : (cdmaDbm < evdoDbm ? cdmaDbm : evdoDbm)); + } + if (DBG) log("getDbm=" + dBm); + return dBm; + } + + /** + * Get Gsm signal strength as dBm + * + * @hide + */ + public int getGsmDbm() { + int dBm; + + int gsmSignalStrength = getGsmSignalStrength(); + int asu = (gsmSignalStrength == 99 ? -1 : gsmSignalStrength); + if (asu != -1) { + dBm = -113 + (2 * asu); + } else { + dBm = -1; + } + if (DBG) log("getGsmDbm=" + dBm); + return dBm; + } + + /** + * Get gsm as level 0..4 + * + * @hide + */ + public int getGsmLevel() { + int level; + + // ASU ranges from 0 to 31 - TS 27.007 Sec 8.5 + // asu = 0 (-113dB or less) is very weak + // signal, its better to show 0 bars to the user in such cases. + // asu = 99 is a special case, where the signal strength is unknown. + int asu = getGsmSignalStrength(); + if (asu <= 2 || asu == 99) level = SIGNAL_STRENGTH_NONE_OR_UNKNOWN; + else if (asu >= 12) level = SIGNAL_STRENGTH_GREAT; + else if (asu >= 8) level = SIGNAL_STRENGTH_GOOD; + else if (asu >= 5) level = SIGNAL_STRENGTH_MODERATE; + else level = SIGNAL_STRENGTH_POOR; + if (DBG) log("getGsmLevel=" + level); + return level; + } + + /** + * Get the gsm signal level as an asu value between 0..31, 99 is unknown + * + * @hide + */ + public int getGsmAsuLevel() { + // ASU ranges from 0 to 31 - TS 27.007 Sec 8.5 + // asu = 0 (-113dB or less) is very weak + // signal, its better to show 0 bars to the user in such cases. + // asu = 99 is a special case, where the signal strength is unknown. + int level = getGsmSignalStrength(); + if (DBG) log("getGsmAsuLevel=" + level); + return level; + } + + /** + * Get cdma as level 0..4 + * + * @hide + */ + public int getCdmaLevel() { + final int cdmaDbm = getCdmaDbm(); + final int cdmaEcio = getCdmaEcio(); + int levelDbm; + int levelEcio; + + if (cdmaDbm >= -75) levelDbm = SIGNAL_STRENGTH_GREAT; + else if (cdmaDbm >= -85) levelDbm = SIGNAL_STRENGTH_GOOD; + else if (cdmaDbm >= -95) levelDbm = SIGNAL_STRENGTH_MODERATE; + else if (cdmaDbm >= -100) levelDbm = SIGNAL_STRENGTH_POOR; + else levelDbm = SIGNAL_STRENGTH_NONE_OR_UNKNOWN; + + // Ec/Io are in dB*10 + if (cdmaEcio >= -90) levelEcio = SIGNAL_STRENGTH_GREAT; + else if (cdmaEcio >= -110) levelEcio = SIGNAL_STRENGTH_GOOD; + else if (cdmaEcio >= -130) levelEcio = SIGNAL_STRENGTH_MODERATE; + else if (cdmaEcio >= -150) levelEcio = SIGNAL_STRENGTH_POOR; + else levelEcio = SIGNAL_STRENGTH_NONE_OR_UNKNOWN; + + int level = (levelDbm < levelEcio) ? levelDbm : levelEcio; + if (DBG) log("getCdmaLevel=" + level); + return level; + } + + /** + * Get the cdma signal level as an asu value between 0..31, 99 is unknown + * + * @hide + */ + public int getCdmaAsuLevel() { + final int cdmaDbm = getCdmaDbm(); + final int cdmaEcio = getCdmaEcio(); + int cdmaAsuLevel; + int ecioAsuLevel; + + if (cdmaDbm >= -75) cdmaAsuLevel = 16; + else if (cdmaDbm >= -82) cdmaAsuLevel = 8; + else if (cdmaDbm >= -90) cdmaAsuLevel = 4; + else if (cdmaDbm >= -95) cdmaAsuLevel = 2; + else if (cdmaDbm >= -100) cdmaAsuLevel = 1; + else cdmaAsuLevel = 99; + + // Ec/Io are in dB*10 + if (cdmaEcio >= -90) ecioAsuLevel = 16; + else if (cdmaEcio >= -100) ecioAsuLevel = 8; + else if (cdmaEcio >= -115) ecioAsuLevel = 4; + else if (cdmaEcio >= -130) ecioAsuLevel = 2; + else if (cdmaEcio >= -150) ecioAsuLevel = 1; + else ecioAsuLevel = 99; + + int level = (cdmaAsuLevel < ecioAsuLevel) ? cdmaAsuLevel : ecioAsuLevel; + if (DBG) log("getCdmaAsuLevel=" + level); + return level; + } + + /** + * Get Evdo as level 0..4 + * + * @hide + */ + public int getEvdoLevel() { + int evdoDbm = getEvdoDbm(); + int evdoSnr = getEvdoSnr(); + int levelEvdoDbm; + int levelEvdoSnr; + + if (evdoDbm >= -65) levelEvdoDbm = SIGNAL_STRENGTH_GREAT; + else if (evdoDbm >= -75) levelEvdoDbm = SIGNAL_STRENGTH_GOOD; + else if (evdoDbm >= -90) levelEvdoDbm = SIGNAL_STRENGTH_MODERATE; + else if (evdoDbm >= -105) levelEvdoDbm = SIGNAL_STRENGTH_POOR; + else levelEvdoDbm = SIGNAL_STRENGTH_NONE_OR_UNKNOWN; + + if (evdoSnr >= 7) levelEvdoSnr = SIGNAL_STRENGTH_GREAT; + else if (evdoSnr >= 5) levelEvdoSnr = SIGNAL_STRENGTH_GOOD; + else if (evdoSnr >= 3) levelEvdoSnr = SIGNAL_STRENGTH_MODERATE; + else if (evdoSnr >= 1) levelEvdoSnr = SIGNAL_STRENGTH_POOR; + else levelEvdoSnr = SIGNAL_STRENGTH_NONE_OR_UNKNOWN; + + int level = (levelEvdoDbm < levelEvdoSnr) ? levelEvdoDbm : levelEvdoSnr; + if (DBG) log("getEvdoLevel=" + level); + return level; + } + + /** + * Get the evdo signal level as an asu value between 0..31, 99 is unknown + * + * @hide + */ + public int getEvdoAsuLevel() { + int evdoDbm = getEvdoDbm(); + int evdoSnr = getEvdoSnr(); + int levelEvdoDbm; + int levelEvdoSnr; + + if (evdoDbm >= -65) levelEvdoDbm = 16; + else if (evdoDbm >= -75) levelEvdoDbm = 8; + else if (evdoDbm >= -85) levelEvdoDbm = 4; + else if (evdoDbm >= -95) levelEvdoDbm = 2; + else if (evdoDbm >= -105) levelEvdoDbm = 1; + else levelEvdoDbm = 99; + + if (evdoSnr >= 7) levelEvdoSnr = 16; + else if (evdoSnr >= 6) levelEvdoSnr = 8; + else if (evdoSnr >= 5) levelEvdoSnr = 4; + else if (evdoSnr >= 3) levelEvdoSnr = 2; + else if (evdoSnr >= 1) levelEvdoSnr = 1; + else levelEvdoSnr = 99; + + int level = (levelEvdoDbm < levelEvdoSnr) ? levelEvdoDbm : levelEvdoSnr; + if (DBG) log("getEvdoAsuLevel=" + level); + return level; + } + + /** + * Get LTE as dBm + * + * @hide + */ + public int getLteDbm() { + return mLteRsrp; + } + + /** + * Get LTE as level 0..4 + * + * @hide + */ + public int getLteLevel() { + /* + * TS 36.214 Physical Layer Section 5.1.3 TS 36.331 RRC RSSI = received + * signal + noise RSRP = reference signal dBm RSRQ = quality of signal + * dB= Number of Resource blocksxRSRP/RSSI SNR = gain=signal/noise ratio + * = -10log P1/P2 dB + */ + int rssiIconLevel = SIGNAL_STRENGTH_NONE_OR_UNKNOWN, rsrpIconLevel = -1, snrIconLevel = -1; + + int[] threshRsrp = Resources.getSystem().getIntArray( + com.android.internal.R.array.config_lteDbmThresholds); + if (threshRsrp.length != 6) { + Log.wtf(LOG_TAG, "getLteLevel - config_lteDbmThresholds has invalid num of elements." + + " Cannot evaluate RSRP signal."); + } else { + if (mLteRsrp > threshRsrp[5]) { + rsrpIconLevel = -1; + } else if (mLteRsrp >= (threshRsrp[4] - mLteRsrpBoost)) { + rsrpIconLevel = SIGNAL_STRENGTH_GREAT; + } else if (mLteRsrp >= (threshRsrp[3] - mLteRsrpBoost)) { + rsrpIconLevel = SIGNAL_STRENGTH_GOOD; + } else if (mLteRsrp >= (threshRsrp[2] - mLteRsrpBoost)) { + rsrpIconLevel = SIGNAL_STRENGTH_MODERATE; + } else if (mLteRsrp >= (threshRsrp[1] - mLteRsrpBoost)) { + rsrpIconLevel = SIGNAL_STRENGTH_POOR; + } else if (mLteRsrp >= threshRsrp[0]) { + rsrpIconLevel = SIGNAL_STRENGTH_NONE_OR_UNKNOWN; + } + } + + /* + * Values are -200 dB to +300 (SNR*10dB) RS_SNR >= 13.0 dB =>4 bars 4.5 + * dB <= RS_SNR < 13.0 dB => 3 bars 1.0 dB <= RS_SNR < 4.5 dB => 2 bars + * -3.0 dB <= RS_SNR < 1.0 dB 1 bar RS_SNR < -3.0 dB/No Service Antenna + * Icon Only + */ + if (mLteRssnr > 300) snrIconLevel = -1; + else if (mLteRssnr >= 130) snrIconLevel = SIGNAL_STRENGTH_GREAT; + else if (mLteRssnr >= 45) snrIconLevel = SIGNAL_STRENGTH_GOOD; + else if (mLteRssnr >= 10) snrIconLevel = SIGNAL_STRENGTH_MODERATE; + else if (mLteRssnr >= -30) snrIconLevel = SIGNAL_STRENGTH_POOR; + else if (mLteRssnr >= -200) + snrIconLevel = SIGNAL_STRENGTH_NONE_OR_UNKNOWN; + + if (DBG) log("getLTELevel - rsrp:" + mLteRsrp + " snr:" + mLteRssnr + " rsrpIconLevel:" + + rsrpIconLevel + " snrIconLevel:" + snrIconLevel + + " lteRsrpBoost:" + mLteRsrpBoost); + + /* Choose a measurement type to use for notification */ + if (snrIconLevel != -1 && rsrpIconLevel != -1) { + /* + * The number of bars displayed shall be the smaller of the bars + * associated with LTE RSRP and the bars associated with the LTE + * RS_SNR + */ + return (rsrpIconLevel < snrIconLevel ? rsrpIconLevel : snrIconLevel); + } + + if (snrIconLevel != -1) return snrIconLevel; + + if (rsrpIconLevel != -1) return rsrpIconLevel; + + /* Valid values are (0-63, 99) as defined in TS 36.331 */ + if (mLteSignalStrength > 63) rssiIconLevel = SIGNAL_STRENGTH_NONE_OR_UNKNOWN; + else if (mLteSignalStrength >= 12) rssiIconLevel = SIGNAL_STRENGTH_GREAT; + else if (mLteSignalStrength >= 8) rssiIconLevel = SIGNAL_STRENGTH_GOOD; + else if (mLteSignalStrength >= 5) rssiIconLevel = SIGNAL_STRENGTH_MODERATE; + else if (mLteSignalStrength >= 0) rssiIconLevel = SIGNAL_STRENGTH_POOR; + + if (DBG) log("getLTELevel - rssi:" + mLteSignalStrength + " rssiIconLevel:" + + rssiIconLevel); + return rssiIconLevel; + + } + /** + * Get the LTE signal level as an asu value between 0..97, 99 is unknown + * Asu is calculated based on 3GPP RSRP. Refer to 3GPP 27.007 (Ver 10.3.0) Sec 8.69 + * + * @hide + */ + public int getLteAsuLevel() { + int lteAsuLevel = 99; + int lteDbm = getLteDbm(); + /* + * 3GPP 27.007 (Ver 10.3.0) Sec 8.69 + * 0 -140 dBm or less + * 1 -139 dBm + * 2...96 -138... -44 dBm + * 97 -43 dBm or greater + * 255 not known or not detectable + */ + /* + * validateInput will always give a valid range between -140 t0 -44 as + * per ril.h. so RSRP >= -43 & <-140 will fall under asu level 255 + * and not 97 or 0 + */ + if (lteDbm == SignalStrength.INVALID) lteAsuLevel = 255; + else lteAsuLevel = lteDbm + 140; + if (DBG) log("Lte Asu level: "+lteAsuLevel); + return lteAsuLevel; + } + + /** + * @return true if this is for GSM + */ + public boolean isGsm() { + return this.isGsm; + } + + /** + * @return get TD_SCDMA dbm + * + * @hide + */ + public int getTdScdmaDbm() { + return this.mTdScdmaRscp; + } + + /** + * Get TD-SCDMA as level 0..4 + * Range : 25 to 120 + * INT_MAX: 0x7FFFFFFF denotes invalid value + * Reference: 3GPP TS 25.123, section 9.1.1.1 + * + * @hide + */ + public int getTdScdmaLevel() { + final int tdScdmaDbm = getTdScdmaDbm(); + int level; + + if ((tdScdmaDbm > -25) || (tdScdmaDbm == SignalStrength.INVALID)) + level = SIGNAL_STRENGTH_NONE_OR_UNKNOWN; + else if (tdScdmaDbm >= -49) level = SIGNAL_STRENGTH_GREAT; + else if (tdScdmaDbm >= -73) level = SIGNAL_STRENGTH_GOOD; + else if (tdScdmaDbm >= -97) level = SIGNAL_STRENGTH_MODERATE; + else if (tdScdmaDbm >= -110) level = SIGNAL_STRENGTH_POOR; + else level = SIGNAL_STRENGTH_NONE_OR_UNKNOWN; + + if (DBG) log("getTdScdmaLevel = " + level); + return level; + } + + /** + * Get the TD-SCDMA signal level as an asu value. + * + * @hide + */ + public int getTdScdmaAsuLevel() { + final int tdScdmaDbm = getTdScdmaDbm(); + int tdScdmaAsuLevel; + + if (tdScdmaDbm == INVALID) tdScdmaAsuLevel = 255; + else tdScdmaAsuLevel = tdScdmaDbm + 120; + if (DBG) log("TD-SCDMA Asu level: " + tdScdmaAsuLevel); + return tdScdmaAsuLevel; + } + + /** + * @return hash code + */ + @Override + public int hashCode() { + int primeNum = 31; + return ((mGsmSignalStrength * primeNum) + + (mGsmBitErrorRate * primeNum) + + (mCdmaDbm * primeNum) + (mCdmaEcio * primeNum) + + (mEvdoDbm * primeNum) + (mEvdoEcio * primeNum) + (mEvdoSnr * primeNum) + + (mLteSignalStrength * primeNum) + (mLteRsrp * primeNum) + + (mLteRsrq * primeNum) + (mLteRssnr * primeNum) + (mLteCqi * primeNum) + + (mLteRsrpBoost * primeNum) + (mTdScdmaRscp * primeNum) + (isGsm ? 1 : 0)); + } + + /** + * @return true if the signal strengths are the same + */ + @Override + public boolean equals (Object o) { + SignalStrength s; + + try { + s = (SignalStrength) o; + } catch (ClassCastException ex) { + return false; + } + + if (o == null) { + return false; + } + + return (mGsmSignalStrength == s.mGsmSignalStrength + && mGsmBitErrorRate == s.mGsmBitErrorRate + && mCdmaDbm == s.mCdmaDbm + && mCdmaEcio == s.mCdmaEcio + && mEvdoDbm == s.mEvdoDbm + && mEvdoEcio == s.mEvdoEcio + && mEvdoSnr == s.mEvdoSnr + && mLteSignalStrength == s.mLteSignalStrength + && mLteRsrp == s.mLteRsrp + && mLteRsrq == s.mLteRsrq + && mLteRssnr == s.mLteRssnr + && mLteCqi == s.mLteCqi + && mLteRsrpBoost == s.mLteRsrpBoost + && mTdScdmaRscp == s.mTdScdmaRscp + && isGsm == s.isGsm); + } + + /** + * @return string representation. + */ + @Override + public String toString() { + return ("SignalStrength:" + + " " + mGsmSignalStrength + + " " + mGsmBitErrorRate + + " " + mCdmaDbm + + " " + mCdmaEcio + + " " + mEvdoDbm + + " " + mEvdoEcio + + " " + mEvdoSnr + + " " + mLteSignalStrength + + " " + mLteRsrp + + " " + mLteRsrq + + " " + mLteRssnr + + " " + mLteCqi + + " " + mLteRsrpBoost + + " " + mTdScdmaRscp + + " " + (isGsm ? "gsm|lte" : "cdma")); + } + + /** + * Set SignalStrength based on intent notifier map + * + * @param m intent notifier map + * @hide + */ + private void setFromNotifierBundle(Bundle m) { + mGsmSignalStrength = m.getInt("GsmSignalStrength"); + mGsmBitErrorRate = m.getInt("GsmBitErrorRate"); + mCdmaDbm = m.getInt("CdmaDbm"); + mCdmaEcio = m.getInt("CdmaEcio"); + mEvdoDbm = m.getInt("EvdoDbm"); + mEvdoEcio = m.getInt("EvdoEcio"); + mEvdoSnr = m.getInt("EvdoSnr"); + mLteSignalStrength = m.getInt("LteSignalStrength"); + mLteRsrp = m.getInt("LteRsrp"); + mLteRsrq = m.getInt("LteRsrq"); + mLteRssnr = m.getInt("LteRssnr"); + mLteCqi = m.getInt("LteCqi"); + mLteRsrpBoost = m.getInt("lteRsrpBoost"); + mTdScdmaRscp = m.getInt("TdScdma"); + isGsm = m.getBoolean("isGsm"); + } + + /** + * Set intent notifier Bundle based on SignalStrength + * + * @param m intent notifier Bundle + * @hide + */ + public void fillInNotifierBundle(Bundle m) { + m.putInt("GsmSignalStrength", mGsmSignalStrength); + m.putInt("GsmBitErrorRate", mGsmBitErrorRate); + m.putInt("CdmaDbm", mCdmaDbm); + m.putInt("CdmaEcio", mCdmaEcio); + m.putInt("EvdoDbm", mEvdoDbm); + m.putInt("EvdoEcio", mEvdoEcio); + m.putInt("EvdoSnr", mEvdoSnr); + m.putInt("LteSignalStrength", mLteSignalStrength); + m.putInt("LteRsrp", mLteRsrp); + m.putInt("LteRsrq", mLteRsrq); + m.putInt("LteRssnr", mLteRssnr); + m.putInt("LteCqi", mLteCqi); + m.putInt("lteRsrpBoost", mLteRsrpBoost); + m.putInt("TdScdma", mTdScdmaRscp); + m.putBoolean("isGsm", isGsm); + } + + /** + * log + */ + private static void log(String s) { + Rlog.w(LOG_TAG, s); + } +} diff --git a/android/telephony/SmsCbCmasInfo.java b/android/telephony/SmsCbCmasInfo.java new file mode 100644 index 00000000..c9129244 --- /dev/null +++ b/android/telephony/SmsCbCmasInfo.java @@ -0,0 +1,310 @@ +/* + * Copyright (C) 2012 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.telephony; + +import android.os.Parcel; +import android.os.Parcelable; + +/** + * Contains CMAS warning notification Type 1 elements for a {@link SmsCbMessage}. + * Supported values for each element are defined in TIA-1149-0-1 (CMAS over CDMA) and + * 3GPP TS 23.041 (for GSM/UMTS). + * + * {@hide} + */ +public class SmsCbCmasInfo implements Parcelable { + + // CMAS message class (in GSM/UMTS message identifier or CDMA service category). + + /** Presidential-level alert (Korean Public Alert System Class 0 message). */ + public static final int CMAS_CLASS_PRESIDENTIAL_LEVEL_ALERT = 0x00; + + /** Extreme threat to life and property (Korean Public Alert System Class 1 message). */ + public static final int CMAS_CLASS_EXTREME_THREAT = 0x01; + + /** Severe threat to life and property (Korean Public Alert System Class 1 message). */ + public static final int CMAS_CLASS_SEVERE_THREAT = 0x02; + + /** Child abduction emergency (AMBER Alert). */ + public static final int CMAS_CLASS_CHILD_ABDUCTION_EMERGENCY = 0x03; + + /** CMAS test message. */ + public static final int CMAS_CLASS_REQUIRED_MONTHLY_TEST = 0x04; + + /** CMAS exercise. */ + public static final int CMAS_CLASS_CMAS_EXERCISE = 0x05; + + /** CMAS category for operator defined use. */ + public static final int CMAS_CLASS_OPERATOR_DEFINED_USE = 0x06; + + /** CMAS category for warning types that are reserved for future extension. */ + public static final int CMAS_CLASS_UNKNOWN = -1; + + // CMAS alert category (in CDMA type 1 elements record). + + /** CMAS alert category: Geophysical including landslide. */ + public static final int CMAS_CATEGORY_GEO = 0x00; + + /** CMAS alert category: Meteorological including flood. */ + public static final int CMAS_CATEGORY_MET = 0x01; + + /** CMAS alert category: General emergency and public safety. */ + public static final int CMAS_CATEGORY_SAFETY = 0x02; + + /** CMAS alert category: Law enforcement, military, homeland/local/private security. */ + public static final int CMAS_CATEGORY_SECURITY = 0x03; + + /** CMAS alert category: Rescue and recovery. */ + public static final int CMAS_CATEGORY_RESCUE = 0x04; + + /** CMAS alert category: Fire suppression and rescue. */ + public static final int CMAS_CATEGORY_FIRE = 0x05; + + /** CMAS alert category: Medical and public health. */ + public static final int CMAS_CATEGORY_HEALTH = 0x06; + + /** CMAS alert category: Pollution and other environmental. */ + public static final int CMAS_CATEGORY_ENV = 0x07; + + /** CMAS alert category: Public and private transportation. */ + public static final int CMAS_CATEGORY_TRANSPORT = 0x08; + + /** CMAS alert category: Utility, telecom, other non-transport infrastructure. */ + public static final int CMAS_CATEGORY_INFRA = 0x09; + + /** CMAS alert category: Chem, bio, radiological, nuclear, high explosive threat or attack. */ + public static final int CMAS_CATEGORY_CBRNE = 0x0a; + + /** CMAS alert category: Other events. */ + public static final int CMAS_CATEGORY_OTHER = 0x0b; + + /** + * CMAS alert category is unknown. The category is only available for CDMA broadcasts + * containing a type 1 elements record, so GSM and UMTS broadcasts always return unknown. + */ + public static final int CMAS_CATEGORY_UNKNOWN = -1; + + // CMAS response type (in CDMA type 1 elements record). + + /** CMAS response type: Take shelter in place. */ + public static final int CMAS_RESPONSE_TYPE_SHELTER = 0x00; + + /** CMAS response type: Evacuate (Relocate). */ + public static final int CMAS_RESPONSE_TYPE_EVACUATE = 0x01; + + /** CMAS response type: Make preparations. */ + public static final int CMAS_RESPONSE_TYPE_PREPARE = 0x02; + + /** CMAS response type: Execute a pre-planned activity. */ + public static final int CMAS_RESPONSE_TYPE_EXECUTE = 0x03; + + /** CMAS response type: Attend to information sources. */ + public static final int CMAS_RESPONSE_TYPE_MONITOR = 0x04; + + /** CMAS response type: Avoid hazard. */ + public static final int CMAS_RESPONSE_TYPE_AVOID = 0x05; + + /** CMAS response type: Evaluate the information in this message (not for public warnings). */ + public static final int CMAS_RESPONSE_TYPE_ASSESS = 0x06; + + /** CMAS response type: No action recommended. */ + public static final int CMAS_RESPONSE_TYPE_NONE = 0x07; + + /** + * CMAS response type is unknown. The response type is only available for CDMA broadcasts + * containing a type 1 elements record, so GSM and UMTS broadcasts always return unknown. + */ + public static final int CMAS_RESPONSE_TYPE_UNKNOWN = -1; + + // 4-bit CMAS severity (in GSM/UMTS message identifier or CDMA type 1 elements record). + + /** CMAS severity type: Extraordinary threat to life or property. */ + public static final int CMAS_SEVERITY_EXTREME = 0x0; + + /** CMAS severity type: Significant threat to life or property. */ + public static final int CMAS_SEVERITY_SEVERE = 0x1; + + /** + * CMAS alert severity is unknown. The severity is available for CDMA warning alerts + * containing a type 1 elements record and for all GSM and UMTS alerts except for the + * Presidential-level alert class (Korean Public Alert System Class 0). + */ + public static final int CMAS_SEVERITY_UNKNOWN = -1; + + // CMAS urgency (in GSM/UMTS message identifier or CDMA type 1 elements record). + + /** CMAS urgency type: Responsive action should be taken immediately. */ + public static final int CMAS_URGENCY_IMMEDIATE = 0x0; + + /** CMAS urgency type: Responsive action should be taken within the next hour. */ + public static final int CMAS_URGENCY_EXPECTED = 0x1; + + /** + * CMAS alert urgency is unknown. The urgency is available for CDMA warning alerts + * containing a type 1 elements record and for all GSM and UMTS alerts except for the + * Presidential-level alert class (Korean Public Alert System Class 0). + */ + public static final int CMAS_URGENCY_UNKNOWN = -1; + + // CMAS certainty (in GSM/UMTS message identifier or CDMA type 1 elements record). + + /** CMAS certainty type: Determined to have occurred or to be ongoing. */ + public static final int CMAS_CERTAINTY_OBSERVED = 0x0; + + /** CMAS certainty type: Likely (probability > ~50%). */ + public static final int CMAS_CERTAINTY_LIKELY = 0x1; + + /** + * CMAS alert certainty is unknown. The certainty is available for CDMA warning alerts + * containing a type 1 elements record and for all GSM and UMTS alerts except for the + * Presidential-level alert class (Korean Public Alert System Class 0). + */ + public static final int CMAS_CERTAINTY_UNKNOWN = -1; + + /** CMAS message class. */ + private final int mMessageClass; + + /** CMAS category. */ + private final int mCategory; + + /** CMAS response type. */ + private final int mResponseType; + + /** CMAS severity. */ + private final int mSeverity; + + /** CMAS urgency. */ + private final int mUrgency; + + /** CMAS certainty. */ + private final int mCertainty; + + /** Create a new SmsCbCmasInfo object with the specified values. */ + public SmsCbCmasInfo(int messageClass, int category, int responseType, int severity, + int urgency, int certainty) { + mMessageClass = messageClass; + mCategory = category; + mResponseType = responseType; + mSeverity = severity; + mUrgency = urgency; + mCertainty = certainty; + } + + /** Create a new SmsCbCmasInfo object from a Parcel. */ + SmsCbCmasInfo(Parcel in) { + mMessageClass = in.readInt(); + mCategory = in.readInt(); + mResponseType = in.readInt(); + mSeverity = in.readInt(); + mUrgency = in.readInt(); + mCertainty = in.readInt(); + } + + /** + * Flatten this object into a Parcel. + * + * @param dest The Parcel in which the object should be written. + * @param flags Additional flags about how the object should be written (ignored). + */ + @Override + public void writeToParcel(Parcel dest, int flags) { + dest.writeInt(mMessageClass); + dest.writeInt(mCategory); + dest.writeInt(mResponseType); + dest.writeInt(mSeverity); + dest.writeInt(mUrgency); + dest.writeInt(mCertainty); + } + + /** + * Returns the CMAS message class, e.g. {@link #CMAS_CLASS_PRESIDENTIAL_LEVEL_ALERT}. + * @return one of the {@code CMAS_CLASS} values + */ + public int getMessageClass() { + return mMessageClass; + } + + /** + * Returns the CMAS category, e.g. {@link #CMAS_CATEGORY_GEO}. + * @return one of the {@code CMAS_CATEGORY} values + */ + public int getCategory() { + return mCategory; + } + + /** + * Returns the CMAS response type, e.g. {@link #CMAS_RESPONSE_TYPE_SHELTER}. + * @return one of the {@code CMAS_RESPONSE_TYPE} values + */ + public int getResponseType() { + return mResponseType; + } + + /** + * Returns the CMAS severity, e.g. {@link #CMAS_SEVERITY_EXTREME}. + * @return one of the {@code CMAS_SEVERITY} values + */ + public int getSeverity() { + return mSeverity; + } + + /** + * Returns the CMAS urgency, e.g. {@link #CMAS_URGENCY_IMMEDIATE}. + * @return one of the {@code CMAS_URGENCY} values + */ + public int getUrgency() { + return mUrgency; + } + + /** + * Returns the CMAS certainty, e.g. {@link #CMAS_CERTAINTY_OBSERVED}. + * @return one of the {@code CMAS_CERTAINTY} values + */ + public int getCertainty() { + return mCertainty; + } + + @Override + public String toString() { + return "SmsCbCmasInfo{messageClass=" + mMessageClass + ", category=" + mCategory + + ", responseType=" + mResponseType + ", severity=" + mSeverity + + ", urgency=" + mUrgency + ", certainty=" + mCertainty + '}'; + } + + /** + * Describe the kinds of special objects contained in the marshalled representation. + * @return a bitmask indicating this Parcelable contains no special objects + */ + @Override + public int describeContents() { + return 0; + } + + /** Creator for unparcelling objects. */ + public static final Parcelable.Creator<SmsCbCmasInfo> + CREATOR = new Parcelable.Creator<SmsCbCmasInfo>() { + @Override + public SmsCbCmasInfo createFromParcel(Parcel in) { + return new SmsCbCmasInfo(in); + } + + @Override + public SmsCbCmasInfo[] newArray(int size) { + return new SmsCbCmasInfo[size]; + } + }; +} diff --git a/android/telephony/SmsCbEtwsInfo.java b/android/telephony/SmsCbEtwsInfo.java new file mode 100644 index 00000000..14e02de2 --- /dev/null +++ b/android/telephony/SmsCbEtwsInfo.java @@ -0,0 +1,222 @@ +/* + * Copyright (C) 2012 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.telephony; + +import android.os.Parcel; +import android.os.Parcelable; +import android.text.format.Time; + +import com.android.internal.telephony.uicc.IccUtils; + +import java.util.Arrays; + +/** + * Contains information elements for a GSM or UMTS ETWS warning notification. + * Supported values for each element are defined in 3GPP TS 23.041. + * + * {@hide} + */ +public class SmsCbEtwsInfo implements Parcelable { + + /** ETWS warning type for earthquake. */ + public static final int ETWS_WARNING_TYPE_EARTHQUAKE = 0x00; + + /** ETWS warning type for tsunami. */ + public static final int ETWS_WARNING_TYPE_TSUNAMI = 0x01; + + /** ETWS warning type for earthquake and tsunami. */ + public static final int ETWS_WARNING_TYPE_EARTHQUAKE_AND_TSUNAMI = 0x02; + + /** ETWS warning type for test messages. */ + public static final int ETWS_WARNING_TYPE_TEST_MESSAGE = 0x03; + + /** ETWS warning type for other emergency types. */ + public static final int ETWS_WARNING_TYPE_OTHER_EMERGENCY = 0x04; + + /** Unknown ETWS warning type. */ + public static final int ETWS_WARNING_TYPE_UNKNOWN = -1; + + /** One of the ETWS warning type constants defined in this class. */ + private final int mWarningType; + + /** Whether or not to activate the emergency user alert tone and vibration. */ + private final boolean mEmergencyUserAlert; + + /** Whether or not to activate a popup alert. */ + private final boolean mActivatePopup; + + /** Whether ETWS primary message or not/ */ + private final boolean mPrimary; + + /** + * 50-byte security information (ETWS primary notification for GSM only). As of Release 10, + * 3GPP TS 23.041 states that the UE shall ignore the ETWS primary notification timestamp + * and digital signature if received. Therefore it is treated as a raw byte array and + * parceled with the broadcast intent if present, but the timestamp is only computed if an + * application asks for the individual components. + */ + private final byte[] mWarningSecurityInformation; + + /** Create a new SmsCbEtwsInfo object with the specified values. */ + public SmsCbEtwsInfo(int warningType, boolean emergencyUserAlert, boolean activatePopup, + boolean primary, byte[] warningSecurityInformation) { + mWarningType = warningType; + mEmergencyUserAlert = emergencyUserAlert; + mActivatePopup = activatePopup; + mPrimary = primary; + mWarningSecurityInformation = warningSecurityInformation; + } + + /** Create a new SmsCbEtwsInfo object from a Parcel. */ + SmsCbEtwsInfo(Parcel in) { + mWarningType = in.readInt(); + mEmergencyUserAlert = (in.readInt() != 0); + mActivatePopup = (in.readInt() != 0); + mPrimary = (in.readInt() != 0); + mWarningSecurityInformation = in.createByteArray(); + } + + /** + * Flatten this object into a Parcel. + * + * @param dest The Parcel in which the object should be written. + * @param flags Additional flags about how the object should be written (ignored). + */ + @Override + public void writeToParcel(Parcel dest, int flags) { + dest.writeInt(mWarningType); + dest.writeInt(mEmergencyUserAlert ? 1 : 0); + dest.writeInt(mActivatePopup ? 1 : 0); + dest.writeInt(mPrimary ? 1 : 0); + dest.writeByteArray(mWarningSecurityInformation); + } + + /** + * Returns the ETWS warning type. + * @return a warning type such as {@link #ETWS_WARNING_TYPE_EARTHQUAKE} + */ + public int getWarningType() { + return mWarningType; + } + + /** + * Returns the ETWS emergency user alert flag. + * @return true to notify terminal to activate emergency user alert; false otherwise + */ + public boolean isEmergencyUserAlert() { + return mEmergencyUserAlert; + } + + /** + * Returns the ETWS activate popup flag. + * @return true to notify terminal to activate display popup; false otherwise + */ + public boolean isPopupAlert() { + return mActivatePopup; + } + + /** + * Returns the ETWS format flag. + * @return true if the message is primary message, otherwise secondary message + */ + public boolean isPrimary() { + return mPrimary; + } + + /** + * Returns the Warning-Security-Information timestamp (GSM primary notifications only). + * As of Release 10, 3GPP TS 23.041 states that the UE shall ignore this value if received. + * @return a UTC timestamp in System.currentTimeMillis() format, or 0 if not present + */ + public long getPrimaryNotificationTimestamp() { + if (mWarningSecurityInformation == null || mWarningSecurityInformation.length < 7) { + return 0; + } + + int year = IccUtils.gsmBcdByteToInt(mWarningSecurityInformation[0]); + int month = IccUtils.gsmBcdByteToInt(mWarningSecurityInformation[1]); + int day = IccUtils.gsmBcdByteToInt(mWarningSecurityInformation[2]); + int hour = IccUtils.gsmBcdByteToInt(mWarningSecurityInformation[3]); + int minute = IccUtils.gsmBcdByteToInt(mWarningSecurityInformation[4]); + int second = IccUtils.gsmBcdByteToInt(mWarningSecurityInformation[5]); + + // For the timezone, the most significant bit of the + // least significant nibble is the sign byte + // (meaning the max range of this field is 79 quarter-hours, + // which is more than enough) + + byte tzByte = mWarningSecurityInformation[6]; + + // Mask out sign bit. + int timezoneOffset = IccUtils.gsmBcdByteToInt((byte) (tzByte & (~0x08))); + + timezoneOffset = ((tzByte & 0x08) == 0) ? timezoneOffset : -timezoneOffset; + + Time time = new Time(Time.TIMEZONE_UTC); + + // We only need to support years above 2000. + time.year = year + 2000; + time.month = month - 1; + time.monthDay = day; + time.hour = hour; + time.minute = minute; + time.second = second; + + // Timezone offset is in quarter hours. + return time.toMillis(true) - timezoneOffset * 15 * 60 * 1000; + } + + /** + * Returns the digital signature (GSM primary notifications only). As of Release 10, + * 3GPP TS 23.041 states that the UE shall ignore this value if received. + * @return a byte array containing a copy of the primary notification digital signature + */ + public byte[] getPrimaryNotificationSignature() { + if (mWarningSecurityInformation == null || mWarningSecurityInformation.length < 50) { + return null; + } + return Arrays.copyOfRange(mWarningSecurityInformation, 7, 50); + } + + @Override + public String toString() { + return "SmsCbEtwsInfo{warningType=" + mWarningType + ", emergencyUserAlert=" + + mEmergencyUserAlert + ", activatePopup=" + mActivatePopup + '}'; + } + + /** + * Describe the kinds of special objects contained in the marshalled representation. + * @return a bitmask indicating this Parcelable contains no special objects + */ + @Override + public int describeContents() { + return 0; + } + + /** Creator for unparcelling objects. */ + public static final Creator<SmsCbEtwsInfo> CREATOR = new Creator<SmsCbEtwsInfo>() { + @Override + public SmsCbEtwsInfo createFromParcel(Parcel in) { + return new SmsCbEtwsInfo(in); + } + + @Override + public SmsCbEtwsInfo[] newArray(int size) { + return new SmsCbEtwsInfo[size]; + } + }; +} diff --git a/android/telephony/SmsCbLocation.java b/android/telephony/SmsCbLocation.java new file mode 100644 index 00000000..6eb72a86 --- /dev/null +++ b/android/telephony/SmsCbLocation.java @@ -0,0 +1,200 @@ +/* + * Copyright (C) 2012 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.telephony; + +import android.os.Parcel; +import android.os.Parcelable; + +/** + * Represents the location and geographical scope of a cell broadcast message. + * For GSM/UMTS, the Location Area and Cell ID are set when the broadcast + * geographical scope is cell wide or Location Area wide. For CDMA, the + * broadcast geographical scope is always PLMN wide. + * + * @hide + */ +public class SmsCbLocation implements Parcelable { + + /** The PLMN. Note that this field may be an empty string, but isn't allowed to be null. */ + private final String mPlmn; + + private final int mLac; + private final int mCid; + + /** + * Construct an empty location object. This is used for some test cases, and for + * cell broadcasts saved in older versions of the database without location info. + */ + public SmsCbLocation() { + mPlmn = ""; + mLac = -1; + mCid = -1; + } + + /** + * Construct a location object for the PLMN. This class is immutable, so + * the same object can be reused for multiple broadcasts. + */ + public SmsCbLocation(String plmn) { + mPlmn = plmn; + mLac = -1; + mCid = -1; + } + + /** + * Construct a location object for the PLMN, LAC, and Cell ID. This class is immutable, so + * the same object can be reused for multiple broadcasts. + */ + public SmsCbLocation(String plmn, int lac, int cid) { + mPlmn = plmn; + mLac = lac; + mCid = cid; + } + + /** + * Initialize the object from a Parcel. + */ + public SmsCbLocation(Parcel in) { + mPlmn = in.readString(); + mLac = in.readInt(); + mCid = in.readInt(); + } + + /** + * Returns the MCC/MNC of the network as a String. + * @return the PLMN identifier (MCC+MNC) as a String + */ + public String getPlmn() { + return mPlmn; + } + + /** + * Returns the GSM location area code, or UMTS service area code. + * @return location area code, -1 if unknown, 0xffff max legal value + */ + public int getLac() { + return mLac; + } + + /** + * Returns the GSM or UMTS cell ID. + * @return gsm cell id, -1 if unknown, 0xffff max legal value + */ + public int getCid() { + return mCid; + } + + @Override + public int hashCode() { + int hash = mPlmn.hashCode(); + hash = hash * 31 + mLac; + hash = hash * 31 + mCid; + return hash; + } + + @Override + public boolean equals(Object o) { + if (o == this) { + return true; + } + if (o == null || !(o instanceof SmsCbLocation)) { + return false; + } + SmsCbLocation other = (SmsCbLocation) o; + return mPlmn.equals(other.mPlmn) && mLac == other.mLac && mCid == other.mCid; + } + + @Override + public String toString() { + return '[' + mPlmn + ',' + mLac + ',' + mCid + ']'; + } + + /** + * Test whether this location is within the location area of the specified object. + * + * @param area the location area to compare with this location + * @return true if this location is contained within the specified location area + */ + public boolean isInLocationArea(SmsCbLocation area) { + if (mCid != -1 && mCid != area.mCid) { + return false; + } + if (mLac != -1 && mLac != area.mLac) { + return false; + } + return mPlmn.equals(area.mPlmn); + } + + /** + * Test whether this location is within the location area of the CellLocation. + * + * @param plmn the PLMN to use for comparison + * @param lac the Location Area (GSM) or Service Area (UMTS) to compare with + * @param cid the Cell ID to compare with + * @return true if this location is contained within the specified PLMN, LAC, and Cell ID + */ + public boolean isInLocationArea(String plmn, int lac, int cid) { + if (!mPlmn.equals(plmn)) { + return false; + } + + if (mLac != -1 && mLac != lac) { + return false; + } + + if (mCid != -1 && mCid != cid) { + return false; + } + + return true; + } + + /** + * Flatten this object into a Parcel. + * + * @param dest The Parcel in which the object should be written. + * @param flags Additional flags about how the object should be written (ignored). + */ + @Override + public void writeToParcel(Parcel dest, int flags) { + dest.writeString(mPlmn); + dest.writeInt(mLac); + dest.writeInt(mCid); + } + + public static final Parcelable.Creator<SmsCbLocation> CREATOR + = new Parcelable.Creator<SmsCbLocation>() { + @Override + public SmsCbLocation createFromParcel(Parcel in) { + return new SmsCbLocation(in); + } + + @Override + public SmsCbLocation[] newArray(int size) { + return new SmsCbLocation[size]; + } + }; + + /** + * Describe the kinds of special objects contained in the marshalled representation. + * @return a bitmask indicating this Parcelable contains no special objects + */ + @Override + public int describeContents() { + return 0; + } +} diff --git a/android/telephony/SmsCbMessage.java b/android/telephony/SmsCbMessage.java new file mode 100644 index 00000000..046bf8c7 --- /dev/null +++ b/android/telephony/SmsCbMessage.java @@ -0,0 +1,382 @@ +/* + * Copyright (C) 2010 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.telephony; + +import android.os.Parcel; +import android.os.Parcelable; + +/** + * Parcelable object containing a received cell broadcast message. There are four different types + * of Cell Broadcast messages: + * + * <ul> + * <li>opt-in informational broadcasts, e.g. news, weather, stock quotes, sports scores</li> + * <li>cell information messages, broadcast on channel 50, indicating the current cell name for + * roaming purposes (required to display on the idle screen in Brazil)</li> + * <li>emergency broadcasts for the Japanese Earthquake and Tsunami Warning System (ETWS)</li> + * <li>emergency broadcasts for the American Commercial Mobile Alert Service (CMAS)</li> + * </ul> + * + * <p>There are also four different CB message formats: GSM, ETWS Primary Notification (GSM only), + * UMTS, and CDMA. Some fields are only applicable for some message formats. Other fields were + * unified under a common name, avoiding some names, such as "Message Identifier", that refer to + * two completely different concepts in 3GPP and CDMA. + * + * <p>The GSM/UMTS Message Identifier field is available via {@link #getServiceCategory}, the name + * of the equivalent field in CDMA. In both cases the service category is a 16-bit value, but 3GPP + * and 3GPP2 have completely different meanings for the respective values. For ETWS and CMAS, the + * application should + * + * <p>The CDMA Message Identifier field is available via {@link #getSerialNumber}, which is used + * to detect the receipt of a duplicate message to be discarded. In CDMA, the message ID is + * unique to the current PLMN. In GSM/UMTS, there is a 16-bit serial number containing a 2-bit + * Geographical Scope field which indicates whether the 10-bit message code and 4-bit update number + * are considered unique to the PLMN, to the current cell, or to the current Location Area (or + * Service Area in UMTS). The relevant values are concatenated into a single String which will be + * unique if the messages are not duplicates. + * + * <p>The SMS dispatcher does not detect duplicate messages. However, it does concatenate the + * pages of a GSM multi-page cell broadcast into a single SmsCbMessage object. + * + * <p>Interested applications with {@code RECEIVE_SMS_PERMISSION} can register to receive + * {@code SMS_CB_RECEIVED_ACTION} broadcast intents for incoming non-emergency broadcasts. + * Only system applications such as the CellBroadcastReceiver may receive notifications for + * emergency broadcasts (ETWS and CMAS). This is intended to prevent any potential for delays or + * interference with the immediate display of the alert message and playing of the alert sound and + * vibration pattern, which could be caused by poorly written or malicious non-system code. + * + * @hide + */ +public class SmsCbMessage implements Parcelable { + + protected static final String LOG_TAG = "SMSCB"; + + /** Cell wide geographical scope with immediate display (GSM/UMTS only). */ + public static final int GEOGRAPHICAL_SCOPE_CELL_WIDE_IMMEDIATE = 0; + + /** PLMN wide geographical scope (GSM/UMTS and all CDMA broadcasts). */ + public static final int GEOGRAPHICAL_SCOPE_PLMN_WIDE = 1; + + /** Location / service area wide geographical scope (GSM/UMTS only). */ + public static final int GEOGRAPHICAL_SCOPE_LA_WIDE = 2; + + /** Cell wide geographical scope (GSM/UMTS only). */ + public static final int GEOGRAPHICAL_SCOPE_CELL_WIDE = 3; + + /** GSM or UMTS format cell broadcast. */ + public static final int MESSAGE_FORMAT_3GPP = 1; + + /** CDMA format cell broadcast. */ + public static final int MESSAGE_FORMAT_3GPP2 = 2; + + /** Normal message priority. */ + public static final int MESSAGE_PRIORITY_NORMAL = 0; + + /** Interactive message priority. */ + public static final int MESSAGE_PRIORITY_INTERACTIVE = 1; + + /** Urgent message priority. */ + public static final int MESSAGE_PRIORITY_URGENT = 2; + + /** Emergency message priority. */ + public static final int MESSAGE_PRIORITY_EMERGENCY = 3; + + /** Format of this message (for interpretation of service category values). */ + private final int mMessageFormat; + + /** Geographical scope of broadcast. */ + private final int mGeographicalScope; + + /** + * Serial number of broadcast (message identifier for CDMA, geographical scope + message code + + * update number for GSM/UMTS). The serial number plus the location code uniquely identify + * a cell broadcast for duplicate detection. + */ + private final int mSerialNumber; + + /** + * Location identifier for this message. It consists of the current operator MCC/MNC as a + * 5 or 6-digit decimal string. In addition, for GSM/UMTS, if the Geographical Scope of the + * message is not binary 01, the Location Area is included for comparison. If the GS is + * 00 or 11, the Cell ID is also included. LAC and Cell ID are -1 if not specified. + */ + private final SmsCbLocation mLocation; + + /** + * 16-bit CDMA service category or GSM/UMTS message identifier. For ETWS and CMAS warnings, + * the information provided by the category is also available via {@link #getEtwsWarningInfo()} + * or {@link #getCmasWarningInfo()}. + */ + private final int mServiceCategory; + + /** Message language, as a two-character string, e.g. "en". */ + private final String mLanguage; + + /** Message body, as a String. */ + private final String mBody; + + /** Message priority (including emergency priority). */ + private final int mPriority; + + /** ETWS warning notification information (ETWS warnings only). */ + private final SmsCbEtwsInfo mEtwsWarningInfo; + + /** CMAS warning notification information (CMAS warnings only). */ + private final SmsCbCmasInfo mCmasWarningInfo; + + /** + * Create a new SmsCbMessage with the specified data. + */ + public SmsCbMessage(int messageFormat, int geographicalScope, int serialNumber, + SmsCbLocation location, int serviceCategory, String language, String body, + int priority, SmsCbEtwsInfo etwsWarningInfo, SmsCbCmasInfo cmasWarningInfo) { + mMessageFormat = messageFormat; + mGeographicalScope = geographicalScope; + mSerialNumber = serialNumber; + mLocation = location; + mServiceCategory = serviceCategory; + mLanguage = language; + mBody = body; + mPriority = priority; + mEtwsWarningInfo = etwsWarningInfo; + mCmasWarningInfo = cmasWarningInfo; + } + + /** Create a new SmsCbMessage object from a Parcel. */ + public SmsCbMessage(Parcel in) { + mMessageFormat = in.readInt(); + mGeographicalScope = in.readInt(); + mSerialNumber = in.readInt(); + mLocation = new SmsCbLocation(in); + mServiceCategory = in.readInt(); + mLanguage = in.readString(); + mBody = in.readString(); + mPriority = in.readInt(); + int type = in.readInt(); + switch (type) { + case 'E': + // unparcel ETWS warning information + mEtwsWarningInfo = new SmsCbEtwsInfo(in); + mCmasWarningInfo = null; + break; + + case 'C': + // unparcel CMAS warning information + mEtwsWarningInfo = null; + mCmasWarningInfo = new SmsCbCmasInfo(in); + break; + + default: + mEtwsWarningInfo = null; + mCmasWarningInfo = null; + } + } + + /** + * Flatten this object into a Parcel. + * + * @param dest The Parcel in which the object should be written. + * @param flags Additional flags about how the object should be written (ignored). + */ + @Override + public void writeToParcel(Parcel dest, int flags) { + dest.writeInt(mMessageFormat); + dest.writeInt(mGeographicalScope); + dest.writeInt(mSerialNumber); + mLocation.writeToParcel(dest, flags); + dest.writeInt(mServiceCategory); + dest.writeString(mLanguage); + dest.writeString(mBody); + dest.writeInt(mPriority); + if (mEtwsWarningInfo != null) { + // parcel ETWS warning information + dest.writeInt('E'); + mEtwsWarningInfo.writeToParcel(dest, flags); + } else if (mCmasWarningInfo != null) { + // parcel CMAS warning information + dest.writeInt('C'); + mCmasWarningInfo.writeToParcel(dest, flags); + } else { + // no ETWS or CMAS warning information + dest.writeInt('0'); + } + } + + public static final Parcelable.Creator<SmsCbMessage> CREATOR + = new Parcelable.Creator<SmsCbMessage>() { + @Override + public SmsCbMessage createFromParcel(Parcel in) { + return new SmsCbMessage(in); + } + + @Override + public SmsCbMessage[] newArray(int size) { + return new SmsCbMessage[size]; + } + }; + + /** + * Return the geographical scope of this message (GSM/UMTS only). + * + * @return Geographical scope + */ + public int getGeographicalScope() { + return mGeographicalScope; + } + + /** + * Return the broadcast serial number of broadcast (message identifier for CDMA, or + * geographical scope + message code + update number for GSM/UMTS). The serial number plus + * the location code uniquely identify a cell broadcast for duplicate detection. + * + * @return the 16-bit CDMA message identifier or GSM/UMTS serial number + */ + public int getSerialNumber() { + return mSerialNumber; + } + + /** + * Return the location identifier for this message, consisting of the MCC/MNC as a + * 5 or 6-digit decimal string. In addition, for GSM/UMTS, if the Geographical Scope of the + * message is not binary 01, the Location Area is included. If the GS is 00 or 11, the + * cell ID is also included. The {@link SmsCbLocation} object includes a method to test + * if the location is included within another location area or within a PLMN and CellLocation. + * + * @return the geographical location code for duplicate message detection + */ + public SmsCbLocation getLocation() { + return mLocation; + } + + /** + * Return the 16-bit CDMA service category or GSM/UMTS message identifier. The interpretation + * of the category is radio technology specific. For ETWS and CMAS warnings, the information + * provided by the category is available via {@link #getEtwsWarningInfo()} or + * {@link #getCmasWarningInfo()} in a radio technology independent format. + * + * @return the radio technology specific service category + */ + public int getServiceCategory() { + return mServiceCategory; + } + + /** + * Get the ISO-639-1 language code for this message, or null if unspecified + * + * @return Language code + */ + public String getLanguageCode() { + return mLanguage; + } + + /** + * Get the body of this message, or null if no body available + * + * @return Body, or null + */ + public String getMessageBody() { + return mBody; + } + + /** + * Get the message format ({@link #MESSAGE_FORMAT_3GPP} or {@link #MESSAGE_FORMAT_3GPP2}). + * @return an integer representing 3GPP or 3GPP2 message format + */ + public int getMessageFormat() { + return mMessageFormat; + } + + /** + * Get the message priority. Normal broadcasts return {@link #MESSAGE_PRIORITY_NORMAL} + * and emergency broadcasts return {@link #MESSAGE_PRIORITY_EMERGENCY}. CDMA also may return + * {@link #MESSAGE_PRIORITY_INTERACTIVE} or {@link #MESSAGE_PRIORITY_URGENT}. + * @return an integer representing the message priority + */ + public int getMessagePriority() { + return mPriority; + } + + /** + * If this is an ETWS warning notification then this method will return an object containing + * the ETWS warning type, the emergency user alert flag, and the popup flag. If this is an + * ETWS primary notification (GSM only), there will also be a 7-byte timestamp and 43-byte + * digital signature. As of Release 10, 3GPP TS 23.041 states that the UE shall ignore the + * ETWS primary notification timestamp and digital signature if received. + * + * @return an SmsCbEtwsInfo object, or null if this is not an ETWS warning notification + */ + public SmsCbEtwsInfo getEtwsWarningInfo() { + return mEtwsWarningInfo; + } + + /** + * If this is a CMAS warning notification then this method will return an object containing + * the CMAS message class, category, response type, severity, urgency and certainty. + * The message class is always present. Severity, urgency and certainty are present for CDMA + * warning notifications containing a type 1 elements record and for GSM and UMTS warnings + * except for the Presidential-level alert category. Category and response type are only + * available for CDMA notifications containing a type 1 elements record. + * + * @return an SmsCbCmasInfo object, or null if this is not a CMAS warning notification + */ + public SmsCbCmasInfo getCmasWarningInfo() { + return mCmasWarningInfo; + } + + /** + * Return whether this message is an emergency (PWS) message type. + * @return true if the message is a public warning notification; false otherwise + */ + public boolean isEmergencyMessage() { + return mPriority == MESSAGE_PRIORITY_EMERGENCY; + } + + /** + * Return whether this message is an ETWS warning alert. + * @return true if the message is an ETWS warning notification; false otherwise + */ + public boolean isEtwsMessage() { + return mEtwsWarningInfo != null; + } + + /** + * Return whether this message is a CMAS warning alert. + * @return true if the message is a CMAS warning notification; false otherwise + */ + public boolean isCmasMessage() { + return mCmasWarningInfo != null; + } + + @Override + public String toString() { + return "SmsCbMessage{geographicalScope=" + mGeographicalScope + ", serialNumber=" + + mSerialNumber + ", location=" + mLocation + ", serviceCategory=" + + mServiceCategory + ", language=" + mLanguage + ", body=" + mBody + + ", priority=" + mPriority + + (mEtwsWarningInfo != null ? (", " + mEtwsWarningInfo.toString()) : "") + + (mCmasWarningInfo != null ? (", " + mCmasWarningInfo.toString()) : "") + '}'; + } + + /** + * Describe the kinds of special objects contained in the marshalled representation. + * @return a bitmask indicating this Parcelable contains no special objects + */ + @Override + public int describeContents() { + return 0; + } +} diff --git a/android/telephony/SmsManager.java b/android/telephony/SmsManager.java new file mode 100644 index 00000000..6029995f --- /dev/null +++ b/android/telephony/SmsManager.java @@ -0,0 +1,1714 @@ +/* + * Copyright (C) 2008 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.telephony; + +import android.annotation.RequiresPermission; +import android.annotation.SystemApi; +import android.app.ActivityThread; +import android.app.PendingIntent; +import android.content.ActivityNotFoundException; +import android.content.ContentValues; +import android.content.Context; +import android.content.Intent; +import android.net.Uri; +import android.os.BaseBundle; +import android.os.Bundle; +import android.os.RemoteException; +import android.os.ServiceManager; +import android.text.TextUtils; +import android.util.ArrayMap; +import android.util.Log; + +import com.android.internal.telephony.IMms; +import com.android.internal.telephony.ISms; +import com.android.internal.telephony.SmsRawData; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; +import java.util.Map; + +/* + * TODO(code review): Curious question... Why are a lot of these + * methods not declared as static, since they do not seem to require + * any local object state? Presumably this cannot be changed without + * interfering with the API... + */ + +/** + * Manages SMS operations such as sending data, text, and pdu SMS messages. + * Get this object by calling the static method {@link #getDefault()}. + * + * <p>For information about how to behave as the default SMS app on Android 4.4 (API level 19) + * and higher, see {@link android.provider.Telephony}. + */ +public final class SmsManager { + private static final String TAG = "SmsManager"; + /** + * A psuedo-subId that represents the default subId at any given time. The actual subId it + * represents changes as the default subId is changed. + */ + private static final int DEFAULT_SUBSCRIPTION_ID = -1002; + + /** Singleton object constructed during class initialization. */ + private static final SmsManager sInstance = new SmsManager(DEFAULT_SUBSCRIPTION_ID); + private static final Object sLockObject = new Object(); + + /** @hide */ + public static final int CELL_BROADCAST_RAN_TYPE_GSM = 0; + /** @hide */ + public static final int CELL_BROADCAST_RAN_TYPE_CDMA = 1; + + /** SMS record length from TS 51.011 10.5.3 + * @hide + */ + public static final int SMS_RECORD_LENGTH = 176; + + /** SMS record length from C.S0023 3.4.27 + * @hide + */ + public static final int CDMA_SMS_RECORD_LENGTH = 255; + + private static final Map<Integer, SmsManager> sSubInstances = + new ArrayMap<Integer, SmsManager>(); + + /** A concrete subscription id, or the pseudo DEFAULT_SUBSCRIPTION_ID */ + private int mSubId; + + /* + * Key for the various carrier-dependent configuration values. + * Some of the values are used by the system in processing SMS or MMS messages. Others + * are provided for the convenience of SMS applications. + */ + + /** + * Whether to append transaction id to MMS WAP Push M-Notification.ind's content location URI + * when constructing the download URL of a new MMS (boolean type) + */ + public static final String MMS_CONFIG_APPEND_TRANSACTION_ID = + CarrierConfigManager.KEY_MMS_APPEND_TRANSACTION_ID_BOOL; + /** + * Whether MMS is enabled for the current carrier (boolean type) + */ + public static final String + MMS_CONFIG_MMS_ENABLED = CarrierConfigManager.KEY_MMS_MMS_ENABLED_BOOL; + /** + * Whether group MMS is enabled for the current carrier (boolean type) + */ + public static final String + MMS_CONFIG_GROUP_MMS_ENABLED = CarrierConfigManager.KEY_MMS_GROUP_MMS_ENABLED_BOOL; + /** + * If this is enabled, M-NotifyResp.ind should be sent to the WAP Push content location instead + * of the default MMSC (boolean type) + */ + public static final String MMS_CONFIG_NOTIFY_WAP_MMSC_ENABLED = + CarrierConfigManager.KEY_MMS_NOTIFY_WAP_MMSC_ENABLED_BOOL; + /** + * Whether alias is enabled (boolean type) + */ + public static final String + MMS_CONFIG_ALIAS_ENABLED = CarrierConfigManager.KEY_MMS_ALIAS_ENABLED_BOOL; + /** + * Whether audio is allowed to be attached for MMS messages (boolean type) + */ + public static final String + MMS_CONFIG_ALLOW_ATTACH_AUDIO = CarrierConfigManager.KEY_MMS_ALLOW_ATTACH_AUDIO_BOOL; + /** + * Whether multipart SMS is enabled (boolean type) + */ + public static final String MMS_CONFIG_MULTIPART_SMS_ENABLED = + CarrierConfigManager.KEY_MMS_MULTIPART_SMS_ENABLED_BOOL; + /** + * Whether SMS delivery report is enabled (boolean type) + */ + public static final String MMS_CONFIG_SMS_DELIVERY_REPORT_ENABLED = + CarrierConfigManager.KEY_MMS_SMS_DELIVERY_REPORT_ENABLED_BOOL; + /** + * Whether content-disposition field should be expected in an MMS PDU (boolean type) + */ + public static final String MMS_CONFIG_SUPPORT_MMS_CONTENT_DISPOSITION = + CarrierConfigManager.KEY_MMS_SUPPORT_MMS_CONTENT_DISPOSITION_BOOL; + /** + * Whether multipart SMS should be sent as separate messages + */ + public static final String MMS_CONFIG_SEND_MULTIPART_SMS_AS_SEPARATE_MESSAGES = + CarrierConfigManager.KEY_MMS_SEND_MULTIPART_SMS_AS_SEPARATE_MESSAGES_BOOL; + /** + * Whether MMS read report is enabled (boolean type) + */ + public static final String MMS_CONFIG_MMS_READ_REPORT_ENABLED = + CarrierConfigManager.KEY_MMS_MMS_READ_REPORT_ENABLED_BOOL; + /** + * Whether MMS delivery report is enabled (boolean type) + */ + public static final String MMS_CONFIG_MMS_DELIVERY_REPORT_ENABLED = + CarrierConfigManager.KEY_MMS_MMS_DELIVERY_REPORT_ENABLED_BOOL; + /** + * Max MMS message size in bytes (int type) + */ + public static final String + MMS_CONFIG_MAX_MESSAGE_SIZE = CarrierConfigManager.KEY_MMS_MAX_MESSAGE_SIZE_INT; + /** + * Max MMS image width (int type) + */ + public static final String + MMS_CONFIG_MAX_IMAGE_WIDTH = CarrierConfigManager.KEY_MMS_MAX_IMAGE_WIDTH_INT; + /** + * Max MMS image height (int type) + */ + public static final String + MMS_CONFIG_MAX_IMAGE_HEIGHT = CarrierConfigManager.KEY_MMS_MAX_IMAGE_HEIGHT_INT; + /** + * Limit of recipients of MMS messages (int type) + */ + public static final String + MMS_CONFIG_RECIPIENT_LIMIT = CarrierConfigManager.KEY_MMS_RECIPIENT_LIMIT_INT; + /** + * Min alias character count (int type) + */ + public static final String + MMS_CONFIG_ALIAS_MIN_CHARS = CarrierConfigManager.KEY_MMS_ALIAS_MIN_CHARS_INT; + /** + * Max alias character count (int type) + */ + public static final String + MMS_CONFIG_ALIAS_MAX_CHARS = CarrierConfigManager.KEY_MMS_ALIAS_MAX_CHARS_INT; + /** + * When the number of parts of a multipart SMS reaches this threshold, it should be converted + * into an MMS (int type) + */ + public static final String MMS_CONFIG_SMS_TO_MMS_TEXT_THRESHOLD = + CarrierConfigManager.KEY_MMS_SMS_TO_MMS_TEXT_THRESHOLD_INT; + /** + * Some carriers require SMS to be converted into MMS when text length reaches this threshold + * (int type) + */ + public static final String MMS_CONFIG_SMS_TO_MMS_TEXT_LENGTH_THRESHOLD = + CarrierConfigManager.KEY_MMS_SMS_TO_MMS_TEXT_LENGTH_THRESHOLD_INT; + /** + * Max message text size (int type) + */ + public static final String MMS_CONFIG_MESSAGE_TEXT_MAX_SIZE = + CarrierConfigManager.KEY_MMS_MESSAGE_TEXT_MAX_SIZE_INT; + /** + * Max message subject length (int type) + */ + public static final String + MMS_CONFIG_SUBJECT_MAX_LENGTH = CarrierConfigManager.KEY_MMS_SUBJECT_MAX_LENGTH_INT; + /** + * MMS HTTP socket timeout in milliseconds (int type) + */ + public static final String + MMS_CONFIG_HTTP_SOCKET_TIMEOUT = CarrierConfigManager.KEY_MMS_HTTP_SOCKET_TIMEOUT_INT; + /** + * The name of the UA Prof URL HTTP header for MMS HTTP request (String type) + */ + public static final String + MMS_CONFIG_UA_PROF_TAG_NAME = CarrierConfigManager.KEY_MMS_UA_PROF_TAG_NAME_STRING; + /** + * The User-Agent header value for MMS HTTP request (String type) + */ + public static final String + MMS_CONFIG_USER_AGENT = CarrierConfigManager.KEY_MMS_USER_AGENT_STRING; + /** + * The UA Profile URL header value for MMS HTTP request (String type) + */ + public static final String + MMS_CONFIG_UA_PROF_URL = CarrierConfigManager.KEY_MMS_UA_PROF_URL_STRING; + /** + * A list of HTTP headers to add to MMS HTTP request, separated by "|" (String type) + */ + public static final String + MMS_CONFIG_HTTP_PARAMS = CarrierConfigManager.KEY_MMS_HTTP_PARAMS_STRING; + /** + * Email gateway number (String type) + */ + public static final String MMS_CONFIG_EMAIL_GATEWAY_NUMBER = + CarrierConfigManager.KEY_MMS_EMAIL_GATEWAY_NUMBER_STRING; + /** + * The suffix to append to the NAI header value for MMS HTTP request (String type) + */ + public static final String + MMS_CONFIG_NAI_SUFFIX = CarrierConfigManager.KEY_MMS_NAI_SUFFIX_STRING; + /** + * If true, show the cell broadcast (amber alert) in the SMS settings. Some carriers don't want + * this shown. (Boolean type) + */ + public static final String MMS_CONFIG_SHOW_CELL_BROADCAST_APP_LINKS = + CarrierConfigManager.KEY_MMS_SHOW_CELL_BROADCAST_APP_LINKS_BOOL; + /** + * Whether the carrier MMSC supports charset field in Content-Type header. If this is false, + * then we don't add "charset" to "Content-Type" + */ + public static final String MMS_CONFIG_SUPPORT_HTTP_CHARSET_HEADER = + CarrierConfigManager.KEY_MMS_SUPPORT_HTTP_CHARSET_HEADER_BOOL; + /** + * If true, add "Connection: close" header to MMS HTTP requests so the connection + * is immediately closed (disabling keep-alive). (Boolean type) + * @hide + */ + public static final String MMS_CONFIG_CLOSE_CONNECTION = + CarrierConfigManager.KEY_MMS_CLOSE_CONNECTION_BOOL; + + /* + * Forwarded constants from SimDialogActivity. + */ + private static String DIALOG_TYPE_KEY = "dialog_type"; + private static final int SMS_PICK = 2; + + /** + * Send a text based SMS. + * + * <p class="note"><strong>Note:</strong> Using this method requires that your app has the + * {@link android.Manifest.permission#SEND_SMS} permission.</p> + * + * <p class="note"><strong>Note:</strong> Beginning with Android 4.4 (API level 19), if + * <em>and only if</em> an app is not selected as the default SMS app, the system automatically + * writes messages sent using this method to the SMS Provider (the default SMS app is always + * responsible for writing its sent messages to the SMS Provider). For information about + * how to behave as the default SMS app, see {@link android.provider.Telephony}.</p> + * + * + * @param destinationAddress the address to send the message to + * @param scAddress is the service center address or null to use + * the current default SMSC + * @param text the body of the message to send + * @param sentIntent if not NULL this <code>PendingIntent</code> is + * broadcast when the message is successfully sent, or failed. + * The result code will be <code>Activity.RESULT_OK</code> for success, + * or one of these errors:<br> + * <code>RESULT_ERROR_GENERIC_FAILURE</code><br> + * <code>RESULT_ERROR_RADIO_OFF</code><br> + * <code>RESULT_ERROR_NULL_PDU</code><br> + * For <code>RESULT_ERROR_GENERIC_FAILURE</code> the sentIntent may include + * the extra "errorCode" containing a radio technology specific value, + * generally only useful for troubleshooting.<br> + * The per-application based SMS control checks sentIntent. If sentIntent + * is NULL the caller will be checked against all unknown applications, + * which cause smaller number of SMS to be sent in checking period. + * @param deliveryIntent if not NULL this <code>PendingIntent</code> is + * broadcast when the message is delivered to the recipient. The + * raw pdu of the status report is in the extended data ("pdu"). + * + * @throws IllegalArgumentException if destinationAddress or text are empty + */ + public void sendTextMessage( + String destinationAddress, String scAddress, String text, + PendingIntent sentIntent, PendingIntent deliveryIntent) { + sendTextMessageInternal(destinationAddress, scAddress, text, sentIntent, deliveryIntent, + true /* persistMessage*/); + } + + private void sendTextMessageInternal(String destinationAddress, String scAddress, + String text, PendingIntent sentIntent, PendingIntent deliveryIntent, + boolean persistMessage) { + if (TextUtils.isEmpty(destinationAddress)) { + throw new IllegalArgumentException("Invalid destinationAddress"); + } + + if (TextUtils.isEmpty(text)) { + throw new IllegalArgumentException("Invalid message body"); + } + + try { + ISms iccISms = getISmsServiceOrThrow(); + iccISms.sendTextForSubscriber(getSubscriptionId(), ActivityThread.currentPackageName(), + destinationAddress, + scAddress, text, sentIntent, deliveryIntent, + persistMessage); + } catch (RemoteException ex) { + // ignore it + } + } + + /** + * Send a text based SMS without writing it into the SMS Provider. + * + * <p>Requires Permission: + * {@link android.Manifest.permission#MODIFY_PHONE_STATE} or the calling app has carrier + * privileges. + * </p> + * + * @see #sendTextMessage(String, String, String, PendingIntent, PendingIntent) + * @hide + */ + @SystemApi + @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) + public void sendTextMessageWithoutPersisting( + String destinationAddress, String scAddress, String text, + PendingIntent sentIntent, PendingIntent deliveryIntent) { + sendTextMessageInternal(destinationAddress, scAddress, text, sentIntent, deliveryIntent, + false /* persistMessage */); + } + + /** + * A variant of {@link SmsManager#sendTextMessage} that allows self to be the caller. This is + * for internal use only. + * + * @param persistMessage whether to persist the sent message in the SMS app. the caller must be + * the Phone process if set to false. + * + * @hide + */ + public void sendTextMessageWithSelfPermissions( + String destinationAddress, String scAddress, String text, + PendingIntent sentIntent, PendingIntent deliveryIntent, boolean persistMessage) { + if (TextUtils.isEmpty(destinationAddress)) { + throw new IllegalArgumentException("Invalid destinationAddress"); + } + + if (TextUtils.isEmpty(text)) { + throw new IllegalArgumentException("Invalid message body"); + } + + try { + ISms iccISms = getISmsServiceOrThrow(); + iccISms.sendTextForSubscriberWithSelfPermissions(getSubscriptionId(), + ActivityThread.currentPackageName(), + destinationAddress, + scAddress, text, sentIntent, deliveryIntent, persistMessage); + } catch (RemoteException ex) { + // ignore it + } + } + + /** + * Inject an SMS PDU into the android application framework. + * + * <p>Requires permission: {@link android.Manifest.permission#MODIFY_PHONE_STATE} or carrier + * privileges. @see android.telephony.TelephonyManager#hasCarrierPrivileges + * + * @param pdu is the byte array of pdu to be injected into android application framework + * @param format is the format of SMS pdu (3gpp or 3gpp2) + * @param receivedIntent if not NULL this <code>PendingIntent</code> is + * broadcast when the message is successfully received by the + * android application framework, or failed. This intent is broadcasted at + * the same time an SMS received from radio is acknowledged back. + * The result code will be <code>RESULT_SMS_HANDLED</code> for success, or + * <code>RESULT_SMS_GENERIC_ERROR</code> for error. + * + * @throws IllegalArgumentException if format is not one of 3gpp and 3gpp2. + */ + public void injectSmsPdu(byte[] pdu, String format, PendingIntent receivedIntent) { + if (!format.equals(SmsMessage.FORMAT_3GPP) && !format.equals(SmsMessage.FORMAT_3GPP2)) { + // Format must be either 3gpp or 3gpp2. + throw new IllegalArgumentException( + "Invalid pdu format. format must be either 3gpp or 3gpp2"); + } + try { + ISms iccISms = ISms.Stub.asInterface(ServiceManager.getService("isms")); + if (iccISms != null) { + iccISms.injectSmsPduForSubscriber( + getSubscriptionId(), pdu, format, receivedIntent); + } + } catch (RemoteException ex) { + // ignore it + } + } + + /** + * Divide a message text into several fragments, none bigger than + * the maximum SMS message size. + * + * @param text the original message. Must not be null. + * @return an <code>ArrayList</code> of strings that, in order, + * comprise the original message + * + * @throws IllegalArgumentException if text is null + */ + public ArrayList<String> divideMessage(String text) { + if (null == text) { + throw new IllegalArgumentException("text is null"); + } + return SmsMessage.fragmentText(text); + } + + /** + * Send a multi-part text based SMS. The callee should have already + * divided the message into correctly sized parts by calling + * <code>divideMessage</code>. + * + * <p class="note"><strong>Note:</strong> Using this method requires that your app has the + * {@link android.Manifest.permission#SEND_SMS} permission.</p> + * + * <p class="note"><strong>Note:</strong> Beginning with Android 4.4 (API level 19), if + * <em>and only if</em> an app is not selected as the default SMS app, the system automatically + * writes messages sent using this method to the SMS Provider (the default SMS app is always + * responsible for writing its sent messages to the SMS Provider). For information about + * how to behave as the default SMS app, see {@link android.provider.Telephony}.</p> + * + * @param destinationAddress the address to send the message to + * @param scAddress is the service center address or null to use + * the current default SMSC + * @param parts an <code>ArrayList</code> of strings that, in order, + * comprise the original message + * @param sentIntents if not null, an <code>ArrayList</code> of + * <code>PendingIntent</code>s (one for each message part) that is + * broadcast when the corresponding message part has been sent. + * The result code will be <code>Activity.RESULT_OK</code> for success, + * or one of these errors:<br> + * <code>RESULT_ERROR_GENERIC_FAILURE</code><br> + * <code>RESULT_ERROR_RADIO_OFF</code><br> + * <code>RESULT_ERROR_NULL_PDU</code><br> + * For <code>RESULT_ERROR_GENERIC_FAILURE</code> each sentIntent may include + * the extra "errorCode" containing a radio technology specific value, + * generally only useful for troubleshooting.<br> + * The per-application based SMS control checks sentIntent. If sentIntent + * is NULL the caller will be checked against all unknown applications, + * which cause smaller number of SMS to be sent in checking period. + * @param deliveryIntents if not null, an <code>ArrayList</code> of + * <code>PendingIntent</code>s (one for each message part) that is + * broadcast when the corresponding message part has been delivered + * to the recipient. The raw pdu of the status report is in the + * extended data ("pdu"). + * + * @throws IllegalArgumentException if destinationAddress or data are empty + */ + public void sendMultipartTextMessage( + String destinationAddress, String scAddress, ArrayList<String> parts, + ArrayList<PendingIntent> sentIntents, ArrayList<PendingIntent> deliveryIntents) { + sendMultipartTextMessageInternal(destinationAddress, scAddress, parts, sentIntents, + deliveryIntents, true /* persistMessage*/); + } + + private void sendMultipartTextMessageInternal( + String destinationAddress, String scAddress, List<String> parts, + List<PendingIntent> sentIntents, List<PendingIntent> deliveryIntents, + boolean persistMessage) { + if (TextUtils.isEmpty(destinationAddress)) { + throw new IllegalArgumentException("Invalid destinationAddress"); + } + if (parts == null || parts.size() < 1) { + throw new IllegalArgumentException("Invalid message body"); + } + + if (parts.size() > 1) { + try { + ISms iccISms = getISmsServiceOrThrow(); + iccISms.sendMultipartTextForSubscriber(getSubscriptionId(), + ActivityThread.currentPackageName(), + destinationAddress, scAddress, parts, + sentIntents, deliveryIntents, persistMessage); + } catch (RemoteException ex) { + // ignore it + } + } else { + PendingIntent sentIntent = null; + PendingIntent deliveryIntent = null; + if (sentIntents != null && sentIntents.size() > 0) { + sentIntent = sentIntents.get(0); + } + if (deliveryIntents != null && deliveryIntents.size() > 0) { + deliveryIntent = deliveryIntents.get(0); + } + sendTextMessage(destinationAddress, scAddress, parts.get(0), + sentIntent, deliveryIntent); + } + } + + /** + * Send a multi-part text based SMS without writing it into the SMS Provider. + * + * <p>Requires Permission: + * {@link android.Manifest.permission#MODIFY_PHONE_STATE} or the calling app has carrier + * privileges. + * </p> + * + * @see #sendMultipartTextMessage(String, String, ArrayList, ArrayList, ArrayList) + * @hide + **/ + @SystemApi + @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) + public void sendMultipartTextMessageWithoutPersisting( + String destinationAddress, String scAddress, List<String> parts, + List<PendingIntent> sentIntents, List<PendingIntent> deliveryIntents) { + sendMultipartTextMessageInternal(destinationAddress, scAddress, parts, sentIntents, + deliveryIntents, false /* persistMessage*/); + } + + /** + * Send a data based SMS to a specific application port. + * + * <p class="note"><strong>Note:</strong> Using this method requires that your app has the + * {@link android.Manifest.permission#SEND_SMS} permission.</p> + * + * @param destinationAddress the address to send the message to + * @param scAddress is the service center address or null to use + * the current default SMSC + * @param destinationPort the port to deliver the message to + * @param data the body of the message to send + * @param sentIntent if not NULL this <code>PendingIntent</code> is + * broadcast when the message is successfully sent, or failed. + * The result code will be <code>Activity.RESULT_OK</code> for success, + * or one of these errors:<br> + * <code>RESULT_ERROR_GENERIC_FAILURE</code><br> + * <code>RESULT_ERROR_RADIO_OFF</code><br> + * <code>RESULT_ERROR_NULL_PDU</code><br> + * For <code>RESULT_ERROR_GENERIC_FAILURE</code> the sentIntent may include + * the extra "errorCode" containing a radio technology specific value, + * generally only useful for troubleshooting.<br> + * The per-application based SMS control checks sentIntent. If sentIntent + * is NULL the caller will be checked against all unknown applications, + * which cause smaller number of SMS to be sent in checking period. + * @param deliveryIntent if not NULL this <code>PendingIntent</code> is + * broadcast when the message is delivered to the recipient. The + * raw pdu of the status report is in the extended data ("pdu"). + * + * @throws IllegalArgumentException if destinationAddress or data are empty + */ + public void sendDataMessage( + String destinationAddress, String scAddress, short destinationPort, + byte[] data, PendingIntent sentIntent, PendingIntent deliveryIntent) { + if (TextUtils.isEmpty(destinationAddress)) { + throw new IllegalArgumentException("Invalid destinationAddress"); + } + + if (data == null || data.length == 0) { + throw new IllegalArgumentException("Invalid message data"); + } + + try { + ISms iccISms = getISmsServiceOrThrow(); + iccISms.sendDataForSubscriber(getSubscriptionId(), ActivityThread.currentPackageName(), + destinationAddress, scAddress, destinationPort & 0xFFFF, + data, sentIntent, deliveryIntent); + } catch (RemoteException ex) { + // ignore it + } + } + + /** + * A variant of {@link SmsManager#sendDataMessage} that allows self to be the caller. This is + * for internal use only. + * + * @hide + */ + public void sendDataMessageWithSelfPermissions( + String destinationAddress, String scAddress, short destinationPort, + byte[] data, PendingIntent sentIntent, PendingIntent deliveryIntent) { + if (TextUtils.isEmpty(destinationAddress)) { + throw new IllegalArgumentException("Invalid destinationAddress"); + } + + if (data == null || data.length == 0) { + throw new IllegalArgumentException("Invalid message data"); + } + + try { + ISms iccISms = getISmsServiceOrThrow(); + iccISms.sendDataForSubscriberWithSelfPermissions(getSubscriptionId(), + ActivityThread.currentPackageName(), destinationAddress, scAddress, + destinationPort & 0xFFFF, data, sentIntent, deliveryIntent); + } catch (RemoteException ex) { + // ignore it + } + } + + + + /** + * Get the SmsManager associated with the default subscription id. The instance will always be + * associated with the default subscription id, even if the default subscription id is changed. + * + * @return the SmsManager associated with the default subscription id + */ + public static SmsManager getDefault() { + return sInstance; + } + + /** + * Get the the instance of the SmsManager associated with a particular subscription id + * + * @param subId an SMS subscription id, typically accessed using + * {@link android.telephony.SubscriptionManager} + * @return the instance of the SmsManager associated with subId + */ + public static SmsManager getSmsManagerForSubscriptionId(int subId) { + // TODO(shri): Add javadoc link once SubscriptionManager is made public api + synchronized(sLockObject) { + SmsManager smsManager = sSubInstances.get(subId); + if (smsManager == null) { + smsManager = new SmsManager(subId); + sSubInstances.put(subId, smsManager); + } + return smsManager; + } + } + + private SmsManager(int subId) { + mSubId = subId; + } + + /** + * Get the associated subscription id. If the instance was returned by {@link #getDefault()}, + * then this method may return different values at different points in time (if the user + * changes the default subscription id). It will return < 0 if the default subscription id + * cannot be determined. + * + * Additionally, to support legacy applications that are not multi-SIM aware, + * if the following are true: + * - We are using a multi-SIM device + * - A default SMS SIM has not been selected + * - At least one SIM subscription is available + * then ask the user to set the default SMS SIM. + * + * @return associated subscription id + */ + public int getSubscriptionId() { + final int subId = (mSubId == DEFAULT_SUBSCRIPTION_ID) + ? getDefaultSmsSubscriptionId() : mSubId; + boolean isSmsSimPickActivityNeeded = false; + final Context context = ActivityThread.currentApplication().getApplicationContext(); + try { + ISms iccISms = getISmsService(); + if (iccISms != null) { + isSmsSimPickActivityNeeded = iccISms.isSmsSimPickActivityNeeded(subId); + } + } catch (RemoteException ex) { + Log.e(TAG, "Exception in getSubscriptionId"); + } + + if (isSmsSimPickActivityNeeded) { + Log.d(TAG, "getSubscriptionId isSmsSimPickActivityNeeded is true"); + // ask the user for a default SMS SIM. + Intent intent = new Intent(); + intent.setClassName("com.android.settings", + "com.android.settings.sim.SimDialogActivity"); + intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK); + intent.putExtra(DIALOG_TYPE_KEY, SMS_PICK); + try { + context.startActivity(intent); + } catch (ActivityNotFoundException anfe) { + // If Settings is not installed, only log the error as we do not want to break + // legacy applications. + Log.e(TAG, "Unable to launch Settings application."); + } + } + + return subId; + } + + /** + * Returns the ISms service, or throws an UnsupportedOperationException if + * the service does not exist. + */ + private static ISms getISmsServiceOrThrow() { + ISms iccISms = getISmsService(); + if (iccISms == null) { + throw new UnsupportedOperationException("Sms is not supported"); + } + return iccISms; + } + + private static ISms getISmsService() { + return ISms.Stub.asInterface(ServiceManager.getService("isms")); + } + + /** + * Copy a raw SMS PDU to the ICC. + * ICC (Integrated Circuit Card) is the card of the device. + * For example, this can be the SIM or USIM for GSM. + * + * @param smsc the SMSC for this message, or NULL for the default SMSC + * @param pdu the raw PDU to store + * @param status message status (STATUS_ON_ICC_READ, STATUS_ON_ICC_UNREAD, + * STATUS_ON_ICC_SENT, STATUS_ON_ICC_UNSENT) + * @return true for success + * + * @throws IllegalArgumentException if pdu is NULL + * {@hide} + */ + public boolean copyMessageToIcc(byte[] smsc, byte[] pdu,int status) { + boolean success = false; + + if (null == pdu) { + throw new IllegalArgumentException("pdu is NULL"); + } + try { + ISms iccISms = getISmsService(); + if (iccISms != null) { + success = iccISms.copyMessageToIccEfForSubscriber(getSubscriptionId(), + ActivityThread.currentPackageName(), + status, pdu, smsc); + } + } catch (RemoteException ex) { + // ignore it + } + + return success; + } + + /** + * Delete the specified message from the ICC. + * ICC (Integrated Circuit Card) is the card of the device. + * For example, this can be the SIM or USIM for GSM. + * + * @param messageIndex is the record index of the message on ICC + * @return true for success + * + * {@hide} + */ + public boolean + deleteMessageFromIcc(int messageIndex) { + boolean success = false; + byte[] pdu = new byte[SMS_RECORD_LENGTH-1]; + Arrays.fill(pdu, (byte)0xff); + + try { + ISms iccISms = getISmsService(); + if (iccISms != null) { + success = iccISms.updateMessageOnIccEfForSubscriber(getSubscriptionId(), + ActivityThread.currentPackageName(), + messageIndex, STATUS_ON_ICC_FREE, pdu); + } + } catch (RemoteException ex) { + // ignore it + } + + return success; + } + + /** + * Update the specified message on the ICC. + * ICC (Integrated Circuit Card) is the card of the device. + * For example, this can be the SIM or USIM for GSM. + * + * @param messageIndex record index of message to update + * @param newStatus new message status (STATUS_ON_ICC_READ, + * STATUS_ON_ICC_UNREAD, STATUS_ON_ICC_SENT, + * STATUS_ON_ICC_UNSENT, STATUS_ON_ICC_FREE) + * @param pdu the raw PDU to store + * @return true for success + * + * {@hide} + */ + public boolean updateMessageOnIcc(int messageIndex, int newStatus, byte[] pdu) { + boolean success = false; + + try { + ISms iccISms = getISmsService(); + if (iccISms != null) { + success = iccISms.updateMessageOnIccEfForSubscriber(getSubscriptionId(), + ActivityThread.currentPackageName(), + messageIndex, newStatus, pdu); + } + } catch (RemoteException ex) { + // ignore it + } + + return success; + } + + /** + * Retrieves all messages currently stored on ICC. + * ICC (Integrated Circuit Card) is the card of the device. + * For example, this can be the SIM or USIM for GSM. + * + * @return <code>ArrayList</code> of <code>SmsMessage</code> objects + * + * {@hide} + */ + public ArrayList<SmsMessage> getAllMessagesFromIcc() { + List<SmsRawData> records = null; + + try { + ISms iccISms = getISmsService(); + if (iccISms != null) { + records = iccISms.getAllMessagesFromIccEfForSubscriber( + getSubscriptionId(), + ActivityThread.currentPackageName()); + } + } catch (RemoteException ex) { + // ignore it + } + + return createMessageListFromRawRecords(records); + } + + /** + * Enable reception of cell broadcast (SMS-CB) messages with the given + * message identifier and RAN type. The RAN type specify this message ID + * belong to 3GPP (GSM) or 3GPP2(CDMA).Note that if two different clients + * enable the same message identifier, they must both disable it for the device to stop + * receiving those messages. All received messages will be broadcast in an + * intent with the action "android.provider.Telephony.SMS_CB_RECEIVED". + * Note: This call is blocking, callers may want to avoid calling it from + * the main thread of an application. + * + * @param messageIdentifier Message identifier as specified in TS 23.041 (3GPP) + * or C.R1001-G (3GPP2) + * @param ranType as defined in class SmsManager, the value can be one of these: + * android.telephony.SmsMessage.CELL_BROADCAST_RAN_TYPE_GSM + * android.telephony.SmsMessage.CELL_BROADCAST_RAN_TYPE_CDMA + * @return true if successful, false otherwise + * @see #disableCellBroadcast(int, int) + * + * {@hide} + */ + public boolean enableCellBroadcast(int messageIdentifier, int ranType) { + boolean success = false; + + try { + ISms iccISms = getISmsService(); + if (iccISms != null) { + success = iccISms.enableCellBroadcastForSubscriber( + getSubscriptionId(), messageIdentifier, ranType); + } + } catch (RemoteException ex) { + // ignore it + } + + return success; + } + + /** + * Disable reception of cell broadcast (SMS-CB) messages with the given + * message identifier and RAN type. The RAN type specify this message ID + * belong to 3GPP (GSM) or 3GPP2(CDMA). Note that if two different clients + * enable the same message identifier, they must both disable it for the + * device to stop receiving those messages. + * Note: This call is blocking, callers may want to avoid calling it from + * the main thread of an application. + * + * @param messageIdentifier Message identifier as specified in TS 23.041 (3GPP) + * or C.R1001-G (3GPP2) + * @param ranType as defined in class SmsManager, the value can be one of these: + * android.telephony.SmsMessage.CELL_BROADCAST_RAN_TYPE_GSM + * android.telephony.SmsMessage.CELL_BROADCAST_RAN_TYPE_CDMA + * @return true if successful, false otherwise + * + * @see #enableCellBroadcast(int, int) + * + * {@hide} + */ + public boolean disableCellBroadcast(int messageIdentifier, int ranType) { + boolean success = false; + + try { + ISms iccISms = getISmsService(); + if (iccISms != null) { + success = iccISms.disableCellBroadcastForSubscriber( + getSubscriptionId(), messageIdentifier, ranType); + } + } catch (RemoteException ex) { + // ignore it + } + + return success; + } + + /** + * Enable reception of cell broadcast (SMS-CB) messages with the given + * message identifier range and RAN type. The RAN type specify this message ID + * belong to 3GPP (GSM) or 3GPP2(CDMA). Note that if two different clients enable + * the same message identifier, they must both disable it for the device to stop + * receiving those messages. All received messages will be broadcast in an + * intent with the action "android.provider.Telephony.SMS_CB_RECEIVED". + * Note: This call is blocking, callers may want to avoid calling it from + * the main thread of an application. + * + * @param startMessageId first message identifier as specified in TS 23.041 (3GPP) + * or C.R1001-G (3GPP2) + * @param endMessageId last message identifier as specified in TS 23.041 (3GPP) + * or C.R1001-G (3GPP2) + * @param ranType as defined in class SmsManager, the value can be one of these: + * android.telephony.SmsMessage.CELL_BROADCAST_RAN_TYPE_GSM + * android.telephony.SmsMessage.CELL_BROADCAST_RAN_TYPE_CDMA + * @return true if successful, false otherwise + * @see #disableCellBroadcastRange(int, int, int) + * + * @throws IllegalArgumentException if endMessageId < startMessageId + * {@hide} + */ + public boolean enableCellBroadcastRange(int startMessageId, int endMessageId, int ranType) { + boolean success = false; + + if (endMessageId < startMessageId) { + throw new IllegalArgumentException("endMessageId < startMessageId"); + } + try { + ISms iccISms = getISmsService(); + if (iccISms != null) { + success = iccISms.enableCellBroadcastRangeForSubscriber(getSubscriptionId(), + startMessageId, endMessageId, ranType); + } + } catch (RemoteException ex) { + // ignore it + } + + return success; + } + + /** + * Disable reception of cell broadcast (SMS-CB) messages with the given + * message identifier range and RAN type. The RAN type specify this message + * ID range belong to 3GPP (GSM) or 3GPP2(CDMA). Note that if two different + * clients enable the same message identifier, they must both disable it for + * the device to stop receiving those messages. + * Note: This call is blocking, callers may want to avoid calling it from + * the main thread of an application. + * + * @param startMessageId first message identifier as specified in TS 23.041 (3GPP) + * or C.R1001-G (3GPP2) + * @param endMessageId last message identifier as specified in TS 23.041 (3GPP) + * or C.R1001-G (3GPP2) + * @param ranType as defined in class SmsManager, the value can be one of these: + * android.telephony.SmsMessage.CELL_BROADCAST_RAN_TYPE_GSM + * android.telephony.SmsMessage.CELL_BROADCAST_RAN_TYPE_CDMA + * @return true if successful, false otherwise + * + * @see #enableCellBroadcastRange(int, int, int) + * + * @throws IllegalArgumentException if endMessageId < startMessageId + * {@hide} + */ + public boolean disableCellBroadcastRange(int startMessageId, int endMessageId, int ranType) { + boolean success = false; + + if (endMessageId < startMessageId) { + throw new IllegalArgumentException("endMessageId < startMessageId"); + } + try { + ISms iccISms = getISmsService(); + if (iccISms != null) { + success = iccISms.disableCellBroadcastRangeForSubscriber(getSubscriptionId(), + startMessageId, endMessageId, ranType); + } + } catch (RemoteException ex) { + // ignore it + } + + return success; + } + + /** + * Create a list of <code>SmsMessage</code>s from a list of RawSmsData + * records returned by <code>getAllMessagesFromIcc()</code> + * + * @param records SMS EF records, returned by + * <code>getAllMessagesFromIcc</code> + * @return <code>ArrayList</code> of <code>SmsMessage</code> objects. + */ + private static ArrayList<SmsMessage> createMessageListFromRawRecords(List<SmsRawData> records) { + ArrayList<SmsMessage> messages = new ArrayList<SmsMessage>(); + if (records != null) { + int count = records.size(); + for (int i = 0; i < count; i++) { + SmsRawData data = records.get(i); + // List contains all records, including "free" records (null) + if (data != null) { + SmsMessage sms = SmsMessage.createFromEfRecord(i+1, data.getBytes()); + if (sms != null) { + messages.add(sms); + } + } + } + } + return messages; + } + + /** + * SMS over IMS is supported if IMS is registered and SMS is supported + * on IMS. + * + * @return true if SMS over IMS is supported, false otherwise + * + * @see #getImsSmsFormat() + * + * @hide + */ + public boolean isImsSmsSupported() { + boolean boSupported = false; + try { + ISms iccISms = getISmsService(); + if (iccISms != null) { + boSupported = iccISms.isImsSmsSupportedForSubscriber(getSubscriptionId()); + } + } catch (RemoteException ex) { + // ignore it + } + return boSupported; + } + + /** + * Gets SMS format supported on IMS. SMS over IMS format is + * either 3GPP or 3GPP2. + * + * @return SmsMessage.FORMAT_3GPP, + * SmsMessage.FORMAT_3GPP2 + * or SmsMessage.FORMAT_UNKNOWN + * + * @see #isImsSmsSupported() + * + * @hide + */ + public String getImsSmsFormat() { + String format = com.android.internal.telephony.SmsConstants.FORMAT_UNKNOWN; + try { + ISms iccISms = getISmsService(); + if (iccISms != null) { + format = iccISms.getImsSmsFormatForSubscriber(getSubscriptionId()); + } + } catch (RemoteException ex) { + // ignore it + } + return format; + } + + /** + * Get default sms subscription id + * + * @return the default SMS subscription id + */ + public static int getDefaultSmsSubscriptionId() { + ISms iccISms = null; + try { + iccISms = ISms.Stub.asInterface(ServiceManager.getService("isms")); + return iccISms.getPreferredSmsSubscription(); + } catch (RemoteException ex) { + return -1; + } catch (NullPointerException ex) { + return -1; + } + } + + /** + * Get SMS prompt property, enabled or not + * + * @return true if enabled, false otherwise + * @hide + */ + public boolean isSMSPromptEnabled() { + ISms iccISms = null; + try { + iccISms = ISms.Stub.asInterface(ServiceManager.getService("isms")); + return iccISms.isSMSPromptEnabled(); + } catch (RemoteException ex) { + return false; + } catch (NullPointerException ex) { + return false; + } + } + + // see SmsMessage.getStatusOnIcc + + /** Free space (TS 51.011 10.5.3 / 3GPP2 C.S0023 3.4.27). */ + static public final int STATUS_ON_ICC_FREE = 0; + + /** Received and read (TS 51.011 10.5.3 / 3GPP2 C.S0023 3.4.27). */ + static public final int STATUS_ON_ICC_READ = 1; + + /** Received and unread (TS 51.011 10.5.3 / 3GPP2 C.S0023 3.4.27). */ + static public final int STATUS_ON_ICC_UNREAD = 3; + + /** Stored and sent (TS 51.011 10.5.3 / 3GPP2 C.S0023 3.4.27). */ + static public final int STATUS_ON_ICC_SENT = 5; + + /** Stored and unsent (TS 51.011 10.5.3 / 3GPP2 C.S0023 3.4.27). */ + static public final int STATUS_ON_ICC_UNSENT = 7; + + // SMS send failure result codes + + /** Generic failure cause */ + static public final int RESULT_ERROR_GENERIC_FAILURE = 1; + /** Failed because radio was explicitly turned off */ + static public final int RESULT_ERROR_RADIO_OFF = 2; + /** Failed because no pdu provided */ + static public final int RESULT_ERROR_NULL_PDU = 3; + /** Failed because service is currently unavailable */ + static public final int RESULT_ERROR_NO_SERVICE = 4; + /** Failed because we reached the sending queue limit. */ + static public final int RESULT_ERROR_LIMIT_EXCEEDED = 5; + /** Failed because FDN is enabled. {@hide} */ + static public final int RESULT_ERROR_FDN_CHECK_FAILURE = 6; + /** Failed because user denied the sending of this short code. */ + static public final int RESULT_ERROR_SHORT_CODE_NOT_ALLOWED = 7; + /** Failed because the user has denied this app ever send premium short codes. */ + static public final int RESULT_ERROR_SHORT_CODE_NEVER_ALLOWED = 8; + + static private final String PHONE_PACKAGE_NAME = "com.android.phone"; + + /** + * Send an MMS message + * + * @param context application context + * @param contentUri the content Uri from which the message pdu will be read + * @param locationUrl the optional location url where message should be sent to + * @param configOverrides the carrier-specific messaging configuration values to override for + * sending the message. + * @param sentIntent if not NULL this <code>PendingIntent</code> is + * broadcast when the message is successfully sent, or failed + * @throws IllegalArgumentException if contentUri is empty + */ + public void sendMultimediaMessage(Context context, Uri contentUri, String locationUrl, + Bundle configOverrides, PendingIntent sentIntent) { + if (contentUri == null) { + throw new IllegalArgumentException("Uri contentUri null"); + } + try { + final IMms iMms = IMms.Stub.asInterface(ServiceManager.getService("imms")); + if (iMms == null) { + return; + } + + iMms.sendMessage(getSubscriptionId(), ActivityThread.currentPackageName(), contentUri, + locationUrl, configOverrides, sentIntent); + } catch (RemoteException e) { + // Ignore it + } + } + + /** + * Download an MMS message from carrier by a given location URL + * + * @param context application context + * @param locationUrl the location URL of the MMS message to be downloaded, usually obtained + * from the MMS WAP push notification + * @param contentUri the content uri to which the downloaded pdu will be written + * @param configOverrides the carrier-specific messaging configuration values to override for + * downloading the message. + * @param downloadedIntent if not NULL this <code>PendingIntent</code> is + * broadcast when the message is downloaded, or the download is failed + * @throws IllegalArgumentException if locationUrl or contentUri is empty + */ + public void downloadMultimediaMessage(Context context, String locationUrl, Uri contentUri, + Bundle configOverrides, PendingIntent downloadedIntent) { + if (TextUtils.isEmpty(locationUrl)) { + throw new IllegalArgumentException("Empty MMS location URL"); + } + if (contentUri == null) { + throw new IllegalArgumentException("Uri contentUri null"); + } + try { + final IMms iMms = IMms.Stub.asInterface(ServiceManager.getService("imms")); + if (iMms == null) { + return; + } + iMms.downloadMessage( + getSubscriptionId(), ActivityThread.currentPackageName(), locationUrl, + contentUri, configOverrides, downloadedIntent); + } catch (RemoteException e) { + // Ignore it + } + } + + // MMS send/download failure result codes + public static final int MMS_ERROR_UNSPECIFIED = 1; + public static final int MMS_ERROR_INVALID_APN = 2; + public static final int MMS_ERROR_UNABLE_CONNECT_MMS = 3; + public static final int MMS_ERROR_HTTP_FAILURE = 4; + public static final int MMS_ERROR_IO_ERROR = 5; + public static final int MMS_ERROR_RETRY = 6; + public static final int MMS_ERROR_CONFIGURATION_ERROR = 7; + public static final int MMS_ERROR_NO_DATA_NETWORK = 8; + + /** Intent extra name for MMS sending result data in byte array type */ + public static final String EXTRA_MMS_DATA = "android.telephony.extra.MMS_DATA"; + /** Intent extra name for HTTP status code for MMS HTTP failure in integer type */ + public static final String EXTRA_MMS_HTTP_STATUS = "android.telephony.extra.MMS_HTTP_STATUS"; + + /** + * Import a text message into system's SMS store + * + * Only default SMS apps can import SMS + * + * @param address the destination(source) address of the sent(received) message + * @param type the type of the message + * @param text the message text + * @param timestampMillis the message timestamp in milliseconds + * @param seen if the message is seen + * @param read if the message is read + * @return the message URI, null if failed + * @hide + */ + public Uri importTextMessage(String address, int type, String text, long timestampMillis, + boolean seen, boolean read) { + try { + IMms iMms = IMms.Stub.asInterface(ServiceManager.getService("imms")); + if (iMms != null) { + return iMms.importTextMessage(ActivityThread.currentPackageName(), + address, type, text, timestampMillis, seen, read); + } + } catch (RemoteException ex) { + // ignore it + } + return null; + } + + /** Represents the received SMS message for importing {@hide} */ + public static final int SMS_TYPE_INCOMING = 0; + /** Represents the sent SMS message for importing {@hide} */ + public static final int SMS_TYPE_OUTGOING = 1; + + /** + * Import a multimedia message into system's MMS store. Only the following PDU type is + * supported: Retrieve.conf, Send.req, Notification.ind, Delivery.ind, Read-Orig.ind + * + * Only default SMS apps can import MMS + * + * @param contentUri the content uri from which to read the PDU of the message to import + * @param messageId the optional message id. Use null if not specifying + * @param timestampSecs the optional message timestamp. Use -1 if not specifying + * @param seen if the message is seen + * @param read if the message is read + * @return the message URI, null if failed + * @throws IllegalArgumentException if pdu is empty + * {@hide} + */ + public Uri importMultimediaMessage(Uri contentUri, String messageId, long timestampSecs, + boolean seen, boolean read) { + if (contentUri == null) { + throw new IllegalArgumentException("Uri contentUri null"); + } + try { + IMms iMms = IMms.Stub.asInterface(ServiceManager.getService("imms")); + if (iMms != null) { + return iMms.importMultimediaMessage(ActivityThread.currentPackageName(), + contentUri, messageId, timestampSecs, seen, read); + } + } catch (RemoteException ex) { + // ignore it + } + return null; + } + + /** + * Delete a system stored SMS or MMS message + * + * Only default SMS apps can delete system stored SMS and MMS messages + * + * @param messageUri the URI of the stored message + * @return true if deletion is successful, false otherwise + * @throws IllegalArgumentException if messageUri is empty + * {@hide} + */ + public boolean deleteStoredMessage(Uri messageUri) { + if (messageUri == null) { + throw new IllegalArgumentException("Empty message URI"); + } + try { + IMms iMms = IMms.Stub.asInterface(ServiceManager.getService("imms")); + if (iMms != null) { + return iMms.deleteStoredMessage(ActivityThread.currentPackageName(), messageUri); + } + } catch (RemoteException ex) { + // ignore it + } + return false; + } + + /** + * Delete a system stored SMS or MMS thread + * + * Only default SMS apps can delete system stored SMS and MMS conversations + * + * @param conversationId the ID of the message conversation + * @return true if deletion is successful, false otherwise + * {@hide} + */ + public boolean deleteStoredConversation(long conversationId) { + try { + IMms iMms = IMms.Stub.asInterface(ServiceManager.getService("imms")); + if (iMms != null) { + return iMms.deleteStoredConversation( + ActivityThread.currentPackageName(), conversationId); + } + } catch (RemoteException ex) { + // ignore it + } + return false; + } + + /** + * Update the status properties of a system stored SMS or MMS message, e.g. + * the read status of a message, etc. + * + * @param messageUri the URI of the stored message + * @param statusValues a list of status properties in key-value pairs to update + * @return true if update is successful, false otherwise + * @throws IllegalArgumentException if messageUri is empty + * {@hide} + */ + public boolean updateStoredMessageStatus(Uri messageUri, ContentValues statusValues) { + if (messageUri == null) { + throw new IllegalArgumentException("Empty message URI"); + } + try { + IMms iMms = IMms.Stub.asInterface(ServiceManager.getService("imms")); + if (iMms != null) { + return iMms.updateStoredMessageStatus(ActivityThread.currentPackageName(), + messageUri, statusValues); + } + } catch (RemoteException ex) { + // ignore it + } + return false; + } + + /** Message status property: whether the message has been seen. 1 means seen, 0 not {@hide} */ + public static final String MESSAGE_STATUS_SEEN = "seen"; + /** Message status property: whether the message has been read. 1 means read, 0 not {@hide} */ + public static final String MESSAGE_STATUS_READ = "read"; + + /** + * Archive or unarchive a stored conversation + * + * @param conversationId the ID of the message conversation + * @param archived true to archive the conversation, false to unarchive + * @return true if update is successful, false otherwise + * {@hide} + */ + public boolean archiveStoredConversation(long conversationId, boolean archived) { + try { + IMms iMms = IMms.Stub.asInterface(ServiceManager.getService("imms")); + if (iMms != null) { + return iMms.archiveStoredConversation(ActivityThread.currentPackageName(), + conversationId, archived); + } + } catch (RemoteException ex) { + // ignore it + } + return false; + } + + /** + * Add a text message draft to system SMS store + * + * Only default SMS apps can add SMS draft + * + * @param address the destination address of message + * @param text the body of the message to send + * @return the URI of the stored draft message + * {@hide} + */ + public Uri addTextMessageDraft(String address, String text) { + try { + IMms iMms = IMms.Stub.asInterface(ServiceManager.getService("imms")); + if (iMms != null) { + return iMms.addTextMessageDraft(ActivityThread.currentPackageName(), address, text); + } + } catch (RemoteException ex) { + // ignore it + } + return null; + } + + /** + * Add a multimedia message draft to system MMS store + * + * Only default SMS apps can add MMS draft + * + * @param contentUri the content uri from which to read the PDU data of the draft MMS + * @return the URI of the stored draft message + * @throws IllegalArgumentException if pdu is empty + * {@hide} + */ + public Uri addMultimediaMessageDraft(Uri contentUri) { + if (contentUri == null) { + throw new IllegalArgumentException("Uri contentUri null"); + } + try { + IMms iMms = IMms.Stub.asInterface(ServiceManager.getService("imms")); + if (iMms != null) { + return iMms.addMultimediaMessageDraft(ActivityThread.currentPackageName(), + contentUri); + } + } catch (RemoteException ex) { + // ignore it + } + return null; + } + + /** + * Send a system stored text message. + * + * You can only send a failed text message or a draft text message. + * + * @param messageUri the URI of the stored message + * @param scAddress is the service center address or null to use the current default SMSC + * @param sentIntent if not NULL this <code>PendingIntent</code> is + * broadcast when the message is successfully sent, or failed. + * The result code will be <code>Activity.RESULT_OK</code> for success, + * or one of these errors:<br> + * <code>RESULT_ERROR_GENERIC_FAILURE</code><br> + * <code>RESULT_ERROR_RADIO_OFF</code><br> + * <code>RESULT_ERROR_NULL_PDU</code><br> + * For <code>RESULT_ERROR_GENERIC_FAILURE</code> the sentIntent may include + * the extra "errorCode" containing a radio technology specific value, + * generally only useful for troubleshooting.<br> + * The per-application based SMS control checks sentIntent. If sentIntent + * is NULL the caller will be checked against all unknown applications, + * which cause smaller number of SMS to be sent in checking period. + * @param deliveryIntent if not NULL this <code>PendingIntent</code> is + * broadcast when the message is delivered to the recipient. The + * raw pdu of the status report is in the extended data ("pdu"). + * + * @throws IllegalArgumentException if messageUri is empty + * {@hide} + */ + public void sendStoredTextMessage(Uri messageUri, String scAddress, PendingIntent sentIntent, + PendingIntent deliveryIntent) { + if (messageUri == null) { + throw new IllegalArgumentException("Empty message URI"); + } + try { + ISms iccISms = getISmsServiceOrThrow(); + iccISms.sendStoredText( + getSubscriptionId(), ActivityThread.currentPackageName(), messageUri, + scAddress, sentIntent, deliveryIntent); + } catch (RemoteException ex) { + // ignore it + } + } + + /** + * Send a system stored multi-part text message. + * + * You can only send a failed text message or a draft text message. + * The provided <code>PendingIntent</code> lists should match the part number of the + * divided text of the stored message by using <code>divideMessage</code> + * + * @param messageUri the URI of the stored message + * @param scAddress is the service center address or null to use + * the current default SMSC + * @param sentIntents if not null, an <code>ArrayList</code> of + * <code>PendingIntent</code>s (one for each message part) that is + * broadcast when the corresponding message part has been sent. + * The result code will be <code>Activity.RESULT_OK</code> for success, + * or one of these errors:<br> + * <code>RESULT_ERROR_GENERIC_FAILURE</code><br> + * <code>RESULT_ERROR_RADIO_OFF</code><br> + * <code>RESULT_ERROR_NULL_PDU</code><br> + * For <code>RESULT_ERROR_GENERIC_FAILURE</code> each sentIntent may include + * the extra "errorCode" containing a radio technology specific value, + * generally only useful for troubleshooting.<br> + * The per-application based SMS control checks sentIntent. If sentIntent + * is NULL the caller will be checked against all unknown applications, + * which cause smaller number of SMS to be sent in checking period. + * @param deliveryIntents if not null, an <code>ArrayList</code> of + * <code>PendingIntent</code>s (one for each message part) that is + * broadcast when the corresponding message part has been delivered + * to the recipient. The raw pdu of the status report is in the + * extended data ("pdu"). + * + * @throws IllegalArgumentException if messageUri is empty + * {@hide} + */ + public void sendStoredMultipartTextMessage(Uri messageUri, String scAddress, + ArrayList<PendingIntent> sentIntents, ArrayList<PendingIntent> deliveryIntents) { + if (messageUri == null) { + throw new IllegalArgumentException("Empty message URI"); + } + try { + ISms iccISms = getISmsServiceOrThrow(); + iccISms.sendStoredMultipartText( + getSubscriptionId(), ActivityThread.currentPackageName(), messageUri, + scAddress, sentIntents, deliveryIntents); + } catch (RemoteException ex) { + // ignore it + } + } + + /** + * Send a system stored MMS message + * + * This is used for sending a previously sent, but failed-to-send, message or + * for sending a text message that has been stored as a draft. + * + * @param messageUri the URI of the stored message + * @param configOverrides the carrier-specific messaging configuration values to override for + * sending the message. + * @param sentIntent if not NULL this <code>PendingIntent</code> is + * broadcast when the message is successfully sent, or failed + * @throws IllegalArgumentException if messageUri is empty + * {@hide} + */ + public void sendStoredMultimediaMessage(Uri messageUri, Bundle configOverrides, + PendingIntent sentIntent) { + if (messageUri == null) { + throw new IllegalArgumentException("Empty message URI"); + } + try { + IMms iMms = IMms.Stub.asInterface(ServiceManager.getService("imms")); + if (iMms != null) { + iMms.sendStoredMessage( + getSubscriptionId(), ActivityThread.currentPackageName(), messageUri, + configOverrides, sentIntent); + } + } catch (RemoteException ex) { + // ignore it + } + } + + /** + * Turns on/off the flag to automatically write sent/received SMS/MMS messages into system + * + * When this flag is on, all SMS/MMS sent/received are stored by system automatically + * When this flag is off, only SMS/MMS sent by non-default SMS apps are stored by system + * automatically + * + * This flag can only be changed by default SMS apps + * + * @param enabled Whether to enable message auto persisting + * {@hide} + */ + public void setAutoPersisting(boolean enabled) { + try { + IMms iMms = IMms.Stub.asInterface(ServiceManager.getService("imms")); + if (iMms != null) { + iMms.setAutoPersisting(ActivityThread.currentPackageName(), enabled); + } + } catch (RemoteException ex) { + // ignore it + } + } + + /** + * Get the value of the flag to automatically write sent/received SMS/MMS messages into system + * + * When this flag is on, all SMS/MMS sent/received are stored by system automatically + * When this flag is off, only SMS/MMS sent by non-default SMS apps are stored by system + * automatically + * + * @return the current value of the auto persist flag + * {@hide} + */ + public boolean getAutoPersisting() { + try { + IMms iMms = IMms.Stub.asInterface(ServiceManager.getService("imms")); + if (iMms != null) { + return iMms.getAutoPersisting(); + } + } catch (RemoteException ex) { + // ignore it + } + return false; + } + + /** + * Get carrier-dependent configuration values. + * + * @return bundle key/values pairs of configuration values + */ + public Bundle getCarrierConfigValues() { + try { + IMms iMms = IMms.Stub.asInterface(ServiceManager.getService("imms")); + if (iMms != null) { + return iMms.getCarrierConfigValues(getSubscriptionId()); + } + } catch (RemoteException ex) { + // ignore it + } + return null; + } + + /** + * Create a single use app specific incoming SMS request for the the calling package. + * + * This method returns a token that if included in a subsequent incoming SMS message will cause + * {@code intent} to be sent with the SMS data. + * + * The token is only good for one use, after an SMS has been received containing the token all + * subsequent SMS messages with the token will be routed as normal. + * + * An app can only have one request at a time, if the app already has a request pending it will + * be replaced with a new request. + * + * @return Token to include in an SMS message. The token will be 11 characters long. + * @see android.provider.Telephony.Sms.Intents#getMessagesFromIntent + */ + public String createAppSpecificSmsToken(PendingIntent intent) { + try { + ISms iccSms = getISmsServiceOrThrow(); + return iccSms.createAppSpecificSmsToken(getSubscriptionId(), + ActivityThread.currentPackageName(), intent); + + } catch (RemoteException ex) { + ex.rethrowFromSystemServer(); + return null; + } + } + + /** + * Filters a bundle to only contain MMS config variables. + * + * This is for use with bundles returned by {@link CarrierConfigManager} which contain MMS + * config and unrelated config. It is assumed that all MMS_CONFIG_* keys are present in the + * supplied bundle. + * + * @param config a Bundle that contains MMS config variables and possibly more. + * @return a new Bundle that only contains the MMS_CONFIG_* keys defined above. + * @hide + */ + public static Bundle getMmsConfig(BaseBundle config) { + Bundle filtered = new Bundle(); + filtered.putBoolean(MMS_CONFIG_APPEND_TRANSACTION_ID, + config.getBoolean(MMS_CONFIG_APPEND_TRANSACTION_ID)); + filtered.putBoolean(MMS_CONFIG_MMS_ENABLED, config.getBoolean(MMS_CONFIG_MMS_ENABLED)); + filtered.putBoolean(MMS_CONFIG_GROUP_MMS_ENABLED, + config.getBoolean(MMS_CONFIG_GROUP_MMS_ENABLED)); + filtered.putBoolean(MMS_CONFIG_NOTIFY_WAP_MMSC_ENABLED, + config.getBoolean(MMS_CONFIG_NOTIFY_WAP_MMSC_ENABLED)); + filtered.putBoolean(MMS_CONFIG_ALIAS_ENABLED, config.getBoolean(MMS_CONFIG_ALIAS_ENABLED)); + filtered.putBoolean(MMS_CONFIG_ALLOW_ATTACH_AUDIO, + config.getBoolean(MMS_CONFIG_ALLOW_ATTACH_AUDIO)); + filtered.putBoolean(MMS_CONFIG_MULTIPART_SMS_ENABLED, + config.getBoolean(MMS_CONFIG_MULTIPART_SMS_ENABLED)); + filtered.putBoolean(MMS_CONFIG_SMS_DELIVERY_REPORT_ENABLED, + config.getBoolean(MMS_CONFIG_SMS_DELIVERY_REPORT_ENABLED)); + filtered.putBoolean(MMS_CONFIG_SUPPORT_MMS_CONTENT_DISPOSITION, + config.getBoolean(MMS_CONFIG_SUPPORT_MMS_CONTENT_DISPOSITION)); + filtered.putBoolean(MMS_CONFIG_SEND_MULTIPART_SMS_AS_SEPARATE_MESSAGES, + config.getBoolean(MMS_CONFIG_SEND_MULTIPART_SMS_AS_SEPARATE_MESSAGES)); + filtered.putBoolean(MMS_CONFIG_MMS_READ_REPORT_ENABLED, + config.getBoolean(MMS_CONFIG_MMS_READ_REPORT_ENABLED)); + filtered.putBoolean(MMS_CONFIG_MMS_DELIVERY_REPORT_ENABLED, + config.getBoolean(MMS_CONFIG_MMS_DELIVERY_REPORT_ENABLED)); + filtered.putBoolean(MMS_CONFIG_CLOSE_CONNECTION, + config.getBoolean(MMS_CONFIG_CLOSE_CONNECTION)); + filtered.putInt(MMS_CONFIG_MAX_MESSAGE_SIZE, config.getInt(MMS_CONFIG_MAX_MESSAGE_SIZE)); + filtered.putInt(MMS_CONFIG_MAX_IMAGE_WIDTH, config.getInt(MMS_CONFIG_MAX_IMAGE_WIDTH)); + filtered.putInt(MMS_CONFIG_MAX_IMAGE_HEIGHT, config.getInt(MMS_CONFIG_MAX_IMAGE_HEIGHT)); + filtered.putInt(MMS_CONFIG_RECIPIENT_LIMIT, config.getInt(MMS_CONFIG_RECIPIENT_LIMIT)); + filtered.putInt(MMS_CONFIG_ALIAS_MIN_CHARS, config.getInt(MMS_CONFIG_ALIAS_MIN_CHARS)); + filtered.putInt(MMS_CONFIG_ALIAS_MAX_CHARS, config.getInt(MMS_CONFIG_ALIAS_MAX_CHARS)); + filtered.putInt(MMS_CONFIG_SMS_TO_MMS_TEXT_THRESHOLD, + config.getInt(MMS_CONFIG_SMS_TO_MMS_TEXT_THRESHOLD)); + filtered.putInt(MMS_CONFIG_SMS_TO_MMS_TEXT_LENGTH_THRESHOLD, + config.getInt(MMS_CONFIG_SMS_TO_MMS_TEXT_LENGTH_THRESHOLD)); + filtered.putInt(MMS_CONFIG_MESSAGE_TEXT_MAX_SIZE, + config.getInt(MMS_CONFIG_MESSAGE_TEXT_MAX_SIZE)); + filtered.putInt(MMS_CONFIG_SUBJECT_MAX_LENGTH, + config.getInt(MMS_CONFIG_SUBJECT_MAX_LENGTH)); + filtered.putInt(MMS_CONFIG_HTTP_SOCKET_TIMEOUT, + config.getInt(MMS_CONFIG_HTTP_SOCKET_TIMEOUT)); + filtered.putString(MMS_CONFIG_UA_PROF_TAG_NAME, + config.getString(MMS_CONFIG_UA_PROF_TAG_NAME)); + filtered.putString(MMS_CONFIG_USER_AGENT, config.getString(MMS_CONFIG_USER_AGENT)); + filtered.putString(MMS_CONFIG_UA_PROF_URL, config.getString(MMS_CONFIG_UA_PROF_URL)); + filtered.putString(MMS_CONFIG_HTTP_PARAMS, config.getString(MMS_CONFIG_HTTP_PARAMS)); + filtered.putString(MMS_CONFIG_EMAIL_GATEWAY_NUMBER, + config.getString(MMS_CONFIG_EMAIL_GATEWAY_NUMBER)); + filtered.putString(MMS_CONFIG_NAI_SUFFIX, config.getString(MMS_CONFIG_NAI_SUFFIX)); + filtered.putBoolean(MMS_CONFIG_SHOW_CELL_BROADCAST_APP_LINKS, + config.getBoolean(MMS_CONFIG_SHOW_CELL_BROADCAST_APP_LINKS)); + filtered.putBoolean(MMS_CONFIG_SUPPORT_HTTP_CHARSET_HEADER, + config.getBoolean(MMS_CONFIG_SUPPORT_HTTP_CHARSET_HEADER)); + return filtered; + } + +} diff --git a/android/telephony/SmsMessage.java b/android/telephony/SmsMessage.java new file mode 100644 index 00000000..dcdda868 --- /dev/null +++ b/android/telephony/SmsMessage.java @@ -0,0 +1,926 @@ +/* + * Copyright (C) 2008 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.telephony; + +import android.os.Binder; +import android.os.Parcel; +import android.content.res.Resources; +import android.text.TextUtils; + +import com.android.internal.telephony.GsmAlphabet; +import com.android.internal.telephony.GsmAlphabet.TextEncodingDetails; +import com.android.internal.telephony.SmsConstants; +import com.android.internal.telephony.SmsMessageBase; +import com.android.internal.telephony.SmsMessageBase.SubmitPduBase; +import com.android.internal.telephony.Sms7BitEncodingTranslator; + +import java.lang.Math; +import java.util.ArrayList; +import java.util.Arrays; + +import static android.telephony.TelephonyManager.PHONE_TYPE_CDMA; + + +/** + * A Short Message Service message. + * @see android.provider.Telephony.Sms.Intents#getMessagesFromIntent + */ +public class SmsMessage { + private static final String LOG_TAG = "SmsMessage"; + + /** + * SMS Class enumeration. + * See TS 23.038. + * + */ + public enum MessageClass{ + UNKNOWN, CLASS_0, CLASS_1, CLASS_2, CLASS_3; + } + + /** User data text encoding code unit size */ + public static final int ENCODING_UNKNOWN = 0; + public static final int ENCODING_7BIT = 1; + public static final int ENCODING_8BIT = 2; + public static final int ENCODING_16BIT = 3; + /** + * @hide This value is not defined in global standard. Only in Korea, this is used. + */ + public static final int ENCODING_KSC5601 = 4; + + /** The maximum number of payload bytes per message */ + public static final int MAX_USER_DATA_BYTES = 140; + + /** + * The maximum number of payload bytes per message if a user data header + * is present. This assumes the header only contains the + * CONCATENATED_8_BIT_REFERENCE element. + */ + public static final int MAX_USER_DATA_BYTES_WITH_HEADER = 134; + + /** The maximum number of payload septets per message */ + public static final int MAX_USER_DATA_SEPTETS = 160; + + /** + * The maximum number of payload septets per message if a user data header + * is present. This assumes the header only contains the + * CONCATENATED_8_BIT_REFERENCE element. + */ + public static final int MAX_USER_DATA_SEPTETS_WITH_HEADER = 153; + + /** + * Indicates a 3GPP format SMS message. + * @hide pending API council approval + */ + public static final String FORMAT_3GPP = "3gpp"; + + /** + * Indicates a 3GPP2 format SMS message. + * @hide pending API council approval + */ + public static final String FORMAT_3GPP2 = "3gpp2"; + + /** Contains actual SmsMessage. Only public for debugging and for framework layer. + * + * @hide + */ + public SmsMessageBase mWrappedSmsMessage; + + /** Indicates the subId + * + * @hide + */ + private int mSubId = 0; + + /** set Subscription information + * + * @hide + */ + public void setSubId(int subId) { + mSubId = subId; + } + + /** get Subscription information + * + * @hide + */ + public int getSubId() { + return mSubId; + } + + public static class SubmitPdu { + + public byte[] encodedScAddress; // Null if not applicable. + public byte[] encodedMessage; + + @Override + public String toString() { + return "SubmitPdu: encodedScAddress = " + + Arrays.toString(encodedScAddress) + + ", encodedMessage = " + + Arrays.toString(encodedMessage); + } + + /** + * @hide + */ + protected SubmitPdu(SubmitPduBase spb) { + this.encodedMessage = spb.encodedMessage; + this.encodedScAddress = spb.encodedScAddress; + } + + } + + /** + * @hide + */ + public SmsMessage(SmsMessageBase smb) { + mWrappedSmsMessage = smb; + } + + /** + * Create an SmsMessage from a raw PDU. Guess format based on Voice + * technology first, if it fails use other format. + * All applications which handle + * incoming SMS messages by processing the {@code SMS_RECEIVED_ACTION} broadcast + * intent <b>must</b> now pass the new {@code format} String extra from the intent + * into the new method {@code createFromPdu(byte[], String)} which takes an + * extra format parameter. This is required in order to correctly decode the PDU on + * devices that require support for both 3GPP and 3GPP2 formats at the same time, + * such as dual-mode GSM/CDMA and CDMA/LTE phones. + * @deprecated Use {@link #createFromPdu(byte[], String)} instead. + */ + @Deprecated + public static SmsMessage createFromPdu(byte[] pdu) { + SmsMessage message = null; + + // cdma(3gpp2) vs gsm(3gpp) format info was not given, + // guess from active voice phone type + int activePhone = TelephonyManager.getDefault().getCurrentPhoneType(); + String format = (PHONE_TYPE_CDMA == activePhone) ? + SmsConstants.FORMAT_3GPP2 : SmsConstants.FORMAT_3GPP; + message = createFromPdu(pdu, format); + + if (null == message || null == message.mWrappedSmsMessage) { + // decoding pdu failed based on activePhone type, must be other format + format = (PHONE_TYPE_CDMA == activePhone) ? + SmsConstants.FORMAT_3GPP : SmsConstants.FORMAT_3GPP2; + message = createFromPdu(pdu, format); + } + return message; + } + + /** + * Create an SmsMessage from a raw PDU with the specified message format. The + * message format is passed in the + * {@link android.provider.Telephony.Sms.Intents#SMS_RECEIVED_ACTION} as the {@code format} + * String extra, and will be either "3gpp" for GSM/UMTS/LTE messages in 3GPP format + * or "3gpp2" for CDMA/LTE messages in 3GPP2 format. + * + * @param pdu the message PDU from the + * {@link android.provider.Telephony.Sms.Intents#SMS_RECEIVED_ACTION} intent + * @param format the format extra from the + * {@link android.provider.Telephony.Sms.Intents#SMS_RECEIVED_ACTION} intent + */ + public static SmsMessage createFromPdu(byte[] pdu, String format) { + SmsMessageBase wrappedMessage; + + if (SmsConstants.FORMAT_3GPP2.equals(format)) { + wrappedMessage = com.android.internal.telephony.cdma.SmsMessage.createFromPdu(pdu); + } else if (SmsConstants.FORMAT_3GPP.equals(format)) { + wrappedMessage = com.android.internal.telephony.gsm.SmsMessage.createFromPdu(pdu); + } else { + Rlog.e(LOG_TAG, "createFromPdu(): unsupported message format " + format); + return null; + } + + if (wrappedMessage != null) { + return new SmsMessage(wrappedMessage); + } else { + Rlog.e(LOG_TAG, "createFromPdu(): wrappedMessage is null"); + return null; + } + } + + /** + * TS 27.005 3.4.1 lines[0] and lines[1] are the two lines read from the + * +CMT unsolicited response (PDU mode, of course) + * +CMT: [<alpha>],<length><CR><LF><pdu> + * + * Only public for debugging and for RIL + * + * {@hide} + */ + public static SmsMessage newFromCMT(byte[] pdu) { + // received SMS in 3GPP format + SmsMessageBase wrappedMessage = + com.android.internal.telephony.gsm.SmsMessage.newFromCMT(pdu); + + if (wrappedMessage != null) { + return new SmsMessage(wrappedMessage); + } else { + Rlog.e(LOG_TAG, "newFromCMT(): wrappedMessage is null"); + return null; + } + } + + /** + * Create an SmsMessage from an SMS EF record. + * + * @param index Index of SMS record. This should be index in ArrayList + * returned by SmsManager.getAllMessagesFromSim + 1. + * @param data Record data. + * @return An SmsMessage representing the record. + * + * @hide + */ + public static SmsMessage createFromEfRecord(int index, byte[] data) { + SmsMessageBase wrappedMessage; + + if (isCdmaVoice()) { + wrappedMessage = com.android.internal.telephony.cdma.SmsMessage.createFromEfRecord( + index, data); + } else { + wrappedMessage = com.android.internal.telephony.gsm.SmsMessage.createFromEfRecord( + index, data); + } + + if (wrappedMessage != null) { + return new SmsMessage(wrappedMessage); + } else { + Rlog.e(LOG_TAG, "createFromEfRecord(): wrappedMessage is null"); + return null; + } + } + + /** + * Get the TP-Layer-Length for the given SMS-SUBMIT PDU Basically, the + * length in bytes (not hex chars) less the SMSC header + * + * FIXME: This method is only used by a CTS test case that isn't run on CDMA devices. + * We should probably deprecate it and remove the obsolete test case. + */ + public static int getTPLayerLengthForPDU(String pdu) { + if (isCdmaVoice()) { + return com.android.internal.telephony.cdma.SmsMessage.getTPLayerLengthForPDU(pdu); + } else { + return com.android.internal.telephony.gsm.SmsMessage.getTPLayerLengthForPDU(pdu); + } + } + + /* + * TODO(cleanup): It would make some sense if the result of + * preprocessing a message to determine the proper encoding (i.e. + * the resulting data structure from calculateLength) could be + * passed as an argument to the actual final encoding function. + * This would better ensure that the logic behind size calculation + * actually matched the encoding. + */ + + /** + * Calculates the number of SMS's required to encode the message body and + * the number of characters remaining until the next message. + * + * @param msgBody the message to encode + * @param use7bitOnly if true, characters that are not part of the + * radio-specific 7-bit encoding are counted as single + * space chars. If false, and if the messageBody contains + * non-7-bit encodable characters, length is calculated + * using a 16-bit encoding. + * @return an int[4] with int[0] being the number of SMS's + * required, int[1] the number of code units used, and + * int[2] is the number of code units remaining until the + * next message. int[3] is an indicator of the encoding + * code unit size (see the ENCODING_* definitions in SmsConstants) + */ + public static int[] calculateLength(CharSequence msgBody, boolean use7bitOnly) { + // this function is for MO SMS + TextEncodingDetails ted = (useCdmaFormatForMoSms()) ? + com.android.internal.telephony.cdma.SmsMessage.calculateLength(msgBody, use7bitOnly, + true) : + com.android.internal.telephony.gsm.SmsMessage.calculateLength(msgBody, use7bitOnly); + int ret[] = new int[4]; + ret[0] = ted.msgCount; + ret[1] = ted.codeUnitCount; + ret[2] = ted.codeUnitsRemaining; + ret[3] = ted.codeUnitSize; + return ret; + } + + /** + * Divide a message text into several fragments, none bigger than + * the maximum SMS message text size. + * + * @param text text, must not be null. + * @return an <code>ArrayList</code> of strings that, in order, + * comprise the original msg text + * + * @hide + */ + public static ArrayList<String> fragmentText(String text) { + // This function is for MO SMS + TextEncodingDetails ted = (useCdmaFormatForMoSms()) ? + com.android.internal.telephony.cdma.SmsMessage.calculateLength(text, false, true) : + com.android.internal.telephony.gsm.SmsMessage.calculateLength(text, false); + + // TODO(cleanup): The code here could be rolled into the logic + // below cleanly if these MAX_* constants were defined more + // flexibly... + + int limit; + if (ted.codeUnitSize == SmsConstants.ENCODING_7BIT) { + int udhLength; + if (ted.languageTable != 0 && ted.languageShiftTable != 0) { + udhLength = GsmAlphabet.UDH_SEPTET_COST_TWO_SHIFT_TABLES; + } else if (ted.languageTable != 0 || ted.languageShiftTable != 0) { + udhLength = GsmAlphabet.UDH_SEPTET_COST_ONE_SHIFT_TABLE; + } else { + udhLength = 0; + } + + if (ted.msgCount > 1) { + udhLength += GsmAlphabet.UDH_SEPTET_COST_CONCATENATED_MESSAGE; + } + + if (udhLength != 0) { + udhLength += GsmAlphabet.UDH_SEPTET_COST_LENGTH; + } + + limit = SmsConstants.MAX_USER_DATA_SEPTETS - udhLength; + } else { + if (ted.msgCount > 1) { + limit = SmsConstants.MAX_USER_DATA_BYTES_WITH_HEADER; + // If EMS is not supported, break down EMS into single segment SMS + // and add page info " x/y". + // In the case of UCS2 encoding, we need 8 bytes for this, + // but we only have 6 bytes from UDH, so truncate the limit for + // each segment by 2 bytes (1 char). + // Make sure total number of segments is less than 10. + if (!hasEmsSupport() && ted.msgCount < 10) { + limit -= 2; + } + } else { + limit = SmsConstants.MAX_USER_DATA_BYTES; + } + } + + String newMsgBody = null; + Resources r = Resources.getSystem(); + if (r.getBoolean(com.android.internal.R.bool.config_sms_force_7bit_encoding)) { + newMsgBody = Sms7BitEncodingTranslator.translate(text); + } + if (TextUtils.isEmpty(newMsgBody)) { + newMsgBody = text; + } + int pos = 0; // Index in code units. + int textLen = newMsgBody.length(); + ArrayList<String> result = new ArrayList<String>(ted.msgCount); + while (pos < textLen) { + int nextPos = 0; // Counts code units. + if (ted.codeUnitSize == SmsConstants.ENCODING_7BIT) { + if (useCdmaFormatForMoSms() && ted.msgCount == 1) { + // For a singleton CDMA message, the encoding must be ASCII... + nextPos = pos + Math.min(limit, textLen - pos); + } else { + // For multi-segment messages, CDMA 7bit equals GSM 7bit encoding (EMS mode). + nextPos = GsmAlphabet.findGsmSeptetLimitIndex(newMsgBody, pos, limit, + ted.languageTable, ted.languageShiftTable); + } + } else { // Assume unicode. + nextPos = SmsMessageBase.findNextUnicodePosition(pos, limit, newMsgBody); + } + if ((nextPos <= pos) || (nextPos > textLen)) { + Rlog.e(LOG_TAG, "fragmentText failed (" + pos + " >= " + nextPos + " or " + + nextPos + " >= " + textLen + ")"); + break; + } + result.add(newMsgBody.substring(pos, nextPos)); + pos = nextPos; + } + return result; + } + + /** + * Calculates the number of SMS's required to encode the message body and + * the number of characters remaining until the next message, given the + * current encoding. + * + * @param messageBody the message to encode + * @param use7bitOnly if true, characters that are not part of the radio + * specific (GSM / CDMA) alphabet encoding are converted to as a + * single space characters. If false, a messageBody containing + * non-GSM or non-CDMA alphabet characters are encoded using + * 16-bit encoding. + * @return an int[4] with int[0] being the number of SMS's required, int[1] + * the number of code units used, and int[2] is the number of code + * units remaining until the next message. int[3] is the encoding + * type that should be used for the message. + */ + public static int[] calculateLength(String messageBody, boolean use7bitOnly) { + return calculateLength((CharSequence)messageBody, use7bitOnly); + } + + /* + * TODO(cleanup): It looks like there is now no useful reason why + * apps should generate pdus themselves using these routines, + * instead of handing the raw data to SMSDispatcher (and thereby + * have the phone process do the encoding). Moreover, CDMA now + * has shared state (in the form of the msgId system property) + * which can only be modified by the phone process, and hence + * makes the output of these routines incorrect. Since they now + * serve no purpose, they should probably just return null + * directly, and be deprecated. Going further in that direction, + * the above parsers of serialized pdu data should probably also + * be gotten rid of, hiding all but the necessarily visible + * structured data from client apps. A possible concern with + * doing this is that apps may be using these routines to generate + * pdus that are then sent elsewhere, some network server, for + * example, and that always returning null would thereby break + * otherwise useful apps. + */ + + /** + * Get an SMS-SUBMIT PDU for a destination address and a message. + * This method will not attempt to use any GSM national language 7 bit encodings. + * + * @param scAddress Service Centre address. Null means use default. + * @return a <code>SubmitPdu</code> containing the encoded SC + * address, if applicable, and the encoded message. + * Returns null on encode error. + */ + public static SubmitPdu getSubmitPdu(String scAddress, + String destinationAddress, String message, boolean statusReportRequested) { + return getSubmitPdu(scAddress, destinationAddress, message, statusReportRequested, + SubscriptionManager.getDefaultSmsSubscriptionId()); + } + + /** + * Get an SMS-SUBMIT PDU for a destination address and a message. + * This method will not attempt to use any GSM national language 7 bit encodings. + * + * @param scAddress Service Centre address. Null means use default. + * @param destinationAddress the address of the destination for the message. + * @param message String representation of the message payload. + * @param statusReportRequested Indicates whether a report is requested for this message. + * @param subId Subscription of the message + * @return a <code>SubmitPdu</code> containing the encoded SC + * address, if applicable, and the encoded message. + * Returns null on encode error. + * @hide + */ + public static SubmitPdu getSubmitPdu(String scAddress, + String destinationAddress, String message, boolean statusReportRequested, int subId) { + SubmitPduBase spb; + if (useCdmaFormatForMoSms(subId)) { + spb = com.android.internal.telephony.cdma.SmsMessage.getSubmitPdu(scAddress, + destinationAddress, message, statusReportRequested, null); + } else { + spb = com.android.internal.telephony.gsm.SmsMessage.getSubmitPdu(scAddress, + destinationAddress, message, statusReportRequested); + } + + return new SubmitPdu(spb); + } + + /** + * Get an SMS-SUBMIT PDU for a data message to a destination address & port. + * This method will not attempt to use any GSM national language 7 bit encodings. + * + * @param scAddress Service Centre address. null == use default + * @param destinationAddress the address of the destination for the message + * @param destinationPort the port to deliver the message to at the + * destination + * @param data the data for the message + * @return a <code>SubmitPdu</code> containing the encoded SC + * address, if applicable, and the encoded message. + * Returns null on encode error. + */ + public static SubmitPdu getSubmitPdu(String scAddress, + String destinationAddress, short destinationPort, byte[] data, + boolean statusReportRequested) { + SubmitPduBase spb; + + if (useCdmaFormatForMoSms()) { + spb = com.android.internal.telephony.cdma.SmsMessage.getSubmitPdu(scAddress, + destinationAddress, destinationPort, data, statusReportRequested); + } else { + spb = com.android.internal.telephony.gsm.SmsMessage.getSubmitPdu(scAddress, + destinationAddress, destinationPort, data, statusReportRequested); + } + + return new SubmitPdu(spb); + } + + /** + * Returns the address of the SMS service center that relayed this message + * or null if there is none. + */ + public String getServiceCenterAddress() { + return mWrappedSmsMessage.getServiceCenterAddress(); + } + + /** + * Returns the originating address (sender) of this SMS message in String + * form or null if unavailable + */ + public String getOriginatingAddress() { + return mWrappedSmsMessage.getOriginatingAddress(); + } + + /** + * Returns the originating address, or email from address if this message + * was from an email gateway. Returns null if originating address + * unavailable. + */ + public String getDisplayOriginatingAddress() { + return mWrappedSmsMessage.getDisplayOriginatingAddress(); + } + + /** + * Returns the message body as a String, if it exists and is text based. + * @return message body is there is one, otherwise null + */ + public String getMessageBody() { + return mWrappedSmsMessage.getMessageBody(); + } + + /** + * Returns the class of this message. + */ + public MessageClass getMessageClass() { + switch(mWrappedSmsMessage.getMessageClass()) { + case CLASS_0: return MessageClass.CLASS_0; + case CLASS_1: return MessageClass.CLASS_1; + case CLASS_2: return MessageClass.CLASS_2; + case CLASS_3: return MessageClass.CLASS_3; + default: return MessageClass.UNKNOWN; + + } + } + + /** + * Returns the message body, or email message body if this message was from + * an email gateway. Returns null if message body unavailable. + */ + public String getDisplayMessageBody() { + return mWrappedSmsMessage.getDisplayMessageBody(); + } + + /** + * Unofficial convention of a subject line enclosed in parens empty string + * if not present + */ + public String getPseudoSubject() { + return mWrappedSmsMessage.getPseudoSubject(); + } + + /** + * Returns the service centre timestamp in currentTimeMillis() format + */ + public long getTimestampMillis() { + return mWrappedSmsMessage.getTimestampMillis(); + } + + /** + * Returns true if message is an email. + * + * @return true if this message came through an email gateway and email + * sender / subject / parsed body are available + */ + public boolean isEmail() { + return mWrappedSmsMessage.isEmail(); + } + + /** + * @return if isEmail() is true, body of the email sent through the gateway. + * null otherwise + */ + public String getEmailBody() { + return mWrappedSmsMessage.getEmailBody(); + } + + /** + * @return if isEmail() is true, email from address of email sent through + * the gateway. null otherwise + */ + public String getEmailFrom() { + return mWrappedSmsMessage.getEmailFrom(); + } + + /** + * Get protocol identifier. + */ + public int getProtocolIdentifier() { + return mWrappedSmsMessage.getProtocolIdentifier(); + } + + /** + * See TS 23.040 9.2.3.9 returns true if this is a "replace short message" + * SMS + */ + public boolean isReplace() { + return mWrappedSmsMessage.isReplace(); + } + + /** + * Returns true for CPHS MWI toggle message. + * + * @return true if this is a CPHS MWI toggle message See CPHS 4.2 section + * B.4.2 + */ + public boolean isCphsMwiMessage() { + return mWrappedSmsMessage.isCphsMwiMessage(); + } + + /** + * returns true if this message is a CPHS voicemail / message waiting + * indicator (MWI) clear message + */ + public boolean isMWIClearMessage() { + return mWrappedSmsMessage.isMWIClearMessage(); + } + + /** + * returns true if this message is a CPHS voicemail / message waiting + * indicator (MWI) set message + */ + public boolean isMWISetMessage() { + return mWrappedSmsMessage.isMWISetMessage(); + } + + /** + * returns true if this message is a "Message Waiting Indication Group: + * Discard Message" notification and should not be stored. + */ + public boolean isMwiDontStore() { + return mWrappedSmsMessage.isMwiDontStore(); + } + + /** + * returns the user data section minus the user data header if one was + * present. + */ + public byte[] getUserData() { + return mWrappedSmsMessage.getUserData(); + } + + /** + * Returns the raw PDU for the message. + * + * @return the raw PDU for the message. + */ + public byte[] getPdu() { + return mWrappedSmsMessage.getPdu(); + } + + /** + * Returns the status of the message on the SIM (read, unread, sent, unsent). + * + * @return the status of the message on the SIM. These are: + * SmsManager.STATUS_ON_SIM_FREE + * SmsManager.STATUS_ON_SIM_READ + * SmsManager.STATUS_ON_SIM_UNREAD + * SmsManager.STATUS_ON_SIM_SEND + * SmsManager.STATUS_ON_SIM_UNSENT + * @deprecated Use getStatusOnIcc instead. + */ + @Deprecated public int getStatusOnSim() { + return mWrappedSmsMessage.getStatusOnIcc(); + } + + /** + * Returns the status of the message on the ICC (read, unread, sent, unsent). + * + * @return the status of the message on the ICC. These are: + * SmsManager.STATUS_ON_ICC_FREE + * SmsManager.STATUS_ON_ICC_READ + * SmsManager.STATUS_ON_ICC_UNREAD + * SmsManager.STATUS_ON_ICC_SEND + * SmsManager.STATUS_ON_ICC_UNSENT + */ + public int getStatusOnIcc() { + return mWrappedSmsMessage.getStatusOnIcc(); + } + + /** + * Returns the record index of the message on the SIM (1-based index). + * @return the record index of the message on the SIM, or -1 if this + * SmsMessage was not created from a SIM SMS EF record. + * @deprecated Use getIndexOnIcc instead. + */ + @Deprecated public int getIndexOnSim() { + return mWrappedSmsMessage.getIndexOnIcc(); + } + + /** + * Returns the record index of the message on the ICC (1-based index). + * @return the record index of the message on the ICC, or -1 if this + * SmsMessage was not created from a ICC SMS EF record. + */ + public int getIndexOnIcc() { + return mWrappedSmsMessage.getIndexOnIcc(); + } + + /** + * GSM: + * For an SMS-STATUS-REPORT message, this returns the status field from + * the status report. This field indicates the status of a previously + * submitted SMS, if requested. See TS 23.040, 9.2.3.15 TP-Status for a + * description of values. + * CDMA: + * For not interfering with status codes from GSM, the value is + * shifted to the bits 31-16. + * The value is composed of an error class (bits 25-24) and a status code (bits 23-16). + * Possible codes are described in C.S0015-B, v2.0, 4.5.21. + * + * @return 0 indicates the previously sent message was received. + * See TS 23.040, 9.9.2.3.15 and C.S0015-B, v2.0, 4.5.21 + * for a description of other possible values. + */ + public int getStatus() { + return mWrappedSmsMessage.getStatus(); + } + + /** + * Return true iff the message is a SMS-STATUS-REPORT message. + */ + public boolean isStatusReportMessage() { + return mWrappedSmsMessage.isStatusReportMessage(); + } + + /** + * Returns true iff the <code>TP-Reply-Path</code> bit is set in + * this message. + */ + public boolean isReplyPathPresent() { + return mWrappedSmsMessage.isReplyPathPresent(); + } + + /** + * Determines whether or not to use CDMA format for MO SMS. + * If SMS over IMS is supported, then format is based on IMS SMS format, + * otherwise format is based on current phone type. + * + * @return true if Cdma format should be used for MO SMS, false otherwise. + */ + private static boolean useCdmaFormatForMoSms() { + // IMS is registered with SMS support, check the SMS format supported + return useCdmaFormatForMoSms(SubscriptionManager.getDefaultSmsSubscriptionId()); + } + + /** + * Determines whether or not to use CDMA format for MO SMS. + * If SMS over IMS is supported, then format is based on IMS SMS format, + * otherwise format is based on current phone type. + * + * @param subId Subscription for which phone type is returned. + * + * @return true if Cdma format should be used for MO SMS, false otherwise. + */ + private static boolean useCdmaFormatForMoSms(int subId) { + SmsManager smsManager = SmsManager.getSmsManagerForSubscriptionId(subId); + if (!smsManager.isImsSmsSupported()) { + // use Voice technology to determine SMS format. + return isCdmaVoice(subId); + } + // IMS is registered with SMS support, check the SMS format supported + return (SmsConstants.FORMAT_3GPP2.equals(smsManager.getImsSmsFormat())); + } + + /** + * Determines whether or not to current phone type is cdma. + * + * @return true if current phone type is cdma, false otherwise. + */ + private static boolean isCdmaVoice() { + return isCdmaVoice(SubscriptionManager.getDefaultSmsSubscriptionId()); + } + + /** + * Determines whether or not to current phone type is cdma + * + * @return true if current phone type is cdma, false otherwise. + */ + private static boolean isCdmaVoice(int subId) { + int activePhone = TelephonyManager.getDefault().getCurrentPhoneType(subId); + return (PHONE_TYPE_CDMA == activePhone); + } + /** + * Decide if the carrier supports long SMS. + * {@hide} + */ + public static boolean hasEmsSupport() { + if (!isNoEmsSupportConfigListExisted()) { + return true; + } + + String simOperator; + String gid; + final long identity = Binder.clearCallingIdentity(); + try { + simOperator = TelephonyManager.getDefault().getSimOperatorNumeric(); + gid = TelephonyManager.getDefault().getGroupIdLevel1(); + } finally { + Binder.restoreCallingIdentity(identity); + } + + if (!TextUtils.isEmpty(simOperator)) { + for (NoEmsSupportConfig currentConfig : mNoEmsSupportConfigList) { + if (simOperator.startsWith(currentConfig.mOperatorNumber) && + (TextUtils.isEmpty(currentConfig.mGid1) || + (!TextUtils.isEmpty(currentConfig.mGid1) && + currentConfig.mGid1.equalsIgnoreCase(gid)))) { + return false; + } + } + } + return true; + } + + /** + * Check where to add " x/y" in each SMS segment, begin or end. + * {@hide} + */ + public static boolean shouldAppendPageNumberAsPrefix() { + if (!isNoEmsSupportConfigListExisted()) { + return false; + } + + String simOperator; + String gid; + final long identity = Binder.clearCallingIdentity(); + try { + simOperator = TelephonyManager.getDefault().getSimOperatorNumeric(); + gid = TelephonyManager.getDefault().getGroupIdLevel1(); + } finally { + Binder.restoreCallingIdentity(identity); + } + + for (NoEmsSupportConfig currentConfig : mNoEmsSupportConfigList) { + if (simOperator.startsWith(currentConfig.mOperatorNumber) && + (TextUtils.isEmpty(currentConfig.mGid1) || + (!TextUtils.isEmpty(currentConfig.mGid1) + && currentConfig.mGid1.equalsIgnoreCase(gid)))) { + return currentConfig.mIsPrefix; + } + } + return false; + } + + private static class NoEmsSupportConfig { + String mOperatorNumber; + String mGid1; + boolean mIsPrefix; + + public NoEmsSupportConfig(String[] config) { + mOperatorNumber = config[0]; + mIsPrefix = "prefix".equals(config[1]); + mGid1 = config.length > 2 ? config[2] : null; + } + + @Override + public String toString() { + return "NoEmsSupportConfig { mOperatorNumber = " + mOperatorNumber + + ", mIsPrefix = " + mIsPrefix + ", mGid1 = " + mGid1 + " }"; + } + } + + private static NoEmsSupportConfig[] mNoEmsSupportConfigList = null; + private static boolean mIsNoEmsSupportConfigListLoaded = false; + + private static boolean isNoEmsSupportConfigListExisted() { + if (!mIsNoEmsSupportConfigListLoaded) { + Resources r = Resources.getSystem(); + if (r != null) { + String[] listArray = r.getStringArray( + com.android.internal.R.array.no_ems_support_sim_operators); + if ((listArray != null) && (listArray.length > 0)) { + mNoEmsSupportConfigList = new NoEmsSupportConfig[listArray.length]; + for (int i=0; i<listArray.length; i++) { + mNoEmsSupportConfigList[i] = new NoEmsSupportConfig(listArray[i].split(";")); + } + } + mIsNoEmsSupportConfigListLoaded = true; + } + } + + if (mNoEmsSupportConfigList != null && mNoEmsSupportConfigList.length != 0) { + return true; + } + + return false; + } +} diff --git a/android/telephony/SubscriptionInfo.java b/android/telephony/SubscriptionInfo.java new file mode 100644 index 00000000..4e1c15fa --- /dev/null +++ b/android/telephony/SubscriptionInfo.java @@ -0,0 +1,469 @@ +/* + * Copyright (C) 2014 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.telephony; + +import android.annotation.Nullable; +import android.content.Context; +import android.content.pm.PackageInfo; +import android.content.pm.PackageManager; +import android.graphics.Bitmap; +import android.graphics.Canvas; +import android.graphics.Color; +import android.graphics.Paint; +import android.graphics.PorterDuff; +import android.graphics.PorterDuffColorFilter; +import android.graphics.Rect; +import android.graphics.Typeface; +import android.os.Build; +import android.os.Parcel; +import android.os.Parcelable; +import android.util.DisplayMetrics; + +import java.util.Arrays; + +/** + * A Parcelable class for Subscription Information. + */ +public class SubscriptionInfo implements Parcelable { + + /** + * Size of text to render on the icon. + */ + private static final int TEXT_SIZE = 16; + + /** + * Subscription Identifier, this is a device unique number + * and not an index into an array + */ + private int mId; + + /** + * The GID for a SIM that maybe associated with this subscription, empty if unknown + */ + private String mIccId; + + /** + * The index of the slot that currently contains the subscription + * and not necessarily unique and maybe INVALID_SLOT_ID if unknown + */ + private int mSimSlotIndex; + + /** + * The name displayed to the user that identifies this subscription + */ + private CharSequence mDisplayName; + + /** + * String that identifies SPN/PLMN + * TODO : Add a new field that identifies only SPN for a sim + */ + private CharSequence mCarrierName; + + /** + * The source of the name, NAME_SOURCE_UNDEFINED, NAME_SOURCE_DEFAULT_SOURCE, + * NAME_SOURCE_SIM_SOURCE or NAME_SOURCE_USER_INPUT. + */ + private int mNameSource; + + /** + * The color to be used for tinting the icon when displaying to the user + */ + private int mIconTint; + + /** + * A number presented to the user identify this subscription + */ + private String mNumber; + + /** + * Data roaming state, DATA_RAOMING_ENABLE, DATA_RAOMING_DISABLE + */ + private int mDataRoaming; + + /** + * SIM Icon bitmap + */ + private Bitmap mIconBitmap; + + /** + * Mobile Country Code + */ + private int mMcc; + + /** + * Mobile Network Code + */ + private int mMnc; + + /** + * ISO Country code for the subscription's provider + */ + private String mCountryIso; + + /** + * Whether the subscription is an embedded one. + */ + private boolean mIsEmbedded; + + /** + * The access rules for this subscription, if it is embedded and defines any. + */ + @Nullable + private UiccAccessRule[] mAccessRules; + + /** + * @hide + */ + public SubscriptionInfo(int id, String iccId, int simSlotIndex, CharSequence displayName, + CharSequence carrierName, int nameSource, int iconTint, String number, int roaming, + Bitmap icon, int mcc, int mnc, String countryIso) { + this(id, iccId, simSlotIndex, displayName, carrierName, nameSource, iconTint, number, + roaming, icon, mcc, mnc, countryIso, false /* isEmbedded */, + null /* accessRules */); + } + + /** + * @hide + */ + public SubscriptionInfo(int id, String iccId, int simSlotIndex, CharSequence displayName, + CharSequence carrierName, int nameSource, int iconTint, String number, int roaming, + Bitmap icon, int mcc, int mnc, String countryIso, boolean isEmbedded, + @Nullable UiccAccessRule[] accessRules) { + this.mId = id; + this.mIccId = iccId; + this.mSimSlotIndex = simSlotIndex; + this.mDisplayName = displayName; + this.mCarrierName = carrierName; + this.mNameSource = nameSource; + this.mIconTint = iconTint; + this.mNumber = number; + this.mDataRoaming = roaming; + this.mIconBitmap = icon; + this.mMcc = mcc; + this.mMnc = mnc; + this.mCountryIso = countryIso; + this.mIsEmbedded = isEmbedded; + this.mAccessRules = accessRules; + } + + /** + * @return the subscription ID. + */ + public int getSubscriptionId() { + return this.mId; + } + + /** + * @return the ICC ID. + */ + public String getIccId() { + return this.mIccId; + } + + /** + * @return the slot index of this Subscription's SIM card. + */ + public int getSimSlotIndex() { + return this.mSimSlotIndex; + } + + /** + * @return the name displayed to the user that identifies this subscription + */ + public CharSequence getDisplayName() { + return this.mDisplayName; + } + + /** + * Sets the name displayed to the user that identifies this subscription + * @hide + */ + public void setDisplayName(CharSequence name) { + this.mDisplayName = name; + } + + /** + * @return the name displayed to the user that identifies Subscription provider name + */ + public CharSequence getCarrierName() { + return this.mCarrierName; + } + + /** + * Sets the name displayed to the user that identifies Subscription provider name + * @hide + */ + public void setCarrierName(CharSequence name) { + this.mCarrierName = name; + } + + /** + * @return the source of the name, eg NAME_SOURCE_UNDEFINED, NAME_SOURCE_DEFAULT_SOURCE, + * NAME_SOURCE_SIM_SOURCE or NAME_SOURCE_USER_INPUT. + * @hide + */ + public int getNameSource() { + return this.mNameSource; + } + + /** + * Creates and returns an icon {@code Bitmap} to represent this {@code SubscriptionInfo} in a user + * interface. + * + * @param context A {@code Context} to get the {@code DisplayMetrics}s from. + * + * @return A bitmap icon for this {@code SubscriptionInfo}. + */ + public Bitmap createIconBitmap(Context context) { + int width = mIconBitmap.getWidth(); + int height = mIconBitmap.getHeight(); + DisplayMetrics metrics = context.getResources().getDisplayMetrics(); + + // Create a new bitmap of the same size because it will be modified. + Bitmap workingBitmap = Bitmap.createBitmap(metrics, width, height, mIconBitmap.getConfig()); + + Canvas canvas = new Canvas(workingBitmap); + Paint paint = new Paint(); + + // Tint the icon with the color. + paint.setColorFilter(new PorterDuffColorFilter(mIconTint, PorterDuff.Mode.SRC_ATOP)); + canvas.drawBitmap(mIconBitmap, 0, 0, paint); + paint.setColorFilter(null); + + // Write the sim slot index. + paint.setAntiAlias(true); + paint.setTypeface(Typeface.create("sans-serif", Typeface.NORMAL)); + paint.setColor(Color.WHITE); + // Set text size scaled by density + paint.setTextSize(TEXT_SIZE * metrics.density); + // Convert sim slot index to localized string + final String index = String.format("%d", mSimSlotIndex + 1); + final Rect textBound = new Rect(); + paint.getTextBounds(index, 0, 1, textBound); + final float xOffset = (width / 2.f) - textBound.centerX(); + final float yOffset = (height / 2.f) - textBound.centerY(); + canvas.drawText(index, xOffset, yOffset, paint); + + return workingBitmap; + } + + /** + * A highlight color to use in displaying information about this {@code PhoneAccount}. + * + * @return A hexadecimal color value. + */ + public int getIconTint() { + return mIconTint; + } + + /** + * Sets the color displayed to the user that identifies this subscription + * @hide + */ + public void setIconTint(int iconTint) { + this.mIconTint = iconTint; + } + + /** + * @return the number of this subscription. + */ + public String getNumber() { + return mNumber; + } + + /** + * @return the data roaming state for this subscription, either + * {@link SubscriptionManager#DATA_ROAMING_ENABLE} or {@link SubscriptionManager#DATA_ROAMING_DISABLE}. + */ + public int getDataRoaming() { + return this.mDataRoaming; + } + + /** + * @return the MCC. + */ + public int getMcc() { + return this.mMcc; + } + + /** + * @return the MNC. + */ + public int getMnc() { + return this.mMnc; + } + + /** + * @return the ISO country code + */ + public String getCountryIso() { + return this.mCountryIso; + } + + /** + * @return whether the subscription is an embedded one. + * @hide + * + * TODO(b/35851809): Make this public. + */ + public boolean isEmbedded() { + return this.mIsEmbedded; + } + + /** + * Checks whether the app with the given context is authorized to manage this subscription + * according to its metadata. Only supported for embedded subscriptions (if {@link #isEmbedded} + * returns true). + * + * @param context Context of the application to check. + * @return whether the app is authorized to manage this subscription per its metadata. + * @throws UnsupportedOperationException if this subscription is not embedded. + * @hide + * + * TODO(b/35851809): Make this public. + */ + public boolean canManageSubscription(Context context) { + return canManageSubscription(context, context.getPackageName()); + } + + /** + * Checks whether the given app is authorized to manage this subscription according to its + * metadata. Only supported for embedded subscriptions (if {@link #isEmbedded} returns true). + * + * @param context Any context. + * @param packageName Package name of the app to check. + * @return whether the app is authorized to manage this subscription per its metadata. + * @throws UnsupportedOperationException if this subscription is not embedded. + * @hide + */ + public boolean canManageSubscription(Context context, String packageName) { + if (!isEmbedded()) { + throw new UnsupportedOperationException("Not an embedded subscription"); + } + if (mAccessRules == null) { + return false; + } + PackageManager packageManager = context.getPackageManager(); + PackageInfo packageInfo; + try { + packageInfo = packageManager.getPackageInfo(packageName, PackageManager.GET_SIGNATURES); + } catch (PackageManager.NameNotFoundException e) { + throw new IllegalArgumentException("Unknown package: " + packageName, e); + } + for (UiccAccessRule rule : mAccessRules) { + if (rule.getCarrierPrivilegeStatus(packageInfo) + == TelephonyManager.CARRIER_PRIVILEGE_STATUS_HAS_ACCESS) { + return true; + } + } + return false; + } + + /** + * @return the {@link UiccAccessRule}s dictating who is authorized to manage this subscription. + * @throws UnsupportedOperationException if this subscription is not embedded. + * @hide + * + * TODO(b/35851809): Make this a SystemApi. + */ + public @Nullable UiccAccessRule[] getAccessRules() { + if (!isEmbedded()) { + throw new UnsupportedOperationException("Not an embedded subscription"); + } + return mAccessRules; + } + + public static final Parcelable.Creator<SubscriptionInfo> CREATOR = new Parcelable.Creator<SubscriptionInfo>() { + @Override + public SubscriptionInfo createFromParcel(Parcel source) { + int id = source.readInt(); + String iccId = source.readString(); + int simSlotIndex = source.readInt(); + CharSequence displayName = source.readCharSequence(); + CharSequence carrierName = source.readCharSequence(); + int nameSource = source.readInt(); + int iconTint = source.readInt(); + String number = source.readString(); + int dataRoaming = source.readInt(); + int mcc = source.readInt(); + int mnc = source.readInt(); + String countryIso = source.readString(); + Bitmap iconBitmap = Bitmap.CREATOR.createFromParcel(source); + boolean isEmbedded = source.readBoolean(); + UiccAccessRule[] accessRules = source.createTypedArray(UiccAccessRule.CREATOR); + + return new SubscriptionInfo(id, iccId, simSlotIndex, displayName, carrierName, + nameSource, iconTint, number, dataRoaming, iconBitmap, mcc, mnc, countryIso, + isEmbedded, accessRules); + } + + @Override + public SubscriptionInfo[] newArray(int size) { + return new SubscriptionInfo[size]; + } + }; + + @Override + public void writeToParcel(Parcel dest, int flags) { + dest.writeInt(mId); + dest.writeString(mIccId); + dest.writeInt(mSimSlotIndex); + dest.writeCharSequence(mDisplayName); + dest.writeCharSequence(mCarrierName); + dest.writeInt(mNameSource); + dest.writeInt(mIconTint); + dest.writeString(mNumber); + dest.writeInt(mDataRoaming); + dest.writeInt(mMcc); + dest.writeInt(mMnc); + dest.writeString(mCountryIso); + mIconBitmap.writeToParcel(dest, flags); + dest.writeBoolean(mIsEmbedded); + dest.writeTypedArray(mAccessRules, flags); + } + + @Override + public int describeContents() { + return 0; + } + + /** + * @hide + */ + public static String givePrintableIccid(String iccId) { + String iccIdToPrint = null; + if (iccId != null) { + if (iccId.length() > 9 && !Build.IS_DEBUGGABLE) { + iccIdToPrint = iccId.substring(0, 9) + Rlog.pii(false, iccId.substring(9)); + } else { + iccIdToPrint = iccId; + } + } + return iccIdToPrint; + } + + @Override + public String toString() { + String iccIdToPrint = givePrintableIccid(mIccId); + return "{id=" + mId + ", iccId=" + iccIdToPrint + " simSlotIndex=" + mSimSlotIndex + + " displayName=" + mDisplayName + " carrierName=" + mCarrierName + + " nameSource=" + mNameSource + " iconTint=" + mIconTint + + " dataRoaming=" + mDataRoaming + " iconBitmap=" + mIconBitmap + " mcc " + mMcc + + " mnc " + mMnc + " isEmbedded " + mIsEmbedded + + " accessRules " + Arrays.toString(mAccessRules) + "}"; + } +} diff --git a/android/telephony/SubscriptionManager.java b/android/telephony/SubscriptionManager.java new file mode 100644 index 00000000..88f4880a --- /dev/null +++ b/android/telephony/SubscriptionManager.java @@ -0,0 +1,1603 @@ +/* + * Copyright (C) 2014 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.telephony; + +import android.annotation.NonNull; +import android.annotation.SdkConstant; +import android.annotation.SystemApi; +import android.annotation.SdkConstant.SdkConstantType; +import android.annotation.SystemService; +import android.content.Context; +import android.content.Intent; +import android.content.res.Configuration; +import android.content.res.Resources; +import android.net.INetworkPolicyManager; +import android.net.Uri; +import android.os.Handler; +import android.os.Message; +import android.os.RemoteException; +import android.os.ServiceManager; +import android.util.DisplayMetrics; +import com.android.internal.telephony.IOnSubscriptionsChangedListener; +import com.android.internal.telephony.ISub; +import com.android.internal.telephony.ITelephonyRegistry; +import com.android.internal.telephony.PhoneConstants; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collections; +import java.util.List; + +/** + * SubscriptionManager is the application interface to SubscriptionController + * and provides information about the current Telephony Subscriptions. + * <p> + * All SDK public methods require android.Manifest.permission.READ_PHONE_STATE unless otherwise + * specified. + */ +@SystemService(Context.TELEPHONY_SUBSCRIPTION_SERVICE) +public class SubscriptionManager { + private static final String LOG_TAG = "SubscriptionManager"; + private static final boolean DBG = false; + private static final boolean VDBG = false; + + /** An invalid subscription identifier */ + public static final int INVALID_SUBSCRIPTION_ID = -1; + + /** Base value for Dummy SUBSCRIPTION_ID's. */ + /** FIXME: Remove DummySubId's, but for now have them map just below INVALID_SUBSCRIPTION_ID + /** @hide */ + public static final int DUMMY_SUBSCRIPTION_ID_BASE = INVALID_SUBSCRIPTION_ID - 1; + + /** An invalid phone identifier */ + /** @hide */ + public static final int INVALID_PHONE_INDEX = -1; + + /** An invalid slot identifier */ + /** @hide */ + public static final int INVALID_SIM_SLOT_INDEX = -1; + + /** Indicates the caller wants the default sub id. */ + /** @hide */ + public static final int DEFAULT_SUBSCRIPTION_ID = Integer.MAX_VALUE; + + /** + * Indicates the caller wants the default phone id. + * Used in SubscriptionController and Phone but do we really need it??? + * @hide + */ + public static final int DEFAULT_PHONE_INDEX = Integer.MAX_VALUE; + + /** Indicates the caller wants the default slot id. NOT used remove? */ + /** @hide */ + public static final int DEFAULT_SIM_SLOT_INDEX = Integer.MAX_VALUE; + + /** Minimum possible subid that represents a subscription */ + /** @hide */ + public static final int MIN_SUBSCRIPTION_ID_VALUE = 0; + + /** Maximum possible subid that represents a subscription */ + /** @hide */ + public static final int MAX_SUBSCRIPTION_ID_VALUE = DEFAULT_SUBSCRIPTION_ID - 1; + + /** @hide */ + public static final Uri CONTENT_URI = Uri.parse("content://telephony/siminfo"); + + /** + * TelephonyProvider unique key column name is the subscription id. + * <P>Type: TEXT (String)</P> + */ + /** @hide */ + public static final String UNIQUE_KEY_SUBSCRIPTION_ID = "_id"; + + /** + * TelephonyProvider column name for SIM ICC Identifier + * <P>Type: TEXT (String)</P> + */ + /** @hide */ + public static final String ICC_ID = "icc_id"; + + /** + * TelephonyProvider column name for user SIM_SlOT_INDEX + * <P>Type: INTEGER (int)</P> + */ + /** @hide */ + public static final String SIM_SLOT_INDEX = "sim_id"; + + /** SIM is not inserted */ + /** @hide */ + public static final int SIM_NOT_INSERTED = -1; + + /** + * TelephonyProvider column name for user displayed name. + * <P>Type: TEXT (String)</P> + */ + /** @hide */ + public static final String DISPLAY_NAME = "display_name"; + + /** + * TelephonyProvider column name for the service provider name for the SIM. + * <P>Type: TEXT (String)</P> + */ + /** @hide */ + public static final String CARRIER_NAME = "carrier_name"; + + /** + * Default name resource + * @hide + */ + public static final int DEFAULT_NAME_RES = com.android.internal.R.string.unknownName; + + /** + * TelephonyProvider column name for source of the user displayed name. + * <P>Type: INT (int)</P> with one of the NAME_SOURCE_XXXX values below + * + * @hide + */ + public static final String NAME_SOURCE = "name_source"; + + /** + * The name_source is undefined + * @hide + */ + public static final int NAME_SOURCE_UNDEFINDED = -1; + + /** + * The name_source is the default + * @hide + */ + public static final int NAME_SOURCE_DEFAULT_SOURCE = 0; + + /** + * The name_source is from the SIM + * @hide + */ + public static final int NAME_SOURCE_SIM_SOURCE = 1; + + /** + * The name_source is from the user + * @hide + */ + public static final int NAME_SOURCE_USER_INPUT = 2; + + /** + * TelephonyProvider column name for the color of a SIM. + * <P>Type: INTEGER (int)</P> + */ + /** @hide */ + public static final String COLOR = "color"; + + /** @hide */ + public static final int COLOR_1 = 0; + + /** @hide */ + public static final int COLOR_2 = 1; + + /** @hide */ + public static final int COLOR_3 = 2; + + /** @hide */ + public static final int COLOR_4 = 3; + + /** @hide */ + public static final int COLOR_DEFAULT = COLOR_1; + + /** + * TelephonyProvider column name for the phone number of a SIM. + * <P>Type: TEXT (String)</P> + */ + /** @hide */ + public static final String NUMBER = "number"; + + /** + * TelephonyProvider column name for the number display format of a SIM. + * <P>Type: INTEGER (int)</P> + */ + /** @hide */ + public static final String DISPLAY_NUMBER_FORMAT = "display_number_format"; + + /** @hide */ + public static final int DISPLAY_NUMBER_NONE = 0; + + /** @hide */ + public static final int DISPLAY_NUMBER_FIRST = 1; + + /** @hide */ + public static final int DISPLAY_NUMBER_LAST = 2; + + /** @hide */ + public static final int DISPLAY_NUMBER_DEFAULT = DISPLAY_NUMBER_FIRST; + + /** + * TelephonyProvider column name for permission for data roaming of a SIM. + * <P>Type: INTEGER (int)</P> + */ + /** @hide */ + public static final String DATA_ROAMING = "data_roaming"; + + /** Indicates that data roaming is enabled for a subscription */ + public static final int DATA_ROAMING_ENABLE = 1; + + /** Indicates that data roaming is disabled for a subscription */ + public static final int DATA_ROAMING_DISABLE = 0; + + /** @hide */ + public static final int DATA_ROAMING_DEFAULT = DATA_ROAMING_DISABLE; + + /** @hide */ + public static final int SIM_PROVISIONED = 0; + + /** + * TelephonyProvider column name for the MCC associated with a SIM. + * <P>Type: INTEGER (int)</P> + * @hide + */ + public static final String MCC = "mcc"; + + /** + * TelephonyProvider column name for the MNC associated with a SIM. + * <P>Type: INTEGER (int)</P> + * @hide + */ + public static final String MNC = "mnc"; + + /** + * TelephonyProvider column name for the sim provisioning status associated with a SIM. + * <P>Type: INTEGER (int)</P> + * @hide + */ + public static final String SIM_PROVISIONING_STATUS = "sim_provisioning_status"; + + /** + * TelephonyProvider column name for whether a subscription is embedded (that is, present on an + * eSIM). + * <p>Type: INTEGER (int), 1 for embedded or 0 for non-embedded. + * @hide + */ + public static final String IS_EMBEDDED = "is_embedded"; + + /** + * TelephonyProvider column name for the encoded {@link UiccAccessRule}s from + * {@link UiccAccessRule#encodeRules}. Only present if {@link #IS_EMBEDDED} is 1. + * <p>TYPE: BLOB + * @hide + */ + public static final String ACCESS_RULES = "access_rules"; + + /** + * TelephonyProvider column name identifying whether an embedded subscription is on a removable + * card. Such subscriptions are marked inaccessible as soon as the current card is removed. + * Otherwise, they will remain accessible unless explicitly deleted. Only present if + * {@link #IS_EMBEDDED} is 1. + * <p>TYPE: INTEGER (int), 1 for removable or 0 for non-removable. + * @hide + */ + public static final String IS_REMOVABLE = "is_removable"; + + /** + * TelephonyProvider column name for extreme threat in CB settings + * @hide + */ + public static final String CB_EXTREME_THREAT_ALERT = "enable_cmas_extreme_threat_alerts"; + + /** + * TelephonyProvider column name for severe threat in CB settings + *@hide + */ + public static final String CB_SEVERE_THREAT_ALERT = "enable_cmas_severe_threat_alerts"; + + /** + * TelephonyProvider column name for amber alert in CB settings + *@hide + */ + public static final String CB_AMBER_ALERT = "enable_cmas_amber_alerts"; + + /** + * TelephonyProvider column name for emergency alert in CB settings + *@hide + */ + public static final String CB_EMERGENCY_ALERT = "enable_emergency_alerts"; + + /** + * TelephonyProvider column name for alert sound duration in CB settings + *@hide + */ + public static final String CB_ALERT_SOUND_DURATION = "alert_sound_duration"; + + /** + * TelephonyProvider column name for alert reminder interval in CB settings + *@hide + */ + public static final String CB_ALERT_REMINDER_INTERVAL = "alert_reminder_interval"; + + /** + * TelephonyProvider column name for enabling vibrate in CB settings + *@hide + */ + public static final String CB_ALERT_VIBRATE = "enable_alert_vibrate"; + + /** + * TelephonyProvider column name for enabling alert speech in CB settings + *@hide + */ + public static final String CB_ALERT_SPEECH = "enable_alert_speech"; + + /** + * TelephonyProvider column name for ETWS test alert in CB settings + *@hide + */ + public static final String CB_ETWS_TEST_ALERT = "enable_etws_test_alerts"; + + /** + * TelephonyProvider column name for enable channel50 alert in CB settings + *@hide + */ + public static final String CB_CHANNEL_50_ALERT = "enable_channel_50_alerts"; + + /** + * TelephonyProvider column name for CMAS test alert in CB settings + *@hide + */ + public static final String CB_CMAS_TEST_ALERT= "enable_cmas_test_alerts"; + + /** + * TelephonyProvider column name for Opt out dialog in CB settings + *@hide + */ + public static final String CB_OPT_OUT_DIALOG = "show_cmas_opt_out_dialog"; + + /** + * Broadcast Action: The user has changed one of the default subs related to + * data, phone calls, or sms</p> + * + * TODO: Change to a listener + * @hide + */ + @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION) + public static final String SUB_DEFAULT_CHANGED_ACTION = + "android.intent.action.SUB_DEFAULT_CHANGED"; + + /** + * Broadcast Action: The default subscription has changed. This has the following + * extra values:</p> + * The {@link #EXTRA_SUBSCRIPTION_INDEX} extra indicates the current default subscription index + */ + @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION) + public static final String ACTION_DEFAULT_SUBSCRIPTION_CHANGED + = "android.telephony.action.DEFAULT_SUBSCRIPTION_CHANGED"; + + /** + * Broadcast Action: The default sms subscription has changed. This has the following + * extra values:</p> + * {@link #EXTRA_SUBSCRIPTION_INDEX} extra indicates the current default sms + * subscription index + */ + @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION) + public static final String ACTION_DEFAULT_SMS_SUBSCRIPTION_CHANGED + = "android.telephony.action.DEFAULT_SMS_SUBSCRIPTION_CHANGED"; + + /** + * Integer extra used with {@link #ACTION_DEFAULT_SUBSCRIPTION_CHANGED} and + * {@link #ACTION_DEFAULT_SMS_SUBSCRIPTION_CHANGED} to indicate the subscription + * which has changed. + */ + public static final String EXTRA_SUBSCRIPTION_INDEX = "android.telephony.extra.SUBSCRIPTION_INDEX"; + + private final Context mContext; + + /** + * A listener class for monitoring changes to {@link SubscriptionInfo} records. + * <p> + * Override the onSubscriptionsChanged method in the object that extends this + * class and pass it to {@link #addOnSubscriptionsChangedListener(OnSubscriptionsChangedListener)} + * to register your listener and to unregister invoke + * {@link #removeOnSubscriptionsChangedListener(OnSubscriptionsChangedListener)} + * <p> + * Permissions android.Manifest.permission.READ_PHONE_STATE is required + * for #onSubscriptionsChanged to be invoked. + */ + public static class OnSubscriptionsChangedListener { + private final Handler mHandler = new Handler() { + @Override + public void handleMessage(Message msg) { + if (DBG) { + log("handleMessage: invoke the overriden onSubscriptionsChanged()"); + } + OnSubscriptionsChangedListener.this.onSubscriptionsChanged(); + } + }; + + /** + * Callback invoked when there is any change to any SubscriptionInfo. Typically + * this method would invoke {@link #getActiveSubscriptionInfoList} + */ + public void onSubscriptionsChanged() { + if (DBG) log("onSubscriptionsChanged: NOT OVERRIDDEN"); + } + + /** + * The callback methods need to be called on the handler thread where + * this object was created. If the binder did that for us it'd be nice. + */ + IOnSubscriptionsChangedListener callback = new IOnSubscriptionsChangedListener.Stub() { + @Override + public void onSubscriptionsChanged() { + if (DBG) log("callback: received, sendEmptyMessage(0) to handler"); + mHandler.sendEmptyMessage(0); + } + }; + + private void log(String s) { + Rlog.d(LOG_TAG, s); + } + } + + /** @hide */ + public SubscriptionManager(Context context) { + if (DBG) logd("SubscriptionManager created"); + mContext = context; + } + + /** + * Get an instance of the SubscriptionManager from the Context. + * This invokes {@link android.content.Context#getSystemService + * Context.getSystemService(Context.TELEPHONY_SUBSCRIPTION_SERVICE)}. + * + * @param context to use. + * @return SubscriptionManager instance + */ + public static SubscriptionManager from(Context context) { + return (SubscriptionManager) context.getSystemService( + Context.TELEPHONY_SUBSCRIPTION_SERVICE); + } + + /** + * Register for changes to the list of active {@link SubscriptionInfo} records or to the + * individual records themselves. When a change occurs the onSubscriptionsChanged method of + * the listener will be invoked immediately if there has been a notification. + * + * @param listener an instance of {@link OnSubscriptionsChangedListener} with + * onSubscriptionsChanged overridden. + */ + public void addOnSubscriptionsChangedListener(OnSubscriptionsChangedListener listener) { + String pkgForDebug = mContext != null ? mContext.getOpPackageName() : "<unknown>"; + if (DBG) { + logd("register OnSubscriptionsChangedListener pkgForDebug=" + pkgForDebug + + " listener=" + listener); + } + try { + // We use the TelephonyRegistry as it runs in the system and thus is always + // available. Where as SubscriptionController could crash and not be available + ITelephonyRegistry tr = ITelephonyRegistry.Stub.asInterface(ServiceManager.getService( + "telephony.registry")); + if (tr != null) { + tr.addOnSubscriptionsChangedListener(pkgForDebug, listener.callback); + } + } catch (RemoteException ex) { + // Should not happen + } + } + + /** + * Unregister the {@link OnSubscriptionsChangedListener}. This is not strictly necessary + * as the listener will automatically be unregistered if an attempt to invoke the listener + * fails. + * + * @param listener that is to be unregistered. + */ + public void removeOnSubscriptionsChangedListener(OnSubscriptionsChangedListener listener) { + String pkgForDebug = mContext != null ? mContext.getOpPackageName() : "<unknown>"; + if (DBG) { + logd("unregister OnSubscriptionsChangedListener pkgForDebug=" + pkgForDebug + + " listener=" + listener); + } + try { + // We use the TelephonyRegistry as its runs in the system and thus is always + // available where as SubscriptionController could crash and not be available + ITelephonyRegistry tr = ITelephonyRegistry.Stub.asInterface(ServiceManager.getService( + "telephony.registry")); + if (tr != null) { + tr.removeOnSubscriptionsChangedListener(pkgForDebug, listener.callback); + } + } catch (RemoteException ex) { + // Should not happen + } + } + + /** + * Get the active SubscriptionInfo with the input subId. + * + * @param subId The unique SubscriptionInfo key in database. + * @return SubscriptionInfo, maybe null if its not active. + */ + public SubscriptionInfo getActiveSubscriptionInfo(int subId) { + if (VDBG) logd("[getActiveSubscriptionInfo]+ subId=" + subId); + if (!isValidSubscriptionId(subId)) { + if (DBG) { + logd("[getActiveSubscriptionInfo]- invalid subId"); + } + return null; + } + + SubscriptionInfo subInfo = null; + + try { + ISub iSub = ISub.Stub.asInterface(ServiceManager.getService("isub")); + if (iSub != null) { + subInfo = iSub.getActiveSubscriptionInfo(subId, mContext.getOpPackageName()); + } + } catch (RemoteException ex) { + // ignore it + } + + return subInfo; + + } + + /** + * Get the active SubscriptionInfo associated with the iccId + * @param iccId the IccId of SIM card + * @return SubscriptionInfo, maybe null if its not active + * @hide + */ + public SubscriptionInfo getActiveSubscriptionInfoForIccIndex(String iccId) { + if (VDBG) logd("[getActiveSubscriptionInfoForIccIndex]+ iccId=" + iccId); + if (iccId == null) { + logd("[getActiveSubscriptionInfoForIccIndex]- null iccid"); + return null; + } + + SubscriptionInfo result = null; + + try { + ISub iSub = ISub.Stub.asInterface(ServiceManager.getService("isub")); + if (iSub != null) { + result = iSub.getActiveSubscriptionInfoForIccId(iccId, mContext.getOpPackageName()); + } + } catch (RemoteException ex) { + // ignore it + } + + return result; + } + + /** + * Get the active SubscriptionInfo associated with the slotIndex + * @param slotIndex the slot which the subscription is inserted + * @return SubscriptionInfo, maybe null if its not active + */ + public SubscriptionInfo getActiveSubscriptionInfoForSimSlotIndex(int slotIndex) { + if (VDBG) logd("[getActiveSubscriptionInfoForSimSlotIndex]+ slotIndex=" + slotIndex); + if (!isValidSlotIndex(slotIndex)) { + logd("[getActiveSubscriptionInfoForSimSlotIndex]- invalid slotIndex"); + return null; + } + + SubscriptionInfo result = null; + + try { + ISub iSub = ISub.Stub.asInterface(ServiceManager.getService("isub")); + if (iSub != null) { + result = iSub.getActiveSubscriptionInfoForSimSlotIndex(slotIndex, + mContext.getOpPackageName()); + } + } catch (RemoteException ex) { + // ignore it + } + + return result; + } + + /** + * @return List of all SubscriptionInfo records in database, + * include those that were inserted before, maybe empty but not null. + * @hide + */ + public List<SubscriptionInfo> getAllSubscriptionInfoList() { + if (VDBG) logd("[getAllSubscriptionInfoList]+"); + + List<SubscriptionInfo> result = null; + + try { + ISub iSub = ISub.Stub.asInterface(ServiceManager.getService("isub")); + if (iSub != null) { + result = iSub.getAllSubInfoList(mContext.getOpPackageName()); + } + } catch (RemoteException ex) { + // ignore it + } + + if (result == null) { + result = new ArrayList<>(); + } + return result; + } + + /** + * Get the SubscriptionInfo(s) of the currently inserted SIM(s). The records will be sorted + * by {@link SubscriptionInfo#getSimSlotIndex} then by {@link SubscriptionInfo#getSubscriptionId}. + * + * @return Sorted list of the currently {@link SubscriptionInfo} records available on the device. + * <ul> + * <li> + * If null is returned the current state is unknown but if a {@link OnSubscriptionsChangedListener} + * has been registered {@link OnSubscriptionsChangedListener#onSubscriptionsChanged} will be + * invoked in the future. + * </li> + * <li> + * If the list is empty then there are no {@link SubscriptionInfo} records currently available. + * </li> + * <li> + * if the list is non-empty the list is sorted by {@link SubscriptionInfo#getSimSlotIndex} + * then by {@link SubscriptionInfo#getSubscriptionId}. + * </li> + * </ul> + */ + public List<SubscriptionInfo> getActiveSubscriptionInfoList() { + List<SubscriptionInfo> result = null; + + try { + ISub iSub = ISub.Stub.asInterface(ServiceManager.getService("isub")); + if (iSub != null) { + result = iSub.getActiveSubscriptionInfoList(mContext.getOpPackageName()); + } + } catch (RemoteException ex) { + // ignore it + } + return result; + } + + /** + * Gets the SubscriptionInfo(s) of all available subscriptions, if any. + * + * <p>Available subscriptions include active ones (those with a non-negative + * {@link SubscriptionInfo#getSimSlotIndex()}) as well as inactive but installed embedded + * subscriptions. + * + * <p>The records will be sorted by {@link SubscriptionInfo#getSimSlotIndex} then by + * {@link SubscriptionInfo#getSubscriptionId}. + * + * @return Sorted list of the current {@link SubscriptionInfo} records available on the + * device. + * <ul> + * <li> + * If null is returned the current state is unknown but if a + * {@link OnSubscriptionsChangedListener} has been registered + * {@link OnSubscriptionsChangedListener#onSubscriptionsChanged} will be invoked in the future. + * <li> + * If the list is empty then there are no {@link SubscriptionInfo} records currently available. + * <li> + * if the list is non-empty the list is sorted by {@link SubscriptionInfo#getSimSlotIndex} + * then by {@link SubscriptionInfo#getSubscriptionId}. + * </ul> + * @hide + * + * TODO(b/35851809): Make this a SystemApi. + */ + public List<SubscriptionInfo> getAvailableSubscriptionInfoList() { + List<SubscriptionInfo> result = null; + + try { + ISub iSub = ISub.Stub.asInterface(ServiceManager.getService("isub")); + if (iSub != null) { + result = iSub.getAvailableSubscriptionInfoList(mContext.getOpPackageName()); + } + } catch (RemoteException ex) { + // ignore it + } + return result; + } + + /** + * Gets the SubscriptionInfo(s) of all embedded subscriptions accessible to the calling app, if + * any. + * + * <p>Only those subscriptions for which the calling app has carrier privileges per the + * subscription metadata, if any, will be included in the returned list. + * + * <p>The records will be sorted by {@link SubscriptionInfo#getSimSlotIndex} then by + * {@link SubscriptionInfo#getSubscriptionId}. + * + * @return Sorted list of the current embedded {@link SubscriptionInfo} records available on the + * device which are accessible to the caller. + * <ul> + * <li> + * If null is returned the current state is unknown but if a + * {@link OnSubscriptionsChangedListener} has been registered + * {@link OnSubscriptionsChangedListener#onSubscriptionsChanged} will be invoked in the future. + * <li> + * If the list is empty then there are no {@link SubscriptionInfo} records currently available. + * <li> + * if the list is non-empty the list is sorted by {@link SubscriptionInfo#getSimSlotIndex} + * then by {@link SubscriptionInfo#getSubscriptionId}. + * </ul> + * @hide + * + * TODO(b/35851809): Make this public. + */ + public List<SubscriptionInfo> getAccessibleSubscriptionInfoList() { + List<SubscriptionInfo> result = null; + + try { + ISub iSub = ISub.Stub.asInterface(ServiceManager.getService("isub")); + if (iSub != null) { + result = iSub.getAccessibleSubscriptionInfoList(mContext.getOpPackageName()); + } + } catch (RemoteException ex) { + // ignore it + } + return result; + } + + /** + * Request a refresh of the platform cache of profile information. + * + * <p>Should be called by the EuiccService implementation whenever this information changes due + * to an operation done outside the scope of a request initiated by the platform to the + * EuiccService. There is no need to refresh for downloads, deletes, or other operations that + * were made through the EuiccService. + * + * <p>Requires the {@link android.Manifest.permission#WRITE_EMBEDDED_SUBSCRIPTIONS} permission. + * @hide + * + * TODO(b/35851809): Make this a SystemApi. + */ + public void requestEmbeddedSubscriptionInfoListRefresh() { + try { + ISub iSub = ISub.Stub.asInterface(ServiceManager.getService("isub")); + if (iSub != null) { + iSub.requestEmbeddedSubscriptionInfoListRefresh(); + } + } catch (RemoteException ex) { + // ignore it + } + } + + /** + * @return the count of all subscriptions in the database, this includes + * all subscriptions that have been seen. + * @hide + */ + public int getAllSubscriptionInfoCount() { + if (VDBG) logd("[getAllSubscriptionInfoCount]+"); + + int result = 0; + + try { + ISub iSub = ISub.Stub.asInterface(ServiceManager.getService("isub")); + if (iSub != null) { + result = iSub.getAllSubInfoCount(mContext.getOpPackageName()); + } + } catch (RemoteException ex) { + // ignore it + } + + return result; + } + + /** + * @return the current number of active subscriptions. There is no guarantee the value + * returned by this method will be the same as the length of the list returned by + * {@link #getActiveSubscriptionInfoList}. + */ + public int getActiveSubscriptionInfoCount() { + int result = 0; + + try { + ISub iSub = ISub.Stub.asInterface(ServiceManager.getService("isub")); + if (iSub != null) { + result = iSub.getActiveSubInfoCount(mContext.getOpPackageName()); + } + } catch (RemoteException ex) { + // ignore it + } + + return result; + } + + /** + * @return the maximum number of active subscriptions that will be returned by + * {@link #getActiveSubscriptionInfoList} and the value returned by + * {@link #getActiveSubscriptionInfoCount}. + */ + public int getActiveSubscriptionInfoCountMax() { + int result = 0; + + try { + ISub iSub = ISub.Stub.asInterface(ServiceManager.getService("isub")); + if (iSub != null) { + result = iSub.getActiveSubInfoCountMax(); + } + } catch (RemoteException ex) { + // ignore it + } + + return result; + } + + /** + * Add a new SubscriptionInfo to SubscriptionInfo database if needed + * @param iccId the IccId of the SIM card + * @param slotIndex the slot which the SIM is inserted + * @return the URL of the newly created row or the updated row + * @hide + */ + public Uri addSubscriptionInfoRecord(String iccId, int slotIndex) { + if (VDBG) logd("[addSubscriptionInfoRecord]+ iccId:" + iccId + " slotIndex:" + slotIndex); + if (iccId == null) { + logd("[addSubscriptionInfoRecord]- null iccId"); + } + if (!isValidSlotIndex(slotIndex)) { + logd("[addSubscriptionInfoRecord]- invalid slotIndex"); + } + + try { + ISub iSub = ISub.Stub.asInterface(ServiceManager.getService("isub")); + if (iSub != null) { + // FIXME: This returns 1 on success, 0 on error should should we return it? + iSub.addSubInfoRecord(iccId, slotIndex); + } + } catch (RemoteException ex) { + // ignore it + } + + // FIXME: Always returns null? + return null; + + } + + /** + * Set SIM icon tint color by simInfo index + * @param tint the RGB value of icon tint color of the SIM + * @param subId the unique SubInfoRecord index in database + * @return the number of records updated + * @hide + */ + public int setIconTint(int tint, int subId) { + if (VDBG) logd("[setIconTint]+ tint:" + tint + " subId:" + subId); + if (!isValidSubscriptionId(subId)) { + logd("[setIconTint]- fail"); + return -1; + } + + int result = 0; + + try { + ISub iSub = ISub.Stub.asInterface(ServiceManager.getService("isub")); + if (iSub != null) { + result = iSub.setIconTint(tint, subId); + } + } catch (RemoteException ex) { + // ignore it + } + + return result; + + } + + /** + * Set display name by simInfo index + * @param displayName the display name of SIM card + * @param subId the unique SubscriptionInfo index in database + * @return the number of records updated + * @hide + */ + public int setDisplayName(String displayName, int subId) { + return setDisplayName(displayName, subId, NAME_SOURCE_UNDEFINDED); + } + + /** + * Set display name by simInfo index with name source + * @param displayName the display name of SIM card + * @param subId the unique SubscriptionInfo index in database + * @param nameSource 0: NAME_SOURCE_DEFAULT_SOURCE, 1: NAME_SOURCE_SIM_SOURCE, + * 2: NAME_SOURCE_USER_INPUT, -1 NAME_SOURCE_UNDEFINED + * @return the number of records updated or < 0 if invalid subId + * @hide + */ + public int setDisplayName(String displayName, int subId, long nameSource) { + if (VDBG) { + logd("[setDisplayName]+ displayName:" + displayName + " subId:" + subId + + " nameSource:" + nameSource); + } + if (!isValidSubscriptionId(subId)) { + logd("[setDisplayName]- fail"); + return -1; + } + + int result = 0; + + try { + ISub iSub = ISub.Stub.asInterface(ServiceManager.getService("isub")); + if (iSub != null) { + result = iSub.setDisplayNameUsingSrc(displayName, subId, nameSource); + } + } catch (RemoteException ex) { + // ignore it + } + + return result; + + } + + /** + * Set phone number by subId + * @param number the phone number of the SIM + * @param subId the unique SubscriptionInfo index in database + * @return the number of records updated + * @hide + */ + public int setDisplayNumber(String number, int subId) { + if (number == null || !isValidSubscriptionId(subId)) { + logd("[setDisplayNumber]- fail"); + return -1; + } + + int result = 0; + + try { + ISub iSub = ISub.Stub.asInterface(ServiceManager.getService("isub")); + if (iSub != null) { + result = iSub.setDisplayNumber(number, subId); + } + } catch (RemoteException ex) { + // ignore it + } + + return result; + + } + + /** + * Set data roaming by simInfo index + * @param roaming 0:Don't allow data when roaming, 1:Allow data when roaming + * @param subId the unique SubscriptionInfo index in database + * @return the number of records updated + * @hide + */ + public int setDataRoaming(int roaming, int subId) { + if (VDBG) logd("[setDataRoaming]+ roaming:" + roaming + " subId:" + subId); + if (roaming < 0 || !isValidSubscriptionId(subId)) { + logd("[setDataRoaming]- fail"); + return -1; + } + + int result = 0; + + try { + ISub iSub = ISub.Stub.asInterface(ServiceManager.getService("isub")); + if (iSub != null) { + result = iSub.setDataRoaming(roaming, subId); + } + } catch (RemoteException ex) { + // ignore it + } + + return result; + } + + /** + * Get slotIndex associated with the subscription. + * @return slotIndex as a positive integer or a negative value if an error either + * SIM_NOT_INSERTED or < 0 if an invalid slot index + * @hide + */ + public static int getSlotIndex(int subId) { + if (!isValidSubscriptionId(subId)) { + if (DBG) { + logd("[getSlotIndex]- fail"); + } + } + + int result = INVALID_SIM_SLOT_INDEX; + + try { + ISub iSub = ISub.Stub.asInterface(ServiceManager.getService("isub")); + if (iSub != null) { + result = iSub.getSlotIndex(subId); + } + } catch (RemoteException ex) { + // ignore it + } + + return result; + + } + + /** @hide */ + public static int[] getSubId(int slotIndex) { + if (!isValidSlotIndex(slotIndex)) { + logd("[getSubId]- fail"); + return null; + } + + int[] subId = null; + + try { + ISub iSub = ISub.Stub.asInterface(ServiceManager.getService("isub")); + if (iSub != null) { + subId = iSub.getSubId(slotIndex); + } + } catch (RemoteException ex) { + // ignore it + } + + return subId; + } + + /** @hide */ + public static int getPhoneId(int subId) { + if (!isValidSubscriptionId(subId)) { + if (DBG) { + logd("[getPhoneId]- fail"); + } + return INVALID_PHONE_INDEX; + } + + int result = INVALID_PHONE_INDEX; + + try { + ISub iSub = ISub.Stub.asInterface(ServiceManager.getService("isub")); + if (iSub != null) { + result = iSub.getPhoneId(subId); + } + } catch (RemoteException ex) { + // ignore it + } + + if (VDBG) logd("[getPhoneId]- phoneId=" + result); + return result; + + } + + private static void logd(String msg) { + Rlog.d(LOG_TAG, msg); + } + + /** + * Returns the system's default subscription id. + * + * For a voice capable device, it will return getDefaultVoiceSubscriptionId. + * For a data only device, it will return the getDefaultDataSubscriptionId. + * May return an INVALID_SUBSCRIPTION_ID on error. + * + * @return the "system" default subscription id. + */ + public static int getDefaultSubscriptionId() { + int subId = INVALID_SUBSCRIPTION_ID; + + try { + ISub iSub = ISub.Stub.asInterface(ServiceManager.getService("isub")); + if (iSub != null) { + subId = iSub.getDefaultSubId(); + } + } catch (RemoteException ex) { + // ignore it + } + + if (VDBG) logd("getDefaultSubId=" + subId); + return subId; + } + + /** + * Returns the system's default voice subscription id. + * + * On a data only device or on error, will return INVALID_SUBSCRIPTION_ID. + * + * @return the default voice subscription Id. + */ + public static int getDefaultVoiceSubscriptionId() { + int subId = INVALID_SUBSCRIPTION_ID; + + try { + ISub iSub = ISub.Stub.asInterface(ServiceManager.getService("isub")); + if (iSub != null) { + subId = iSub.getDefaultVoiceSubId(); + } + } catch (RemoteException ex) { + // ignore it + } + + if (VDBG) logd("getDefaultVoiceSubscriptionId, sub id = " + subId); + return subId; + } + + /** @hide */ + public void setDefaultVoiceSubId(int subId) { + if (VDBG) logd("setDefaultVoiceSubId sub id = " + subId); + try { + ISub iSub = ISub.Stub.asInterface(ServiceManager.getService("isub")); + if (iSub != null) { + iSub.setDefaultVoiceSubId(subId); + } + } catch (RemoteException ex) { + // ignore it + } + } + + /** + * Return the SubscriptionInfo for default voice subscription. + * + * Will return null on data only devices, or on error. + * + * @return the SubscriptionInfo for the default voice subscription. + * @hide + */ + public SubscriptionInfo getDefaultVoiceSubscriptionInfo() { + return getActiveSubscriptionInfo(getDefaultVoiceSubscriptionId()); + } + + /** @hide */ + public static int getDefaultVoicePhoneId() { + return getPhoneId(getDefaultVoiceSubscriptionId()); + } + + /** + * Returns the system's default SMS subscription id. + * + * On a data only device or on error, will return INVALID_SUBSCRIPTION_ID. + * + * @return the default SMS subscription Id. + */ + public static int getDefaultSmsSubscriptionId() { + int subId = INVALID_SUBSCRIPTION_ID; + + try { + ISub iSub = ISub.Stub.asInterface(ServiceManager.getService("isub")); + if (iSub != null) { + subId = iSub.getDefaultSmsSubId(); + } + } catch (RemoteException ex) { + // ignore it + } + + if (VDBG) logd("getDefaultSmsSubscriptionId, sub id = " + subId); + return subId; + } + + /** @hide */ + public void setDefaultSmsSubId(int subId) { + if (VDBG) logd("setDefaultSmsSubId sub id = " + subId); + try { + ISub iSub = ISub.Stub.asInterface(ServiceManager.getService("isub")); + if (iSub != null) { + iSub.setDefaultSmsSubId(subId); + } + } catch (RemoteException ex) { + // ignore it + } + } + + /** + * Return the SubscriptionInfo for default voice subscription. + * + * Will return null on data only devices, or on error. + * + * @return the SubscriptionInfo for the default SMS subscription. + * @hide + */ + public SubscriptionInfo getDefaultSmsSubscriptionInfo() { + return getActiveSubscriptionInfo(getDefaultSmsSubscriptionId()); + } + + /** @hide */ + public int getDefaultSmsPhoneId() { + return getPhoneId(getDefaultSmsSubscriptionId()); + } + + /** + * Returns the system's default data subscription id. + * + * On a voice only device or on error, will return INVALID_SUBSCRIPTION_ID. + * + * @return the default data subscription Id. + */ + public static int getDefaultDataSubscriptionId() { + int subId = INVALID_SUBSCRIPTION_ID; + + try { + ISub iSub = ISub.Stub.asInterface(ServiceManager.getService("isub")); + if (iSub != null) { + subId = iSub.getDefaultDataSubId(); + } + } catch (RemoteException ex) { + // ignore it + } + + if (VDBG) logd("getDefaultDataSubscriptionId, sub id = " + subId); + return subId; + } + + /** @hide */ + public void setDefaultDataSubId(int subId) { + if (VDBG) logd("setDataSubscription sub id = " + subId); + try { + ISub iSub = ISub.Stub.asInterface(ServiceManager.getService("isub")); + if (iSub != null) { + iSub.setDefaultDataSubId(subId); + } + } catch (RemoteException ex) { + // ignore it + } + } + + /** + * Return the SubscriptionInfo for default data subscription. + * + * Will return null on voice only devices, or on error. + * + * @return the SubscriptionInfo for the default data subscription. + * @hide + */ + public SubscriptionInfo getDefaultDataSubscriptionInfo() { + return getActiveSubscriptionInfo(getDefaultDataSubscriptionId()); + } + + /** @hide */ + public int getDefaultDataPhoneId() { + return getPhoneId(getDefaultDataSubscriptionId()); + } + + /** @hide */ + public void clearSubscriptionInfo() { + try { + ISub iSub = ISub.Stub.asInterface(ServiceManager.getService("isub")); + if (iSub != null) { + iSub.clearSubInfo(); + } + } catch (RemoteException ex) { + // ignore it + } + + return; + } + + //FIXME this is vulnerable to race conditions + /** @hide */ + public boolean allDefaultsSelected() { + if (!isValidSubscriptionId(getDefaultDataSubscriptionId())) { + return false; + } + if (!isValidSubscriptionId(getDefaultSmsSubscriptionId())) { + return false; + } + if (!isValidSubscriptionId(getDefaultVoiceSubscriptionId())) { + return false; + } + return true; + } + + /** + * If a default is set to subscription which is not active, this will reset that default back to + * an invalid subscription id, i.e. < 0. + * @hide + */ + public void clearDefaultsForInactiveSubIds() { + if (VDBG) logd("clearDefaultsForInactiveSubIds"); + try { + ISub iSub = ISub.Stub.asInterface(ServiceManager.getService("isub")); + if (iSub != null) { + iSub.clearDefaultsForInactiveSubIds(); + } + } catch (RemoteException ex) { + // ignore it + } + } + + /** + * @return true if a valid subId else false + * @hide + */ + public static boolean isValidSubscriptionId(int subId) { + return subId > INVALID_SUBSCRIPTION_ID ; + } + + /** + * @return true if subId is an usable subId value else false. A + * usable subId means its neither a INVALID_SUBSCRIPTION_ID nor a DEFAULT_SUB_ID. + * @hide + */ + public static boolean isUsableSubIdValue(int subId) { + return subId >= MIN_SUBSCRIPTION_ID_VALUE && subId <= MAX_SUBSCRIPTION_ID_VALUE; + } + + /** @hide */ + public static boolean isValidSlotIndex(int slotIndex) { + return slotIndex >= 0 && slotIndex < TelephonyManager.getDefault().getSimCount(); + } + + /** @hide */ + public static boolean isValidPhoneId(int phoneId) { + return phoneId >= 0 && phoneId < TelephonyManager.getDefault().getPhoneCount(); + } + + /** @hide */ + public static void putPhoneIdAndSubIdExtra(Intent intent, int phoneId) { + int[] subIds = SubscriptionManager.getSubId(phoneId); + if (subIds != null && subIds.length > 0) { + putPhoneIdAndSubIdExtra(intent, phoneId, subIds[0]); + } else { + logd("putPhoneIdAndSubIdExtra: no valid subs"); + } + } + + /** @hide */ + public static void putPhoneIdAndSubIdExtra(Intent intent, int phoneId, int subId) { + if (VDBG) logd("putPhoneIdAndSubIdExtra: phoneId=" + phoneId + " subId=" + subId); + intent.putExtra(PhoneConstants.SUBSCRIPTION_KEY, subId); + intent.putExtra(EXTRA_SUBSCRIPTION_INDEX, subId); + intent.putExtra(PhoneConstants.PHONE_KEY, phoneId); + //FIXME this is using phoneId and slotIndex interchangeably + //Eventually, this should be removed as it is not the slot id + intent.putExtra(PhoneConstants.SLOT_KEY, phoneId); + } + + /** + * @return the list of subId's that are active, + * is never null but the length maybe 0. + * @hide + */ + public @NonNull int[] getActiveSubscriptionIdList() { + int[] subId = null; + + try { + ISub iSub = ISub.Stub.asInterface(ServiceManager.getService("isub")); + if (iSub != null) { + subId = iSub.getActiveSubIdList(); + } + } catch (RemoteException ex) { + // ignore it + } + + if (subId == null) { + subId = new int[0]; + } + + return subId; + + } + + /** + * Returns true if the device is considered roaming on the current + * network for a subscription. + * <p> + * Availability: Only when user registered to a network. + * + * @param subId The subscription ID + * @return true if the network for the subscription is roaming, false otherwise + */ + public boolean isNetworkRoaming(int subId) { + final int phoneId = getPhoneId(subId); + if (phoneId < 0) { + // What else can we do? + return false; + } + return TelephonyManager.getDefault().isNetworkRoaming(subId); + } + + /** + * Returns a constant indicating the state of sim for the slot index. + * + * @param slotIndex + * + * {@See TelephonyManager#SIM_STATE_UNKNOWN} + * {@See TelephonyManager#SIM_STATE_ABSENT} + * {@See TelephonyManager#SIM_STATE_PIN_REQUIRED} + * {@See TelephonyManager#SIM_STATE_PUK_REQUIRED} + * {@See TelephonyManager#SIM_STATE_NETWORK_LOCKED} + * {@See TelephonyManager#SIM_STATE_READY} + * {@See TelephonyManager#SIM_STATE_NOT_READY} + * {@See TelephonyManager#SIM_STATE_PERM_DISABLED} + * {@See TelephonyManager#SIM_STATE_CARD_IO_ERROR} + * + * {@hide} + */ + public static int getSimStateForSlotIndex(int slotIndex) { + int simState = TelephonyManager.SIM_STATE_UNKNOWN; + + try { + ISub iSub = ISub.Stub.asInterface(ServiceManager.getService("isub")); + if (iSub != null) { + simState = iSub.getSimStateForSlotIndex(slotIndex); + } + } catch (RemoteException ex) { + } + + return simState; + } + + /** + * Store properties associated with SubscriptionInfo in database + * @param subId Subscription Id of Subscription + * @param propKey Column name in database associated with SubscriptionInfo + * @param propValue Value to store in DB for particular subId & column name + * @hide + */ + public static void setSubscriptionProperty(int subId, String propKey, String propValue) { + try { + ISub iSub = ISub.Stub.asInterface(ServiceManager.getService("isub")); + if (iSub != null) { + iSub.setSubscriptionProperty(subId, propKey, propValue); + } + } catch (RemoteException ex) { + // ignore it + } + } + + /** + * Store properties associated with SubscriptionInfo in database + * @param subId Subscription Id of Subscription + * @param propKey Column name in SubscriptionInfo database + * @return Value associated with subId and propKey column in database + * @hide + */ + private static String getSubscriptionProperty(int subId, String propKey, + Context context) { + String resultValue = null; + try { + ISub iSub = ISub.Stub.asInterface(ServiceManager.getService("isub")); + if (iSub != null) { + resultValue = iSub.getSubscriptionProperty(subId, propKey, + context.getOpPackageName()); + } + } catch (RemoteException ex) { + // ignore it + } + return resultValue; + } + + /** + * Returns boolean value corresponding to query result. + * @param subId Subscription Id of Subscription + * @param propKey Column name in SubscriptionInfo database + * @param defValue Default boolean value to be returned + * @return boolean result value to be returned + * @hide + */ + public static boolean getBooleanSubscriptionProperty(int subId, String propKey, + boolean defValue, Context context) { + String result = getSubscriptionProperty(subId, propKey, context); + if (result != null) { + try { + return Integer.parseInt(result) == 1; + } catch (NumberFormatException err) { + logd("getBooleanSubscriptionProperty NumberFormat exception"); + } + } + return defValue; + } + + /** + * Returns integer value corresponding to query result. + * @param subId Subscription Id of Subscription + * @param propKey Column name in SubscriptionInfo database + * @param defValue Default integer value to be returned + * @return integer result value to be returned + * @hide + */ + public static int getIntegerSubscriptionProperty(int subId, String propKey, int defValue, + Context context) { + String result = getSubscriptionProperty(subId, propKey, context); + if (result != null) { + try { + return Integer.parseInt(result); + } catch (NumberFormatException err) { + logd("getBooleanSubscriptionProperty NumberFormat exception"); + } + } + return defValue; + } + + /** + * Returns the resources associated with Subscription. + * @param context Context object + * @param subId Subscription Id of Subscription who's resources are required + * @return Resources associated with Subscription. + * @hide + */ + public static Resources getResourcesForSubId(Context context, int subId) { + final SubscriptionInfo subInfo = + SubscriptionManager.from(context).getActiveSubscriptionInfo(subId); + + Configuration config = context.getResources().getConfiguration(); + Configuration newConfig = new Configuration(); + newConfig.setTo(config); + if (subInfo != null) { + newConfig.mcc = subInfo.getMcc(); + newConfig.mnc = subInfo.getMnc(); + if (newConfig.mnc == 0) newConfig.mnc = Configuration.MNC_ZERO; + } + DisplayMetrics metrics = context.getResources().getDisplayMetrics(); + DisplayMetrics newMetrics = new DisplayMetrics(); + newMetrics.setTo(metrics); + return new Resources(context.getResources().getAssets(), newMetrics, newConfig); + } + + /** + * @return true if the sub ID is active. i.e. The sub ID corresponds to a known subscription + * and the SIM providing the subscription is present in a slot and in "LOADED" state. + * @hide + */ + public boolean isActiveSubId(int subId) { + try { + ISub iSub = ISub.Stub.asInterface(ServiceManager.getService("isub")); + if (iSub != null) { + return iSub.isActiveSubId(subId); + } + } catch (RemoteException ex) { + } + return false; + } + + /** + * Get the description of the billing relationship plan between a carrier + * and a specific subscriber. + * <p> + * This method is only accessible to the following narrow set of apps: + * <ul> + * <li>The carrier app for this subscriberId, as determined by + * {@link TelephonyManager#hasCarrierPrivileges(int)}. + * <li>The carrier app explicitly delegated access through + * {@link CarrierConfigManager#KEY_CONFIG_PLANS_PACKAGE_OVERRIDE_STRING}. + * </ul> + * + * @param subId the subscriber this relationship applies to + * @hide + */ + @SystemApi + public @NonNull List<SubscriptionPlan> getSubscriptionPlans(int subId) { + final INetworkPolicyManager npm = INetworkPolicyManager.Stub + .asInterface(ServiceManager.getService(Context.NETWORK_POLICY_SERVICE)); + try { + SubscriptionPlan[] subscriptionPlans = + npm.getSubscriptionPlans(subId, mContext.getOpPackageName()); + return subscriptionPlans == null + ? Collections.emptyList() : Arrays.asList(subscriptionPlans); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + } + + /** + * Set the description of the billing relationship plan between a carrier + * and a specific subscriber. + * <p> + * This method is only accessible to the following narrow set of apps: + * <ul> + * <li>The carrier app for this subscriberId, as determined by + * {@link TelephonyManager#hasCarrierPrivileges(int)}. + * <li>The carrier app explicitly delegated access through + * {@link CarrierConfigManager#KEY_CONFIG_PLANS_PACKAGE_OVERRIDE_STRING}. + * </ul> + * + * @param subId the subscriber this relationship applies to + * @param plans the list of plans. The first plan is always the primary and + * most important plan. Any additional plans are secondary and + * may not be displayed or used by decision making logic. + * @hide + */ + @SystemApi + public void setSubscriptionPlans(int subId, @NonNull List<SubscriptionPlan> plans) { + final INetworkPolicyManager npm = INetworkPolicyManager.Stub + .asInterface(ServiceManager.getService(Context.NETWORK_POLICY_SERVICE)); + try { + npm.setSubscriptionPlans(subId, plans.toArray(new SubscriptionPlan[plans.size()]), + mContext.getOpPackageName()); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + } +} diff --git a/android/telephony/SubscriptionPlan.java b/android/telephony/SubscriptionPlan.java new file mode 100644 index 00000000..265e3e7c --- /dev/null +++ b/android/telephony/SubscriptionPlan.java @@ -0,0 +1,301 @@ +/* + * Copyright (C) 2017 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.telephony; + +import android.annotation.BytesLong; +import android.annotation.CurrentTimeMillisLong; +import android.annotation.IntDef; +import android.annotation.NonNull; +import android.annotation.Nullable; +import android.annotation.SystemApi; +import android.os.Parcel; +import android.os.Parcelable; +import android.util.Pair; +import android.util.RecurrenceRule; + +import com.android.internal.util.Preconditions; + +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.time.Period; +import java.time.ZonedDateTime; +import java.util.Iterator; + +/** + * Description of a billing relationship plan between a carrier and a specific + * subscriber. This information is used to present more useful UI to users, such + * as explaining how much mobile data they have remaining, and what will happen + * when they run out. + * + * @see SubscriptionManager#setSubscriptionPlans(int, java.util.List) + * @see SubscriptionManager#getSubscriptionPlans(int) + * @hide + */ +@SystemApi +public final class SubscriptionPlan implements Parcelable { + /** {@hide} */ + @IntDef(prefix = "LIMIT_BEHAVIOR_", value = { + LIMIT_BEHAVIOR_UNKNOWN, + LIMIT_BEHAVIOR_DISABLED, + LIMIT_BEHAVIOR_BILLED, + LIMIT_BEHAVIOR_THROTTLED, + }) + @Retention(RetentionPolicy.SOURCE) + public @interface LimitBehavior {} + + /** When a resource limit is hit, the behavior is unknown. */ + public static final int LIMIT_BEHAVIOR_UNKNOWN = -1; + /** When a resource limit is hit, access is disabled. */ + public static final int LIMIT_BEHAVIOR_DISABLED = 0; + /** When a resource limit is hit, the user is billed automatically. */ + public static final int LIMIT_BEHAVIOR_BILLED = 1; + /** When a resource limit is hit, access is throttled to a slower rate. */ + public static final int LIMIT_BEHAVIOR_THROTTLED = 2; + + /** Value indicating a number of bytes is unknown. */ + public static final long BYTES_UNKNOWN = -1; + /** Value indicating a number of bytes is unlimited. */ + public static final long BYTES_UNLIMITED = Long.MAX_VALUE; + + /** Value indicating a timestamp is unknown. */ + public static final long TIME_UNKNOWN = -1; + + private final RecurrenceRule cycleRule; + private CharSequence title; + private CharSequence summary; + private long dataLimitBytes = BYTES_UNKNOWN; + private int dataLimitBehavior = LIMIT_BEHAVIOR_UNKNOWN; + private long dataUsageBytes = BYTES_UNKNOWN; + private long dataUsageTime = TIME_UNKNOWN; + + private SubscriptionPlan(RecurrenceRule cycleRule) { + this.cycleRule = Preconditions.checkNotNull(cycleRule); + } + + private SubscriptionPlan(Parcel source) { + cycleRule = source.readParcelable(null); + title = source.readCharSequence(); + summary = source.readCharSequence(); + dataLimitBytes = source.readLong(); + dataLimitBehavior = source.readInt(); + dataUsageBytes = source.readLong(); + dataUsageTime = source.readLong(); + } + + @Override + public int describeContents() { + return 0; + } + + @Override + public void writeToParcel(Parcel dest, int flags) { + dest.writeParcelable(cycleRule, flags); + dest.writeCharSequence(title); + dest.writeCharSequence(summary); + dest.writeLong(dataLimitBytes); + dest.writeInt(dataLimitBehavior); + dest.writeLong(dataUsageBytes); + dest.writeLong(dataUsageTime); + } + + @Override + public String toString() { + return new StringBuilder("SubscriptionPlan{") + .append("cycleRule=").append(cycleRule) + .append(" title=").append(title) + .append(" summary=").append(summary) + .append(" dataLimitBytes=").append(dataLimitBytes) + .append(" dataLimitBehavior=").append(dataLimitBehavior) + .append(" dataUsageBytes=").append(dataUsageBytes) + .append(" dataUsageTime=").append(dataUsageTime) + .append("}").toString(); + } + + public static final Parcelable.Creator<SubscriptionPlan> CREATOR = new Parcelable.Creator<SubscriptionPlan>() { + @Override + public SubscriptionPlan createFromParcel(Parcel source) { + return new SubscriptionPlan(source); + } + + @Override + public SubscriptionPlan[] newArray(int size) { + return new SubscriptionPlan[size]; + } + }; + + /** {@hide} */ + public @NonNull RecurrenceRule getCycleRule() { + return cycleRule; + } + + /** Return the short title of this plan. */ + public @Nullable CharSequence getTitle() { + return title; + } + + /** Return the short summary of this plan. */ + public @Nullable CharSequence getSummary() { + return summary; + } + + /** + * Return the usage threshold at which data access changes according to + * {@link #getDataLimitBehavior()}. + */ + public @BytesLong long getDataLimitBytes() { + return dataLimitBytes; + } + + /** + * Return the behavior of data access when usage reaches + * {@link #getDataLimitBytes()}. + */ + public @LimitBehavior int getDataLimitBehavior() { + return dataLimitBehavior; + } + + /** + * Return a snapshot of currently known mobile data usage at + * {@link #getDataUsageTime()}. + */ + public @BytesLong long getDataUsageBytes() { + return dataUsageBytes; + } + + /** + * Return the time at which {@link #getDataUsageBytes()} was valid. + */ + public @CurrentTimeMillisLong long getDataUsageTime() { + return dataUsageTime; + } + + /** + * Return an iterator that will return all valid data usage cycles based on + * any recurrence rules. The iterator starts from the currently active cycle + * and walks backwards through time. + */ + public Iterator<Pair<ZonedDateTime, ZonedDateTime>> cycleIterator() { + return cycleRule.cycleIterator(); + } + + /** + * Builder for a {@link SubscriptionPlan}. + */ + public static class Builder { + private final SubscriptionPlan plan; + + /** {@hide} */ + public Builder(ZonedDateTime start, ZonedDateTime end, Period period) { + plan = new SubscriptionPlan(new RecurrenceRule(start, end, period)); + } + + /** + * Start defining a {@link SubscriptionPlan} that covers a very specific + * window of time, and never automatically recurs. + */ + public static Builder createNonrecurring(ZonedDateTime start, ZonedDateTime end) { + if (!end.isAfter(start)) { + throw new IllegalArgumentException( + "End " + end + " isn't after start " + start); + } + return new Builder(start, end, null); + } + + /** + * Start defining a {@link SubscriptionPlan} that will recur + * automatically every month. It will always recur on the same day of a + * particular month. When a particular month ends before the defined + * recurrence day, the plan will recur on the last instant of that + * month. + */ + public static Builder createRecurringMonthly(ZonedDateTime start) { + return new Builder(start, null, Period.ofMonths(1)); + } + + /** + * Start defining a {@link SubscriptionPlan} that will recur + * automatically every week. + */ + public static Builder createRecurringWeekly(ZonedDateTime start) { + return new Builder(start, null, Period.ofDays(7)); + } + + /** + * Start defining a {@link SubscriptionPlan} that will recur + * automatically every day. + */ + public static Builder createRecurringDaily(ZonedDateTime start) { + return new Builder(start, null, Period.ofDays(1)); + } + + public SubscriptionPlan build() { + return plan; + } + + /** Set the short title of this plan. */ + public Builder setTitle(@Nullable CharSequence title) { + plan.title = title; + return this; + } + + /** Set the short summary of this plan. */ + public Builder setSummary(@Nullable CharSequence summary) { + plan.summary = summary; + return this; + } + + /** + * Set the usage threshold at which data access changes. + * + * @param dataLimitBytes the usage threshold at which data access + * changes + * @param dataLimitBehavior the behavior of data access when usage + * reaches the threshold + */ + public Builder setDataLimit(@BytesLong long dataLimitBytes, + @LimitBehavior int dataLimitBehavior) { + if (dataLimitBytes < 0) { + throw new IllegalArgumentException("Limit bytes must be positive"); + } + if (dataLimitBehavior < 0) { + throw new IllegalArgumentException("Limit behavior must be defined"); + } + plan.dataLimitBytes = dataLimitBytes; + plan.dataLimitBehavior = dataLimitBehavior; + return this; + } + + /** + * Set a snapshot of currently known mobile data usage. + * + * @param dataUsageBytes the currently known mobile data usage + * @param dataUsageTime the time at which this snapshot was valid + */ + public Builder setDataUsage(@BytesLong long dataUsageBytes, + @CurrentTimeMillisLong long dataUsageTime) { + if (dataUsageBytes < 0) { + throw new IllegalArgumentException("Usage bytes must be positive"); + } + if (dataUsageTime < 0) { + throw new IllegalArgumentException("Usage time must be positive"); + } + plan.dataUsageBytes = dataUsageBytes; + plan.dataUsageTime = dataUsageTime; + return this; + } + } +} diff --git a/android/telephony/TelephonyHistogram.java b/android/telephony/TelephonyHistogram.java new file mode 100644 index 00000000..e1c3d7b3 --- /dev/null +++ b/android/telephony/TelephonyHistogram.java @@ -0,0 +1,313 @@ +/* + * 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.telephony; + +import android.annotation.SystemApi; +import android.os.Parcel; +import android.os.Parcelable; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; + +/** + * Parcelable class to store Telephony histogram. + * @hide + */ +@SystemApi +public final class TelephonyHistogram implements Parcelable { + // Type of Telephony histogram Eg: RIL histogram will have all timing data associated with + // RIL calls. Similarly we can have any other Telephony histogram. + private final int mCategory; + + // Unique Id identifying a sample within particular category of histogram + private final int mId; + + // Min time taken in ms + private int mMinTimeMs; + + // Max time taken in ms + private int mMaxTimeMs; + + // Average time taken in ms + private int mAverageTimeMs; + + // Total count of samples + private int mSampleCount; + + // Array storing time taken for first #RANGE_CALCULATION_COUNT samples of histogram. + private int[] mInitialTimings; + + // Total number of time ranges expected (must be greater than 1) + private final int mBucketCount; + + // Array storing endpoints of range buckets. Calculated based on values of minTime & maxTime + // after totalTimeCount is #RANGE_CALCULATION_COUNT. + private final int[] mBucketEndPoints; + + // Array storing counts for each time range starting from smallest value range + private final int[] mBucketCounters; + + /** + * Constant for Telephony category + */ + public static final int TELEPHONY_CATEGORY_RIL = 1; + + // Count of Histogram samples after which time buckets are created. + private static final int RANGE_CALCULATION_COUNT = 10; + + + // Constant used to indicate #initialTimings is null while parceling + private static final int ABSENT = 0; + + // Constant used to indicate #initialTimings is not null while parceling + private static final int PRESENT = 1; + + // Throws exception if #totalBuckets is not greater than one. + public TelephonyHistogram (int category, int id, int bucketCount) { + if (bucketCount <= 1) { + throw new IllegalArgumentException("Invalid number of buckets"); + } + mCategory = category; + mId = id; + mMinTimeMs = Integer.MAX_VALUE; + mMaxTimeMs = 0; + mAverageTimeMs = 0; + mSampleCount = 0; + mInitialTimings = new int[RANGE_CALCULATION_COUNT]; + mBucketCount = bucketCount; + mBucketEndPoints = new int[bucketCount - 1]; + mBucketCounters = new int[bucketCount]; + } + + public TelephonyHistogram(TelephonyHistogram th) { + mCategory = th.getCategory(); + mId = th.getId(); + mMinTimeMs = th.getMinTime(); + mMaxTimeMs = th.getMaxTime(); + mAverageTimeMs = th.getAverageTime(); + mSampleCount = th.getSampleCount(); + mInitialTimings = th.getInitialTimings(); + mBucketCount = th.getBucketCount(); + mBucketEndPoints = th.getBucketEndPoints(); + mBucketCounters = th.getBucketCounters(); + } + + public int getCategory() { + return mCategory; + } + + public int getId() { + return mId; + } + + public int getMinTime() { + return mMinTimeMs; + } + + public int getMaxTime() { + return mMaxTimeMs; + } + + public int getAverageTime() { + return mAverageTimeMs; + } + + public int getSampleCount () { + return mSampleCount; + } + + private int[] getInitialTimings() { + return mInitialTimings; + } + + public int getBucketCount() { + return mBucketCount; + } + + public int[] getBucketEndPoints() { + if (mSampleCount > 1 && mSampleCount < 10) { + int[] tempEndPoints = new int[mBucketCount - 1]; + calculateBucketEndPoints(tempEndPoints); + return tempEndPoints; + } else { + return getDeepCopyOfArray(mBucketEndPoints); + } + } + + public int[] getBucketCounters() { + if (mSampleCount > 1 && mSampleCount < 10) { + int[] tempEndPoints = new int[mBucketCount - 1]; + int[] tempBucketCounters = new int[mBucketCount]; + calculateBucketEndPoints(tempEndPoints); + for (int j = 0; j < mSampleCount; j++) { + addToBucketCounter(tempEndPoints, tempBucketCounters, mInitialTimings[j]); + } + return tempBucketCounters; + } else { + return getDeepCopyOfArray(mBucketCounters); + } + } + + private int[] getDeepCopyOfArray(int[] array) { + int[] clone = new int[array.length]; + System.arraycopy(array, 0, clone, 0, array.length); + return clone; + } + + private void addToBucketCounter(int[] bucketEndPoints, int[] bucketCounters, int time) { + int i; + for (i = 0; i < bucketEndPoints.length; i++) { + if (time <= bucketEndPoints[i]) { + bucketCounters[i]++; + return; + } + } + bucketCounters[i]++; + } + + private void calculateBucketEndPoints(int[] bucketEndPoints) { + for (int i = 1; i < mBucketCount; i++) { + int endPt = mMinTimeMs + (i * (mMaxTimeMs - mMinTimeMs)) / mBucketCount; + bucketEndPoints[i - 1] = endPt; + } + } + + // Add new value of time taken + // This function updates minTime, maxTime, averageTime & totalTimeCount every time it is + // called. initialTimings[] is updated if totalTimeCount <= #RANGE_CALCULATION_COUNT. When + // totalTimeCount = RANGE_CALCULATION_COUNT, based on the min, max time & the number of buckets + // expected, bucketEndPoints[] would be calculated. Then bucketCounters[] would be filled up + // using values stored in initialTimings[]. Thereafter bucketCounters[] will always be updated. + public void addTimeTaken(int time) { + // Initialize all fields if its first entry or if integer overflow is going to occur while + // trying to calculate averageTime + if (mSampleCount == 0 || (mSampleCount == Integer.MAX_VALUE)) { + if (mSampleCount == 0) { + mMinTimeMs = time; + mMaxTimeMs = time; + mAverageTimeMs = time; + } else { + mInitialTimings = new int[RANGE_CALCULATION_COUNT]; + } + mSampleCount = 1; + Arrays.fill(mInitialTimings, 0); + mInitialTimings[0] = time; + Arrays.fill(mBucketEndPoints, 0); + Arrays.fill(mBucketCounters, 0); + } else { + if (time < mMinTimeMs) { + mMinTimeMs = time; + } + if (time > mMaxTimeMs) { + mMaxTimeMs = time; + } + long totalTime = ((long)mAverageTimeMs) * mSampleCount + time; + mAverageTimeMs = (int)(totalTime/++mSampleCount); + + if (mSampleCount < RANGE_CALCULATION_COUNT) { + mInitialTimings[mSampleCount - 1] = time; + } else if (mSampleCount == RANGE_CALCULATION_COUNT) { + mInitialTimings[mSampleCount - 1] = time; + + // Calculate bucket endpoints based on bucketCount expected + calculateBucketEndPoints(mBucketEndPoints); + + // Use values stored in initialTimings[] to update bucketCounters + for (int j = 0; j < RANGE_CALCULATION_COUNT; j++) { + addToBucketCounter(mBucketEndPoints, mBucketCounters, mInitialTimings[j]); + } + mInitialTimings = null; + } else { + addToBucketCounter(mBucketEndPoints, mBucketCounters, time); + } + + } + } + + public String toString() { + String basic = " Histogram id = " + mId + " Time(ms): min = " + mMinTimeMs + " max = " + + mMaxTimeMs + " avg = " + mAverageTimeMs + " Count = " + mSampleCount; + if (mSampleCount < RANGE_CALCULATION_COUNT) { + return basic; + } else { + StringBuffer intervals = new StringBuffer(" Interval Endpoints:"); + for (int i = 0; i < mBucketEndPoints.length; i++) { + intervals.append(" " + mBucketEndPoints[i]); + } + intervals.append(" Interval counters:"); + for (int i = 0; i < mBucketCounters.length; i++) { + intervals.append(" " + mBucketCounters[i]); + } + return basic + intervals; + } + } + + public static final Parcelable.Creator<TelephonyHistogram> CREATOR = + new Parcelable.Creator<TelephonyHistogram> () { + + @Override + public TelephonyHistogram createFromParcel(Parcel in) { + return new TelephonyHistogram(in); + } + + @Override + public TelephonyHistogram[] newArray(int size) { + return new TelephonyHistogram[size]; + } + }; + + public TelephonyHistogram(Parcel in) { + mCategory = in.readInt(); + mId = in.readInt(); + mMinTimeMs = in.readInt(); + mMaxTimeMs = in.readInt(); + mAverageTimeMs = in.readInt(); + mSampleCount = in.readInt(); + if (in.readInt() == PRESENT) { + mInitialTimings = new int[RANGE_CALCULATION_COUNT]; + in.readIntArray(mInitialTimings); + } + mBucketCount = in.readInt(); + mBucketEndPoints = new int[mBucketCount - 1]; + in.readIntArray(mBucketEndPoints); + mBucketCounters = new int[mBucketCount]; + in.readIntArray(mBucketCounters); + } + + public void writeToParcel(Parcel out, int flags) { + out.writeInt(mCategory); + out.writeInt(mId); + out.writeInt(mMinTimeMs); + out.writeInt(mMaxTimeMs); + out.writeInt(mAverageTimeMs); + out.writeInt(mSampleCount); + if (mInitialTimings == null) { + out.writeInt(ABSENT); + } else { + out.writeInt(PRESENT); + out.writeIntArray(mInitialTimings); + } + out.writeInt(mBucketCount); + out.writeIntArray(mBucketEndPoints); + out.writeIntArray(mBucketCounters); + } + + @Override + public int describeContents() { + return 0; + } +} diff --git a/android/telephony/TelephonyManager.java b/android/telephony/TelephonyManager.java new file mode 100644 index 00000000..cde0bdfd --- /dev/null +++ b/android/telephony/TelephonyManager.java @@ -0,0 +1,6853 @@ +/* + * Copyright (C) 2008 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.telephony; + +import static com.android.internal.util.Preconditions.checkNotNull; + +import android.annotation.IntDef; +import android.annotation.Nullable; +import android.annotation.RequiresPermission; +import android.annotation.SdkConstant; +import android.annotation.SuppressLint; +import android.annotation.SdkConstant.SdkConstantType; +import android.annotation.SystemApi; +import android.annotation.SystemService; +import android.annotation.WorkerThread; +import android.app.ActivityThread; +import android.app.PendingIntent; +import android.content.ContentResolver; +import android.content.Context; +import android.content.Intent; +import android.net.ConnectivityManager; +import android.net.NetworkStats; +import android.net.Uri; +import android.os.BatteryStats; +import android.os.Bundle; +import android.os.Handler; +import android.os.PersistableBundle; +import android.os.RemoteException; +import android.os.ResultReceiver; +import android.os.ServiceManager; +import android.os.SystemProperties; +import android.provider.Settings; +import android.provider.Settings.SettingNotFoundException; +import android.service.carrier.CarrierIdentifier; +import android.telecom.PhoneAccount; +import android.telecom.PhoneAccountHandle; +import android.telecom.TelecomManager; +import android.telephony.VisualVoicemailService.VisualVoicemailTask; +import android.telephony.ims.feature.ImsFeature; +import android.util.Log; + +import com.android.ims.internal.IImsServiceController; +import com.android.ims.internal.IImsServiceFeatureListener; +import com.android.internal.annotations.VisibleForTesting; +import com.android.internal.telecom.ITelecomService; +import com.android.internal.telephony.CellNetworkScanResult; +import com.android.internal.telephony.IPhoneSubInfo; +import com.android.internal.telephony.ITelephony; +import com.android.internal.telephony.ITelephonyRegistry; +import com.android.internal.telephony.OperatorInfo; +import com.android.internal.telephony.PhoneConstants; +import com.android.internal.telephony.RILConstants; +import com.android.internal.telephony.TelephonyProperties; + +import java.io.FileInputStream; +import java.io.IOException; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +/** + * Provides access to information about the telephony services on + * the device. Applications can use the methods in this class to + * determine telephony services and states, as well as to access some + * types of subscriber information. Applications can also register + * a listener to receive notification of telephony state changes. + * <p> + * The returned TelephonyManager will use the default subscription for all calls. + * To call an API for a specific subscription, use {@link #createForSubscriptionId(int)}. e.g. + * <code> + * telephonyManager = defaultSubTelephonyManager.createForSubscriptionId(subId); + * </code> + * <p> + * Note that access to some telephony information is + * permission-protected. Your application cannot access the protected + * information unless it has the appropriate permissions declared in + * its manifest file. Where permissions apply, they are noted in the + * the methods through which you access the protected information. + */ +@SystemService(Context.TELEPHONY_SERVICE) +public class TelephonyManager { + private static final String TAG = "TelephonyManager"; + + /** + * The key to use when placing the result of {@link #requestModemActivityInfo(ResultReceiver)} + * into the ResultReceiver Bundle. + * @hide + */ + public static final String MODEM_ACTIVITY_RESULT_KEY = + BatteryStats.RESULT_RECEIVER_CONTROLLER_KEY; + + private static ITelephonyRegistry sRegistry; + + /** + * The allowed states of Wi-Fi calling. + * + * @hide + */ + public interface WifiCallingChoices { + /** Always use Wi-Fi calling */ + static final int ALWAYS_USE = 0; + /** Ask the user whether to use Wi-Fi on every call */ + static final int ASK_EVERY_TIME = 1; + /** Never use Wi-Fi calling */ + static final int NEVER_USE = 2; + } + + /** The otaspMode passed to PhoneStateListener#onOtaspChanged */ + /** @hide */ + static public final int OTASP_UNINITIALIZED = 0; + /** @hide */ + static public final int OTASP_UNKNOWN = 1; + /** @hide */ + static public final int OTASP_NEEDED = 2; + /** @hide */ + static public final int OTASP_NOT_NEEDED = 3; + /* OtaUtil has conflict enum 4: OtaUtils.OTASP_FAILURE_SPC_RETRIES */ + /** @hide */ + static public final int OTASP_SIM_UNPROVISIONED = 5; + + + /** @hide */ + static public final int KEY_TYPE_EPDG = 1; + + /** @hide */ + static public final int KEY_TYPE_WLAN = 2; + + private final Context mContext; + private final int mSubId; + private SubscriptionManager mSubscriptionManager; + private TelephonyScanManager mTelephonyScanManager; + + private static String multiSimConfig = + SystemProperties.get(TelephonyProperties.PROPERTY_MULTI_SIM_CONFIG); + + /** Enum indicating multisim variants + * DSDS - Dual SIM Dual Standby + * DSDA - Dual SIM Dual Active + * TSTS - Triple SIM Triple Standby + **/ + /** @hide */ + public enum MultiSimVariants { + DSDS, + DSDA, + TSTS, + UNKNOWN + }; + + /** @hide */ + public TelephonyManager(Context context) { + this(context, SubscriptionManager.DEFAULT_SUBSCRIPTION_ID); + } + + /** @hide */ + public TelephonyManager(Context context, int subId) { + mSubId = subId; + Context appContext = context.getApplicationContext(); + if (appContext != null) { + mContext = appContext; + } else { + mContext = context; + } + mSubscriptionManager = SubscriptionManager.from(mContext); + + if (sRegistry == null) { + sRegistry = ITelephonyRegistry.Stub.asInterface(ServiceManager.getService( + "telephony.registry")); + } + } + + /** @hide */ + private TelephonyManager() { + mContext = null; + mSubId = SubscriptionManager.INVALID_SUBSCRIPTION_ID; + } + + private static TelephonyManager sInstance = new TelephonyManager(); + + /** @hide + /* @deprecated - use getSystemService as described above */ + public static TelephonyManager getDefault() { + return sInstance; + } + + private String getOpPackageName() { + // For legacy reasons the TelephonyManager has API for getting + // a static instance with no context set preventing us from + // getting the op package name. As a workaround we do a best + // effort and get the context from the current activity thread. + if (mContext != null) { + return mContext.getOpPackageName(); + } + return ActivityThread.currentOpPackageName(); + } + + /** + * Returns the multi SIM variant + * Returns DSDS for Dual SIM Dual Standby + * Returns DSDA for Dual SIM Dual Active + * Returns TSTS for Triple SIM Triple Standby + * Returns UNKNOWN for others + */ + /** {@hide} */ + public MultiSimVariants getMultiSimConfiguration() { + String mSimConfig = + SystemProperties.get(TelephonyProperties.PROPERTY_MULTI_SIM_CONFIG); + if (mSimConfig.equals("dsds")) { + return MultiSimVariants.DSDS; + } else if (mSimConfig.equals("dsda")) { + return MultiSimVariants.DSDA; + } else if (mSimConfig.equals("tsts")) { + return MultiSimVariants.TSTS; + } else { + return MultiSimVariants.UNKNOWN; + } + } + + + /** + * Returns the number of phones available. + * Returns 0 if none of voice, sms, data is not supported + * Returns 1 for Single standby mode (Single SIM functionality) + * Returns 2 for Dual standby mode.(Dual SIM functionality) + */ + public int getPhoneCount() { + int phoneCount = 1; + switch (getMultiSimConfiguration()) { + case UNKNOWN: + // if voice or sms or data is supported, return 1 otherwise 0 + if (isVoiceCapable() || isSmsCapable()) { + phoneCount = 1; + } else { + // todo: try to clean this up further by getting rid of the nested conditions + if (mContext == null) { + phoneCount = 1; + } else { + // check for data support + ConnectivityManager cm = (ConnectivityManager)mContext.getSystemService( + Context.CONNECTIVITY_SERVICE); + if (cm == null) { + phoneCount = 1; + } else { + if (cm.isNetworkSupported(ConnectivityManager.TYPE_MOBILE)) { + phoneCount = 1; + } else { + phoneCount = 0; + } + } + } + } + break; + case DSDS: + case DSDA: + phoneCount = PhoneConstants.MAX_PHONE_COUNT_DUAL_SIM; + break; + case TSTS: + phoneCount = PhoneConstants.MAX_PHONE_COUNT_TRI_SIM; + break; + } + return phoneCount; + } + + /** {@hide} */ + public static TelephonyManager from(Context context) { + return (TelephonyManager) context.getSystemService(Context.TELEPHONY_SERVICE); + } + + /** + * Create a new TelephonyManager object pinned to the given subscription ID. + * + * @return a TelephonyManager that uses the given subId for all calls. + */ + public TelephonyManager createForSubscriptionId(int subId) { + // Don't reuse any TelephonyManager objects. + return new TelephonyManager(mContext, subId); + } + + /** + * Create a new TelephonyManager object pinned to the subscription ID associated with the given + * phone account. + * + * @return a TelephonyManager that uses the given phone account for all calls, or {@code null} + * if the phone account does not correspond to a valid subscription ID. + */ + @Nullable + public TelephonyManager createForPhoneAccountHandle(PhoneAccountHandle phoneAccountHandle) { + int subId = getSubIdForPhoneAccountHandle(phoneAccountHandle); + if (!SubscriptionManager.isValidSubscriptionId(subId)) { + return null; + } + return new TelephonyManager(mContext, subId); + } + + /** {@hide} */ + public boolean isMultiSimEnabled() { + return (multiSimConfig.equals("dsds") || multiSimConfig.equals("dsda") || + multiSimConfig.equals("tsts")); + } + + // + // Broadcast Intent actions + // + + /** + * Broadcast intent action indicating that the call state + * on the device has changed. + * + * <p> + * The {@link #EXTRA_STATE} extra indicates the new call state. + * If the new state is RINGING, a second extra + * {@link #EXTRA_INCOMING_NUMBER} provides the incoming phone number as + * a String. + * + * <p class="note"> + * This was a {@link android.content.Context#sendStickyBroadcast sticky} + * broadcast in version 1.0, but it is no longer sticky. + * Instead, use {@link #getCallState} to synchronously query the current call state. + * + * @see #EXTRA_STATE + * @see #EXTRA_INCOMING_NUMBER + * @see #getCallState + */ + @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION) + @RequiresPermission(android.Manifest.permission.READ_PHONE_STATE) + public static final String ACTION_PHONE_STATE_CHANGED = + "android.intent.action.PHONE_STATE"; + + /** + * The Phone app sends this intent when a user opts to respond-via-message during an incoming + * call. By default, the device's default SMS app consumes this message and sends a text message + * to the caller. A third party app can also provide this functionality by consuming this Intent + * with a {@link android.app.Service} and sending the message using its own messaging system. + * <p>The intent contains a URI (available from {@link android.content.Intent#getData}) + * describing the recipient, using either the {@code sms:}, {@code smsto:}, {@code mms:}, + * or {@code mmsto:} URI schema. Each of these URI schema carry the recipient information the + * same way: the path part of the URI contains the recipient's phone number or a comma-separated + * set of phone numbers if there are multiple recipients. For example, {@code + * smsto:2065551234}.</p> + * + * <p>The intent may also contain extras for the message text (in {@link + * android.content.Intent#EXTRA_TEXT}) and a message subject + * (in {@link android.content.Intent#EXTRA_SUBJECT}).</p> + * + * <p class="note"><strong>Note:</strong> + * The intent-filter that consumes this Intent needs to be in a {@link android.app.Service} + * that requires the + * permission {@link android.Manifest.permission#SEND_RESPOND_VIA_MESSAGE}.</p> + * <p>For example, the service that receives this intent can be declared in the manifest file + * with an intent filter like this:</p> + * <pre> + * <!-- Service that delivers SMS messages received from the phone "quick response" --> + * <service android:name=".HeadlessSmsSendService" + * android:permission="android.permission.SEND_RESPOND_VIA_MESSAGE" + * android:exported="true" > + * <intent-filter> + * <action android:name="android.intent.action.RESPOND_VIA_MESSAGE" /> + * <category android:name="android.intent.category.DEFAULT" /> + * <data android:scheme="sms" /> + * <data android:scheme="smsto" /> + * <data android:scheme="mms" /> + * <data android:scheme="mmsto" /> + * </intent-filter> + * </service></pre> + * <p> + * Output: nothing. + */ + @SdkConstant(SdkConstantType.SERVICE_ACTION) + public static final String ACTION_RESPOND_VIA_MESSAGE = + "android.intent.action.RESPOND_VIA_MESSAGE"; + + /** + * The emergency dialer may choose to present activities with intent filters for this + * action as emergency assistance buttons that launch the activity when clicked. + * + * @hide + */ + @SdkConstant(SdkConstantType.ACTIVITY_INTENT_ACTION) + public static final String ACTION_EMERGENCY_ASSISTANCE = + "android.telephony.action.EMERGENCY_ASSISTANCE"; + + /** + * A boolean meta-data value indicating whether the voicemail settings should be hidden in the + * call settings page launched by + * {@link android.telecom.TelecomManager#ACTION_SHOW_CALL_SETTINGS}. + * Dialer implementations (see {@link android.telecom.TelecomManager#getDefaultDialerPackage()}) + * which would also like to manage voicemail settings should set this meta-data to {@code true} + * in the manifest registration of their application. + * + * @see android.telecom.TelecomManager#ACTION_SHOW_CALL_SETTINGS + * @see #ACTION_CONFIGURE_VOICEMAIL + * @see #EXTRA_HIDE_PUBLIC_SETTINGS + */ + public static final String METADATA_HIDE_VOICEMAIL_SETTINGS_MENU = + "android.telephony.HIDE_VOICEMAIL_SETTINGS_MENU"; + + /** + * Open the voicemail settings activity to make changes to voicemail configuration. + * + * <p> + * The {@link #EXTRA_HIDE_PUBLIC_SETTINGS} hides settings the dialer will modify through public + * API if set. + * + * @see #EXTRA_HIDE_PUBLIC_SETTINGS + */ + @SdkConstant(SdkConstantType.ACTIVITY_INTENT_ACTION) + public static final String ACTION_CONFIGURE_VOICEMAIL = + "android.telephony.action.CONFIGURE_VOICEMAIL"; + + /** + * The boolean value indicating whether the voicemail settings activity launched by {@link + * #ACTION_CONFIGURE_VOICEMAIL} should hide settings accessible through public API. This is + * used by dialer implementations which provides their own voicemail settings UI, but still + * needs to expose device specific voicemail settings to the user. + * + * @see #ACTION_CONFIGURE_VOICEMAIL + * @see #METADATA_HIDE_VOICEMAIL_SETTINGS_MENU + */ + public static final String EXTRA_HIDE_PUBLIC_SETTINGS = + "android.telephony.extra.HIDE_PUBLIC_SETTINGS"; + + /** + * @hide + */ + public static final boolean EMERGENCY_ASSISTANCE_ENABLED = true; + + /** + * The lookup key used with the {@link #ACTION_PHONE_STATE_CHANGED} broadcast + * for a String containing the new call state. + * + * <p class="note"> + * Retrieve with + * {@link android.content.Intent#getStringExtra(String)}. + * + * @see #EXTRA_STATE_IDLE + * @see #EXTRA_STATE_RINGING + * @see #EXTRA_STATE_OFFHOOK + */ + public static final String EXTRA_STATE = PhoneConstants.STATE_KEY; + + /** + * Value used with {@link #EXTRA_STATE} corresponding to + * {@link #CALL_STATE_IDLE}. + */ + public static final String EXTRA_STATE_IDLE = PhoneConstants.State.IDLE.toString(); + + /** + * Value used with {@link #EXTRA_STATE} corresponding to + * {@link #CALL_STATE_RINGING}. + */ + public static final String EXTRA_STATE_RINGING = PhoneConstants.State.RINGING.toString(); + + /** + * Value used with {@link #EXTRA_STATE} corresponding to + * {@link #CALL_STATE_OFFHOOK}. + */ + public static final String EXTRA_STATE_OFFHOOK = PhoneConstants.State.OFFHOOK.toString(); + + /** + * The lookup key used with the {@link #ACTION_PHONE_STATE_CHANGED} broadcast + * for a String containing the incoming phone number. + * Only valid when the new call state is RINGING. + * + * <p class="note"> + * Retrieve with + * {@link android.content.Intent#getStringExtra(String)}. + */ + public static final String EXTRA_INCOMING_NUMBER = "incoming_number"; + + /** + * Broadcast intent action indicating that a precise call state + * (cellular) on the device has changed. + * + * <p> + * The {@link #EXTRA_RINGING_CALL_STATE} extra indicates the ringing call state. + * The {@link #EXTRA_FOREGROUND_CALL_STATE} extra indicates the foreground call state. + * The {@link #EXTRA_BACKGROUND_CALL_STATE} extra indicates the background call state. + * The {@link #EXTRA_DISCONNECT_CAUSE} extra indicates the disconnect cause. + * The {@link #EXTRA_PRECISE_DISCONNECT_CAUSE} extra indicates the precise disconnect cause. + * + * <p class="note"> + * Requires the READ_PRECISE_PHONE_STATE permission. + * + * @see #EXTRA_RINGING_CALL_STATE + * @see #EXTRA_FOREGROUND_CALL_STATE + * @see #EXTRA_BACKGROUND_CALL_STATE + * @see #EXTRA_DISCONNECT_CAUSE + * @see #EXTRA_PRECISE_DISCONNECT_CAUSE + * + * <p class="note"> + * Requires the READ_PRECISE_PHONE_STATE permission. + * + * @hide + */ + @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION) + public static final String ACTION_PRECISE_CALL_STATE_CHANGED = + "android.intent.action.PRECISE_CALL_STATE"; + + /** + * The lookup key used with the {@link #ACTION_PRECISE_CALL_STATE_CHANGED} broadcast + * for an integer containing the state of the current ringing call. + * + * @see PreciseCallState#PRECISE_CALL_STATE_NOT_VALID + * @see PreciseCallState#PRECISE_CALL_STATE_IDLE + * @see PreciseCallState#PRECISE_CALL_STATE_ACTIVE + * @see PreciseCallState#PRECISE_CALL_STATE_HOLDING + * @see PreciseCallState#PRECISE_CALL_STATE_DIALING + * @see PreciseCallState#PRECISE_CALL_STATE_ALERTING + * @see PreciseCallState#PRECISE_CALL_STATE_INCOMING + * @see PreciseCallState#PRECISE_CALL_STATE_WAITING + * @see PreciseCallState#PRECISE_CALL_STATE_DISCONNECTED + * @see PreciseCallState#PRECISE_CALL_STATE_DISCONNECTING + * + * <p class="note"> + * Retrieve with + * {@link android.content.Intent#getIntExtra(String name, int defaultValue)}. + * + * @hide + */ + public static final String EXTRA_RINGING_CALL_STATE = "ringing_state"; + + /** + * The lookup key used with the {@link #ACTION_PRECISE_CALL_STATE_CHANGED} broadcast + * for an integer containing the state of the current foreground call. + * + * @see PreciseCallState#PRECISE_CALL_STATE_NOT_VALID + * @see PreciseCallState#PRECISE_CALL_STATE_IDLE + * @see PreciseCallState#PRECISE_CALL_STATE_ACTIVE + * @see PreciseCallState#PRECISE_CALL_STATE_HOLDING + * @see PreciseCallState#PRECISE_CALL_STATE_DIALING + * @see PreciseCallState#PRECISE_CALL_STATE_ALERTING + * @see PreciseCallState#PRECISE_CALL_STATE_INCOMING + * @see PreciseCallState#PRECISE_CALL_STATE_WAITING + * @see PreciseCallState#PRECISE_CALL_STATE_DISCONNECTED + * @see PreciseCallState#PRECISE_CALL_STATE_DISCONNECTING + * + * <p class="note"> + * Retrieve with + * {@link android.content.Intent#getIntExtra(String name, int defaultValue)}. + * + * @hide + */ + public static final String EXTRA_FOREGROUND_CALL_STATE = "foreground_state"; + + /** + * The lookup key used with the {@link #ACTION_PRECISE_CALL_STATE_CHANGED} broadcast + * for an integer containing the state of the current background call. + * + * @see PreciseCallState#PRECISE_CALL_STATE_NOT_VALID + * @see PreciseCallState#PRECISE_CALL_STATE_IDLE + * @see PreciseCallState#PRECISE_CALL_STATE_ACTIVE + * @see PreciseCallState#PRECISE_CALL_STATE_HOLDING + * @see PreciseCallState#PRECISE_CALL_STATE_DIALING + * @see PreciseCallState#PRECISE_CALL_STATE_ALERTING + * @see PreciseCallState#PRECISE_CALL_STATE_INCOMING + * @see PreciseCallState#PRECISE_CALL_STATE_WAITING + * @see PreciseCallState#PRECISE_CALL_STATE_DISCONNECTED + * @see PreciseCallState#PRECISE_CALL_STATE_DISCONNECTING + * + * <p class="note"> + * Retrieve with + * {@link android.content.Intent#getIntExtra(String name, int defaultValue)}. + * + * @hide + */ + public static final String EXTRA_BACKGROUND_CALL_STATE = "background_state"; + + /** + * The lookup key used with the {@link #ACTION_PRECISE_CALL_STATE_CHANGED} broadcast + * for an integer containing the disconnect cause. + * + * @see DisconnectCause + * + * <p class="note"> + * Retrieve with + * {@link android.content.Intent#getIntExtra(String name, int defaultValue)}. + * + * @hide + */ + public static final String EXTRA_DISCONNECT_CAUSE = "disconnect_cause"; + + /** + * The lookup key used with the {@link #ACTION_PRECISE_CALL_STATE_CHANGED} broadcast + * for an integer containing the disconnect cause provided by the RIL. + * + * @see PreciseDisconnectCause + * + * <p class="note"> + * Retrieve with + * {@link android.content.Intent#getIntExtra(String name, int defaultValue)}. + * + * @hide + */ + public static final String EXTRA_PRECISE_DISCONNECT_CAUSE = "precise_disconnect_cause"; + + /** + * Broadcast intent action indicating a data connection has changed, + * providing precise information about the connection. + * + * <p> + * The {@link #EXTRA_DATA_STATE} extra indicates the connection state. + * The {@link #EXTRA_DATA_NETWORK_TYPE} extra indicates the connection network type. + * The {@link #EXTRA_DATA_APN_TYPE} extra indicates the APN type. + * The {@link #EXTRA_DATA_APN} extra indicates the APN. + * The {@link #EXTRA_DATA_CHANGE_REASON} extra indicates the connection change reason. + * The {@link #EXTRA_DATA_IFACE_PROPERTIES} extra indicates the connection interface. + * The {@link #EXTRA_DATA_FAILURE_CAUSE} extra indicates the connection fail cause. + * + * <p class="note"> + * Requires the READ_PRECISE_PHONE_STATE permission. + * + * @see #EXTRA_DATA_STATE + * @see #EXTRA_DATA_NETWORK_TYPE + * @see #EXTRA_DATA_APN_TYPE + * @see #EXTRA_DATA_APN + * @see #EXTRA_DATA_CHANGE_REASON + * @see #EXTRA_DATA_IFACE + * @see #EXTRA_DATA_FAILURE_CAUSE + * @hide + * + * @deprecated If the app is running in the background, it won't be able to receive this + * broadcast. Apps should use ConnectivityManager {@link #registerNetworkCallback( + * android.net.NetworkRequest, ConnectivityManager.NetworkCallback)} to listen for network + * changes. + */ + @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION) + @Deprecated + public static final String ACTION_PRECISE_DATA_CONNECTION_STATE_CHANGED = + "android.intent.action.PRECISE_DATA_CONNECTION_STATE_CHANGED"; + + /** + * The lookup key used with the {@link #ACTION_PRECISE_DATA_CONNECTION_STATE_CHANGED} broadcast + * for an integer containing the state of the current data connection. + * + * @see TelephonyManager#DATA_UNKNOWN + * @see TelephonyManager#DATA_DISCONNECTED + * @see TelephonyManager#DATA_CONNECTING + * @see TelephonyManager#DATA_CONNECTED + * @see TelephonyManager#DATA_SUSPENDED + * + * <p class="note"> + * Retrieve with + * {@link android.content.Intent#getIntExtra(String name, int defaultValue)}. + * + * @hide + */ + public static final String EXTRA_DATA_STATE = PhoneConstants.STATE_KEY; + + /** + * The lookup key used with the {@link #ACTION_PRECISE_DATA_CONNECTION_STATE_CHANGED} broadcast + * for an integer containing the network type. + * + * @see TelephonyManager#NETWORK_TYPE_UNKNOWN + * @see TelephonyManager#NETWORK_TYPE_GPRS + * @see TelephonyManager#NETWORK_TYPE_EDGE + * @see TelephonyManager#NETWORK_TYPE_UMTS + * @see TelephonyManager#NETWORK_TYPE_CDMA + * @see TelephonyManager#NETWORK_TYPE_EVDO_0 + * @see TelephonyManager#NETWORK_TYPE_EVDO_A + * @see TelephonyManager#NETWORK_TYPE_1xRTT + * @see TelephonyManager#NETWORK_TYPE_HSDPA + * @see TelephonyManager#NETWORK_TYPE_HSUPA + * @see TelephonyManager#NETWORK_TYPE_HSPA + * @see TelephonyManager#NETWORK_TYPE_IDEN + * @see TelephonyManager#NETWORK_TYPE_EVDO_B + * @see TelephonyManager#NETWORK_TYPE_LTE + * @see TelephonyManager#NETWORK_TYPE_EHRPD + * @see TelephonyManager#NETWORK_TYPE_HSPAP + * + * <p class="note"> + * Retrieve with + * {@link android.content.Intent#getIntExtra(String name, int defaultValue)}. + * + * @hide + */ + public static final String EXTRA_DATA_NETWORK_TYPE = PhoneConstants.DATA_NETWORK_TYPE_KEY; + + /** + * The lookup key used with the {@link #ACTION_PRECISE_DATA_CONNECTION_STATE_CHANGED} broadcast + * for an String containing the data APN type. + * + * <p class="note"> + * Retrieve with + * {@link android.content.Intent#getStringExtra(String name)}. + * + * @hide + */ + public static final String EXTRA_DATA_APN_TYPE = PhoneConstants.DATA_APN_TYPE_KEY; + + /** + * The lookup key used with the {@link #ACTION_PRECISE_DATA_CONNECTION_STATE_CHANGED} broadcast + * for an String containing the data APN. + * + * <p class="note"> + * Retrieve with + * {@link android.content.Intent#getStringExtra(String name)}. + * + * @hide + */ + public static final String EXTRA_DATA_APN = PhoneConstants.DATA_APN_KEY; + + /** + * The lookup key used with the {@link #ACTION_PRECISE_DATA_CONNECTION_STATE_CHANGED} broadcast + * for an String representation of the change reason. + * + * <p class="note"> + * Retrieve with + * {@link android.content.Intent#getStringExtra(String name)}. + * + * @hide + */ + public static final String EXTRA_DATA_CHANGE_REASON = PhoneConstants.STATE_CHANGE_REASON_KEY; + + /** + * The lookup key used with the {@link #ACTION_PRECISE_DATA_CONNECTION_STATE_CHANGED} broadcast + * for an String representation of the data interface. + * + * <p class="note"> + * Retrieve with + * {@link android.content.Intent#getParcelableExtra(String name)}. + * + * @hide + */ + public static final String EXTRA_DATA_LINK_PROPERTIES_KEY = PhoneConstants.DATA_LINK_PROPERTIES_KEY; + + /** + * The lookup key used with the {@link #ACTION_PRECISE_DATA_CONNECTION_STATE_CHANGED} broadcast + * for the data connection fail cause. + * + * <p class="note"> + * Retrieve with + * {@link android.content.Intent#getStringExtra(String name)}. + * + * @hide + */ + public static final String EXTRA_DATA_FAILURE_CAUSE = PhoneConstants.DATA_FAILURE_CAUSE_KEY; + + /** + * Broadcast intent action for letting the default dialer to know to show voicemail + * notification. + * + * <p> + * The {@link #EXTRA_PHONE_ACCOUNT_HANDLE} extra indicates which {@link PhoneAccountHandle} the + * voicemail is received on. + * The {@link #EXTRA_NOTIFICATION_COUNT} extra indicates the total numbers of unheard + * voicemails. + * The {@link #EXTRA_VOICEMAIL_NUMBER} extra indicates the voicemail number if available. + * The {@link #EXTRA_CALL_VOICEMAIL_INTENT} extra is a {@link android.app.PendingIntent} that + * will call the voicemail number when sent. This extra will be empty if the voicemail number + * is not set, and {@link #EXTRA_LAUNCH_VOICEMAIL_SETTINGS_INTENT} will be set instead. + * The {@link #EXTRA_LAUNCH_VOICEMAIL_SETTINGS_INTENT} extra is a + * {@link android.app.PendingIntent} that will launch the voicemail settings. This extra is only + * available when the voicemail number is not set. + * The {@link #EXTRA_IS_REFRESH} extra indicates whether the notification is a refresh or a new + * notification. + * + * @see #EXTRA_PHONE_ACCOUNT_HANDLE + * @see #EXTRA_NOTIFICATION_COUNT + * @see #EXTRA_VOICEMAIL_NUMBER + * @see #EXTRA_CALL_VOICEMAIL_INTENT + * @see #EXTRA_LAUNCH_VOICEMAIL_SETTINGS_INTENT + * @see #EXTRA_IS_REFRESH + */ + public static final String ACTION_SHOW_VOICEMAIL_NOTIFICATION = + "android.telephony.action.SHOW_VOICEMAIL_NOTIFICATION"; + + /** + * The extra used with an {@link #ACTION_SHOW_VOICEMAIL_NOTIFICATION} {@code Intent} to specify + * the {@link PhoneAccountHandle} the notification is for. + * <p class="note"> + * Retrieve with {@link android.content.Intent#getParcelableExtra(String)}. + */ + public static final String EXTRA_PHONE_ACCOUNT_HANDLE = + "android.telephony.extra.PHONE_ACCOUNT_HANDLE"; + + /** + * The number of voice messages associated with the notification. + */ + public static final String EXTRA_NOTIFICATION_COUNT = + "android.telephony.extra.NOTIFICATION_COUNT"; + + /** + * The voicemail number. + */ + public static final String EXTRA_VOICEMAIL_NUMBER = + "android.telephony.extra.VOICEMAIL_NUMBER"; + + /** + * The intent to call voicemail. + */ + public static final String EXTRA_CALL_VOICEMAIL_INTENT = + "android.telephony.extra.CALL_VOICEMAIL_INTENT"; + + /** + * The intent to launch voicemail settings. + */ + public static final String EXTRA_LAUNCH_VOICEMAIL_SETTINGS_INTENT = + "android.telephony.extra.LAUNCH_VOICEMAIL_SETTINGS_INTENT"; + + /** + * Boolean value representing whether the {@link + * TelephonyManager#ACTION_SHOW_VOICEMAIL_NOTIFICATION} is new or a refresh of an existing + * notification. Notification refresh happens after reboot or connectivity changes. The user has + * already been notified for the voicemail so it should not alert the user, and should not be + * shown again if the user has dismissed it. + */ + public static final String EXTRA_IS_REFRESH = "android.telephony.extra.IS_REFRESH"; + + /** + * {@link android.telecom.Connection} event used to indicate that an IMS call has be + * successfully handed over from WIFI to LTE. + * <p> + * Sent via {@link android.telecom.Connection#sendConnectionEvent(String, Bundle)}. + * The {@link Bundle} parameter is expected to be null when this connection event is used. + * @hide + */ + public static final String EVENT_HANDOVER_VIDEO_FROM_WIFI_TO_LTE = + "android.telephony.event.EVENT_HANDOVER_VIDEO_FROM_WIFI_TO_LTE"; + + /** + * {@link android.telecom.Connection} event used to indicate that an IMS call failed to be + * handed over from LTE to WIFI. + * <p> + * Sent via {@link android.telecom.Connection#sendConnectionEvent(String, Bundle)}. + * The {@link Bundle} parameter is expected to be null when this connection event is used. + * @hide + */ + public static final String EVENT_HANDOVER_TO_WIFI_FAILED = + "android.telephony.event.EVENT_HANDOVER_TO_WIFI_FAILED"; + + /** + * {@link android.telecom.Connection} event used to indicate that a video call was downgraded to + * audio because the data limit was reached. + * <p> + * Sent via {@link android.telecom.Connection#sendConnectionEvent(String, Bundle)}. + * The {@link Bundle} parameter is expected to be null when this connection event is used. + * @hide + */ + public static final String EVENT_DOWNGRADE_DATA_LIMIT_REACHED = + "android.telephony.event.EVENT_DOWNGRADE_DATA_LIMIT_REACHED"; + + /** + * {@link android.telecom.Connection} event used to indicate that a video call was downgraded to + * audio because the data was disabled. + * <p> + * Sent via {@link android.telecom.Connection#sendConnectionEvent(String, Bundle)}. + * The {@link Bundle} parameter is expected to be null when this connection event is used. + * @hide + */ + public static final String EVENT_DOWNGRADE_DATA_DISABLED = + "android.telephony.event.EVENT_DOWNGRADE_DATA_DISABLED"; + + /** + * {@link android.telecom.Connection} event used to indicate that the InCall UI should notify + * the user when an international call is placed while on WFC only. + * <p> + * Used when the carrier config value + * {@link CarrierConfigManager#KEY_NOTIFY_INTERNATIONAL_CALL_ON_WFC_BOOL} is true, the device + * is on WFC (VoLTE not available) and an international number is dialed. + * <p> + * Sent via {@link android.telecom.Connection#sendConnectionEvent(String, Bundle)}. + * The {@link Bundle} parameter is expected to be null when this connection event is used. + * @hide + */ + public static final String EVENT_NOTIFY_INTERNATIONAL_CALL_ON_WFC = + "android.telephony.event.EVENT_NOTIFY_INTERNATIONAL_CALL_ON_WFC"; + + /** + * {@link android.telecom.Connection} event used to indicate that an outgoing call has been + * forwarded to another number. + * <p> + * Sent in response to an IMS supplementary service notification indicating the call has been + * forwarded. + * <p> + * Sent via {@link android.telecom.Connection#sendConnectionEvent(String, Bundle)}. + * The {@link Bundle} parameter is expected to be null when this connection event is used. + * @hide + */ + public static final String EVENT_CALL_FORWARDED = + "android.telephony.event.EVENT_CALL_FORWARDED"; + + /* Visual voicemail protocols */ + + /** + * The OMTP protocol. + */ + public static final String VVM_TYPE_OMTP = "vvm_type_omtp"; + + /** + * A flavor of OMTP protocol with a different mobile originated (MO) format + */ + public static final String VVM_TYPE_CVVM = "vvm_type_cvvm"; + + /** + * Key in bundle returned by {@link #getVisualVoicemailPackageName()}, indicating whether visual + * voicemail was enabled or disabled by the user. If the user never explicitly changed this + * setting, this key will not exist. + * + * @see #getVisualVoicemailSettings() + * @hide + */ + @SystemApi + public static final String EXTRA_VISUAL_VOICEMAIL_ENABLED_BY_USER_BOOL = + "android.telephony.extra.VISUAL_VOICEMAIL_ENABLED_BY_USER_BOOL"; + + /** + * Key in bundle returned by {@link #getVisualVoicemailPackageName()}, indicating the voicemail + * access PIN scrambled during the auto provisioning process. The user is expected to reset + * their PIN if this value is not {@code null}. + * + * @see #getVisualVoicemailSettings() + * @hide + */ + @SystemApi + public static final String EXTRA_VOICEMAIL_SCRAMBLED_PIN_STRING = + "android.telephony.extra.VOICEMAIL_SCRAMBLED_PIN_STRING"; + + /** + * @hide + */ + public static final String USSD_RESPONSE = "USSD_RESPONSE"; + + /** + * USSD return code success. + * @hide + */ + public static final int USSD_RETURN_SUCCESS = 100; + + /** + * Failed code returned when the mobile network has failed to complete a USSD request. + * <p> + * Returned via {@link TelephonyManager.UssdResponseCallback#onReceiveUssdResponseFailed( + * TelephonyManager, String, int)}. + */ + public static final int USSD_RETURN_FAILURE = -1; + + /** + * Failure code returned when a USSD request has failed to execute because the Telephony + * service is unavailable. + * <p> + * Returned via {@link TelephonyManager.UssdResponseCallback#onReceiveUssdResponseFailed( + * TelephonyManager, String, int)}. + */ + public static final int USSD_ERROR_SERVICE_UNAVAIL = -2; + + // + // + // Device Info + // + // + + /** + * Returns the software version number for the device, for example, + * the IMEI/SV for GSM phones. Return null if the software version is + * not available. + */ + @RequiresPermission(android.Manifest.permission.READ_PHONE_STATE) + public String getDeviceSoftwareVersion() { + return getDeviceSoftwareVersion(getSlotIndex()); + } + + /** + * Returns the software version number for the device, for example, + * the IMEI/SV for GSM phones. Return null if the software version is + * not available. + * + * @param slotIndex of which deviceID is returned + */ + /** {@hide} */ + @RequiresPermission(android.Manifest.permission.READ_PHONE_STATE) + public String getDeviceSoftwareVersion(int slotIndex) { + ITelephony telephony = getITelephony(); + if (telephony == null) return null; + + try { + return telephony.getDeviceSoftwareVersionForSlot(slotIndex, getOpPackageName()); + } catch (RemoteException ex) { + return null; + } catch (NullPointerException ex) { + return null; + } + } + + /** + * Returns the unique device ID, for example, the IMEI for GSM and the MEID + * or ESN for CDMA phones. Return null if device ID is not available. + * + * @deprecated Use (@link getImei} which returns IMEI for GSM or (@link getMeid} which returns + * MEID for CDMA. + */ + @Deprecated + @RequiresPermission(android.Manifest.permission.READ_PHONE_STATE) + public String getDeviceId() { + try { + ITelephony telephony = getITelephony(); + if (telephony == null) + return null; + return telephony.getDeviceId(mContext.getOpPackageName()); + } catch (RemoteException ex) { + return null; + } catch (NullPointerException ex) { + return null; + } + } + + /** + * Returns the unique device ID of a subscription, for example, the IMEI for + * GSM and the MEID for CDMA phones. Return null if device ID is not available. + * + * @param slotIndex of which deviceID is returned + * + * @deprecated Use (@link getImei} which returns IMEI for GSM or (@link getMeid} which returns + * MEID for CDMA. + */ + @Deprecated + @RequiresPermission(android.Manifest.permission.READ_PHONE_STATE) + public String getDeviceId(int slotIndex) { + // FIXME this assumes phoneId == slotIndex + try { + IPhoneSubInfo info = getSubscriberInfo(); + if (info == null) + return null; + return info.getDeviceIdForPhone(slotIndex, mContext.getOpPackageName()); + } catch (RemoteException ex) { + return null; + } catch (NullPointerException ex) { + return null; + } + } + + /** + * Returns the IMEI (International Mobile Equipment Identity). Return null if IMEI is not + * available. + */ + @RequiresPermission(android.Manifest.permission.READ_PHONE_STATE) + public String getImei() { + return getImei(getSlotIndex()); + } + + /** + * Returns the IMEI (International Mobile Equipment Identity). Return null if IMEI is not + * available. + * + * @param slotIndex of which IMEI is returned + */ + @RequiresPermission(android.Manifest.permission.READ_PHONE_STATE) + public String getImei(int slotIndex) { + ITelephony telephony = getITelephony(); + if (telephony == null) return null; + + try { + return telephony.getImeiForSlot(slotIndex, getOpPackageName()); + } catch (RemoteException ex) { + return null; + } catch (NullPointerException ex) { + return null; + } + } + + /** + * Returns the MEID (Mobile Equipment Identifier). Return null if MEID is not available. + */ + @RequiresPermission(android.Manifest.permission.READ_PHONE_STATE) + public String getMeid() { + return getMeid(getSlotIndex()); + } + + /** + * Returns the MEID (Mobile Equipment Identifier). Return null if MEID is not available. + * + * @param slotIndex of which MEID is returned + */ + @RequiresPermission(android.Manifest.permission.READ_PHONE_STATE) + public String getMeid(int slotIndex) { + ITelephony telephony = getITelephony(); + if (telephony == null) return null; + + try { + return telephony.getMeidForSlot(slotIndex, getOpPackageName()); + } catch (RemoteException ex) { + return null; + } catch (NullPointerException ex) { + return null; + } + } + + /** + * Returns the NAI. Return null if NAI is not available. + * + */ + /** {@hide}*/ + public String getNai() { + return getNai(getSlotIndex()); + } + + /** + * Returns the NAI. Return null if NAI is not available. + * + * @param slotIndex of which Nai is returned + */ + /** {@hide}*/ + public String getNai(int slotIndex) { + int[] subId = SubscriptionManager.getSubId(slotIndex); + try { + IPhoneSubInfo info = getSubscriberInfo(); + if (info == null) + return null; + String nai = info.getNaiForSubscriber(subId[0], mContext.getOpPackageName()); + if (Log.isLoggable(TAG, Log.VERBOSE)) { + Rlog.v(TAG, "Nai = " + nai); + } + return nai; + } catch (RemoteException ex) { + return null; + } catch (NullPointerException ex) { + return null; + } + } + + /** + * Returns the current location of the device. + *<p> + * If there is only one radio in the device and that radio has an LTE connection, + * this method will return null. The implementation must not to try add LTE + * identifiers into the existing cdma/gsm classes. + *<p> + * @return Current location of the device or null if not available. + * + * @deprecated use {@link #getAllCellInfo} instead, which returns a superset of this API. + */ + @Deprecated + @RequiresPermission(anyOf = { + android.Manifest.permission.ACCESS_COARSE_LOCATION, + android.Manifest.permission.ACCESS_FINE_LOCATION + }) + public CellLocation getCellLocation() { + try { + ITelephony telephony = getITelephony(); + if (telephony == null) { + Rlog.d(TAG, "getCellLocation returning null because telephony is null"); + return null; + } + Bundle bundle = telephony.getCellLocation(mContext.getOpPackageName()); + if (bundle.isEmpty()) { + Rlog.d(TAG, "getCellLocation returning null because bundle is empty"); + return null; + } + CellLocation cl = CellLocation.newFromBundle(bundle); + if (cl.isEmpty()) { + Rlog.d(TAG, "getCellLocation returning null because CellLocation is empty"); + return null; + } + return cl; + } catch (RemoteException ex) { + Rlog.d(TAG, "getCellLocation returning null due to RemoteException " + ex); + return null; + } catch (NullPointerException ex) { + Rlog.d(TAG, "getCellLocation returning null due to NullPointerException " + ex); + return null; + } + } + + /** + * Enables location update notifications. {@link PhoneStateListener#onCellLocationChanged + * PhoneStateListener.onCellLocationChanged} will be called on location updates. + * + * @hide + */ + @RequiresPermission(android.Manifest.permission.CONTROL_LOCATION_UPDATES) + public void enableLocationUpdates() { + enableLocationUpdates(getSubId()); + } + + /** + * Enables location update notifications for a subscription. + * {@link PhoneStateListener#onCellLocationChanged + * PhoneStateListener.onCellLocationChanged} will be called on location updates. + * + * @param subId for which the location updates are enabled + * @hide + */ + @RequiresPermission(android.Manifest.permission.CONTROL_LOCATION_UPDATES) + public void enableLocationUpdates(int subId) { + try { + ITelephony telephony = getITelephony(); + if (telephony != null) + telephony.enableLocationUpdatesForSubscriber(subId); + } catch (RemoteException ex) { + } catch (NullPointerException ex) { + } + } + + /** + * Disables location update notifications. {@link PhoneStateListener#onCellLocationChanged + * PhoneStateListener.onCellLocationChanged} will be called on location updates. + * + * @hide + */ + @RequiresPermission(android.Manifest.permission.CONTROL_LOCATION_UPDATES) + public void disableLocationUpdates() { + disableLocationUpdates(getSubId()); + } + + /** @hide */ + public void disableLocationUpdates(int subId) { + try { + ITelephony telephony = getITelephony(); + if (telephony != null) + telephony.disableLocationUpdatesForSubscriber(subId); + } catch (RemoteException ex) { + } catch (NullPointerException ex) { + } + } + + /** + * Returns the neighboring cell information of the device. + * + * @return List of NeighboringCellInfo or null if info unavailable. + * + * @deprecated Use {@link #getAllCellInfo} which returns a superset of the information + * from NeighboringCellInfo. + */ + @Deprecated + @RequiresPermission(android.Manifest.permission.ACCESS_COARSE_LOCATION) + public List<NeighboringCellInfo> getNeighboringCellInfo() { + try { + ITelephony telephony = getITelephony(); + if (telephony == null) + return null; + return telephony.getNeighboringCellInfo(mContext.getOpPackageName()); + } catch (RemoteException ex) { + return null; + } catch (NullPointerException ex) { + return null; + } + } + + /** No phone radio. */ + public static final int PHONE_TYPE_NONE = PhoneConstants.PHONE_TYPE_NONE; + /** Phone radio is GSM. */ + public static final int PHONE_TYPE_GSM = PhoneConstants.PHONE_TYPE_GSM; + /** Phone radio is CDMA. */ + public static final int PHONE_TYPE_CDMA = PhoneConstants.PHONE_TYPE_CDMA; + /** Phone is via SIP. */ + public static final int PHONE_TYPE_SIP = PhoneConstants.PHONE_TYPE_SIP; + + /** + * Returns the current phone type. + * TODO: This is a last minute change and hence hidden. + * + * @see #PHONE_TYPE_NONE + * @see #PHONE_TYPE_GSM + * @see #PHONE_TYPE_CDMA + * @see #PHONE_TYPE_SIP + * + * {@hide} + */ + @SystemApi + public int getCurrentPhoneType() { + return getCurrentPhoneType(getSubId()); + } + + /** + * Returns a constant indicating the device phone type for a subscription. + * + * @see #PHONE_TYPE_NONE + * @see #PHONE_TYPE_GSM + * @see #PHONE_TYPE_CDMA + * + * @param subId for which phone type is returned + * @hide + */ + @SystemApi + public int getCurrentPhoneType(int subId) { + int phoneId; + if (subId == SubscriptionManager.INVALID_SUBSCRIPTION_ID) { + // if we don't have any sims, we don't have subscriptions, but we + // still may want to know what type of phone we've got. + phoneId = 0; + } else { + phoneId = SubscriptionManager.getPhoneId(subId); + } + + return getCurrentPhoneTypeForSlot(phoneId); + } + + /** + * See getCurrentPhoneType. + * + * @hide + */ + public int getCurrentPhoneTypeForSlot(int slotIndex) { + try{ + ITelephony telephony = getITelephony(); + if (telephony != null) { + return telephony.getActivePhoneTypeForSlot(slotIndex); + } else { + // This can happen when the ITelephony interface is not up yet. + return getPhoneTypeFromProperty(slotIndex); + } + } catch (RemoteException ex) { + // This shouldn't happen in the normal case, as a backup we + // read from the system property. + return getPhoneTypeFromProperty(slotIndex); + } catch (NullPointerException ex) { + // This shouldn't happen in the normal case, as a backup we + // read from the system property. + return getPhoneTypeFromProperty(slotIndex); + } + } + + /** + * Returns a constant indicating the device phone type. This + * indicates the type of radio used to transmit voice calls. + * + * @see #PHONE_TYPE_NONE + * @see #PHONE_TYPE_GSM + * @see #PHONE_TYPE_CDMA + * @see #PHONE_TYPE_SIP + */ + public int getPhoneType() { + if (!isVoiceCapable()) { + return PHONE_TYPE_NONE; + } + return getCurrentPhoneType(); + } + + private int getPhoneTypeFromProperty() { + return getPhoneTypeFromProperty(getPhoneId()); + } + + /** {@hide} */ + private int getPhoneTypeFromProperty(int phoneId) { + String type = getTelephonyProperty(phoneId, + TelephonyProperties.CURRENT_ACTIVE_PHONE, null); + if (type == null || type.isEmpty()) { + return getPhoneTypeFromNetworkType(phoneId); + } + return Integer.parseInt(type); + } + + private int getPhoneTypeFromNetworkType() { + return getPhoneTypeFromNetworkType(getPhoneId()); + } + + /** {@hide} */ + private int getPhoneTypeFromNetworkType(int phoneId) { + // When the system property CURRENT_ACTIVE_PHONE, has not been set, + // use the system property for default network type. + // This is a fail safe, and can only happen at first boot. + String mode = getTelephonyProperty(phoneId, "ro.telephony.default_network", null); + if (mode != null && !mode.isEmpty()) { + return TelephonyManager.getPhoneType(Integer.parseInt(mode)); + } + return TelephonyManager.PHONE_TYPE_NONE; + } + + /** + * This function returns the type of the phone, depending + * on the network mode. + * + * @param networkMode + * @return Phone Type + * + * @hide + */ + public static int getPhoneType(int networkMode) { + switch(networkMode) { + case RILConstants.NETWORK_MODE_CDMA: + case RILConstants.NETWORK_MODE_CDMA_NO_EVDO: + case RILConstants.NETWORK_MODE_EVDO_NO_CDMA: + return PhoneConstants.PHONE_TYPE_CDMA; + + case RILConstants.NETWORK_MODE_WCDMA_PREF: + case RILConstants.NETWORK_MODE_GSM_ONLY: + case RILConstants.NETWORK_MODE_WCDMA_ONLY: + case RILConstants.NETWORK_MODE_GSM_UMTS: + case RILConstants.NETWORK_MODE_LTE_GSM_WCDMA: + case RILConstants.NETWORK_MODE_LTE_WCDMA: + case RILConstants.NETWORK_MODE_LTE_CDMA_EVDO_GSM_WCDMA: + case RILConstants.NETWORK_MODE_TDSCDMA_ONLY: + case RILConstants.NETWORK_MODE_TDSCDMA_WCDMA: + case RILConstants.NETWORK_MODE_LTE_TDSCDMA: + case RILConstants.NETWORK_MODE_TDSCDMA_GSM: + case RILConstants.NETWORK_MODE_LTE_TDSCDMA_GSM: + case RILConstants.NETWORK_MODE_TDSCDMA_GSM_WCDMA: + case RILConstants.NETWORK_MODE_LTE_TDSCDMA_WCDMA: + case RILConstants.NETWORK_MODE_LTE_TDSCDMA_GSM_WCDMA: + case RILConstants.NETWORK_MODE_LTE_TDSCDMA_CDMA_EVDO_GSM_WCDMA: + return PhoneConstants.PHONE_TYPE_GSM; + + // Use CDMA Phone for the global mode including CDMA + case RILConstants.NETWORK_MODE_GLOBAL: + case RILConstants.NETWORK_MODE_LTE_CDMA_EVDO: + case RILConstants.NETWORK_MODE_TDSCDMA_CDMA_EVDO_GSM_WCDMA: + return PhoneConstants.PHONE_TYPE_CDMA; + + case RILConstants.NETWORK_MODE_LTE_ONLY: + if (getLteOnCdmaModeStatic() == PhoneConstants.LTE_ON_CDMA_TRUE) { + return PhoneConstants.PHONE_TYPE_CDMA; + } else { + return PhoneConstants.PHONE_TYPE_GSM; + } + default: + return PhoneConstants.PHONE_TYPE_GSM; + } + } + + /** + * The contents of the /proc/cmdline file + */ + private static String getProcCmdLine() + { + String cmdline = ""; + FileInputStream is = null; + try { + is = new FileInputStream("/proc/cmdline"); + byte [] buffer = new byte[2048]; + int count = is.read(buffer); + if (count > 0) { + cmdline = new String(buffer, 0, count); + } + } catch (IOException e) { + Rlog.d(TAG, "No /proc/cmdline exception=" + e); + } finally { + if (is != null) { + try { + is.close(); + } catch (IOException e) { + } + } + } + Rlog.d(TAG, "/proc/cmdline=" + cmdline); + return cmdline; + } + + /** Kernel command line */ + private static final String sKernelCmdLine = getProcCmdLine(); + + /** Pattern for selecting the product type from the kernel command line */ + private static final Pattern sProductTypePattern = + Pattern.compile("\\sproduct_type\\s*=\\s*(\\w+)"); + + /** The ProductType used for LTE on CDMA devices */ + private static final String sLteOnCdmaProductType = + SystemProperties.get(TelephonyProperties.PROPERTY_LTE_ON_CDMA_PRODUCT_TYPE, ""); + + /** + * Return if the current radio is LTE on CDMA. This + * is a tri-state return value as for a period of time + * the mode may be unknown. + * + * @return {@link PhoneConstants#LTE_ON_CDMA_UNKNOWN}, {@link PhoneConstants#LTE_ON_CDMA_FALSE} + * or {@link PhoneConstants#LTE_ON_CDMA_TRUE} + * + * @hide + */ + public static int getLteOnCdmaModeStatic() { + int retVal; + int curVal; + String productType = ""; + + curVal = SystemProperties.getInt(TelephonyProperties.PROPERTY_LTE_ON_CDMA_DEVICE, + PhoneConstants.LTE_ON_CDMA_UNKNOWN); + retVal = curVal; + if (retVal == PhoneConstants.LTE_ON_CDMA_UNKNOWN) { + Matcher matcher = sProductTypePattern.matcher(sKernelCmdLine); + if (matcher.find()) { + productType = matcher.group(1); + if (sLteOnCdmaProductType.equals(productType)) { + retVal = PhoneConstants.LTE_ON_CDMA_TRUE; + } else { + retVal = PhoneConstants.LTE_ON_CDMA_FALSE; + } + } else { + retVal = PhoneConstants.LTE_ON_CDMA_FALSE; + } + } + + Rlog.d(TAG, "getLteOnCdmaMode=" + retVal + " curVal=" + curVal + + " product_type='" + productType + + "' lteOnCdmaProductType='" + sLteOnCdmaProductType + "'"); + return retVal; + } + + // + // + // Current Network + // + // + + /** + * Returns the alphabetic name of current registered operator. + * <p> + * Availability: Only when user is registered to a network. Result may be + * unreliable on CDMA networks (use {@link #getPhoneType()} to determine if + * on a CDMA network). + */ + public String getNetworkOperatorName() { + return getNetworkOperatorName(getSubId()); + } + + /** + * Returns the alphabetic name of current registered operator + * for a particular subscription. + * <p> + * Availability: Only when user is registered to a network. Result may be + * unreliable on CDMA networks (use {@link #getPhoneType()} to determine if + * on a CDMA network). + * @param subId + * @hide + */ + public String getNetworkOperatorName(int subId) { + int phoneId = SubscriptionManager.getPhoneId(subId); + return getTelephonyProperty(phoneId, TelephonyProperties.PROPERTY_OPERATOR_ALPHA, ""); + } + + /** + * Returns the numeric name (MCC+MNC) of current registered operator. + * <p> + * Availability: Only when user is registered to a network. Result may be + * unreliable on CDMA networks (use {@link #getPhoneType()} to determine if + * on a CDMA network). + */ + public String getNetworkOperator() { + return getNetworkOperatorForPhone(getPhoneId()); + } + + /** + * Returns the numeric name (MCC+MNC) of current registered operator + * for a particular subscription. + * <p> + * Availability: Only when user is registered to a network. Result may be + * unreliable on CDMA networks (use {@link #getPhoneType()} to determine if + * on a CDMA network). + * + * @param subId + * @hide + */ + public String getNetworkOperator(int subId) { + int phoneId = SubscriptionManager.getPhoneId(subId); + return getNetworkOperatorForPhone(phoneId); + } + + /** + * Returns the numeric name (MCC+MNC) of current registered operator + * for a particular subscription. + * <p> + * Availability: Only when user is registered to a network. Result may be + * unreliable on CDMA networks (use {@link #getPhoneType()} to determine if + * on a CDMA network). + * + * @param phoneId + * @hide + **/ + public String getNetworkOperatorForPhone(int phoneId) { + return getTelephonyProperty(phoneId, TelephonyProperties.PROPERTY_OPERATOR_NUMERIC, ""); + } + + + /** + * Returns the network specifier of the subscription ID pinned to the TelephonyManager. The + * network specifier is used by {@link + * android.net.NetworkRequest.Builder#setNetworkSpecifier(String)} to create a {@link + * android.net.NetworkRequest} that connects through the subscription. + * + * @see android.net.NetworkRequest.Builder#setNetworkSpecifier(String) + * @see #createForSubscriptionId(int) + * @see #createForPhoneAccountHandle(PhoneAccountHandle) + */ + public String getNetworkSpecifier() { + return String.valueOf(getSubId()); + } + + /** + * Returns the carrier config of the subscription ID pinned to the TelephonyManager. If an + * invalid subscription ID is pinned to the TelephonyManager, the returned config will contain + * default values. + * + * @see CarrierConfigManager#getConfigForSubId(int) + * @see #createForSubscriptionId(int) + * @see #createForPhoneAccountHandle(PhoneAccountHandle) + */ + @WorkerThread + @RequiresPermission(android.Manifest.permission.READ_PHONE_STATE) + public PersistableBundle getCarrierConfig() { + CarrierConfigManager carrierConfigManager = mContext + .getSystemService(CarrierConfigManager.class); + return carrierConfigManager.getConfigForSubId(getSubId()); + } + + /** + * Returns true if the device is considered roaming on the current + * network, for GSM purposes. + * <p> + * Availability: Only when user registered to a network. + */ + public boolean isNetworkRoaming() { + return isNetworkRoaming(getSubId()); + } + + /** + * Returns true if the device is considered roaming on the current + * network for a subscription. + * <p> + * Availability: Only when user registered to a network. + * + * @param subId + * @hide + */ + public boolean isNetworkRoaming(int subId) { + int phoneId = SubscriptionManager.getPhoneId(subId); + return Boolean.parseBoolean(getTelephonyProperty(phoneId, + TelephonyProperties.PROPERTY_OPERATOR_ISROAMING, null)); + } + + /** + * Returns the ISO country code equivalent of the current registered + * operator's MCC (Mobile Country Code). + * <p> + * Availability: Only when user is registered to a network. Result may be + * unreliable on CDMA networks (use {@link #getPhoneType()} to determine if + * on a CDMA network). + */ + public String getNetworkCountryIso() { + return getNetworkCountryIsoForPhone(getPhoneId()); + } + + /** + * Returns the ISO country code equivalent of the current registered + * operator's MCC (Mobile Country Code) of a subscription. + * <p> + * Availability: Only when user is registered to a network. Result may be + * unreliable on CDMA networks (use {@link #getPhoneType()} to determine if + * on a CDMA network). + * + * @param subId for which Network CountryIso is returned + * @hide + */ + public String getNetworkCountryIso(int subId) { + return getNetworkCountryIsoForPhone(getPhoneId(subId)); + } + + /** + * Returns the ISO country code equivalent of the current registered + * operator's MCC (Mobile Country Code) of a subscription. + * <p> + * Availability: Only when user is registered to a network. Result may be + * unreliable on CDMA networks (use {@link #getPhoneType()} to determine if + * on a CDMA network). + * + * @param phoneId for which Network CountryIso is returned + */ + /** {@hide} */ + public String getNetworkCountryIsoForPhone(int phoneId) { + try { + ITelephony telephony = getITelephony(); + if (telephony == null) return ""; + return telephony.getNetworkCountryIsoForPhone(phoneId); + } catch (RemoteException ex) { + return ""; + } + } + + /* + * When adding a network type to the list below, make sure to add the correct icon to + * MobileSignalController.mapIconSets(). + */ + /** Network type is unknown */ + public static final int NETWORK_TYPE_UNKNOWN = 0; + /** Current network is GPRS */ + public static final int NETWORK_TYPE_GPRS = 1; + /** Current network is EDGE */ + public static final int NETWORK_TYPE_EDGE = 2; + /** Current network is UMTS */ + public static final int NETWORK_TYPE_UMTS = 3; + /** Current network is CDMA: Either IS95A or IS95B*/ + public static final int NETWORK_TYPE_CDMA = 4; + /** Current network is EVDO revision 0*/ + public static final int NETWORK_TYPE_EVDO_0 = 5; + /** Current network is EVDO revision A*/ + public static final int NETWORK_TYPE_EVDO_A = 6; + /** Current network is 1xRTT*/ + public static final int NETWORK_TYPE_1xRTT = 7; + /** Current network is HSDPA */ + public static final int NETWORK_TYPE_HSDPA = 8; + /** Current network is HSUPA */ + public static final int NETWORK_TYPE_HSUPA = 9; + /** Current network is HSPA */ + public static final int NETWORK_TYPE_HSPA = 10; + /** Current network is iDen */ + public static final int NETWORK_TYPE_IDEN = 11; + /** Current network is EVDO revision B*/ + public static final int NETWORK_TYPE_EVDO_B = 12; + /** Current network is LTE */ + public static final int NETWORK_TYPE_LTE = 13; + /** Current network is eHRPD */ + public static final int NETWORK_TYPE_EHRPD = 14; + /** Current network is HSPA+ */ + public static final int NETWORK_TYPE_HSPAP = 15; + /** Current network is GSM */ + public static final int NETWORK_TYPE_GSM = 16; + /** Current network is TD_SCDMA */ + public static final int NETWORK_TYPE_TD_SCDMA = 17; + /** Current network is IWLAN */ + public static final int NETWORK_TYPE_IWLAN = 18; + /** Current network is LTE_CA {@hide} */ + public static final int NETWORK_TYPE_LTE_CA = 19; + /** + * @return the NETWORK_TYPE_xxxx for current data connection. + */ + public int getNetworkType() { + try { + ITelephony telephony = getITelephony(); + if (telephony != null) { + return telephony.getNetworkType(); + } else { + // This can happen when the ITelephony interface is not up yet. + return NETWORK_TYPE_UNKNOWN; + } + } catch(RemoteException ex) { + // This shouldn't happen in the normal case + return NETWORK_TYPE_UNKNOWN; + } catch (NullPointerException ex) { + // This could happen before phone restarts due to crashing + return NETWORK_TYPE_UNKNOWN; + } + } + + /** + * Returns a constant indicating the radio technology (network type) + * currently in use on the device for a subscription. + * @return the network type + * + * @param subId for which network type is returned + * + * @see #NETWORK_TYPE_UNKNOWN + * @see #NETWORK_TYPE_GPRS + * @see #NETWORK_TYPE_EDGE + * @see #NETWORK_TYPE_UMTS + * @see #NETWORK_TYPE_HSDPA + * @see #NETWORK_TYPE_HSUPA + * @see #NETWORK_TYPE_HSPA + * @see #NETWORK_TYPE_CDMA + * @see #NETWORK_TYPE_EVDO_0 + * @see #NETWORK_TYPE_EVDO_A + * @see #NETWORK_TYPE_EVDO_B + * @see #NETWORK_TYPE_1xRTT + * @see #NETWORK_TYPE_IDEN + * @see #NETWORK_TYPE_LTE + * @see #NETWORK_TYPE_EHRPD + * @see #NETWORK_TYPE_HSPAP + * + * @hide + */ + @RequiresPermission(android.Manifest.permission.READ_PHONE_STATE) + public int getNetworkType(int subId) { + try { + ITelephony telephony = getITelephony(); + if (telephony != null) { + return telephony.getNetworkTypeForSubscriber(subId, getOpPackageName()); + } else { + // This can happen when the ITelephony interface is not up yet. + return NETWORK_TYPE_UNKNOWN; + } + } catch(RemoteException ex) { + // This shouldn't happen in the normal case + return NETWORK_TYPE_UNKNOWN; + } catch (NullPointerException ex) { + // This could happen before phone restarts due to crashing + return NETWORK_TYPE_UNKNOWN; + } + } + + /** + * Returns a constant indicating the radio technology (network type) + * currently in use on the device for data transmission. + * + * If this object has been created with {@link #createForSubscriptionId}, applies to the given + * subId. Otherwise, applies to {@link SubscriptionManager#getDefaultDataSubscriptionId()} + * + * @return the network type + * + * @see #NETWORK_TYPE_UNKNOWN + * @see #NETWORK_TYPE_GPRS + * @see #NETWORK_TYPE_EDGE + * @see #NETWORK_TYPE_UMTS + * @see #NETWORK_TYPE_HSDPA + * @see #NETWORK_TYPE_HSUPA + * @see #NETWORK_TYPE_HSPA + * @see #NETWORK_TYPE_CDMA + * @see #NETWORK_TYPE_EVDO_0 + * @see #NETWORK_TYPE_EVDO_A + * @see #NETWORK_TYPE_EVDO_B + * @see #NETWORK_TYPE_1xRTT + * @see #NETWORK_TYPE_IDEN + * @see #NETWORK_TYPE_LTE + * @see #NETWORK_TYPE_EHRPD + * @see #NETWORK_TYPE_HSPAP + */ + @RequiresPermission(android.Manifest.permission.READ_PHONE_STATE) + public int getDataNetworkType() { + return getDataNetworkType(getSubId(SubscriptionManager.getDefaultDataSubscriptionId())); + } + + /** + * Returns a constant indicating the radio technology (network type) + * currently in use on the device for data transmission for a subscription + * @return the network type + * + * @param subId for which network type is returned + * @hide + */ + @RequiresPermission(android.Manifest.permission.READ_PHONE_STATE) + public int getDataNetworkType(int subId) { + try{ + ITelephony telephony = getITelephony(); + if (telephony != null) { + return telephony.getDataNetworkTypeForSubscriber(subId, getOpPackageName()); + } else { + // This can happen when the ITelephony interface is not up yet. + return NETWORK_TYPE_UNKNOWN; + } + } catch(RemoteException ex) { + // This shouldn't happen in the normal case + return NETWORK_TYPE_UNKNOWN; + } catch (NullPointerException ex) { + // This could happen before phone restarts due to crashing + return NETWORK_TYPE_UNKNOWN; + } + } + + /** + * Returns the NETWORK_TYPE_xxxx for voice + */ + @RequiresPermission(android.Manifest.permission.READ_PHONE_STATE) + public int getVoiceNetworkType() { + return getVoiceNetworkType(getSubId()); + } + + /** + * Returns the NETWORK_TYPE_xxxx for voice for a subId + * @hide + */ + @RequiresPermission(android.Manifest.permission.READ_PHONE_STATE) + public int getVoiceNetworkType(int subId) { + try{ + ITelephony telephony = getITelephony(); + if (telephony != null) { + return telephony.getVoiceNetworkTypeForSubscriber(subId, getOpPackageName()); + } else { + // This can happen when the ITelephony interface is not up yet. + return NETWORK_TYPE_UNKNOWN; + } + } catch(RemoteException ex) { + // This shouldn't happen in the normal case + return NETWORK_TYPE_UNKNOWN; + } catch (NullPointerException ex) { + // This could happen before phone restarts due to crashing + return NETWORK_TYPE_UNKNOWN; + } + } + + /** + * Network Class Definitions. + * Do not change this order, it is used for sorting during emergency calling in + * {@link TelephonyConnectionService#getFirstPhoneForEmergencyCall()}. Any newer technologies + * should be added after the current definitions. + */ + /** Unknown network class. {@hide} */ + public static final int NETWORK_CLASS_UNKNOWN = 0; + /** Class of broadly defined "2G" networks. {@hide} */ + public static final int NETWORK_CLASS_2_G = 1; + /** Class of broadly defined "3G" networks. {@hide} */ + public static final int NETWORK_CLASS_3_G = 2; + /** Class of broadly defined "4G" networks. {@hide} */ + public static final int NETWORK_CLASS_4_G = 3; + + /** + * Return general class of network type, such as "3G" or "4G". In cases + * where classification is contentious, this method is conservative. + * + * @hide + */ + public static int getNetworkClass(int networkType) { + switch (networkType) { + case NETWORK_TYPE_GPRS: + case NETWORK_TYPE_GSM: + case NETWORK_TYPE_EDGE: + case NETWORK_TYPE_CDMA: + case NETWORK_TYPE_1xRTT: + case NETWORK_TYPE_IDEN: + return NETWORK_CLASS_2_G; + case NETWORK_TYPE_UMTS: + case NETWORK_TYPE_EVDO_0: + case NETWORK_TYPE_EVDO_A: + case NETWORK_TYPE_HSDPA: + case NETWORK_TYPE_HSUPA: + case NETWORK_TYPE_HSPA: + case NETWORK_TYPE_EVDO_B: + case NETWORK_TYPE_EHRPD: + case NETWORK_TYPE_HSPAP: + case NETWORK_TYPE_TD_SCDMA: + return NETWORK_CLASS_3_G; + case NETWORK_TYPE_LTE: + case NETWORK_TYPE_IWLAN: + case NETWORK_TYPE_LTE_CA: + return NETWORK_CLASS_4_G; + default: + return NETWORK_CLASS_UNKNOWN; + } + } + + /** + * Returns a string representation of the radio technology (network type) + * currently in use on the device. + * @return the name of the radio technology + * + * @hide pending API council review + */ + public String getNetworkTypeName() { + return getNetworkTypeName(getNetworkType()); + } + + /** + * Returns a string representation of the radio technology (network type) + * currently in use on the device. + * @param subId for which network type is returned + * @return the name of the radio technology + * + */ + /** {@hide} */ + public static String getNetworkTypeName(int type) { + switch (type) { + case NETWORK_TYPE_GPRS: + return "GPRS"; + case NETWORK_TYPE_EDGE: + return "EDGE"; + case NETWORK_TYPE_UMTS: + return "UMTS"; + case NETWORK_TYPE_HSDPA: + return "HSDPA"; + case NETWORK_TYPE_HSUPA: + return "HSUPA"; + case NETWORK_TYPE_HSPA: + return "HSPA"; + case NETWORK_TYPE_CDMA: + return "CDMA"; + case NETWORK_TYPE_EVDO_0: + return "CDMA - EvDo rev. 0"; + case NETWORK_TYPE_EVDO_A: + return "CDMA - EvDo rev. A"; + case NETWORK_TYPE_EVDO_B: + return "CDMA - EvDo rev. B"; + case NETWORK_TYPE_1xRTT: + return "CDMA - 1xRTT"; + case NETWORK_TYPE_LTE: + return "LTE"; + case NETWORK_TYPE_EHRPD: + return "CDMA - eHRPD"; + case NETWORK_TYPE_IDEN: + return "iDEN"; + case NETWORK_TYPE_HSPAP: + return "HSPA+"; + case NETWORK_TYPE_GSM: + return "GSM"; + case NETWORK_TYPE_TD_SCDMA: + return "TD_SCDMA"; + case NETWORK_TYPE_IWLAN: + return "IWLAN"; + case NETWORK_TYPE_LTE_CA: + return "LTE_CA"; + default: + return "UNKNOWN"; + } + } + + // + // + // SIM Card + // + // + + /** + * SIM card state: Unknown. Signifies that the SIM is in transition + * between states. For example, when the user inputs the SIM pin + * under PIN_REQUIRED state, a query for sim status returns + * this state before turning to SIM_STATE_READY. + * + * These are the ordinal value of IccCardConstants.State. + */ + public static final int SIM_STATE_UNKNOWN = 0; + /** SIM card state: no SIM card is available in the device */ + public static final int SIM_STATE_ABSENT = 1; + /** SIM card state: Locked: requires the user's SIM PIN to unlock */ + public static final int SIM_STATE_PIN_REQUIRED = 2; + /** SIM card state: Locked: requires the user's SIM PUK to unlock */ + public static final int SIM_STATE_PUK_REQUIRED = 3; + /** SIM card state: Locked: requires a network PIN to unlock */ + public static final int SIM_STATE_NETWORK_LOCKED = 4; + /** SIM card state: Ready */ + public static final int SIM_STATE_READY = 5; + /** SIM card state: SIM Card is NOT READY */ + public static final int SIM_STATE_NOT_READY = 6; + /** SIM card state: SIM Card Error, permanently disabled */ + public static final int SIM_STATE_PERM_DISABLED = 7; + /** SIM card state: SIM Card Error, present but faulty */ + public static final int SIM_STATE_CARD_IO_ERROR = 8; + /** SIM card state: SIM Card restricted, present but not usable due to + * carrier restrictions. + */ + public static final int SIM_STATE_CARD_RESTRICTED = 9; + + /** + * @return true if a ICC card is present + */ + public boolean hasIccCard() { + return hasIccCard(getSlotIndex()); + } + + /** + * @return true if a ICC card is present for a subscription + * + * @param slotIndex for which icc card presence is checked + */ + /** {@hide} */ + // FIXME Input argument slotIndex should be of type int + public boolean hasIccCard(int slotIndex) { + + try { + ITelephony telephony = getITelephony(); + if (telephony == null) + return false; + return telephony.hasIccCardUsingSlotIndex(slotIndex); + } catch (RemoteException ex) { + // Assume no ICC card if remote exception which shouldn't happen + return false; + } catch (NullPointerException ex) { + // This could happen before phone restarts due to crashing + return false; + } + } + + /** + * Returns a constant indicating the state of the default SIM card. + * + * @see #SIM_STATE_UNKNOWN + * @see #SIM_STATE_ABSENT + * @see #SIM_STATE_PIN_REQUIRED + * @see #SIM_STATE_PUK_REQUIRED + * @see #SIM_STATE_NETWORK_LOCKED + * @see #SIM_STATE_READY + * @see #SIM_STATE_NOT_READY + * @see #SIM_STATE_PERM_DISABLED + * @see #SIM_STATE_CARD_IO_ERROR + * @see #SIM_STATE_CARD_RESTRICTED + */ + public int getSimState() { + int slotIndex = getSlotIndex(); + // slotIndex may be invalid due to sim being absent. In that case query all slots to get + // sim state + if (slotIndex < 0) { + // query for all slots and return absent if all sim states are absent, otherwise + // return unknown + for (int i = 0; i < getPhoneCount(); i++) { + int simState = getSimState(i); + if (simState != SIM_STATE_ABSENT) { + Rlog.d(TAG, "getSimState: default sim:" + slotIndex + ", sim state for " + + "slotIndex=" + i + " is " + simState + ", return state as unknown"); + return SIM_STATE_UNKNOWN; + } + } + Rlog.d(TAG, "getSimState: default sim:" + slotIndex + ", all SIMs absent, return " + + "state as absent"); + return SIM_STATE_ABSENT; + } + return getSimState(slotIndex); + } + + /** + * Returns a constant indicating the state of the device SIM card in a slot. + * + * @param slotIndex + * + * @see #SIM_STATE_UNKNOWN + * @see #SIM_STATE_ABSENT + * @see #SIM_STATE_PIN_REQUIRED + * @see #SIM_STATE_PUK_REQUIRED + * @see #SIM_STATE_NETWORK_LOCKED + * @see #SIM_STATE_READY + * @see #SIM_STATE_NOT_READY + * @see #SIM_STATE_PERM_DISABLED + * @see #SIM_STATE_CARD_IO_ERROR + * @see #SIM_STATE_CARD_RESTRICTED + */ + public int getSimState(int slotIndex) { + int simState = SubscriptionManager.getSimStateForSlotIndex(slotIndex); + return simState; + } + + /** + * Returns the MCC+MNC (mobile country code + mobile network code) of the + * provider of the SIM. 5 or 6 decimal digits. + * <p> + * Availability: SIM state must be {@link #SIM_STATE_READY} + * + * @see #getSimState + */ + public String getSimOperator() { + return getSimOperatorNumeric(); + } + + /** + * Returns the MCC+MNC (mobile country code + mobile network code) of the + * provider of the SIM. 5 or 6 decimal digits. + * <p> + * Availability: SIM state must be {@link #SIM_STATE_READY} + * + * @see #getSimState + * + * @param subId for which SimOperator is returned + * @hide + */ + public String getSimOperator(int subId) { + return getSimOperatorNumeric(subId); + } + + /** + * Returns the MCC+MNC (mobile country code + mobile network code) of the + * provider of the SIM. 5 or 6 decimal digits. + * <p> + * Availability: SIM state must be {@link #SIM_STATE_READY} + * + * @see #getSimState + * @hide + */ + public String getSimOperatorNumeric() { + int subId = SubscriptionManager.getDefaultDataSubscriptionId(); + if (!SubscriptionManager.isUsableSubIdValue(subId)) { + subId = SubscriptionManager.getDefaultSmsSubscriptionId(); + if (!SubscriptionManager.isUsableSubIdValue(subId)) { + subId = SubscriptionManager.getDefaultVoiceSubscriptionId(); + if (!SubscriptionManager.isUsableSubIdValue(subId)) { + subId = SubscriptionManager.getDefaultSubscriptionId(); + } + } + } + return getSimOperatorNumeric(subId); + } + + /** + * Returns the MCC+MNC (mobile country code + mobile network code) of the + * provider of the SIM for a particular subscription. 5 or 6 decimal digits. + * <p> + * Availability: SIM state must be {@link #SIM_STATE_READY} + * + * @see #getSimState + * + * @param subId for which SimOperator is returned + * @hide + */ + public String getSimOperatorNumeric(int subId) { + int phoneId = SubscriptionManager.getPhoneId(subId); + return getSimOperatorNumericForPhone(phoneId); + } + + /** + * Returns the MCC+MNC (mobile country code + mobile network code) of the + * provider of the SIM for a particular subscription. 5 or 6 decimal digits. + * <p> + * + * @param phoneId for which SimOperator is returned + * @hide + */ + public String getSimOperatorNumericForPhone(int phoneId) { + return getTelephonyProperty(phoneId, + TelephonyProperties.PROPERTY_ICC_OPERATOR_NUMERIC, ""); + } + + /** + * Returns the Service Provider Name (SPN). + * <p> + * Availability: SIM state must be {@link #SIM_STATE_READY} + * + * @see #getSimState + */ + public String getSimOperatorName() { + return getSimOperatorNameForPhone(getPhoneId()); + } + + /** + * Returns the Service Provider Name (SPN). + * <p> + * Availability: SIM state must be {@link #SIM_STATE_READY} + * + * @see #getSimState + * + * @param subId for which SimOperatorName is returned + * @hide + */ + public String getSimOperatorName(int subId) { + int phoneId = SubscriptionManager.getPhoneId(subId); + return getSimOperatorNameForPhone(phoneId); + } + + /** + * Returns the Service Provider Name (SPN). + * + * @hide + */ + public String getSimOperatorNameForPhone(int phoneId) { + return getTelephonyProperty(phoneId, + TelephonyProperties.PROPERTY_ICC_OPERATOR_ALPHA, ""); + } + + /** + * Returns the ISO country code equivalent for the SIM provider's country code. + */ + public String getSimCountryIso() { + return getSimCountryIsoForPhone(getPhoneId()); + } + + /** + * Returns the ISO country code equivalent for the SIM provider's country code. + * + * @param subId for which SimCountryIso is returned + * @hide + */ + public String getSimCountryIso(int subId) { + int phoneId = SubscriptionManager.getPhoneId(subId); + return getSimCountryIsoForPhone(phoneId); + } + + /** + * Returns the ISO country code equivalent for the SIM provider's country code. + * + * @hide + */ + public String getSimCountryIsoForPhone(int phoneId) { + return getTelephonyProperty(phoneId, + TelephonyProperties.PROPERTY_ICC_OPERATOR_ISO_COUNTRY, ""); + } + + /** + * Returns the serial number of the SIM, if applicable. Return null if it is + * unavailable. + */ + @RequiresPermission(android.Manifest.permission.READ_PHONE_STATE) + public String getSimSerialNumber() { + return getSimSerialNumber(getSubId()); + } + + /** + * Returns the serial number for the given subscription, if applicable. Return null if it is + * unavailable. + * <p> + * @param subId for which Sim Serial number is returned + * @hide + */ + @RequiresPermission(android.Manifest.permission.READ_PHONE_STATE) + public String getSimSerialNumber(int subId) { + try { + IPhoneSubInfo info = getSubscriberInfo(); + if (info == null) + return null; + return info.getIccSerialNumberForSubscriber(subId, mContext.getOpPackageName()); + } catch (RemoteException ex) { + return null; + } catch (NullPointerException ex) { + // This could happen before phone restarts due to crashing + return null; + } + } + + /** + * Return if the current radio is LTE on CDMA. This + * is a tri-state return value as for a period of time + * the mode may be unknown. + * + * @return {@link PhoneConstants#LTE_ON_CDMA_UNKNOWN}, {@link PhoneConstants#LTE_ON_CDMA_FALSE} + * or {@link PhoneConstants#LTE_ON_CDMA_TRUE} + * + * @hide + */ + @RequiresPermission(android.Manifest.permission.READ_PHONE_STATE) + public int getLteOnCdmaMode() { + return getLteOnCdmaMode(getSubId()); + } + + /** + * Return if the current radio is LTE on CDMA for Subscription. This + * is a tri-state return value as for a period of time + * the mode may be unknown. + * + * @param subId for which radio is LTE on CDMA is returned + * @return {@link PhoneConstants#LTE_ON_CDMA_UNKNOWN}, {@link PhoneConstants#LTE_ON_CDMA_FALSE} + * or {@link PhoneConstants#LTE_ON_CDMA_TRUE} + * @hide + */ + @RequiresPermission(android.Manifest.permission.READ_PHONE_STATE) + public int getLteOnCdmaMode(int subId) { + try { + ITelephony telephony = getITelephony(); + if (telephony == null) + return PhoneConstants.LTE_ON_CDMA_UNKNOWN; + return telephony.getLteOnCdmaModeForSubscriber(subId, getOpPackageName()); + } catch (RemoteException ex) { + // Assume no ICC card if remote exception which shouldn't happen + return PhoneConstants.LTE_ON_CDMA_UNKNOWN; + } catch (NullPointerException ex) { + // This could happen before phone restarts due to crashing + return PhoneConstants.LTE_ON_CDMA_UNKNOWN; + } + } + + // + // + // Subscriber Info + // + // + + /** + * Returns the unique subscriber ID, for example, the IMSI for a GSM phone. + * Return null if it is unavailable. + */ + @RequiresPermission(android.Manifest.permission.READ_PHONE_STATE) + public String getSubscriberId() { + return getSubscriberId(getSubId()); + } + + /** + * Returns the unique subscriber ID, for example, the IMSI for a GSM phone + * for a subscription. + * Return null if it is unavailable. + * + * @param subId whose subscriber id is returned + * @hide + */ + @RequiresPermission(android.Manifest.permission.READ_PHONE_STATE) + public String getSubscriberId(int subId) { + try { + IPhoneSubInfo info = getSubscriberInfo(); + if (info == null) + return null; + return info.getSubscriberIdForSubscriber(subId, mContext.getOpPackageName()); + } catch (RemoteException ex) { + return null; + } catch (NullPointerException ex) { + // This could happen before phone restarts due to crashing + return null; + } + } + + /** + * Returns Carrier specific information that will be used to encrypt the IMSI and IMPI. + * This includes the public key and the key identifier. For multi-sim devices, if no subId + * has been specified, we will return the value for the dafault data sim. + * Return null if it is unavailable. + * <p> + * Requires Permission: + * {@link android.Manifest.permission#READ_PHONE_STATE READ_PHONE_STATE} + * @param keyType whether the key is being used for wlan or epdg. Valid key types are + * {@link TelephonyManager#KEY_TYPE_EPDG} or + * {@link TelephonyManager#KEY_TYPE_WLAN}. + * @return ImsiEncryptionInfo Carrier specific information that will be used to encrypt the + * IMSI and IMPI. This includes the public key and the key identifier. This information + * will be stored in the device keystore. The system will return a null when no key was + * found, and the carrier does not require a key. The system will throw the following + * exceptions: + * 1. IllegalArgumentException when an invalid key is sent. + * 2. RuntimeException if the key is required but not found; and also if there was an + * internal exception. + * @hide + */ + public ImsiEncryptionInfo getCarrierInfoForImsiEncryption(int keyType) { + try { + IPhoneSubInfo info = getSubscriberInfo(); + if (info == null) { + throw new RuntimeException("IMSI error: Subscriber Info is null"); + } + int subId = getSubId(SubscriptionManager.getDefaultDataSubscriptionId()); + if (keyType != KEY_TYPE_EPDG && keyType != KEY_TYPE_WLAN) { + throw new IllegalArgumentException("IMSI error: Invalid key type"); + } + ImsiEncryptionInfo imsiEncryptionInfo = info.getCarrierInfoForImsiEncryption( + subId, keyType, mContext.getOpPackageName()); + if (imsiEncryptionInfo == null + && isImsiEncryptionRequired(subId, keyType)) { + Rlog.e(TAG, "IMSI error: key is required but not found"); + throw new RuntimeException("IMSI error: key is required but not found"); + } + return imsiEncryptionInfo; + } catch (RemoteException ex) { + Rlog.e(TAG, "getCarrierInfoForImsiEncryption RemoteException" + ex); + throw new RuntimeException("IMSI error: Remote Exception"); + } catch (NullPointerException ex) { + // This could happen before phone restarts due to crashing + Rlog.e(TAG, "getCarrierInfoForImsiEncryption NullPointerException" + ex); + throw new RuntimeException("IMSI error: Null Pointer exception"); + } + } + + /** + * @param keyAvailability bitmask that defines the availabilty of keys for a type. + * @param keyType the key type which is being checked. (WLAN, EPDG) + * @return true if the digit at position keyType is 1, else false. + * @hide + */ + private static boolean isKeyEnabled(int keyAvailability, int keyType) { + int returnValue = (keyAvailability >> (keyType - 1)) & 1; + return (returnValue == 1) ? true : false; + } + + /** + * If Carrier requires Imsi to be encrypted. + * @hide + */ + private boolean isImsiEncryptionRequired(int subId, int keyType) { + CarrierConfigManager configManager = + (CarrierConfigManager) mContext.getSystemService(Context.CARRIER_CONFIG_SERVICE); + if (configManager == null) { + return false; + } + PersistableBundle pb = configManager.getConfigForSubId(subId); + if (pb == null) { + return false; + } + int keyAvailability = pb.getInt(CarrierConfigManager.IMSI_KEY_AVAILABILITY_INT); + return isKeyEnabled(keyAvailability, keyType); + } + + /** + * Sets the Carrier specific information that will be used to encrypt the IMSI and IMPI. + * This includes the public key and the key identifier. This information will be stored in the + * device keystore. + * <p> + * Requires Permission: + * {@link android.Manifest.permission#READ_PHONE_STATE READ_PHONE_STATE} + * @param imsiEncryptionInfo which includes the Key Type, the Public Key + * (java.security.PublicKey) and the Key Identifier.and the Key Identifier. + * The keyIdentifier Attribute value pair that helps a server locate + * the private key to decrypt the permanent identity. This field is + * optional and if it is present then it’s always separated from encrypted + * permanent identity with “,”. Key identifier AVP is presented in ASCII string + * with “name=value” format. + * @hide + */ + public void setCarrierInfoForImsiEncryption(ImsiEncryptionInfo imsiEncryptionInfo) { + try { + IPhoneSubInfo info = getSubscriberInfo(); + if (info == null) return; + info.setCarrierInfoForImsiEncryption(mSubId, mContext.getOpPackageName(), + imsiEncryptionInfo); + } catch (NullPointerException ex) { + // This could happen before phone restarts due to crashing + return; + } catch (RemoteException ex) { + Rlog.e(TAG, "setCarrierInfoForImsiEncryption RemoteException", ex); + return; + } + } + + /** + * Returns the Group Identifier Level1 for a GSM phone. + * Return null if it is unavailable. + */ + @RequiresPermission(android.Manifest.permission.READ_PHONE_STATE) + public String getGroupIdLevel1() { + try { + IPhoneSubInfo info = getSubscriberInfo(); + if (info == null) + return null; + return info.getGroupIdLevel1(mContext.getOpPackageName()); + } catch (RemoteException ex) { + return null; + } catch (NullPointerException ex) { + // This could happen before phone restarts due to crashing + return null; + } + } + + /** + * Returns the Group Identifier Level1 for a GSM phone for a particular subscription. + * Return null if it is unavailable. + * + * @param subId whose subscriber id is returned + * @hide + */ + @RequiresPermission(android.Manifest.permission.READ_PHONE_STATE) + public String getGroupIdLevel1(int subId) { + try { + IPhoneSubInfo info = getSubscriberInfo(); + if (info == null) + return null; + return info.getGroupIdLevel1ForSubscriber(subId, mContext.getOpPackageName()); + } catch (RemoteException ex) { + return null; + } catch (NullPointerException ex) { + // This could happen before phone restarts due to crashing + return null; + } + } + + /** + * Returns the phone number string for line 1, for example, the MSISDN + * for a GSM phone. Return null if it is unavailable. + * <p> + * The default SMS app can also use this. + */ + @RequiresPermission(anyOf = { + android.Manifest.permission.READ_PHONE_STATE, + android.Manifest.permission.READ_SMS, + android.Manifest.permission.READ_PHONE_NUMBERS + }) + public String getLine1Number() { + return getLine1Number(getSubId()); + } + + /** + * Returns the phone number string for line 1, for example, the MSISDN + * for a GSM phone for a particular subscription. Return null if it is unavailable. + * <p> + * The default SMS app can also use this. + * + * @param subId whose phone number for line 1 is returned + * @hide + */ + @RequiresPermission(anyOf = { + android.Manifest.permission.READ_PHONE_STATE, + android.Manifest.permission.READ_SMS, + android.Manifest.permission.READ_PHONE_NUMBERS + }) + public String getLine1Number(int subId) { + String number = null; + try { + ITelephony telephony = getITelephony(); + if (telephony != null) + number = telephony.getLine1NumberForDisplay(subId, mContext.getOpPackageName()); + } catch (RemoteException ex) { + } catch (NullPointerException ex) { + } + if (number != null) { + return number; + } + try { + IPhoneSubInfo info = getSubscriberInfo(); + if (info == null) + return null; + return info.getLine1NumberForSubscriber(subId, mContext.getOpPackageName()); + } catch (RemoteException ex) { + return null; + } catch (NullPointerException ex) { + // This could happen before phone restarts due to crashing + return null; + } + } + + /** + * Set the line 1 phone number string and its alphatag for the current ICCID + * for display purpose only, for example, displayed in Phone Status. It won't + * change the actual MSISDN/MDN. To unset alphatag or number, pass in a null + * value. + * + * <p>Requires that the calling app has carrier privileges. + * @see #hasCarrierPrivileges + * + * @param alphaTag alpha-tagging of the dailing nubmer + * @param number The dialing number + * @return true if the operation was executed correctly. + */ + public boolean setLine1NumberForDisplay(String alphaTag, String number) { + return setLine1NumberForDisplay(getSubId(), alphaTag, number); + } + + /** + * Set the line 1 phone number string and its alphatag for the current ICCID + * for display purpose only, for example, displayed in Phone Status. It won't + * change the actual MSISDN/MDN. To unset alphatag or number, pass in a null + * value. + * + * <p>Requires that the calling app has carrier privileges. + * @see #hasCarrierPrivileges + * + * @param subId the subscriber that the alphatag and dialing number belongs to. + * @param alphaTag alpha-tagging of the dailing nubmer + * @param number The dialing number + * @return true if the operation was executed correctly. + * @hide + */ + public boolean setLine1NumberForDisplay(int subId, String alphaTag, String number) { + try { + ITelephony telephony = getITelephony(); + if (telephony != null) + return telephony.setLine1NumberForDisplayForSubscriber(subId, alphaTag, number); + } catch (RemoteException ex) { + } catch (NullPointerException ex) { + } + return false; + } + + /** + * Returns the alphabetic identifier associated with the line 1 number. + * Return null if it is unavailable. + * @hide + * nobody seems to call this. + */ + @RequiresPermission(android.Manifest.permission.READ_PHONE_STATE) + public String getLine1AlphaTag() { + return getLine1AlphaTag(getSubId()); + } + + /** + * Returns the alphabetic identifier associated with the line 1 number + * for a subscription. + * Return null if it is unavailable. + * @param subId whose alphabetic identifier associated with line 1 is returned + * nobody seems to call this. + * @hide + */ + @RequiresPermission(android.Manifest.permission.READ_PHONE_STATE) + public String getLine1AlphaTag(int subId) { + String alphaTag = null; + try { + ITelephony telephony = getITelephony(); + if (telephony != null) + alphaTag = telephony.getLine1AlphaTagForDisplay(subId, + getOpPackageName()); + } catch (RemoteException ex) { + } catch (NullPointerException ex) { + } + if (alphaTag != null) { + return alphaTag; + } + try { + IPhoneSubInfo info = getSubscriberInfo(); + if (info == null) + return null; + return info.getLine1AlphaTagForSubscriber(subId, getOpPackageName()); + } catch (RemoteException ex) { + return null; + } catch (NullPointerException ex) { + // This could happen before phone restarts due to crashing + return null; + } + } + + /** + * Return the set of subscriber IDs that should be considered as "merged + * together" for data usage purposes. This is commonly {@code null} to + * indicate no merging is required. Any returned subscribers are sorted in a + * deterministic order. + * + * @hide + */ + public @Nullable String[] getMergedSubscriberIds() { + try { + ITelephony telephony = getITelephony(); + if (telephony != null) + return telephony.getMergedSubscriberIds(getOpPackageName()); + } catch (RemoteException ex) { + } catch (NullPointerException ex) { + } + return null; + } + + /** + * Returns the MSISDN string. + * for a GSM phone. Return null if it is unavailable. + * + * @hide + */ + @RequiresPermission(android.Manifest.permission.READ_PHONE_STATE) + public String getMsisdn() { + return getMsisdn(getSubId()); + } + + /** + * Returns the MSISDN string. + * for a GSM phone. Return null if it is unavailable. + * + * @param subId for which msisdn is returned + * @hide + */ + @RequiresPermission(android.Manifest.permission.READ_PHONE_STATE) + public String getMsisdn(int subId) { + try { + IPhoneSubInfo info = getSubscriberInfo(); + if (info == null) + return null; + return info.getMsisdnForSubscriber(subId, getOpPackageName()); + } catch (RemoteException ex) { + return null; + } catch (NullPointerException ex) { + // This could happen before phone restarts due to crashing + return null; + } + } + + /** + * Returns the voice mail number. Return null if it is unavailable. + */ + @RequiresPermission(android.Manifest.permission.READ_PHONE_STATE) + public String getVoiceMailNumber() { + return getVoiceMailNumber(getSubId()); + } + + /** + * Returns the voice mail number for a subscription. + * Return null if it is unavailable. + * @param subId whose voice mail number is returned + * @hide + */ + @RequiresPermission(android.Manifest.permission.READ_PHONE_STATE) + public String getVoiceMailNumber(int subId) { + try { + IPhoneSubInfo info = getSubscriberInfo(); + if (info == null) + return null; + return info.getVoiceMailNumberForSubscriber(subId, getOpPackageName()); + } catch (RemoteException ex) { + return null; + } catch (NullPointerException ex) { + // This could happen before phone restarts due to crashing + return null; + } + } + + /** + * Returns the complete voice mail number. Return null if it is unavailable. + * + * @hide + */ + @RequiresPermission(android.Manifest.permission.CALL_PRIVILEGED) + public String getCompleteVoiceMailNumber() { + return getCompleteVoiceMailNumber(getSubId()); + } + + /** + * Returns the complete voice mail number. Return null if it is unavailable. + * + * @param subId + * @hide + */ + @RequiresPermission(android.Manifest.permission.CALL_PRIVILEGED) + public String getCompleteVoiceMailNumber(int subId) { + try { + IPhoneSubInfo info = getSubscriberInfo(); + if (info == null) + return null; + return info.getCompleteVoiceMailNumberForSubscriber(subId); + } catch (RemoteException ex) { + return null; + } catch (NullPointerException ex) { + // This could happen before phone restarts due to crashing + return null; + } + } + + /** + * Sets the voice mail number. + * + * <p>Requires that the calling app has carrier privileges. + * @see #hasCarrierPrivileges + * + * @param alphaTag The alpha tag to display. + * @param number The voicemail number. + */ + public boolean setVoiceMailNumber(String alphaTag, String number) { + return setVoiceMailNumber(getSubId(), alphaTag, number); + } + + /** + * Sets the voicemail number for the given subscriber. + * + * <p>Requires that the calling app has carrier privileges. + * @see #hasCarrierPrivileges + * + * @param subId The subscription id. + * @param alphaTag The alpha tag to display. + * @param number The voicemail number. + * @hide + */ + public boolean setVoiceMailNumber(int subId, String alphaTag, String number) { + try { + ITelephony telephony = getITelephony(); + if (telephony != null) + return telephony.setVoiceMailNumber(subId, alphaTag, number); + } catch (RemoteException ex) { + } catch (NullPointerException ex) { + } + return false; + } + + /** + * Enables or disables the visual voicemail client for a phone account. + * + * <p>Requires that the calling app is the default dialer, or has carrier privileges, or + * has permission {@link android.Manifest.permission#MODIFY_PHONE_STATE MODIFY_PHONE_STATE}. + * @see #hasCarrierPrivileges + * + * @param phoneAccountHandle the phone account to change the client state + * @param enabled the new state of the client + * @hide + * @deprecated Visual voicemail no longer in telephony. {@link VisualVoicemailService} should + * be implemented instead. + */ + @SystemApi + @SuppressLint("Doclava125") + public void setVisualVoicemailEnabled(PhoneAccountHandle phoneAccountHandle, boolean enabled){ + } + + /** + * Returns whether the visual voicemail client is enabled. + * + * @param phoneAccountHandle the phone account to check for. + * @return {@code true} when the visual voicemail client is enabled for this client + * @hide + * @deprecated Visual voicemail no longer in telephony. {@link VisualVoicemailService} should + * be implemented instead. + */ + @SystemApi + @RequiresPermission(android.Manifest.permission.READ_PHONE_STATE) + @SuppressLint("Doclava125") + public boolean isVisualVoicemailEnabled(PhoneAccountHandle phoneAccountHandle){ + return false; + } + + /** + * Returns an opaque bundle of settings formerly used by the visual voicemail client for the + * subscription ID pinned to the TelephonyManager, or {@code null} if the subscription ID is + * invalid. This method allows the system dialer to migrate settings out of the pre-O visual + * voicemail client in telephony. + * + * <p>Requires the caller to be the system dialer. + * + * @see #KEY_VISUAL_VOICEMAIL_ENABLED_BY_USER_BOOL + * @see #KEY_VOICEMAIL_SCRAMBLED_PIN_STRING + * + * @hide + */ + @SystemApi + @SuppressLint("Doclava125") + @Nullable + public Bundle getVisualVoicemailSettings(){ + try { + ITelephony telephony = getITelephony(); + if (telephony != null) { + return telephony + .getVisualVoicemailSettings(mContext.getOpPackageName(), mSubId); + } + } catch (RemoteException ex) { + } catch (NullPointerException ex) { + } + return null; + } + + /** + * Returns the package responsible of processing visual voicemail for the subscription ID pinned + * to the TelephonyManager. Returns {@code null} when there is no package responsible for + * processing visual voicemail for the subscription. + * + * @see #createForSubscriptionId(int) + * @see #createForPhoneAccountHandle(PhoneAccountHandle) + * @see VisualVoicemailService + */ + @Nullable + @RequiresPermission(android.Manifest.permission.READ_PHONE_STATE) + public String getVisualVoicemailPackageName() { + try { + ITelephony telephony = getITelephony(); + if (telephony != null) { + return telephony + .getVisualVoicemailPackageName(mContext.getOpPackageName(), getSubId()); + } + } catch (RemoteException ex) { + } catch (NullPointerException ex) { + } + return null; + } + + /** + * Set the visual voicemail SMS filter settings for the subscription ID pinned + * to the TelephonyManager. + * When the filter is enabled, {@link + * VisualVoicemailService#onSmsReceived(VisualVoicemailTask, VisualVoicemailSms)} will be + * called when a SMS matching the settings is received. The caller should have + * {@link android.Manifest.permission#READ_PHONE_STATE READ_PHONE_STATE} and implement a + * VisualVoicemailService. + * + * <p>Requires Permission: + * {@link android.Manifest.permission#READ_PHONE_STATE READ_PHONE_STATE} + * + * @param settings The settings for the filter, or {@code null} to disable the filter. + */ + public void setVisualVoicemailSmsFilterSettings(VisualVoicemailSmsFilterSettings settings) { + if (settings == null) { + disableVisualVoicemailSmsFilter(mSubId); + } else { + enableVisualVoicemailSmsFilter(mSubId, settings); + } + } + + /** + * Send a visual voicemail SMS. The caller must be the current default dialer. + * A {@link VisualVoicemailService} uses this method to send a command via SMS to the carrier's + * visual voicemail server. Some examples for carriers using the OMTP standard include + * activating and deactivating visual voicemail, or requesting the current visual voicemail + * provisioning status. See the OMTP Visual Voicemail specification for more information on the + * format of these SMS messages. + * + * <p>Requires Permission: + * {@link android.Manifest.permission#SEND_SMS SEND_SMS} + * + * @param number The destination number. + * @param port The destination port for data SMS, or 0 for text SMS. + * @param text The message content. For data sms, it will be encoded as a UTF-8 byte stream. + * @param sentIntent The sent intent passed to the {@link SmsManager} + * + * @throws SecurityException if the caller is not the current default dialer + * + * @see SmsManager#sendDataMessage(String, String, short, byte[], PendingIntent, PendingIntent) + * @see SmsManager#sendTextMessage(String, String, String, PendingIntent, PendingIntent) + */ + public void sendVisualVoicemailSms(String number, int port, String text, + PendingIntent sentIntent) { + sendVisualVoicemailSmsForSubscriber(mSubId, number, port, text, sentIntent); + } + + /** + * Enables the visual voicemail SMS filter for a phone account. When the filter is + * enabled, Incoming SMS messages matching the OMTP VVM SMS interface will be redirected to the + * visual voicemail client with + * {@link android.provider.VoicemailContract.ACTION_VOICEMAIL_SMS_RECEIVED}. + * + * <p>This takes effect only when the caller is the default dialer. The enabled status and + * settings persist through default dialer changes, but the filter will only honor the setting + * set by the current default dialer. + * + * + * @param subId The subscription id of the phone account. + * @param settings The settings for the filter. + */ + /** @hide */ + public void enableVisualVoicemailSmsFilter(int subId, + VisualVoicemailSmsFilterSettings settings) { + if(settings == null){ + throw new IllegalArgumentException("Settings cannot be null"); + } + try { + ITelephony telephony = getITelephony(); + if (telephony != null) { + telephony.enableVisualVoicemailSmsFilter(mContext.getOpPackageName(), subId, + settings); + } + } catch (RemoteException ex) { + } catch (NullPointerException ex) { + } + } + + /** + * Disables the visual voicemail SMS filter for a phone account. + * + * <p>This takes effect only when the caller is the default dialer. The enabled status and + * settings persist through default dialer changes, but the filter will only honor the setting + * set by the current default dialer. + */ + /** @hide */ + public void disableVisualVoicemailSmsFilter(int subId) { + try { + ITelephony telephony = getITelephony(); + if (telephony != null) { + telephony.disableVisualVoicemailSmsFilter(mContext.getOpPackageName(), subId); + } + } catch (RemoteException ex) { + } catch (NullPointerException ex) { + } + } + + /** + * @returns the settings of the visual voicemail SMS filter for a phone account, or {@code null} + * if the filter is disabled. + * + * <p>This takes effect only when the caller is the default dialer. The enabled status and + * settings persist through default dialer changes, but the filter will only honor the setting + * set by the current default dialer. + */ + /** @hide */ + @Nullable + public VisualVoicemailSmsFilterSettings getVisualVoicemailSmsFilterSettings(int subId) { + try { + ITelephony telephony = getITelephony(); + if (telephony != null) { + return telephony + .getVisualVoicemailSmsFilterSettings(mContext.getOpPackageName(), subId); + } + } catch (RemoteException ex) { + } catch (NullPointerException ex) { + } + + return null; + } + + /** + * @returns the settings of the visual voicemail SMS filter for a phone account set by the + * current active visual voicemail client, or {@code null} if the filter is disabled. + * + * <p>Requires the calling app to have READ_PRIVILEGED_PHONE_STATE permission. + */ + /** @hide */ + @Nullable + public VisualVoicemailSmsFilterSettings getActiveVisualVoicemailSmsFilterSettings(int subId) { + try { + ITelephony telephony = getITelephony(); + if (telephony != null) { + return telephony.getActiveVisualVoicemailSmsFilterSettings(subId); + } + } catch (RemoteException ex) { + } catch (NullPointerException ex) { + } + + return null; + } + + /** + * Send a visual voicemail SMS. The IPC caller must be the current default dialer. + * + * @param phoneAccountHandle The account to send the SMS with. + * @param number The destination number. + * @param port The destination port for data SMS, or 0 for text SMS. + * @param text The message content. For data sms, it will be encoded as a UTF-8 byte stream. + * @param sentIntent The sent intent passed to the {@link SmsManager} + * + * @see SmsManager#sendDataMessage(String, String, short, byte[], PendingIntent, PendingIntent) + * @see SmsManager#sendTextMessage(String, String, String, PendingIntent, PendingIntent) + * + * @hide + */ + @RequiresPermission(android.Manifest.permission.SEND_SMS) + public void sendVisualVoicemailSmsForSubscriber(int subId, String number, int port, + String text, PendingIntent sentIntent) { + try { + ITelephony telephony = getITelephony(); + if (telephony != null) { + telephony.sendVisualVoicemailSmsForSubscriber( + mContext.getOpPackageName(), subId, number, port, text, sentIntent); + } + } catch (RemoteException ex) { + } + } + + /** + * Initial SIM activation state, unknown. Not set by any carrier apps. + * @hide + */ + public static final int SIM_ACTIVATION_STATE_UNKNOWN = 0; + + /** + * indicate SIM is under activation procedure now. + * intermediate state followed by another state update with activation procedure result: + * @see #SIM_ACTIVATION_STATE_ACTIVATED + * @see #SIM_ACTIVATION_STATE_DEACTIVATED + * @see #SIM_ACTIVATION_STATE_RESTRICTED + * @hide + */ + public static final int SIM_ACTIVATION_STATE_ACTIVATING = 1; + + /** + * Indicate SIM has been successfully activated with full service + * @hide + */ + public static final int SIM_ACTIVATION_STATE_ACTIVATED = 2; + + /** + * Indicate SIM has been deactivated by the carrier so that service is not available + * and requires activation service to enable services. + * Carrier apps could be signalled to set activation state to deactivated if detected + * deactivated sim state and set it back to activated after successfully run activation service. + * @hide + */ + public static final int SIM_ACTIVATION_STATE_DEACTIVATED = 3; + + /** + * Restricted state indicate SIM has been activated but service are restricted. + * note this is currently available for data activation state. For example out of byte sim. + * @hide + */ + public static final int SIM_ACTIVATION_STATE_RESTRICTED = 4; + + /** + * Sets the voice activation state for the given subscriber. + * + * <p>Requires Permission: + * {@link android.Manifest.permission#MODIFY_PHONE_STATE MODIFY_PHONE_STATE} + * Or the calling app has carrier privileges. @see #hasCarrierPrivileges + * + * @param subId The subscription id. + * @param activationState The voice activation state of the given subscriber. + * @see #SIM_ACTIVATION_STATE_UNKNOWN + * @see #SIM_ACTIVATION_STATE_ACTIVATING + * @see #SIM_ACTIVATION_STATE_ACTIVATED + * @see #SIM_ACTIVATION_STATE_DEACTIVATED + * @hide + */ + public void setVoiceActivationState(int subId, int activationState) { + try { + ITelephony telephony = getITelephony(); + if (telephony != null) + telephony.setVoiceActivationState(subId, activationState); + } catch (RemoteException ex) { + } catch (NullPointerException ex) { + } + } + + /** + * Sets the data activation state for the given subscriber. + * + * <p>Requires Permission: + * {@link android.Manifest.permission#MODIFY_PHONE_STATE} + * Or the calling app has carrier privileges. @see #hasCarrierPrivileges + * + * @param subId The subscription id. + * @param activationState The data activation state of the given subscriber. + * @see #SIM_ACTIVATION_STATE_UNKNOWN + * @see #SIM_ACTIVATION_STATE_ACTIVATING + * @see #SIM_ACTIVATION_STATE_ACTIVATED + * @see #SIM_ACTIVATION_STATE_DEACTIVATED + * @see #SIM_ACTIVATION_STATE_RESTRICTED + * @hide + */ + public void setDataActivationState(int subId, int activationState) { + try { + ITelephony telephony = getITelephony(); + if (telephony != null) + telephony.setDataActivationState(subId, activationState); + } catch (RemoteException ex) { + } catch (NullPointerException ex) { + } + } + + /** + * Returns the voice activation state for the given subscriber. + * + * @param subId The subscription id. + * + * @return voiceActivationState for the given subscriber + * @see #SIM_ACTIVATION_STATE_UNKNOWN + * @see #SIM_ACTIVATION_STATE_ACTIVATING + * @see #SIM_ACTIVATION_STATE_ACTIVATED + * @see #SIM_ACTIVATION_STATE_DEACTIVATED + * @hide + */ + @RequiresPermission(android.Manifest.permission.READ_PHONE_STATE) + public int getVoiceActivationState(int subId) { + try { + ITelephony telephony = getITelephony(); + if (telephony != null) + return telephony.getVoiceActivationState(subId, getOpPackageName()); + } catch (RemoteException ex) { + } catch (NullPointerException ex) { + } + return SIM_ACTIVATION_STATE_UNKNOWN; + } + + /** + * Returns the data activation state for the given subscriber. + * + * @param subId The subscription id. + * + * @return dataActivationState for the given subscriber + * @see #SIM_ACTIVATION_STATE_UNKNOWN + * @see #SIM_ACTIVATION_STATE_ACTIVATING + * @see #SIM_ACTIVATION_STATE_ACTIVATED + * @see #SIM_ACTIVATION_STATE_DEACTIVATED + * @see #SIM_ACTIVATION_STATE_RESTRICTED + * @hide + */ + @RequiresPermission(android.Manifest.permission.READ_PHONE_STATE) + public int getDataActivationState(int subId) { + try { + ITelephony telephony = getITelephony(); + if (telephony != null) + return telephony.getDataActivationState(subId, getOpPackageName()); + } catch (RemoteException ex) { + } catch (NullPointerException ex) { + } + return SIM_ACTIVATION_STATE_UNKNOWN; + } + + /** + * Returns the voice mail count. Return 0 if unavailable, -1 if there are unread voice messages + * but the count is unknown. + * @hide + */ + @RequiresPermission(android.Manifest.permission.READ_PHONE_STATE) + public int getVoiceMessageCount() { + return getVoiceMessageCount(getSubId()); + } + + /** + * Returns the voice mail count for a subscription. Return 0 if unavailable. + * @param subId whose voice message count is returned + * @hide + */ + @RequiresPermission(android.Manifest.permission.READ_PHONE_STATE) + public int getVoiceMessageCount(int subId) { + try { + ITelephony telephony = getITelephony(); + if (telephony == null) + return 0; + return telephony.getVoiceMessageCountForSubscriber(subId); + } catch (RemoteException ex) { + return 0; + } catch (NullPointerException ex) { + // This could happen before phone restarts due to crashing + return 0; + } + } + + /** + * Retrieves the alphabetic identifier associated with the voice + * mail number. + */ + @RequiresPermission(android.Manifest.permission.READ_PHONE_STATE) + public String getVoiceMailAlphaTag() { + return getVoiceMailAlphaTag(getSubId()); + } + + /** + * Retrieves the alphabetic identifier associated with the voice + * mail number for a subscription. + * @param subId whose alphabetic identifier associated with the + * voice mail number is returned + * @hide + */ + @RequiresPermission(android.Manifest.permission.READ_PHONE_STATE) + public String getVoiceMailAlphaTag(int subId) { + try { + IPhoneSubInfo info = getSubscriberInfo(); + if (info == null) + return null; + return info.getVoiceMailAlphaTagForSubscriber(subId, getOpPackageName()); + } catch (RemoteException ex) { + return null; + } catch (NullPointerException ex) { + // This could happen before phone restarts due to crashing + return null; + } + } + + /** + * Send the special dialer code. The IPC caller must be the current default dialer or has + * carrier privileges. + * @see #hasCarrierPrivileges + * + * @param inputCode The special dialer code to send + * + * @throws SecurityException if the caller does not have carrier privileges or is not the + * current default dialer + * + * @throws IllegalStateException if telephony service is unavailable. + */ + public void sendDialerSpecialCode(String inputCode) { + try { + final ITelephony telephony = getITelephony(); + telephony.sendDialerSpecialCode(mContext.getOpPackageName(), inputCode); + } catch (RemoteException ex) { + // This could happen if binder process crashes. + ex.rethrowFromSystemServer(); + } catch (NullPointerException ex) { + // This could happen before phone restarts due to crashing + throw new IllegalStateException("Telephony service unavailable"); + } + } + + /** + * Returns the IMS private user identity (IMPI) that was loaded from the ISIM. + * @return the IMPI, or null if not present or not loaded + * @hide + */ + public String getIsimImpi() { + try { + IPhoneSubInfo info = getSubscriberInfo(); + if (info == null) + return null; + //get the Isim Impi based on subId + return info.getIsimImpi(getSubId()); + } catch (RemoteException ex) { + return null; + } catch (NullPointerException ex) { + // This could happen before phone restarts due to crashing + return null; + } + } + + /** + * Returns the IMS home network domain name that was loaded from the ISIM. + * @return the IMS domain name, or null if not present or not loaded + * @hide + */ + public String getIsimDomain() { + try { + IPhoneSubInfo info = getSubscriberInfo(); + if (info == null) + return null; + //get the Isim Domain based on subId + return info.getIsimDomain(getSubId()); + } catch (RemoteException ex) { + return null; + } catch (NullPointerException ex) { + // This could happen before phone restarts due to crashing + return null; + } + } + + /** + * Returns the IMS public user identities (IMPU) that were loaded from the ISIM. + * @return an array of IMPU strings, with one IMPU per string, or null if + * not present or not loaded + * @hide + */ + public String[] getIsimImpu() { + try { + IPhoneSubInfo info = getSubscriberInfo(); + if (info == null) + return null; + //get the Isim Impu based on subId + return info.getIsimImpu(getSubId()); + } catch (RemoteException ex) { + return null; + } catch (NullPointerException ex) { + // This could happen before phone restarts due to crashing + return null; + } + } + + /** + * @hide + */ + private IPhoneSubInfo getSubscriberInfo() { + // get it each time because that process crashes a lot + return IPhoneSubInfo.Stub.asInterface(ServiceManager.getService("iphonesubinfo")); + } + + /** Device call state: No activity. */ + public static final int CALL_STATE_IDLE = 0; + /** Device call state: Ringing. A new call arrived and is + * ringing or waiting. In the latter case, another call is + * already active. */ + public static final int CALL_STATE_RINGING = 1; + /** Device call state: Off-hook. At least one call exists + * that is dialing, active, or on hold, and no calls are ringing + * or waiting. */ + public static final int CALL_STATE_OFFHOOK = 2; + + /** + * Returns one of the following constants that represents the current state of all + * phone calls. + * + * {@link TelephonyManager#CALL_STATE_RINGING} + * {@link TelephonyManager#CALL_STATE_OFFHOOK} + * {@link TelephonyManager#CALL_STATE_IDLE} + */ + public int getCallState() { + try { + ITelecomService telecom = getTelecomService(); + if (telecom != null) { + return telecom.getCallState(); + } + } catch (RemoteException e) { + Log.e(TAG, "Error calling ITelecomService#getCallState", e); + } + return CALL_STATE_IDLE; + } + + /** + * Returns a constant indicating the call state (cellular) on the device + * for a subscription. + * + * @param subId whose call state is returned + * @hide + */ + public int getCallState(int subId) { + int phoneId = SubscriptionManager.getPhoneId(subId); + return getCallStateForSlot(phoneId); + } + + /** + * See getCallState. + * + * @hide + */ + public int getCallStateForSlot(int slotIndex) { + try { + ITelephony telephony = getITelephony(); + if (telephony == null) + return CALL_STATE_IDLE; + return telephony.getCallStateForSlot(slotIndex); + } catch (RemoteException ex) { + // the phone process is restarting. + return CALL_STATE_IDLE; + } catch (NullPointerException ex) { + // the phone process is restarting. + return CALL_STATE_IDLE; + } + } + + + /** Data connection activity: No traffic. */ + public static final int DATA_ACTIVITY_NONE = 0x00000000; + /** Data connection activity: Currently receiving IP PPP traffic. */ + public static final int DATA_ACTIVITY_IN = 0x00000001; + /** Data connection activity: Currently sending IP PPP traffic. */ + public static final int DATA_ACTIVITY_OUT = 0x00000002; + /** Data connection activity: Currently both sending and receiving + * IP PPP traffic. */ + public static final int DATA_ACTIVITY_INOUT = DATA_ACTIVITY_IN | DATA_ACTIVITY_OUT; + /** + * Data connection is active, but physical link is down + */ + public static final int DATA_ACTIVITY_DORMANT = 0x00000004; + + /** + * Returns a constant indicating the type of activity on a data connection + * (cellular). + * + * @see #DATA_ACTIVITY_NONE + * @see #DATA_ACTIVITY_IN + * @see #DATA_ACTIVITY_OUT + * @see #DATA_ACTIVITY_INOUT + * @see #DATA_ACTIVITY_DORMANT + */ + public int getDataActivity() { + try { + ITelephony telephony = getITelephony(); + if (telephony == null) + return DATA_ACTIVITY_NONE; + return telephony.getDataActivity(); + } catch (RemoteException ex) { + // the phone process is restarting. + return DATA_ACTIVITY_NONE; + } catch (NullPointerException ex) { + // the phone process is restarting. + return DATA_ACTIVITY_NONE; + } + } + + /** Data connection state: Unknown. Used before we know the state. + * @hide + */ + public static final int DATA_UNKNOWN = -1; + /** Data connection state: Disconnected. IP traffic not available. */ + public static final int DATA_DISCONNECTED = 0; + /** Data connection state: Currently setting up a data connection. */ + public static final int DATA_CONNECTING = 1; + /** Data connection state: Connected. IP traffic should be available. */ + public static final int DATA_CONNECTED = 2; + /** Data connection state: Suspended. The connection is up, but IP + * traffic is temporarily unavailable. For example, in a 2G network, + * data activity may be suspended when a voice call arrives. */ + public static final int DATA_SUSPENDED = 3; + + /** + * Returns a constant indicating the current data connection state + * (cellular). + * + * @see #DATA_DISCONNECTED + * @see #DATA_CONNECTING + * @see #DATA_CONNECTED + * @see #DATA_SUSPENDED + */ + public int getDataState() { + try { + ITelephony telephony = getITelephony(); + if (telephony == null) + return DATA_DISCONNECTED; + return telephony.getDataState(); + } catch (RemoteException ex) { + // the phone process is restarting. + return DATA_DISCONNECTED; + } catch (NullPointerException ex) { + return DATA_DISCONNECTED; + } + } + + /** + * @hide + */ + private ITelephony getITelephony() { + return ITelephony.Stub.asInterface(ServiceManager.getService(Context.TELEPHONY_SERVICE)); + } + + /** + * @hide + */ + private ITelecomService getTelecomService() { + return ITelecomService.Stub.asInterface(ServiceManager.getService(Context.TELECOM_SERVICE)); + } + + // + // + // PhoneStateListener + // + // + + /** + * Registers a listener object to receive notification of changes + * in specified telephony states. + * <p> + * To register a listener, pass a {@link PhoneStateListener} + * and specify at least one telephony state of interest in + * the events argument. + * + * At registration, and when a specified telephony state + * changes, the telephony manager invokes the appropriate + * callback method on the listener object and passes the + * current (updated) values. + * <p> + * To unregister a listener, pass the listener object and set the + * events argument to + * {@link PhoneStateListener#LISTEN_NONE LISTEN_NONE} (0). + * + * @param listener The {@link PhoneStateListener} object to register + * (or unregister) + * @param events The telephony state(s) of interest to the listener, + * as a bitwise-OR combination of {@link PhoneStateListener} + * LISTEN_ flags. + */ + public void listen(PhoneStateListener listener, int events) { + if (mContext == null) return; + try { + boolean notifyNow = (getITelephony() != null); + // If the listener has not explicitly set the subId (for example, created with the + // default constructor), replace the subId so it will listen to the account the + // telephony manager is created with. + if (listener.mSubId == null) { + listener.mSubId = mSubId; + } + sRegistry.listenForSubscriber(listener.mSubId, getOpPackageName(), + listener.callback, events, notifyNow); + } catch (RemoteException ex) { + // system process dead + } catch (NullPointerException ex) { + // system process dead + } + } + + /** + * Returns the CDMA ERI icon index to display + * @hide + */ + @RequiresPermission(android.Manifest.permission.READ_PHONE_STATE) + public int getCdmaEriIconIndex() { + return getCdmaEriIconIndex(getSubId()); + } + + /** + * Returns the CDMA ERI icon index to display for a subscription + * @hide + */ + @RequiresPermission(android.Manifest.permission.READ_PHONE_STATE) + public int getCdmaEriIconIndex(int subId) { + try { + ITelephony telephony = getITelephony(); + if (telephony == null) + return -1; + return telephony.getCdmaEriIconIndexForSubscriber(subId, getOpPackageName()); + } catch (RemoteException ex) { + // the phone process is restarting. + return -1; + } catch (NullPointerException ex) { + return -1; + } + } + + /** + * Returns the CDMA ERI icon mode, + * 0 - ON + * 1 - FLASHING + * + * @hide + */ + @RequiresPermission(android.Manifest.permission.READ_PHONE_STATE) + public int getCdmaEriIconMode() { + return getCdmaEriIconMode(getSubId()); + } + + /** + * Returns the CDMA ERI icon mode for a subscription. + * 0 - ON + * 1 - FLASHING + * + * @hide + */ + @RequiresPermission(android.Manifest.permission.READ_PHONE_STATE) + public int getCdmaEriIconMode(int subId) { + try { + ITelephony telephony = getITelephony(); + if (telephony == null) + return -1; + return telephony.getCdmaEriIconModeForSubscriber(subId, getOpPackageName()); + } catch (RemoteException ex) { + // the phone process is restarting. + return -1; + } catch (NullPointerException ex) { + return -1; + } + } + + /** + * Returns the CDMA ERI text, + * + * @hide + */ + @RequiresPermission(android.Manifest.permission.READ_PHONE_STATE) + public String getCdmaEriText() { + return getCdmaEriText(getSubId()); + } + + /** + * Returns the CDMA ERI text, of a subscription + * + * @hide + */ + @RequiresPermission(android.Manifest.permission.READ_PHONE_STATE) + public String getCdmaEriText(int subId) { + try { + ITelephony telephony = getITelephony(); + if (telephony == null) + return null; + return telephony.getCdmaEriTextForSubscriber(subId, getOpPackageName()); + } catch (RemoteException ex) { + // the phone process is restarting. + return null; + } catch (NullPointerException ex) { + return null; + } + } + + /** + * @return true if the current device is "voice capable". + * <p> + * "Voice capable" means that this device supports circuit-switched + * (i.e. voice) phone calls over the telephony network, and is allowed + * to display the in-call UI while a cellular voice call is active. + * This will be false on "data only" devices which can't make voice + * calls and don't support any in-call UI. + * <p> + * Note: the meaning of this flag is subtly different from the + * PackageManager.FEATURE_TELEPHONY system feature, which is available + * on any device with a telephony radio, even if the device is + * data-only. + */ + public boolean isVoiceCapable() { + if (mContext == null) return true; + return mContext.getResources().getBoolean( + com.android.internal.R.bool.config_voice_capable); + } + + /** + * @return true if the current device supports sms service. + * <p> + * If true, this means that the device supports both sending and + * receiving sms via the telephony network. + * <p> + * Note: Voicemail waiting sms, cell broadcasting sms, and MMS are + * disabled when device doesn't support sms. + */ + public boolean isSmsCapable() { + if (mContext == null) return true; + return mContext.getResources().getBoolean( + com.android.internal.R.bool.config_sms_capable); + } + + /** + * Returns all observed cell information from all radios on the + * device including the primary and neighboring cells. Calling this method does + * not trigger a call to {@link android.telephony.PhoneStateListener#onCellInfoChanged + * onCellInfoChanged()}, or change the rate at which + * {@link android.telephony.PhoneStateListener#onCellInfoChanged + * onCellInfoChanged()} is called. + * + *<p> + * The list can include one or more {@link android.telephony.CellInfoGsm CellInfoGsm}, + * {@link android.telephony.CellInfoCdma CellInfoCdma}, + * {@link android.telephony.CellInfoLte CellInfoLte}, and + * {@link android.telephony.CellInfoWcdma CellInfoWcdma} objects, in any combination. + * On devices with multiple radios it is typical to see instances of + * one or more of any these in the list. In addition, zero, one, or more + * of the returned objects may be considered registered; that is, their + * {@link android.telephony.CellInfo#isRegistered CellInfo.isRegistered()} + * methods may return true. + * + * <p>This method returns valid data for registered cells on devices with + * {@link android.content.pm.PackageManager#FEATURE_TELEPHONY}. In cases where only + * partial information is available for a particular CellInfo entry, unavailable fields + * will be reported as Integer.MAX_VALUE. All reported cells will include at least a + * valid set of technology-specific identification info and a power level measurement. + * + *<p> + * This method is preferred over using {@link + * android.telephony.TelephonyManager#getCellLocation getCellLocation()}. + * However, for older devices, <code>getAllCellInfo()</code> may return + * null. In these cases, you should call {@link + * android.telephony.TelephonyManager#getCellLocation getCellLocation()} + * instead. + * + * @return List of {@link android.telephony.CellInfo}; null if cell + * information is unavailable. + */ + @RequiresPermission(android.Manifest.permission.ACCESS_COARSE_LOCATION) + public List<CellInfo> getAllCellInfo() { + try { + ITelephony telephony = getITelephony(); + if (telephony == null) + return null; + return telephony.getAllCellInfo(getOpPackageName()); + } catch (RemoteException ex) { + return null; + } catch (NullPointerException ex) { + return null; + } + } + + /** + * Sets the minimum time in milli-seconds between {@link PhoneStateListener#onCellInfoChanged + * PhoneStateListener.onCellInfoChanged} will be invoked. + *<p> + * The default, 0, means invoke onCellInfoChanged when any of the reported + * information changes. Setting the value to INT_MAX(0x7fffffff) means never issue + * A onCellInfoChanged. + *<p> + * @param rateInMillis the rate + * + * @hide + */ + public void setCellInfoListRate(int rateInMillis) { + try { + ITelephony telephony = getITelephony(); + if (telephony != null) + telephony.setCellInfoListRate(rateInMillis); + } catch (RemoteException ex) { + } catch (NullPointerException ex) { + } + } + + /** + * Returns the MMS user agent. + */ + public String getMmsUserAgent() { + if (mContext == null) return null; + return mContext.getResources().getString( + com.android.internal.R.string.config_mms_user_agent); + } + + /** + * Returns the MMS user agent profile URL. + */ + public String getMmsUAProfUrl() { + if (mContext == null) return null; + return mContext.getResources().getString( + com.android.internal.R.string.config_mms_user_agent_profile_url); + } + + /** + * Opens a logical channel to the ICC card. + * + * Input parameters equivalent to TS 27.007 AT+CCHO command. + * + * <p>Requires Permission: + * {@link android.Manifest.permission#MODIFY_PHONE_STATE MODIFY_PHONE_STATE} + * Or the calling app has carrier privileges. @see #hasCarrierPrivileges + * + * @param AID Application id. See ETSI 102.221 and 101.220. + * @return an IccOpenLogicalChannelResponse object. + * @deprecated Replaced by {@link #iccOpenLogicalChannel(String, int)} + */ + @Deprecated + public IccOpenLogicalChannelResponse iccOpenLogicalChannel(String AID) { + return iccOpenLogicalChannel(getSubId(), AID, -1); + } + + /** + * Opens a logical channel to the ICC card. + * + * Input parameters equivalent to TS 27.007 AT+CCHO command. + * + * <p>Requires Permission: + * {@link android.Manifest.permission#MODIFY_PHONE_STATE MODIFY_PHONE_STATE} + * Or the calling app has carrier privileges. @see #hasCarrierPrivileges + * + * @param AID Application id. See ETSI 102.221 and 101.220. + * @param p2 P2 parameter (described in ISO 7816-4). + * @return an IccOpenLogicalChannelResponse object. + */ + public IccOpenLogicalChannelResponse iccOpenLogicalChannel(String AID, int p2) { + return iccOpenLogicalChannel(getSubId(), AID, p2); + } + + /** + * Opens a logical channel to the ICC card. + * + * Input parameters equivalent to TS 27.007 AT+CCHO command. + * + * <p>Requires Permission: + * {@link android.Manifest.permission#MODIFY_PHONE_STATE MODIFY_PHONE_STATE} + * Or the calling app has carrier privileges. @see #hasCarrierPrivileges + * + * @param subId The subscription to use. + * @param AID Application id. See ETSI 102.221 and 101.220. + * @param p2 P2 parameter (described in ISO 7816-4). + * @return an IccOpenLogicalChannelResponse object. + * @hide + */ + public IccOpenLogicalChannelResponse iccOpenLogicalChannel(int subId, String AID, int p2) { + try { + ITelephony telephony = getITelephony(); + if (telephony != null) + return telephony.iccOpenLogicalChannel(subId, getOpPackageName(), AID, p2); + } catch (RemoteException ex) { + } catch (NullPointerException ex) { + } + return null; + } + + /** + * Closes a previously opened logical channel to the ICC card. + * + * Input parameters equivalent to TS 27.007 AT+CCHC command. + * + * <p>Requires Permission: + * {@link android.Manifest.permission#MODIFY_PHONE_STATE MODIFY_PHONE_STATE} + * Or the calling app has carrier privileges. @see #hasCarrierPrivileges + * + * @param channel is the channel id to be closed as retruned by a successful + * iccOpenLogicalChannel. + * @return true if the channel was closed successfully. + */ + public boolean iccCloseLogicalChannel(int channel) { + return iccCloseLogicalChannel(getSubId(), channel); + } + + /** + * Closes a previously opened logical channel to the ICC card. + * + * Input parameters equivalent to TS 27.007 AT+CCHC command. + * + * <p>Requires Permission: + * {@link android.Manifest.permission#MODIFY_PHONE_STATE MODIFY_PHONE_STATE} + * Or the calling app has carrier privileges. @see #hasCarrierPrivileges + * + * @param subId The subscription to use. + * @param channel is the channel id to be closed as retruned by a successful + * iccOpenLogicalChannel. + * @return true if the channel was closed successfully. + * @hide + */ + public boolean iccCloseLogicalChannel(int subId, int channel) { + try { + ITelephony telephony = getITelephony(); + if (telephony != null) + return telephony.iccCloseLogicalChannel(subId, channel); + } catch (RemoteException ex) { + } catch (NullPointerException ex) { + } + return false; + } + + /** + * Transmit an APDU to the ICC card over a logical channel. + * + * Input parameters equivalent to TS 27.007 AT+CGLA command. + * + * <p>Requires Permission: + * {@link android.Manifest.permission#MODIFY_PHONE_STATE MODIFY_PHONE_STATE} + * Or the calling app has carrier privileges. @see #hasCarrierPrivileges + * + * @param channel is the channel id to be closed as returned by a successful + * iccOpenLogicalChannel. + * @param cla Class of the APDU command. + * @param instruction Instruction of the APDU command. + * @param p1 P1 value of the APDU command. + * @param p2 P2 value of the APDU command. + * @param p3 P3 value of the APDU command. If p3 is negative a 4 byte APDU + * is sent to the SIM. + * @param data Data to be sent with the APDU. + * @return The APDU response from the ICC card with the status appended at + * the end. + */ + public String iccTransmitApduLogicalChannel(int channel, int cla, + int instruction, int p1, int p2, int p3, String data) { + return iccTransmitApduLogicalChannel(getSubId(), channel, cla, + instruction, p1, p2, p3, data); + } + + /** + * Transmit an APDU to the ICC card over a logical channel. + * + * Input parameters equivalent to TS 27.007 AT+CGLA command. + * + * <p>Requires Permission: + * {@link android.Manifest.permission#MODIFY_PHONE_STATE MODIFY_PHONE_STATE} + * Or the calling app has carrier privileges. @see #hasCarrierPrivileges + * + * @param subId The subscription to use. + * @param channel is the channel id to be closed as returned by a successful + * iccOpenLogicalChannel. + * @param cla Class of the APDU command. + * @param instruction Instruction of the APDU command. + * @param p1 P1 value of the APDU command. + * @param p2 P2 value of the APDU command. + * @param p3 P3 value of the APDU command. If p3 is negative a 4 byte APDU + * is sent to the SIM. + * @param data Data to be sent with the APDU. + * @return The APDU response from the ICC card with the status appended at + * the end. + * @hide + */ + public String iccTransmitApduLogicalChannel(int subId, int channel, int cla, + int instruction, int p1, int p2, int p3, String data) { + try { + ITelephony telephony = getITelephony(); + if (telephony != null) + return telephony.iccTransmitApduLogicalChannel(subId, channel, cla, + instruction, p1, p2, p3, data); + } catch (RemoteException ex) { + } catch (NullPointerException ex) { + } + return ""; + } + + /** + * Transmit an APDU to the ICC card over the basic channel. + * + * Input parameters equivalent to TS 27.007 AT+CSIM command. + * + * <p>Requires Permission: + * {@link android.Manifest.permission#MODIFY_PHONE_STATE MODIFY_PHONE_STATE} + * Or the calling app has carrier privileges. @see #hasCarrierPrivileges + * + * @param cla Class of the APDU command. + * @param instruction Instruction of the APDU command. + * @param p1 P1 value of the APDU command. + * @param p2 P2 value of the APDU command. + * @param p3 P3 value of the APDU command. If p3 is negative a 4 byte APDU + * is sent to the SIM. + * @param data Data to be sent with the APDU. + * @return The APDU response from the ICC card with the status appended at + * the end. + */ + public String iccTransmitApduBasicChannel(int cla, + int instruction, int p1, int p2, int p3, String data) { + return iccTransmitApduBasicChannel(getSubId(), cla, + instruction, p1, p2, p3, data); + } + + /** + * Transmit an APDU to the ICC card over the basic channel. + * + * Input parameters equivalent to TS 27.007 AT+CSIM command. + * + * <p>Requires Permission: + * {@link android.Manifest.permission#MODIFY_PHONE_STATE MODIFY_PHONE_STATE} + * Or the calling app has carrier privileges. @see #hasCarrierPrivileges + * + * @param subId The subscription to use. + * @param cla Class of the APDU command. + * @param instruction Instruction of the APDU command. + * @param p1 P1 value of the APDU command. + * @param p2 P2 value of the APDU command. + * @param p3 P3 value of the APDU command. If p3 is negative a 4 byte APDU + * is sent to the SIM. + * @param data Data to be sent with the APDU. + * @return The APDU response from the ICC card with the status appended at + * the end. + * @hide + */ + public String iccTransmitApduBasicChannel(int subId, int cla, + int instruction, int p1, int p2, int p3, String data) { + try { + ITelephony telephony = getITelephony(); + if (telephony != null) + return telephony.iccTransmitApduBasicChannel(subId, cla, + instruction, p1, p2, p3, data); + } catch (RemoteException ex) { + } catch (NullPointerException ex) { + } + return ""; + } + + /** + * Returns the response APDU for a command APDU sent through SIM_IO. + * + * <p>Requires Permission: + * {@link android.Manifest.permission#MODIFY_PHONE_STATE MODIFY_PHONE_STATE} + * Or the calling app has carrier privileges. @see #hasCarrierPrivileges + * + * @param fileID + * @param command + * @param p1 P1 value of the APDU command. + * @param p2 P2 value of the APDU command. + * @param p3 P3 value of the APDU command. + * @param filePath + * @return The APDU response. + */ + public byte[] iccExchangeSimIO(int fileID, int command, int p1, int p2, int p3, + String filePath) { + return iccExchangeSimIO(getSubId(), fileID, command, p1, p2, p3, filePath); + } + + /** + * Returns the response APDU for a command APDU sent through SIM_IO. + * + * <p>Requires Permission: + * {@link android.Manifest.permission#MODIFY_PHONE_STATE MODIFY_PHONE_STATE} + * Or the calling app has carrier privileges. @see #hasCarrierPrivileges + * + * @param subId The subscription to use. + * @param fileID + * @param command + * @param p1 P1 value of the APDU command. + * @param p2 P2 value of the APDU command. + * @param p3 P3 value of the APDU command. + * @param filePath + * @return The APDU response. + * @hide + */ + public byte[] iccExchangeSimIO(int subId, int fileID, int command, int p1, int p2, + int p3, String filePath) { + try { + ITelephony telephony = getITelephony(); + if (telephony != null) + return telephony.iccExchangeSimIO(subId, fileID, command, p1, p2, p3, filePath); + } catch (RemoteException ex) { + } catch (NullPointerException ex) { + } + return null; + } + + /** + * Send ENVELOPE to the SIM and return the response. + * + * <p>Requires Permission: + * {@link android.Manifest.permission#MODIFY_PHONE_STATE MODIFY_PHONE_STATE} + * Or the calling app has carrier privileges. @see #hasCarrierPrivileges + * + * @param content String containing SAT/USAT response in hexadecimal + * format starting with command tag. See TS 102 223 for + * details. + * @return The APDU response from the ICC card in hexadecimal format + * with the last 4 bytes being the status word. If the command fails, + * returns an empty string. + */ + public String sendEnvelopeWithStatus(String content) { + return sendEnvelopeWithStatus(getSubId(), content); + } + + /** + * Send ENVELOPE to the SIM and return the response. + * + * <p>Requires Permission: + * {@link android.Manifest.permission#MODIFY_PHONE_STATE MODIFY_PHONE_STATE} + * Or the calling app has carrier privileges. @see #hasCarrierPrivileges + * + * @param subId The subscription to use. + * @param content String containing SAT/USAT response in hexadecimal + * format starting with command tag. See TS 102 223 for + * details. + * @return The APDU response from the ICC card in hexadecimal format + * with the last 4 bytes being the status word. If the command fails, + * returns an empty string. + * @hide + */ + public String sendEnvelopeWithStatus(int subId, String content) { + try { + ITelephony telephony = getITelephony(); + if (telephony != null) + return telephony.sendEnvelopeWithStatus(subId, content); + } catch (RemoteException ex) { + } catch (NullPointerException ex) { + } + return ""; + } + + /** + * Read one of the NV items defined in com.android.internal.telephony.RadioNVItems. + * Used for device configuration by some CDMA operators. + * <p> + * Requires Permission: + * {@link android.Manifest.permission#MODIFY_PHONE_STATE MODIFY_PHONE_STATE} + * Or the calling app has carrier privileges. @see #hasCarrierPrivileges + * + * @param itemID the ID of the item to read. + * @return the NV item as a String, or null on any failure. + * + * @hide + */ + public String nvReadItem(int itemID) { + try { + ITelephony telephony = getITelephony(); + if (telephony != null) + return telephony.nvReadItem(itemID); + } catch (RemoteException ex) { + Rlog.e(TAG, "nvReadItem RemoteException", ex); + } catch (NullPointerException ex) { + Rlog.e(TAG, "nvReadItem NPE", ex); + } + return ""; + } + + /** + * Write one of the NV items defined in com.android.internal.telephony.RadioNVItems. + * Used for device configuration by some CDMA operators. + * <p> + * Requires Permission: + * {@link android.Manifest.permission#MODIFY_PHONE_STATE MODIFY_PHONE_STATE} + * Or the calling app has carrier privileges. @see #hasCarrierPrivileges + * + * @param itemID the ID of the item to read. + * @param itemValue the value to write, as a String. + * @return true on success; false on any failure. + * + * @hide + */ + public boolean nvWriteItem(int itemID, String itemValue) { + try { + ITelephony telephony = getITelephony(); + if (telephony != null) + return telephony.nvWriteItem(itemID, itemValue); + } catch (RemoteException ex) { + Rlog.e(TAG, "nvWriteItem RemoteException", ex); + } catch (NullPointerException ex) { + Rlog.e(TAG, "nvWriteItem NPE", ex); + } + return false; + } + + /** + * Update the CDMA Preferred Roaming List (PRL) in the radio NV storage. + * Used for device configuration by some CDMA operators. + * <p> + * Requires Permission: + * {@link android.Manifest.permission#MODIFY_PHONE_STATE MODIFY_PHONE_STATE} + * Or the calling app has carrier privileges. @see #hasCarrierPrivileges + * + * @param preferredRoamingList byte array containing the new PRL. + * @return true on success; false on any failure. + * + * @hide + */ + public boolean nvWriteCdmaPrl(byte[] preferredRoamingList) { + try { + ITelephony telephony = getITelephony(); + if (telephony != null) + return telephony.nvWriteCdmaPrl(preferredRoamingList); + } catch (RemoteException ex) { + Rlog.e(TAG, "nvWriteCdmaPrl RemoteException", ex); + } catch (NullPointerException ex) { + Rlog.e(TAG, "nvWriteCdmaPrl NPE", ex); + } + return false; + } + + /** + * Perform the specified type of NV config reset. The radio will be taken offline + * and the device must be rebooted after the operation. Used for device + * configuration by some CDMA operators. + * <p> + * Requires Permission: + * {@link android.Manifest.permission#MODIFY_PHONE_STATE MODIFY_PHONE_STATE} + * Or the calling app has carrier privileges. @see #hasCarrierPrivileges + * + * @param resetType reset type: 1: reload NV reset, 2: erase NV reset, 3: factory NV reset + * @return true on success; false on any failure. + * + * @hide + */ + public boolean nvResetConfig(int resetType) { + try { + ITelephony telephony = getITelephony(); + if (telephony != null) + return telephony.nvResetConfig(resetType); + } catch (RemoteException ex) { + Rlog.e(TAG, "nvResetConfig RemoteException", ex); + } catch (NullPointerException ex) { + Rlog.e(TAG, "nvResetConfig NPE", ex); + } + return false; + } + + /** + * Return an appropriate subscription ID for any situation. + * + * If this object has been created with {@link #createForSubscriptionId}, then the provided + * subId is returned. Otherwise, the default subId will be returned. + */ + private int getSubId() { + if (SubscriptionManager.isUsableSubIdValue(mSubId)) { + return mSubId; + } + return SubscriptionManager.getDefaultSubscriptionId(); + } + + /** + * Return an appropriate subscription ID for any situation. + * + * If this object has been created with {@link #createForSubscriptionId}, then the provided + * subId is returned. Otherwise, the preferred subId which is based on caller's context is + * returned. + * {@see SubscriptionManager#getDefaultDataSubscriptionId()} + * {@see SubscriptionManager#getDefaultVoiceSubscriptionId()} + * {@see SubscriptionManager#getDefaultSmsSubscriptionId()} + */ + private int getSubId(int preferredSubId) { + if (SubscriptionManager.isUsableSubIdValue(mSubId)) { + return mSubId; + } + return preferredSubId; + } + + /** + * Return an appropriate phone ID for any situation. + * + * If this object has been created with {@link #createForSubscriptionId}, then the phoneId + * associated with the provided subId is returned. Otherwise, the default phoneId associated + * with the default subId will be returned. + */ + private int getPhoneId() { + return SubscriptionManager.getPhoneId(getSubId()); + } + + /** + * Return an appropriate phone ID for any situation. + * + * If this object has been created with {@link #createForSubscriptionId}, then the phoneId + * associated with the provided subId is returned. Otherwise, return the phoneId associated + * with the preferred subId based on caller's context. + * {@see SubscriptionManager#getDefaultDataSubscriptionId()} + * {@see SubscriptionManager#getDefaultVoiceSubscriptionId()} + * {@see SubscriptionManager#getDefaultSmsSubscriptionId()} + */ + private int getPhoneId(int preferredSubId) { + return SubscriptionManager.getPhoneId(getSubId(preferredSubId)); + } + + /** + * Return an appropriate slot index for any situation. + * + * if this object has been created with {@link #createForSubscriptionId}, then the slot index + * associated with the provided subId is returned. Otherwise, return the slot index associated + * with the default subId. + * If SIM is not inserted, return default SIM slot index. + * + * {@hide} + */ + @VisibleForTesting + public int getSlotIndex() { + int slotIndex = SubscriptionManager.getSlotIndex(getSubId()); + if (slotIndex == SubscriptionManager.SIM_NOT_INSERTED) { + slotIndex = SubscriptionManager.DEFAULT_SIM_SLOT_INDEX; + } + return slotIndex; + } + + /** + * Sets the telephony property with the value specified. + * + * @hide + */ + public static void setTelephonyProperty(int phoneId, String property, String value) { + String propVal = ""; + String p[] = null; + String prop = SystemProperties.get(property); + + if (value == null) { + value = ""; + } + + if (prop != null) { + p = prop.split(","); + } + + if (!SubscriptionManager.isValidPhoneId(phoneId)) { + Rlog.d(TAG, "setTelephonyProperty: invalid phoneId=" + phoneId + + " property=" + property + " value: " + value + " prop=" + prop); + return; + } + + for (int i = 0; i < phoneId; i++) { + String str = ""; + if ((p != null) && (i < p.length)) { + str = p[i]; + } + propVal = propVal + str + ","; + } + + propVal = propVal + value; + if (p != null) { + for (int i = phoneId + 1; i < p.length; i++) { + propVal = propVal + "," + p[i]; + } + } + + if (propVal.length() > SystemProperties.PROP_VALUE_MAX) { + Rlog.d(TAG, "setTelephonyProperty: property too long phoneId=" + phoneId + + " property=" + property + " value: " + value + " propVal=" + propVal); + return; + } + + Rlog.d(TAG, "setTelephonyProperty: success phoneId=" + phoneId + + " property=" + property + " value: " + value + " propVal=" + propVal); + SystemProperties.set(property, propVal); + } + + /** + * Convenience function for retrieving a value from the secure settings + * value list as an integer. Note that internally setting values are + * always stored as strings; this function converts the string to an + * integer for you. + * <p> + * This version does not take a default value. If the setting has not + * been set, or the string value is not a number, + * it throws {@link SettingNotFoundException}. + * + * @param cr The ContentResolver to access. + * @param name The name of the setting to retrieve. + * @param index The index of the list + * + * @throws SettingNotFoundException Thrown if a setting by the given + * name can't be found or the setting value is not an integer. + * + * @return The value at the given index of settings. + * @hide + */ + public static int getIntAtIndex(android.content.ContentResolver cr, + String name, int index) + throws android.provider.Settings.SettingNotFoundException { + String v = android.provider.Settings.Global.getString(cr, name); + if (v != null) { + String valArray[] = v.split(","); + if ((index >= 0) && (index < valArray.length) && (valArray[index] != null)) { + try { + return Integer.parseInt(valArray[index]); + } catch (NumberFormatException e) { + //Log.e(TAG, "Exception while parsing Integer: ", e); + } + } + } + throw new android.provider.Settings.SettingNotFoundException(name); + } + + /** + * Convenience function for updating settings value as coma separated + * integer values. This will either create a new entry in the table if the + * given name does not exist, or modify the value of the existing row + * with that name. Note that internally setting values are always + * stored as strings, so this function converts the given value to a + * string before storing it. + * + * @param cr The ContentResolver to access. + * @param name The name of the setting to modify. + * @param index The index of the list + * @param value The new value for the setting to be added to the list. + * @return true if the value was set, false on database errors + * @hide + */ + public static boolean putIntAtIndex(android.content.ContentResolver cr, + String name, int index, int value) { + String data = ""; + String valArray[] = null; + String v = android.provider.Settings.Global.getString(cr, name); + + if (index == Integer.MAX_VALUE) { + throw new RuntimeException("putIntAtIndex index == MAX_VALUE index=" + index); + } + if (index < 0) { + throw new RuntimeException("putIntAtIndex index < 0 index=" + index); + } + if (v != null) { + valArray = v.split(","); + } + + // Copy the elements from valArray till index + for (int i = 0; i < index; i++) { + String str = ""; + if ((valArray != null) && (i < valArray.length)) { + str = valArray[i]; + } + data = data + str + ","; + } + + data = data + value; + + // Copy the remaining elements from valArray if any. + if (valArray != null) { + for (int i = index+1; i < valArray.length; i++) { + data = data + "," + valArray[i]; + } + } + return android.provider.Settings.Global.putString(cr, name, data); + } + + /** + * Gets the telephony property. + * + * @hide + */ + public static String getTelephonyProperty(int phoneId, String property, String defaultVal) { + String propVal = null; + String prop = SystemProperties.get(property); + if ((prop != null) && (prop.length() > 0)) { + String values[] = prop.split(","); + if ((phoneId >= 0) && (phoneId < values.length) && (values[phoneId] != null)) { + propVal = values[phoneId]; + } + } + return propVal == null ? defaultVal : propVal; + } + + /** @hide */ + public int getSimCount() { + // FIXME Need to get it from Telephony Dev Controller when that gets implemented! + // and then this method shouldn't be used at all! + if(isMultiSimEnabled()) { + return 2; + } else { + return 1; + } + } + + /** + * Returns the IMS Service Table (IST) that was loaded from the ISIM. + * @return IMS Service Table or null if not present or not loaded + * @hide + */ + public String getIsimIst() { + try { + IPhoneSubInfo info = getSubscriberInfo(); + if (info == null) + return null; + //get the Isim Ist based on subId + return info.getIsimIst(getSubId()); + } catch (RemoteException ex) { + return null; + } catch (NullPointerException ex) { + // This could happen before phone restarts due to crashing + return null; + } + } + + /** + * Returns the IMS Proxy Call Session Control Function(PCSCF) that were loaded from the ISIM. + * @return an array of PCSCF strings with one PCSCF per string, or null if + * not present or not loaded + * @hide + */ + public String[] getIsimPcscf() { + try { + IPhoneSubInfo info = getSubscriberInfo(); + if (info == null) + return null; + //get the Isim Pcscf based on subId + return info.getIsimPcscf(getSubId()); + } catch (RemoteException ex) { + return null; + } catch (NullPointerException ex) { + // This could happen before phone restarts due to crashing + return null; + } + } + + /** + * Returns the response of ISIM Authetification through RIL. + * Returns null if the Authentification hasn't been successed or isn't present iphonesubinfo. + * @return the response of ISIM Authetification, or null if not available + * @hide + * @deprecated + * @see getIccAuthentication with appType=PhoneConstants.APPTYPE_ISIM + */ + public String getIsimChallengeResponse(String nonce){ + try { + IPhoneSubInfo info = getSubscriberInfo(); + if (info == null) + return null; + return info.getIsimChallengeResponse(nonce); + } catch (RemoteException ex) { + return null; + } catch (NullPointerException ex) { + // This could happen before phone restarts due to crashing + return null; + } + } + + // ICC SIM Application Types + /** UICC application type is SIM */ + public static final int APPTYPE_SIM = PhoneConstants.APPTYPE_SIM; + /** UICC application type is USIM */ + public static final int APPTYPE_USIM = PhoneConstants.APPTYPE_USIM; + /** UICC application type is RUIM */ + public static final int APPTYPE_RUIM = PhoneConstants.APPTYPE_RUIM; + /** UICC application type is CSIM */ + public static final int APPTYPE_CSIM = PhoneConstants.APPTYPE_CSIM; + /** UICC application type is ISIM */ + public static final int APPTYPE_ISIM = PhoneConstants.APPTYPE_ISIM; + // authContext (parameter P2) when doing UICC challenge, + // per 3GPP TS 31.102 (Section 7.1.2) + /** Authentication type for UICC challenge is EAP SIM. See RFC 4186 for details. */ + public static final int AUTHTYPE_EAP_SIM = PhoneConstants.AUTH_CONTEXT_EAP_SIM; + /** Authentication type for UICC challenge is EAP AKA. See RFC 4187 for details. */ + public static final int AUTHTYPE_EAP_AKA = PhoneConstants.AUTH_CONTEXT_EAP_AKA; + + /** + * Returns the response of authentication for the default subscription. + * Returns null if the authentication hasn't been successful + * + * <p>Requires that the calling app has carrier privileges or READ_PRIVILEGED_PHONE_STATE + * permission. + * + * @param appType the icc application type, like {@link #APPTYPE_USIM} + * @param authType the authentication type, {@link #AUTHTYPE_EAP_AKA} or + * {@link #AUTHTYPE_EAP_SIM} + * @param data authentication challenge data, base64 encoded. + * See 3GPP TS 31.102 7.1.2 for more details. + * @return the response of authentication, or null if not available + * + * @see #hasCarrierPrivileges + */ + public String getIccAuthentication(int appType, int authType, String data) { + return getIccAuthentication(getSubId(), appType, authType, data); + } + + /** + * Returns the response of USIM Authentication for specified subId. + * Returns null if the authentication hasn't been successful + * + * <p>Requires that the calling app has carrier privileges. + * + * @param subId subscription ID used for authentication + * @param appType the icc application type, like {@link #APPTYPE_USIM} + * @param authType the authentication type, {@link #AUTHTYPE_EAP_AKA} or + * {@link #AUTHTYPE_EAP_SIM} + * @param data authentication challenge data, base64 encoded. + * See 3GPP TS 31.102 7.1.2 for more details. + * @return the response of authentication, or null if not available + * + * @see #hasCarrierPrivileges + * @hide + */ + public String getIccAuthentication(int subId, int appType, int authType, String data) { + try { + IPhoneSubInfo info = getSubscriberInfo(); + if (info == null) + return null; + return info.getIccSimChallengeResponse(subId, appType, authType, data); + } catch (RemoteException ex) { + return null; + } catch (NullPointerException ex) { + // This could happen before phone starts + return null; + } + } + + /** + * Returns an array of Forbidden PLMNs from the USIM App + * Returns null if the query fails. + * + * @return an array of forbidden PLMNs or null if not available + */ + @RequiresPermission(android.Manifest.permission.READ_PHONE_STATE) + public String[] getForbiddenPlmns() { + return getForbiddenPlmns(getSubId(), APPTYPE_USIM); + } + + /** + * Returns an array of Forbidden PLMNs from the specified SIM App + * Returns null if the query fails. + * + * @param subId subscription ID used for authentication + * @param appType the icc application type, like {@link #APPTYPE_USIM} + * @return fplmns an array of forbidden PLMNs + * @hide + */ + @RequiresPermission(android.Manifest.permission.READ_PHONE_STATE) + public String[] getForbiddenPlmns(int subId, int appType) { + try { + ITelephony telephony = getITelephony(); + if (telephony == null) + return null; + return telephony.getForbiddenPlmns(subId, appType); + } catch (RemoteException ex) { + return null; + } catch (NullPointerException ex) { + // This could happen before phone starts + return null; + } + } + + /** + * Get P-CSCF address from PCO after data connection is established or modified. + * @param apnType the apnType, "ims" for IMS APN, "emergency" for EMERGENCY APN + * @return array of P-CSCF address + * @hide + */ + public String[] getPcscfAddress(String apnType) { + try { + ITelephony telephony = getITelephony(); + if (telephony == null) + return new String[0]; + return telephony.getPcscfAddress(apnType, getOpPackageName()); + } catch (RemoteException e) { + return new String[0]; + } + } + + /** @hide */ + @IntDef({ImsFeature.EMERGENCY_MMTEL, ImsFeature.MMTEL, ImsFeature.RCS}) + @Retention(RetentionPolicy.SOURCE) + public @interface Feature {} + + /** + * Returns the {@link IImsServiceController} that corresponds to the given slot Id and IMS + * feature or {@link null} if the service is not available. If an ImsServiceController is + * available, the {@link IImsServiceFeatureListener} callback is registered as a listener for + * feature updates. + * @param slotIndex The SIM slot that we are requesting the {@link IImsServiceController} for. + * @param feature The IMS Feature we are requesting, corresponding to {@link ImsFeature}. + * @param callback Listener that will send updates to ImsManager when there are updates to + * ImsServiceController. + * @return {@link IImsServiceController} interface for the feature specified or {@link null} if + * it is unavailable. + * @hide + */ + public IImsServiceController getImsServiceControllerAndListen(int slotIndex, @Feature int feature, + IImsServiceFeatureListener callback) { + try { + ITelephony telephony = getITelephony(); + if (telephony != null) { + return telephony.getImsServiceControllerAndListen(slotIndex, feature, callback); + } + } catch (RemoteException e) { + Rlog.e(TAG, "getImsServiceControllerAndListen, RemoteException: " + e.getMessage()); + } + return null; + } + + /** + * Set IMS registration state + * + * @param Registration state + * @hide + */ + public void setImsRegistrationState(boolean registered) { + try { + ITelephony telephony = getITelephony(); + if (telephony != null) + telephony.setImsRegistrationState(registered); + } catch (RemoteException e) { + } + } + + /** + * Get the preferred network type. + * Used for device configuration by some CDMA operators. + * <p> + * Requires Permission: + * {@link android.Manifest.permission#MODIFY_PHONE_STATE MODIFY_PHONE_STATE} + * Or the calling app has carrier privileges. @see #hasCarrierPrivileges + * + * @return the preferred network type, defined in RILConstants.java. + * @hide + */ + public int getPreferredNetworkType(int subId) { + try { + ITelephony telephony = getITelephony(); + if (telephony != null) + return telephony.getPreferredNetworkType(subId); + } catch (RemoteException ex) { + Rlog.e(TAG, "getPreferredNetworkType RemoteException", ex); + } catch (NullPointerException ex) { + Rlog.e(TAG, "getPreferredNetworkType NPE", ex); + } + return -1; + } + + /** + * Sets the network selection mode to automatic. + * <p> + * Requires Permission: + * {@link android.Manifest.permission#MODIFY_PHONE_STATE MODIFY_PHONE_STATE} + * Or the calling app has carrier privileges. @see #hasCarrierPrivileges + * + * @hide + * TODO: Add an overload that takes no args. + */ + public void setNetworkSelectionModeAutomatic(int subId) { + try { + ITelephony telephony = getITelephony(); + if (telephony != null) + telephony.setNetworkSelectionModeAutomatic(subId); + } catch (RemoteException ex) { + Rlog.e(TAG, "setNetworkSelectionModeAutomatic RemoteException", ex); + } catch (NullPointerException ex) { + Rlog.e(TAG, "setNetworkSelectionModeAutomatic NPE", ex); + } + } + + /** + * Perform a radio scan and return the list of avialble networks. + * + * The return value is a list of the OperatorInfo of the networks found. Note that this + * scan can take a long time (sometimes minutes) to happen. + * + * <p> + * Requires Permission: + * {@link android.Manifest.permission#MODIFY_PHONE_STATE MODIFY_PHONE_STATE} + * Or the calling app has carrier privileges. @see #hasCarrierPrivileges + * + * @hide + * TODO: Add an overload that takes no args. + */ + public CellNetworkScanResult getCellNetworkScanResults(int subId) { + try { + ITelephony telephony = getITelephony(); + if (telephony != null) + return telephony.getCellNetworkScanResults(subId); + } catch (RemoteException ex) { + Rlog.e(TAG, "getCellNetworkScanResults RemoteException", ex); + } catch (NullPointerException ex) { + Rlog.e(TAG, "getCellNetworkScanResults NPE", ex); + } + return null; + } + + /** + * Request a network scan. + * + * This method is asynchronous, so the network scan results will be returned by callback. + * The returned NetworkScan will contain a callback method which can be used to stop the scan. + * + * <p> + * Requires Permission: + * {@link android.Manifest.permission#MODIFY_PHONE_STATE MODIFY_PHONE_STATE} + * Or the calling app has carrier privileges. @see #hasCarrierPrivileges + * + * @param request Contains all the RAT with bands/channels that need to be scanned. + * @param callback Returns network scan results or errors. + * @return A NetworkScan obj which contains a callback which can stop the scan. + * @hide + */ + public NetworkScan requestNetworkScan( + NetworkScanRequest request, TelephonyScanManager.NetworkScanCallback callback) { + synchronized (this) { + if (mTelephonyScanManager == null) { + mTelephonyScanManager = new TelephonyScanManager(); + } + } + return mTelephonyScanManager.requestNetworkScan(getSubId(), request, callback); + } + + /** + * Ask the radio to connect to the input network and change selection mode to manual. + * + * <p> + * Requires Permission: + * {@link android.Manifest.permission#MODIFY_PHONE_STATE MODIFY_PHONE_STATE} + * Or the calling app has carrier privileges. @see #hasCarrierPrivileges + * + * @hide + * TODO: Add an overload that takes no args. + */ + public boolean setNetworkSelectionModeManual(int subId, OperatorInfo operator, + boolean persistSelection) { + try { + ITelephony telephony = getITelephony(); + if (telephony != null) + return telephony.setNetworkSelectionModeManual(subId, operator, persistSelection); + } catch (RemoteException ex) { + Rlog.e(TAG, "setNetworkSelectionModeManual RemoteException", ex); + } catch (NullPointerException ex) { + Rlog.e(TAG, "setNetworkSelectionModeManual NPE", ex); + } + return false; + } + + /** + * Set the preferred network type. + * Used for device configuration by some CDMA operators. + * <p> + * Requires Permission: + * {@link android.Manifest.permission#MODIFY_PHONE_STATE MODIFY_PHONE_STATE} + * Or the calling app has carrier privileges. @see #hasCarrierPrivileges + * + * @param subId the id of the subscription to set the preferred network type for. + * @param networkType the preferred network type, defined in RILConstants.java. + * @return true on success; false on any failure. + * @hide + */ + public boolean setPreferredNetworkType(int subId, int networkType) { + try { + ITelephony telephony = getITelephony(); + if (telephony != null) + return telephony.setPreferredNetworkType(subId, networkType); + } catch (RemoteException ex) { + Rlog.e(TAG, "setPreferredNetworkType RemoteException", ex); + } catch (NullPointerException ex) { + Rlog.e(TAG, "setPreferredNetworkType NPE", ex); + } + return false; + } + + /** + * Set the preferred network type to global mode which includes LTE, CDMA, EvDo and GSM/WCDMA. + * + * <p> + * Requires that the calling app has carrier privileges. + * @see #hasCarrierPrivileges + * + * @return true on success; false on any failure. + */ + public boolean setPreferredNetworkTypeToGlobal() { + return setPreferredNetworkTypeToGlobal(getSubId()); + } + + /** + * Set the preferred network type to global mode which includes LTE, CDMA, EvDo and GSM/WCDMA. + * + * <p> + * Requires that the calling app has carrier privileges. + * @see #hasCarrierPrivileges + * + * @return true on success; false on any failure. + * @hide + */ + public boolean setPreferredNetworkTypeToGlobal(int subId) { + return setPreferredNetworkType(subId, RILConstants.NETWORK_MODE_LTE_CDMA_EVDO_GSM_WCDMA); + } + + /** + * Check TETHER_DUN_REQUIRED and TETHER_DUN_APN settings, net.tethering.noprovisioning + * SystemProperty, and config_tether_apndata to decide whether DUN APN is required for + * tethering. + * + * @return 0: Not required. 1: required. 2: Not set. + * @hide + */ + public int getTetherApnRequired() { + try { + ITelephony telephony = getITelephony(); + if (telephony != null) + return telephony.getTetherApnRequired(); + } catch (RemoteException ex) { + Rlog.e(TAG, "hasMatchedTetherApnSetting RemoteException", ex); + } catch (NullPointerException ex) { + Rlog.e(TAG, "hasMatchedTetherApnSetting NPE", ex); + } + return 2; + } + + + /** + * Values used to return status for hasCarrierPrivileges call. + */ + /** @hide */ @SystemApi + public static final int CARRIER_PRIVILEGE_STATUS_HAS_ACCESS = 1; + /** @hide */ @SystemApi + public static final int CARRIER_PRIVILEGE_STATUS_NO_ACCESS = 0; + /** @hide */ @SystemApi + public static final int CARRIER_PRIVILEGE_STATUS_RULES_NOT_LOADED = -1; + /** @hide */ @SystemApi + public static final int CARRIER_PRIVILEGE_STATUS_ERROR_LOADING_RULES = -2; + + /** + * Has the calling application been granted carrier privileges by the carrier. + * + * If any of the packages in the calling UID has carrier privileges, the + * call will return true. This access is granted by the owner of the UICC + * card and does not depend on the registered carrier. + * + * @return true if the app has carrier privileges. + */ + public boolean hasCarrierPrivileges() { + return hasCarrierPrivileges(getSubId()); + } + + /** + * Has the calling application been granted carrier privileges by the carrier. + * + * If any of the packages in the calling UID has carrier privileges, the + * call will return true. This access is granted by the owner of the UICC + * card and does not depend on the registered carrier. + * + * @param subId The subscription to use. + * @return true if the app has carrier privileges. + * @hide + */ + public boolean hasCarrierPrivileges(int subId) { + try { + ITelephony telephony = getITelephony(); + if (telephony != null) { + return telephony.getCarrierPrivilegeStatus(mSubId) == + CARRIER_PRIVILEGE_STATUS_HAS_ACCESS; + } + } catch (RemoteException ex) { + Rlog.e(TAG, "hasCarrierPrivileges RemoteException", ex); + } catch (NullPointerException ex) { + Rlog.e(TAG, "hasCarrierPrivileges NPE", ex); + } + return false; + } + + /** + * Override the branding for the current ICCID. + * + * Once set, whenever the SIM is present in the device, the service + * provider name (SPN) and the operator name will both be replaced by the + * brand value input. To unset the value, the same function should be + * called with a null brand value. + * + * <p>Requires that the calling app has carrier privileges. + * @see #hasCarrierPrivileges + * + * @param brand The brand name to display/set. + * @return true if the operation was executed correctly. + */ + public boolean setOperatorBrandOverride(String brand) { + return setOperatorBrandOverride(getSubId(), brand); + } + + /** + * Override the branding for the current ICCID. + * + * Once set, whenever the SIM is present in the device, the service + * provider name (SPN) and the operator name will both be replaced by the + * brand value input. To unset the value, the same function should be + * called with a null brand value. + * + * <p>Requires that the calling app has carrier privileges. + * @see #hasCarrierPrivileges + * + * @param subId The subscription to use. + * @param brand The brand name to display/set. + * @return true if the operation was executed correctly. + * @hide + */ + public boolean setOperatorBrandOverride(int subId, String brand) { + try { + ITelephony telephony = getITelephony(); + if (telephony != null) + return telephony.setOperatorBrandOverride(subId, brand); + } catch (RemoteException ex) { + Rlog.e(TAG, "setOperatorBrandOverride RemoteException", ex); + } catch (NullPointerException ex) { + Rlog.e(TAG, "setOperatorBrandOverride NPE", ex); + } + return false; + } + + /** + * Override the roaming preference for the current ICCID. + * + * Using this call, the carrier app (see #hasCarrierPrivileges) can override + * the platform's notion of a network operator being considered roaming or not. + * The change only affects the ICCID that was active when this call was made. + * + * If null is passed as any of the input, the corresponding value is deleted. + * + * <p>Requires that the caller have carrier privilege. See #hasCarrierPrivileges. + * + * @param gsmRoamingList - List of MCCMNCs to be considered roaming for 3GPP RATs. + * @param gsmNonRoamingList - List of MCCMNCs to be considered not roaming for 3GPP RATs. + * @param cdmaRoamingList - List of SIDs to be considered roaming for 3GPP2 RATs. + * @param cdmaNonRoamingList - List of SIDs to be considered not roaming for 3GPP2 RATs. + * @return true if the operation was executed correctly. + * + * @hide + */ + public boolean setRoamingOverride(List<String> gsmRoamingList, + List<String> gsmNonRoamingList, List<String> cdmaRoamingList, + List<String> cdmaNonRoamingList) { + return setRoamingOverride(getSubId(), gsmRoamingList, gsmNonRoamingList, + cdmaRoamingList, cdmaNonRoamingList); + } + + /** + * Override the roaming preference for the current ICCID. + * + * Using this call, the carrier app (see #hasCarrierPrivileges) can override + * the platform's notion of a network operator being considered roaming or not. + * The change only affects the ICCID that was active when this call was made. + * + * If null is passed as any of the input, the corresponding value is deleted. + * + * <p>Requires that the caller have carrier privilege. See #hasCarrierPrivileges. + * + * @param subId for which the roaming overrides apply. + * @param gsmRoamingList - List of MCCMNCs to be considered roaming for 3GPP RATs. + * @param gsmNonRoamingList - List of MCCMNCs to be considered not roaming for 3GPP RATs. + * @param cdmaRoamingList - List of SIDs to be considered roaming for 3GPP2 RATs. + * @param cdmaNonRoamingList - List of SIDs to be considered not roaming for 3GPP2 RATs. + * @return true if the operation was executed correctly. + * + * @hide + */ + public boolean setRoamingOverride(int subId, List<String> gsmRoamingList, + List<String> gsmNonRoamingList, List<String> cdmaRoamingList, + List<String> cdmaNonRoamingList) { + try { + ITelephony telephony = getITelephony(); + if (telephony != null) + return telephony.setRoamingOverride(subId, gsmRoamingList, gsmNonRoamingList, + cdmaRoamingList, cdmaNonRoamingList); + } catch (RemoteException ex) { + Rlog.e(TAG, "setRoamingOverride RemoteException", ex); + } catch (NullPointerException ex) { + Rlog.e(TAG, "setRoamingOverride NPE", ex); + } + return false; + } + + /** + * Expose the rest of ITelephony to @SystemApi + */ + + /** @hide */ + @SystemApi + @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) + public String getCdmaMdn() { + return getCdmaMdn(getSubId()); + } + + /** @hide */ + @SystemApi + @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) + public String getCdmaMdn(int subId) { + try { + ITelephony telephony = getITelephony(); + if (telephony == null) + return null; + return telephony.getCdmaMdn(subId); + } catch (RemoteException ex) { + return null; + } catch (NullPointerException ex) { + return null; + } + } + + /** @hide */ + @SystemApi + @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) + public String getCdmaMin() { + return getCdmaMin(getSubId()); + } + + /** @hide */ + @SystemApi + @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) + public String getCdmaMin(int subId) { + try { + ITelephony telephony = getITelephony(); + if (telephony == null) + return null; + return telephony.getCdmaMin(subId); + } catch (RemoteException ex) { + return null; + } catch (NullPointerException ex) { + return null; + } + } + + /** @hide */ + @SystemApi + @SuppressLint("Doclava125") + public int checkCarrierPrivilegesForPackage(String pkgName) { + try { + ITelephony telephony = getITelephony(); + if (telephony != null) + return telephony.checkCarrierPrivilegesForPackage(pkgName); + } catch (RemoteException ex) { + Rlog.e(TAG, "checkCarrierPrivilegesForPackage RemoteException", ex); + } catch (NullPointerException ex) { + Rlog.e(TAG, "checkCarrierPrivilegesForPackage NPE", ex); + } + return CARRIER_PRIVILEGE_STATUS_NO_ACCESS; + } + + /** @hide */ + @SystemApi + @SuppressLint("Doclava125") + public int checkCarrierPrivilegesForPackageAnyPhone(String pkgName) { + try { + ITelephony telephony = getITelephony(); + if (telephony != null) + return telephony.checkCarrierPrivilegesForPackageAnyPhone(pkgName); + } catch (RemoteException ex) { + Rlog.e(TAG, "checkCarrierPrivilegesForPackageAnyPhone RemoteException", ex); + } catch (NullPointerException ex) { + Rlog.e(TAG, "checkCarrierPrivilegesForPackageAnyPhone NPE", ex); + } + return CARRIER_PRIVILEGE_STATUS_NO_ACCESS; + } + + /** @hide */ + @SystemApi + public List<String> getCarrierPackageNamesForIntent(Intent intent) { + return getCarrierPackageNamesForIntentAndPhone(intent, getPhoneId()); + } + + /** @hide */ + @SystemApi + public List<String> getCarrierPackageNamesForIntentAndPhone(Intent intent, int phoneId) { + try { + ITelephony telephony = getITelephony(); + if (telephony != null) + return telephony.getCarrierPackageNamesForIntentAndPhone(intent, phoneId); + } catch (RemoteException ex) { + Rlog.e(TAG, "getCarrierPackageNamesForIntentAndPhone RemoteException", ex); + } catch (NullPointerException ex) { + Rlog.e(TAG, "getCarrierPackageNamesForIntentAndPhone NPE", ex); + } + return null; + } + + /** @hide */ + public List<String> getPackagesWithCarrierPrivileges() { + try { + ITelephony telephony = getITelephony(); + if (telephony != null) { + return telephony.getPackagesWithCarrierPrivileges(); + } + } catch (RemoteException ex) { + Rlog.e(TAG, "getPackagesWithCarrierPrivileges RemoteException", ex); + } catch (NullPointerException ex) { + Rlog.e(TAG, "getPackagesWithCarrierPrivileges NPE", ex); + } + return Collections.EMPTY_LIST; + } + + /** @hide */ + @SystemApi + @SuppressLint("Doclava125") + public void dial(String number) { + try { + ITelephony telephony = getITelephony(); + if (telephony != null) + telephony.dial(number); + } catch (RemoteException e) { + Log.e(TAG, "Error calling ITelephony#dial", e); + } + } + + /** + * @deprecated Use {@link android.telecom.TelecomManager#placeCall(Uri address, + * Bundle extras)} instead. + * @hide + */ + @Deprecated + @SystemApi + @RequiresPermission(android.Manifest.permission.CALL_PHONE) + public void call(String callingPackage, String number) { + try { + ITelephony telephony = getITelephony(); + if (telephony != null) + telephony.call(callingPackage, number); + } catch (RemoteException e) { + Log.e(TAG, "Error calling ITelephony#call", e); + } + } + + /** + * @deprecated Use {@link android.telecom.TelecomManager#endCall()} instead. + * @hide + */ + @Deprecated + @SystemApi + @RequiresPermission(android.Manifest.permission.CALL_PHONE) + public boolean endCall() { + try { + ITelephony telephony = getITelephony(); + if (telephony != null) + return telephony.endCall(); + } catch (RemoteException e) { + Log.e(TAG, "Error calling ITelephony#endCall", e); + } + return false; + } + + /** + * @deprecated Use {@link android.telecom.TelecomManager#acceptRingingCall} instead + * @hide + */ + @Deprecated + @SystemApi + @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) + public void answerRingingCall() { + try { + ITelephony telephony = getITelephony(); + if (telephony != null) + telephony.answerRingingCall(); + } catch (RemoteException e) { + Log.e(TAG, "Error calling ITelephony#answerRingingCall", e); + } + } + + /** + * @deprecated Use {@link android.telecom.TelecomManager#silenceRinger} instead + * @hide + */ + @Deprecated + @SystemApi + @SuppressLint("Doclava125") + public void silenceRinger() { + try { + getTelecomService().silenceRinger(getOpPackageName()); + } catch (RemoteException e) { + Log.e(TAG, "Error calling ITelecomService#silenceRinger", e); + } + } + + /** @hide */ + @SystemApi + @RequiresPermission(anyOf = { + android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE, + android.Manifest.permission.READ_PHONE_STATE + }) + public boolean isOffhook() { + try { + ITelephony telephony = getITelephony(); + if (telephony != null) + return telephony.isOffhook(getOpPackageName()); + } catch (RemoteException e) { + Log.e(TAG, "Error calling ITelephony#isOffhook", e); + } + return false; + } + + /** @hide */ + @SystemApi + @RequiresPermission(anyOf = { + android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE, + android.Manifest.permission.READ_PHONE_STATE + }) + public boolean isRinging() { + try { + ITelephony telephony = getITelephony(); + if (telephony != null) + return telephony.isRinging(getOpPackageName()); + } catch (RemoteException e) { + Log.e(TAG, "Error calling ITelephony#isRinging", e); + } + return false; + } + + /** @hide */ + @SystemApi + @RequiresPermission(anyOf = { + android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE, + android.Manifest.permission.READ_PHONE_STATE + }) + public boolean isIdle() { + try { + ITelephony telephony = getITelephony(); + if (telephony != null) + return telephony.isIdle(getOpPackageName()); + } catch (RemoteException e) { + Log.e(TAG, "Error calling ITelephony#isIdle", e); + } + return true; + } + + /** @hide */ + @SystemApi + @RequiresPermission(anyOf = { + android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE, + android.Manifest.permission.READ_PHONE_STATE + }) + public boolean isRadioOn() { + try { + ITelephony telephony = getITelephony(); + if (telephony != null) + return telephony.isRadioOn(getOpPackageName()); + } catch (RemoteException e) { + Log.e(TAG, "Error calling ITelephony#isRadioOn", e); + } + return false; + } + + /** @hide */ + @SystemApi + @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) + public boolean supplyPin(String pin) { + try { + ITelephony telephony = getITelephony(); + if (telephony != null) + return telephony.supplyPin(pin); + } catch (RemoteException e) { + Log.e(TAG, "Error calling ITelephony#supplyPin", e); + } + return false; + } + + /** @hide */ + @SystemApi + @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) + public boolean supplyPuk(String puk, String pin) { + try { + ITelephony telephony = getITelephony(); + if (telephony != null) + return telephony.supplyPuk(puk, pin); + } catch (RemoteException e) { + Log.e(TAG, "Error calling ITelephony#supplyPuk", e); + } + return false; + } + + /** @hide */ + @SystemApi + @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) + public int[] supplyPinReportResult(String pin) { + try { + ITelephony telephony = getITelephony(); + if (telephony != null) + return telephony.supplyPinReportResult(pin); + } catch (RemoteException e) { + Log.e(TAG, "Error calling ITelephony#supplyPinReportResult", e); + } + return new int[0]; + } + + /** @hide */ + @SystemApi + @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) + public int[] supplyPukReportResult(String puk, String pin) { + try { + ITelephony telephony = getITelephony(); + if (telephony != null) + return telephony.supplyPukReportResult(puk, pin); + } catch (RemoteException e) { + Log.e(TAG, "Error calling ITelephony#]", e); + } + return new int[0]; + } + + /** + * Used to notify callers of + * {@link TelephonyManager#sendUssdRequest(String, UssdResponseCallback, Handler)} when the + * network either successfully executes a USSD request, or if there was a failure while + * executing the request. + * <p> + * {@link #onReceiveUssdResponse(TelephonyManager, String, CharSequence)} will be called if the + * USSD request has succeeded. + * {@link #onReceiveUssdResponseFailed(TelephonyManager, String, int)} will be called if the + * USSD request has failed. + */ + public static abstract class UssdResponseCallback { + /** + * Called when a USSD request has succeeded. The {@code response} contains the USSD + * response received from the network. The calling app can choose to either display the + * response to the user or perform some operation based on the response. + * <p> + * USSD responses are unstructured text and their content is determined by the mobile network + * operator. + * + * @param telephonyManager the TelephonyManager the callback is registered to. + * @param request the USSD request sent to the mobile network. + * @param response the response to the USSD request provided by the mobile network. + **/ + public void onReceiveUssdResponse(final TelephonyManager telephonyManager, + String request, CharSequence response) {}; + + /** + * Called when a USSD request has failed to complete. + * + * @param telephonyManager the TelephonyManager the callback is registered to. + * @param request the USSD request sent to the mobile network. + * @param failureCode failure code indicating why the request failed. Will be either + * {@link TelephonyManager#USSD_RETURN_FAILURE} or + * {@link TelephonyManager#USSD_ERROR_SERVICE_UNAVAIL}. + **/ + public void onReceiveUssdResponseFailed(final TelephonyManager telephonyManager, + String request, int failureCode) {}; + } + + /** + * Sends an Unstructured Supplementary Service Data (USSD) request to the mobile network and + * informs the caller of the response via the supplied {@code callback}. + * <p>Carriers define USSD codes which can be sent by the user to request information such as + * the user's current data balance or minutes balance. + * <p>Requires permission: + * {@link android.Manifest.permission#CALL_PHONE} + * @param ussdRequest the USSD command to be executed. + * @param callback called by the framework to inform the caller of the result of executing the + * USSD request (see {@link UssdResponseCallback}). + * @param handler the {@link Handler} to run the request on. + */ + @RequiresPermission(android.Manifest.permission.CALL_PHONE) + public void sendUssdRequest(String ussdRequest, + final UssdResponseCallback callback, Handler handler) { + checkNotNull(callback, "UssdResponseCallback cannot be null."); + final TelephonyManager telephonyManager = this; + + ResultReceiver wrappedCallback = new ResultReceiver(handler) { + @Override + protected void onReceiveResult(int resultCode, Bundle ussdResponse) { + Rlog.d(TAG, "USSD:" + resultCode); + checkNotNull(ussdResponse, "ussdResponse cannot be null."); + UssdResponse response = ussdResponse.getParcelable(USSD_RESPONSE); + + if (resultCode == USSD_RETURN_SUCCESS) { + callback.onReceiveUssdResponse(telephonyManager, response.getUssdRequest(), + response.getReturnMessage()); + } else { + callback.onReceiveUssdResponseFailed(telephonyManager, + response.getUssdRequest(), resultCode); + } + } + }; + + try { + ITelephony telephony = getITelephony(); + if (telephony != null) { + telephony.handleUssdRequest(getSubId(), ussdRequest, wrappedCallback); + } + } catch (RemoteException e) { + Log.e(TAG, "Error calling ITelephony#sendUSSDCode", e); + UssdResponse response = new UssdResponse(ussdRequest, ""); + Bundle returnData = new Bundle(); + returnData.putParcelable(USSD_RESPONSE, response); + wrappedCallback.send(USSD_ERROR_SERVICE_UNAVAIL, returnData); + } + } + + /** + * Whether the device is currently on a technology (e.g. UMTS or LTE) which can support + * voice and data simultaneously. This can change based on location or network condition. + * + * @return {@code true} if simultaneous voice and data supported, and {@code false} otherwise. + */ + public boolean isConcurrentVoiceAndDataSupported() { + try { + ITelephony telephony = getITelephony(); + return (telephony == null ? false : telephony.isConcurrentVoiceAndDataAllowed( + getSubId())); + } catch (RemoteException e) { + Log.e(TAG, "Error calling ITelephony#isConcurrentVoiceAndDataAllowed", e); + } + return false; + } + + /** @hide */ + @SystemApi + @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) + public boolean handlePinMmi(String dialString) { + try { + ITelephony telephony = getITelephony(); + if (telephony != null) + return telephony.handlePinMmi(dialString); + } catch (RemoteException e) { + Log.e(TAG, "Error calling ITelephony#handlePinMmi", e); + } + return false; + } + + /** @hide */ + @SystemApi + @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) + public boolean handlePinMmiForSubscriber(int subId, String dialString) { + try { + ITelephony telephony = getITelephony(); + if (telephony != null) + return telephony.handlePinMmiForSubscriber(subId, dialString); + } catch (RemoteException e) { + Log.e(TAG, "Error calling ITelephony#handlePinMmi", e); + } + return false; + } + + /** @hide */ + @SystemApi + @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) + public void toggleRadioOnOff() { + try { + ITelephony telephony = getITelephony(); + if (telephony != null) + telephony.toggleRadioOnOff(); + } catch (RemoteException e) { + Log.e(TAG, "Error calling ITelephony#toggleRadioOnOff", e); + } + } + + /** @hide */ + @SystemApi + @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) + public boolean setRadio(boolean turnOn) { + try { + ITelephony telephony = getITelephony(); + if (telephony != null) + return telephony.setRadio(turnOn); + } catch (RemoteException e) { + Log.e(TAG, "Error calling ITelephony#setRadio", e); + } + return false; + } + + /** @hide */ + @SystemApi + @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) + public boolean setRadioPower(boolean turnOn) { + try { + ITelephony telephony = getITelephony(); + if (telephony != null) + return telephony.setRadioPower(turnOn); + } catch (RemoteException e) { + Log.e(TAG, "Error calling ITelephony#setRadioPower", e); + } + return false; + } + + /** @hide */ + @SystemApi + @SuppressLint("Doclava125") + public void updateServiceLocation() { + try { + ITelephony telephony = getITelephony(); + if (telephony != null) + telephony.updateServiceLocation(); + } catch (RemoteException e) { + Log.e(TAG, "Error calling ITelephony#updateServiceLocation", e); + } + } + + /** @hide */ + @SystemApi + @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) + public boolean enableDataConnectivity() { + try { + ITelephony telephony = getITelephony(); + if (telephony != null) + return telephony.enableDataConnectivity(); + } catch (RemoteException e) { + Log.e(TAG, "Error calling ITelephony#enableDataConnectivity", e); + } + return false; + } + + /** @hide */ + @SystemApi + @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) + public boolean disableDataConnectivity() { + try { + ITelephony telephony = getITelephony(); + if (telephony != null) + return telephony.disableDataConnectivity(); + } catch (RemoteException e) { + Log.e(TAG, "Error calling ITelephony#disableDataConnectivity", e); + } + return false; + } + + /** @hide */ + @SystemApi + public boolean isDataConnectivityPossible() { + try { + ITelephony telephony = getITelephony(); + if (telephony != null) + return telephony.isDataConnectivityPossible(getSubId(SubscriptionManager + .getDefaultDataSubscriptionId())); + } catch (RemoteException e) { + Log.e(TAG, "Error calling ITelephony#isDataAllowed", e); + } + return false; + } + + /** @hide */ + @SystemApi + public boolean needsOtaServiceProvisioning() { + try { + ITelephony telephony = getITelephony(); + if (telephony != null) + return telephony.needsOtaServiceProvisioning(); + } catch (RemoteException e) { + Log.e(TAG, "Error calling ITelephony#needsOtaServiceProvisioning", e); + } + return false; + } + + /** + * Turns mobile data on or off. + * If this object has been created with {@link #createForSubscriptionId}, applies to the given + * subId. Otherwise, applies to {@link SubscriptionManager#getDefaultDataSubscriptionId()} + * + * <p>Requires Permission: + * {@link android.Manifest.permission#MODIFY_PHONE_STATE MODIFY_PHONE_STATE} or that the + * calling app has carrier privileges. + * + * @param enable Whether to enable mobile data. + * + * @see #hasCarrierPrivileges + */ + @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) + public void setDataEnabled(boolean enable) { + setDataEnabled(getSubId(SubscriptionManager.getDefaultDataSubscriptionId()), enable); + } + + /** @hide */ + @SystemApi + @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) + public void setDataEnabled(int subId, boolean enable) { + try { + Log.d(TAG, "setDataEnabled: enabled=" + enable); + ITelephony telephony = getITelephony(); + if (telephony != null) + telephony.setDataEnabled(subId, enable); + } catch (RemoteException e) { + Log.e(TAG, "Error calling ITelephony#setDataEnabled", e); + } + } + + + /** + * @deprecated use {@link #isDataEnabled()} instead. + * @hide + */ + @SystemApi + @Deprecated + public boolean getDataEnabled() { + return isDataEnabled(); + } + + /** + * Returns whether mobile data is enabled or not. + * + * If this object has been created with {@link #createForSubscriptionId}, applies to the given + * subId. Otherwise, applies to {@link SubscriptionManager#getDefaultDataSubscriptionId()} + * + * <p>Requires one of the following permissions: + * {@link android.Manifest.permission#ACCESS_NETWORK_STATE ACCESS_NETWORK_STATE}, + * {@link android.Manifest.permission#MODIFY_PHONE_STATE MODIFY_PHONE_STATE}, or that the + * calling app has carrier privileges. + * + * <p>Note that this does not take into account any data restrictions that may be present on the + * calling app. Such restrictions may be inspected with + * {@link ConnectivityManager#getRestrictBackgroundStatus}. + * + * @return true if mobile data is enabled. + * + * @see #hasCarrierPrivileges + */ + @SuppressWarnings("deprecation") + public boolean isDataEnabled() { + return getDataEnabled(getSubId(SubscriptionManager.getDefaultDataSubscriptionId())); + } + + /** + * @deprecated use {@link #isDataEnabled(int)} instead. + * @hide + */ + @SystemApi + public boolean getDataEnabled(int subId) { + boolean retVal = false; + try { + ITelephony telephony = getITelephony(); + if (telephony != null) + retVal = telephony.getDataEnabled(subId); + } catch (RemoteException e) { + Log.e(TAG, "Error calling ITelephony#getDataEnabled", e); + } catch (NullPointerException e) { + } + return retVal; + } + + /** + * Returns the result and response from RIL for oem request + * + * @param oemReq the data is sent to ril. + * @param oemResp the respose data from RIL. + * @return negative value request was not handled or get error + * 0 request was handled succesfully, but no response data + * positive value success, data length of response + * @hide + * @deprecated OEM needs a vendor-extension hal and their apps should use that instead + */ + @Deprecated + public int invokeOemRilRequestRaw(byte[] oemReq, byte[] oemResp) { + try { + ITelephony telephony = getITelephony(); + if (telephony != null) + return telephony.invokeOemRilRequestRaw(oemReq, oemResp); + } catch (RemoteException ex) { + } catch (NullPointerException ex) { + } + return -1; + } + + /** @hide */ + @SystemApi + @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) + public void enableVideoCalling(boolean enable) { + try { + ITelephony telephony = getITelephony(); + if (telephony != null) + telephony.enableVideoCalling(enable); + } catch (RemoteException e) { + Log.e(TAG, "Error calling ITelephony#enableVideoCalling", e); + } + } + + /** @hide */ + @SystemApi + @RequiresPermission(anyOf = { + android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE, + android.Manifest.permission.READ_PHONE_STATE + }) + public boolean isVideoCallingEnabled() { + try { + ITelephony telephony = getITelephony(); + if (telephony != null) + return telephony.isVideoCallingEnabled(getOpPackageName()); + } catch (RemoteException e) { + Log.e(TAG, "Error calling ITelephony#isVideoCallingEnabled", e); + } + return false; + } + + /** + * Whether the device supports configuring the DTMF tone length. + * + * @return {@code true} if the DTMF tone length can be changed, and {@code false} otherwise. + */ + public boolean canChangeDtmfToneLength() { + try { + ITelephony telephony = getITelephony(); + if (telephony != null) { + return telephony.canChangeDtmfToneLength(); + } + } catch (RemoteException e) { + Log.e(TAG, "Error calling ITelephony#canChangeDtmfToneLength", e); + } catch (SecurityException e) { + Log.e(TAG, "Permission error calling ITelephony#canChangeDtmfToneLength", e); + } + return false; + } + + /** + * Whether the device is a world phone. + * + * @return {@code true} if the device is a world phone, and {@code false} otherwise. + */ + public boolean isWorldPhone() { + try { + ITelephony telephony = getITelephony(); + if (telephony != null) { + return telephony.isWorldPhone(); + } + } catch (RemoteException e) { + Log.e(TAG, "Error calling ITelephony#isWorldPhone", e); + } catch (SecurityException e) { + Log.e(TAG, "Permission error calling ITelephony#isWorldPhone", e); + } + return false; + } + + /** + * @deprecated Use {@link TelecomManager#isTtySupported()} instead + * Whether the phone supports TTY mode. + * + * @return {@code true} if the device supports TTY mode, and {@code false} otherwise. + * + */ + @Deprecated + public boolean isTtyModeSupported() { + try { + ITelephony telephony = getITelephony(); + if (telephony != null) { + return telephony.isTtyModeSupported(); + } + } catch (RemoteException e) { + Log.e(TAG, "Error calling ITelephony#isTtyModeSupported", e); + } catch (SecurityException e) { + Log.e(TAG, "Permission error calling ITelephony#isTtyModeSupported", e); + } + return false; + } + + /** + * Whether the phone supports hearing aid compatibility. + * + * @return {@code true} if the device supports hearing aid compatibility, and {@code false} + * otherwise. + */ + public boolean isHearingAidCompatibilitySupported() { + try { + ITelephony telephony = getITelephony(); + if (telephony != null) { + return telephony.isHearingAidCompatibilitySupported(); + } + } catch (RemoteException e) { + Log.e(TAG, "Error calling ITelephony#isHearingAidCompatibilitySupported", e); + } catch (SecurityException e) { + Log.e(TAG, "Permission error calling ITelephony#isHearingAidCompatibilitySupported", e); + } + return false; + } + + /** + * Returns the IMS Registration Status + * @hide + */ + public boolean isImsRegistered() { + try { + ITelephony telephony = getITelephony(); + if (telephony == null) + return false; + return telephony.isImsRegistered(); + } catch (RemoteException ex) { + return false; + } catch (NullPointerException ex) { + return false; + } + } + + /** + * Returns the IMS Registration Status for a particular Subscription ID + * + * @param subId Subscription ID + * @return true if IMS status is registered, false if the IMS status is not registered or a + * RemoteException occurred. + * + * @hide + */ + public boolean isImsRegistered(int subId) { + try { + return getITelephony().isImsRegisteredForSubscriber(subId); + } catch (RemoteException ex) { + return false; + } catch (NullPointerException ex) { + return false; + } + } + + /** + * Returns the Status of Volte + * @hide + */ + public boolean isVolteAvailable() { + try { + return getITelephony().isVolteAvailable(); + } catch (RemoteException ex) { + return false; + } catch (NullPointerException ex) { + return false; + } + } + + /** + * Returns the Status of video telephony (VT) + * @hide + */ + public boolean isVideoTelephonyAvailable() { + try { + return getITelephony().isVideoTelephonyAvailable(); + } catch (RemoteException ex) { + return false; + } catch (NullPointerException ex) { + return false; + } + } + + /** + * Returns the Status of Wi-Fi Calling + * @hide + */ + public boolean isWifiCallingAvailable() { + try { + return getITelephony().isWifiCallingAvailable(); + } catch (RemoteException ex) { + return false; + } catch (NullPointerException ex) { + return false; + } + } + + /** + * Set TelephonyProperties.PROPERTY_ICC_OPERATOR_NUMERIC for the default phone. + * + * @hide + */ + public void setSimOperatorNumeric(String numeric) { + int phoneId = getPhoneId(); + setSimOperatorNumericForPhone(phoneId, numeric); + } + + /** + * Set TelephonyProperties.PROPERTY_ICC_OPERATOR_NUMERIC for the given phone. + * + * @hide + */ + public void setSimOperatorNumericForPhone(int phoneId, String numeric) { + setTelephonyProperty(phoneId, + TelephonyProperties.PROPERTY_ICC_OPERATOR_NUMERIC, numeric); + } + + /** + * Set TelephonyProperties.PROPERTY_ICC_OPERATOR_NUMERIC for the default phone. + * + * @hide + */ + public void setSimOperatorName(String name) { + int phoneId = getPhoneId(); + setSimOperatorNameForPhone(phoneId, name); + } + + /** + * Set TelephonyProperties.PROPERTY_ICC_OPERATOR_NUMERIC for the given phone. + * + * @hide + */ + public void setSimOperatorNameForPhone(int phoneId, String name) { + setTelephonyProperty(phoneId, + TelephonyProperties.PROPERTY_ICC_OPERATOR_ALPHA, name); + } + + /** + * Set TelephonyProperties.PROPERTY_ICC_OPERATOR_ISO_COUNTRY for the default phone. + * + * @hide + */ + public void setSimCountryIso(String iso) { + int phoneId = getPhoneId(); + setSimCountryIsoForPhone(phoneId, iso); + } + + /** + * Set TelephonyProperties.PROPERTY_ICC_OPERATOR_ISO_COUNTRY for the given phone. + * + * @hide + */ + public void setSimCountryIsoForPhone(int phoneId, String iso) { + setTelephonyProperty(phoneId, + TelephonyProperties.PROPERTY_ICC_OPERATOR_ISO_COUNTRY, iso); + } + + /** + * Set TelephonyProperties.PROPERTY_SIM_STATE for the default phone. + * + * @hide + */ + public void setSimState(String state) { + int phoneId = getPhoneId(); + setSimStateForPhone(phoneId, state); + } + + /** + * Set TelephonyProperties.PROPERTY_SIM_STATE for the given phone. + * + * @hide + */ + public void setSimStateForPhone(int phoneId, String state) { + setTelephonyProperty(phoneId, + TelephonyProperties.PROPERTY_SIM_STATE, state); + } + + /** + * Requested state of SIM + * + * CARD_POWER_DOWN + * Powers down the SIM. SIM must be up prior. + * + * CARD_POWER_UP + * Powers up the SIM normally. SIM must be down prior. + * + * CARD_POWER_UP_PASS_THROUGH + * Powers up the SIM in PASS_THROUGH mode. SIM must be down prior. + * When SIM is powered up in PASS_THOUGH mode, the modem does not send + * any command to it (for example SELECT of MF, or TERMINAL CAPABILITY), + * and the SIM card is controlled completely by Telephony sending APDUs + * directly. The SIM card state will be RIL_CARDSTATE_PRESENT and the + * number of card apps will be 0. + * No new error code is generated. Emergency calls are supported in the + * same way as if the SIM card is absent. + * The PASS_THROUGH mode is valid only for the specific card session where it + * is activated, and normal behavior occurs at the next SIM initialization, + * unless PASS_THROUGH mode is requested again. Hence, the last power-up mode + * is NOT persistent across boots. On reboot, SIM will power up normally. + */ + /** @hide */ + public static final int CARD_POWER_DOWN = 0; + /** @hide */ + public static final int CARD_POWER_UP = 1; + /** @hide */ + public static final int CARD_POWER_UP_PASS_THROUGH = 2; + + /** + * Set SIM card power state. + * + * @param state State of SIM (power down, power up, pass through) + * @see #CARD_POWER_DOWN + * @see #CARD_POWER_UP + * @see #CARD_POWER_UP_PASS_THROUGH + * Callers should monitor for {@link TelephonyIntents#ACTION_SIM_STATE_CHANGED} + * broadcasts to determine success or failure and timeout if needed. + * + * <p>Requires Permission: + * {@link android.Manifest.permission#MODIFY_PHONE_STATE MODIFY_PHONE_STATE} + * + * @hide + **/ + public void setSimPowerState(int state) { + setSimPowerStateForSlot(getSlotIndex(), state); + } + + /** + * Set SIM card power state. + * + * @param slotIndex SIM slot id + * @param state State of SIM (power down, power up, pass through) + * @see #CARD_POWER_DOWN + * @see #CARD_POWER_UP + * @see #CARD_POWER_UP_PASS_THROUGH + * Callers should monitor for {@link TelephonyIntents#ACTION_SIM_STATE_CHANGED} + * broadcasts to determine success or failure and timeout if needed. + * + * <p>Requires Permission: + * {@link android.Manifest.permission#MODIFY_PHONE_STATE MODIFY_PHONE_STATE} + * + * @hide + **/ + public void setSimPowerStateForSlot(int slotIndex, int state) { + try { + ITelephony telephony = getITelephony(); + if (telephony != null) { + telephony.setSimPowerStateForSlot(slotIndex, state); + } + } catch (RemoteException e) { + Log.e(TAG, "Error calling ITelephony#setSimPowerStateForSlot", e); + } catch (SecurityException e) { + Log.e(TAG, "Permission error calling ITelephony#setSimPowerStateForSlot", e); + } + } + + /** + * Set baseband version for the default phone. + * + * @param version baseband version + * @hide + */ + public void setBasebandVersion(String version) { + int phoneId = getPhoneId(); + setBasebandVersionForPhone(phoneId, version); + } + + /** + * Set baseband version by phone id. + * + * @param phoneId for which baseband version is set + * @param version baseband version + * @hide + */ + public void setBasebandVersionForPhone(int phoneId, String version) { + if (SubscriptionManager.isValidPhoneId(phoneId)) { + String prop = TelephonyProperties.PROPERTY_BASEBAND_VERSION + + ((phoneId == 0) ? "" : Integer.toString(phoneId)); + SystemProperties.set(prop, version); + } + } + + /** + * Set phone type for the default phone. + * + * @param type phone type + * + * @hide + */ + public void setPhoneType(int type) { + int phoneId = getPhoneId(); + setPhoneType(phoneId, type); + } + + /** + * Set phone type by phone id. + * + * @param phoneId for which phone type is set + * @param type phone type + * + * @hide + */ + public void setPhoneType(int phoneId, int type) { + if (SubscriptionManager.isValidPhoneId(phoneId)) { + TelephonyManager.setTelephonyProperty(phoneId, + TelephonyProperties.CURRENT_ACTIVE_PHONE, String.valueOf(type)); + } + } + + /** + * Get OTASP number schema for the default phone. + * + * @param defaultValue default value + * @return OTA SP number schema + * + * @hide + */ + public String getOtaSpNumberSchema(String defaultValue) { + int phoneId = getPhoneId(); + return getOtaSpNumberSchemaForPhone(phoneId, defaultValue); + } + + /** + * Get OTASP number schema by phone id. + * + * @param phoneId for which OTA SP number schema is get + * @param defaultValue default value + * @return OTA SP number schema + * + * @hide + */ + public String getOtaSpNumberSchemaForPhone(int phoneId, String defaultValue) { + if (SubscriptionManager.isValidPhoneId(phoneId)) { + return TelephonyManager.getTelephonyProperty(phoneId, + TelephonyProperties.PROPERTY_OTASP_NUM_SCHEMA, defaultValue); + } + + return defaultValue; + } + + /** + * Get SMS receive capable from system property for the default phone. + * + * @param defaultValue default value + * @return SMS receive capable + * + * @hide + */ + public boolean getSmsReceiveCapable(boolean defaultValue) { + int phoneId = getPhoneId(); + return getSmsReceiveCapableForPhone(phoneId, defaultValue); + } + + /** + * Get SMS receive capable from system property by phone id. + * + * @param phoneId for which SMS receive capable is get + * @param defaultValue default value + * @return SMS receive capable + * + * @hide + */ + public boolean getSmsReceiveCapableForPhone(int phoneId, boolean defaultValue) { + if (SubscriptionManager.isValidPhoneId(phoneId)) { + return Boolean.parseBoolean(TelephonyManager.getTelephonyProperty(phoneId, + TelephonyProperties.PROPERTY_SMS_RECEIVE, String.valueOf(defaultValue))); + } + + return defaultValue; + } + + /** + * Get SMS send capable from system property for the default phone. + * + * @param defaultValue default value + * @return SMS send capable + * + * @hide + */ + public boolean getSmsSendCapable(boolean defaultValue) { + int phoneId = getPhoneId(); + return getSmsSendCapableForPhone(phoneId, defaultValue); + } + + /** + * Get SMS send capable from system property by phone id. + * + * @param phoneId for which SMS send capable is get + * @param defaultValue default value + * @return SMS send capable + * + * @hide + */ + public boolean getSmsSendCapableForPhone(int phoneId, boolean defaultValue) { + if (SubscriptionManager.isValidPhoneId(phoneId)) { + return Boolean.parseBoolean(TelephonyManager.getTelephonyProperty(phoneId, + TelephonyProperties.PROPERTY_SMS_SEND, String.valueOf(defaultValue))); + } + + return defaultValue; + } + + /** + * Set the alphabetic name of current registered operator. + * @param name the alphabetic name of current registered operator. + * @hide + */ + public void setNetworkOperatorName(String name) { + int phoneId = getPhoneId(); + setNetworkOperatorNameForPhone(phoneId, name); + } + + /** + * Set the alphabetic name of current registered operator. + * @param phoneId which phone you want to set + * @param name the alphabetic name of current registered operator. + * @hide + */ + public void setNetworkOperatorNameForPhone(int phoneId, String name) { + if (SubscriptionManager.isValidPhoneId(phoneId)) { + setTelephonyProperty(phoneId, TelephonyProperties.PROPERTY_OPERATOR_ALPHA, name); + } + } + + /** + * Set the numeric name (MCC+MNC) of current registered operator. + * @param operator the numeric name (MCC+MNC) of current registered operator + * @hide + */ + public void setNetworkOperatorNumeric(String numeric) { + int phoneId = getPhoneId(); + setNetworkOperatorNumericForPhone(phoneId, numeric); + } + + /** + * Set the numeric name (MCC+MNC) of current registered operator. + * @param phoneId for which phone type is set + * @param operator the numeric name (MCC+MNC) of current registered operator + * @hide + */ + public void setNetworkOperatorNumericForPhone(int phoneId, String numeric) { + setTelephonyProperty(phoneId, TelephonyProperties.PROPERTY_OPERATOR_NUMERIC, numeric); + } + + /** + * Set roaming state of the current network, for GSM purposes. + * @param isRoaming is network in romaing state or not + * @hide + */ + public void setNetworkRoaming(boolean isRoaming) { + int phoneId = getPhoneId(); + setNetworkRoamingForPhone(phoneId, isRoaming); + } + + /** + * Set roaming state of the current network, for GSM purposes. + * @param phoneId which phone you want to set + * @param isRoaming is network in romaing state or not + * @hide + */ + public void setNetworkRoamingForPhone(int phoneId, boolean isRoaming) { + if (SubscriptionManager.isValidPhoneId(phoneId)) { + setTelephonyProperty(phoneId, TelephonyProperties.PROPERTY_OPERATOR_ISROAMING, + isRoaming ? "true" : "false"); + } + } + + /** + * Set the ISO country code equivalent of the current registered + * operator's MCC (Mobile Country Code). + * @param iso the ISO country code equivalent of the current registered + * @hide + */ + public void setNetworkCountryIso(String iso) { + int phoneId = getPhoneId(); + setNetworkCountryIsoForPhone(phoneId, iso); + } + + /** + * Set the ISO country code equivalent of the current registered + * operator's MCC (Mobile Country Code). + * @param phoneId which phone you want to set + * @param iso the ISO country code equivalent of the current registered + * @hide + */ + public void setNetworkCountryIsoForPhone(int phoneId, String iso) { + if (SubscriptionManager.isValidPhoneId(phoneId)) { + setTelephonyProperty(phoneId, + TelephonyProperties.PROPERTY_OPERATOR_ISO_COUNTRY, iso); + } + } + + /** + * Set the network type currently in use on the device for data transmission. + * + * If this object has been created with {@link #createForSubscriptionId}, applies to the + * phoneId associated with the given subId. Otherwise, applies to the phoneId associated with + * {@link SubscriptionManager#getDefaultDataSubscriptionId()} + * @param type the network type currently in use on the device for data transmission + * @hide + */ + public void setDataNetworkType(int type) { + int phoneId = getPhoneId(SubscriptionManager.getDefaultDataSubscriptionId()); + setDataNetworkTypeForPhone(phoneId, type); + } + + /** + * Set the network type currently in use on the device for data transmission. + * @param phoneId which phone you want to set + * @param type the network type currently in use on the device for data transmission + * @hide + */ + public void setDataNetworkTypeForPhone(int phoneId, int type) { + if (SubscriptionManager.isValidPhoneId(phoneId)) { + setTelephonyProperty(phoneId, + TelephonyProperties.PROPERTY_DATA_NETWORK_TYPE, + ServiceState.rilRadioTechnologyToString(type)); + } + } + + /** + * Returns the subscription ID for the given phone account. + * @hide + */ + public int getSubIdForPhoneAccount(PhoneAccount phoneAccount) { + int retval = SubscriptionManager.INVALID_SUBSCRIPTION_ID; + try { + ITelephony service = getITelephony(); + if (service != null) { + retval = service.getSubIdForPhoneAccount(phoneAccount); + } + } catch (RemoteException e) { + } + + return retval; + } + + private int getSubIdForPhoneAccountHandle(PhoneAccountHandle phoneAccountHandle) { + int retval = SubscriptionManager.INVALID_SUBSCRIPTION_ID; + try { + ITelecomService service = getTelecomService(); + if (service != null) { + retval = getSubIdForPhoneAccount(service.getPhoneAccount(phoneAccountHandle)); + } + } catch (RemoteException e) { + } + + return retval; + } + + /** + * Resets telephony manager settings back to factory defaults. + * + * @hide + */ + public void factoryReset(int subId) { + try { + Log.d(TAG, "factoryReset: subId=" + subId); + ITelephony telephony = getITelephony(); + if (telephony != null) + telephony.factoryReset(subId); + } catch (RemoteException e) { + } + } + + + /** @hide */ + public String getLocaleFromDefaultSim() { + try { + final ITelephony telephony = getITelephony(); + if (telephony != null) { + return telephony.getLocaleFromDefaultSim(); + } + } catch (RemoteException ex) { + } + return null; + } + + /** + * Requests the modem activity info. The recipient will place the result + * in `result`. + * @param result The object on which the recipient will send the resulting + * {@link android.telephony.ModemActivityInfo} object. + * @hide + */ + public void requestModemActivityInfo(ResultReceiver result) { + try { + ITelephony service = getITelephony(); + if (service != null) { + service.requestModemActivityInfo(result); + return; + } + } catch (RemoteException e) { + Log.e(TAG, "Error calling ITelephony#getModemActivityInfo", e); + } + result.send(0, null); + } + + /** + * Returns the current {@link ServiceState} information. + */ + @RequiresPermission(android.Manifest.permission.READ_PHONE_STATE) + public ServiceState getServiceState() { + return getServiceStateForSubscriber(getSubId()); + } + + /** + * Returns the service state information on specified subscription. Callers require + * either READ_PRIVILEGED_PHONE_STATE or READ_PHONE_STATE to retrieve the information. + * @hide + */ + public ServiceState getServiceStateForSubscriber(int subId) { + try { + ITelephony service = getITelephony(); + if (service != null) { + return service.getServiceStateForSubscriber(subId, getOpPackageName()); + } + } catch (RemoteException e) { + Log.e(TAG, "Error calling ITelephony#getServiceStateForSubscriber", e); + } + return null; + } + + /** + * Returns the URI for the per-account voicemail ringtone set in Phone settings. + * + * @param accountHandle The handle for the {@link PhoneAccount} for which to retrieve the + * voicemail ringtone. + * @return The URI for the ringtone to play when receiving a voicemail from a specific + * PhoneAccount. + */ + public Uri getVoicemailRingtoneUri(PhoneAccountHandle accountHandle) { + try { + ITelephony service = getITelephony(); + if (service != null) { + return service.getVoicemailRingtoneUri(accountHandle); + } + } catch (RemoteException e) { + Log.e(TAG, "Error calling ITelephony#getVoicemailRingtoneUri", e); + } + return null; + } + + /** + * Sets the per-account voicemail ringtone. + * + * <p>Requires that the calling app is the default dialer, or has carrier privileges, or has + * permission {@link android.Manifest.permission#MODIFY_PHONE_STATE MODIFY_PHONE_STATE}. + * + * @param phoneAccountHandle The handle for the {@link PhoneAccount} for which to set the + * voicemail ringtone. + * @param uri The URI for the ringtone to play when receiving a voicemail from a specific + * PhoneAccount. + * @see #hasCarrierPrivileges + */ + public void setVoicemailRingtoneUri(PhoneAccountHandle phoneAccountHandle, Uri uri) { + try { + ITelephony service = getITelephony(); + if (service != null) { + service.setVoicemailRingtoneUri(getOpPackageName(), phoneAccountHandle, uri); + } + } catch (RemoteException e) { + Log.e(TAG, "Error calling ITelephony#setVoicemailRingtoneUri", e); + } + } + + /** + * Returns whether vibration is set for voicemail notification in Phone settings. + * + * @param accountHandle The handle for the {@link PhoneAccount} for which to retrieve the + * voicemail vibration setting. + * @return {@code true} if the vibration is set for this PhoneAccount, {@code false} otherwise. + */ + public boolean isVoicemailVibrationEnabled(PhoneAccountHandle accountHandle) { + try { + ITelephony service = getITelephony(); + if (service != null) { + return service.isVoicemailVibrationEnabled(accountHandle); + } + } catch (RemoteException e) { + Log.e(TAG, "Error calling ITelephony#isVoicemailVibrationEnabled", e); + } + return false; + } + + /** + * Sets the per-account preference whether vibration is enabled for voicemail notifications. + * + * <p>Requires that the calling app is the default dialer, or has carrier privileges, or has + * permission {@link android.Manifest.permission#MODIFY_PHONE_STATE MODIFY_PHONE_STATE}. + * + * @param phoneAccountHandle The handle for the {@link PhoneAccount} for which to set the + * voicemail vibration setting. + * @param enabled Whether to enable or disable vibration for voicemail notifications from a + * specific PhoneAccount. + * @see #hasCarrierPrivileges + */ + public void setVoicemailVibrationEnabled(PhoneAccountHandle phoneAccountHandle, + boolean enabled) { + try { + ITelephony service = getITelephony(); + if (service != null) { + service.setVoicemailVibrationEnabled(getOpPackageName(), phoneAccountHandle, + enabled); + } + } catch (RemoteException e) { + Log.e(TAG, "Error calling ITelephony#isVoicemailVibrationEnabled", e); + } + } + + /** + * Return the application ID for the app type like {@link APPTYPE_CSIM}. + * + * Requires that the calling app has READ_PRIVILEGED_PHONE_STATE permission + * + * @param appType the uicc app type like {@link APPTYPE_CSIM} + * @return Application ID for specificied app type or null if no uicc or error. + * @hide + */ + public String getAidForAppType(int appType) { + return getAidForAppType(getSubId(), appType); + } + + /** + * Return the application ID for the app type like {@link APPTYPE_CSIM}. + * + * Requires that the calling app has READ_PRIVILEGED_PHONE_STATE permission + * + * @param subId the subscription ID that this request applies to. + * @param appType the uicc app type, like {@link APPTYPE_CSIM} + * @return Application ID for specificied app type or null if no uicc or error. + * @hide + */ + public String getAidForAppType(int subId, int appType) { + try { + ITelephony service = getITelephony(); + if (service != null) { + return service.getAidForAppType(subId, appType); + } + } catch (RemoteException e) { + Log.e(TAG, "Error calling ITelephony#getAidForAppType", e); + } + return null; + } + + /** + * Return the Electronic Serial Number. + * + * Requires that the calling app has READ_PRIVILEGED_PHONE_STATE permission + * + * @return ESN or null if error. + * @hide + */ + public String getEsn() { + return getEsn(getSubId()); + } + + /** + * Return the Electronic Serial Number. + * + * Requires that the calling app has READ_PRIVILEGED_PHONE_STATE permission + * + * @param subId the subscription ID that this request applies to. + * @return ESN or null if error. + * @hide + */ + public String getEsn(int subId) { + try { + ITelephony service = getITelephony(); + if (service != null) { + return service.getEsn(subId); + } + } catch (RemoteException e) { + Log.e(TAG, "Error calling ITelephony#getEsn", e); + } + return null; + } + + /** + * Return the Preferred Roaming List Version + * + * Requires that the calling app has READ_PRIVILEGED_PHONE_STATE permission + * + * @return PRLVersion or null if error. + * @hide + */ + public String getCdmaPrlVersion() { + return getCdmaPrlVersion(getSubId()); + } + + /** + * Return the Preferred Roaming List Version + * + * Requires that the calling app has READ_PRIVILEGED_PHONE_STATE permission + * + * @param subId the subscription ID that this request applies to. + * @return PRLVersion or null if error. + * @hide + */ + public String getCdmaPrlVersion(int subId) { + try { + ITelephony service = getITelephony(); + if (service != null) { + return service.getCdmaPrlVersion(subId); + } + } catch (RemoteException e) { + Log.e(TAG, "Error calling ITelephony#getCdmaPrlVersion", e); + } + return null; + } + + /** + * Get snapshot of Telephony histograms + * @return List of Telephony histograms + * Requires Permission: + * {@link android.Manifest.permission#MODIFY_PHONE_STATE MODIFY_PHONE_STATE} + * Or the calling app has carrier privileges. + * @hide + */ + @SystemApi + @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) + public List<TelephonyHistogram> getTelephonyHistograms() { + try { + ITelephony service = getITelephony(); + if (service != null) { + return service.getTelephonyHistograms(); + } + } catch (RemoteException e) { + Log.e(TAG, "Error calling ITelephony#getTelephonyHistograms", e); + } + return null; + } + + /** + * Set the allowed carrier list for slotIndex + * Require system privileges. In the future we may add this to carrier APIs. + * + * <p>Requires Permission: + * {@link android.Manifest.permission#MODIFY_PHONE_STATE} + * + * <p>This method works only on devices with {@link + * android.content.pm.PackageManager#FEATURE_TELEPHONY_CARRIERLOCK} enabled. + * + * @return The number of carriers set successfully. Should be length of + * carrierList on success; -1 if carrierList null or on error. + * @hide + */ + @SystemApi + @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) + public int setAllowedCarriers(int slotIndex, List<CarrierIdentifier> carriers) { + try { + ITelephony service = getITelephony(); + if (service != null) { + return service.setAllowedCarriers(slotIndex, carriers); + } + } catch (RemoteException e) { + Log.e(TAG, "Error calling ITelephony#setAllowedCarriers", e); + } catch (NullPointerException e) { + Log.e(TAG, "Error calling ITelephony#setAllowedCarriers", e); + } + return -1; + } + + /** + * Get the allowed carrier list for slotIndex. + * Require system privileges. In the future we may add this to carrier APIs. + * + * <p>This method returns valid data on devices with {@link + * android.content.pm.PackageManager#FEATURE_TELEPHONY_CARRIERLOCK} enabled. + * + * @return List of {@link android.telephony.CarrierIdentifier}; empty list + * means all carriers are allowed. + * @hide + */ + @SystemApi + @RequiresPermission(android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE) + public List<CarrierIdentifier> getAllowedCarriers(int slotIndex) { + try { + ITelephony service = getITelephony(); + if (service != null) { + return service.getAllowedCarriers(slotIndex); + } + } catch (RemoteException e) { + Log.e(TAG, "Error calling ITelephony#getAllowedCarriers", e); + } catch (NullPointerException e) { + Log.e(TAG, "Error calling ITelephony#getAllowedCarriers", e); + } + return new ArrayList<CarrierIdentifier>(0); + } + + /** + * Action set from carrier signalling broadcast receivers to enable/disable metered apns + * Permissions android.Manifest.permission.MODIFY_PHONE_STATE is required + * @param subId the subscription ID that this action applies to. + * @param enabled control enable or disable metered apns. + * @hide + */ + public void carrierActionSetMeteredApnsEnabled(int subId, boolean enabled) { + try { + ITelephony service = getITelephony(); + if (service != null) { + service.carrierActionSetMeteredApnsEnabled(subId, enabled); + } + } catch (RemoteException e) { + Log.e(TAG, "Error calling ITelephony#carrierActionSetMeteredApnsEnabled", e); + } + } + + /** + * Action set from carrier signalling broadcast receivers to enable/disable radio + * Permissions android.Manifest.permission.MODIFY_PHONE_STATE is required + * @param subId the subscription ID that this action applies to. + * @param enabled control enable or disable radio. + * @hide + */ + public void carrierActionSetRadioEnabled(int subId, boolean enabled) { + try { + ITelephony service = getITelephony(); + if (service != null) { + service.carrierActionSetRadioEnabled(subId, enabled); + } + } catch (RemoteException e) { + Log.e(TAG, "Error calling ITelephony#carrierActionSetRadioEnabled", e); + } + } + + /** + * Action set from carrier signalling broadcast receivers to start/stop reporting default + * network available events + * Permissions android.Manifest.permission.MODIFY_PHONE_STATE is required + * @param subId the subscription ID that this action applies to. + * @param report control start/stop reporting network status. + * @hide + */ + public void carrierActionReportDefaultNetworkStatus(int subId, boolean report) { + try { + ITelephony service = getITelephony(); + if (service != null) { + service.carrierActionReportDefaultNetworkStatus(subId, report); + } + } catch (RemoteException e) { + Log.e(TAG, "Error calling ITelephony#carrierActionReportDefaultNetworkStatus", e); + } + } + + /** + * Get aggregated video call data usage since boot. + * Permissions android.Manifest.permission.READ_NETWORK_USAGE_HISTORY is required. + * + * @param how one of the NetworkStats.STATS_PER_* constants depending on whether the request is + * for data usage per uid or overall usage. + * @return Snapshot of video call data usage + * @hide + */ + public NetworkStats getVtDataUsage(int how) { + boolean perUidStats = (how == NetworkStats.STATS_PER_UID); + try { + ITelephony service = getITelephony(); + if (service != null) { + return service.getVtDataUsage(getSubId(), perUidStats); + } + } catch (RemoteException e) { + Log.e(TAG, "Error calling ITelephony#getVtDataUsage", e); + } + return null; + } + + /** + * Policy control of data connection. Usually used when data limit is passed. + * @param enabled True if enabling the data, otherwise disabling. + * @param subId sub id + * @hide + */ + public void setPolicyDataEnabled(boolean enabled, int subId) { + try { + ITelephony service = getITelephony(); + if (service != null) { + service.setPolicyDataEnabled(enabled, subId); + } + } catch (RemoteException e) { + Log.e(TAG, "Error calling ITelephony#setPolicyDataEnabled", e); + } + } + + /** + * Get Client request stats which will contain statistical information + * on each request made by client. + * Callers require either READ_PRIVILEGED_PHONE_STATE or + * READ_PHONE_STATE to retrieve the information. + * @param subId sub id + * @return List of Client Request Stats + * @hide + */ + public List<ClientRequestStats> getClientRequestStats(int subId) { + try { + ITelephony service = getITelephony(); + if (service != null) { + return service.getClientRequestStats(getOpPackageName(), subId); + } + } catch (RemoteException e) { + Log.e(TAG, "Error calling ITelephony#getClientRequestStats", e); + } + + return null; + } + + /** + * Check if phone is in emergency callback mode + * @return true if phone is in emergency callback mode + * @hide + */ + public boolean getEmergencyCallbackMode() { + return getEmergencyCallbackMode(getSubId()); + } + + /** + * Check if phone is in emergency callback mode + * @return true if phone is in emergency callback mode + * @param subId the subscription ID that this action applies to. + * @hide + */ + public boolean getEmergencyCallbackMode(int subId) { + try { + ITelephony telephony = getITelephony(); + if (telephony == null) { + return false; + } + return telephony.getEmergencyCallbackMode(subId); + } catch (RemoteException e) { + Log.e(TAG, "Error calling ITelephony#getEmergencyCallbackMode", e); + } + return false; + } + + /** + * Get the most recently available signal strength information. + * + * Get the most recent SignalStrength information reported by the modem. Due + * to power saving this information may not always be current. + * @return the most recent cached signal strength info from the modem + */ + @Nullable + public SignalStrength getSignalStrength() { + try { + ITelephony service = getITelephony(); + if (service != null) { + return service.getSignalStrength(getSubId()); + } + } catch (RemoteException e) { + Log.e(TAG, "Error calling ITelephony#getSignalStrength", e); + } + return null; + } +} diff --git a/android/telephony/TelephonyScanManager.java b/android/telephony/TelephonyScanManager.java new file mode 100644 index 00000000..92a21b6f --- /dev/null +++ b/android/telephony/TelephonyScanManager.java @@ -0,0 +1,198 @@ +/* + * Copyright (C) 2017 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.telephony; + +import static com.android.internal.util.Preconditions.checkNotNull; + +import android.content.Context; +import android.os.Binder; +import android.os.Bundle; +import android.os.Handler; +import android.os.HandlerThread; +import android.os.Looper; +import android.os.Message; +import android.os.Messenger; +import android.os.Parcelable; +import android.os.RemoteException; +import android.os.ServiceManager; +import android.util.Log; +import android.util.SparseArray; +import java.util.Arrays; +import java.util.List; + +import com.android.internal.telephony.ITelephony; + +/** + * Manages the radio access network scan requests and callbacks. + * @hide + */ +public final class TelephonyScanManager { + + private static final String TAG = "TelephonyScanManager"; + + /** @hide */ + public static final String SCAN_RESULT_KEY = "scanResult"; + + /** @hide */ + public static final int CALLBACK_SCAN_RESULTS = 1; + /** @hide */ + public static final int CALLBACK_SCAN_ERROR = 2; + /** @hide */ + public static final int CALLBACK_SCAN_COMPLETE = 3; + + /** + * The caller of {@link #requestNetworkScan(NetworkScanRequest, NetworkScanCallback)} should + * implement and provide this callback so that the scan results or errors can be returned. + */ + public static abstract class NetworkScanCallback { + /** Returns the scan results to the user, this callback will be called multiple times. */ + public void onResults(List<CellInfo> results) {} + + /** + * Informs the user that the scan has stopped. + * + * This callback will be called when the scan is finished or cancelled by the user. + * The related NetworkScanRequest will be deleted after this callback. + */ + public void onComplete() {} + + /** + * Informs the user that there is some error about the scan. + * + * This callback will be called whenever there is any error about the scan, but the scan + * won't stop unless the onComplete() callback is called. + */ + public void onError(int error) {} + } + + private static class NetworkScanInfo { + private final NetworkScanRequest mRequest; + private final NetworkScanCallback mCallback; + + NetworkScanInfo(NetworkScanRequest request, NetworkScanCallback callback) { + mRequest = request; + mCallback = callback; + } + } + + private final Looper mLooper; + private final Messenger mMessenger; + private SparseArray<NetworkScanInfo> mScanInfo = new SparseArray<NetworkScanInfo>(); + + public TelephonyScanManager() { + HandlerThread thread = new HandlerThread(TAG); + thread.start(); + mLooper = thread.getLooper(); + mMessenger = new Messenger(new Handler(mLooper) { + @Override + public void handleMessage(Message message) { + checkNotNull(message, "message cannot be null"); + NetworkScanInfo nsi; + synchronized (mScanInfo) { + nsi = mScanInfo.get(message.arg2); + } + if (nsi == null) { + throw new RuntimeException( + "Failed to find NetworkScanInfo with id " + message.arg2); + } + NetworkScanCallback callback = nsi.mCallback; + if (callback == null) { + throw new RuntimeException( + "Failed to find NetworkScanCallback with id " + message.arg2); + } + + switch (message.what) { + case CALLBACK_SCAN_RESULTS: + try { + final Bundle b = message.getData(); + final Parcelable[] parcelables = b.getParcelableArray(SCAN_RESULT_KEY); + CellInfo[] ci = new CellInfo[parcelables.length]; + for (int i = 0; i < parcelables.length; i++) { + ci[i] = (CellInfo) parcelables[i]; + } + callback.onResults((List<CellInfo>) Arrays.asList(ci)); + } catch (Exception e) { + Rlog.e(TAG, "Exception in networkscan callback onResults", e); + } + break; + case CALLBACK_SCAN_ERROR: + try { + callback.onError(message.arg1); + } catch (Exception e) { + Rlog.e(TAG, "Exception in networkscan callback onError", e); + } + break; + case CALLBACK_SCAN_COMPLETE: + try { + callback.onComplete(); + mScanInfo.remove(message.arg2); + } catch (Exception e) { + Rlog.e(TAG, "Exception in networkscan callback onComplete", e); + } + break; + default: + Rlog.e(TAG, "Unhandled message " + Integer.toHexString(message.what)); + break; + } + } + }); + } + + /** + * Request a network scan. + * + * This method is asynchronous, so the network scan results will be returned by callback. + * The returned NetworkScan will contain a callback method which can be used to stop the scan. + * + * <p> + * Requires Permission: + * {@link android.Manifest.permission#MODIFY_PHONE_STATE MODIFY_PHONE_STATE} + * Or the calling app has carrier privileges. @see #hasCarrierPrivileges + * + * @param request Contains all the RAT with bands/channels that need to be scanned. + * @param callback Returns network scan results or errors. + * @return A NetworkScan obj which contains a callback which can stop the scan. + * @hide + */ + public NetworkScan requestNetworkScan(int subId, + NetworkScanRequest request, NetworkScanCallback callback) { + try { + ITelephony telephony = getITelephony(); + if (telephony != null) { + int scanId = telephony.requestNetworkScan(subId, request, mMessenger, new Binder()); + saveScanInfo(scanId, request, callback); + return new NetworkScan(scanId, subId); + } + } catch (RemoteException ex) { + Rlog.e(TAG, "requestNetworkScan RemoteException", ex); + } catch (NullPointerException ex) { + Rlog.e(TAG, "requestNetworkScan NPE", ex); + } + return null; + } + + private void saveScanInfo(int id, NetworkScanRequest request, NetworkScanCallback callback) { + synchronized (mScanInfo) { + mScanInfo.put(id, new NetworkScanInfo(request, callback)); + } + } + + private ITelephony getITelephony() { + return ITelephony.Stub.asInterface( + ServiceManager.getService(Context.TELEPHONY_SERVICE)); + } +} diff --git a/android/telephony/UiccAccessRule.java b/android/telephony/UiccAccessRule.java new file mode 100644 index 00000000..e42a7583 --- /dev/null +++ b/android/telephony/UiccAccessRule.java @@ -0,0 +1,230 @@ +/* + * Copyright (C) 2017 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.telephony; + +import android.annotation.Nullable; +import android.content.pm.PackageInfo; +import android.content.pm.Signature; +import android.os.Parcel; +import android.os.Parcelable; +import android.text.TextUtils; + +import com.android.internal.telephony.uicc.IccUtils; + +import java.io.ByteArrayInputStream; +import java.io.ByteArrayOutputStream; +import java.io.DataInputStream; +import java.io.DataOutputStream; +import java.io.IOException; +import java.security.MessageDigest; +import java.security.NoSuchAlgorithmException; +import java.util.Arrays; + +/** + * Describes a single UICC access rule according to the GlobalPlatform Secure Element Access Control + * specification. + * + * @hide + * + * TODO(b/35851809): Make this a SystemApi. + */ +public final class UiccAccessRule implements Parcelable { + private static final String TAG = "UiccAccessRule"; + + private static final int ENCODING_VERSION = 1; + + public static final Creator<UiccAccessRule> CREATOR = new Creator<UiccAccessRule>() { + @Override + public UiccAccessRule createFromParcel(Parcel in) { + return new UiccAccessRule(in); + } + + @Override + public UiccAccessRule[] newArray(int size) { + return new UiccAccessRule[size]; + } + }; + + /** + * Encode these access rules as a byte array which can be parsed with {@link #decodeRules}. + * @hide + */ + @Nullable + public static byte[] encodeRules(@Nullable UiccAccessRule[] accessRules) { + if (accessRules == null) { + return null; + } + try { + ByteArrayOutputStream baos = new ByteArrayOutputStream(); + DataOutputStream output = new DataOutputStream(baos); + output.writeInt(ENCODING_VERSION); + output.writeInt(accessRules.length); + for (UiccAccessRule accessRule : accessRules) { + output.writeInt(accessRule.mCertificateHash.length); + output.write(accessRule.mCertificateHash); + if (accessRule.mPackageName != null) { + output.writeBoolean(true); + output.writeUTF(accessRule.mPackageName); + } else { + output.writeBoolean(false); + } + output.writeLong(accessRule.mAccessType); + } + output.close(); + return baos.toByteArray(); + } catch (IOException e) { + throw new IllegalStateException( + "ByteArrayOutputStream should never lead to an IOException", e); + } + } + + /** + * Decodes a byte array generated with {@link #encodeRules}. + * @hide + */ + @Nullable + public static UiccAccessRule[] decodeRules(@Nullable byte[] encodedRules) { + if (encodedRules == null) { + return null; + } + ByteArrayInputStream bais = new ByteArrayInputStream(encodedRules); + try (DataInputStream input = new DataInputStream(bais)) { + input.readInt(); // version; currently ignored + int count = input.readInt(); + UiccAccessRule[] accessRules = new UiccAccessRule[count]; + for (int i = 0; i < count; i++) { + int certificateHashLength = input.readInt(); + byte[] certificateHash = new byte[certificateHashLength]; + input.readFully(certificateHash); + String packageName = input.readBoolean() ? input.readUTF() : null; + long accessType = input.readLong(); + accessRules[i] = new UiccAccessRule(certificateHash, packageName, accessType); + } + input.close(); + return accessRules; + } catch (IOException e) { + throw new IllegalStateException( + "ByteArrayInputStream should never lead to an IOException", e); + } + } + + private final byte[] mCertificateHash; + private final @Nullable String mPackageName; + // This bit is not currently used, but reserved for future use. + private final long mAccessType; + + public UiccAccessRule(byte[] certificateHash, @Nullable String packageName, long accessType) { + this.mCertificateHash = certificateHash; + this.mPackageName = packageName; + this.mAccessType = accessType; + } + + UiccAccessRule(Parcel in) { + mCertificateHash = in.createByteArray(); + mPackageName = in.readString(); + mAccessType = in.readLong(); + } + + @Override + public void writeToParcel(Parcel dest, int flags) { + dest.writeByteArray(mCertificateHash); + dest.writeString(mPackageName); + dest.writeLong(mAccessType); + } + + /** + * Return the package name this rule applies to. + * + * @return the package name, or null if this rule applies to any package signed with the given + * certificate. + */ + public @Nullable String getPackageName() { + return mPackageName; + } + + /** + * Returns the carrier privilege status associated with the given package. + * + * @param packageInfo package info fetched from + * {@link android.content.pm.PackageManager#getPackageInfo}. + * {@link android.content.pm.PackageManager#GET_SIGNATURES} must have been passed in. + * @return either {@link TelephonyManager#CARRIER_PRIVILEGE_STATUS_HAS_ACCESS} or + * {@link TelephonyManager#CARRIER_PRIVILEGE_STATUS_NO_ACCESS}. + */ + public int getCarrierPrivilegeStatus(PackageInfo packageInfo) { + if (packageInfo.signatures == null || packageInfo.signatures.length == 0) { + throw new IllegalArgumentException( + "Must use GET_SIGNATURES when looking up package info"); + } + + for (Signature sig : packageInfo.signatures) { + int accessStatus = getCarrierPrivilegeStatus(sig, packageInfo.packageName); + if (accessStatus != TelephonyManager.CARRIER_PRIVILEGE_STATUS_NO_ACCESS) { + return accessStatus; + } + } + + return TelephonyManager.CARRIER_PRIVILEGE_STATUS_NO_ACCESS; + } + + /** + * Returns the carrier privilege status for the given certificate and package name. + * + * @param signature The signature of the certificate. + * @param packageName name of the package. + * @return either {@link TelephonyManager#CARRIER_PRIVILEGE_STATUS_HAS_ACCESS} or + * {@link TelephonyManager#CARRIER_PRIVILEGE_STATUS_NO_ACCESS}. + */ + public int getCarrierPrivilegeStatus(Signature signature, String packageName) { + // SHA-1 is for backward compatible support only, strongly discouraged for new use. + byte[] certHash = getCertHash(signature, "SHA-1"); + byte[] certHash256 = getCertHash(signature, "SHA-256"); + if (matches(certHash, packageName) || matches(certHash256, packageName)) { + return TelephonyManager.CARRIER_PRIVILEGE_STATUS_HAS_ACCESS; + } + + return TelephonyManager.CARRIER_PRIVILEGE_STATUS_NO_ACCESS; + } + + private boolean matches(byte[] certHash, String packageName) { + return certHash != null && Arrays.equals(this.mCertificateHash, certHash) && + (TextUtils.isEmpty(this.mPackageName) || this.mPackageName.equals(packageName)); + } + + @Override + public String toString() { + return "cert: " + IccUtils.bytesToHexString(mCertificateHash) + " pkg: " + + mPackageName + " access: " + mAccessType; + } + + @Override + public int describeContents() { + return 0; + } + + /** + * Converts a Signature into a Certificate hash usable for comparison. + */ + private static byte[] getCertHash(Signature signature, String algo) { + try { + MessageDigest md = MessageDigest.getInstance(algo); + return md.digest(signature.toByteArray()); + } catch (NoSuchAlgorithmException ex) { + Rlog.e(TAG, "NoSuchAlgorithmException: " + ex); + } + return null; + } +} diff --git a/android/telephony/UssdResponse.java b/android/telephony/UssdResponse.java new file mode 100644 index 00000000..5df681d4 --- /dev/null +++ b/android/telephony/UssdResponse.java @@ -0,0 +1,80 @@ +/* + * Copyright (C) 2006 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.telephony; + +import android.os.Parcel; +import android.os.Parcelable; +import android.text.TextUtils; + +/** + * Represents the Ussd response, including + * the message and the return code. + * @hide + */ +public final class UssdResponse implements Parcelable { + private CharSequence mReturnMessage; + private String mUssdRequest; + + + /** + * Implement the Parcelable interface + */ + @Override + public void writeToParcel(Parcel dest, int flags) { + dest.writeString(mUssdRequest); + TextUtils.writeToParcel(mReturnMessage, dest, 0); + } + + public String getUssdRequest() { + return mUssdRequest; + } + + public CharSequence getReturnMessage() { + return mReturnMessage; + } + + /** + * Implement the Parcelable interface + */ + @Override + public int describeContents() { + return 0; + } + + /** + * * Initialize the object from the request and return message. + */ + public UssdResponse(String ussdRequest, CharSequence returnMessage) { + mUssdRequest = ussdRequest; + mReturnMessage = returnMessage; + } + + public static final Parcelable.Creator<UssdResponse> CREATOR = new Creator<UssdResponse>() { + + @Override + public UssdResponse createFromParcel(Parcel in) { + String request = in.readString(); + CharSequence message = TextUtils.CHAR_SEQUENCE_CREATOR.createFromParcel(in); + return new UssdResponse(request, message); + } + + @Override + public UssdResponse[] newArray(int size) { + return new UssdResponse[size]; + } + }; +} diff --git a/android/telephony/VisualVoicemailService.java b/android/telephony/VisualVoicemailService.java new file mode 100644 index 00000000..fe30eb7b --- /dev/null +++ b/android/telephony/VisualVoicemailService.java @@ -0,0 +1,296 @@ +/* + * 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.telephony; + +import android.annotation.MainThread; +import android.annotation.SdkConstant; +import android.annotation.SystemApi; +import android.app.PendingIntent; +import android.app.Service; +import android.content.Context; +import android.content.Intent; +import android.os.Bundle; +import android.os.Handler; +import android.os.IBinder; +import android.os.Message; +import android.os.Messenger; +import android.os.RemoteException; +import android.telecom.PhoneAccountHandle; +import android.telecom.TelecomManager; +import android.util.Log; + +/** + * This service is implemented by dialer apps that wishes to handle OMTP or similar visual + * voicemails. Telephony binds to this service when the cell service is first connected, a visual + * voicemail SMS has been received, or when a SIM has been removed. Telephony will only bind to the + * default dialer for such events (See {@link TelecomManager#getDefaultDialerPackage()}). The + * {@link android.service.carrier.CarrierMessagingService} precedes the VisualVoicemailService in + * the SMS filtering chain and may intercept the visual voicemail SMS before it reaches this + * service. + * <p> + * To extend this class, The service must be declared in the manifest file with + * the {@link android.Manifest.permission#BIND_VISUAL_VOICEMAIL_SERVICE} permission and include an + * intent filter with the {@link #SERVICE_INTERFACE} action. + * <p> + * Below is an example manifest registration for a {@code VisualVoicemailService}. + * <pre> + * {@code + * <service android:name="your.package.YourVisualVoicemailServiceImplementation" + * android:permission="android.permission.BIND_VISUAL_VOICEMAIL_SERVICE"> + * <intent-filter> + * <action android:name="android.telephony.VisualVoicemailService"/> + * </intent-filter> + * </service> + * } + * </pre> + */ +public abstract class VisualVoicemailService extends Service { + + private static final String TAG = "VvmService"; + + /** + * The {@link Intent} that must be declared as handled by the service. + */ + @SdkConstant(SdkConstant.SdkConstantType.SERVICE_ACTION) + public static final String SERVICE_INTERFACE = "android.telephony.VisualVoicemailService"; + + /** + * @hide + */ + public static final int MSG_ON_CELL_SERVICE_CONNECTED = 1; + /** + * @hide + */ + public static final int MSG_ON_SMS_RECEIVED = 2; + /** + * @hide + */ + public static final int MSG_ON_SIM_REMOVED = 3; + /** + * @hide + */ + public static final int MSG_TASK_ENDED = 4; + /** + * @hide + */ + public static final int MSG_TASK_STOPPED = 5; + + /** + * @hide + */ + public static final String DATA_PHONE_ACCOUNT_HANDLE = "data_phone_account_handle"; + /** + * @hide + */ + public static final String DATA_SMS = "data_sms"; + + /** + * Represents a visual voicemail event which needs to be handled. While the task is being + * processed telephony will hold a wakelock for the VisualVoicemailService. The service can + * unblock the main thread and pass the task to a worker thread. Once the task is finished, + * {@link VisualVoicemailTask#finish()} should be called to signal telephony to release the + * resources. Telephony will call {@link VisualVoicemailService#onStopped(VisualVoicemailTask)} + * when the task is going to be terminated before completion. + * + * @see #onCellServiceConnected(VisualVoicemailTask, PhoneAccountHandle) + * @see #onSmsReceived(VisualVoicemailTask, VisualVoicemailSms) + * @see #onSimRemoved(VisualVoicemailTask, PhoneAccountHandle) + * @see #onStopped(VisualVoicemailTask) + */ + public static class VisualVoicemailTask { + + private final int mTaskId; + private final Messenger mReplyTo; + + private VisualVoicemailTask(Messenger replyTo, int taskId) { + mTaskId = taskId; + mReplyTo = replyTo; + } + + /** + * Call to signal telephony the task has completed. Must be called for every task. + */ + public final void finish() { + Message message = Message.obtain(); + try { + message.what = MSG_TASK_ENDED; + message.arg1 = mTaskId; + mReplyTo.send(message); + } catch (RemoteException e) { + Log.e(TAG, + "Cannot send MSG_TASK_ENDED, remote handler no longer exist"); + } + } + + @Override + public boolean equals(Object obj) { + if (!(obj instanceof VisualVoicemailTask)) { + return false; + } + return mTaskId == ((VisualVoicemailTask) obj).mTaskId; + } + + @Override + public int hashCode() { + return mTaskId; + } + } + + /** + * Handles messages sent by telephony. + */ + private final Messenger mMessenger = new Messenger(new Handler() { + @Override + public void handleMessage(final Message msg) { + final PhoneAccountHandle handle = msg.getData() + .getParcelable(DATA_PHONE_ACCOUNT_HANDLE); + VisualVoicemailTask task = new VisualVoicemailTask(msg.replyTo, msg.arg1); + switch (msg.what) { + case MSG_ON_CELL_SERVICE_CONNECTED: + onCellServiceConnected(task, handle); + break; + case MSG_ON_SMS_RECEIVED: + VisualVoicemailSms sms = msg.getData().getParcelable(DATA_SMS); + onSmsReceived(task, sms); + break; + case MSG_ON_SIM_REMOVED: + onSimRemoved(task, handle); + break; + case MSG_TASK_STOPPED: + onStopped(task); + break; + default: + super.handleMessage(msg); + break; + } + } + }); + + @Override + public IBinder onBind(Intent intent) { + return mMessenger.getBinder(); + } + + /** + * Called when the cellular service is connected on a {@link PhoneAccountHandle} for the first + * time, or when the carrier config has changed. It will not be called when the signal is lost + * then restored. + * + * @param task The task representing this event. {@link VisualVoicemailTask#finish()} must be + * called when the task is completed. + * @param phoneAccountHandle The {@link PhoneAccountHandle} triggering this event. + */ + @MainThread + public abstract void onCellServiceConnected(VisualVoicemailTask task, + PhoneAccountHandle phoneAccountHandle); + + /** + * Called when a SMS matching the {@link VisualVoicemailSmsFilterSettings} set by + * {@link TelephonyManager#setVisualVoicemailSmsFilterSettings(VisualVoicemailSmsFilterSettings) + * } + * is received. + * + * @param task The task representing this event. {@link VisualVoicemailTask#finish()} must be + * called when the task is completed. + * @param sms The content of the received SMS. + */ + @MainThread + public abstract void onSmsReceived(VisualVoicemailTask task, + VisualVoicemailSms sms); + + /** + * Called when a SIM is removed. + * + * @param task The task representing this event. {@link VisualVoicemailTask#finish()} must be + * called when the task is completed. + * @param phoneAccountHandle The {@link PhoneAccountHandle} triggering this event. + */ + @MainThread + public abstract void onSimRemoved(VisualVoicemailTask task, + PhoneAccountHandle phoneAccountHandle); + + /** + * Called before the system is about to terminate a task. The service should persist any + * necessary data and call finish on the task immediately. + */ + @MainThread + public abstract void onStopped(VisualVoicemailTask task); + + /** + * Set the visual voicemail SMS filter settings for the VisualVoicemailService. + * {@link #onSmsReceived(VisualVoicemailTask, VisualVoicemailSms)} will be called when + * a SMS matching the settings is received. The caller should have + * {@link android.Manifest.permission#READ_PHONE_STATE READ_PHONE_STATE} and implements a + * VisualVoicemailService. + * <p> + * <p>Requires Permission: + * {@link android.Manifest.permission#READ_PHONE_STATE READ_PHONE_STATE} + * + * @param phoneAccountHandle The account to apply the settings to. + * @param settings The settings for the filter, or {@code null} to disable the filter. + * + * @hide + */ + @SystemApi + public static final void setSmsFilterSettings(Context context, + PhoneAccountHandle phoneAccountHandle, + VisualVoicemailSmsFilterSettings settings) { + TelephonyManager telephonyManager = context.getSystemService(TelephonyManager.class); + int subId = getSubId(context, phoneAccountHandle); + if (settings == null) { + telephonyManager.disableVisualVoicemailSmsFilter(subId); + } else { + telephonyManager.enableVisualVoicemailSmsFilter(subId, settings); + } + } + + /** + * Send a visual voicemail SMS. The caller must be the current default dialer. + * <p> + * <p>Requires Permission: + * {@link android.Manifest.permission#SEND_SMS SEND_SMS} + * + * @param phoneAccountHandle The account to send the SMS with. + * @param number The destination number. + * @param port The destination port for data SMS, or 0 for text SMS. + * @param text The message content. For data sms, it will be encoded as a UTF-8 byte stream. + * @param sentIntent The sent intent passed to the {@link SmsManager} + * + * @throws SecurityException if the caller is not the current default dialer + * + * @see SmsManager#sendDataMessage(String, String, short, byte[], PendingIntent, PendingIntent) + * @see SmsManager#sendTextMessage(String, String, String, PendingIntent, PendingIntent) + * + * @hide + */ + @SystemApi + public static final void sendVisualVoicemailSms(Context context, + PhoneAccountHandle phoneAccountHandle, String number, + short port, String text, PendingIntent sentIntent) { + TelephonyManager telephonyManager = context.getSystemService(TelephonyManager.class); + telephonyManager.sendVisualVoicemailSmsForSubscriber(getSubId(context, phoneAccountHandle), + number, port, text, sentIntent); + } + + private static int getSubId(Context context, PhoneAccountHandle phoneAccountHandle) { + TelephonyManager telephonyManager = context.getSystemService(TelephonyManager.class); + TelecomManager telecomManager = context.getSystemService(TelecomManager.class); + return telephonyManager + .getSubIdForPhoneAccount(telecomManager.getPhoneAccount(phoneAccountHandle)); + } + +} diff --git a/android/telephony/VisualVoicemailSms.java b/android/telephony/VisualVoicemailSms.java new file mode 100644 index 00000000..1e6ea4bf --- /dev/null +++ b/android/telephony/VisualVoicemailSms.java @@ -0,0 +1,149 @@ +/* + * 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.telephony; + +import android.annotation.Nullable; +import android.os.Bundle; +import android.os.Parcel; +import android.os.Parcelable; +import android.telecom.PhoneAccountHandle; +import android.telephony.VisualVoicemailService.VisualVoicemailTask; + +/** + * Represents the content of a visual voicemail SMS. If a incoming SMS matches the {@link + * VisualVoicemailSmsFilterSettings} set by the default dialer, {@link + * VisualVoicemailService#onSmsReceived(VisualVoicemailTask, VisualVoicemailSms)} will be called. + */ +public final class VisualVoicemailSms implements Parcelable { + + private final PhoneAccountHandle mPhoneAccountHandle; + @Nullable + private final String mPrefix; + + @Nullable + private final Bundle mFields; + + private final String mMessageBody; + + VisualVoicemailSms(Builder builder) { + mPhoneAccountHandle = builder.mPhoneAccountHandle; + mPrefix = builder.mPrefix; + mFields = builder.mFields; + mMessageBody = builder.mMessageBody; + } + + /** + * The {@link PhoneAccountHandle} that received the SMS. + */ + public PhoneAccountHandle getPhoneAccountHandle() { + return mPhoneAccountHandle; + } + + /** + * The event type of the SMS or {@code null} if the framework cannot parse the SMS as voicemail + * but the carrier pattern indicates it is. Common values are "SYNC" or "STATUS". + */ + public String getPrefix() { + return mPrefix; + } + + /** + * The key-value pairs sent by the SMS, or {@code null} if the framework cannot parse the SMS as + * voicemail but the carrier pattern indicates it is. The interpretation of the fields is + * carrier dependent. + */ + public Bundle getFields() { + return mFields; + } + + /** + * Raw message body of the received SMS. + */ + public String getMessageBody() { + return mMessageBody; + } + + /** + * Builder for the {@link VisualVoicemailSms}. Internal use only. + * + * @hide + */ + public static class Builder { + + private PhoneAccountHandle mPhoneAccountHandle; + private String mPrefix; + private Bundle mFields; + private String mMessageBody; + + public VisualVoicemailSms build() { + return new VisualVoicemailSms(this); + } + + public Builder setPhoneAccountHandle(PhoneAccountHandle phoneAccountHandle) { + this.mPhoneAccountHandle = phoneAccountHandle; + return this; + } + + public Builder setPrefix(String prefix) { + this.mPrefix = prefix; + return this; + } + + public Builder setFields(Bundle fields) { + this.mFields = fields; + return this; + } + + public Builder setMessageBody(String messageBody) { + this.mMessageBody = messageBody; + return this; + } + + } + + + public static final Creator<VisualVoicemailSms> CREATOR = + new Creator<VisualVoicemailSms>() { + @Override + public VisualVoicemailSms createFromParcel(Parcel in) { + return new Builder() + .setPhoneAccountHandle((PhoneAccountHandle) in.readParcelable(null)) + .setPrefix(in.readString()) + .setFields(in.readBundle()) + .setMessageBody(in.readString()) + .build(); + } + + @Override + public VisualVoicemailSms[] newArray(int size) { + return new VisualVoicemailSms[size]; + } + }; + + @Override + public int describeContents() { + return 0; + } + + @Override + public void writeToParcel(Parcel dest, int flags) { + dest.writeParcelable(getPhoneAccountHandle(), flags); + dest.writeString(getPrefix()); + dest.writeBundle(getFields()); + dest.writeString(getMessageBody()); + } +} diff --git a/android/telephony/VisualVoicemailSmsFilterSettings.java b/android/telephony/VisualVoicemailSmsFilterSettings.java new file mode 100644 index 00000000..8ed96a3a --- /dev/null +++ b/android/telephony/VisualVoicemailSmsFilterSettings.java @@ -0,0 +1,188 @@ +/* + * 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.telephony; + +import android.content.Context; +import android.os.Parcel; +import android.os.Parcelable; + +import android.telecom.PhoneAccountHandle; +import android.telephony.VisualVoicemailService.VisualVoicemailTask; +import java.util.Collections; +import java.util.List; + +/** + * Class to represent various settings for the visual voicemail SMS filter. When the filter is + * enabled, incoming SMS matching the generalized OMTP format: + * + * <p>[clientPrefix]:[prefix]:([key]=[value];)* + * + * <p>will be regarded as a visual voicemail SMS, and removed before reaching the SMS provider. The + * {@link VisualVoicemailService} in the current default dialer will be bound and + * {@link VisualVoicemailService#onSmsReceived(VisualVoicemailTask, VisualVoicemailSms)} + * will called with the information extracted from the SMS. + * + * <p>Use {@link android.telephony.VisualVoicemailSmsFilterSettings.Builder} to construct this + * class. + * + * @see TelephonyManager#setVisualVoicemailSmsFilterSettings(VisualVoicemailSmsFilterSettings) + */ +public final class VisualVoicemailSmsFilterSettings implements Parcelable { + + + /** + * The visual voicemail SMS message does not have to be a data SMS, and can be directed to any + * port. + */ + public static final int DESTINATION_PORT_ANY = -1; + + /** + * The visual voicemail SMS message can be directed to any port, but must be a data SMS. + */ + public static final int DESTINATION_PORT_DATA_SMS = -2; + + /** + * @hide + */ + public static final String DEFAULT_CLIENT_PREFIX = "//VVM"; + /** + * @hide + */ + public static final List<String> DEFAULT_ORIGINATING_NUMBERS = Collections.emptyList(); + /** + * @hide + */ + public static final int DEFAULT_DESTINATION_PORT = DESTINATION_PORT_ANY; + + /** + * Builder class for {@link VisualVoicemailSmsFilterSettings} objects. + */ + public static class Builder { + + private String mClientPrefix = DEFAULT_CLIENT_PREFIX; + private List<String> mOriginatingNumbers = DEFAULT_ORIGINATING_NUMBERS; + private int mDestinationPort = DEFAULT_DESTINATION_PORT; + + public VisualVoicemailSmsFilterSettings build() { + return new VisualVoicemailSmsFilterSettings(this); + } + + /** + * Sets the client prefix for the visual voicemail SMS filter. The client prefix will appear + * at the start of a visual voicemail SMS message, followed by a colon(:). + */ + public Builder setClientPrefix(String clientPrefix) { + if (clientPrefix == null) { + throw new IllegalArgumentException("Client prefix cannot be null"); + } + mClientPrefix = clientPrefix; + return this; + } + + /** + * Sets the originating number whitelist for the visual voicemail SMS filter. If the list is + * not null only the SMS messages from a number in the list can be considered as a visual + * voicemail SMS. Otherwise, messages from any address will be considered. + */ + public Builder setOriginatingNumbers(List<String> originatingNumbers) { + if (originatingNumbers == null) { + throw new IllegalArgumentException("Originating numbers cannot be null"); + } + mOriginatingNumbers = originatingNumbers; + return this; + } + + /** + * Sets the destination port for the visual voicemail SMS filter. + * + * @param destinationPort The destination port, or {@link #DESTINATION_PORT_ANY}, or {@link + * #DESTINATION_PORT_DATA_SMS} + */ + public Builder setDestinationPort(int destinationPort) { + mDestinationPort = destinationPort; + return this; + } + + } + + /** + * The client prefix for the visual voicemail SMS filter. The client prefix will appear at the + * start of a visual voicemail SMS message, followed by a colon(:). + */ + public final String clientPrefix; + + /** + * The originating number whitelist for the visual voicemail SMS filter of a phone account. If + * the list is not null only the SMS messages from a number in the list can be considered as a + * visual voicemail SMS. Otherwise, messages from any address will be considered. + */ + public final List<String> originatingNumbers; + + /** + * The destination port for the visual voicemail SMS filter, or {@link #DESTINATION_PORT_ANY}, + * or {@link #DESTINATION_PORT_DATA_SMS} + */ + public final int destinationPort; + + /** + * Use {@link Builder} to construct + */ + private VisualVoicemailSmsFilterSettings(Builder builder) { + clientPrefix = builder.mClientPrefix; + originatingNumbers = builder.mOriginatingNumbers; + destinationPort = builder.mDestinationPort; + } + + public static final Creator<VisualVoicemailSmsFilterSettings> CREATOR = + new Creator<VisualVoicemailSmsFilterSettings>() { + @Override + public VisualVoicemailSmsFilterSettings createFromParcel(Parcel in) { + Builder builder = new Builder(); + builder.setClientPrefix(in.readString()); + builder.setOriginatingNumbers(in.createStringArrayList()); + builder.setDestinationPort(in.readInt()); + + return builder.build(); + } + + @Override + public VisualVoicemailSmsFilterSettings[] newArray(int size) { + return new VisualVoicemailSmsFilterSettings[size]; + } + }; + + @Override + public int describeContents() { + return 0; + } + + @Override + public void writeToParcel(Parcel dest, int flags) { + dest.writeString(clientPrefix); + dest.writeStringList(originatingNumbers); + dest.writeInt(destinationPort); + } + + @Override + public String toString(){ + return "[VisualVoicemailSmsFilterSettings " + + "clientPrefix=" + clientPrefix + + ", originatingNumbers=" + originatingNumbers + + ", destinationPort=" + destinationPort + + "]"; + } + +} diff --git a/android/telephony/VoLteServiceState.java b/android/telephony/VoLteServiceState.java new file mode 100644 index 00000000..afef601b --- /dev/null +++ b/android/telephony/VoLteServiceState.java @@ -0,0 +1,229 @@ +/* + * Copyright (C) 2014 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.telephony; + +import android.os.Bundle; +import android.os.Parcel; +import android.os.Parcelable; +import android.telephony.Rlog; + +/** + * Contains LTE network state related information. + * + * @hide + */ +public final class VoLteServiceState implements Parcelable { + + private static final String LOG_TAG = "VoLteServiceState"; + private static final boolean DBG = false; + + //Use int max, as -1 is a valid value in signal strength + public static final int INVALID = 0x7FFFFFFF; + + public static final int NOT_SUPPORTED = 0; + public static final int SUPPORTED = 1; + + // Single Radio Voice Call Continuity(SRVCC) progress state + public static final int HANDOVER_STARTED = 0; + public static final int HANDOVER_COMPLETED = 1; + public static final int HANDOVER_FAILED = 2; + public static final int HANDOVER_CANCELED = 3; + + private int mSrvccState; + + /** + * Create a new VoLteServiceState from a intent notifier Bundle + * + * This method is used by PhoneStateIntentReceiver and maybe by + * external applications. + * + * @param m Bundle from intent notifier + * @return newly created VoLteServiceState + * + * @hide + */ + public static VoLteServiceState newFromBundle(Bundle m) { + VoLteServiceState ret; + ret = new VoLteServiceState(); + ret.setFromNotifierBundle(m); + return ret; + } + + /** + * Empty constructor + * + * @hide + */ + public VoLteServiceState() { + initialize(); + } + + /** + * Constructor + * + * @hide + */ + public VoLteServiceState(int srvccState) { + initialize(); + + mSrvccState = srvccState; + } + + /** + * Copy constructors + * + * @param s Source VoLteServiceState + * + * @hide + */ + public VoLteServiceState(VoLteServiceState s) { + copyFrom(s); + } + + /** + * Initialize values to defaults. + * + * @hide + */ + private void initialize() { + mSrvccState = INVALID; + } + + /** + * @hide + */ + protected void copyFrom(VoLteServiceState s) { + mSrvccState = s.mSrvccState; + } + + /** + * Construct a VoLteServiceState object from the given parcel. + * + * @hide + */ + public VoLteServiceState(Parcel in) { + if (DBG) log("Size of VoLteServiceState parcel:" + in.dataSize()); + + mSrvccState = in.readInt(); + } + + /** + * {@link Parcelable#writeToParcel} + */ + public void writeToParcel(Parcel out, int flags) { + out.writeInt(mSrvccState); + } + + /** + * {@link Parcelable#describeContents} + */ + public int describeContents() { + return 0; + } + + /** + * {@link Parcelable.Creator} + * + * @hide + */ + public static final Parcelable.Creator<VoLteServiceState> CREATOR = new Parcelable.Creator() { + public VoLteServiceState createFromParcel(Parcel in) { + return new VoLteServiceState(in); + } + + public VoLteServiceState[] newArray(int size) { + return new VoLteServiceState[size]; + } + }; + + /** + * Validate the individual fields as per the range + * specified in ril.h + * Set to invalid any field that is not in the valid range + * + * @return + * Valid values for all fields + * @hide + */ + public void validateInput() { + } + + public int hashCode() { + int primeNum = 31; + return ((mSrvccState * primeNum)); + } + + /** + * @return true if the LTE network states are the same + */ + @Override + public boolean equals (Object o) { + VoLteServiceState s; + + try { + s = (VoLteServiceState) o; + } catch (ClassCastException ex) { + return false; + } + + if (o == null) { + return false; + } + + return (mSrvccState == s.mSrvccState); + } + + /** + * @return string representation. + */ + @Override + public String toString() { + return ("VoLteServiceState:" + + " " + mSrvccState); + } + + /** + * Set VoLteServiceState based on intent notifier map + * + * @param m intent notifier map + * @hide + */ + private void setFromNotifierBundle(Bundle m) { + mSrvccState = m.getInt("mSrvccState"); + } + + /** + * Set intent notifier Bundle based on VoLteServiceState + * + * @param m intent notifier Bundle + * @hide + */ + public void fillInNotifierBundle(Bundle m) { + m.putInt("mSrvccState", mSrvccState); + } + + public int getSrvccState() { + return mSrvccState; + } + + /** + * log + */ + private static void log(String s) { + Rlog.w(LOG_TAG, s); + } +} diff --git a/android/telephony/cdma/CdmaCellLocation.java b/android/telephony/cdma/CdmaCellLocation.java new file mode 100644 index 00000000..7c10569f --- /dev/null +++ b/android/telephony/cdma/CdmaCellLocation.java @@ -0,0 +1,250 @@ +/* + * Copyright (C) 2006 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.telephony.cdma; + +import android.os.Bundle; +import android.telephony.CellLocation; + +/** + * Represents the cell location on a CDMA phone. + */ +public class CdmaCellLocation extends CellLocation { + private int mBaseStationId = -1; + + /** + * @hide + */ + public final static int INVALID_LAT_LONG = Integer.MAX_VALUE; + + /** + * Latitude is a decimal number as specified in 3GPP2 C.S0005-A v6.0. + * It is represented in units of 0.25 seconds and ranges from -1296000 + * to 1296000, both values inclusive (corresponding to a range of -90 + * to +90 degrees). Integer.MAX_VALUE is considered invalid value. + */ + private int mBaseStationLatitude = INVALID_LAT_LONG; + + /** + * Longitude is a decimal number as specified in 3GPP2 C.S0005-A v6.0. + * It is represented in units of 0.25 seconds and ranges from -2592000 + * to 2592000, both values inclusive (corresponding to a range of -180 + * to +180 degrees). Integer.MAX_VALUE is considered invalid value. + */ + private int mBaseStationLongitude = INVALID_LAT_LONG; + + private int mSystemId = -1; + private int mNetworkId = -1; + + /** + * Empty constructor. + * Initializes the BID, SID, NID and base station latitude and longitude + * to invalid values. + */ + public CdmaCellLocation() { + this.mBaseStationId = -1; + this.mBaseStationLatitude = INVALID_LAT_LONG; + this.mBaseStationLongitude = INVALID_LAT_LONG; + this.mSystemId = -1; + this.mNetworkId = -1; + } + + /** + * Initialize the object from a bundle. + */ + public CdmaCellLocation(Bundle bundle) { + this.mBaseStationId = bundle.getInt("baseStationId", mBaseStationId); + this.mBaseStationLatitude = bundle.getInt("baseStationLatitude", mBaseStationLatitude); + this.mBaseStationLongitude = bundle.getInt("baseStationLongitude", mBaseStationLongitude); + this.mSystemId = bundle.getInt("systemId", mSystemId); + this.mNetworkId = bundle.getInt("networkId", mNetworkId); + } + + /** + * @return cdma base station identification number, -1 if unknown + */ + public int getBaseStationId() { + return this.mBaseStationId; + } + + /** + * Latitude is a decimal number as specified in 3GPP2 C.S0005-A v6.0. + * (http://www.3gpp2.org/public_html/specs/C.S0005-A_v6.0.pdf) + * It is represented in units of 0.25 seconds and ranges from -1296000 + * to 1296000, both values inclusive (corresponding to a range of -90 + * to +90 degrees). Integer.MAX_VALUE is considered invalid value. + * + * @return cdma base station latitude in units of 0.25 seconds, Integer.MAX_VALUE if unknown + */ + public int getBaseStationLatitude() { + return this.mBaseStationLatitude; + } + + /** + * Longitude is a decimal number as specified in 3GPP2 C.S0005-A v6.0. + * (http://www.3gpp2.org/public_html/specs/C.S0005-A_v6.0.pdf) + * It is represented in units of 0.25 seconds and ranges from -2592000 + * to 2592000, both values inclusive (corresponding to a range of -180 + * to +180 degrees). Integer.MAX_VALUE is considered invalid value. + * + * @return cdma base station longitude in units of 0.25 seconds, Integer.MAX_VALUE if unknown + */ + public int getBaseStationLongitude() { + return this.mBaseStationLongitude; + } + + /** + * @return cdma system identification number, -1 if unknown + */ + public int getSystemId() { + return this.mSystemId; + } + + /** + * @return cdma network identification number, -1 if unknown + */ + public int getNetworkId() { + return this.mNetworkId; + } + + /** + * Invalidate this object. The cell location data is set to invalid values. + */ + @Override + public void setStateInvalid() { + this.mBaseStationId = -1; + this.mBaseStationLatitude = INVALID_LAT_LONG; + this.mBaseStationLongitude = INVALID_LAT_LONG; + this.mSystemId = -1; + this.mNetworkId = -1; + } + + /** + * Set the cell location data. + */ + public void setCellLocationData(int baseStationId, int baseStationLatitude, + int baseStationLongitude) { + // The following values have to be written in the correct sequence + this.mBaseStationId = baseStationId; + this.mBaseStationLatitude = baseStationLatitude; //values[2]; + this.mBaseStationLongitude = baseStationLongitude; //values[3]; + } + + /** + * Set the cell location data. + */ + public void setCellLocationData(int baseStationId, int baseStationLatitude, + int baseStationLongitude, int systemId, int networkId) { + // The following values have to be written in the correct sequence + this.mBaseStationId = baseStationId; + this.mBaseStationLatitude = baseStationLatitude; //values[2]; + this.mBaseStationLongitude = baseStationLongitude; //values[3]; + this.mSystemId = systemId; + this.mNetworkId = networkId; + } + + @Override + public int hashCode() { + return this.mBaseStationId ^ this.mBaseStationLatitude ^ this.mBaseStationLongitude + ^ this.mSystemId ^ this.mNetworkId; + } + + @Override + public boolean equals(Object o) { + CdmaCellLocation s; + + try { + s = (CdmaCellLocation)o; + } catch (ClassCastException ex) { + return false; + } + + if (o == null) { + return false; + } + + return (equalsHandlesNulls(this.mBaseStationId, s.mBaseStationId) && + equalsHandlesNulls(this.mBaseStationLatitude, s.mBaseStationLatitude) && + equalsHandlesNulls(this.mBaseStationLongitude, s.mBaseStationLongitude) && + equalsHandlesNulls(this.mSystemId, s.mSystemId) && + equalsHandlesNulls(this.mNetworkId, s.mNetworkId) + ); + } + + @Override + public String toString() { + return "[" + this.mBaseStationId + "," + + this.mBaseStationLatitude + "," + + this.mBaseStationLongitude + "," + + this.mSystemId + "," + + this.mNetworkId + "]"; + } + + /** + * Test whether two objects hold the same data values or both are null + * + * @param a first obj + * @param b second obj + * @return true if two objects equal or both are null + */ + private static boolean equalsHandlesNulls(Object a, Object b) { + return (a == null) ? (b == null) : a.equals (b); + } + + /** + * Fill the cell location data into the intent notifier Bundle based on service state + * + * @param bundleToFill intent notifier Bundle + */ + public void fillInNotifierBundle(Bundle bundleToFill) { + bundleToFill.putInt("baseStationId", this.mBaseStationId); + bundleToFill.putInt("baseStationLatitude", this.mBaseStationLatitude); + bundleToFill.putInt("baseStationLongitude", this.mBaseStationLongitude); + bundleToFill.putInt("systemId", this.mSystemId); + bundleToFill.putInt("networkId", this.mNetworkId); + } + + /** + * @hide + */ + public boolean isEmpty() { + return (this.mBaseStationId == -1 && + this.mBaseStationLatitude == INVALID_LAT_LONG && + this.mBaseStationLongitude == INVALID_LAT_LONG && + this.mSystemId == -1 && + this.mNetworkId == -1); + } + + /** + * Converts latitude or longitude from 0.25 seconds (as defined in the + * 3GPP2 C.S0005-A v6.0 standard) to decimal degrees + * + * @param quartSec latitude or longitude in 0.25 seconds units + * @return latitude or longitude in decimal degrees units + * @throws IllegalArgumentException if value is less than -2592000, + * greater than 2592000, or is not a number. + */ + public static double convertQuartSecToDecDegrees(int quartSec) { + if(Double.isNaN(quartSec) || quartSec < -2592000 || quartSec > 2592000){ + // Invalid value + throw new IllegalArgumentException("Invalid coordiante value:" + quartSec); + } + return ((double)quartSec) / (3600 * 4); + } + +} + + diff --git a/android/telephony/cdma/CdmaSmsCbProgramData.java b/android/telephony/cdma/CdmaSmsCbProgramData.java new file mode 100644 index 00000000..010ad2b0 --- /dev/null +++ b/android/telephony/cdma/CdmaSmsCbProgramData.java @@ -0,0 +1,210 @@ +/* + * Copyright (C) 2012 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.telephony.cdma; + +import android.os.Parcel; +import android.os.Parcelable; + +/** + * CDMA Service Category Program Data from SCPT teleservice SMS. + * The CellBroadcastReceiver app receives an Intent with action + * {@link android.provider.Telephony.Sms.Intents#SMS_SERVICE_CATEGORY_PROGRAM_DATA_RECEIVED_ACTION} + * containing an array of these objects to update its list of cell broadcast service categories + * to display. + * + * {@hide} + */ +public class CdmaSmsCbProgramData implements Parcelable { + + /** Delete the specified service category from the list of enabled categories. */ + public static final int OPERATION_DELETE_CATEGORY = 0; + + /** Add the specified service category to the list of enabled categories. */ + public static final int OPERATION_ADD_CATEGORY = 1; + + /** Clear all service categories from the list of enabled categories. */ + public static final int OPERATION_CLEAR_CATEGORIES = 2; + + /** Alert option: no alert. */ + public static final int ALERT_OPTION_NO_ALERT = 0; + + /** Alert option: default alert. */ + public static final int ALERT_OPTION_DEFAULT_ALERT = 1; + + /** Alert option: vibrate alert once. */ + public static final int ALERT_OPTION_VIBRATE_ONCE = 2; + + /** Alert option: vibrate alert - repeat. */ + public static final int ALERT_OPTION_VIBRATE_REPEAT = 3; + + /** Alert option: visual alert once. */ + public static final int ALERT_OPTION_VISUAL_ONCE = 4; + + /** Alert option: visual alert - repeat. */ + public static final int ALERT_OPTION_VISUAL_REPEAT = 5; + + /** Alert option: low-priority alert once. */ + public static final int ALERT_OPTION_LOW_PRIORITY_ONCE = 6; + + /** Alert option: low-priority alert - repeat. */ + public static final int ALERT_OPTION_LOW_PRIORITY_REPEAT = 7; + + /** Alert option: medium-priority alert once. */ + public static final int ALERT_OPTION_MED_PRIORITY_ONCE = 8; + + /** Alert option: medium-priority alert - repeat. */ + public static final int ALERT_OPTION_MED_PRIORITY_REPEAT = 9; + + /** Alert option: high-priority alert once. */ + public static final int ALERT_OPTION_HIGH_PRIORITY_ONCE = 10; + + /** Alert option: high-priority alert - repeat. */ + public static final int ALERT_OPTION_HIGH_PRIORITY_REPEAT = 11; + + /** Service category operation (add/delete/clear). */ + private final int mOperation; + + /** Service category to modify. */ + private final int mCategory; + + /** Language used for service category name (defined in BearerData.LANGUAGE_*). */ + private final int mLanguage; + + /** Maximum number of messages to store for this service category. */ + private final int mMaxMessages; + + /** Service category alert option. */ + private final int mAlertOption; + + /** Name of service category. */ + private final String mCategoryName; + + /** Create a new CdmaSmsCbProgramData object with the specified values. */ + public CdmaSmsCbProgramData(int operation, int category, int language, int maxMessages, + int alertOption, String categoryName) { + mOperation = operation; + mCategory = category; + mLanguage = language; + mMaxMessages = maxMessages; + mAlertOption = alertOption; + mCategoryName = categoryName; + } + + /** Create a new CdmaSmsCbProgramData object from a Parcel. */ + CdmaSmsCbProgramData(Parcel in) { + mOperation = in.readInt(); + mCategory = in.readInt(); + mLanguage = in.readInt(); + mMaxMessages = in.readInt(); + mAlertOption = in.readInt(); + mCategoryName = in.readString(); + } + + /** + * Flatten this object into a Parcel. + * + * @param dest The Parcel in which the object should be written. + * @param flags Additional flags about how the object should be written (ignored). + */ + @Override + public void writeToParcel(Parcel dest, int flags) { + dest.writeInt(mOperation); + dest.writeInt(mCategory); + dest.writeInt(mLanguage); + dest.writeInt(mMaxMessages); + dest.writeInt(mAlertOption); + dest.writeString(mCategoryName); + } + + /** + * Returns the service category operation, e.g. {@link #OPERATION_ADD_CATEGORY}. + * @return one of the {@code OPERATION_*} values + */ + public int getOperation() { + return mOperation; + } + + /** + * Returns the CDMA service category to modify. + * @return a 16-bit CDMA service category value + */ + public int getCategory() { + return mCategory; + } + + /** + * Returns the CDMA language code for this service category. + * @return one of the language values defined in BearerData.LANGUAGE_* + */ + public int getLanguage() { + return mLanguage; + } + + /** + * Returns the maximum number of messages to store for this service category. + * @return the maximum number of messages to store for this service category + */ + public int getMaxMessages() { + return mMaxMessages; + } + + /** + * Returns the service category alert option, e.g. {@link #ALERT_OPTION_DEFAULT_ALERT}. + * @return one of the {@code ALERT_OPTION_*} values + */ + public int getAlertOption() { + return mAlertOption; + } + + /** + * Returns the service category name, in the language specified by {@link #getLanguage()}. + * @return an optional service category name + */ + public String getCategoryName() { + return mCategoryName; + } + + @Override + public String toString() { + return "CdmaSmsCbProgramData{operation=" + mOperation + ", category=" + mCategory + + ", language=" + mLanguage + ", max messages=" + mMaxMessages + + ", alert option=" + mAlertOption + ", category name=" + mCategoryName + '}'; + } + + /** + * Describe the kinds of special objects contained in the marshalled representation. + * @return a bitmask indicating this Parcelable contains no special objects + */ + @Override + public int describeContents() { + return 0; + } + + /** Creator for unparcelling objects. */ + public static final Parcelable.Creator<CdmaSmsCbProgramData> + CREATOR = new Parcelable.Creator<CdmaSmsCbProgramData>() { + @Override + public CdmaSmsCbProgramData createFromParcel(Parcel in) { + return new CdmaSmsCbProgramData(in); + } + + @Override + public CdmaSmsCbProgramData[] newArray(int size) { + return new CdmaSmsCbProgramData[size]; + } + }; +} diff --git a/android/telephony/cdma/CdmaSmsCbProgramResults.java b/android/telephony/cdma/CdmaSmsCbProgramResults.java new file mode 100644 index 00000000..68bfa3ce --- /dev/null +++ b/android/telephony/cdma/CdmaSmsCbProgramResults.java @@ -0,0 +1,144 @@ +/* + * Copyright (C) 2012 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.telephony.cdma; + +import android.os.Parcel; +import android.os.Parcelable; + +/** + * CDMA Service Category Program Results from SCPT teleservice SMS. + * + * {@hide} + */ +public class CdmaSmsCbProgramResults implements Parcelable { + + /** Program result: success. */ + public static final int RESULT_SUCCESS = 0; + + /** Program result: memory limit exceeded. */ + public static final int RESULT_MEMORY_LIMIT_EXCEEDED = 1; + + /** Program result: limit exceeded. */ + public static final int RESULT_CATEGORY_LIMIT_EXCEEDED = 2; + + /** Program result: category already opted in. */ + public static final int RESULT_CATEGORY_ALREADY_ADDED = 3; + + /** Program result: category already opted in. */ + public static final int RESULT_CATEGORY_ALREADY_DELETED = 4; + + /** Program result: invalid MAX_MESSAGES. */ + public static final int RESULT_INVALID_MAX_MESSAGES = 5; + + /** Program result: invalid ALERT_OPTION. */ + public static final int RESULT_INVALID_ALERT_OPTION = 6; + + /** Program result: invalid service category name. */ + public static final int RESULT_INVALID_CATEGORY_NAME = 7; + + /** Program result: unspecified programming failure. */ + public static final int RESULT_UNSPECIFIED_FAILURE = 8; + + /** Service category to modify. */ + private final int mCategory; + + /** Language used for service category name (defined in BearerData.LANGUAGE_*). */ + private final int mLanguage; + + /** Result of service category programming for this category. */ + private final int mCategoryResult; + + /** Create a new CdmaSmsCbProgramResults object with the specified values. */ + public CdmaSmsCbProgramResults(int category, int language, int categoryResult) { + mCategory = category; + mLanguage = language; + mCategoryResult = categoryResult; + } + + /** Create a new CdmaSmsCbProgramResults object from a Parcel. */ + CdmaSmsCbProgramResults(Parcel in) { + mCategory = in.readInt(); + mLanguage = in.readInt(); + mCategoryResult = in.readInt(); + } + + /** + * Flatten this object into a Parcel. + * + * @param dest The Parcel in which the object should be written. + * @param flags Additional flags about how the object should be written (ignored). + */ + @Override + public void writeToParcel(Parcel dest, int flags) { + dest.writeInt(mCategory); + dest.writeInt(mLanguage); + dest.writeInt(mCategoryResult); + } + + /** + * Returns the CDMA service category to modify. + * @return a 16-bit CDMA service category value + */ + public int getCategory() { + return mCategory; + } + + /** + * Returns the CDMA language code for this service category. + * @return one of the language values defined in BearerData.LANGUAGE_* + */ + public int getLanguage() { + return mLanguage; + } + + /** + * Returns the result of service programming for this category + * @return the result of service programming for this category + */ + public int getCategoryResult() { + return mCategoryResult; + } + + @Override + public String toString() { + return "CdmaSmsCbProgramResults{category=" + mCategory + + ", language=" + mLanguage + ", result=" + mCategoryResult + '}'; + } + + /** + * Describe the kinds of special objects contained in the marshalled representation. + * @return a bitmask indicating this Parcelable contains no special objects + */ + @Override + public int describeContents() { + return 0; + } + + /** Creator for unparcelling objects. */ + public static final Parcelable.Creator<CdmaSmsCbProgramResults> + CREATOR = new Parcelable.Creator<CdmaSmsCbProgramResults>() { + @Override + public CdmaSmsCbProgramResults createFromParcel(Parcel in) { + return new CdmaSmsCbProgramResults(in); + } + + @Override + public CdmaSmsCbProgramResults[] newArray(int size) { + return new CdmaSmsCbProgramResults[size]; + } + }; +} diff --git a/android/telephony/euicc/DownloadableSubscription.java b/android/telephony/euicc/DownloadableSubscription.java new file mode 100644 index 00000000..b5484e34 --- /dev/null +++ b/android/telephony/euicc/DownloadableSubscription.java @@ -0,0 +1,145 @@ +/* + * Copyright (C) 2017 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.telephony.euicc; + +import android.annotation.Nullable; +import android.os.Parcel; +import android.os.Parcelable; +import android.telephony.UiccAccessRule; + +import com.android.internal.util.Preconditions; + +/** + * Information about a subscription which is available for download. + * + * TODO(b/35851809): Make this public. + * @hide + */ +public final class DownloadableSubscription implements Parcelable { + + public static final Creator<DownloadableSubscription> CREATOR = + new Creator<DownloadableSubscription>() { + @Override + public DownloadableSubscription createFromParcel(Parcel in) { + return new DownloadableSubscription(in); + } + + @Override + public DownloadableSubscription[] newArray(int size) { + return new DownloadableSubscription[size]; + } + }; + + /** + * Activation code. May be null for subscriptions which are not based on activation codes, e.g. + * to download a default subscription assigned to this device. + * @hide + * + * TODO(b/35851809): Make this a SystemApi. + */ + @Nullable + public final String encodedActivationCode; + + // see getCarrierName and setCarrierName + @Nullable + private String carrierName; + // see getAccessRules and setAccessRules + private UiccAccessRule[] accessRules; + + /** @hide */ + private DownloadableSubscription(String encodedActivationCode) { + this.encodedActivationCode = encodedActivationCode; + } + + private DownloadableSubscription(Parcel in) { + encodedActivationCode = in.readString(); + carrierName = in.readString(); + accessRules = in.createTypedArray(UiccAccessRule.CREATOR); + } + + /** + * Create a DownloadableSubscription for the given activation code. + * + * @param encodedActivationCode the activation code to use. Must not be null. + * @return the {@link DownloadableSubscription} which may be passed to + * {@link EuiccManager#downloadSubscription}. + */ + public static DownloadableSubscription forActivationCode(String encodedActivationCode) { + Preconditions.checkNotNull(encodedActivationCode, "Activation code may not be null"); + return new DownloadableSubscription(encodedActivationCode); + } + + /** + * Set the user-visible carrier name. + * @hide + * + * TODO(b/35851809): Make this a SystemApi. + */ + public void setCarrierName(String carrierName) { + this.carrierName = carrierName; + } + + /** + * Returns the user-visible carrier name. + * + * <p>Only present for downloadable subscriptions that were queried from a server (as opposed to + * those created with {@link #forActivationCode}). May be populated with + * {@link EuiccManager#getDownloadableSubscriptionMetadata}. + * @hide + * + * TODO(b/35851809): Make this a SystemApi. + */ + @Nullable + public String getCarrierName() { + return carrierName; + } + + /** + * Returns the {@link UiccAccessRule}s dictating access to this subscription. + * + * <p>Only present for downloadable subscriptions that were queried from a server (as opposed to + * those created with {@link #forActivationCode}). May be populated with + * {@link EuiccManager#getDownloadableSubscriptionMetadata}. + * @hide + * + * TODO(b/35851809): Make this a SystemApi. + */ + public UiccAccessRule[] getAccessRules() { + return accessRules; + } + + /** + * Set the {@link UiccAccessRule}s dictating access to this subscription. + * @hide + * + * TODO(b/35851809): Make this a SystemApi. + */ + public void setAccessRules(UiccAccessRule[] accessRules) { + this.accessRules = accessRules; + } + + @Override + public void writeToParcel(Parcel dest, int flags) { + dest.writeString(encodedActivationCode); + dest.writeString(carrierName); + dest.writeTypedArray(accessRules, flags); + } + + @Override + public int describeContents() { + return 0; + } +} diff --git a/android/telephony/euicc/EuiccInfo.java b/android/telephony/euicc/EuiccInfo.java new file mode 100644 index 00000000..5bfff08f --- /dev/null +++ b/android/telephony/euicc/EuiccInfo.java @@ -0,0 +1,72 @@ +/* + * Copyright (C) 2017 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.telephony.euicc; + +import android.annotation.Nullable; +import android.os.Parcel; +import android.os.Parcelable; + +/** + * Information about an eUICC chip/device. + * + * @see EuiccManager#getEuiccInfo + * @hide + * + * TODO(b/35851809): Make this public. + */ +// WARNING: Do not add any privacy-sensitive fields to this class (such as an eUICC identifier)! +// This API is accessible to all applications. Privacy-sensitive fields should be returned in their +// own APIs guarded with appropriate permission checks. +public final class EuiccInfo implements Parcelable { + + public static final Creator<EuiccInfo> CREATOR = + new Creator<EuiccInfo>() { + @Override + public EuiccInfo createFromParcel(Parcel in) { + return new EuiccInfo(in); + } + + @Override + public EuiccInfo[] newArray(int size) { + return new EuiccInfo[size]; + } + }; + + /** + * Version of the operating system running on the eUICC. This field is hardware-specific and is + * not guaranteed to match any particular format. + */ + @Nullable + public final String osVersion; + + public EuiccInfo(@Nullable String osVersion) { + this.osVersion = osVersion; + } + + private EuiccInfo(Parcel in) { + osVersion = in.readString(); + } + + @Override + public void writeToParcel(Parcel dest, int flags) { + dest.writeString(osVersion); + } + + @Override + public int describeContents() { + return 0; + } +} diff --git a/android/telephony/euicc/EuiccManager.java b/android/telephony/euicc/EuiccManager.java new file mode 100644 index 00000000..a13af5f4 --- /dev/null +++ b/android/telephony/euicc/EuiccManager.java @@ -0,0 +1,523 @@ +/* + * Copyright (C) 2017 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.telephony.euicc; + +import android.annotation.Nullable; +import android.annotation.SdkConstant; +import android.app.Activity; +import android.app.PendingIntent; +import android.content.Context; +import android.content.Intent; +import android.content.IntentSender; +import android.content.pm.PackageManager; +import android.os.Bundle; +import android.os.RemoteException; +import android.os.ServiceManager; + +import com.android.internal.telephony.euicc.IEuiccController; + +/** + * EuiccManager is the application interface to eUICCs, or eSIMs/embedded SIMs. + * + * <p>You do not instantiate this class directly; instead, you retrieve an instance through + * {@link Context#getSystemService(String)} and {@link Context#EUICC_SERVICE}. + * + * <p>See {@link #isEnabled} before attempting to use these APIs. + * + * TODO(b/35851809): Make this public. + * @hide + */ +public class EuiccManager { + + /** + * Intent action to launch the embedded SIM (eUICC) management settings screen. + * + * <p>This screen shows a list of embedded profiles and offers the user the ability to switch + * between them, download new profiles, and delete unused profiles. + * + * <p>The activity will immediately finish with {@link android.app.Activity#RESULT_CANCELED} if + * {@link #isEnabled} is false. + */ + @SdkConstant(SdkConstant.SdkConstantType.ACTIVITY_INTENT_ACTION) + public static final String ACTION_MANAGE_EMBEDDED_SUBSCRIPTIONS = + "android.telephony.euicc.action.MANAGE_EMBEDDED_SUBSCRIPTIONS"; + + /** + * Intent action to provision an embedded subscription. + * + * <p>May be called during device provisioning to launch a screen to perform embedded SIM + * provisioning, e.g. if no physical SIM is present and the user elects to configure their + * embedded SIM. + * + * <p>The activity will immediately finish with {@link android.app.Activity#RESULT_CANCELED} if + * {@link #isEnabled} is false or if the device is already provisioned. + * + * TODO(b/35851809): Make this a SystemApi. + */ + @SdkConstant(SdkConstant.SdkConstantType.ACTIVITY_INTENT_ACTION) + public static final String ACTION_PROVISION_EMBEDDED_SUBSCRIPTION = + "android.telephony.euicc.action.PROVISION_EMBEDDED_SUBSCRIPTION"; + + /** + * Intent action to handle a resolvable error. + * @hide + */ + public static final String ACTION_RESOLVE_ERROR = + "android.telephony.euicc.action.RESOLVE_ERROR"; + + /** + * Result code for an operation indicating that the operation succeeded. + */ + public static final int EMBEDDED_SUBSCRIPTION_RESULT_OK = 0; + + /** + * Result code for an operation indicating that the user must take some action before the + * operation can continue. + * + * @see #startResolutionActivity + */ + public static final int EMBEDDED_SUBSCRIPTION_RESULT_RESOLVABLE_ERROR = 1; + + /** + * Result code for an operation indicating that an unresolvable error occurred. + * + * {@link #EXTRA_EMBEDDED_SUBSCRIPTION_DETAILED_CODE} will be populated with a detailed error + * code for logging/debugging purposes only. + */ + public static final int EMBEDDED_SUBSCRIPTION_RESULT_ERROR = 2; + + /** + * Key for an extra set on {@link PendingIntent} result callbacks providing a detailed result + * code. + * + * <p>This code is an implementation detail of the embedded subscription manager and is only + * intended for logging or debugging purposes. + */ + public static final String EXTRA_EMBEDDED_SUBSCRIPTION_DETAILED_CODE = + "android.telephony.euicc.extra.EMBEDDED_SUBSCRIPTION_DETAILED_CODE"; + + /** + * Key for an extra set on {@link #getDownloadableSubscriptionMetadata} PendingIntent result + * callbacks providing the downloadable subscription metadata. + * @hide + * + * TODO(b/35851809): Make this a SystemApi. + */ + public static final String EXTRA_EMBEDDED_SUBSCRIPTION_DOWNLOADABLE_SUBSCRIPTION = + "android.telephony.euicc.extra.EMBEDDED_SUBSCRIPTION_DOWNLOADABLE_SUBSCRIPTION"; + + /** + * Key for an extra set on {@link #getDefaultDownloadableSubscriptionList} PendingIntent result + * callbacks providing the list of available downloadable subscriptions. + * @hide + * + * TODO(b/35851809): Make this a SystemApi. + */ + public static final String EXTRA_EMBEDDED_SUBSCRIPTION_DOWNLOADABLE_SUBSCRIPTIONS = + "android.telephony.euicc.extra.EMBEDDED_SUBSCRIPTION_DOWNLOADABLE_SUBSCRIPTIONS"; + + /** + * Key for an extra set on {@link PendingIntent} result callbacks providing the resolution + * pending intent for {@link #EMBEDDED_SUBSCRIPTION_RESULT_RESOLVABLE_ERROR}s. + * @hide + */ + public static final String EXTRA_EMBEDDED_SUBSCRIPTION_RESOLUTION_INTENT = + "android.telephony.euicc.extra.EMBEDDED_SUBSCRIPTION_RESOLUTION_INTENT"; + + /** + * Key for an extra set on the {@link #EXTRA_EMBEDDED_SUBSCRIPTION_RESOLUTION_INTENT} intent + * containing the EuiccService action to launch for resolution. + * @hide + */ + public static final String EXTRA_EMBEDDED_SUBSCRIPTION_RESOLUTION_ACTION = + "android.telephony.euicc.extra.EMBEDDED_SUBSCRIPTION_RESOLUTION_ACTION"; + + /** + * Key for an extra set on the {@link #EXTRA_EMBEDDED_SUBSCRIPTION_RESOLUTION_INTENT} intent + * providing the callback to execute after resolution is completed. + * @hide + */ + public static final String EXTRA_EMBEDDED_SUBSCRIPTION_RESOLUTION_CALLBACK_INTENT = + "android.telephony.euicc.extra.EMBEDDED_SUBSCRIPTION_RESOLUTION_CALLBACK_INTENT"; + + /** + * Key for an extra set on the {@link #ACTION_PROVISION_EMBEDDED_SUBSCRIPTION} intent for + * whether the user choses to use eUICC to set up network in SUW. + * @hide + */ + public static final String EXTRA_FORCE_PROVISION = + "android.telephony.euicc.extra.FORCE_PROVISION"; + + /** + * Optional meta-data attribute for a carrier app providing an icon to use to represent the + * carrier. If not provided, the app's launcher icon will be used as a fallback. + */ + public static final String META_DATA_CARRIER_ICON = "android.telephony.euicc.carriericon"; + + private final Context mContext; + private final IEuiccController mController; + + /** @hide */ + public EuiccManager(Context context) { + mContext = context; + mController = IEuiccController.Stub.asInterface(ServiceManager.getService("econtroller")); + } + + /** + * Whether embedded subscriptions are currently enabled. + * + * <p>Even on devices with the {@link PackageManager#FEATURE_TELEPHONY_EUICC} feature, embedded + * subscriptions may be turned off, e.g. because of a carrier restriction from an inserted + * physical SIM. Therefore, this runtime check should be used before accessing embedded + * subscription APIs. + * + * @return true if embedded subscriptions are currently enabled. + */ + public boolean isEnabled() { + // In the future, this may reach out to IEuiccController (if non-null) to check any dynamic + // restrictions. + return mController != null; + } + + /** + * Returns the EID identifying the eUICC hardware. + * + * <p>Requires that the calling app has carrier privileges on the active subscription on the + * eUICC. + * + * @return the EID. May be null if {@link #isEnabled()} is false or the eUICC is not ready. + */ + @Nullable + public String getEid() { + if (!isEnabled()) { + return null; + } + try { + return mController.getEid(); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + } + + /** + * Attempt to download the given {@link DownloadableSubscription}. + * + * <p>Requires the {@link android.Manifest.permission#WRITE_EMBEDDED_SUBSCRIPTIONS} permission, + * or the calling app must be authorized to manage both the currently-active subscription and + * the subscription to be downloaded according to the subscription metadata. Without the former, + * an {@link #EMBEDDED_SUBSCRIPTION_RESULT_RESOLVABLE_ERROR} will be returned in the callback + * intent to prompt the user to accept the download. + * + * @param subscription the subscription to download. + * @param switchAfterDownload if true, the profile will be activated upon successful download. + * @param callbackIntent a PendingIntent to launch when the operation completes. + */ + public void downloadSubscription(DownloadableSubscription subscription, + boolean switchAfterDownload, PendingIntent callbackIntent) { + if (!isEnabled()) { + sendUnavailableError(callbackIntent); + return; + } + try { + mController.downloadSubscription(subscription, switchAfterDownload, + mContext.getOpPackageName(), callbackIntent); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + } + + /** + * Start an activity to resolve a user-resolvable error. + * + * <p>If an operation returns {@link #EMBEDDED_SUBSCRIPTION_RESULT_RESOLVABLE_ERROR}, this + * method may be called to prompt the user to resolve the issue. + * + * <p>This method may only be called once for a particular error. + * + * @param activity the calling activity (which should be in the foreground). + * @param requestCode an application-specific request code which will be provided to + * {@link Activity#onActivityResult} upon completion. Note that the operation may still be + * in progress when the resolution activity completes; it is not fully finished until the + * callback intent is triggered. + * @param resultIntent the Intent provided to the initial callback intent which failed with + * {@link #EMBEDDED_SUBSCRIPTION_RESULT_RESOLVABLE_ERROR}. + * @param callbackIntent a PendingIntent to launch when the operation completes. This is + * trigered upon completion of the original operation that required user resolution. + * @throws android.content.IntentSender.SendIntentException if called more than once. + */ + public void startResolutionActivity(Activity activity, int requestCode, Intent resultIntent, + PendingIntent callbackIntent) throws IntentSender.SendIntentException { + PendingIntent resolutionIntent = + resultIntent.getParcelableExtra(EXTRA_EMBEDDED_SUBSCRIPTION_RESOLUTION_INTENT); + if (resolutionIntent == null) { + throw new IllegalArgumentException("Invalid result intent"); + } + Intent fillInIntent = new Intent(); + fillInIntent.putExtra(EuiccManager.EXTRA_EMBEDDED_SUBSCRIPTION_RESOLUTION_CALLBACK_INTENT, + callbackIntent); + activity.startIntentSenderForResult(resolutionIntent.getIntentSender(), requestCode, + fillInIntent, 0 /* flagsMask */, 0 /* flagsValues */, 0 /* extraFlags */); + } + + /** + * Continue an operation after the user resolves an error. + * + * <p>To be called by the LUI upon completion of a resolvable error flow. + * + * @param resolutionIntent The original intent used to start the LUI. + * @param resolutionExtras Resolution-specific extras depending on the result of the resolution. + * For example, this may indicate whether the user has consented or may include the input + * they provided. + * @hide + * + * TODO(b/35851809): Make this a SystemApi. + */ + public void continueOperation(Intent resolutionIntent, Bundle resolutionExtras) { + if (!isEnabled()) { + PendingIntent callbackIntent = + resolutionIntent.getParcelableExtra( + EuiccManager.EXTRA_EMBEDDED_SUBSCRIPTION_RESOLUTION_CALLBACK_INTENT); + if (callbackIntent != null) { + sendUnavailableError(callbackIntent); + } + return; + } + try { + mController.continueOperation(resolutionIntent, resolutionExtras); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + } + + /** + * Fills in the metadata for a DownloadableSubscription. + * + * <p>May be used in cases that a DownloadableSubscription was constructed to download a + * profile, but the metadata for the profile is unknown (e.g. we only know the activation code). + * The callback will be triggered with an Intent with + * {@link #EXTRA_EMBEDDED_SUBSCRIPTION_DOWNLOADABLE_SUBSCRIPTION} set to the + * downloadable subscription metadata upon success. + * + * <p>Requires that the calling app has the + * {@link android.Manifest.permission#WRITE_EMBEDDED_SUBSCRIPTIONS} permission. This is for + * internal system use only. + * + * @param subscription the subscription which needs metadata filled in + * @param callbackIntent a PendingIntent to launch when the operation completes. + * @hide + * + * TODO(b/35851809): Make this a SystemApi. + */ + public void getDownloadableSubscriptionMetadata( + DownloadableSubscription subscription, PendingIntent callbackIntent) { + if (!isEnabled()) { + sendUnavailableError(callbackIntent); + return; + } + try { + mController.getDownloadableSubscriptionMetadata( + subscription, mContext.getOpPackageName(), callbackIntent); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + } + + /** + * Gets metadata for subscription which are available for download on this device. + * + * <p>Subscriptions returned here may be passed to {@link #downloadSubscription}. They may have + * been pre-assigned to this particular device, for example. The callback will be triggered with + * an Intent with {@link #EXTRA_EMBEDDED_SUBSCRIPTION_DOWNLOADABLE_SUBSCRIPTIONS} set to the + * list of available subscriptions upon success. + * + * <p>Requires that the calling app has the + * {@link android.Manifest.permission#WRITE_EMBEDDED_SUBSCRIPTIONS} permission. This is for + * internal system use only. + * + * @param callbackIntent a PendingIntent to launch when the operation completes. + * @hide + * + * TODO(b/35851809): Make this a SystemApi. + */ + public void getDefaultDownloadableSubscriptionList(PendingIntent callbackIntent) { + if (!isEnabled()) { + sendUnavailableError(callbackIntent); + return; + } + try { + mController.getDefaultDownloadableSubscriptionList( + mContext.getOpPackageName(), callbackIntent); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + } + + /** + * Returns information about the eUICC chip/device. + * + * @return the {@link EuiccInfo}. May be null if {@link #isEnabled()} is false or the eUICC is + * not ready. + */ + @Nullable + public EuiccInfo getEuiccInfo() { + if (!isEnabled()) { + return null; + } + try { + return mController.getEuiccInfo(); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + } + + /** + * Deletes the given subscription. + * + * <p>If this subscription is currently active, the device will first switch away from it onto + * an "empty" subscription. + * + * <p>Requires that the calling app has carrier privileges according to the metadata of the + * profile to be deleted, or the + * {@link android.Manifest.permission#WRITE_EMBEDDED_SUBSCRIPTIONS} permission. + * + * @param subscriptionId the ID of the subscription to delete. + * @param callbackIntent a PendingIntent to launch when the operation completes. + */ + public void deleteSubscription(int subscriptionId, PendingIntent callbackIntent) { + if (!isEnabled()) { + sendUnavailableError(callbackIntent); + return; + } + try { + mController.deleteSubscription( + subscriptionId, mContext.getOpPackageName(), callbackIntent); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + } + + /** + * Switch to (enable) the given subscription. + * + * <p>Requires the {@link android.Manifest.permission#WRITE_EMBEDDED_SUBSCRIPTIONS} permission, + * or the calling app must be authorized to manage both the currently-active subscription and + * the subscription to be enabled according to the subscription metadata. Without the former, + * an {@link #EMBEDDED_SUBSCRIPTION_RESULT_RESOLVABLE_ERROR} will be returned in the callback + * intent to prompt the user to accept the download. + * + * @param subscriptionId the ID of the subscription to enable. May be + * {@link android.telephony.SubscriptionManager#INVALID_SUBSCRIPTION_ID} to deactivate the + * current profile without activating another profile to replace it. + * @param callbackIntent a PendingIntent to launch when the operation completes. + */ + public void switchToSubscription(int subscriptionId, PendingIntent callbackIntent) { + if (!isEnabled()) { + sendUnavailableError(callbackIntent); + return; + } + try { + mController.switchToSubscription( + subscriptionId, mContext.getOpPackageName(), callbackIntent); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + } + + /** + * Update the nickname for the given subscription. + * + * <p>Requires that the calling app has the + * {@link android.Manifest.permission#WRITE_EMBEDDED_SUBSCRIPTIONS} permission. This is for + * internal system use only. + * + * @param subscriptionId the ID of the subscription to update. + * @param nickname the new nickname to apply. + * @param callbackIntent a PendingIntent to launch when the operation completes. + * @hide + */ + public void updateSubscriptionNickname( + int subscriptionId, String nickname, PendingIntent callbackIntent) { + if (!isEnabled()) { + sendUnavailableError(callbackIntent); + return; + } + try { + mController.updateSubscriptionNickname(subscriptionId, nickname, callbackIntent); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + } + + /** + * Erase all subscriptions and reset the eUICC. + * + * <p>Requires that the calling app has the + * {@link android.Manifest.permission#WRITE_EMBEDDED_SUBSCRIPTIONS} permission. This is for + * internal system use only. + * + * @param callbackIntent a PendingIntent to launch when the operation completes. + * @hide + */ + public void eraseSubscriptions(PendingIntent callbackIntent) { + if (!isEnabled()) { + sendUnavailableError(callbackIntent); + return; + } + try { + mController.eraseSubscriptions(callbackIntent); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + } + + /** + * Ensure that subscriptions will be retained on the next factory reset. + * + * <p>By default, all subscriptions on the eUICC are erased the first time a device boots (ever + * and after factory resets). This ensures that the data is wiped after a factory reset is + * performed via fastboot or recovery mode, as these modes do not support the necessary radio + * communication needed to wipe the eSIM. + * + * <p>However, this method may be called right before a factory reset issued via settings when + * the user elects to retain subscriptions. Doing so will mark them for retention so that they + * are not cleared after the ensuing reset. + * + * <p>Requires that the calling app has the {@link android.Manifest.permission#MASTER_CLEAR} + * permission. This is for internal system use only. + * + * @param callbackIntent a PendingIntent to launch when the operation completes. + * @hide + */ + public void retainSubscriptionsForFactoryReset(PendingIntent callbackIntent) { + if (!isEnabled()) { + sendUnavailableError(callbackIntent); + return; + } + try { + mController.retainSubscriptionsForFactoryReset(callbackIntent); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + } + + private static void sendUnavailableError(PendingIntent callbackIntent) { + try { + callbackIntent.send(EMBEDDED_SUBSCRIPTION_RESULT_ERROR); + } catch (PendingIntent.CanceledException e) { + // Caller canceled the callback; do nothing. + } + } +} diff --git a/android/telephony/gsm/GsmCellLocation.java b/android/telephony/gsm/GsmCellLocation.java new file mode 100644 index 00000000..17178023 --- /dev/null +++ b/android/telephony/gsm/GsmCellLocation.java @@ -0,0 +1,154 @@ +/* + * Copyright (C) 2006 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.telephony.gsm; + +import android.os.Bundle; +import android.telephony.CellLocation; + +/** + * Represents the cell location on a GSM phone. + */ +public class GsmCellLocation extends CellLocation { + private int mLac; + private int mCid; + private int mPsc; + + /** + * Empty constructor. Initializes the LAC and CID to -1. + */ + public GsmCellLocation() { + mLac = -1; + mCid = -1; + mPsc = -1; + } + + /** + * Initialize the object from a bundle. + */ + public GsmCellLocation(Bundle bundle) { + mLac = bundle.getInt("lac", -1); + mCid = bundle.getInt("cid", -1); + mPsc = bundle.getInt("psc", -1); + } + + /** + * @return gsm location area code, -1 if unknown, 0xffff max legal value + */ + public int getLac() { + return mLac; + } + + /** + * @return gsm cell id, -1 if unknown, 0xffff max legal value + */ + public int getCid() { + return mCid; + } + + /** + * On a UMTS network, returns the primary scrambling code of the serving + * cell. + * + * @return primary scrambling code for UMTS, -1 if unknown or GSM + */ + public int getPsc() { + return mPsc; + } + + /** + * Invalidate this object. The location area code and the cell id are set to -1. + */ + @Override + public void setStateInvalid() { + mLac = -1; + mCid = -1; + mPsc = -1; + } + + /** + * Set the location area code and the cell id. + */ + public void setLacAndCid(int lac, int cid) { + mLac = lac; + mCid = cid; + } + + /** + * Set the primary scrambling code. + * @hide + */ + public void setPsc(int psc) { + mPsc = psc; + } + + @Override + public int hashCode() { + return mLac ^ mCid; + } + + @Override + public boolean equals(Object o) { + GsmCellLocation s; + + try { + s = (GsmCellLocation)o; + } catch (ClassCastException ex) { + return false; + } + + if (o == null) { + return false; + } + + return equalsHandlesNulls(mLac, s.mLac) && equalsHandlesNulls(mCid, s.mCid) + && equalsHandlesNulls(mPsc, s.mPsc); + } + + @Override + public String toString() { + return "["+ mLac + "," + mCid + "," + mPsc + "]"; + } + + /** + * Test whether two objects hold the same data values or both are null + * + * @param a first obj + * @param b second obj + * @return true if two objects equal or both are null + */ + private static boolean equalsHandlesNulls(Object a, Object b) { + return (a == null) ? (b == null) : a.equals (b); + } + + /** + * Set intent notifier Bundle based on service state + * + * @param m intent notifier Bundle + */ + public void fillInNotifierBundle(Bundle m) { + m.putInt("lac", mLac); + m.putInt("cid", mCid); + m.putInt("psc", mPsc); + } + + /** + * @hide + */ + public boolean isEmpty() { + return (mLac == -1 && mCid == -1 && mPsc == -1); + } +} diff --git a/android/telephony/gsm/SmsManager.java b/android/telephony/gsm/SmsManager.java new file mode 100644 index 00000000..777802a5 --- /dev/null +++ b/android/telephony/gsm/SmsManager.java @@ -0,0 +1,261 @@ +/* + * Copyright (C) 2007 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.telephony.gsm; + +import android.app.PendingIntent; + +import java.util.ArrayList; + + +/** + * Manages SMS operations such as sending data, text, and pdu SMS messages. + * Get this object by calling the static method SmsManager.getDefault(). + * @deprecated Replaced by android.telephony.SmsManager that supports both GSM and CDMA. + */ +@Deprecated public final class SmsManager { + private static SmsManager sInstance; + private android.telephony.SmsManager mSmsMgrProxy; + + /** Get the default instance of the SmsManager + * + * @return the default instance of the SmsManager + * @deprecated Use android.telephony.SmsManager. + */ + @Deprecated + public static final SmsManager getDefault() { + if (sInstance == null) { + sInstance = new SmsManager(); + } + return sInstance; + } + + @Deprecated + private SmsManager() { + mSmsMgrProxy = android.telephony.SmsManager.getDefault(); + } + + /** + * Send a text based SMS. + * + * @param destinationAddress the address to send the message to + * @param scAddress is the service center address or null to use + * the current default SMSC + * @param text the body of the message to send + * @param sentIntent if not NULL this <code>PendingIntent</code> is + * broadcast when the message is successfully sent, or failed. + * The result code will be <code>Activity.RESULT_OK<code> for success, + * or one of these errors: + * <code>RESULT_ERROR_GENERIC_FAILURE</code> + * <code>RESULT_ERROR_RADIO_OFF</code> + * <code>RESULT_ERROR_NULL_PDU</code>. + * The per-application based SMS control checks sentIntent. If sentIntent + * is NULL the caller will be checked against all unknown applications, + * which cause smaller number of SMS to be sent in checking period. + * @param deliveryIntent if not NULL this <code>PendingIntent</code> is + * broadcast when the message is delivered to the recipient. The + * raw pdu of the status report is in the extended data ("pdu"). + * + * @throws IllegalArgumentException if destinationAddress or text are empty + * @deprecated Use android.telephony.SmsManager. + */ + @Deprecated + public final void sendTextMessage( + String destinationAddress, String scAddress, String text, + PendingIntent sentIntent, PendingIntent deliveryIntent) { + mSmsMgrProxy.sendTextMessage(destinationAddress, scAddress, text, + sentIntent, deliveryIntent); + } + + /** + * Divide a text message into several messages, none bigger than + * the maximum SMS message size. + * + * @param text the original message. Must not be null. + * @return an <code>ArrayList</code> of strings that, in order, + * comprise the original message + * @deprecated Use android.telephony.SmsManager. + */ + @Deprecated + public final ArrayList<String> divideMessage(String text) { + return mSmsMgrProxy.divideMessage(text); + } + + /** + * Send a multi-part text based SMS. The callee should have already + * divided the message into correctly sized parts by calling + * <code>divideMessage</code>. + * + * @param destinationAddress the address to send the message to + * @param scAddress is the service center address or null to use + * the current default SMSC + * @param parts an <code>ArrayList</code> of strings that, in order, + * comprise the original message + * @param sentIntents if not null, an <code>ArrayList</code> of + * <code>PendingIntent</code>s (one for each message part) that is + * broadcast when the corresponding message part has been sent. + * The result code will be <code>Activity.RESULT_OK<code> for success, + * or one of these errors: + * <code>RESULT_ERROR_GENERIC_FAILURE</code> + * <code>RESULT_ERROR_RADIO_OFF</code> + * <code>RESULT_ERROR_NULL_PDU</code>. + * The per-application based SMS control checks sentIntent. If sentIntent + * is NULL the caller will be checked against all unknown applicaitons, + * which cause smaller number of SMS to be sent in checking period. + * @param deliveryIntents if not null, an <code>ArrayList</code> of + * <code>PendingIntent</code>s (one for each message part) that is + * broadcast when the corresponding message part has been delivered + * to the recipient. The raw pdu of the status report is in the + * extended data ("pdu"). + * + * @throws IllegalArgumentException if destinationAddress or data are empty + * @deprecated Use android.telephony.SmsManager. + */ + @Deprecated + public final void sendMultipartTextMessage( + String destinationAddress, String scAddress, ArrayList<String> parts, + ArrayList<PendingIntent> sentIntents, ArrayList<PendingIntent> deliveryIntents) { + mSmsMgrProxy.sendMultipartTextMessage(destinationAddress, scAddress, parts, + sentIntents, deliveryIntents); + } + + /** + * Send a data based SMS to a specific application port. + * + * @param destinationAddress the address to send the message to + * @param scAddress is the service center address or null to use + * the current default SMSC + * @param destinationPort the port to deliver the message to + * @param data the body of the message to send + * @param sentIntent if not NULL this <code>PendingIntent</code> is + * broadcast when the message is sucessfully sent, or failed. + * The result code will be <code>Activity.RESULT_OK<code> for success, + * or one of these errors: + * <code>RESULT_ERROR_GENERIC_FAILURE</code> + * <code>RESULT_ERROR_RADIO_OFF</code> + * <code>RESULT_ERROR_NULL_PDU</code>. + * The per-application based SMS control checks sentIntent. If sentIntent + * is NULL the caller will be checked against all unknown applicaitons, + * which cause smaller number of SMS to be sent in checking period. + * @param deliveryIntent if not NULL this <code>PendingIntent</code> is + * broadcast when the message is delivered to the recipient. The + * raw pdu of the status report is in the extended data ("pdu"). + * + * @throws IllegalArgumentException if destinationAddress or data are empty + * @deprecated Use android.telephony.SmsManager. + */ + @Deprecated + public final void sendDataMessage( + String destinationAddress, String scAddress, short destinationPort, + byte[] data, PendingIntent sentIntent, PendingIntent deliveryIntent) { + mSmsMgrProxy.sendDataMessage(destinationAddress, scAddress, destinationPort, + data, sentIntent, deliveryIntent); + } + + /** + * Copy a raw SMS PDU to the SIM. + * + * @param smsc the SMSC for this message, or NULL for the default SMSC + * @param pdu the raw PDU to store + * @param status message status (STATUS_ON_SIM_READ, STATUS_ON_SIM_UNREAD, + * STATUS_ON_SIM_SENT, STATUS_ON_SIM_UNSENT) + * @return true for success + * @deprecated Use android.telephony.SmsManager. + * {@hide} + */ + @Deprecated + public final boolean copyMessageToSim(byte[] smsc, byte[] pdu, int status) { + return mSmsMgrProxy.copyMessageToIcc(smsc, pdu, status); + } + + /** + * Delete the specified message from the SIM. + * + * @param messageIndex is the record index of the message on SIM + * @return true for success + * @deprecated Use android.telephony.SmsManager. + * {@hide} + */ + @Deprecated + public final boolean deleteMessageFromSim(int messageIndex) { + return mSmsMgrProxy.deleteMessageFromIcc(messageIndex); + } + + /** + * Update the specified message on the SIM. + * + * @param messageIndex record index of message to update + * @param newStatus new message status (STATUS_ON_SIM_READ, + * STATUS_ON_SIM_UNREAD, STATUS_ON_SIM_SENT, + * STATUS_ON_SIM_UNSENT, STATUS_ON_SIM_FREE) + * @param pdu the raw PDU to store + * @return true for success + * @deprecated Use android.telephony.SmsManager. + * {@hide} + */ + @Deprecated + public final boolean updateMessageOnSim(int messageIndex, int newStatus, byte[] pdu) { + return mSmsMgrProxy.updateMessageOnIcc(messageIndex, newStatus, pdu); + } + + /** + * Retrieves all messages currently stored on SIM. + * @return <code>ArrayList</code> of <code>SmsMessage</code> objects + * @deprecated Use android.telephony.SmsManager. + * {@hide} + */ + @Deprecated + public final ArrayList<android.telephony.SmsMessage> getAllMessagesFromSim() { + return android.telephony.SmsManager.getDefault().getAllMessagesFromIcc(); + } + + /** Free space (TS 51.011 10.5.3). + * @deprecated Use android.telephony.SmsManager. */ + @Deprecated static public final int STATUS_ON_SIM_FREE = 0; + + /** Received and read (TS 51.011 10.5.3). + * @deprecated Use android.telephony.SmsManager. */ + @Deprecated static public final int STATUS_ON_SIM_READ = 1; + + /** Received and unread (TS 51.011 10.5.3). + * @deprecated Use android.telephony.SmsManager. */ + @Deprecated static public final int STATUS_ON_SIM_UNREAD = 3; + + /** Stored and sent (TS 51.011 10.5.3). + * @deprecated Use android.telephony.SmsManager. */ + @Deprecated static public final int STATUS_ON_SIM_SENT = 5; + + /** Stored and unsent (TS 51.011 10.5.3). + * @deprecated Use android.telephony.SmsManager. */ + @Deprecated static public final int STATUS_ON_SIM_UNSENT = 7; + + /** Generic failure cause + * @deprecated Use android.telephony.SmsManager. */ + @Deprecated static public final int RESULT_ERROR_GENERIC_FAILURE = 1; + + /** Failed because radio was explicitly turned off + * @deprecated Use android.telephony.SmsManager. */ + @Deprecated static public final int RESULT_ERROR_RADIO_OFF = 2; + + /** Failed because no pdu provided + * @deprecated Use android.telephony.SmsManager. */ + @Deprecated static public final int RESULT_ERROR_NULL_PDU = 3; + + /** Failed because service is currently unavailable + * @deprecated Use android.telephony.SmsManager. */ + @Deprecated static public final int RESULT_ERROR_NO_SERVICE = 4; + +} diff --git a/android/telephony/gsm/SmsMessage.java b/android/telephony/gsm/SmsMessage.java new file mode 100644 index 00000000..8d5dac7e --- /dev/null +++ b/android/telephony/gsm/SmsMessage.java @@ -0,0 +1,625 @@ +/* + * Copyright (C) 2008 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.telephony.gsm; + +import android.telephony.TelephonyManager; + +import com.android.internal.telephony.GsmAlphabet; +import com.android.internal.telephony.SmsHeader; +import com.android.internal.telephony.SmsMessageBase; +import com.android.internal.telephony.SmsMessageBase.SubmitPduBase; + +import java.util.Arrays; + +import static android.telephony.TelephonyManager.PHONE_TYPE_CDMA; + + +/** + * A Short Message Service message. + * @deprecated Replaced by android.telephony.SmsMessage that supports both GSM and CDMA. + */ +@Deprecated +public class SmsMessage { + /** + * SMS Class enumeration. + * See TS 23.038. + * @deprecated Use android.telephony.SmsMessage. + */ + @Deprecated + public enum MessageClass{ + UNKNOWN, CLASS_0, CLASS_1, CLASS_2, CLASS_3; + } + + /** Unknown encoding scheme (see TS 23.038) + * @deprecated Use android.telephony.SmsMessage. + */ + @Deprecated public static final int ENCODING_UNKNOWN = 0; + + /** 7-bit encoding scheme (see TS 23.038) + * @deprecated Use android.telephony.SmsMessage. + */ + @Deprecated public static final int ENCODING_7BIT = 1; + + /** 8-bit encoding scheme (see TS 23.038) + * @deprecated Use android.telephony.SmsMessage. + */ + @Deprecated public static final int ENCODING_8BIT = 2; + + /** 16-bit encoding scheme (see TS 23.038) + * @deprecated Use android.telephony.SmsMessage. + */ + @Deprecated public static final int ENCODING_16BIT = 3; + + /** The maximum number of payload bytes per message + * @deprecated Use android.telephony.SmsMessage. + */ + @Deprecated public static final int MAX_USER_DATA_BYTES = 140; + + /** + * The maximum number of payload bytes per message if a user data header + * is present. This assumes the header only contains the + * CONCATENATED_8_BIT_REFERENCE element. + * + * @deprecated Use android.telephony.SmsMessage. + * @hide pending API Council approval to extend the public API + */ + @Deprecated public static final int MAX_USER_DATA_BYTES_WITH_HEADER = 134; + + /** The maximum number of payload septets per message + * @deprecated Use android.telephony.SmsMessage. + */ + @Deprecated public static final int MAX_USER_DATA_SEPTETS = 160; + + /** + * The maximum number of payload septets per message if a user data header + * is present. This assumes the header only contains the + * CONCATENATED_8_BIT_REFERENCE element. + * @deprecated Use android.telephony.SmsMessage. + */ + @Deprecated public static final int MAX_USER_DATA_SEPTETS_WITH_HEADER = 153; + + /** Contains actual SmsMessage. Only public for debugging and for framework layer. + * @deprecated Use android.telephony.SmsMessage. + * {@hide} + */ + @Deprecated public SmsMessageBase mWrappedSmsMessage; + + /** @deprecated Use android.telephony.SmsMessage. */ + @Deprecated + public static class SubmitPdu { + /** @deprecated Use android.telephony.SmsMessage. */ + @Deprecated public byte[] encodedScAddress; // Null if not applicable. + /** @deprecated Use android.telephony.SmsMessage. */ + @Deprecated public byte[] encodedMessage; + + //Constructor + /** @deprecated Use android.telephony.SmsMessage. */ + @Deprecated + public SubmitPdu() { + } + + /** @deprecated Use android.telephony.SmsMessage. + * {@hide} + */ + @Deprecated + protected SubmitPdu(SubmitPduBase spb) { + this.encodedMessage = spb.encodedMessage; + this.encodedScAddress = spb.encodedScAddress; + } + + /** @deprecated Use android.telephony.SmsMessage. */ + @Override + @Deprecated + public String toString() { + return "SubmitPdu: encodedScAddress = " + + Arrays.toString(encodedScAddress) + + ", encodedMessage = " + + Arrays.toString(encodedMessage); + } + } + + // Constructor + /** @deprecated Use android.telephony.SmsMessage. */ + @Deprecated + public SmsMessage() { + this(getSmsFacility()); + } + + private SmsMessage(SmsMessageBase smb) { + mWrappedSmsMessage = smb; + } + + /** + * Create an SmsMessage from a raw PDU. + * @deprecated Use android.telephony.SmsMessage. + */ + @Deprecated + public static SmsMessage createFromPdu(byte[] pdu) { + SmsMessageBase wrappedMessage; + int activePhone = TelephonyManager.getDefault().getCurrentPhoneType(); + + if (PHONE_TYPE_CDMA == activePhone) { + wrappedMessage = com.android.internal.telephony.cdma.SmsMessage.createFromPdu(pdu); + } else { + wrappedMessage = com.android.internal.telephony.gsm.SmsMessage.createFromPdu(pdu); + } + + return new SmsMessage(wrappedMessage); + } + + /** + * Get the TP-Layer-Length for the given SMS-SUBMIT PDU Basically, the + * length in bytes (not hex chars) less the SMSC header + * @deprecated Use android.telephony.SmsMessage. + */ + @Deprecated + public static int getTPLayerLengthForPDU(String pdu) { + int activePhone = TelephonyManager.getDefault().getCurrentPhoneType(); + + if (PHONE_TYPE_CDMA == activePhone) { + return com.android.internal.telephony.cdma.SmsMessage.getTPLayerLengthForPDU(pdu); + } else { + return com.android.internal.telephony.gsm.SmsMessage.getTPLayerLengthForPDU(pdu); + } + } + + /** + * Calculates the number of SMS's required to encode the message body and + * the number of characters remaining until the next message, given the + * current encoding. + * + * @param messageBody the message to encode + * @param use7bitOnly if true, characters that are not part of the GSM + * alphabet are counted as a single space char. If false, a + * messageBody containing non-GSM alphabet characters is calculated + * for 16-bit encoding. + * @return an int[4] with int[0] being the number of SMS's required, int[1] + * the number of code units used, and int[2] is the number of code + * units remaining until the next message. int[3] is the encoding + * type that should be used for the message. + * @deprecated Use android.telephony.SmsMessage. + */ + @Deprecated + public static int[] calculateLength(CharSequence messageBody, boolean use7bitOnly) { + GsmAlphabet.TextEncodingDetails ted = + com.android.internal.telephony.gsm.SmsMessage + .calculateLength(messageBody, use7bitOnly); + int ret[] = new int[4]; + ret[0] = ted.msgCount; + ret[1] = ted.codeUnitCount; + ret[2] = ted.codeUnitsRemaining; + ret[3] = ted.codeUnitSize; + return ret; + } + + /** + * Calculates the number of SMS's required to encode the message body and + * the number of characters remaining until the next message, given the + * current encoding. + * + * @param messageBody the message to encode + * @param use7bitOnly if true, characters that are not part of the GSM + * alphabet are counted as a single space char. If false, a + * messageBody containing non-GSM alphabet characters is calculated + * for 16-bit encoding. + * @return an int[4] with int[0] being the number of SMS's required, int[1] + * the number of code units used, and int[2] is the number of code + * units remaining until the next message. int[3] is the encoding + * type that should be used for the message. + * @deprecated Use android.telephony.SmsMessage. + */ + @Deprecated + public static int[] calculateLength(String messageBody, boolean use7bitOnly) { + return calculateLength((CharSequence)messageBody, use7bitOnly); + } + + /** + * Get an SMS-SUBMIT PDU for a destination address and a message + * + * @param scAddress Service Centre address. Null means use default. + * @return a <code>SubmitPdu</code> containing the encoded SC + * address, if applicable, and the encoded message. + * Returns null on encode error. + * @deprecated Use android.telephony.SmsMessage. + * @hide + */ + @Deprecated + public static SubmitPdu getSubmitPdu(String scAddress, + String destinationAddress, String message, + boolean statusReportRequested, byte[] header) { + SubmitPduBase spb; + int activePhone = TelephonyManager.getDefault().getCurrentPhoneType(); + + if (PHONE_TYPE_CDMA == activePhone) { + spb = com.android.internal.telephony.cdma.SmsMessage.getSubmitPdu(scAddress, + destinationAddress, message, statusReportRequested, + SmsHeader.fromByteArray(header)); + } else { + spb = com.android.internal.telephony.gsm.SmsMessage.getSubmitPdu(scAddress, + destinationAddress, message, statusReportRequested, header); + } + + return new SubmitPdu(spb); + } + + /** + * Get an SMS-SUBMIT PDU for a destination address and a message + * + * @param scAddress Service Centre address. Null means use default. + * @return a <code>SubmitPdu</code> containing the encoded SC + * address, if applicable, and the encoded message. + * Returns null on encode error. + * @deprecated Use android.telephony.SmsMessage. + */ + @Deprecated + public static SubmitPdu getSubmitPdu(String scAddress, + String destinationAddress, String message, boolean statusReportRequested) { + SubmitPduBase spb; + int activePhone = TelephonyManager.getDefault().getCurrentPhoneType(); + + if (PHONE_TYPE_CDMA == activePhone) { + spb = com.android.internal.telephony.cdma.SmsMessage.getSubmitPdu(scAddress, + destinationAddress, message, statusReportRequested, null); + } else { + spb = com.android.internal.telephony.gsm.SmsMessage.getSubmitPdu(scAddress, + destinationAddress, message, statusReportRequested); + } + + return new SubmitPdu(spb); + } + + /** + * Get an SMS-SUBMIT PDU for a data message to a destination address & port + * + * @param scAddress Service Centre address. null == use default + * @param destinationAddress the address of the destination for the message + * @param destinationPort the port to deliver the message to at the + * destination + * @param data the dat for the message + * @return a <code>SubmitPdu</code> containing the encoded SC + * address, if applicable, and the encoded message. + * Returns null on encode error. + * @deprecated Use android.telephony.SmsMessage. + */ + @Deprecated + public static SubmitPdu getSubmitPdu(String scAddress, + String destinationAddress, short destinationPort, byte[] data, + boolean statusReportRequested) { + SubmitPduBase spb; + int activePhone = TelephonyManager.getDefault().getCurrentPhoneType(); + + if (PHONE_TYPE_CDMA == activePhone) { + spb = com.android.internal.telephony.cdma.SmsMessage.getSubmitPdu(scAddress, + destinationAddress, destinationPort, data, statusReportRequested); + } else { + spb = com.android.internal.telephony.gsm.SmsMessage.getSubmitPdu(scAddress, + destinationAddress, destinationPort, data, statusReportRequested); + } + + return new SubmitPdu(spb); + } + + /** + * Returns the address of the SMS service center that relayed this message + * or null if there is none. + * @deprecated Use android.telephony.SmsMessage. + */ + @Deprecated + public String getServiceCenterAddress() { + return mWrappedSmsMessage.getServiceCenterAddress(); + } + + /** + * Returns the originating address (sender) of this SMS message in String + * form or null if unavailable + * @deprecated Use android.telephony.SmsMessage. + */ + @Deprecated + public String getOriginatingAddress() { + return mWrappedSmsMessage.getOriginatingAddress(); + } + + /** + * Returns the originating address, or email from address if this message + * was from an email gateway. Returns null if originating address + * unavailable. + * @deprecated Use android.telephony.SmsMessage. + */ + @Deprecated + public String getDisplayOriginatingAddress() { + return mWrappedSmsMessage.getDisplayOriginatingAddress(); + } + + /** + * Returns the message body as a String, if it exists and is text based. + * @return message body is there is one, otherwise null + * @deprecated Use android.telephony.SmsMessage. + */ + @Deprecated + public String getMessageBody() { + return mWrappedSmsMessage.getMessageBody(); + } + + /** + * Returns the class of this message. + * @deprecated Use android.telephony.SmsMessage. + */ + @Deprecated + public MessageClass getMessageClass() { + int index = mWrappedSmsMessage.getMessageClass().ordinal(); + + return MessageClass.values()[index]; + } + + /** + * Returns the message body, or email message body if this message was from + * an email gateway. Returns null if message body unavailable. + * @deprecated Use android.telephony.SmsMessage. + */ + @Deprecated + public String getDisplayMessageBody() { + return mWrappedSmsMessage.getDisplayMessageBody(); + } + + /** + * Unofficial convention of a subject line enclosed in parens empty string + * if not present + * @deprecated Use android.telephony.SmsMessage. + */ + @Deprecated + public String getPseudoSubject() { + return mWrappedSmsMessage.getPseudoSubject(); + } + + /** + * Returns the service centre timestamp in currentTimeMillis() format + * @deprecated Use android.telephony.SmsMessage. + */ + @Deprecated + public long getTimestampMillis() { + return mWrappedSmsMessage.getTimestampMillis(); + } + + /** + * Returns true if message is an email. + * + * @return true if this message came through an email gateway and email + * sender / subject / parsed body are available + * @deprecated Use android.telephony.SmsMessage. + */ + @Deprecated + public boolean isEmail() { + return mWrappedSmsMessage.isEmail(); + } + + /** + * @return if isEmail() is true, body of the email sent through the gateway. + * null otherwise + * @deprecated Use android.telephony.SmsMessage. + */ + @Deprecated + public String getEmailBody() { + return mWrappedSmsMessage.getEmailBody(); + } + + /** + * @return if isEmail() is true, email from address of email sent through + * the gateway. null otherwise + * @deprecated Use android.telephony.SmsMessage. + */ + @Deprecated + public String getEmailFrom() { + return mWrappedSmsMessage.getEmailFrom(); + } + + /** + * Get protocol identifier. + * @deprecated Use android.telephony.SmsMessage. + */ + @Deprecated + public int getProtocolIdentifier() { + return mWrappedSmsMessage.getProtocolIdentifier(); + } + + /** + * See TS 23.040 9.2.3.9 returns true if this is a "replace short message" SMS + * @deprecated Use android.telephony.SmsMessage. + */ + @Deprecated + public boolean isReplace() { + return mWrappedSmsMessage.isReplace(); + } + + /** + * Returns true for CPHS MWI toggle message. + * + * @return true if this is a CPHS MWI toggle message See CPHS 4.2 section B.4.2 + * @deprecated Use android.telephony.SmsMessage. + */ + @Deprecated + public boolean isCphsMwiMessage() { + return mWrappedSmsMessage.isCphsMwiMessage(); + } + + /** + * returns true if this message is a CPHS voicemail / message waiting + * indicator (MWI) clear message + * @deprecated Use android.telephony.SmsMessage. + */ + @Deprecated + public boolean isMWIClearMessage() { + return mWrappedSmsMessage.isMWIClearMessage(); + } + + /** + * returns true if this message is a CPHS voicemail / message waiting + * indicator (MWI) set message + * @deprecated Use android.telephony.SmsMessage. + */ + @Deprecated + public boolean isMWISetMessage() { + return mWrappedSmsMessage.isMWISetMessage(); + } + + /** + * returns true if this message is a "Message Waiting Indication Group: + * Discard Message" notification and should not be stored. + * @deprecated Use android.telephony.SmsMessage. + */ + @Deprecated + public boolean isMwiDontStore() { + return mWrappedSmsMessage.isMwiDontStore(); + } + + /** + * returns the user data section minus the user data header if one was present. + * @deprecated Use android.telephony.SmsMessage. + */ + @Deprecated + public byte[] getUserData() { + return mWrappedSmsMessage.getUserData(); + } + + /* Not part of the SDK interface and only needed by specific classes: + protected SmsHeader getUserDataHeader() + */ + + /** + * Returns the raw PDU for the message. + * + * @return the raw PDU for the message. + * @deprecated Use android.telephony.SmsMessage. + */ + @Deprecated + public byte[] getPdu() { + return mWrappedSmsMessage.getPdu(); + } + + /** + * Returns the status of the message on the SIM (read, unread, sent, unsent). + * + * @return the status of the message on the SIM. These are: + * SmsManager.STATUS_ON_SIM_FREE + * SmsManager.STATUS_ON_SIM_READ + * SmsManager.STATUS_ON_SIM_UNREAD + * SmsManager.STATUS_ON_SIM_SEND + * SmsManager.STATUS_ON_SIM_UNSENT + * @deprecated Use android.telephony.SmsMessage and getStatusOnIcc instead. + */ + @Deprecated + public int getStatusOnSim() { + return mWrappedSmsMessage.getStatusOnIcc(); + } + + /** + * Returns the status of the message on the ICC (read, unread, sent, unsent). + * + * @return the status of the message on the ICC. These are: + * SmsManager.STATUS_ON_ICC_FREE + * SmsManager.STATUS_ON_ICC_READ + * SmsManager.STATUS_ON_ICC_UNREAD + * SmsManager.STATUS_ON_ICC_SEND + * SmsManager.STATUS_ON_ICC_UNSENT + * @deprecated Use android.telephony.SmsMessage. + * @hide + */ + @Deprecated + public int getStatusOnIcc() { + + return mWrappedSmsMessage.getStatusOnIcc(); + } + + /** + * Returns the record index of the message on the SIM (1-based index). + * @return the record index of the message on the SIM, or -1 if this + * SmsMessage was not created from a SIM SMS EF record. + * @deprecated Use android.telephony.SmsMessage and getIndexOnIcc instead. + */ + @Deprecated + public int getIndexOnSim() { + return mWrappedSmsMessage.getIndexOnIcc(); + } + + /** + * Returns the record index of the message on the ICC (1-based index). + * @return the record index of the message on the ICC, or -1 if this + * SmsMessage was not created from a ICC SMS EF record. + * @deprecated Use android.telephony.SmsMessage. + * @hide + */ + @Deprecated + public int getIndexOnIcc() { + + return mWrappedSmsMessage.getIndexOnIcc(); + } + + /** + * GSM: + * For an SMS-STATUS-REPORT message, this returns the status field from + * the status report. This field indicates the status of a previously + * submitted SMS, if requested. See TS 23.040, 9.2.3.15 TP-Status for a + * description of values. + * CDMA: + * For not interfering with status codes from GSM, the value is + * shifted to the bits 31-16. + * The value is composed of an error class (bits 25-24) and a status code (bits 23-16). + * Possible codes are described in C.S0015-B, v2.0, 4.5.21. + * + * @return 0 indicates the previously sent message was received. + * See TS 23.040, 9.9.2.3.15 and C.S0015-B, v2.0, 4.5.21 + * for a description of other possible values. + * @deprecated Use android.telephony.SmsMessage. + */ + @Deprecated + public int getStatus() { + return mWrappedSmsMessage.getStatus(); + } + + /** + * Return true iff the message is a SMS-STATUS-REPORT message. + * @deprecated Use android.telephony.SmsMessage. + */ + @Deprecated + public boolean isStatusReportMessage() { + return mWrappedSmsMessage.isStatusReportMessage(); + } + + /** + * Returns true iff the <code>TP-Reply-Path</code> bit is set in + * this message. + * @deprecated Use android.telephony.SmsMessage. + */ + @Deprecated + public boolean isReplyPathPresent() { + return mWrappedSmsMessage.isReplyPathPresent(); + } + + /** This method returns the reference to a specific + * SmsMessage object, which is used for accessing its static methods. + * @return Specific SmsMessage. + * @deprecated Use android.telephony.SmsMessage. + */ + @Deprecated + private static final SmsMessageBase getSmsFacility(){ + int activePhone = TelephonyManager.getDefault().getCurrentPhoneType(); + if (PHONE_TYPE_CDMA == activePhone) { + return new com.android.internal.telephony.cdma.SmsMessage(); + } else { + return new com.android.internal.telephony.gsm.SmsMessage(); + } + } +} diff --git a/android/telephony/ims/ImsService.java b/android/telephony/ims/ImsService.java new file mode 100644 index 00000000..9d91cc33 --- /dev/null +++ b/android/telephony/ims/ImsService.java @@ -0,0 +1,493 @@ +/* + * Copyright (C) 2017 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.telephony.ims; + +import android.annotation.SystemApi; +import android.app.PendingIntent; +import android.app.Service; +import android.content.Intent; +import android.content.pm.PackageManager; +import android.os.IBinder; +import android.os.Message; +import android.os.RemoteException; +import android.telephony.CarrierConfigManager; +import android.telephony.ims.feature.ImsFeature; +import android.telephony.ims.feature.MMTelFeature; +import android.telephony.ims.feature.RcsFeature; +import android.util.Log; +import android.util.SparseArray; + +import com.android.ims.ImsCallProfile; +import com.android.ims.internal.IImsCallSession; +import com.android.ims.internal.IImsCallSessionListener; +import com.android.ims.internal.IImsConfig; +import com.android.ims.internal.IImsEcbm; +import com.android.ims.internal.IImsFeatureStatusCallback; +import com.android.ims.internal.IImsMultiEndpoint; +import com.android.ims.internal.IImsRegistrationListener; +import com.android.ims.internal.IImsServiceController; +import com.android.ims.internal.IImsServiceFeatureListener; +import com.android.ims.internal.IImsUt; +import com.android.internal.annotations.VisibleForTesting; + +import static android.Manifest.permission.MODIFY_PHONE_STATE; +import static android.Manifest.permission.READ_PHONE_STATE; +import static android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE; + +/** + * Main ImsService implementation, which binds via the Telephony ImsResolver. Services that extend + * ImsService must register the service in their AndroidManifest to be detected by the framework. + * First, the application must declare that they use the "android.permission.BIND_IMS_SERVICE" + * permission. Then, the ImsService definition in the manifest must follow the following format: + * + * ... + * <service android:name=".EgImsService" + * android:permission="android.permission.BIND_IMS_SERVICE" > + * <!-- Apps must declare which features they support as metadata. The different categories are + * defined below. In this example, the RCS_FEATURE feature is supported. --> + * <meta-data android:name="android.telephony.ims.RCS_FEATURE" android:value="true" /> + * <intent-filter> + * <action android:name="android.telephony.ims.ImsService" /> + * </intent-filter> + * </service> + * ... + * + * The telephony framework will then bind to the ImsService you have defined in your manifest + * if you are either: + * 1) Defined as the default ImsService for the device in the device overlay using + * "config_ims_package". + * 2) Defined as a Carrier Provided ImsService in the Carrier Configuration using + * {@link CarrierConfigManager#KEY_CONFIG_IMS_PACKAGE_OVERRIDE_STRING}. + * + * The features that are currently supported in an ImsService are: + * - RCS_FEATURE: This ImsService implements the RcsFeature class. + * - MMTEL_FEATURE: This ImsService implements the MMTelFeature class. + * - EMERGENCY_MMTEL_FEATURE: This ImsService implements the MMTelFeature class and will be + * available to place emergency calls at all times. This MUST be implemented by the default + * ImsService provided in the device overlay. + * @hide + */ +@SystemApi +public class ImsService extends Service { + + private static final String LOG_TAG = "ImsService"; + + /** + * The intent that must be defined as an intent-filter in the AndroidManifest of the ImsService. + * @hide + */ + public static final String SERVICE_INTERFACE = "android.telephony.ims.ImsService"; + + // A map of slot Id -> Set of features corresponding to that slot. + private final SparseArray<SparseArray<ImsFeature>> mFeatures = new SparseArray<>(); + + /** + * @hide + */ + // Implements all supported features as a flat interface. + protected final IBinder mImsServiceController = new IImsServiceController.Stub() { + + @Override + public void createImsFeature(int slotId, int feature, IImsFeatureStatusCallback c) + throws RemoteException { + synchronized (mFeatures) { + enforceCallingOrSelfPermission(MODIFY_PHONE_STATE, "createImsFeature"); + onCreateImsFeatureInternal(slotId, feature, c); + } + } + + @Override + public void removeImsFeature(int slotId, int feature, IImsFeatureStatusCallback c) + throws RemoteException { + synchronized (mFeatures) { + enforceCallingOrSelfPermission(MODIFY_PHONE_STATE, "removeImsFeature"); + onRemoveImsFeatureInternal(slotId, feature, c); + } + } + + @Override + public int startSession(int slotId, int featureType, PendingIntent incomingCallIntent, + IImsRegistrationListener listener) throws RemoteException { + enforceCallingOrSelfPermission(MODIFY_PHONE_STATE, "startSession"); + synchronized (mFeatures) { + MMTelFeature feature = resolveMMTelFeature(slotId, featureType); + if (feature != null) { + return feature.startSession(incomingCallIntent, listener); + } + } + return 0; + } + + @Override + public void endSession(int slotId, int featureType, int sessionId) throws RemoteException { + synchronized (mFeatures) { + enforceCallingOrSelfPermission(MODIFY_PHONE_STATE, "endSession"); + MMTelFeature feature = resolveMMTelFeature(slotId, featureType); + if (feature != null) { + feature.endSession(sessionId); + } + } + } + + @Override + public boolean isConnected(int slotId, int featureType, int callSessionType, int callType) + throws RemoteException { + enforceReadPhoneStatePermission("isConnected"); + synchronized (mFeatures) { + MMTelFeature feature = resolveMMTelFeature(slotId, featureType); + if (feature != null) { + return feature.isConnected(callSessionType, callType); + } + } + return false; + } + + @Override + public boolean isOpened(int slotId, int featureType) throws RemoteException { + enforceReadPhoneStatePermission("isOpened"); + synchronized (mFeatures) { + MMTelFeature feature = resolveMMTelFeature(slotId, featureType); + if (feature != null) { + return feature.isOpened(); + } + } + return false; + } + + @Override + public int getFeatureStatus(int slotId, int featureType) throws RemoteException { + enforceReadPhoneStatePermission("getFeatureStatus"); + int status = ImsFeature.STATE_NOT_AVAILABLE; + synchronized (mFeatures) { + SparseArray<ImsFeature> featureMap = mFeatures.get(slotId); + if (featureMap != null) { + ImsFeature feature = getImsFeatureFromType(featureMap, featureType); + if (feature != null) { + status = feature.getFeatureState(); + } + } + } + return status; + } + + @Override + public void addRegistrationListener(int slotId, int featureType, + IImsRegistrationListener listener) throws RemoteException { + enforceReadPhoneStatePermission("addRegistrationListener"); + synchronized (mFeatures) { + MMTelFeature feature = resolveMMTelFeature(slotId, featureType); + if (feature != null) { + feature.addRegistrationListener(listener); + } + } + } + + @Override + public void removeRegistrationListener(int slotId, int featureType, + IImsRegistrationListener listener) throws RemoteException { + enforceReadPhoneStatePermission("removeRegistrationListener"); + synchronized (mFeatures) { + MMTelFeature feature = resolveMMTelFeature(slotId, featureType); + if (feature != null) { + feature.removeRegistrationListener(listener); + } + } + } + + @Override + public ImsCallProfile createCallProfile(int slotId, int featureType, int sessionId, + int callSessionType, int callType) throws RemoteException { + enforceCallingOrSelfPermission(MODIFY_PHONE_STATE, "createCallProfile"); + synchronized (mFeatures) { + MMTelFeature feature = resolveMMTelFeature(slotId, featureType); + if (feature != null) { + return feature.createCallProfile(sessionId, callSessionType, callType); + } + } + return null; + } + + @Override + public IImsCallSession createCallSession(int slotId, int featureType, int sessionId, + ImsCallProfile profile, IImsCallSessionListener listener) throws RemoteException { + enforceCallingOrSelfPermission(MODIFY_PHONE_STATE, "createCallSession"); + synchronized (mFeatures) { + MMTelFeature feature = resolveMMTelFeature(slotId, featureType); + if (feature != null) { + return feature.createCallSession(sessionId, profile, listener); + } + } + return null; + } + + @Override + public IImsCallSession getPendingCallSession(int slotId, int featureType, int sessionId, + String callId) throws RemoteException { + enforceCallingOrSelfPermission(MODIFY_PHONE_STATE, "getPendingCallSession"); + synchronized (mFeatures) { + MMTelFeature feature = resolveMMTelFeature(slotId, featureType); + if (feature != null) { + return feature.getPendingCallSession(sessionId, callId); + } + } + return null; + } + + @Override + public IImsUt getUtInterface(int slotId, int featureType) + throws RemoteException { + enforceCallingOrSelfPermission(MODIFY_PHONE_STATE, "getUtInterface"); + synchronized (mFeatures) { + MMTelFeature feature = resolveMMTelFeature(slotId, featureType); + if (feature != null) { + return feature.getUtInterface(); + } + } + return null; + } + + @Override + public IImsConfig getConfigInterface(int slotId, int featureType) + throws RemoteException { + enforceCallingOrSelfPermission(MODIFY_PHONE_STATE, "getConfigInterface"); + synchronized (mFeatures) { + MMTelFeature feature = resolveMMTelFeature(slotId, featureType); + if (feature != null) { + return feature.getConfigInterface(); + } + } + return null; + } + + @Override + public void turnOnIms(int slotId, int featureType) throws RemoteException { + enforceCallingOrSelfPermission(MODIFY_PHONE_STATE, "turnOnIms"); + synchronized (mFeatures) { + MMTelFeature feature = resolveMMTelFeature(slotId, featureType); + if (feature != null) { + feature.turnOnIms(); + } + } + } + + @Override + public void turnOffIms(int slotId, int featureType) throws RemoteException { + enforceCallingOrSelfPermission(MODIFY_PHONE_STATE, "turnOffIms"); + synchronized (mFeatures) { + MMTelFeature feature = resolveMMTelFeature(slotId, featureType); + if (feature != null) { + feature.turnOffIms(); + } + } + } + + @Override + public IImsEcbm getEcbmInterface(int slotId, int featureType) + throws RemoteException { + enforceCallingOrSelfPermission(MODIFY_PHONE_STATE, "getEcbmInterface"); + synchronized (mFeatures) { + MMTelFeature feature = resolveMMTelFeature(slotId, featureType); + if (feature != null) { + return feature.getEcbmInterface(); + } + } + return null; + } + + @Override + public void setUiTTYMode(int slotId, int featureType, int uiTtyMode, Message onComplete) + throws RemoteException { + enforceCallingOrSelfPermission(MODIFY_PHONE_STATE, "setUiTTYMode"); + synchronized (mFeatures) { + MMTelFeature feature = resolveMMTelFeature(slotId, featureType); + if (feature != null) { + feature.setUiTTYMode(uiTtyMode, onComplete); + } + } + } + + @Override + public IImsMultiEndpoint getMultiEndpointInterface(int slotId, int featureType) + throws RemoteException { + enforceCallingOrSelfPermission(MODIFY_PHONE_STATE, "getMultiEndpointInterface"); + synchronized (mFeatures) { + MMTelFeature feature = resolveMMTelFeature(slotId, featureType); + if (feature != null) { + return feature.getMultiEndpointInterface(); + } + } + return null; + } + + }; + + /** + * @hide + */ + @Override + public IBinder onBind(Intent intent) { + if(SERVICE_INTERFACE.equals(intent.getAction())) { + return mImsServiceController; + } + return null; + } + + /** + * Called from the ImsResolver to create the requested ImsFeature, as defined by the slot and + * featureType + * @param slotId An integer representing which SIM slot the ImsFeature is assigned to. + * @param featureType An integer representing the type of ImsFeature being created. This is + * defined in {@link ImsFeature}. + */ + // Be sure to lock on mFeatures before accessing this method + private void onCreateImsFeatureInternal(int slotId, int featureType, + IImsFeatureStatusCallback c) { + SparseArray<ImsFeature> featureMap = mFeatures.get(slotId); + if (featureMap == null) { + featureMap = new SparseArray<>(); + mFeatures.put(slotId, featureMap); + } + ImsFeature f = makeImsFeature(slotId, featureType); + if (f != null) { + f.setContext(this); + f.setSlotId(slotId); + f.addImsFeatureStatusCallback(c); + featureMap.put(featureType, f); + } + + } + /** + * Called from the ImsResolver to remove an existing ImsFeature, as defined by the slot and + * featureType. + * @param slotId An integer representing which SIM slot the ImsFeature is assigned to. + * @param featureType An integer representing the type of ImsFeature being removed. This is + * defined in {@link ImsFeature}. + */ + // Be sure to lock on mFeatures before accessing this method + private void onRemoveImsFeatureInternal(int slotId, int featureType, + IImsFeatureStatusCallback c) { + SparseArray<ImsFeature> featureMap = mFeatures.get(slotId); + if (featureMap == null) { + return; + } + + ImsFeature featureToRemove = getImsFeatureFromType(featureMap, featureType); + if (featureToRemove != null) { + featureMap.remove(featureType); + featureToRemove.notifyFeatureRemoved(slotId); + // Remove reference to Binder + featureToRemove.removeImsFeatureStatusCallback(c); + } + } + + // Be sure to lock on mFeatures before accessing this method + private MMTelFeature resolveMMTelFeature(int slotId, int featureType) { + SparseArray<ImsFeature> features = getImsFeatureMap(slotId); + MMTelFeature feature = null; + if (features != null) { + feature = resolveImsFeature(features, featureType, MMTelFeature.class); + } + return feature; + } + + // Be sure to lock on mFeatures before accessing this method + private <T extends ImsFeature> T resolveImsFeature(SparseArray<ImsFeature> set, int featureType, + Class<T> className) { + ImsFeature feature = getImsFeatureFromType(set, featureType); + if (feature == null) { + return null; + } + try { + return className.cast(feature); + } catch (ClassCastException e) + { + Log.e(LOG_TAG, "Can not cast ImsFeature! Exception: " + e.getMessage()); + } + return null; + } + + /** + * @hide + */ + @VisibleForTesting + // Be sure to lock on mFeatures before accessing this method + public SparseArray<ImsFeature> getImsFeatureMap(int slotId) { + return mFeatures.get(slotId); + } + + /** + * @hide + */ + @VisibleForTesting + // Be sure to lock on mFeatures before accessing this method + public ImsFeature getImsFeatureFromType(SparseArray<ImsFeature> set, int featureType) { + return set.get(featureType); + } + + private ImsFeature makeImsFeature(int slotId, int feature) { + switch (feature) { + case ImsFeature.EMERGENCY_MMTEL: { + return onCreateEmergencyMMTelImsFeature(slotId); + } + case ImsFeature.MMTEL: { + return onCreateMMTelImsFeature(slotId); + } + case ImsFeature.RCS: { + return onCreateRcsFeature(slotId); + } + } + // Tried to create feature that is not defined. + return null; + } + + /** + * Check for both READ_PHONE_STATE and READ_PRIVILEGED_PHONE_STATE. READ_PHONE_STATE is a + * public permission and READ_PRIVILEGED_PHONE_STATE is only granted to system apps. + */ + private void enforceReadPhoneStatePermission(String fn) { + if (checkCallingOrSelfPermission(READ_PRIVILEGED_PHONE_STATE) + != PackageManager.PERMISSION_GRANTED) { + enforceCallingOrSelfPermission(READ_PHONE_STATE, fn); + } + } + + /** + * @return An implementation of MMTelFeature that will be used by the system for MMTel + * functionality. Must be able to handle emergency calls at any time as well. + * @hide + */ + public MMTelFeature onCreateEmergencyMMTelImsFeature(int slotId) { + return null; + } + + /** + * @return An implementation of MMTelFeature that will be used by the system for MMTel + * functionality. + * @hide + */ + public MMTelFeature onCreateMMTelImsFeature(int slotId) { + return null; + } + + /** + * @return An implementation of RcsFeature that will be used by the system for RCS. + * @hide + */ + public RcsFeature onCreateRcsFeature(int slotId) { + return null; + } +} diff --git a/android/telephony/ims/ImsServiceProxy.java b/android/telephony/ims/ImsServiceProxy.java new file mode 100644 index 00000000..038e295d --- /dev/null +++ b/android/telephony/ims/ImsServiceProxy.java @@ -0,0 +1,335 @@ +/* + * Copyright (C) 2017 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.telephony.ims; + +import android.app.PendingIntent; +import android.os.IBinder; +import android.os.Message; +import android.os.RemoteException; +import android.telephony.ims.feature.IRcsFeature; +import android.telephony.ims.feature.ImsFeature; +import android.util.Log; + +import com.android.ims.ImsCallProfile; +import com.android.ims.internal.IImsCallSession; +import com.android.ims.internal.IImsCallSessionListener; +import com.android.ims.internal.IImsConfig; +import com.android.ims.internal.IImsEcbm; +import com.android.ims.internal.IImsMultiEndpoint; +import com.android.ims.internal.IImsRegistrationListener; +import com.android.ims.internal.IImsServiceController; +import com.android.ims.internal.IImsServiceFeatureListener; +import com.android.ims.internal.IImsUt; + +/** + * A container of the IImsServiceController binder, which implements all of the ImsFeatures that + * the platform currently supports: MMTel and RCS. + * @hide + */ + +public class ImsServiceProxy extends ImsServiceProxyCompat implements IRcsFeature { + + protected String LOG_TAG = "ImsServiceProxy"; + private final int mSupportedFeature; + + // Start by assuming the proxy is available for usage. + private boolean mIsAvailable = true; + // ImsFeature Status from the ImsService. Cached. + private Integer mFeatureStatusCached = null; + private ImsServiceProxy.INotifyStatusChanged mStatusCallback; + private final Object mLock = new Object(); + + public interface INotifyStatusChanged { + void notifyStatusChanged(); + } + + private final IImsServiceFeatureListener mListenerBinder = + new IImsServiceFeatureListener.Stub() { + + @Override + public void imsFeatureCreated(int slotId, int feature) throws RemoteException { + // The feature has been re-enabled. This may happen when the service crashes. + synchronized (mLock) { + if (!mIsAvailable && mSlotId == slotId && feature == mSupportedFeature) { + Log.i(LOG_TAG, "Feature enabled on slotId: " + slotId + " for feature: " + + feature); + mIsAvailable = true; + } + } + } + + @Override + public void imsFeatureRemoved(int slotId, int feature) throws RemoteException { + synchronized (mLock) { + if (mIsAvailable && mSlotId == slotId && feature == mSupportedFeature) { + Log.i(LOG_TAG, "Feature disabled on slotId: " + slotId + " for feature: " + + feature); + mIsAvailable = false; + } + } + } + + @Override + public void imsStatusChanged(int slotId, int feature, int status) throws RemoteException { + synchronized (mLock) { + Log.i(LOG_TAG, "imsStatusChanged: slot: " + slotId + " feature: " + feature + + " status: " + status); + if (mSlotId == slotId && feature == mSupportedFeature) { + mFeatureStatusCached = status; + if (mStatusCallback != null) { + mStatusCallback.notifyStatusChanged(); + } + } + } + } + }; + + public ImsServiceProxy(int slotId, IBinder binder, int featureType) { + super(slotId, binder); + mSupportedFeature = featureType; + } + + public ImsServiceProxy(int slotId, int featureType) { + super(slotId, null /*IBinder*/); + mSupportedFeature = featureType; + } + + public IImsServiceFeatureListener getListener() { + return mListenerBinder; + } + + public void setBinder(IBinder binder) { + mBinder = binder; + } + + @Override + public int startSession(PendingIntent incomingCallIntent, IImsRegistrationListener listener) + throws RemoteException { + synchronized (mLock) { + checkServiceIsReady(); + return getServiceInterface(mBinder).startSession(mSlotId, mSupportedFeature, + incomingCallIntent, listener); + } + } + + @Override + public void endSession(int sessionId) throws RemoteException { + synchronized (mLock) { + // Only check to make sure the binder connection still exists. This method should + // still be able to be called when the state is STATE_NOT_AVAILABLE. + checkBinderConnection(); + getServiceInterface(mBinder).endSession(mSlotId, mSupportedFeature, sessionId); + } + } + + @Override + public boolean isConnected(int callServiceType, int callType) + throws RemoteException { + synchronized (mLock) { + checkServiceIsReady(); + return getServiceInterface(mBinder).isConnected(mSlotId, mSupportedFeature, + callServiceType, callType); + } + } + + @Override + public boolean isOpened() throws RemoteException { + synchronized (mLock) { + checkServiceIsReady(); + return getServiceInterface(mBinder).isOpened(mSlotId, mSupportedFeature); + } + } + + @Override + public void addRegistrationListener(IImsRegistrationListener listener) + throws RemoteException { + synchronized (mLock) { + checkServiceIsReady(); + getServiceInterface(mBinder).addRegistrationListener(mSlotId, mSupportedFeature, + listener); + } + } + + @Override + public void removeRegistrationListener(IImsRegistrationListener listener) + throws RemoteException { + synchronized (mLock) { + checkServiceIsReady(); + getServiceInterface(mBinder).removeRegistrationListener(mSlotId, mSupportedFeature, + listener); + } + } + + @Override + public ImsCallProfile createCallProfile(int sessionId, int callServiceType, int callType) + throws RemoteException { + synchronized (mLock) { + checkServiceIsReady(); + return getServiceInterface(mBinder).createCallProfile(mSlotId, mSupportedFeature, + sessionId, callServiceType, callType); + } + } + + @Override + public IImsCallSession createCallSession(int sessionId, ImsCallProfile profile, + IImsCallSessionListener listener) throws RemoteException { + synchronized (mLock) { + checkServiceIsReady(); + return getServiceInterface(mBinder).createCallSession(mSlotId, mSupportedFeature, + sessionId, profile, listener); + } + } + + @Override + public IImsCallSession getPendingCallSession(int sessionId, String callId) + throws RemoteException { + synchronized (mLock) { + checkServiceIsReady(); + return getServiceInterface(mBinder).getPendingCallSession(mSlotId, mSupportedFeature, + sessionId, callId); + } + } + + @Override + public IImsUt getUtInterface() throws RemoteException { + synchronized (mLock) { + checkServiceIsReady(); + return getServiceInterface(mBinder).getUtInterface(mSlotId, mSupportedFeature); + } + } + + @Override + public IImsConfig getConfigInterface() throws RemoteException { + synchronized (mLock) { + checkServiceIsReady(); + return getServiceInterface(mBinder).getConfigInterface(mSlotId, mSupportedFeature); + } + } + + @Override + public void turnOnIms() throws RemoteException { + synchronized (mLock) { + checkServiceIsReady(); + getServiceInterface(mBinder).turnOnIms(mSlotId, mSupportedFeature); + } + } + + @Override + public void turnOffIms() throws RemoteException { + synchronized (mLock) { + checkServiceIsReady(); + getServiceInterface(mBinder).turnOffIms(mSlotId, mSupportedFeature); + } + } + + @Override + public IImsEcbm getEcbmInterface() throws RemoteException { + synchronized (mLock) { + checkServiceIsReady(); + return getServiceInterface(mBinder).getEcbmInterface(mSlotId, mSupportedFeature); + } + } + + @Override + public void setUiTTYMode(int uiTtyMode, Message onComplete) + throws RemoteException { + synchronized (mLock) { + checkServiceIsReady(); + getServiceInterface(mBinder).setUiTTYMode(mSlotId, mSupportedFeature, uiTtyMode, + onComplete); + } + } + + @Override + public IImsMultiEndpoint getMultiEndpointInterface() throws RemoteException { + synchronized (mLock) { + checkServiceIsReady(); + return getServiceInterface(mBinder).getMultiEndpointInterface(mSlotId, + mSupportedFeature); + } + } + + @Override + public int getFeatureStatus() { + synchronized (mLock) { + if (isBinderAlive() && mFeatureStatusCached != null) { + Log.i(LOG_TAG, "getFeatureStatus - returning cached: " + mFeatureStatusCached); + return mFeatureStatusCached; + } + } + // Don't synchronize on Binder call. + Integer status = retrieveFeatureStatus(); + synchronized (mLock) { + if (status == null) { + return ImsFeature.STATE_NOT_AVAILABLE; + } + // Cache only non-null value for feature status. + mFeatureStatusCached = status; + } + Log.i(LOG_TAG, "getFeatureStatus - returning " + status); + return status; + } + + /** + * Internal method used to retrieve the feature status from the corresponding ImsService. + */ + private Integer retrieveFeatureStatus() { + if (mBinder != null) { + try { + return getServiceInterface(mBinder).getFeatureStatus(mSlotId, mSupportedFeature); + } catch (RemoteException e) { + // Status check failed, don't update cache + } + } + return null; + } + + /** + * @param c Callback that will fire when the feature status has changed. + */ + public void setStatusCallback(INotifyStatusChanged c) { + mStatusCallback = c; + } + + /** + * @return Returns true if the ImsService is ready to take commands, false otherwise. If this + * method returns false, it doesn't mean that the Binder connection is not available (use + * {@link #isBinderReady()} to check that), but that the ImsService is not accepting commands + * at this time. + * + * For example, for DSDS devices, only one slot can be {@link ImsFeature#STATE_READY} to take + * commands at a time, so the other slot must stay at {@link ImsFeature#STATE_NOT_AVAILABLE}. + */ + public boolean isBinderReady() { + return isBinderAlive() && getFeatureStatus() == ImsFeature.STATE_READY; + } + + @Override + public boolean isBinderAlive() { + return mIsAvailable && mBinder != null && mBinder.isBinderAlive(); + } + + protected void checkServiceIsReady() throws RemoteException { + if (!isBinderReady()) { + throw new RemoteException("ImsServiceProxy is not ready to accept commands."); + } + } + + private IImsServiceController getServiceInterface(IBinder b) { + return IImsServiceController.Stub.asInterface(b); + } +} diff --git a/android/telephony/ims/ImsServiceProxyCompat.java b/android/telephony/ims/ImsServiceProxyCompat.java new file mode 100644 index 00000000..bbd5f027 --- /dev/null +++ b/android/telephony/ims/ImsServiceProxyCompat.java @@ -0,0 +1,183 @@ +/* + * Copyright (C) 2017 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.telephony.ims; + +import android.app.PendingIntent; +import android.os.IBinder; +import android.os.Message; +import android.os.RemoteException; +import android.telephony.ims.feature.IMMTelFeature; +import android.telephony.ims.feature.ImsFeature; + +import com.android.ims.ImsCallProfile; +import com.android.ims.internal.IImsCallSession; +import com.android.ims.internal.IImsCallSessionListener; +import com.android.ims.internal.IImsConfig; +import com.android.ims.internal.IImsEcbm; +import com.android.ims.internal.IImsMultiEndpoint; +import com.android.ims.internal.IImsRegistrationListener; +import com.android.ims.internal.IImsService; +import com.android.ims.internal.IImsUt; + +/** + * Compatibility class that implements the new ImsService IMMTelFeature interface, but + * uses the old IImsService interface to support older devices that implement the deprecated + * opt/net/ims interface. + * @hide + */ + +public class ImsServiceProxyCompat implements IMMTelFeature { + + private static final int SERVICE_ID = ImsFeature.MMTEL; + + protected final int mSlotId; + protected IBinder mBinder; + + public ImsServiceProxyCompat(int slotId, IBinder binder) { + mSlotId = slotId; + mBinder = binder; + } + + @Override + public int startSession(PendingIntent incomingCallIntent, IImsRegistrationListener listener) + throws RemoteException { + checkBinderConnection(); + return getServiceInterface(mBinder).open(mSlotId, ImsFeature.MMTEL, incomingCallIntent, + listener); + } + + @Override + public void endSession(int sessionId) throws RemoteException { + checkBinderConnection(); + getServiceInterface(mBinder).close(sessionId); + } + + @Override + public boolean isConnected(int callServiceType, int callType) + throws RemoteException { + checkBinderConnection(); + return getServiceInterface(mBinder).isConnected(SERVICE_ID, callServiceType, callType); + } + + @Override + public boolean isOpened() throws RemoteException { + checkBinderConnection(); + return getServiceInterface(mBinder).isOpened(SERVICE_ID); + } + + @Override + public void addRegistrationListener(IImsRegistrationListener listener) + throws RemoteException { + checkBinderConnection(); + getServiceInterface(mBinder).addRegistrationListener(mSlotId, ImsFeature.MMTEL, listener); + } + + @Override + public void removeRegistrationListener(IImsRegistrationListener listener) + throws RemoteException { + // Not Implemented in old ImsService. If the registration listener becomes invalid, the + // ImsService will remove. + } + + @Override + public ImsCallProfile createCallProfile(int sessionId, int callServiceType, int callType) + throws RemoteException { + checkBinderConnection(); + return getServiceInterface(mBinder).createCallProfile(sessionId, callServiceType, callType); + } + + @Override + public IImsCallSession createCallSession(int sessionId, ImsCallProfile profile, + IImsCallSessionListener listener) throws RemoteException { + checkBinderConnection(); + return getServiceInterface(mBinder).createCallSession(sessionId, profile, listener); + } + + @Override + public IImsCallSession getPendingCallSession(int sessionId, String callId) + throws RemoteException { + checkBinderConnection(); + return getServiceInterface(mBinder).getPendingCallSession(sessionId, callId); + } + + @Override + public IImsUt getUtInterface() throws RemoteException { + checkBinderConnection(); + return getServiceInterface(mBinder).getUtInterface(SERVICE_ID); + } + + @Override + public IImsConfig getConfigInterface() throws RemoteException { + checkBinderConnection(); + return getServiceInterface(mBinder).getConfigInterface(mSlotId); + } + + @Override + public void turnOnIms() throws RemoteException { + checkBinderConnection(); + getServiceInterface(mBinder).turnOnIms(mSlotId); + } + + @Override + public void turnOffIms() throws RemoteException { + checkBinderConnection(); + getServiceInterface(mBinder).turnOffIms(mSlotId); + } + + @Override + public IImsEcbm getEcbmInterface() throws RemoteException { + checkBinderConnection(); + return getServiceInterface(mBinder).getEcbmInterface(SERVICE_ID); + } + + @Override + public void setUiTTYMode(int uiTtyMode, Message onComplete) + throws RemoteException { + checkBinderConnection(); + getServiceInterface(mBinder).setUiTTYMode(SERVICE_ID, uiTtyMode, onComplete); + } + + @Override + public IImsMultiEndpoint getMultiEndpointInterface() throws RemoteException { + checkBinderConnection(); + return getServiceInterface(mBinder).getMultiEndpointInterface(SERVICE_ID); + } + + /** + * Base implementation, always returns READY for compatibility with old ImsService. + */ + public int getFeatureStatus() { + return ImsFeature.STATE_READY; + } + + /** + * @return false if the binder connection is no longer alive. + */ + public boolean isBinderAlive() { + return mBinder != null && mBinder.isBinderAlive(); + } + + private IImsService getServiceInterface(IBinder b) { + return IImsService.Stub.asInterface(b); + } + + protected void checkBinderConnection() throws RemoteException { + if (!isBinderAlive()) { + throw new RemoteException("ImsServiceProxy is not available for that feature."); + } + } +} diff --git a/android/telephony/ims/feature/IMMTelFeature.java b/android/telephony/ims/feature/IMMTelFeature.java new file mode 100644 index 00000000..d65e27eb --- /dev/null +++ b/android/telephony/ims/feature/IMMTelFeature.java @@ -0,0 +1,187 @@ +/* + * Copyright (C) 2017 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.telephony.ims.feature; + +import android.app.PendingIntent; +import android.os.Message; +import android.os.RemoteException; + +import com.android.ims.ImsCallProfile; +import com.android.ims.internal.IImsCallSession; +import com.android.ims.internal.IImsCallSessionListener; +import com.android.ims.internal.IImsConfig; +import com.android.ims.internal.IImsEcbm; +import com.android.ims.internal.IImsMultiEndpoint; +import com.android.ims.internal.IImsRegistrationListener; +import com.android.ims.internal.IImsUt; + +/** + * MMTel interface for an ImsService. When updating this interface, ensure that base implementations + * of your changes are also present in MMTelFeature for compatibility with older versions of the + * MMTel feature. + * @hide + */ + +public interface IMMTelFeature { + + /** + * Notifies the MMTel feature that you would like to start a session. This should always be + * done before making/receiving IMS calls. The IMS service will register the device to the + * operator's network with the credentials (from ISIM) periodically in order to receive calls + * from the operator's network. When the IMS service receives a new call, it will send out an + * intent with the provided action string. The intent contains a call ID extra + * {@link IImsCallSession#getCallId} and it can be used to take a call. + * + * @param incomingCallIntent When an incoming call is received, the IMS service will call + * {@link PendingIntent#send} to send back the intent to the caller with + * {@link #INCOMING_CALL_RESULT_CODE} as the result code and the intent to fill in the call ID; + * It cannot be null. + * @param listener To listen to IMS registration events; It cannot be null + * @return an integer (greater than 0) representing the session id associated with the session + * that has been started. + */ + int startSession(PendingIntent incomingCallIntent, IImsRegistrationListener listener) + throws RemoteException; + + /** + * End a previously started session using the associated sessionId. + * @param sessionId an integer (greater than 0) representing the ongoing session. See + * {@link #startSession}. + */ + void endSession(int sessionId) throws RemoteException; + + /** + * Checks if the IMS service has successfully registered to the IMS network with the specified + * service & call type. + * + * @param callServiceType a service type that is specified in {@link ImsCallProfile} + * {@link ImsCallProfile#SERVICE_TYPE_NORMAL} + * {@link ImsCallProfile#SERVICE_TYPE_EMERGENCY} + * @param callType a call type that is specified in {@link ImsCallProfile} + * {@link ImsCallProfile#CALL_TYPE_VOICE_N_VIDEO} + * {@link ImsCallProfile#CALL_TYPE_VOICE} + * {@link ImsCallProfile#CALL_TYPE_VT} + * {@link ImsCallProfile#CALL_TYPE_VS} + * @return true if the specified service id is connected to the IMS network; false otherwise + * @throws RemoteException + */ + boolean isConnected(int callServiceType, int callType) throws RemoteException; + + /** + * Checks if the specified IMS service is opened. + * + * @return true if the specified service id is opened; false otherwise + */ + boolean isOpened() throws RemoteException; + + /** + * Add a new registration listener for the client associated with the session Id. + * @param listener An implementation of IImsRegistrationListener. + */ + void addRegistrationListener(IImsRegistrationListener listener) + throws RemoteException; + + /** + * Remove a previously registered listener using {@link #addRegistrationListener} for the client + * associated with the session Id. + * @param listener A previously registered IImsRegistrationListener + */ + void removeRegistrationListener(IImsRegistrationListener listener) + throws RemoteException; + + /** + * Creates a {@link ImsCallProfile} from the service capabilities & IMS registration state. + * + * @param sessionId a session id which is obtained from {@link #startSession} + * @param callServiceType a service type that is specified in {@link ImsCallProfile} + * {@link ImsCallProfile#SERVICE_TYPE_NONE} + * {@link ImsCallProfile#SERVICE_TYPE_NORMAL} + * {@link ImsCallProfile#SERVICE_TYPE_EMERGENCY} + * @param callType a call type that is specified in {@link ImsCallProfile} + * {@link ImsCallProfile#CALL_TYPE_VOICE} + * {@link ImsCallProfile#CALL_TYPE_VT} + * {@link ImsCallProfile#CALL_TYPE_VT_TX} + * {@link ImsCallProfile#CALL_TYPE_VT_RX} + * {@link ImsCallProfile#CALL_TYPE_VT_NODIR} + * {@link ImsCallProfile#CALL_TYPE_VS} + * {@link ImsCallProfile#CALL_TYPE_VS_TX} + * {@link ImsCallProfile#CALL_TYPE_VS_RX} + * @return a {@link ImsCallProfile} object + */ + ImsCallProfile createCallProfile(int sessionId, int callServiceType, int callType) + throws RemoteException; + + /** + * Creates a {@link ImsCallSession} with the specified call profile. + * Use other methods, if applicable, instead of interacting with + * {@link ImsCallSession} directly. + * + * @param sessionId a session id which is obtained from {@link #startSession} + * @param profile a call profile to make the call + * @param listener An implementation of IImsCallSessionListener. + */ + IImsCallSession createCallSession(int sessionId, ImsCallProfile profile, + IImsCallSessionListener listener) throws RemoteException; + + /** + * Retrieves the call session associated with a pending call. + * + * @param sessionId a session id which is obtained from {@link #startSession} + * @param callId a call id to make the call + */ + IImsCallSession getPendingCallSession(int sessionId, String callId) throws RemoteException; + + /** + * @return The Ut interface for the supplementary service configuration. + */ + IImsUt getUtInterface() throws RemoteException; + + /** + * @return The config interface for IMS Configuration + */ + IImsConfig getConfigInterface() throws RemoteException; + + /** + * Signal the MMTelFeature to turn on IMS when it has been turned off using {@link #turnOffIms} + * @param sessionId a session id which is obtained from {@link #startSession} + */ + void turnOnIms() throws RemoteException; + + /** + * Signal the MMTelFeature to turn off IMS when it has been turned on using {@link #turnOnIms} + * @param sessionId a session id which is obtained from {@link #startSession} + */ + void turnOffIms() throws RemoteException; + + /** + * @return The Emergency call-back mode interface for emergency VoLTE calls that support it. + */ + IImsEcbm getEcbmInterface() throws RemoteException; + + /** + * Sets the current UI TTY mode for the MMTelFeature. + * @param uiTtyMode An integer containing the new UI TTY Mode. + * @param onComplete A {@link Message} to be used when the mode has been set. + * @throws RemoteException + */ + void setUiTTYMode(int uiTtyMode, Message onComplete) throws RemoteException; + + /** + * @return MultiEndpoint interface for DEP notifications + */ + IImsMultiEndpoint getMultiEndpointInterface() throws RemoteException; +} diff --git a/android/telephony/ims/feature/IRcsFeature.java b/android/telephony/ims/feature/IRcsFeature.java new file mode 100644 index 00000000..e28e1b38 --- /dev/null +++ b/android/telephony/ims/feature/IRcsFeature.java @@ -0,0 +1,26 @@ +/* + * Copyright (C) 2017 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.telephony.ims.feature; + +/** + * Feature interface that provides access to RCS APIs. Currently empty until RCS support is added + * in the framework. + * @hide + */ + +public interface IRcsFeature { +} diff --git a/android/telephony/ims/feature/ImsFeature.java b/android/telephony/ims/feature/ImsFeature.java new file mode 100644 index 00000000..9d880b73 --- /dev/null +++ b/android/telephony/ims/feature/ImsFeature.java @@ -0,0 +1,218 @@ +/* + * Copyright (C) 2017 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.telephony.ims.feature; + +import android.annotation.IntDef; +import android.content.Context; +import android.content.Intent; +import android.os.RemoteException; +import android.telephony.SubscriptionManager; +import android.util.Log; + +import com.android.ims.internal.IImsFeatureStatusCallback; + +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.util.ArrayList; +import java.util.Collections; +import java.util.Iterator; +import java.util.List; +import java.util.Set; +import java.util.WeakHashMap; + +/** + * Base class for all IMS features that are supported by the framework. + * @hide + */ +public abstract class ImsFeature { + + private static final String LOG_TAG = "ImsFeature"; + + /** + * Action to broadcast when ImsService is up. + * Internal use only. + * Only defined here separately compatibility purposes with the old ImsService. + * @hide + */ + public static final String ACTION_IMS_SERVICE_UP = + "com.android.ims.IMS_SERVICE_UP"; + + /** + * Action to broadcast when ImsService is down. + * Internal use only. + * Only defined here separately for compatibility purposes with the old ImsService. + * @hide + */ + public static final String ACTION_IMS_SERVICE_DOWN = + "com.android.ims.IMS_SERVICE_DOWN"; + + /** + * Part of the ACTION_IMS_SERVICE_UP or _DOWN intents. + * A long value; the phone ID corresponding to the IMS service coming up or down. + * Only defined here separately for compatibility purposes with the old ImsService. + * @hide + */ + public static final String EXTRA_PHONE_ID = "android:phone_id"; + + // Invalid feature value + public static final int INVALID = -1; + // ImsFeatures that are defined in the Manifests. Ensure that these values match the previously + // defined values in ImsServiceClass for compatibility purposes. + public static final int EMERGENCY_MMTEL = 0; + public static final int MMTEL = 1; + public static final int RCS = 2; + // Total number of features defined + public static final int MAX = 3; + + // Integer values defining the state of the ImsFeature at any time. + @IntDef(flag = true, + value = { + STATE_NOT_AVAILABLE, + STATE_INITIALIZING, + STATE_READY, + }) + @Retention(RetentionPolicy.SOURCE) + public @interface ImsState {} + public static final int STATE_NOT_AVAILABLE = 0; + public static final int STATE_INITIALIZING = 1; + public static final int STATE_READY = 2; + + private List<INotifyFeatureRemoved> mRemovedListeners = new ArrayList<>(); + private final Set<IImsFeatureStatusCallback> mStatusCallbacks = Collections.newSetFromMap( + new WeakHashMap<IImsFeatureStatusCallback, Boolean>()); + private @ImsState int mState = STATE_NOT_AVAILABLE; + private int mSlotId = SubscriptionManager.INVALID_SIM_SLOT_INDEX; + private Context mContext; + + public interface INotifyFeatureRemoved { + void onFeatureRemoved(int slotId); + } + + public void setContext(Context context) { + mContext = context; + } + + public void setSlotId(int slotId) { + mSlotId = slotId; + } + + public void addFeatureRemovedListener(INotifyFeatureRemoved listener) { + synchronized (mRemovedListeners) { + mRemovedListeners.add(listener); + } + } + + public void removeFeatureRemovedListener(INotifyFeatureRemoved listener) { + synchronized (mRemovedListeners) { + mRemovedListeners.remove(listener); + } + } + + // Not final for testing. + public void notifyFeatureRemoved(int slotId) { + synchronized (mRemovedListeners) { + mRemovedListeners.forEach(l -> l.onFeatureRemoved(slotId)); + onFeatureRemoved(); + } + } + + public int getFeatureState() { + return mState; + } + + protected final void setFeatureState(@ImsState int state) { + if (mState != state) { + mState = state; + notifyFeatureState(state); + } + } + + public void addImsFeatureStatusCallback(IImsFeatureStatusCallback c) { + if (c == null) { + return; + } + try { + // If we have just connected, send queued status. + c.notifyImsFeatureStatus(mState); + // Add the callback if the callback completes successfully without a RemoteException. + synchronized (mStatusCallbacks) { + mStatusCallbacks.add(c); + } + } catch (RemoteException e) { + Log.w(LOG_TAG, "Couldn't notify feature state: " + e.getMessage()); + } + } + + public void removeImsFeatureStatusCallback(IImsFeatureStatusCallback c) { + if (c == null) { + return; + } + synchronized (mStatusCallbacks) { + mStatusCallbacks.remove(c); + } + } + + /** + * Internal method called by ImsFeature when setFeatureState has changed. + * @param state + */ + private void notifyFeatureState(@ImsState int state) { + synchronized (mStatusCallbacks) { + for (Iterator<IImsFeatureStatusCallback> iter = mStatusCallbacks.iterator(); + iter.hasNext(); ) { + IImsFeatureStatusCallback callback = iter.next(); + try { + Log.i(LOG_TAG, "notifying ImsFeatureState=" + state); + callback.notifyImsFeatureStatus(state); + } catch (RemoteException e) { + // remove if the callback is no longer alive. + iter.remove(); + Log.w(LOG_TAG, "Couldn't notify feature state: " + e.getMessage()); + } + } + } + sendImsServiceIntent(state); + } + + /** + * Provide backwards compatibility using deprecated service UP/DOWN intents. + */ + private void sendImsServiceIntent(@ImsState int state) { + if(mContext == null || mSlotId == SubscriptionManager.INVALID_SIM_SLOT_INDEX) { + return; + } + Intent intent; + switch (state) { + case ImsFeature.STATE_NOT_AVAILABLE: + case ImsFeature.STATE_INITIALIZING: + intent = new Intent(ACTION_IMS_SERVICE_DOWN); + break; + case ImsFeature.STATE_READY: + intent = new Intent(ACTION_IMS_SERVICE_UP); + break; + default: + intent = new Intent(ACTION_IMS_SERVICE_DOWN); + } + intent.putExtra(EXTRA_PHONE_ID, mSlotId); + mContext.sendBroadcast(intent); + } + + /** + * Called when the feature is being removed and must be cleaned up. + */ + public abstract void onFeatureRemoved(); +} diff --git a/android/telephony/ims/feature/MMTelFeature.java b/android/telephony/ims/feature/MMTelFeature.java new file mode 100644 index 00000000..a71f0bf0 --- /dev/null +++ b/android/telephony/ims/feature/MMTelFeature.java @@ -0,0 +1,122 @@ +/* + * Copyright (C) 2017 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.telephony.ims.feature; + +import android.app.PendingIntent; +import android.os.Message; + +import com.android.ims.ImsCallProfile; +import com.android.ims.internal.IImsCallSession; +import com.android.ims.internal.IImsCallSessionListener; +import com.android.ims.internal.IImsConfig; +import com.android.ims.internal.IImsEcbm; +import com.android.ims.internal.IImsMultiEndpoint; +import com.android.ims.internal.IImsRegistrationListener; +import com.android.ims.internal.IImsUt; + +import java.util.ArrayList; +import java.util.List; + +/** + * Base implementation, which implements all methods in IMMTelFeature. Any class wishing to use + * MMTelFeature should extend this class and implement all methods that the service supports. + * + * @hide + */ + +public class MMTelFeature extends ImsFeature implements IMMTelFeature { + + @Override + public int startSession(PendingIntent incomingCallIntent, IImsRegistrationListener listener) { + return 0; + } + + @Override + public void endSession(int sessionId) { + } + + @Override + public boolean isConnected(int callSessionType, int callType) { + return false; + } + + @Override + public boolean isOpened() { + return false; + } + + @Override + public void addRegistrationListener(IImsRegistrationListener listener) { + } + + @Override + public void removeRegistrationListener(IImsRegistrationListener listener) { + } + + @Override + public ImsCallProfile createCallProfile(int sessionId, int callSessionType, int callType) { + return null; + } + + @Override + public IImsCallSession createCallSession(int sessionId, ImsCallProfile profile, + IImsCallSessionListener listener) { + return null; + } + + @Override + public IImsCallSession getPendingCallSession(int sessionId, String callId) { + return null; + } + + @Override + public IImsUt getUtInterface() { + return null; + } + + @Override + public IImsConfig getConfigInterface() { + return null; + } + + @Override + public void turnOnIms() { + } + + @Override + public void turnOffIms() { + } + + @Override + public IImsEcbm getEcbmInterface() { + return null; + } + + @Override + public void setUiTTYMode(int uiTtyMode, Message onComplete) { + } + + @Override + public IImsMultiEndpoint getMultiEndpointInterface() { + return null; + } + + @Override + public void onFeatureRemoved() { + + } +} diff --git a/android/telephony/ims/feature/RcsFeature.java b/android/telephony/ims/feature/RcsFeature.java new file mode 100644 index 00000000..9cddc1b9 --- /dev/null +++ b/android/telephony/ims/feature/RcsFeature.java @@ -0,0 +1,35 @@ +/* + * Copyright (C) 2017 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.telephony.ims.feature; + +/** + * Base implementation of the RcsFeature APIs. Any ImsService wishing to support RCS should extend + * this class and provide implementations of the IRcsFeature methods that they support. + * @hide + */ + +public class RcsFeature extends ImsFeature implements IRcsFeature { + + public RcsFeature() { + super(); + } + + @Override + public void onFeatureRemoved() { + + } +} diff --git a/android/telephony/ims/stub/ImsCallSessionImplBase.java b/android/telephony/ims/stub/ImsCallSessionImplBase.java new file mode 100644 index 00000000..80b2f789 --- /dev/null +++ b/android/telephony/ims/stub/ImsCallSessionImplBase.java @@ -0,0 +1,373 @@ +/* + * Copyright (C) 2017 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.telephony.ims.stub; + +import android.os.Message; +import android.os.RemoteException; + +import com.android.ims.ImsCallProfile; +import com.android.ims.ImsStreamMediaProfile; +import com.android.ims.internal.ImsCallSession; +import com.android.ims.internal.IImsCallSession; +import com.android.ims.internal.IImsCallSessionListener; +import com.android.ims.internal.IImsVideoCallProvider; + +/** + * Base implementation of IImsCallSession, which implements stub versions of the methods in the + * IImsCallSession AIDL. Override the methods that your implementation of ImsCallSession supports. + * + * DO NOT remove or change the existing APIs, only add new ones to this Base implementation or you + * will break other implementations of ImsCallSession maintained by other ImsServices. + * + * @hide + */ + +public class ImsCallSessionImplBase extends IImsCallSession.Stub { + + /** + * Closes the object. This object is not usable after being closed. + */ + @Override + public void close() throws RemoteException { + + } + + /** + * Gets the call ID of the session. + * + * @return the call ID + */ + @Override + public String getCallId() throws RemoteException { + return null; + } + + /** + * Gets the call profile that this session is associated with + * + * @return the {@link ImsCallProfile} that this session is associated with + */ + @Override + public ImsCallProfile getCallProfile() throws RemoteException { + return null; + } + + /** + * Gets the local call profile that this session is associated with + * + * @return the local {@link ImsCallProfile} that this session is associated with + */ + @Override + public ImsCallProfile getLocalCallProfile() throws RemoteException { + return null; + } + + /** + * Gets the remote call profile that this session is associated with + * + * @return the remote {@link ImsCallProfile} that this session is associated with + */ + @Override + public ImsCallProfile getRemoteCallProfile() throws RemoteException { + return null; + } + + /** + * Gets the value associated with the specified property of this session. + * + * @return the string value associated with the specified property + */ + @Override + public String getProperty(String name) throws RemoteException { + return null; + } + + /** + * Gets the session state. + * The value returned must be one of the states in {@link ImsCallSession.State}. + * + * @return the session state + */ + @Override + public int getState() throws RemoteException { + return ImsCallSession.State.INVALID; + } + + /** + * Checks if the session is in call. + * + * @return true if the session is in call, false otherwise + */ + @Override + public boolean isInCall() throws RemoteException { + return false; + } + + /** + * Sets the listener to listen to the session events. An {@link ImsCallSession} + * can only hold one listener at a time. Subsequent calls to this method + * override the previous listener. + * + * @param listener to listen to the session events of this object + */ + @Override + public void setListener(IImsCallSessionListener listener) throws RemoteException { + } + + /** + * Mutes or unmutes the mic for the active call. + * + * @param muted true if the call is muted, false otherwise + */ + @Override + public void setMute(boolean muted) throws RemoteException { + } + + /** + * Initiates an IMS call with the specified target and call profile. + * The session listener set in {@link #setListener} is called back upon defined session events. + * The method is only valid to call when the session state is in + * {@link ImsCallSession.State#IDLE}. + * + * @param callee dialed string to make the call to + * @param profile call profile to make the call with the specified service type, + * call type and media information + * @see {@link ImsCallSession.Listener#callSessionStarted}, + * {@link ImsCallSession.Listener#callSessionStartFailed} + */ + @Override + public void start(String callee, ImsCallProfile profile) throws RemoteException { + } + + /** + * Initiates an IMS call with the specified participants and call profile. + * The session listener set in {@link #setListener} is called back upon defined session events. + * The method is only valid to call when the session state is in + * {@link ImsCallSession.State#IDLE}. + * + * @param participants participant list to initiate an IMS conference call + * @param profile call profile to make the call with the specified service type, + * call type and media information + * @see {@link ImsCallSession.Listener#callSessionStarted}, + * {@link ImsCallSession.Listener#callSessionStartFailed} + */ + @Override + public void startConference(String[] participants, ImsCallProfile profile) + throws RemoteException { + } + + /** + * Accepts an incoming call or session update. + * + * @param callType call type specified in {@link ImsCallProfile} to be answered + * @param profile stream media profile {@link ImsStreamMediaProfile} to be answered + * @see {@link ImsCallSession.Listener#callSessionStarted} + */ + @Override + public void accept(int callType, ImsStreamMediaProfile profile) throws RemoteException { + } + + /** + * Rejects an incoming call or session update. + * + * @param reason reason code to reject an incoming call, defined in + * com.android.ims.ImsReasonInfo + * {@link ImsCallSession.Listener#callSessionStartFailed} + */ + @Override + public void reject(int reason) throws RemoteException { + } + + /** + * Terminates a call. + * + * @param reason reason code to terminate a call, defined in + * com.android.ims.ImsReasonInfo + * + * @see {@link ImsCallSession.Listener#callSessionTerminated} + */ + @Override + public void terminate(int reason) throws RemoteException { + } + + /** + * Puts a call on hold. When it succeeds, {@link ImsCallSession.Listener#callSessionHeld} is + * called. + * + * @param profile stream media profile {@link ImsStreamMediaProfile} to hold the call + * @see {@link ImsCallSession.Listener#callSessionHeld}, + * {@link ImsCallSession.Listener#callSessionHoldFailed} + */ + @Override + public void hold(ImsStreamMediaProfile profile) throws RemoteException { + } + + /** + * Continues a call that's on hold. When it succeeds, + * {@link ImsCallSession.Listener#callSessionResumed} is called. + * + * @param profile stream media profile with {@link ImsStreamMediaProfile} to resume the call + * @see {@link ImsCallSession.Listener#callSessionResumed}, + * {@link ImsCallSession.Listener#callSessionResumeFailed} + */ + @Override + public void resume(ImsStreamMediaProfile profile) throws RemoteException { + } + + /** + * Merges the active & hold call. When the merge starts, + * {@link ImsCallSession.Listener#callSessionMergeStarted} is called. + * {@link ImsCallSession.Listener#callSessionMergeComplete} is called if the merge is + * successful, and {@link ImsCallSession.Listener#callSessionMergeFailed} is called if the merge + * fails. + * + * @see {@link ImsCallSession.Listener#callSessionMergeStarted}, + * {@link ImsCallSession.Listener#callSessionMergeComplete}, + * {@link ImsCallSession.Listener#callSessionMergeFailed} + */ + @Override + public void merge() throws RemoteException { + } + + /** + * Updates the current call's properties (ex. call mode change: video upgrade / downgrade). + * + * @param callType call type specified in {@link ImsCallProfile} to be updated + * @param profile stream media profile {@link ImsStreamMediaProfile} to be updated + * @see {@link ImsCallSession.Listener#callSessionUpdated}, + * {@link ImsCallSession.Listener#callSessionUpdateFailed} + */ + @Override + public void update(int callType, ImsStreamMediaProfile profile) throws RemoteException { + } + + /** + * Extends this call to the conference call with the specified recipients. + * + * @param participants participant list to be invited to the conference call after extending the + * call + * @see {@link ImsCallSession.Listener#callSessionConferenceExtended}, + * {@link ImsCallSession.Listener#callSessionConferenceExtendFailed} + */ + @Override + public void extendToConference(String[] participants) throws RemoteException { + } + + /** + * Requests the conference server to invite an additional participants to the conference. + * + * @param participants participant list to be invited to the conference call + * @see {@link ImsCallSession.Listener#callSessionInviteParticipantsRequestDelivered}, + * {@link ImsCallSession.Listener#callSessionInviteParticipantsRequestFailed} + */ + @Override + public void inviteParticipants(String[] participants) throws RemoteException { + } + + /** + * Requests the conference server to remove the specified participants from the conference. + * + * @param participants participant list to be removed from the conference call + * @see {@link ImsCallSession.Listener#callSessionRemoveParticipantsRequestDelivered}, + * {@link ImsCallSession.Listener#callSessionRemoveParticipantsRequestFailed} + */ + @Override + public void removeParticipants(String[] participants) throws RemoteException { + } + + /** + * Sends a DTMF code. According to <a href="http://tools.ietf.org/html/rfc2833">RFC 2833</a>, + * event 0 ~ 9 maps to decimal value 0 ~ 9, '*' to 10, '#' to 11, event 'A' ~ 'D' to 12 ~ 15, + * and event flash to 16. Currently, event flash is not supported. + * + * @param c the DTMF to send. '0' ~ '9', 'A' ~ 'D', '*', '#' are valid inputs. + */ + @Override + public void sendDtmf(char c, Message result) throws RemoteException { + } + + /** + * Start a DTMF code. According to <a href="http://tools.ietf.org/html/rfc2833">RFC 2833</a>, + * event 0 ~ 9 maps to decimal value 0 ~ 9, '*' to 10, '#' to 11, event 'A' ~ 'D' to 12 ~ 15, + * and event flash to 16. Currently, event flash is not supported. + * + * @param c the DTMF to send. '0' ~ '9', 'A' ~ 'D', '*', '#' are valid inputs. + */ + @Override + public void startDtmf(char c) throws RemoteException { + } + + /** + * Stop a DTMF code. + */ + @Override + public void stopDtmf() throws RemoteException { + } + + /** + * Sends an USSD message. + * + * @param ussdMessage USSD message to send + */ + @Override + public void sendUssd(String ussdMessage) throws RemoteException { + } + + /** + * Returns a binder for the video call provider implementation contained within the IMS service + * process. This binder is used by the VideoCallProvider subclass in Telephony which + * intermediates between the propriety implementation and Telecomm/InCall. + */ + @Override + public IImsVideoCallProvider getVideoCallProvider() throws RemoteException { + return null; + } + + /** + * Determines if the current session is multiparty. + * @return {@code True} if the session is multiparty. + */ + @Override + public boolean isMultiparty() throws RemoteException { + return false; + } + + /** + * Device issues RTT modify request + * @param toProfile The profile with requested changes made + */ + @Override + public void sendRttModifyRequest(ImsCallProfile toProfile) { + } + + /** + * Device responds to Remote RTT modify request + * @param status true Accepted the request + * false Declined the request + */ + @Override + public void sendRttModifyResponse(boolean status) { + } + + /** + * Device sends RTT message + * @param rttMessage RTT message to be sent + */ + @Override + public void sendRttMessage(String rttMessage) { + } +} diff --git a/android/telephony/ims/stub/ImsCallSessionListenerImplBase.java b/android/telephony/ims/stub/ImsCallSessionListenerImplBase.java new file mode 100644 index 00000000..6c18935d --- /dev/null +++ b/android/telephony/ims/stub/ImsCallSessionListenerImplBase.java @@ -0,0 +1,298 @@ +/* + * Copyright (C) 2017 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.telephony.ims.stub; + +import com.android.ims.ImsCallProfile; +import com.android.ims.ImsConferenceState; +import com.android.ims.ImsReasonInfo; +import com.android.ims.ImsStreamMediaProfile; +import com.android.ims.ImsSuppServiceNotification; +import com.android.ims.internal.IImsCallSession; +import com.android.ims.internal.IImsCallSessionListener; + +/** + * Base implementation of ImsCallSessionListenerBase, which implements stub versions of the methods + * in the IImsCallSessionListener AIDL. Override the methods that your implementation of + * ImsCallSessionListener supports. + * + * DO NOT remove or change the existing APIs, only add new ones to this Base implementation or you + * will break other implementations of ImsCallSessionListener maintained by other ImsServices. + * + * @hide + */ +public class ImsCallSessionListenerImplBase extends IImsCallSessionListener.Stub { + /** + * Notifies the result of the basic session operation (setup / terminate). + */ + @Override + public void callSessionProgressing(IImsCallSession session, ImsStreamMediaProfile profile) { + // no-op + } + + @Override + public void callSessionStarted(IImsCallSession session, ImsCallProfile profile) { + // no-op + } + + @Override + public void callSessionStartFailed(IImsCallSession session, ImsReasonInfo reasonInfo) { + // no-op + } + + @Override + public void callSessionTerminated(IImsCallSession session, ImsReasonInfo reasonInfo) { + // no-op + } + + /** + * Notifies the result of the call hold/resume operation. + */ + @Override + public void callSessionHeld(IImsCallSession session, ImsCallProfile profile) { + // no-op + } + + @Override + public void callSessionHoldFailed(IImsCallSession session, ImsReasonInfo reasonInfo) { + // no-op + } + + @Override + public void callSessionHoldReceived(IImsCallSession session, ImsCallProfile profile) { + // no-op + } + + @Override + public void callSessionResumed(IImsCallSession session, ImsCallProfile profile) { + // no-op + } + + @Override + public void callSessionResumeFailed(IImsCallSession session, ImsReasonInfo reasonInfo) { + // no-op + } + + @Override + public void callSessionResumeReceived(IImsCallSession session, ImsCallProfile profile) { + // no-op + } + + /** + * Notifies the result of call merge operation. + */ + @Override + public void callSessionMergeStarted(IImsCallSession session, IImsCallSession newSession, + ImsCallProfile profile) { + // no-op + } + + @Override + public void callSessionMergeComplete(IImsCallSession session) { + // no-op + } + + @Override + public void callSessionMergeFailed(IImsCallSession session, ImsReasonInfo reasonInfo) { + // no-op + } + + /** + * Notifies the result of call upgrade / downgrade or any other call + * updates. + */ + @Override + public void callSessionUpdated(IImsCallSession session, ImsCallProfile profile) { + // no-op + } + + @Override + public void callSessionUpdateFailed(IImsCallSession session, ImsReasonInfo reasonInfo) { + // no-op + } + + @Override + public void callSessionUpdateReceived(IImsCallSession session, ImsCallProfile profile) { + // no-op + } + + /** + * Notifies the result of conference extension. + */ + @Override + public void callSessionConferenceExtended(IImsCallSession session, IImsCallSession newSession, + ImsCallProfile profile) { + // no-op + } + + @Override + public void callSessionConferenceExtendFailed(IImsCallSession session, + ImsReasonInfo reasonInfo) { + // no-op + } + + @Override + public void callSessionConferenceExtendReceived(IImsCallSession session, + IImsCallSession newSession, + ImsCallProfile profile) { + // no-op + } + + /** + * Notifies the result of the participant invitation / removal to/from the + * conference session. + */ + @Override + public void callSessionInviteParticipantsRequestDelivered(IImsCallSession session) { + // no-op + } + + @Override + public void callSessionInviteParticipantsRequestFailed(IImsCallSession session, + ImsReasonInfo reasonInfo) { + // no-op + } + + @Override + public void callSessionRemoveParticipantsRequestDelivered(IImsCallSession session) { + // no-op + } + + @Override + public void callSessionRemoveParticipantsRequestFailed(IImsCallSession session, + ImsReasonInfo reasonInfo) { + // no-op + } + + /** + * Notifies the changes of the conference info. the conference session. + */ + @Override + public void callSessionConferenceStateUpdated(IImsCallSession session, + ImsConferenceState state) { + // no-op + } + + /** + * Notifies the incoming USSD message. + */ + @Override + public void callSessionUssdMessageReceived(IImsCallSession session, int mode, + String ussdMessage) { + // no-op + } + + /** + * Notifies of a case where a {@link com.android.ims.internal.ImsCallSession} may potentially + * handover from one radio technology to another. + * @param session + * @param srcAccessTech The source radio access technology; one of the access technology + * constants defined in {@link android.telephony.ServiceState}. For + * example {@link android.telephony.ServiceState#RIL_RADIO_TECHNOLOGY_LTE}. + * @param targetAccessTech The target radio access technology; one of the access technology + * constants defined in {@link android.telephony.ServiceState}. For + * example {@link android.telephony.ServiceState#RIL_RADIO_TECHNOLOGY_LTE}. + */ + @Override + public void callSessionMayHandover(IImsCallSession session, int srcAccessTech, + int targetAccessTech) { + // no-op + } + + /** + * Notifies of handover information for this call + */ + @Override + public void callSessionHandover(IImsCallSession session, int srcAccessTech, + int targetAccessTech, + ImsReasonInfo reasonInfo) { + // no-op + } + + @Override + public void callSessionHandoverFailed(IImsCallSession session, int srcAccessTech, + int targetAccessTech, + ImsReasonInfo reasonInfo) { + // no-op + } + + /** + * Notifies the TTY mode change by remote party. + * + * @param mode one of the following: - + * {@link com.android.internal.telephony.Phone#TTY_MODE_OFF} - + * {@link com.android.internal.telephony.Phone#TTY_MODE_FULL} - + * {@link com.android.internal.telephony.Phone#TTY_MODE_HCO} - + * {@link com.android.internal.telephony.Phone#TTY_MODE_VCO} + */ + @Override + public void callSessionTtyModeReceived(IImsCallSession session, int mode) { + // no-op + } + + /** + * Notifies of a change to the multiparty state for this + * {@code ImsCallSession}. + * + * @param session The call session. + * @param isMultiParty {@code true} if the session became multiparty, + * {@code false} otherwise. + */ + @Override + public void callSessionMultipartyStateChanged(IImsCallSession session, boolean isMultiParty) { + // no-op + } + + /** + * Notifies the supplementary service information for the current session. + */ + @Override + public void callSessionSuppServiceReceived(IImsCallSession session, + ImsSuppServiceNotification suppSrvNotification) { + // no-op + } + + /** + * Received RTT modify request from Remote Party + * @param session The call session. + * @param callProfile ImsCallProfile with updated attribute + */ + @Override + public void callSessionRttModifyRequestReceived(IImsCallSession session, + ImsCallProfile callProfile) { + // no-op + } + + /** + * Received response for RTT modify request + * @param status true : Accepted the request + * false : Declined the request + */ + @Override + public void callSessionRttModifyResponseReceived(int status) { + // no -op + } + + /** + * Device received RTT message from Remote UE + * @param rttMessage RTT message received + */ + @Override + public void callSessionRttMessageReceived(String rttMessage) { + // no-op + } +} + diff --git a/android/telephony/ims/stub/ImsConfigImplBase.java b/android/telephony/ims/stub/ImsConfigImplBase.java new file mode 100644 index 00000000..5a4db99e --- /dev/null +++ b/android/telephony/ims/stub/ImsConfigImplBase.java @@ -0,0 +1,150 @@ +/* + * Copyright (C) 2017 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.telephony.ims.stub; + +import android.os.RemoteException; + +import com.android.ims.ImsConfig; +import com.android.ims.ImsConfigListener; +import com.android.ims.internal.IImsConfig; + +/** + * Base implementation of ImsConfig, which implements stub versions of the methods + * in the IImsConfig AIDL. Override the methods that your implementation of ImsConfig supports. + * + * DO NOT remove or change the existing APIs, only add new ones to this Base implementation or you + * will break other implementations of ImsConfig maintained by other ImsServices. + * + * Provides APIs to get/set the IMS service feature/capability/parameters. + * The config items include: + * 1) Items provisioned by the operator. + * 2) Items configured by user. Mainly service feature class. + * + * @hide + */ + +public class ImsConfigImplBase extends IImsConfig.Stub { + + /** + * Gets the value for ims service/capabilities parameters from the provisioned + * value storage. Synchronous blocking call. + * + * @param item, as defined in com.android.ims.ImsConfig#ConfigConstants. + * @return value in Integer format. + */ + @Override + public int getProvisionedValue(int item) throws RemoteException { + return -1; + } + + /** + * Gets the value for ims service/capabilities parameters from the provisioned + * value storage. Synchronous blocking call. + * + * @param item, as defined in com.android.ims.ImsConfig#ConfigConstants. + * @return value in String format. + */ + @Override + public String getProvisionedStringValue(int item) throws RemoteException { + return null; + } + + /** + * Sets the value for IMS service/capabilities parameters by the operator device + * management entity. It sets the config item value in the provisioned storage + * from which the master value is derived. Synchronous blocking call. + * + * @param item, as defined in com.android.ims.ImsConfig#ConfigConstants. + * @param value in Integer format. + * @return as defined in com.android.ims.ImsConfig#OperationStatusConstants. + */ + @Override + public int setProvisionedValue(int item, int value) throws RemoteException { + return ImsConfig.OperationStatusConstants.FAILED; + } + + /** + * Sets the value for IMS service/capabilities parameters by the operator device + * management entity. It sets the config item value in the provisioned storage + * from which the master value is derived. Synchronous blocking call. + * + * @param item as defined in com.android.ims.ImsConfig#ConfigConstants. + * @param value in String format. + * @return as defined in com.android.ims.ImsConfig#OperationStatusConstants. + */ + @Override + public int setProvisionedStringValue(int item, String value) throws RemoteException { + return ImsConfig.OperationStatusConstants.FAILED; + } + + /** + * Gets the value of the specified IMS feature item for specified network type. + * This operation gets the feature config value from the master storage (i.e. final + * value). Asynchronous non-blocking call. + * + * @param feature as defined in com.android.ims.ImsConfig#FeatureConstants. + * @param network as defined in android.telephony.TelephonyManager#NETWORK_TYPE_XXX. + * @param listener feature value returned asynchronously through listener. + */ + @Override + public void getFeatureValue(int feature, int network, ImsConfigListener listener) + throws RemoteException { + } + + /** + * Sets the value for IMS feature item for specified network type. + * This operation stores the user setting in setting db from which master db + * is derived. + * + * @param feature as defined in com.android.ims.ImsConfig#FeatureConstants. + * @param network as defined in android.telephony.TelephonyManager#NETWORK_TYPE_XXX. + * @param value as defined in com.android.ims.ImsConfig#FeatureValueConstants. + * @param listener, provided if caller needs to be notified for set result. + */ + @Override + public void setFeatureValue(int feature, int network, int value, ImsConfigListener listener) + throws RemoteException { + } + + /** + * Gets the value for IMS VoLTE provisioned. + * This should be the same as the operator provisioned value if applies. + */ + @Override + public boolean getVolteProvisioned() throws RemoteException { + return false; + } + + /** + * Gets the value for IMS feature item video quality. + * + * @param listener Video quality value returned asynchronously through listener. + */ + @Override + public void getVideoQuality(ImsConfigListener listener) throws RemoteException { + } + + /** + * Sets the value for IMS feature item video quality. + * + * @param quality, defines the value of video quality. + * @param listener, provided if caller needs to be notified for set result. + */ + @Override + public void setVideoQuality(int quality, ImsConfigListener listener) throws RemoteException { + } +} diff --git a/android/telephony/ims/stub/ImsEcbmImplBase.java b/android/telephony/ims/stub/ImsEcbmImplBase.java new file mode 100644 index 00000000..89f95ff0 --- /dev/null +++ b/android/telephony/ims/stub/ImsEcbmImplBase.java @@ -0,0 +1,51 @@ +/* + * Copyright (C) 2017 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.telephony.ims.stub; + +import android.os.RemoteException; + +import com.android.ims.internal.IImsEcbm; +import com.android.ims.internal.IImsEcbmListener; + +/** + * Base implementation of ImsEcbm, which implements stub versions of the methods + * in the IImsEcbm AIDL. Override the methods that your implementation of ImsEcbm supports. + * + * DO NOT remove or change the existing APIs, only add new ones to this Base implementation or you + * will break other implementations of ImsEcbm maintained by other ImsServices. + * + * @hide + */ + +public class ImsEcbmImplBase extends IImsEcbm.Stub { + + /** + * Sets the listener. + */ + @Override + public void setListener(IImsEcbmListener listener) throws RemoteException { + + } + + /** + * Requests Modem to come out of ECBM mode + */ + @Override + public void exitEmergencyCallbackMode() throws RemoteException { + + } +} diff --git a/android/telephony/ims/stub/ImsMultiEndpointImplBase.java b/android/telephony/ims/stub/ImsMultiEndpointImplBase.java new file mode 100644 index 00000000..05da9da4 --- /dev/null +++ b/android/telephony/ims/stub/ImsMultiEndpointImplBase.java @@ -0,0 +1,53 @@ +/* + * Copyright (C) 2017 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.telephony.ims.stub; + +import android.os.RemoteException; + +import com.android.ims.internal.IImsExternalCallStateListener; +import com.android.ims.internal.IImsMultiEndpoint; + +/** + * Base implementation of ImsMultiEndpoint, which implements stub versions of the methods + * in the IImsMultiEndpoint AIDL. Override the methods that your implementation of + * ImsMultiEndpoint supports. + * + * DO NOT remove or change the existing APIs, only add new ones to this Base implementation or you + * will break other implementations of ImsMultiEndpoint maintained by other ImsServices. + * + * @hide + */ + +public class ImsMultiEndpointImplBase extends IImsMultiEndpoint.Stub { + + /** + * Sets the listener. + */ + @Override + public void setListener(IImsExternalCallStateListener listener) throws RemoteException { + + } + + /** + * Query API to get the latest Dialog Event Package information + * Should be invoked only after setListener is done + */ + @Override + public void requestImsExternalCallStateInfo() throws RemoteException { + + } +} diff --git a/android/telephony/ims/stub/ImsStreamMediaSessionImplBase.java b/android/telephony/ims/stub/ImsStreamMediaSessionImplBase.java new file mode 100644 index 00000000..92f1a018 --- /dev/null +++ b/android/telephony/ims/stub/ImsStreamMediaSessionImplBase.java @@ -0,0 +1,40 @@ +/* + * Copyright (C) 2017 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.telephony.ims.stub; + +import android.os.RemoteException; + +import com.android.ims.internal.IImsStreamMediaSession; + +/** + * Base implementation of ImsStreamMediaSession, which implements stub versions of the methods + * in the IImsStreamMediaSession AIDL. Override the methods that your implementation of + * ImsStreamMediaSession supports. + * + * DO NOT remove or change the existing APIs, only add new ones to this Base implementation or you + * will break other implementations of ImsStreamMediaSession maintained by other ImsServices. + * + * @hide + */ + +public class ImsStreamMediaSessionImplBase extends IImsStreamMediaSession.Stub { + + @Override + public void close() throws RemoteException { + + } +} diff --git a/android/telephony/ims/stub/ImsUtImplBase.java b/android/telephony/ims/stub/ImsUtImplBase.java new file mode 100644 index 00000000..dc74094d --- /dev/null +++ b/android/telephony/ims/stub/ImsUtImplBase.java @@ -0,0 +1,174 @@ +/* + * Copyright (C) 2017 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.telephony.ims.stub; + +import android.os.Bundle; +import android.os.RemoteException; + +import com.android.ims.internal.IImsUt; +import com.android.ims.internal.IImsUtListener; + +/** + * Base implementation of ImsUt, which implements stub versions of the methods + * in the IImsUt AIDL. Override the methods that your implementation of ImsUt supports. + * + * DO NOT remove or change the existing APIs, only add new ones to this Base implementation or you + * will break other implementations of ImsUt maintained by other ImsServices. + * + * Provides the Ut interface interworking to get/set the supplementary service configuration. + * + * @hide + */ + +public class ImsUtImplBase extends IImsUt.Stub { + + /** + * Closes the object. This object is not usable after being closed. + */ + @Override + public void close() throws RemoteException { + + } + + /** + * Retrieves the configuration of the call barring. + */ + @Override + public int queryCallBarring(int cbType) throws RemoteException { + return -1; + } + + /** + * Retrieves the configuration of the call forward. + */ + @Override + public int queryCallForward(int condition, String number) throws RemoteException { + return -1; + } + + /** + * Retrieves the configuration of the call waiting. + */ + @Override + public int queryCallWaiting() throws RemoteException { + return -1; + } + + /** + * Retrieves the default CLIR setting. + */ + @Override + public int queryCLIR() throws RemoteException { + return -1; + } + + /** + * Retrieves the CLIP call setting. + */ + @Override + public int queryCLIP() throws RemoteException { + return -1; + } + + /** + * Retrieves the COLR call setting. + */ + @Override + public int queryCOLR() throws RemoteException { + return -1; + } + + /** + * Retrieves the COLP call setting. + */ + @Override + public int queryCOLP() throws RemoteException { + return -1; + } + + /** + * Updates or retrieves the supplementary service configuration. + */ + @Override + public int transact(Bundle ssInfo) throws RemoteException { + return -1; + } + + /** + * Updates the configuration of the call barring. + */ + @Override + public int updateCallBarring(int cbType, int action, String[] barrList) throws RemoteException { + return -1; + } + + /** + * Updates the configuration of the call forward. + */ + @Override + public int updateCallForward(int action, int condition, String number, int serviceClass, + int timeSeconds) throws RemoteException { + return 0; + } + + /** + * Updates the configuration of the call waiting. + */ + @Override + public int updateCallWaiting(boolean enable, int serviceClass) throws RemoteException { + return -1; + } + + /** + * Updates the configuration of the CLIR supplementary service. + */ + @Override + public int updateCLIR(int clirMode) throws RemoteException { + return -1; + } + + /** + * Updates the configuration of the CLIP supplementary service. + */ + @Override + public int updateCLIP(boolean enable) throws RemoteException { + return -1; + } + + /** + * Updates the configuration of the COLR supplementary service. + */ + @Override + public int updateCOLR(int presentation) throws RemoteException { + return -1; + } + + /** + * Updates the configuration of the COLP supplementary service. + */ + @Override + public int updateCOLP(boolean enable) throws RemoteException { + return -1; + } + + /** + * Sets the listener. + */ + @Override + public void setListener(IImsUtListener listener) throws RemoteException { + } +} diff --git a/android/telephony/ims/stub/ImsUtListenerImplBase.java b/android/telephony/ims/stub/ImsUtListenerImplBase.java new file mode 100644 index 00000000..b371efb6 --- /dev/null +++ b/android/telephony/ims/stub/ImsUtListenerImplBase.java @@ -0,0 +1,88 @@ +/* + * Copyright (C) 2017 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.telephony.ims.stub; + +import android.os.Bundle; +import android.os.RemoteException; + +import com.android.ims.ImsCallForwardInfo; +import com.android.ims.ImsReasonInfo; +import com.android.ims.ImsSsInfo; +import com.android.ims.internal.IImsUt; +import com.android.ims.internal.IImsUtListener; + +/** + * Base implementation of ImsUtListener, which implements stub versions of the methods + * in the IImsUtListener AIDL. Override the methods that your implementation of + * ImsUtListener supports. + * + * DO NOT remove or change the existing APIs, only add new ones to this Base implementation or you + * will break other implementations of ImsUtListener maintained by other ImsServices. + * + * @hide + */ + +public class ImsUtListenerImplBase extends IImsUtListener.Stub { + + /** + * Notifies the result of the supplementary service configuration udpate. + */ + @Override + public void utConfigurationUpdated(IImsUt ut, int id) throws RemoteException { + } + + @Override + public void utConfigurationUpdateFailed(IImsUt ut, int id, ImsReasonInfo error) + throws RemoteException { + } + + /** + * Notifies the result of the supplementary service configuration query. + */ + @Override + public void utConfigurationQueried(IImsUt ut, int id, Bundle ssInfo) throws RemoteException { + } + + @Override + public void utConfigurationQueryFailed(IImsUt ut, int id, ImsReasonInfo error) + throws RemoteException { + } + + /** + * Notifies the status of the call barring supplementary service. + */ + @Override + public void utConfigurationCallBarringQueried(IImsUt ut, int id, ImsSsInfo[] cbInfo) + throws RemoteException { + } + + /** + * Notifies the status of the call forwarding supplementary service. + */ + @Override + public void utConfigurationCallForwardQueried(IImsUt ut, int id, ImsCallForwardInfo[] cfInfo) + throws RemoteException { + } + + /** + * Notifies the status of the call waiting supplementary service. + */ + @Override + public void utConfigurationCallWaitingQueried(IImsUt ut, int id, ImsSsInfo[] cwInfo) + throws RemoteException { + } +} diff --git a/android/telephony/mbms/DownloadProgressListener.java b/android/telephony/mbms/DownloadProgressListener.java new file mode 100644 index 00000000..d91e9ad2 --- /dev/null +++ b/android/telephony/mbms/DownloadProgressListener.java @@ -0,0 +1,48 @@ +/* + * 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.telephony.mbms; + +import android.os.RemoteException; + +/** + * A optional listener class used by download clients to track progress. + * @hide + */ +public class DownloadProgressListener extends IDownloadProgressListener.Stub { + /** + * Gives process callbacks for a given DownloadRequest. + * This is optionally specified when requesting a download and + * only lives while the app is running - it's unlikely to be useful for + * downloads far in the future. + * + * @param request a {@link DownloadRequest}, indicating which download is being referenced. + * @param fileInfo a {@link FileInfo} specifying the file to report progress on. Note that + * the request may result in many files being downloaded and the client + * may not have been able to get a list of them in advance. + * @param currentDownloadSize is the current amount downloaded. + * @param fullDownloadSize is the total number of bytes that make up the downloaded content. + * This may be different from the decoded final size, but is useful in gauging download + * progress. + * @param currentDecodedSize is the number of bytes that have been decoded. + * @param fullDecodedSize is the total number of bytes that make up the final decoded content. + */ + @Override + public void progress(DownloadRequest request, FileInfo fileInfo, + int currentDownloadSize, int fullDownloadSize, + int currentDecodedSize, int fullDecodedSize) throws RemoteException { + } +} diff --git a/android/telephony/mbms/DownloadRequest.java b/android/telephony/mbms/DownloadRequest.java new file mode 100644 index 00000000..eae9011e --- /dev/null +++ b/android/telephony/mbms/DownloadRequest.java @@ -0,0 +1,377 @@ +/* + * Copyright (C) 2017 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.telephony.mbms; + +import android.content.Intent; +import android.net.Uri; +import android.os.Parcel; +import android.os.Parcelable; +import android.util.Base64; +import android.util.Log; + +import java.io.ByteArrayInputStream; +import java.io.ByteArrayOutputStream; +import java.io.File; +import java.io.IOException; +import java.io.ObjectInputStream; +import java.io.ObjectOutputStream; +import java.io.Serializable; +import java.net.URISyntaxException; +import java.nio.charset.StandardCharsets; +import java.security.MessageDigest; +import java.security.NoSuchAlgorithmException; +import java.util.Objects; + +/** + * A Parcelable class describing a pending Cell-Broadcast download request + * @hide + */ +public class DownloadRequest implements Parcelable { + // Version code used to keep token calculation consistent. + private static final int CURRENT_VERSION = 1; + private static final String LOG_TAG = "MbmsDownloadRequest"; + + /** + * Maximum permissible length for the app's download-completion intent, when serialized via + * {@link Intent#toUri(int)}. + */ + public static final int MAX_APP_INTENT_SIZE = 50000; + + /** + * Maximum permissible length for the app's destination path, when serialized via + * {@link Uri#toString()}. + */ + public static final int MAX_DESTINATION_URI_SIZE = 50000; + + /** @hide */ + private static class OpaqueDataContainer implements Serializable { + private final String destinationUri; + private final String appIntent; + private final int version; + + public OpaqueDataContainer(String destinationUri, String appIntent, int version) { + this.destinationUri = destinationUri; + this.appIntent = appIntent; + this.version = version; + } + } + + public static class Builder { + private String fileServiceId; + private Uri source; + private Uri dest; + private int subscriptionId; + private String appIntent; + private int version = CURRENT_VERSION; + + /** + * Sets the service from which the download request to be built will download from. + * @param serviceInfo + * @return + */ + public Builder setServiceInfo(FileServiceInfo serviceInfo) { + fileServiceId = serviceInfo.getServiceId(); + return this; + } + + /** + * Set the service ID for the download request. For use by the middleware only. + * @hide + * TODO: systemapi + */ + public Builder setServiceId(String serviceId) { + fileServiceId = serviceId; + return this; + } + + /** + * Sets the source URI for the download request to be built. + * @param source + * @return + */ + public Builder setSource(Uri source) { + this.source = source; + return this; + } + + /** + * Sets the destination URI for the download request to be built. The middleware should + * not set this directly. + * @param dest A URI obtained from {@link Uri#fromFile(File)}, denoting the requested + * final destination of the download. + * @return + */ + public Builder setDest(Uri dest) { + if (dest.toString().length() > MAX_DESTINATION_URI_SIZE) { + throw new IllegalArgumentException("Destination uri must not exceed length " + + MAX_DESTINATION_URI_SIZE); + } + this.dest = dest; + return this; + } + + /** + * Set the subscription ID on which the file(s) should be downloaded. + * @param subscriptionId + * @return + */ + public Builder setSubscriptionId(int subscriptionId) { + this.subscriptionId = subscriptionId; + return this; + } + + /** + * Set the {@link Intent} that should be sent when the download completes or fails. This + * should be an intent with a explicit {@link android.content.ComponentName} targeted to a + * {@link android.content.BroadcastReceiver} in the app's package. + * + * The middleware should not use this method. + * @param intent + * @return + */ + public Builder setAppIntent(Intent intent) { + this.appIntent = intent.toUri(0); + if (this.appIntent.length() > MAX_APP_INTENT_SIZE) { + throw new IllegalArgumentException("App intent must not exceed length " + + MAX_APP_INTENT_SIZE); + } + return this; + } + + /** + * For use by the middleware to set the byte array of opaque data. The opaque data + * includes information about the download request that is used by the client app and the + * manager code, but is irrelevant to the middleware. + * @param data A byte array, the contents of which should have been originally obtained + * from {@link DownloadRequest#getOpaqueData()}. + * @return + * TODO: systemapi + * @hide + */ + public Builder setOpaqueData(byte[] data) { + try { + ObjectInputStream stream = new ObjectInputStream(new ByteArrayInputStream(data)); + OpaqueDataContainer dataContainer = (OpaqueDataContainer) stream.readObject(); + version = dataContainer.version; + appIntent = dataContainer.appIntent; + dest = Uri.parse(dataContainer.destinationUri); + } catch (IOException e) { + // Really should never happen + Log.e(LOG_TAG, "Got IOException trying to parse opaque data"); + throw new IllegalArgumentException(e); + } catch (ClassNotFoundException e) { + Log.e(LOG_TAG, "Got ClassNotFoundException trying to parse opaque data"); + throw new IllegalArgumentException(e); + } + return this; + } + + public DownloadRequest build() { + return new DownloadRequest(fileServiceId, source, dest, + subscriptionId, appIntent, version); + } + } + + private final String fileServiceId; + private final Uri sourceUri; + private final Uri destinationUri; + private final int subscriptionId; + private final String serializedResultIntentForApp; + private final int version; + + private DownloadRequest(String fileServiceId, + Uri source, Uri dest, + int sub, String appIntent, int version) { + this.fileServiceId = fileServiceId; + sourceUri = source; + destinationUri = dest; + subscriptionId = sub; + serializedResultIntentForApp = appIntent; + this.version = version; + } + + public static DownloadRequest copy(DownloadRequest other) { + return new DownloadRequest(other); + } + + private DownloadRequest(DownloadRequest dr) { + fileServiceId = dr.fileServiceId; + sourceUri = dr.sourceUri; + destinationUri = dr.destinationUri; + subscriptionId = dr.subscriptionId; + serializedResultIntentForApp = dr.serializedResultIntentForApp; + version = dr.version; + } + + private DownloadRequest(Parcel in) { + fileServiceId = in.readString(); + sourceUri = in.readParcelable(getClass().getClassLoader()); + destinationUri = in.readParcelable(getClass().getClassLoader()); + subscriptionId = in.readInt(); + serializedResultIntentForApp = in.readString(); + version = in.readInt(); + } + + public int describeContents() { + return 0; + } + + public void writeToParcel(Parcel out, int flags) { + out.writeString(fileServiceId); + out.writeParcelable(sourceUri, flags); + out.writeParcelable(destinationUri, flags); + out.writeInt(subscriptionId); + out.writeString(serializedResultIntentForApp); + out.writeInt(version); + } + + /** + * @return The ID of the file service to download from. + */ + public String getFileServiceId() { + return fileServiceId; + } + + /** + * @return The source URI to download from + */ + public Uri getSourceUri() { + return sourceUri; + } + + /** + * For use by the client app only. + * @return The URI of the final destination of the download. + */ + public Uri getDestinationUri() { + return destinationUri; + } + + /** + * @return The subscription ID on which to perform MBMS operations. + */ + public int getSubscriptionId() { + return subscriptionId; + } + + /** + * For internal use -- returns the intent to send to the app after download completion or + * failure. + * @hide + */ + public Intent getIntentForApp() { + try { + return Intent.parseUri(serializedResultIntentForApp, 0); + } catch (URISyntaxException e) { + return null; + } + } + + /** + * For use by the middleware only. The byte array returned from this method should be + * persisted and sent back to the app upon download completion or failure by passing it into + * {@link Builder#setOpaqueData(byte[])}. + * @return A byte array of opaque data to persist. + * @hide + * TODO: systemapi + */ + public byte[] getOpaqueData() { + try { + ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream(); + ObjectOutputStream stream = new ObjectOutputStream(byteArrayOutputStream); + OpaqueDataContainer container = new OpaqueDataContainer( + destinationUri.toString(), serializedResultIntentForApp, version); + stream.writeObject(container); + stream.flush(); + return byteArrayOutputStream.toByteArray(); + } catch (IOException e) { + // Really should never happen + Log.e(LOG_TAG, "Got IOException trying to serialize opaque data"); + return null; + } + } + + /** @hide */ + public int getVersion() { + return version; + } + + public static final Parcelable.Creator<DownloadRequest> CREATOR = + new Parcelable.Creator<DownloadRequest>() { + public DownloadRequest createFromParcel(Parcel in) { + return new DownloadRequest(in); + } + public DownloadRequest[] newArray(int size) { + return new DownloadRequest[size]; + } + }; + + /** + * @hide + */ + public boolean isMultipartDownload() { + // TODO: figure out what qualifies a request as a multipart download request. + return getSourceUri().getLastPathSegment() != null && + getSourceUri().getLastPathSegment().contains("*"); + } + + /** + * Retrieves the hash string that should be used as the filename when storing a token for + * this DownloadRequest. + * @hide + */ + public String getHash() { + MessageDigest digest; + try { + digest = MessageDigest.getInstance("SHA-256"); + } catch (NoSuchAlgorithmException e) { + throw new RuntimeException("Could not get sha256 hash object"); + } + if (version >= 1) { + // Hash the source URI, destination URI, and the app intent + digest.update(sourceUri.toString().getBytes(StandardCharsets.UTF_8)); + digest.update(destinationUri.toString().getBytes(StandardCharsets.UTF_8)); + digest.update(serializedResultIntentForApp.getBytes(StandardCharsets.UTF_8)); + } + // Add updates for future versions here + return Base64.encodeToString(digest.digest(), Base64.URL_SAFE | Base64.NO_WRAP); + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null) { + return false; + } + if (!(o instanceof DownloadRequest)) { + return false; + } + DownloadRequest request = (DownloadRequest) o; + return subscriptionId == request.subscriptionId && + version == request.version && + Objects.equals(fileServiceId, request.fileServiceId) && + Objects.equals(sourceUri, request.sourceUri) && + Objects.equals(destinationUri, request.destinationUri) && + Objects.equals(serializedResultIntentForApp, request.serializedResultIntentForApp); + } + + @Override + public int hashCode() { + return Objects.hash(fileServiceId, sourceUri, destinationUri, + subscriptionId, serializedResultIntentForApp, version); + } +} diff --git a/android/telephony/mbms/FileInfo.java b/android/telephony/mbms/FileInfo.java new file mode 100644 index 00000000..f97131dd --- /dev/null +++ b/android/telephony/mbms/FileInfo.java @@ -0,0 +1,86 @@ +/* + * 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.telephony.mbms; + +import android.net.Uri; +import android.os.Parcel; +import android.os.Parcelable; + +/** + * A Parcelable class Cell-Broadcast downloadable file information. + * @hide + */ +public class FileInfo implements Parcelable { + + /** + * The URI into the carriers infrastructure which points to this file. + * This is used internally but is also one of the few pieces of data about the content that is + * exposed and may be needed for disambiguation by the application. + */ + private final Uri uri; + + /** + * The mime type of the content. + */ + private final String mimeType; + + public static final Parcelable.Creator<FileInfo> CREATOR = + new Parcelable.Creator<FileInfo>() { + @Override + public FileInfo createFromParcel(Parcel source) { + return new FileInfo(source); + } + + @Override + public FileInfo[] newArray(int size) { + return new FileInfo[size]; + } + }; + + /** + * @hide + * TODO: systemapi + */ + public FileInfo(Uri uri, String mimeType) { + this.uri = uri; + this.mimeType = mimeType; + } + + private FileInfo(Parcel in) { + uri = in.readParcelable(null); + mimeType = in.readString(); + } + + @Override + public void writeToParcel(Parcel dest, int flags) { + dest.writeParcelable(uri, flags); + dest.writeString(mimeType); + } + + @Override + public int describeContents() { + return 0; + } + + public Uri getUri() { + return uri; + } + + public String getMimeType() { + return mimeType; + } +} diff --git a/android/telephony/mbms/FileServiceInfo.java b/android/telephony/mbms/FileServiceInfo.java new file mode 100644 index 00000000..8afe4d3c --- /dev/null +++ b/android/telephony/mbms/FileServiceInfo.java @@ -0,0 +1,77 @@ +/* + * 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.telephony.mbms; + +import android.os.Parcel; +import android.os.Parcelable; + +import java.util.ArrayList; +import java.util.Date; +import java.util.List; +import java.util.Locale; +import java.util.Map; + +/** + * A Parcelable class Cell-Broadcast downloadable file information. + * @hide + */ +public class FileServiceInfo extends ServiceInfo implements Parcelable { + private final List<FileInfo> files; + + /** @hide TODO: systemapi */ + public FileServiceInfo(Map<Locale, String> newNames, String newClassName, + List<Locale> newLocales, String newServiceId, Date start, Date end, + List<FileInfo> newFiles) { + super(newNames, newClassName, newLocales, newServiceId, start, end); + files = new ArrayList<>(newFiles); + } + + public static final Parcelable.Creator<FileServiceInfo> CREATOR = + new Parcelable.Creator<FileServiceInfo>() { + @Override + public FileServiceInfo createFromParcel(Parcel source) { + return new FileServiceInfo(source); + } + + @Override + public FileServiceInfo[] newArray(int size) { + return new FileServiceInfo[size]; + } + }; + + FileServiceInfo(Parcel in) { + super(in); + files = new ArrayList<FileInfo>(); + in.readList(files, null); + } + + @Override + public void writeToParcel(Parcel dest, int flags) { + super.writeToParcel(dest, flags); + dest.writeList(files); + } + + @Override + public int describeContents() { + return 0; + } + + public List<FileInfo> getFiles() { + return files; + } + +} diff --git a/android/telephony/mbms/InternalStreamingManagerCallback.java b/android/telephony/mbms/InternalStreamingManagerCallback.java new file mode 100644 index 00000000..b52df8c0 --- /dev/null +++ b/android/telephony/mbms/InternalStreamingManagerCallback.java @@ -0,0 +1,72 @@ +/* + * Copyright (C) 2017 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.telephony.mbms; + +import android.os.Handler; +import android.os.RemoteException; +import android.telephony.mbms.IMbmsStreamingManagerCallback; +import android.telephony.mbms.MbmsStreamingManagerCallback; +import android.telephony.mbms.StreamingServiceInfo; + +import java.util.List; + +/** @hide */ +public class InternalStreamingManagerCallback extends IMbmsStreamingManagerCallback.Stub { + private final Handler mHandler; + private final MbmsStreamingManagerCallback mAppCallback; + + public InternalStreamingManagerCallback(MbmsStreamingManagerCallback appCallback, + Handler handler) { + mAppCallback = appCallback; + mHandler = handler; + } + + @Override + public void error(int errorCode, String message) throws RemoteException { + mHandler.post(new Runnable() { + @Override + public void run() { + mAppCallback.onError(errorCode, message); + } + }); + } + + @Override + public void streamingServicesUpdated(List<StreamingServiceInfo> services) + throws RemoteException { + mHandler.post(new Runnable() { + @Override + public void run() { + mAppCallback.onStreamingServicesUpdated(services); + } + }); + } + + @Override + public void middlewareReady() throws RemoteException { + mHandler.post(new Runnable() { + @Override + public void run() { + mAppCallback.onMiddlewareReady(); + } + }); + } + + public Handler getHandler() { + return mHandler; + } +} diff --git a/android/telephony/mbms/InternalStreamingServiceCallback.java b/android/telephony/mbms/InternalStreamingServiceCallback.java new file mode 100644 index 00000000..bb337b27 --- /dev/null +++ b/android/telephony/mbms/InternalStreamingServiceCallback.java @@ -0,0 +1,81 @@ +/* + * Copyright (C) 2017 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.telephony.mbms; + +import android.os.Handler; +import android.os.RemoteException; + +/** @hide */ +public class InternalStreamingServiceCallback extends IStreamingServiceCallback.Stub { + private final StreamingServiceCallback mAppCallback; + private final Handler mHandler; + + public InternalStreamingServiceCallback(StreamingServiceCallback appCallback, Handler handler) { + mAppCallback = appCallback; + mHandler = handler; + } + + @Override + public void error(int errorCode, String message) throws RemoteException { + mHandler.post(new Runnable() { + @Override + public void run() { + mAppCallback.onError(errorCode, message); + } + }); + } + + @Override + public void streamStateUpdated(int state, int reason) throws RemoteException { + mHandler.post(new Runnable() { + @Override + public void run() { + mAppCallback.onStreamStateUpdated(state, reason); + } + }); + } + + @Override + public void mediaDescriptionUpdated() throws RemoteException { + mHandler.post(new Runnable() { + @Override + public void run() { + mAppCallback.onMediaDescriptionUpdated(); + } + }); + } + + @Override + public void broadcastSignalStrengthUpdated(int signalStrength) throws RemoteException { + mHandler.post(new Runnable() { + @Override + public void run() { + mAppCallback.onBroadcastSignalStrengthUpdated(signalStrength); + } + }); + } + + @Override + public void streamMethodUpdated(int methodType) throws RemoteException { + mHandler.post(new Runnable() { + @Override + public void run() { + mAppCallback.onStreamMethodUpdated(methodType); + } + }); + } +} diff --git a/android/telephony/mbms/MbmsDownloadManagerCallback.java b/android/telephony/mbms/MbmsDownloadManagerCallback.java new file mode 100644 index 00000000..17291d09 --- /dev/null +++ b/android/telephony/mbms/MbmsDownloadManagerCallback.java @@ -0,0 +1,64 @@ +/* + * 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.telephony.mbms; + +import android.os.RemoteException; +import android.telephony.MbmsDownloadManager; + +import java.util.List; + +/** + * A Parcelable class with Cell-Broadcast service information. + * @hide + */ +public class MbmsDownloadManagerCallback extends IMbmsDownloadManagerCallback.Stub { + + @Override + public void error(int errorCode, String message) throws RemoteException { + // default implementation empty + } + + /** + * Called to indicate published File Services have changed. + * + * This will only be called after the application has requested + * a list of file services and specified a service class list + * of interest AND the results of a subsequent getFileServices + * call with the same service class list would return different + * results. + * + * @param services a List of FileServiceInfos + * + */ + @Override + public void fileServicesUpdated(List<FileServiceInfo> services) throws RemoteException { + // default implementation empty + } + + /** + * Called to indicate that the middleware has been initialized and is ready. + * + * Before this method is called, calling any method on an instance of + * {@link android.telephony.MbmsDownloadManager} will result in an {@link MbmsException} + * being thrown with error code {@link MbmsException#ERROR_MIDDLEWARE_NOT_BOUND} + * or {@link MbmsException.GeneralErrors#ERROR_MIDDLEWARE_NOT_YET_READY} + */ + @Override + public void middlewareReady() throws RemoteException { + // default implementation empty + } +} diff --git a/android/telephony/mbms/MbmsDownloadReceiver.java b/android/telephony/mbms/MbmsDownloadReceiver.java new file mode 100644 index 00000000..ba7d120a --- /dev/null +++ b/android/telephony/mbms/MbmsDownloadReceiver.java @@ -0,0 +1,536 @@ +/* + * Copyright (C) 2017 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.telephony.mbms; + +import android.content.BroadcastReceiver; +import android.content.ContentResolver; +import android.content.Context; +import android.content.Intent; +import android.content.pm.ApplicationInfo; +import android.content.pm.PackageManager; +import android.net.Uri; +import android.os.Bundle; +import android.telephony.MbmsDownloadManager; +import android.telephony.mbms.vendor.VendorIntents; +import android.util.Log; + +import java.io.File; +import java.io.FileFilter; +import java.io.FileInputStream; +import java.io.FileOutputStream; +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; +import java.util.ArrayList; +import java.util.Iterator; +import java.util.List; +import java.util.Objects; +import java.util.UUID; + +/** + * @hide + */ +public class MbmsDownloadReceiver extends BroadcastReceiver { + public static final String DOWNLOAD_TOKEN_SUFFIX = ".download_token"; + public static final String MBMS_FILE_PROVIDER_META_DATA_KEY = "mbms-file-provider-authority"; + + /** + * TODO: @SystemApi all these result codes + * Indicates that the requested operation completed without error. + */ + public static final int RESULT_OK = 0; + + /** + * Indicates that the intent sent had an invalid action. This will be the result if + * {@link Intent#getAction()} returns anything other than + * {@link VendorIntents#ACTION_DOWNLOAD_RESULT_INTERNAL}, + * {@link VendorIntents#ACTION_FILE_DESCRIPTOR_REQUEST}, or + * {@link VendorIntents#ACTION_CLEANUP}. + * This is a fatal result code and no result extras should be expected. + */ + public static final int RESULT_INVALID_ACTION = 1; + + /** + * Indicates that the intent was missing some required extras. + * This is a fatal result code and no result extras should be expected. + */ + public static final int RESULT_MALFORMED_INTENT = 2; + + /** + * Indicates that the supplied value for {@link VendorIntents#EXTRA_TEMP_FILE_ROOT} + * does not match what the app has stored. + * This is a fatal result code and no result extras should be expected. + */ + public static final int RESULT_BAD_TEMP_FILE_ROOT = 3; + + /** + * Indicates that the manager was unable to move the completed download to its final location. + * This is a fatal result code and no result extras should be expected. + */ + public static final int RESULT_DOWNLOAD_FINALIZATION_ERROR = 4; + + /** + * Indicates that the manager was unable to generate one or more of the requested file + * descriptors. + * This is a non-fatal result code -- some file descriptors may still be generated, but there + * is no guarantee that they will be the same number as requested. + */ + public static final int RESULT_TEMP_FILE_GENERATION_ERROR = 5; + + private static final String LOG_TAG = "MbmsDownloadReceiver"; + private static final String TEMP_FILE_SUFFIX = ".embms.temp"; + private static final int MAX_TEMP_FILE_RETRIES = 5; + + + private String mFileProviderAuthorityCache = null; + private String mMiddlewarePackageNameCache = null; + + @Override + public void onReceive(Context context, Intent intent) { + if (!verifyIntentContents(context, intent)) { + setResultCode(RESULT_MALFORMED_INTENT); + return; + } + if (!Objects.equals(intent.getStringExtra(VendorIntents.EXTRA_TEMP_FILE_ROOT), + MbmsTempFileProvider.getEmbmsTempFileDir(context).getPath())) { + setResultCode(RESULT_BAD_TEMP_FILE_ROOT); + return; + } + + if (VendorIntents.ACTION_DOWNLOAD_RESULT_INTERNAL.equals(intent.getAction())) { + moveDownloadedFile(context, intent); + cleanupPostMove(context, intent); + } else if (VendorIntents.ACTION_FILE_DESCRIPTOR_REQUEST.equals(intent.getAction())) { + generateTempFiles(context, intent); + } else if (VendorIntents.ACTION_CLEANUP.equals(intent.getAction())) { + cleanupTempFiles(context, intent); + } else { + setResultCode(RESULT_INVALID_ACTION); + } + } + + private boolean verifyIntentContents(Context context, Intent intent) { + if (VendorIntents.ACTION_DOWNLOAD_RESULT_INTERNAL.equals(intent.getAction())) { + if (!intent.hasExtra(MbmsDownloadManager.EXTRA_RESULT)) { + Log.w(LOG_TAG, "Download result did not include a result code. Ignoring."); + return false; + } + if (!intent.hasExtra(VendorIntents.EXTRA_REQUEST)) { + Log.w(LOG_TAG, "Download result did not include the associated request. Ignoring."); + return false; + } + if (!intent.hasExtra(VendorIntents.EXTRA_TEMP_FILE_ROOT)) { + Log.w(LOG_TAG, "Download result did not include the temp file root. Ignoring."); + return false; + } + if (!intent.hasExtra(MbmsDownloadManager.EXTRA_FILE_INFO)) { + Log.w(LOG_TAG, "Download result did not include the associated file info. " + + "Ignoring."); + return false; + } + if (!intent.hasExtra(VendorIntents.EXTRA_FINAL_URI)) { + Log.w(LOG_TAG, "Download result did not include the path to the final " + + "temp file. Ignoring."); + return false; + } + DownloadRequest request = intent.getParcelableExtra(VendorIntents.EXTRA_REQUEST); + String expectedTokenFileName = request.getHash() + DOWNLOAD_TOKEN_SUFFIX; + File expectedTokenFile = new File( + MbmsUtils.getEmbmsTempFileDirForService(context, request.getFileServiceId()), + expectedTokenFileName); + if (!expectedTokenFile.exists()) { + Log.w(LOG_TAG, "Supplied download request does not match a token that we have. " + + "Expected " + expectedTokenFile); + return false; + } + } else if (VendorIntents.ACTION_FILE_DESCRIPTOR_REQUEST.equals(intent.getAction())) { + if (!intent.hasExtra(VendorIntents.EXTRA_SERVICE_INFO)) { + Log.w(LOG_TAG, "Temp file request did not include the associated service info." + + " Ignoring."); + return false; + } + if (!intent.hasExtra(VendorIntents.EXTRA_TEMP_FILE_ROOT)) { + Log.w(LOG_TAG, "Download result did not include the temp file root. Ignoring."); + return false; + } + } else if (VendorIntents.ACTION_CLEANUP.equals(intent.getAction())) { + if (!intent.hasExtra(VendorIntents.EXTRA_SERVICE_INFO)) { + Log.w(LOG_TAG, "Cleanup request did not include the associated service info." + + " Ignoring."); + return false; + } + if (!intent.hasExtra(VendorIntents.EXTRA_TEMP_FILE_ROOT)) { + Log.w(LOG_TAG, "Cleanup request did not include the temp file root. Ignoring."); + return false; + } + if (!intent.hasExtra(VendorIntents.EXTRA_TEMP_FILES_IN_USE)) { + Log.w(LOG_TAG, "Cleanup request did not include the list of temp files in use. " + + "Ignoring."); + return false; + } + } + return true; + } + + private void moveDownloadedFile(Context context, Intent intent) { + DownloadRequest request = intent.getParcelableExtra(VendorIntents.EXTRA_REQUEST); + Intent intentForApp = request.getIntentForApp(); + + int result = intent.getIntExtra(MbmsDownloadManager.EXTRA_RESULT, + MbmsDownloadManager.RESULT_CANCELLED); + intentForApp.putExtra(MbmsDownloadManager.EXTRA_RESULT, result); + + if (result != MbmsDownloadManager.RESULT_SUCCESSFUL) { + Log.i(LOG_TAG, "Download request indicated a failed download. Aborting."); + context.sendBroadcast(intentForApp); + return; + } + + Uri destinationUri = request.getDestinationUri(); + Uri finalTempFile = intent.getParcelableExtra(VendorIntents.EXTRA_FINAL_URI); + if (!verifyTempFilePath(context, request.getFileServiceId(), finalTempFile)) { + Log.w(LOG_TAG, "Download result specified an invalid temp file " + finalTempFile); + setResultCode(RESULT_DOWNLOAD_FINALIZATION_ERROR); + return; + } + + FileInfo completedFileInfo = + (FileInfo) intent.getParcelableExtra(MbmsDownloadManager.EXTRA_FILE_INFO); + String relativePath = calculateDestinationFileRelativePath(request, completedFileInfo); + + Uri finalFileLocation = moveTempFile(finalTempFile, destinationUri, relativePath); + if (finalFileLocation == null) { + Log.w(LOG_TAG, "Failed to move temp file to final destination"); + setResultCode(RESULT_DOWNLOAD_FINALIZATION_ERROR); + return; + } + intentForApp.putExtra(MbmsDownloadManager.EXTRA_COMPLETED_FILE_URI, finalFileLocation); + intentForApp.putExtra(MbmsDownloadManager.EXTRA_FILE_INFO, completedFileInfo); + + context.sendBroadcast(intentForApp); + setResultCode(RESULT_OK); + } + + private void cleanupPostMove(Context context, Intent intent) { + DownloadRequest request = intent.getParcelableExtra(VendorIntents.EXTRA_REQUEST); + if (request == null) { + Log.w(LOG_TAG, "Intent does not include a DownloadRequest. Ignoring."); + return; + } + + List<Uri> tempFiles = intent.getParcelableExtra(VendorIntents.EXTRA_TEMP_LIST); + if (tempFiles == null) { + return; + } + + for (Uri tempFileUri : tempFiles) { + if (verifyTempFilePath(context, request.getFileServiceId(), tempFileUri)) { + File tempFile = new File(tempFileUri.getSchemeSpecificPart()); + tempFile.delete(); + } + } + } + + private void generateTempFiles(Context context, Intent intent) { + FileServiceInfo serviceInfo = + intent.getParcelableExtra(VendorIntents.EXTRA_SERVICE_INFO); + if (serviceInfo == null) { + Log.w(LOG_TAG, "Temp file request did not include the associated service info. " + + "Ignoring."); + setResultCode(RESULT_MALFORMED_INTENT); + return; + } + int fdCount = intent.getIntExtra(VendorIntents.EXTRA_FD_COUNT, 0); + List<Uri> pausedList = intent.getParcelableExtra(VendorIntents.EXTRA_PAUSED_LIST); + + if (fdCount == 0 && (pausedList == null || pausedList.size() == 0)) { + Log.i(LOG_TAG, "No temp files actually requested. Ending."); + setResultCode(RESULT_OK); + setResultExtras(Bundle.EMPTY); + return; + } + + ArrayList<UriPathPair> freshTempFiles = + generateFreshTempFiles(context, serviceInfo, fdCount); + ArrayList<UriPathPair> pausedFiles = + generateUrisForPausedFiles(context, serviceInfo, pausedList); + + Bundle result = new Bundle(); + result.putParcelableArrayList(VendorIntents.EXTRA_FREE_URI_LIST, freshTempFiles); + result.putParcelableArrayList(VendorIntents.EXTRA_PAUSED_URI_LIST, pausedFiles); + setResultCode(RESULT_OK); + setResultExtras(result); + } + + private ArrayList<UriPathPair> generateFreshTempFiles(Context context, + FileServiceInfo serviceInfo, + int freshFdCount) { + File tempFileDir = MbmsUtils.getEmbmsTempFileDirForService(context, + serviceInfo.getServiceId()); + if (!tempFileDir.exists()) { + tempFileDir.mkdirs(); + } + + // Name the files with the template "N-UUID", where N is the request ID and UUID is a + // random uuid. + ArrayList<UriPathPair> result = new ArrayList<>(freshFdCount); + for (int i = 0; i < freshFdCount; i++) { + File tempFile = generateSingleTempFile(tempFileDir); + if (tempFile == null) { + setResultCode(RESULT_TEMP_FILE_GENERATION_ERROR); + Log.w(LOG_TAG, "Failed to generate a temp file. Moving on."); + continue; + } + Uri fileUri = Uri.fromFile(tempFile); + Uri contentUri = MbmsTempFileProvider.getUriForFile( + context, getFileProviderAuthorityCached(context), tempFile); + context.grantUriPermission(getMiddlewarePackageCached(context), contentUri, + Intent.FLAG_GRANT_READ_URI_PERMISSION | Intent.FLAG_GRANT_WRITE_URI_PERMISSION); + result.add(new UriPathPair(fileUri, contentUri)); + } + + return result; + } + + private static File generateSingleTempFile(File tempFileDir) { + int numTries = 0; + while (numTries < MAX_TEMP_FILE_RETRIES) { + numTries++; + String fileName = UUID.randomUUID() + TEMP_FILE_SUFFIX; + File tempFile = new File(tempFileDir, fileName); + try { + if (tempFile.createNewFile()) { + return tempFile.getCanonicalFile(); + } + } catch (IOException e) { + continue; + } + } + return null; + } + + private ArrayList<UriPathPair> generateUrisForPausedFiles(Context context, + FileServiceInfo serviceInfo, List<Uri> pausedFiles) { + if (pausedFiles == null) { + return new ArrayList<>(0); + } + ArrayList<UriPathPair> result = new ArrayList<>(pausedFiles.size()); + + for (Uri fileUri : pausedFiles) { + if (!verifyTempFilePath(context, serviceInfo.getServiceId(), fileUri)) { + Log.w(LOG_TAG, "Supplied file " + fileUri + " is not a valid temp file to resume"); + setResultCode(RESULT_TEMP_FILE_GENERATION_ERROR); + continue; + } + File tempFile = new File(fileUri.getSchemeSpecificPart()); + if (!tempFile.exists()) { + Log.w(LOG_TAG, "Supplied file " + fileUri + " does not exist."); + setResultCode(RESULT_TEMP_FILE_GENERATION_ERROR); + continue; + } + Uri contentUri = MbmsTempFileProvider.getUriForFile( + context, getFileProviderAuthorityCached(context), tempFile); + context.grantUriPermission(getMiddlewarePackageCached(context), contentUri, + Intent.FLAG_GRANT_READ_URI_PERMISSION | Intent.FLAG_GRANT_WRITE_URI_PERMISSION); + + result.add(new UriPathPair(fileUri, contentUri)); + } + return result; + } + + private void cleanupTempFiles(Context context, Intent intent) { + FileServiceInfo serviceInfo = + intent.getParcelableExtra(VendorIntents.EXTRA_SERVICE_INFO); + File tempFileDir = MbmsUtils.getEmbmsTempFileDirForService(context, + serviceInfo.getServiceId()); + final List<Uri> filesInUse = + intent.getParcelableArrayListExtra(VendorIntents.EXTRA_TEMP_FILES_IN_USE); + File[] filesToDelete = tempFileDir.listFiles(new FileFilter() { + @Override + public boolean accept(File file) { + File canonicalFile; + try { + canonicalFile = file.getCanonicalFile(); + } catch (IOException e) { + Log.w(LOG_TAG, "Got IOException canonicalizing " + file + ", not deleting."); + return false; + } + // Reject all files that don't match what we think a temp file should look like + // e.g. download tokens + if (!canonicalFile.getName().endsWith(TEMP_FILE_SUFFIX)) { + return false; + } + // If any of the files in use match the uri, return false to reject it from the + // list to delete. + Uri fileInUseUri = Uri.fromFile(canonicalFile); + return !filesInUse.contains(fileInUseUri); + } + }); + for (File fileToDelete : filesToDelete) { + fileToDelete.delete(); + } + } + + private static String calculateDestinationFileRelativePath(DownloadRequest request, + FileInfo info) { + List<String> filePathComponents = info.getUri().getPathSegments(); + List<String> requestPathComponents = request.getSourceUri().getPathSegments(); + Iterator<String> filePathIter = filePathComponents.iterator(); + Iterator<String> requestPathIter = requestPathComponents.iterator(); + + StringBuilder pathBuilder = new StringBuilder(); + // Iterate through the segments of the carrier's URI to the file, along with the segments + // of the source URI specified in the download request. The relative path is calculated + // as the tail of the file's URI that does not match the path segments in the source URI. + while (filePathIter.hasNext()) { + String currFilePathComponent = filePathIter.next(); + if (requestPathIter.hasNext()) { + String requestFilePathComponent = requestPathIter.next(); + if (requestFilePathComponent.equals(currFilePathComponent)) { + continue; + } + } + pathBuilder.append(currFilePathComponent); + pathBuilder.append('/'); + } + // remove the trailing slash + if (pathBuilder.length() > 0) { + pathBuilder.deleteCharAt(pathBuilder.length() - 1); + } + return pathBuilder.toString(); + } + + /* + * Moves a tempfile located at fromPath to a new location at toPath. If + * toPath is a directory, the destination file will be located at relativePath + * underneath toPath. + */ + private static Uri moveTempFile(Uri fromPath, Uri toPath, String relativePath) { + if (!ContentResolver.SCHEME_FILE.equals(fromPath.getScheme())) { + Log.w(LOG_TAG, "Moving source uri " + fromPath+ " does not have a file scheme"); + return null; + } + if (!ContentResolver.SCHEME_FILE.equals(toPath.getScheme())) { + Log.w(LOG_TAG, "Moving destination uri " + toPath + " does not have a file scheme"); + return null; + } + + File fromFile = new File(fromPath.getSchemeSpecificPart()); + File toFile = new File(toPath.getSchemeSpecificPart()); + if (toFile.isDirectory()) { + toFile = new File(toFile, relativePath); + } + toFile.getParentFile().mkdirs(); + + if (fromFile.renameTo(toFile)) { + return Uri.fromFile(toFile); + } else if (manualMove(fromFile, toFile)) { + return Uri.fromFile(toFile); + } + return null; + } + + private static boolean verifyTempFilePath(Context context, String serviceId, + Uri filePath) { + if (!ContentResolver.SCHEME_FILE.equals(filePath.getScheme())) { + Log.w(LOG_TAG, "Uri " + filePath + " does not have a file scheme"); + return false; + } + + String path = filePath.getSchemeSpecificPart(); + File tempFile = new File(path); + if (!tempFile.exists()) { + Log.w(LOG_TAG, "File at " + path + " does not exist."); + return false; + } + + if (!MbmsUtils.isContainedIn( + MbmsUtils.getEmbmsTempFileDirForService(context, serviceId), tempFile)) { + return false; + } + + return true; + } + + private String getFileProviderAuthorityCached(Context context) { + if (mFileProviderAuthorityCache != null) { + return mFileProviderAuthorityCache; + } + + mFileProviderAuthorityCache = getFileProviderAuthority(context); + return mFileProviderAuthorityCache; + } + + private static String getFileProviderAuthority(Context context) { + ApplicationInfo appInfo; + try { + appInfo = context.getPackageManager() + .getApplicationInfo(context.getPackageName(), PackageManager.GET_META_DATA); + } catch (PackageManager.NameNotFoundException e) { + throw new RuntimeException("Package manager couldn't find " + context.getPackageName()); + } + String authority = appInfo.metaData.getString(MBMS_FILE_PROVIDER_META_DATA_KEY); + if (authority == null) { + throw new RuntimeException("Must declare the file provider authority as meta data"); + } + return authority; + } + + private String getMiddlewarePackageCached(Context context) { + if (mMiddlewarePackageNameCache == null) { + mMiddlewarePackageNameCache = MbmsUtils.getMiddlewareServiceInfo(context, + MbmsDownloadManager.MBMS_DOWNLOAD_SERVICE_ACTION).packageName; + } + return mMiddlewarePackageNameCache; + } + + private static boolean manualMove(File src, File dst) { + InputStream in = null; + OutputStream out = null; + try { + if (!dst.exists()) { + dst.createNewFile(); + } + in = new FileInputStream(src); + out = new FileOutputStream(dst); + byte[] buffer = new byte[2048]; + int len; + do { + len = in.read(buffer); + out.write(buffer, 0, len); + } while (len > 0); + } catch (IOException e) { + Log.w(LOG_TAG, "Manual file move failed due to exception " + e); + if (dst.exists()) { + dst.delete(); + } + return false; + } finally { + try { + if (in != null) { + in.close(); + } + if (out != null) { + out.close(); + } + } catch (IOException e) { + Log.w(LOG_TAG, "Error closing streams: " + e); + } + } + return true; + } +} diff --git a/android/telephony/mbms/MbmsException.java b/android/telephony/mbms/MbmsException.java new file mode 100644 index 00000000..6de5a18e --- /dev/null +++ b/android/telephony/mbms/MbmsException.java @@ -0,0 +1,144 @@ +/* + * Copyright (C) 2017 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.telephony.mbms; + +public class MbmsException extends Exception { + /** Indicates that the operation was successful. */ + public static final int SUCCESS = 0; + + // Following errors are generated in the manager and should not be returned from the + // middleware + /** + * Indicates that either no MBMS middleware app is installed on the device or multiple + * middleware apps are installed on the device. + */ + public static final int ERROR_NO_UNIQUE_MIDDLEWARE = 1; + + /** + * Indicates that the app attempted to perform an operation on an instance of + * TODO: link android.telephony.MbmsDownloadManager or + * {@link android.telephony.MbmsStreamingManager} without being bound to the middleware. + */ + public static final int ERROR_MIDDLEWARE_NOT_BOUND = 2; + + /** Indicates that the middleware has died and the requested operation was not completed.*/ + public static final int ERROR_MIDDLEWARE_LOST = 3; + + /** + * Indicates errors that may be generated during initialization by the + * middleware. They are applicable to both streaming and file-download use-cases. + */ + public static class InitializationErrors { + private InitializationErrors() {} + /** + * Indicates that the app tried to create more than one instance each of + * {@link android.telephony.MbmsStreamingManager} or + * TODO: link android.telephony.MbmsDownloadManager + */ + public static final int ERROR_DUPLICATE_INITIALIZE = 101; + /** Indicates that the app is not authorized to access media via MBMS.*/ + public static final int ERROR_APP_PERMISSIONS_NOT_GRANTED = 102; + /** Indicates that the middleware was unable to initialize for this app. */ + public static final int ERROR_UNABLE_TO_INITIALIZE = 103; + } + + /** + * Indicates the errors that may occur at any point and are applicable to both + * streaming and file-download. + */ + public static class GeneralErrors { + private GeneralErrors() {} + /** + * Indicates that the app attempted to perform an operation before receiving notification + * that the middleware is ready via {@link MbmsStreamingManagerCallback#onMiddlewareReady()} + * or TODO: link MbmsDownloadManagerCallback#middlewareReady + */ + public static final int ERROR_MIDDLEWARE_NOT_YET_READY = 201; + /** + * Indicates that the middleware ran out of memory and was unable to complete the requested + * operation. + */ + public static final int ERROR_OUT_OF_MEMORY = 202; + /** + * Indicates that the requested operation failed due to the middleware being unavailable due + * to a transient condition. The app may retry the operation at a later time. + */ + public static final int ERROR_MIDDLEWARE_TEMPORARILY_UNAVAILABLE = 203; + /** + * Indicates that the requested operation was not performed due to being in emergency + * callback mode. + */ + public static final int ERROR_IN_E911 = 204; + /** Indicates that MBMS is not available due to the device being in roaming. */ + public static final int ERROR_NOT_CONNECTED_TO_HOME_CARRIER_LTE = 205; + /** Indicates that MBMS is not available due to a SIM read error. */ + public static final int ERROR_UNABLE_TO_READ_SIM = 206; + /** + * Indicates that MBMS is not available due to the inserted SIM being from an unsupported + * carrier. + */ + public static final int ERROR_CARRIER_CHANGE_NOT_ALLOWED = 207; + } + + /** + * Indicates the errors that are applicable only to the streaming use-case + */ + public static class StreamingErrors { + private StreamingErrors() {} + /** Indicates that the middleware cannot start a stream due to too many ongoing streams */ + public static final int ERROR_CONCURRENT_SERVICE_LIMIT_REACHED = 301; + + /** Indicates that the middleware was unable to start the streaming service */ + public static final int ERROR_UNABLE_TO_START_SERVICE = 302; + + /** + * Indicates that the app called + * {@link android.telephony.MbmsStreamingManager#startStreaming( + * StreamingServiceInfo, StreamingServiceCallback, android.os.Handler)} + * more than once for the same {@link StreamingServiceInfo}. + */ + public static final int ERROR_DUPLICATE_START_STREAM = 303; + } + + /** + * Indicates the errors that are applicable only to the file-download use-case + * TODO: unhide + * @hide + */ + public static class DownloadErrors { + /** + * Indicates that the app is not allowed to change the temp file root at this time due to + * outstanding download requests. + */ + public static final int ERROR_CANNOT_CHANGE_TEMP_FILE_ROOT = 401; + + /** Indicates that the middleware has no record of the supplied {@link DownloadRequest}. */ + public static final int ERROR_UNKNOWN_DOWNLOAD_REQUEST = 402; + } + + private final int mErrorCode; + + /** @hide */ + public MbmsException(int errorCode) { + super(); + mErrorCode = errorCode; + } + + public int getErrorCode() { + return mErrorCode; + } +} diff --git a/android/telephony/mbms/MbmsStreamingManagerCallback.java b/android/telephony/mbms/MbmsStreamingManagerCallback.java new file mode 100644 index 00000000..b31ffa72 --- /dev/null +++ b/android/telephony/mbms/MbmsStreamingManagerCallback.java @@ -0,0 +1,68 @@ +/* + * 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.telephony.mbms; + +import android.content.Context; +import android.os.RemoteException; +import android.telephony.MbmsStreamingManager; + +import java.util.List; + +/** + * A callback class that is used to receive information from the middleware on MBMS streaming + * services. An instance of this object should be passed into + * {@link android.telephony.MbmsStreamingManager#create(Context, MbmsStreamingManagerCallback)}. + */ +public class MbmsStreamingManagerCallback { + /** + * Called by the middleware when it has detected an error condition. The possible error codes + * are listed in {@link MbmsException}. + * @param errorCode The error code. + * @param message A human-readable message generated by the middleware for debugging purposes. + */ + public void onError(int errorCode, String message) { + // default implementation empty + } + + /** + * Called to indicate published Streaming Services have changed. + * + * This will only be called after the application has requested + * a list of streaming services and specified a service class list + * of interest AND the results of a subsequent getStreamServices + * call with the same service class list would return different + * results. + * + * @param services a List of StreamingServiceInfos + * + */ + public void onStreamingServicesUpdated(List<StreamingServiceInfo> services) { + // default implementation empty + } + + /** + * Called to indicate that the middleware has been initialized and is ready. + * + * Before this method is called, calling any method on an instance of + * {@link android.telephony.MbmsStreamingManager} will result in an {@link MbmsException} + * being thrown with error code {@link MbmsException#ERROR_MIDDLEWARE_NOT_BOUND} + * or {@link MbmsException.GeneralErrors#ERROR_MIDDLEWARE_NOT_YET_READY} + */ + public void onMiddlewareReady() { + // default implementation empty + } +} diff --git a/android/telephony/mbms/MbmsTempFileProvider.java b/android/telephony/mbms/MbmsTempFileProvider.java new file mode 100644 index 00000000..c4d033bf --- /dev/null +++ b/android/telephony/mbms/MbmsTempFileProvider.java @@ -0,0 +1,192 @@ +/* + * Copyright (C) 2017 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.telephony.mbms; + +import android.annotation.NonNull; +import android.annotation.Nullable; +import android.content.ContentProvider; +import android.content.ContentResolver; +import android.content.ContentValues; +import android.content.Context; +import android.content.SharedPreferences; +import android.content.pm.PackageManager; +import android.content.pm.ProviderInfo; +import android.database.Cursor; +import android.net.Uri; +import android.os.Bundle; +import android.os.ParcelFileDescriptor; + +import java.io.File; +import java.io.FileNotFoundException; +import java.io.IOException; +import java.util.Objects; + +/** + * @hide + */ +public class MbmsTempFileProvider extends ContentProvider { + public static final String DEFAULT_TOP_LEVEL_TEMP_DIRECTORY = "androidMbmsTempFileRoot"; + public static final String TEMP_FILE_ROOT_PREF_FILE_NAME = "MbmsTempFileRootPrefs"; + public static final String TEMP_FILE_ROOT_PREF_NAME = "mbms_temp_file_root"; + + private String mAuthority; + private Context mContext; + + @Override + public boolean onCreate() { + return true; + } + + @Override + public Cursor query(@NonNull Uri uri, @Nullable String[] projection, + @Nullable String selection, @Nullable String[] selectionArgs, + @Nullable String sortOrder) { + throw new UnsupportedOperationException("No querying supported"); + } + + @Override + public String getType(@NonNull Uri uri) { + // EMBMS temp files can contain arbitrary content. + return "application/octet-stream"; + } + + @Override + public Uri insert(@NonNull Uri uri, @Nullable ContentValues values) { + throw new UnsupportedOperationException("No inserting supported"); + } + + @Override + public int delete(@NonNull Uri uri, @Nullable String selection, + @Nullable String[] selectionArgs) { + throw new UnsupportedOperationException("No deleting supported"); + } + + @Override + public int update(@NonNull Uri uri, @Nullable ContentValues values, @Nullable String + selection, @Nullable String[] selectionArgs) { + throw new UnsupportedOperationException("No updating supported"); + } + + @Override + public ParcelFileDescriptor openFile(Uri uri, String mode) throws FileNotFoundException { + // ContentProvider has already checked granted permissions + final File file = getFileForUri(mContext, mAuthority, uri); + final int fileMode = ParcelFileDescriptor.parseMode(mode); + return ParcelFileDescriptor.open(file, fileMode); + } + + @Override + public void attachInfo(Context context, ProviderInfo info) { + super.attachInfo(context, info); + + // Sanity check our security + if (info.exported) { + throw new SecurityException("Provider must not be exported"); + } + if (!info.grantUriPermissions) { + throw new SecurityException("Provider must grant uri permissions"); + } + + mAuthority = info.authority; + mContext = context; + } + + public static Uri getUriForFile(Context context, String authority, File file) { + // Get the canonical path of the temp file + String filePath; + try { + filePath = file.getCanonicalPath(); + } catch (IOException e) { + throw new IllegalArgumentException("Could not get canonical path for file " + file); + } + + // Make sure the temp file is contained in the temp file directory as configured in the + // manifest + File tempFileDir = getEmbmsTempFileDir(context); + if (!MbmsUtils.isContainedIn(tempFileDir, file)) { + throw new IllegalArgumentException("File " + file + " is not contained in the temp " + + "file directory, which is " + tempFileDir); + } + + // Get the canonical path of the temp file directory + String tempFileDirPath; + try { + tempFileDirPath = tempFileDir.getCanonicalPath(); + } catch (IOException e) { + throw new RuntimeException( + "Could not get canonical path for temp file root dir " + tempFileDir); + } + + // Start at first char of path under temp file directory + String pathFragment; + if (tempFileDirPath.endsWith("/")) { + pathFragment = filePath.substring(tempFileDirPath.length()); + } else { + pathFragment = filePath.substring(tempFileDirPath.length() + 1); + } + + String encodedPath = Uri.encode(pathFragment); + return new Uri.Builder().scheme(ContentResolver.SCHEME_CONTENT) + .authority(authority).encodedPath(encodedPath).build(); + } + + public static File getFileForUri(Context context, String authority, Uri uri) + throws FileNotFoundException { + if (!ContentResolver.SCHEME_CONTENT.equals(uri.getScheme())) { + throw new IllegalArgumentException("Uri must have scheme content"); + } + if (!Objects.equals(authority, uri.getAuthority())) { + throw new IllegalArgumentException("Uri does not have a matching authority: " + + authority + ", " + uri.getAuthority()); + } + + String relPath = Uri.decode(uri.getEncodedPath()); + File file; + File tempFileDir; + + try { + tempFileDir = getEmbmsTempFileDir(context).getCanonicalFile(); + file = new File(tempFileDir, relPath).getCanonicalFile(); + } catch (IOException e) { + throw new FileNotFoundException("Could not resolve paths"); + } + + if (!file.getPath().startsWith(tempFileDir.getPath())) { + throw new SecurityException("Resolved path jumped beyond configured root"); + } + + return file; + } + + /** + * Returns a File for the directory used to store temp files for this app + */ + public static File getEmbmsTempFileDir(Context context) { + SharedPreferences prefs = context.getSharedPreferences(TEMP_FILE_ROOT_PREF_FILE_NAME, 0); + String storedTempFileRoot = prefs.getString(TEMP_FILE_ROOT_PREF_NAME, null); + try { + if (storedTempFileRoot != null) { + return new File(storedTempFileRoot).getCanonicalFile(); + } else { + return new File(context.getFilesDir(), DEFAULT_TOP_LEVEL_TEMP_DIRECTORY) + .getCanonicalFile(); + } + } catch (IOException e) { + throw new RuntimeException("Unable to canonicalize temp file root path " + e); + } + } +} diff --git a/android/telephony/mbms/MbmsUtils.java b/android/telephony/mbms/MbmsUtils.java new file mode 100644 index 00000000..4b913f82 --- /dev/null +++ b/android/telephony/mbms/MbmsUtils.java @@ -0,0 +1,94 @@ +/* + * Copyright (C) 2017 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.telephony.mbms; + +import android.content.ComponentName; +import android.content.Context; +import android.content.Intent; +import android.content.ServiceConnection; +import android.content.pm.*; +import android.content.pm.ServiceInfo; +import android.util.Log; + +import java.io.File; +import java.io.IOException; +import java.util.List; + +/** + * @hide + */ +public class MbmsUtils { + private static final String LOG_TAG = "MbmsUtils"; + + public static boolean isContainedIn(File parent, File child) { + try { + String parentPath = parent.getCanonicalPath(); + String childPath = child.getCanonicalPath(); + return childPath.startsWith(parentPath); + } catch (IOException e) { + throw new RuntimeException("Failed to resolve canonical paths: " + e); + } + } + + public static ComponentName toComponentName(ComponentInfo ci) { + return new ComponentName(ci.packageName, ci.name); + } + + public static ServiceInfo getMiddlewareServiceInfo(Context context, String serviceAction) { + // Query for the proper service + PackageManager packageManager = context.getPackageManager(); + Intent queryIntent = new Intent(); + queryIntent.setAction(serviceAction); + List<ResolveInfo> downloadServices = packageManager.queryIntentServices(queryIntent, + PackageManager.MATCH_SYSTEM_ONLY); + + if (downloadServices == null || downloadServices.size() == 0) { + Log.w(LOG_TAG, "No download services found, cannot get service info"); + return null; + } + + if (downloadServices.size() > 1) { + Log.w(LOG_TAG, "More than one download service found, cannot get unique service"); + return null; + } + return downloadServices.get(0).serviceInfo; + } + + public static void startBinding(Context context, String serviceAction, + ServiceConnection serviceConnection) throws MbmsException { + Intent bindIntent = new Intent(); + ServiceInfo mbmsServiceInfo = + MbmsUtils.getMiddlewareServiceInfo(context, serviceAction); + + if (mbmsServiceInfo == null) { + throw new MbmsException(MbmsException.ERROR_NO_UNIQUE_MIDDLEWARE); + } + + bindIntent.setComponent(MbmsUtils.toComponentName(mbmsServiceInfo)); + + context.bindService(bindIntent, serviceConnection, Context.BIND_AUTO_CREATE); + } + + /** + * Returns a File linked to the directory used to store temp files for this file service + */ + public static File getEmbmsTempFileDirForService(Context context, String serviceId) { + File embmsTempFileDir = MbmsTempFileProvider.getEmbmsTempFileDir(context); + + return new File(embmsTempFileDir, serviceId); + } +} diff --git a/android/telephony/mbms/ServiceInfo.java b/android/telephony/mbms/ServiceInfo.java new file mode 100644 index 00000000..c01604b0 --- /dev/null +++ b/android/telephony/mbms/ServiceInfo.java @@ -0,0 +1,180 @@ +/* + * 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.telephony.mbms; + +import android.os.Parcel; +import android.os.Parcelable; +import android.text.TextUtils; + +import java.util.ArrayList; +import java.util.Date; +import java.util.HashMap; +import java.util.List; +import java.util.Locale; +import java.util.Map; +import java.util.Objects; +import java.util.Set; + +/** + * Describes a cell-broadcast service. This class should not be instantiated directly -- use + * {@link StreamingServiceInfo} or FileServiceInfo TODO: add link once that's unhidden + */ +public class ServiceInfo { + // arbitrary limit on the number of locale -> name pairs we support + final static int MAP_LIMIT = 1000; + + private final Map<Locale, String> names; + private final String className; + private final List<Locale> locales; + private final String serviceId; + private final Date sessionStartTime; + private final Date sessionEndTime; + + /** @hide */ + public ServiceInfo(Map<Locale, String> newNames, String newClassName, List<Locale> newLocales, + String newServiceId, Date start, Date end) { + if (newNames == null || newNames.isEmpty() || TextUtils.isEmpty(newClassName) + || newLocales == null || newLocales.isEmpty() || TextUtils.isEmpty(newServiceId) + || start == null || end == null) { + throw new IllegalArgumentException("Bad ServiceInfo construction"); + } + if (newNames.size() > MAP_LIMIT) { + throw new RuntimeException("bad map length " + newNames.size()); + } + if (newLocales.size() > MAP_LIMIT) { + throw new RuntimeException("bad locales length " + newLocales.size()); + } + names = new HashMap(newNames.size()); + names.putAll(newNames); + className = newClassName; + locales = new ArrayList(newLocales); + serviceId = newServiceId; + sessionStartTime = (Date)start.clone(); + sessionEndTime = (Date)end.clone(); + } + + /** @hide */ + protected ServiceInfo(Parcel in) { + int mapCount = in.readInt(); + if (mapCount > MAP_LIMIT || mapCount < 0) { + throw new RuntimeException("bad map length" + mapCount); + } + names = new HashMap(mapCount); + while (mapCount-- > 0) { + Locale locale = (java.util.Locale) in.readSerializable(); + String name = in.readString(); + names.put(locale, name); + } + className = in.readString(); + int localesCount = in.readInt(); + if (localesCount > MAP_LIMIT || localesCount < 0) { + throw new RuntimeException("bad locale length " + localesCount); + } + locales = new ArrayList<Locale>(localesCount); + while (localesCount-- > 0) { + Locale l = (java.util.Locale) in.readSerializable(); + locales.add(l); + } + serviceId = in.readString(); + sessionStartTime = (java.util.Date) in.readSerializable(); + sessionEndTime = (java.util.Date) in.readSerializable(); + } + + /** @hide */ + public void writeToParcel(Parcel dest, int flags) { + Set<Locale> keySet = names.keySet(); + dest.writeInt(keySet.size()); + for (Locale l : keySet) { + dest.writeSerializable(l); + dest.writeString(names.get(l)); + } + dest.writeString(className); + int localesCount = locales.size(); + dest.writeInt(localesCount); + for (Locale l : locales) { + dest.writeSerializable(l); + } + dest.writeString(serviceId); + dest.writeSerializable(sessionStartTime); + dest.writeSerializable(sessionEndTime); + } + + /** + * User displayable names listed by language. Do not modify the map returned from this method. + */ + public Map<Locale, String> getNames() { + return names; + } + + /** + * The class name for this service - used to categorize and filter + */ + public String getClassName() { + return className; + } + + /** + * The languages available for this service content + */ + public List<Locale> getLocales() { + return locales; + } + + /** + * The carrier's identifier for the service. + */ + public String getServiceId() { + return serviceId; + } + + /** + * The start time indicating when this service will be available. + */ + public Date getSessionStartTime() { + return sessionStartTime; + } + + /** + * The end time indicating when this session stops being available. + */ + public Date getSessionEndTime() { + return sessionEndTime; + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null) { + return false; + } + if (!(o instanceof ServiceInfo)) { + return false; + } + ServiceInfo that = (ServiceInfo) o; + return Objects.equals(names, that.names) && + Objects.equals(className, that.className) && + Objects.equals(locales, that.locales) && + Objects.equals(serviceId, that.serviceId) && + Objects.equals(sessionStartTime, that.sessionStartTime) && + Objects.equals(sessionEndTime, that.sessionEndTime); + } + + @Override + public int hashCode() { + return Objects.hash(names, className, locales, serviceId, sessionStartTime, sessionEndTime); + } +} diff --git a/android/telephony/mbms/StreamingService.java b/android/telephony/mbms/StreamingService.java new file mode 100644 index 00000000..1d66bac0 --- /dev/null +++ b/android/telephony/mbms/StreamingService.java @@ -0,0 +1,199 @@ +/* + * 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.telephony.mbms; + +import android.annotation.IntDef; +import android.net.Uri; +import android.os.RemoteException; +import android.telephony.mbms.vendor.IMbmsStreamingService; +import android.util.Log; + +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; + +/** + * Class used to represent a single MBMS stream. After a stream has been started with + * {@link android.telephony.MbmsStreamingManager#startStreaming(StreamingServiceInfo, + * StreamingServiceCallback, android.os.Handler)}, + * this class is used to hold information about the stream and control it. + */ +public class StreamingService { + private static final String LOG_TAG = "MbmsStreamingService"; + + /** + * The state of a stream, reported via {@link StreamingServiceCallback#onStreamStateUpdated} + * @hide + */ + @Retention(RetentionPolicy.SOURCE) + @IntDef({STATE_STOPPED, STATE_STARTED, STATE_STALLED}) + public @interface StreamingState {} + public final static int STATE_STOPPED = 1; + public final static int STATE_STARTED = 2; + public final static int STATE_STALLED = 3; + + /** + * The reason for a stream state change, reported via + * {@link StreamingServiceCallback#onStreamStateUpdated} + * @hide + */ + @Retention(RetentionPolicy.SOURCE) + @IntDef({REASON_BY_USER_REQUEST, REASON_END_OF_SESSION, REASON_FREQUENCY_CONFLICT, + REASON_OUT_OF_MEMORY, REASON_NOT_CONNECTED_TO_HOMECARRIER_LTE, + REASON_LEFT_MBMS_BROADCAST_AREA, REASON_NONE}) + public @interface StreamingStateChangeReason {} + + /** + * Indicates that the middleware does not have a reason to provide for the state change. + */ + public static final int REASON_NONE = 0; + + /** + * State changed due to a call to {@link #stopStreaming()} or + * {@link android.telephony.MbmsStreamingManager#startStreaming(StreamingServiceInfo, + * StreamingServiceCallback, android.os.Handler)} + */ + public static final int REASON_BY_USER_REQUEST = 1; + + /** + * State changed due to the streaming session ending at the carrier. + */ + public static final int REASON_END_OF_SESSION = 2; + + /** + * State changed due to a frequency conflict with another requested stream. + */ + public static final int REASON_FREQUENCY_CONFLICT = 3; + + /** + * State changed due to the middleware running out of memory + */ + public static final int REASON_OUT_OF_MEMORY = 4; + + /** + * State changed due to the device leaving the home carrier's LTE network. + */ + public static final int REASON_NOT_CONNECTED_TO_HOMECARRIER_LTE = 5; + + /** + * State changed due to the device leaving the where this stream is being broadcast. + */ + public static final int REASON_LEFT_MBMS_BROADCAST_AREA = 6; + + /** + * The method of transmission currently used for a stream, + * reported via {@link StreamingServiceCallback#onStreamMethodUpdated} + */ + public final static int BROADCAST_METHOD = 1; + public final static int UNICAST_METHOD = 2; + + private final int mSubscriptionId; + private final StreamingServiceInfo mServiceInfo; + private final InternalStreamingServiceCallback mCallback; + + private IMbmsStreamingService mService; + + /** + * @hide + */ + public StreamingService(int subscriptionId, + IMbmsStreamingService service, + StreamingServiceInfo streamingServiceInfo, + InternalStreamingServiceCallback callback) { + mSubscriptionId = subscriptionId; + mService = service; + mServiceInfo = streamingServiceInfo; + mCallback = callback; + } + + /** + * Retreive the Uri used to play this stream. + * + * This may throw a {@link MbmsException} with the error code + * {@link MbmsException#ERROR_MIDDLEWARE_LOST} + * + * May also throw an {@link IllegalArgumentException} or an {@link IllegalStateException} + * + * @return The {@link Uri} to pass to the streaming client. + */ + public Uri getPlaybackUri() throws MbmsException { + if (mService == null) { + throw new IllegalStateException("No streaming service attached"); + } + + try { + return mService.getPlaybackUri(mSubscriptionId, mServiceInfo.getServiceId()); + } catch (RemoteException e) { + Log.w(LOG_TAG, "Remote process died"); + mService = null; + throw new MbmsException(MbmsException.ERROR_MIDDLEWARE_LOST); + } + } + + /** + * Retreive the info for this StreamingService. + */ + public StreamingServiceInfo getInfo() { + return mServiceInfo; + } + + /** + * Stop streaming this service. + * This may throw a {@link MbmsException} with the error code + * {@link MbmsException#ERROR_MIDDLEWARE_LOST} + * + * May also throw an {@link IllegalArgumentException} or an {@link IllegalStateException} + */ + public void stopStreaming() throws MbmsException { + if (mService == null) { + throw new IllegalStateException("No streaming service attached"); + } + + try { + mService.stopStreaming(mSubscriptionId, mServiceInfo.getServiceId()); + } catch (RemoteException e) { + Log.w(LOG_TAG, "Remote process died"); + mService = null; + throw new MbmsException(MbmsException.ERROR_MIDDLEWARE_LOST); + } + } + + /** + * Disposes of this stream. Further operations on this object will fail with an + * {@link IllegalStateException}. + * + * This may throw a {@link MbmsException} with the error code + * {@link MbmsException#ERROR_MIDDLEWARE_LOST} + * May also throw an {@link IllegalStateException} + */ + public void dispose() throws MbmsException { + if (mService == null) { + throw new IllegalStateException("No streaming service attached"); + } + + try { + mService.disposeStream(mSubscriptionId, mServiceInfo.getServiceId()); + } catch (RemoteException e) { + Log.w(LOG_TAG, "Remote process died"); + throw new MbmsException(MbmsException.ERROR_MIDDLEWARE_LOST); + } catch (IllegalArgumentException e) { + throw new IllegalStateException("StreamingService state inconsistent with middleware"); + } finally { + mService = null; + } + } +} + diff --git a/android/telephony/mbms/StreamingServiceCallback.java b/android/telephony/mbms/StreamingServiceCallback.java new file mode 100644 index 00000000..b72c7157 --- /dev/null +++ b/android/telephony/mbms/StreamingServiceCallback.java @@ -0,0 +1,102 @@ +/* + * 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.telephony.mbms; + +/** + * A callback class for use when the application is actively streaming content. The middleware + * will provide updates on the status of the stream via this callback. + */ +public class StreamingServiceCallback { + + /** + * Indicates broadcast signal strength is not available for this service. + * + * This may be due to the service no longer being available due to geography + * or timing (end of service) or because lack of demand has caused the service + * to be delivered via unicast. + */ + public static final int SIGNAL_STRENGTH_UNAVAILABLE = -1; + + /** + * Called by the middleware when it has detected an error condition in this stream. The + * possible error codes are listed in {@link MbmsException}. + * @param errorCode The error code. + * @param message A human-readable message generated by the middleware for debugging purposes. + */ + public void onError(int errorCode, String message) { + // default implementation empty + } + + /** + * Called to indicate this stream has changed state. + * + * See {@link StreamingService#STATE_STOPPED}, {@link StreamingService#STATE_STARTED} + * and {@link StreamingService#STATE_STALLED}. + */ + public void onStreamStateUpdated(@StreamingService.StreamingState int state, + @StreamingService.StreamingStateChangeReason int reason) { + // default implementation empty + } + + /** + * Called to indicate the mpd of a the stream has changed. + * + * Depending on the Dash Client it may need to be either reset + * (less drastic, but original spec didn't allow mpd to change so not + * always supported) or restarted. + * + * This may be called when a looping stream hits the end or + * when parameters have changed to account for time drift. + */ + public void onMediaDescriptionUpdated() { + // default implementation empty + } + + /** + * Broadcast Signal Strength updated. + * + * This signal strength is the BROADCAST signal strength which, + * depending on technology in play and it's deployment, may be + * stronger or weaker than the traditional UNICAST signal + * strength. It a simple int from 0-4 for valid levels or + * {@link #SIGNAL_STRENGTH_UNAVAILABLE} if broadcast is not available + * for this service due to timing, geography or popularity. + */ + public void onBroadcastSignalStrengthUpdated(int signalStrength) { + // default implementation empty + } + + /** + * Notify of bcast/unicast method being used. + * + * This is intended to be informational. Indicates + * whether we're able to use cell broadcast or have + * had to fallback to unicast for this stream. + * + * This must be called once at the beginning of the stream + * around the same time as we change to STATE_STARTED, but + * strict ordering is not specified. It must be called + * again if we change modes, but if that doesn't happen + * the callback won't be used again. + * + * See {@link StreamingService#BROADCAST_METHOD} and + * {@link StreamingService#UNICAST_METHOD} + */ + public void onStreamMethodUpdated(int methodType) { + // default implementation empty + } +} diff --git a/android/telephony/mbms/StreamingServiceInfo.java b/android/telephony/mbms/StreamingServiceInfo.java new file mode 100644 index 00000000..c704f346 --- /dev/null +++ b/android/telephony/mbms/StreamingServiceInfo.java @@ -0,0 +1,75 @@ +/* + * 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.telephony.mbms; + +import android.annotation.SystemApi; +import android.os.Parcel; +import android.os.Parcelable; + +import java.util.Date; +import java.util.List; +import java.util.Locale; +import java.util.Map; + +/** + * Describes a single MBMS streaming service. + */ +public final class StreamingServiceInfo extends ServiceInfo implements Parcelable { + + /** + * @param names User displayable names listed by language. + * @param className The class name for this service - used by frontend apps to categorize and + * filter. + * @param locales The languages available for this service content. + * @param serviceId The carrier's identifier for the service. + * @param start The start time indicating when this service will be available. + * @param end The end time indicating when this session stops being available. + * @hide + */ + @SystemApi + public StreamingServiceInfo(Map<Locale, String> names, String className, + List<Locale> locales, String serviceId, Date start, Date end) { + super(names, className, locales, serviceId, start, end); + } + + public static final Parcelable.Creator<StreamingServiceInfo> CREATOR = + new Parcelable.Creator<StreamingServiceInfo>() { + @Override + public StreamingServiceInfo createFromParcel(Parcel source) { + return new StreamingServiceInfo(source); + } + + @Override + public StreamingServiceInfo[] newArray(int size) { + return new StreamingServiceInfo[size]; + } + }; + + private StreamingServiceInfo(Parcel in) { + super(in); + } + + @Override + public void writeToParcel(Parcel dest, int flags) { + super.writeToParcel(dest, flags); + } + + @Override + public int describeContents() { + return 0; + } +} diff --git a/android/telephony/mbms/UriPathPair.java b/android/telephony/mbms/UriPathPair.java new file mode 100644 index 00000000..7acc270e --- /dev/null +++ b/android/telephony/mbms/UriPathPair.java @@ -0,0 +1,80 @@ +/* + * Copyright (C) 2017 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.telephony.mbms; + +import android.content.ContentResolver; +import android.net.Uri; +import android.os.Parcel; +import android.os.Parcelable; + +/** @hide */ +public class UriPathPair implements Parcelable { + private final Uri mFilePathUri; + private final Uri mContentUri; + + /** @hide */ + public UriPathPair(Uri fileUri, Uri contentUri) { + if (fileUri == null || !ContentResolver.SCHEME_FILE.equals(fileUri.getScheme())) { + throw new IllegalArgumentException("File URI must have file scheme"); + } + if (contentUri == null || !ContentResolver.SCHEME_CONTENT.equals(contentUri.getScheme())) { + throw new IllegalArgumentException("Content URI must have content scheme"); + } + + mFilePathUri = fileUri; + mContentUri = contentUri; + } + + /** @hide */ + protected UriPathPair(Parcel in) { + mFilePathUri = in.readParcelable(Uri.class.getClassLoader()); + mContentUri = in.readParcelable(Uri.class.getClassLoader()); + } + + public static final Creator<UriPathPair> CREATOR = new Creator<UriPathPair>() { + @Override + public UriPathPair createFromParcel(Parcel in) { + return new UriPathPair(in); + } + + @Override + public UriPathPair[] newArray(int size) { + return new UriPathPair[size]; + } + }; + + /** future systemapi */ + public Uri getFilePathUri() { + return mFilePathUri; + } + + /** future systemapi */ + public Uri getContentUri() { + return mContentUri; + } + + @Override + public int describeContents() { + return 0; + } + + @Override + public void writeToParcel(Parcel dest, int flags) { + dest.writeParcelable(mFilePathUri, flags); + dest.writeParcelable(mContentUri, flags); + } +} diff --git a/android/telephony/mbms/vendor/MbmsDownloadServiceBase.java b/android/telephony/mbms/vendor/MbmsDownloadServiceBase.java new file mode 100644 index 00000000..71713d01 --- /dev/null +++ b/android/telephony/mbms/vendor/MbmsDownloadServiceBase.java @@ -0,0 +1,247 @@ +/* + * Copyright (C) 2017 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.telephony.mbms.vendor; + +import android.annotation.NonNull; +import android.os.RemoteException; +import android.telephony.mbms.DownloadProgressListener; +import android.telephony.mbms.DownloadRequest; +import android.telephony.mbms.FileInfo; +import android.telephony.mbms.FileServiceInfo; +import android.telephony.mbms.IDownloadProgressListener; +import android.telephony.mbms.IMbmsDownloadManagerCallback; +import android.telephony.mbms.MbmsDownloadManagerCallback; +import android.telephony.mbms.MbmsException; + +import java.util.List; + +/** + * Base class for MbmsDownloadService. The middleware should extend this base class rather than + * the aidl stub for compatibility + * @hide + * TODO: future systemapi + */ +public class MbmsDownloadServiceBase extends IMbmsDownloadService.Stub { + /** + * Initialize the download service for this app and subId, registering the listener. + * + * May throw an {@link IllegalArgumentException} or an {@link IllegalStateException}, which + * will be intercepted and passed to the app as + * {@link android.telephony.mbms.MbmsException.InitializationErrors#ERROR_UNABLE_TO_INITIALIZE} + * + * May return any value from {@link android.telephony.mbms.MbmsException.InitializationErrors} + * or {@link MbmsException#SUCCESS}. Non-successful error codes will be passed to the app via + * {@link IMbmsDownloadManagerCallback#error(int, String)}. + * + * @param callback The callback to use to communicate with the app. + * @param subscriptionId The subscription ID to use. + */ + public int initialize(int subscriptionId, MbmsDownloadManagerCallback callback) + throws RemoteException { + return 0; + } + + /** + * Actual AIDL implementation -- hides the callback AIDL from the API. + * @hide + */ + @Override + public final int initialize(int subscriptionId, + final IMbmsDownloadManagerCallback callback) throws RemoteException { + return initialize(subscriptionId, new MbmsDownloadManagerCallback() { + @Override + public void error(int errorCode, String message) throws RemoteException { + callback.error(errorCode, message); + } + + @Override + public void fileServicesUpdated(List<FileServiceInfo> services) throws RemoteException { + callback.fileServicesUpdated(services); + } + + @Override + public void middlewareReady() throws RemoteException { + callback.middlewareReady(); + } + }); + } + + /** + * Registers serviceClasses of interest with the appName/subId key. + * Starts async fetching data on streaming services of matching classes to be reported + * later via {@link IMbmsDownloadManagerCallback#fileServicesUpdated(List)} + * + * Note that subsequent calls with the same uid and subId will replace + * the service class list. + * + * May throw an {@link IllegalArgumentException} or an {@link IllegalStateException} + * + * @param subscriptionId The subscription id to use. + * @param serviceClasses The service classes that the app wishes to get info on. The strings + * may contain arbitrary data as negotiated between the app and the + * carrier. + * @return One of {@link MbmsException#SUCCESS} or + * {@link MbmsException.GeneralErrors#ERROR_MIDDLEWARE_NOT_YET_READY}, + */ + @Override + public int getFileServices(int subscriptionId, List<String> serviceClasses) + throws RemoteException { + return 0; + } + + /** + * Sets the temp file root directory for this app/subscriptionId combination. The middleware + * should persist {@code rootDirectoryPath} and send it back when sending intents to the + * app's {@link android.telephony.mbms.MbmsDownloadReceiver}. + * + * If the calling app (as identified by the calling UID) currently has any pending download + * requests that have not been canceled, the middleware must return + * {@link MbmsException.DownloadErrors#ERROR_CANNOT_CHANGE_TEMP_FILE_ROOT} here. + * + * @param subscriptionId The subscription id the download is operating under. + * @param rootDirectoryPath The path to the app's temp file root directory. + * @return {@link MbmsException#SUCCESS}, + * {@link MbmsException.GeneralErrors#ERROR_MIDDLEWARE_NOT_YET_READY} or + * {@link MbmsException.DownloadErrors#ERROR_CANNOT_CHANGE_TEMP_FILE_ROOT} + */ + @Override + public int setTempFileRootDirectory(int subscriptionId, + String rootDirectoryPath) throws RemoteException { + return 0; + } + + /** + * Issues a request to download a set of files. + * + * The middleware should expect that {@link #setTempFileRootDirectory(int, String)} has been + * called for this app between when the app was installed and when this method is called. If + * this is not the case, an {@link IllegalStateException} may be thrown. + * + * @param downloadRequest An object describing the set of files to be downloaded. + * @param listener A listener through which the middleware can provide progress updates to + * the app while both are still running. + * @return Any error from {@link android.telephony.mbms.MbmsException.GeneralErrors} + * or {@link MbmsException#SUCCESS} + */ + public int download(DownloadRequest downloadRequest, DownloadProgressListener listener) { + return 0; + } + + /** + * Actual AIDL implementation -- hides the callback AIDL from the API. + * @hide + */ + @Override + public final int download(DownloadRequest downloadRequest, IDownloadProgressListener listener) + throws RemoteException { + return download(downloadRequest, new DownloadProgressListener() { + @Override + public void progress(DownloadRequest request, FileInfo fileInfo, int + currentDownloadSize, int fullDownloadSize, int currentDecodedSize, int + fullDecodedSize) throws RemoteException { + listener.progress(request, fileInfo, currentDownloadSize, fullDownloadSize, + currentDecodedSize, fullDecodedSize); + } + }); + } + + + /** + * Returns a list of pending {@link DownloadRequest}s that originated from the calling + * application, identified by its uid. A pending request is one that was issued via + * {@link #download(DownloadRequest, IDownloadProgressListener)} but not cancelled through + * {@link #cancelDownload(DownloadRequest)}. + * The middleware must return a non-null result synchronously or throw an exception + * inheriting from {@link RuntimeException}. + * @return A list, possibly empty, of {@link DownloadRequest}s + */ + @Override + public @NonNull List<DownloadRequest> listPendingDownloads(int subscriptionId) + throws RemoteException { + return null; + } + + /** + * Issues a request to cancel the specified download request. + * + * If the middleware is unable to cancel the request for whatever reason, it should return + * synchronously with an error. If this method returns {@link MbmsException#SUCCESS}, the app + * will no longer be expecting any more file-completed intents from the middleware for this + * {@link DownloadRequest}. + * @param downloadRequest The request to cancel + * @return {@link MbmsException#SUCCESS}, + * {@link MbmsException.DownloadErrors#ERROR_UNKNOWN_DOWNLOAD_REQUEST}, + * {@link MbmsException.GeneralErrors#ERROR_MIDDLEWARE_NOT_YET_READY} + */ + @Override + public int cancelDownload(DownloadRequest downloadRequest) throws RemoteException { + return 0; + } + + /** + * Gets information about the status of a file pending download. + * + * If the middleware has not yet been properly initialized or if it has no records of the + * file indicated by {@code fileInfo} being associated with {@code downloadRequest}, + * {@link android.telephony.MbmsDownloadManager#STATUS_UNKNOWN} must be returned. + * + * @param downloadRequest The download request to query. + * @param fileInfo The particular file within the request to get information on. + * @return The status of the download. + */ + @Override + public int getDownloadStatus(DownloadRequest downloadRequest, FileInfo fileInfo) + throws RemoteException { + return 0; + } + + /** + * Resets the middleware's knowledge of previously-downloaded files in this download request. + * + * When this method is called, the middleware must attempt to re-download all the files + * specified by the {@link DownloadRequest}, even if the files have not changed on the server. + * In addition, current in-progress downloads must not be interrupted. + * + * If the middleware is not aware of the specified download request, return + * {@link MbmsException.DownloadErrors#ERROR_UNKNOWN_DOWNLOAD_REQUEST}. + * + * @param downloadRequest The request to re-download files for. + */ + @Override + public int resetDownloadKnowledge(DownloadRequest downloadRequest) + throws RemoteException { + return 0; + } + + /** + * Signals that the app wishes to dispose of the session identified by the + * {@code subscriptionId} argument and the caller's uid. No notification back to the + * app is required for this operation, and the corresponding callback provided via + * {@link #initialize(int, IMbmsDownloadManagerCallback)} should no longer be used + * after this method has been called by the app. + * + * Any download requests issued by the app should remain in effect until the app calls + * {@link #cancelDownload(DownloadRequest)} on another session. + * + * May throw an {@link IllegalStateException} + * + * @param subscriptionId The subscription id to use. + */ + @Override + public void dispose(int subscriptionId) throws RemoteException { + } +} diff --git a/android/telephony/mbms/vendor/MbmsStreamingServiceBase.java b/android/telephony/mbms/vendor/MbmsStreamingServiceBase.java new file mode 100644 index 00000000..843e0482 --- /dev/null +++ b/android/telephony/mbms/vendor/MbmsStreamingServiceBase.java @@ -0,0 +1,279 @@ +/* + * Copyright (C) 2017 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.telephony.mbms.vendor; + +import android.annotation.Nullable; +import android.annotation.SystemApi; +import android.net.Uri; +import android.os.Binder; +import android.os.RemoteException; +import android.telephony.mbms.IMbmsStreamingManagerCallback; +import android.telephony.mbms.IStreamingServiceCallback; +import android.telephony.mbms.MbmsException; +import android.telephony.mbms.MbmsStreamingManagerCallback; +import android.telephony.mbms.StreamingService; +import android.telephony.mbms.StreamingServiceCallback; +import android.telephony.mbms.StreamingServiceInfo; + +import java.util.List; + +/** + * @hide + */ +@SystemApi +public class MbmsStreamingServiceBase extends IMbmsStreamingService.Stub { + /** + * Initialize streaming service for this app and subId, registering the listener. + * + * May throw an {@link IllegalArgumentException} or a {@link SecurityException}, which + * will be intercepted and passed to the app as + * {@link android.telephony.mbms.MbmsException.InitializationErrors#ERROR_UNABLE_TO_INITIALIZE} + * + * May return any value from {@link android.telephony.mbms.MbmsException.InitializationErrors} + * or {@link MbmsException#SUCCESS}. Non-successful error codes will be passed to the app via + * {@link IMbmsStreamingManagerCallback#error(int, String)}. + * + * @param callback The callback to use to communicate with the app. + * @param subscriptionId The subscription ID to use. + */ + public int initialize(MbmsStreamingManagerCallback callback, int subscriptionId) + throws RemoteException { + return 0; + } + + /** + * Actual AIDL implementation that hides the callback AIDL from the middleware. + * @hide + */ + @Override + public final int initialize(final IMbmsStreamingManagerCallback callback, + final int subscriptionId) throws RemoteException { + final int uid = Binder.getCallingUid(); + callback.asBinder().linkToDeath(new DeathRecipient() { + @Override + public void binderDied() { + onAppCallbackDied(uid, subscriptionId); + } + }, 0); + + return initialize(new MbmsStreamingManagerCallback() { + @Override + public void onError(int errorCode, String message) { + try { + callback.error(errorCode, message); + } catch (RemoteException e) { + onAppCallbackDied(uid, subscriptionId); + } + } + + @Override + public void onStreamingServicesUpdated(List<StreamingServiceInfo> services) { + try { + callback.streamingServicesUpdated(services); + } catch (RemoteException e) { + onAppCallbackDied(uid, subscriptionId); + } + } + + @Override + public void onMiddlewareReady() { + try { + callback.middlewareReady(); + } catch (RemoteException e) { + onAppCallbackDied(uid, subscriptionId); + } + } + }, subscriptionId); + } + + + /** + * Registers serviceClasses of interest with the appName/subId key. + * Starts async fetching data on streaming services of matching classes to be reported + * later via {@link IMbmsStreamingManagerCallback#streamingServicesUpdated(List)} + * + * Note that subsequent calls with the same uid and subId will replace + * the service class list. + * + * May throw an {@link IllegalArgumentException} or an {@link IllegalStateException} + * + * @param subscriptionId The subscription id to use. + * @param serviceClasses The service classes that the app wishes to get info on. The strings + * may contain arbitrary data as negotiated between the app and the + * carrier. + * @return {@link MbmsException#SUCCESS} or any of the errors in + * {@link android.telephony.mbms.MbmsException.GeneralErrors} + */ + @Override + public int getStreamingServices(int subscriptionId, + List<String> serviceClasses) throws RemoteException { + return 0; + } + + /** + * Starts streaming on a particular service. This method may perform asynchronous work. When + * the middleware is ready to send bits to the frontend, it should inform the app via + * {@link IStreamingServiceCallback#streamStateUpdated(int, int)}. + * + * May throw an {@link IllegalArgumentException} or an {@link IllegalStateException} + * + * @param subscriptionId The subscription id to use. + * @param serviceId The ID of the streaming service that the app has requested. + * @param callback The callback object on which the app wishes to receive updates. + * @return Any error in {@link android.telephony.mbms.MbmsException.GeneralErrors} + */ + public int startStreaming(int subscriptionId, String serviceId, + StreamingServiceCallback callback) throws RemoteException { + return 0; + } + + /** + * Actual AIDL implementation of startStreaming that hides the callback AIDL from the + * middleware. + * @hide + */ + @Override + public int startStreaming(int subscriptionId, String serviceId, + IStreamingServiceCallback callback) throws RemoteException { + final int uid = Binder.getCallingUid(); + callback.asBinder().linkToDeath(new DeathRecipient() { + @Override + public void binderDied() { + onAppCallbackDied(uid, subscriptionId); + } + }, 0); + + return startStreaming(subscriptionId, serviceId, new StreamingServiceCallback() { + @Override + public void onError(int errorCode, String message) { + try { + callback.error(errorCode, message); + } catch (RemoteException e) { + onAppCallbackDied(uid, subscriptionId); + } + } + + @Override + public void onStreamStateUpdated(@StreamingService.StreamingState int state, + @StreamingService.StreamingStateChangeReason int reason) { + try { + callback.streamStateUpdated(state, reason); + } catch (RemoteException e) { + onAppCallbackDied(uid, subscriptionId); + } + } + + @Override + public void onMediaDescriptionUpdated() { + try { + callback.mediaDescriptionUpdated(); + } catch (RemoteException e) { + onAppCallbackDied(uid, subscriptionId); + } + } + + @Override + public void onBroadcastSignalStrengthUpdated(int signalStrength) { + try { + callback.broadcastSignalStrengthUpdated(signalStrength); + } catch (RemoteException e) { + onAppCallbackDied(uid, subscriptionId); + } + } + + @Override + public void onStreamMethodUpdated(int methodType) { + try { + callback.streamMethodUpdated(methodType); + } catch (RemoteException e) { + onAppCallbackDied(uid, subscriptionId); + } + } + }); + } + + /** + * Retrieves the streaming URI for a particular service. If the middleware is not yet ready to + * stream the service, this method may return null. + * + * May throw an {@link IllegalArgumentException} or an {@link IllegalStateException} + * + * @param subscriptionId The subscription id to use. + * @param serviceId The ID of the streaming service that the app has requested. + * @return An opaque {@link Uri} to be passed to a video player that understands the format. + */ + @Override + public @Nullable Uri getPlaybackUri(int subscriptionId, String serviceId) + throws RemoteException { + return null; + } + + /** + * Stop streaming the stream identified by {@code serviceId}. Notification of the resulting + * stream state change should be reported to the app via + * {@link IStreamingServiceCallback#streamStateUpdated(int, int)}. + * + * May throw an {@link IllegalArgumentException} or an {@link IllegalStateException} + * + * @param subscriptionId The subscription id to use. + * @param serviceId The ID of the streaming service that the app wishes to stop. + */ + @Override + public void stopStreaming(int subscriptionId, String serviceId) + throws RemoteException { + } + + /** + * Dispose of the stream identified by {@code serviceId} for the app identified by the + * {@code appName} and {@code subscriptionId} arguments along with the caller's uid. + * No notification back to the app is required for this operation, and the callback provided via + * {@link #startStreaming(int, String, IStreamingServiceCallback)} should no longer be + * used after this method has called by the app. + * + * May throw an {@link IllegalArgumentException} or an {@link IllegalStateException} + * + * @param subscriptionId The subscription id to use. + * @param serviceId The ID of the streaming service that the app wishes to dispose of. + */ + @Override + public void disposeStream(int subscriptionId, String serviceId) + throws RemoteException { + } + + /** + * Signals that the app wishes to dispose of the session identified by the + * {@code subscriptionId} argument and the caller's uid. No notification back to the + * app is required for this operation, and the corresponding callback provided via + * {@link #initialize(IMbmsStreamingManagerCallback, int)} should no longer be used + * after this method has been called by the app. + * + * May throw an {@link IllegalStateException} + * + * @param subscriptionId The subscription id to use. + */ + @Override + public void dispose(int subscriptionId) throws RemoteException { + } + + /** + * Indicates that the app identified by the given UID and subscription ID has died. + * @param uid the UID of the app, as returned by {@link Binder#getCallingUid()}. + * @param subscriptionId The subscription ID the app is using. + */ + public void onAppCallbackDied(int uid, int subscriptionId) { + } +} diff --git a/android/telephony/mbms/vendor/VendorIntents.java b/android/telephony/mbms/vendor/VendorIntents.java new file mode 100644 index 00000000..367c995c --- /dev/null +++ b/android/telephony/mbms/vendor/VendorIntents.java @@ -0,0 +1,166 @@ +/* + * Copyright (C) 2017 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.telephony.mbms.vendor; + +import android.content.ComponentName; +import android.content.Context; +import android.content.Intent; +import android.content.pm.ResolveInfo; +import android.net.Uri; +import android.telephony.mbms.DownloadRequest; +import android.telephony.mbms.MbmsDownloadReceiver; + +import java.io.File; +import java.util.List; + +/** + * @hide + * TODO: future systemapi + */ +public class VendorIntents { + + /** + * The MBMS middleware should send this when a download of single file has completed or + * failed. Mandatory extras are + * {@link android.telephony.MbmsDownloadManager#EXTRA_RESULT} + * {@link android.telephony.MbmsDownloadManager#EXTRA_FILE_INFO} + * {@link #EXTRA_REQUEST} + * {@link #EXTRA_TEMP_LIST} + * {@link #EXTRA_FINAL_URI} + */ + public static final String ACTION_DOWNLOAD_RESULT_INTERNAL = + "android.telephony.mbms.action.DOWNLOAD_RESULT_INTERNAL"; + + /** + * The MBMS middleware should send this when it wishes to request {@code content://} URIs to + * serve as temp files for downloads or when it wishes to resume paused downloads. Mandatory + * extras are + * {@link #EXTRA_REQUEST} + * + * Optional extras are + * {@link #EXTRA_FD_COUNT} (0 if not present) + * {@link #EXTRA_PAUSED_LIST} (empty if not present) + */ + public static final String ACTION_FILE_DESCRIPTOR_REQUEST = + "android.telephony.mbms.action.FILE_DESCRIPTOR_REQUEST"; + + /** + * The MBMS middleware should send this when it wishes to clean up temp files in the app's + * filesystem. Mandatory extras are: + * {@link #EXTRA_TEMP_FILES_IN_USE} + */ + public static final String ACTION_CLEANUP = + "android.telephony.mbms.action.CLEANUP"; + + /** + * Extra containing a {@link List} of {@link Uri}s that were used as temp files for this + * completed file. These {@link Uri}s should have scheme {@code file://}, and the temp + * files will be deleted upon receipt of the intent. + * May be null. + */ + public static final String EXTRA_TEMP_LIST = "android.telephony.mbms.extra.TEMP_LIST"; + + /** + * Extra containing an integer indicating the number of temp files requested. + */ + public static final String EXTRA_FD_COUNT = "android.telephony.mbms.extra.FD_COUNT"; + + /** + * Extra containing a list of {@link Uri}s that the middleware is requesting access to via + * {@link #ACTION_FILE_DESCRIPTOR_REQUEST} in order to resume downloading. These {@link Uri}s + * should have scheme {@code file://}. + */ + public static final String EXTRA_PAUSED_LIST = "android.telephony.mbms.extra.PAUSED_LIST"; + + /** + * Extra containing a list of {@link android.telephony.mbms.UriPathPair}s, used in the + * response to {@link #ACTION_FILE_DESCRIPTOR_REQUEST}. These are temp files that are meant + * to be used for new file downloads. + */ + public static final String EXTRA_FREE_URI_LIST = "android.telephony.mbms.extra.FREE_URI_LIST"; + + /** + * Extra containing a list of {@link android.telephony.mbms.UriPathPair}s, used in the + * response to {@link #ACTION_FILE_DESCRIPTOR_REQUEST}. These + * {@link android.telephony.mbms.UriPathPair}s contain {@code content://} URIs that provide + * access to previously paused downloads. + */ + public static final String EXTRA_PAUSED_URI_LIST = + "android.telephony.mbms.extra.PAUSED_URI_LIST"; + + /** + * Extra containing a string that points to the middleware's knowledge of where the temp file + * root for the app is. The path should be a canonical path as returned by + * {@link File#getCanonicalPath()} + */ + public static final String EXTRA_TEMP_FILE_ROOT = + "android.telephony.mbms.extra.TEMP_FILE_ROOT"; + + /** + * Extra containing a list of {@link Uri}s indicating temp files which the middleware is + * still using. + */ + public static final String EXTRA_TEMP_FILES_IN_USE = + "android.telephony.mbms.extra.TEMP_FILES_IN_USE"; + + /** + * Extra containing the {@link DownloadRequest} for which the download result or file + * descriptor request is for. Must not be null. + */ + public static final String EXTRA_REQUEST = "android.telephony.mbms.extra.REQUEST"; + + /** + * Extra containing a single {@link Uri} indicating the path to the temp file in which the + * decoded downloaded file resides. Must not be null. + */ + public static final String EXTRA_FINAL_URI = "android.telephony.mbms.extra.FINAL_URI"; + + /** + * Extra containing an instance of {@link android.telephony.mbms.ServiceInfo}, used by + * file-descriptor requests and cleanup requests to specify which service they want to + * request temp files or clean up temp files for, respectively. + */ + public static final String EXTRA_SERVICE_INFO = + "android.telephony.mbms.extra.SERVICE_INFO"; + + /** + * Retrieves the {@link ComponentName} for the {@link android.content.BroadcastReceiver} that + * the various intents from the middleware should be targeted towards. + * @param uid The uid of the frontend app. + * @return The component name of the receiver that the middleware should send its intents to, + * or null if the app didn't declare it in the manifest. + */ + public static ComponentName getAppReceiverFromUid(Context context, int uid) { + String[] packageNames = context.getPackageManager().getPackagesForUid(uid); + if (packageNames == null) { + return null; + } + + for (String packageName : packageNames) { + ComponentName candidate = new ComponentName(packageName, + MbmsDownloadReceiver.class.getCanonicalName()); + Intent queryIntent = new Intent(); + queryIntent.setComponent(candidate); + List<ResolveInfo> receivers = + context.getPackageManager().queryBroadcastReceivers(queryIntent, 0); + if (receivers != null && receivers.size() > 0) { + return candidate; + } + } + return null; + } +} |