关键词:PackageManager、FEATURE、Telephony Manager、SMS Manager、ServiceState、PhoneStateListener


关键词:PackageManager、FEATURE、Telephony Manager、SMS Manager、ServiceState、PhoneStateListener

 

  • 启动电话呼叫
  • 读取电话、网络、数据连接以及SIM状态
  • 监视电话、网络、数据连接以及SIM状态的变化
  • 使用Intent发送SMS和MMS消息
  • 使用SMS Manager发送SMS消息
  • 处理传入的SMS消息

 

电话服务的硬件支持

随着只支持Wi-Fi的Android设备出现,不能再假定运行应用程序的所有硬件都支持电话服务。

将电话功能指定为必须的硬件功能

Permissions that Imply Feature Requirements

<users-feature android:name="android.hardware.telephony" android:required="true" />

检查电话硬件

使用PackageManagerhasSystemFeature方法,并指定检查FEATURE_TELEPHONY功能。

PackageManager packageManager = getPackageManager();
boolean telephonySupported = packageManager.hasSystemFeature(PackageManager.FEATURE_TELEPHONY);

其他的功能参见PackageManager中的FEATURE_开头的静态字符串。

 

使用电话服务

启动电话呼叫

启动电话呼叫的最佳实践是使用一个Intent.ACTION_DIAL的Intent,并通过使用 tel: 模式设置Intent数据来指定要拨打的电话号码。

ACTION_DIAL content://contacts/people/1 -- Display the phone dialer with the person filled in.

ACTION_DIAL tel:123 -- Display the phone dialer with the given number filled in.

Intent call = new Intent(Intent.ACTION_DIAL, Uri.parse("tel:555-1234"));

这会启动一个拨号程序Activity,它应该已经预先填充了你所指定的号码。默认的拨号程序Activity允许用户在显式发起呼叫之前修改要拨打的号码。因此,使用ACTION_DIAL Intent动作并不需要任何特殊权限。

 

替换本机拨号程序

替换本机拨号程序包括以下两个步骤:

1.截获当前由本机拨号程序所服务的Intent。

2.启动并管理拨打电话。

为了截获用户按下硬件呼叫按钮动作,需要在替换拨号程序Activity的manifest文件中包含intent-filter标签来监听以下动作。

<activity android:name=".MyDialerActivity" android:label="@string/app_name">
  <intent-filter>
    <action android:name="android.intent.action.CALL_BUTTON" />
    <category android:name="android.intent.category.DEFAULT" />
  </intent-filter>
  <intent-filter>
    <action android:name="android.intent.action.VIEW" />
    <action android:name="android.intent.action.DIAL" />
    <category android:name="android.intent.category.DEFAULT" />
    <category android:name="android.intent.category.BROWSABLE" />
    <data android:scheme="tel" />
  </intent-filter>
</activity>

Intent.ACTION_CALL_BUTTON:当按下设备的硬件呼叫按钮时该动作被广播。创建一个监听该动作的Intent Filter作为默认动作。

Intent.ACTION_DIAL:这个动作由想要启动电话呼叫的应用程序使用。用于捕获该动作的Intent Filter应当是默认并且可浏览的,并且必须制定 tel: 模式以替换现有的拨号功能。

Intent.ACTION_VIEW:该查看动作由想要查看某条数据的应用程序使用。确保 Intent.Filter 指定了 tel: 模式以允许新的Activity用于查看电话号码。

在Activity启动后,它应该提供一个UI,供用户用来输入或修改要拨打的电话号码,以及启动传出呼叫。此时就需要使用现有的电话服务栈或者替换拨号程序来拨打电话了。

最简单的技术是通过Intent.ACTION_CALL动作使用现有的电话服务栈。

Intent whoyougonnacall = new Intent(Intent.ACTION_CALL,  Uri.parse("tel:555-2368"));
startActivity(whoyougonnacall);

这将使用系统的in-call Activity来启动呼叫,并让系统管理拨号、连接以及语音处理。

要使用该动作,应用程序必须请求CALL_PHONE的user-permission。

<uses-permission android:name="android.permission.CALL_PHONE"/>

或者,也可以通过实现自己的拨号以及语音处理框架来完全替换传出的电话服务栈。

可以使用前面的技术来截获传出的呼叫Intent和修改拨打的号码,或者阻止传出的呼叫,作为完全替换拨号程序的一种方法。

 

访问电话服务的属性及状态

TelephonyManager

TelephonyManager telephonyManager = getSystemService(Context.TELEPHONY_SERVICE);

读取电话设备的详细信息

通过TelephonyManager可以获得电话类型(GSM、CDMA或SIP)、唯一的ID(IMEI或者MEID)、软件版本和手机号码、手机连接到的网络类型、SIM或所连接的运营商网络名称和所在国家等。

除了电话类型,读取其余属性都需要在应用程序的manifest文件中包含READ_PHONE_STATE权限。

<uses-permission android:name="android.permission.READ_PHONE_STATE"/>

读取网络详细信息

getNetworkOperator 读取移动国家代码和移动网络代码(MCC+MNC)

getNetworkCountryIso 读取国家ISO代码

getNetworkOperatorName 读取网络运行时名称

getNetworkType 读取连接的网络信息

读取SIM详细信息

getSimState 获取Sim状态

getSimSerialNumber 获取当前SIM的序列号

getSimOperatorName 获取服务提供商的名称

getSimCountryIso

getSimOperator

读取数据连接和传输状态的详细信息

getDataActivity 查找当前数据传输Activity

getDataState 查找当前数据连接状态

Telephony Manager只指示基于电话服务的数据连接(移动数据,而不是Wi-Fi)。因此,在大多数情况下,Connectivity Manager是确定当前连接状态的更好的方法。

 

使用PhoneStateListener监听电话状态的变化

为了监视并管理电话状态,应用程序必须指定READ_PHONE_STATE权限。

创建一个实现了PhoneStateListener的新类以监听并响应电话状态变化事件,包括呼叫状态(响铃、摘机等)、蜂窝位置变化、语音邮件和呼叫转移状态、电话服务变化以及移动信号强度的变化。

创建了PhoneStateListener以后,将它注册到Telephony Manager中,使用位掩码指示想要监听的事件。

TelephonyManager.listen()

PhoneStateListener phoneStateListener = PhoneStateListener();

telephonyManager.listen(phoneStateListener,
                        PhoneStateListener.LISTEN_CALL_FORWARDING_INDICATOR |
                        PhoneStateListener.LISTEN_CALL_CALL |
                        PhoneStateListener.LISTEN_CELL_INFO |
                        PhoneStateListener.LISTEN_CELL_LOCATION |
                        PhoneStateListener.LISTEN_DATA_ACTIVITY |
                        PhoneStateListener.LISTEN_DATA_CONNECTION_STATE |
                        PhoneStateListener.LISTEN_MESSAGE_WAITING_INDICATOR |
                        PhoneStateListener.LISTEN_SERVICE_STATE |
                        PhoneStateListener.LISTEN_SIGNAL_STRENGTH |
                        PhoneStateListener.LISTEN_SIGNAL_STRENGTHS);

注销监听器,需要调用listen方法并传入PhoneStateListener.LISTEN_NONE作为位掩码参数。

telephonyManager.listen(phoneStateListener, PhoneStateListener.LISTEN_NONE);

只有当应用程序运行时,PhoneStateListener才会收到电话状态变化通知。

监视传入的电话呼叫

如果应用程序只应该在运行时响应传入的电话呼叫,就应该在PhoneStateListener的实现中重写onCallStateChanged方法,并进行注册,以便在呼叫状态发生变化时接受通知。

  • CALL_STATE_IDLE
  • CALL_STATE_RINGING
  • CALL_STATE_OFFHOOK
PhoneStateListener phoneStateListener = new PhoneStateListener() {
    @Override
    public void onCallStateChanged(int state, String incomingNumber) {

      String callStateStr = "Unknow";

      switch (state) {
          case TelephonyManager.CALL_STATE_IDLE:
            callStateStr = "idie";
            break;
          case TelephonyManager.CALL_STATE_OFFHOOK:
            callStateStr = "offhook";
            break;
          case TelephonyManager.CALL_STATE_RINGING:
            callStateStr = "ringing. Incoming number is: " + incomingNumber;
            break;
          default:
            break;
      }

      Toast.makeText(MainActivity.this, callStateStr, Toast.LENGTH_LONG).show();
    }
};

TelephonyManager telephonyManager = getSystemService(TELEPHONY_SERVICE);
telephonyManager.listen(phoneStateListener, PhoneStateListener.LISTEN_CALL_STATE);

一旦状态变为CALL_STATE_RINGING,系统会显示来电屏幕,询问用户是否要接听电话。

应用程序必须处于运行状态才能接收这个回调。如果每当电话状态变化时,就应该启动应用程序,那么可以注册一个Intent Receiver,用于监听表示电话状态发生变化的Broadcast Intent。

跟踪蜂窝位置变化

通过重写PhoneStateListener实现的onCellLocationChanged方法,每当当前的蜂窝位置发生变化时,可以得到通知。在可以注册以便监听蜂窝位置变化之前,需要将ACCESS_COARSE_LOCATION权限添加到应用程序的manifest文件中。

CellLocation

GsmCellLocation

CdmaCellLocation

PhoneStateListener cellLocationListener = new PhoneStateListener() {
    @Override
    public void onCellLocationChanged(CellLocation location) {
        StringBuilder sb = new StringBuilder();
        if (location instanceof GsmCellLocation) {
            GsmCellLocation gsmLocation = (GsmCellLocation) location;
            sb.append(String.valueOf(gsmLocation.getCid()));
        } else if (location instanceof CdmaCellLocation) {
            CdmaCellLocation cdmaLocation = (CdmaCellLocation) location;
            sb.append(cdmaLocation.getBaseStationId());
            sb.append("\n@");
            sb.append(cdmaLocation.getBaseStationLatitude());
            sb.append(cdmaLocation.getBaseStationLongitude());
        }
        Toast.makeText(MyActivity.this, sb.toString(), Toast.LENGTH_LONG);
    }
};
TelephonyManager telephonyManager = (TelephonyManager) getSystemService(TELEPHONY_SERVICE);
telephonyManager.listen(cellLocationListener, PhoneStateListener.LISTEN_CELL_LOCATION);

跟踪服务变化

onServiceStateChanged

ServiceState

STATE_EMERGENCY_ONLY

The phone is registered and locked. Only emergency numbers are allowed.

STATE_IN_SERVICE

Normal operation condition, the phone is registered with an operator either in home network or in roaming.

STATE_OUT_OF_SERVICE

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.

STATE_POWER_OFF

Radio of telephony is explicitly powered off.

监视数据连接和数据传输状态的变化

PhoneStateListener监视移动数据连接以及移动数据传输的变化。但是不包含使用Wi-Fi传输的数据。

onDataActivity用以跟踪数据传输Activity。

onDataConnectionStateChanged以请求通知数据连接状态的变化。

使用Intent Receiver监视传入的电话呼叫

当电话状态由来电、接听和挂断而发生变化时,TelephonyManager会广播一个ACTION_PHONE_STATE_CHANGED Intent。

通过在manifest文件中注册一个监听此Broadcast Intent的Intent Receiver,就可以在任何时候监听来电,即使你的应用程序没有运行。但是需要READ_PHONE_STATE权限。

public class PhoneStateChangedReceiver extends BroadcastReceiver{
    @Override
    public void onReceive(Context context, Intent intent) {
        String phoneState=intent.getStringExtra(TelephonyManager.EXTRA_STATE);
        if(phoneState.equals(TelephonyManager.EXTRA_STATE_RINGING)){
            String phoneNumber=intent.getStringExtra(TelephonyManager.EXTRA_INCOMING_NUMBER);
            Toast.makeText(context,"Incoming Call From: "+phoneNumber,Toast.LENGTH_LONG);
        }
        
    }
}

 

在应用程序中使用SMS和MMS

MMS(多媒体消息服务)消息允许用户发送和接收包含了多媒体附件(如照片、视频和音频)的消息。

Android支持使用安装在设备上、监听SEND和SEND_TO Broadcast Intent的消息传递应用程序来发送SMS和MMS消息。

Android通过SMSManager类在应用程序中提供了完整的SMS功能。

使用Intent从应用程序中发送SMS

Intent smsIntent=new Intent(Intent.ACTION_SENDTO, Uri.parse("sms:55512345"));
smsIntent.putExtra("sms_body","Press send to send me");
startActivity(smsIntent);

使用SMSManager发送SMS消息

SmsManager

为了发送SMS消息,应用程序必须指定 SEND_SMS 权限。

1.发送文本消息

SmsManager smsManager = SmsManager.getDefault();
smsManager.sendTextMessage("55512345", null, "This is a SMS Message", null, null);

sendTextMessage

跟踪并确认SMS消息传递

为了跟踪发出的SMS消息的传递过程并确认成功,需要实现并注册一些Broadcast Receiver,用于监听在创建传入sendTextMessage方法中的Pending Intent时所指定的动作。

PendingIntent: if not NULL this PendingIntent is broadcast when the message is successfully sent, or failed. The result code will be Activity.RESULT_OK

RESULT_ERROR_GENERIC_FAILURE

Generic failure cause

RESULT_ERROR_NO_SERVICE

Failed because service is currently unavailable

RESULT_ERROR_NULL_PDU

Failed because no pdu provided

RESULT_ERROR_RADIO_OFF

Failed because radio was explicitly turned off

SmsManager smsManager = SmsManager.getDefault();

String SENT_SMS_ACTION = "com.example.SENT_SMS_ACTION";
String DELIVERED_SMS_ACTION = "com.example.DELIVERED_SMS_ACTION";

//创建sentIntent参数
Intent sentIntent = new Intent(SENT_SMS_ACTION);
PendingIntent sentPI = PendingIntent.getBroadcast(getApplicationContext(), 0, sentIntent, PendingIntent.FLAG_UPDATE_CURRENT);

//创建deliveryIntent参数
Intent deliveryIntent = new Intent(DELIVERED_SMS_ACTION);
PendingIntent deliveryPI = PendingIntent.getBroadcast(getApplicationContext(), 0, deliveryIntent, PendingIntent.FLAG_UPDATE_CURRENT);

//注册Broadcast Resceiver
registerReceiver(new BroadcastReceiver() {
    @Override
    public void onReceive(Context context, Intent intent) {
      String resultText = "UNKNOWN";

      switch (getResultCode()) {
          case Activity.RESULT_OK:
      resultText = "Transmission Successful";
      break;
          case SmsManager.RESULT_ERROR_GENERIC_FAILURE:
      resultText = "Transmission Failed";
      break;
          case SmsManager.RESULT_ERROR_NO_SERVICE:
      resultText = "Transmission Failed: No Service";
      break;
          case SmsManager.RESULT_ERROR_NULL_PDU:
      resultText = "Transmission Failed: No PDU specified";
      break;
          case SmsManager.RESULT_ERROR_RADIO_OFF:
      resultText = "Transmission Failed: Radio is off";
      break;
      }
          }
      }, new IntentFilter(SENT_SMS_ACTION));

registerReceiver(new BroadcastReceiver() {
    @Override
    public void onReceive(Context context, Intent intent) {
      Toast.makeText(getApplicationContext(), "SMS Delivered", Toast.LENGTH_LONG).show();
          }
      }, new IntentFilter(DELIVERED_SMS_ACTION));

//发送消息
String sendTo = "55512345";
String message = "Android supports programmatic SMS messaging!";

smsManager.sendTextMessage(sendTo, null, message, sentPI, deliveryPI);

遵守最大SMS消息尺寸

每个运营商规定的每条SMS文本消息的最大长度可能不同,但是它们通常被限制为160个字符,因此比此更长的消息需要被分解成一系列更小的部分。

SMS Manager包含divideMessage方法,它可以接受一个字符串作为输入,并将其分成一个消息数组列表,其中的每条消息都不超过最大允许的尺寸。

分解之后就可以使用SMS Manager的sendMultipartTextMessage方法传输消息数组:

ArrayList<String> messageArray = smsManager.divideMessage(message);
ArrayList<PendingIntent> sentIntents = new ArrayList<PendingIntent>();
for (int i = 0; i < messageArray.size(); i++) {
    sentIntents.add(sentPI);
}
smsManager.sendMultipartTextMessage(sendTo, null, messageArray, sentIntents, null);

发送数据消息

sendDataMessage  Send a data based SMS to a specific application port.

short destinationPort = 80;
byte[] data = new byte[]{};
smsManager.sendDataMessage(sendTo, null, destinationPort, data, sentPI, deliveryPI);

监听传入的SMS消息

当设备接收到一条新的SMS消息时,就会使用android.provider.Telephony.SMS_RECEIVED动作触发一个新的Broadcast Intent。

这是一个字符串字面量,当前的SDK并不包含对该字符串的引用,因此在应用程序中使用它时必须显式的指定它。

SMS所接收到的动作字符串是隐藏的 - 所以是不受支持的API。这意味着任何未来的平台发布中可能会改变它。使用不受支持的API不是一种好的做法,因为这样承担了很大的风险。当使用不受支持的平台功能时需要格外小心,因为它们在未来的平台发布中可能发生变化。

监听SMS Broadcast Intent需要在manifest文件中指定RECEIVE_SMS权限。