Phát hiện eSIM và thẻ SIM
Phát hiện thẻ
Thiết bị Android có thẻ SIM và eSIM dùng các mã nhận dạng sau trong API điện thoại, bao gồm cả [`TelephonyManager`](/reference/android/telephony/TelephonyManager) và [`SubscriptionManager`](/reference/android/telephony/SubscriptionManager): * Mã gói thuê bao: mã nhận dạng duy nhất của một gói thuê bao trên thiết bị di động. * Chỉ mục hoặc mã vị trí logic: chỉ mục duy nhất tham chiếu đến vị trí SIM logic. Mã vùng logic bắt đầu từ 0 và tăng lên tuỳ thuộc vào số lượng khe đang hoạt động được hỗ trợ trên thiết bị. Ví dụ: thiết bị hai SIM thường có khe 0 và khe 1. Nếu có nhiều khe thực tế nhưng chỉ hỗ trợ một khe đang hoạt động, thì thiết bị sẽ chỉ có mã vùng logic là 0. * Chỉ mục vị trí thực tế hoặc mã nhận dạng: chỉ mục duy nhất đề cập đến vị trí lắp thẻ SIM thực. Mã vị trí thực bắt đầu từ 0 và tăng lên tuỳ thuộc vào số lượng khe thực tế trên thiết bị. Giá trị này khác với số lượng khe logic mà một thiết bị có, tương ứng với số lượng khe đang hoạt động mà một thiết bị có thể sử dụng. Ví dụ: một thiết bị chuyển đổi giữa chế độ 2 SIM và một SIM có thể luôn có 2 khe cắm vật lý, nhưng ở chế độ một SIM thì thiết bị sẽ chỉ có một khe logic. * Mã thẻ: mã nhận dạng duy nhất dùng để nhận dạng một thẻ UiccCard. ![Sơ đồ về cách sử dụng mã nhận dạng trong trường hợp có 2 khe logic và 3 khe vật lý](/images/guide/topics/connectivity/tel-ids.png) Trong sơ đồ trên: * Thiết bị có 2 khe logic. * Trong vị trí thực 0, có một thẻ UICC thực với hồ sơ đang hoạt động. * Trong vùng thực 2 là một eUICC có hồ sơ đang hoạt động. * Khe thực tế 1 hiện không được sử dụng. ![Sơ đồ về cách sử dụng mã nhận dạng trong trường hợp có 3 khe logic và 2 khe vật lý](/images/guide/topics/connectivity/tel-ids-2.png) Trong sơ đồ trên: * Thiết bị có 3 khe logic. * Trong vị trí thực 0, có một thẻ UICC thực với hồ sơ đang hoạt động. * Trong vùng thực 1 là một eUICC có hai cấu hình đã tải xuống, cả hai đều hoạt động bằng MEP (Nhiều cấu hình được bật).
Tổng quan về giao thức khởi tạo phiên
Android cung cấp một API hỗ trợ Giao thức khởi tạo phiên (SIP). Điều này cho phép bạn thêm các tính năng điện thoại Internet dựa trên SIP vào ứng dụng của mình. Android có một ngăn xếp giao thức SIP đầy đủ và các dịch vụ quản lý cuộc gọi tích hợp cho phép các ứng dụng dễ dàng thiết lập cuộc gọi thoại đi và đến mà không cần phải quản lý phiên hoạt động, giao tiếp ở cấp độ truyền tải hoặc trực tiếp ghi âm/phát âm thanh.
Dưới đây là ví dụ về các loại ứng dụng có thể sử dụng API SIP:
- Hội nghị truyền hình
- Nhắn tin tức thì
Yêu cầu và giới hạn
Dưới đây là các yêu cầu đối với việc phát triển ứng dụng SIP:
- Bạn phải có thiết bị di động chạy Android 2.3 trở lên.
- SIP chạy trên kết nối dữ liệu không dây, vì vậy, thiết bị của bạn phải có kết nối dữ liệu (với dịch vụ dữ liệu di động hoặc Wi-Fi). Tức là bạn không kiểm thử được trên AVD mà chỉ có thể kiểm thử trên thiết bị thực. Để biết thông tin chi tiết, vui lòng xem bài viết Kiểm thử ứng dụng SIP.
- Mỗi người tham gia trong phiên giao tiếp của ứng dụng phải có tài khoản SIP. Có nhiều nhà cung cấp SIP khác nhau cung cấp tài khoản SIP.
Lưu ý: Thư viện android.net.sip
không hỗ trợ các cuộc gọi video. Nếu bạn muốn triển khai tính năng gọi VOIP bằng ngăn xếp SIP (chẳng hạn như android.net.sip
), hãy xem một trong nhiều giải pháp thay thế nguồn mở hiện đại để làm cơ sở cho việc triển khai lệnh gọi VOIP. Ngoài ra, bạn có thể triển khai API ConnectionService
để tích hợp chặt chẽ các lệnh gọi này vào ứng dụng Trình quay số của thiết bị.
Các lớp và giao diện của SIP API
Dưới đây là bản tóm tắt về các lớp và một giao diện (SipRegistrationListener
) có trong API Android SIP:
Lớp/Giao diện | Nội dung mô tả |
---|---|
SipAudioCall |
Xử lý cuộc gọi thoại qua Internet qua SIP. |
SipAudioCall.Listener |
Trình nghe các sự kiện liên quan đến cuộc gọi SIP, chẳng hạn như khi một cuộc gọi đang được nhận ("khi đang đổ chuông") hoặc một cuộc gọi đang đi ("khi đang gọi"). |
SipErrorCode |
Xác định mã lỗi được trả về trong các hành động SIP. |
SipManager |
Cung cấp API cho các tác vụ SIP, chẳng hạn như khởi tạo kết nối SIP và cung cấp quyền truy cập vào các dịch vụ SIP có liên quan. |
SipProfile |
Xác định hồ sơ SIP, bao gồm tài khoản SIP, thông tin miền và máy chủ. |
SipProfile.Builder |
Lớp trợ giúp để tạo SipProfile. |
SipSession |
Đại diện cho một phiên SIP được liên kết với hộp thoại SIP hoặc một giao dịch độc lập không có trong hộp thoại. |
SipSession.Listener |
Trình nghe các sự kiện liên quan đến phiên SIP, chẳng hạn như khi một phiên đang được đăng ký ("khi đăng ký") hoặc một cuộc gọi được gọi đi ("khi gọi"). |
SipSession.State |
Xác định trạng thái phiên SIP, chẳng hạn như "đăng ký", "cuộc gọi đi" và "đang gọi". |
SipRegistrationListener |
Một giao diện làm trình nghe của các sự kiện đăng ký SIP. |
Tạo tệp kê khai
Nếu bạn đang phát triển một ứng dụng sử dụng API SIP, hãy nhớ rằng tính năng này chỉ được hỗ trợ trên Android 2.3 (API cấp 9) và các phiên bản cao hơn của nền tảng này. Ngoài ra, trong số các thiết bị chạy Android 2.3 (API cấp 9) trở lên, không phải thiết bị nào cũng hỗ trợ SIP.
Để sử dụng SIP, hãy thêm các quyền sau vào tệp kê khai của ứng dụng:
android.permission.USE_SIP
android.permission.INTERNET
Để đảm bảo rằng ứng dụng của bạn chỉ có thể được cài đặt trên các thiết bị có thể hỗ trợ SIP, hãy thêm thông tin sau vào tệp kê khai của ứng dụng:
<uses-sdk android:minSdkVersion="9" />
Nhãn này cho biết ứng dụng của bạn yêu cầu Android 2.3 trở lên. Để biết thêm thông tin, hãy xem phần Cấp độ API và tài liệu cho phần tử <uses-sdk>
.
Để kiểm soát cách lọc ứng dụng khỏi các thiết bị không hỗ trợ SIP (ví dụ: trên Google Play), hãy thêm thông tin sau vào tệp kê khai của ứng dụng:
<uses-feature android:name="android.software.sip.voip" />
Trạng thái này cho biết ứng dụng của bạn sử dụng API SIP. Nội dung khai báo phải bao gồm thuộc tính android:required
cho biết liệu bạn có muốn lọc ứng dụng khỏi các thiết bị không hỗ trợ SIP hay không.
Bạn cũng có thể cần khai báo <uses-feature>
khác, tuỳ thuộc vào cách triển khai của bạn. Để biết thêm thông tin, hãy xem tài liệu cho phần tử <uses-feature>
.
Nếu ứng dụng của bạn được thiết kế để nhận lệnh gọi, bạn cũng phải xác định receiver (lớp con BroadcastReceiver
) trong tệp kê khai của ứng dụng:
<receiver android:name=".IncomingCallReceiver" android:label="Call Receiver" />
Dưới đây là các phần trích dẫn từ tệp kê khai Sipdemo:
<?xml version="1.0" encoding="utf-8"?> <manifest xmlns:android="http://schemas.android.com/apk/res/android" package="com.example.android.sip"> ... <receiver android:name=".IncomingCallReceiver" android:label="Call Receiver" /> ... <uses-sdk android:minSdkVersion="9" /> <uses-permission android:name="android.permission.USE_SIP" /> <uses-permission android:name="android.permission.INTERNET" /> ... <uses-feature android:name="android.software.sip.voip" android:required="true" /> <uses-feature android:name="android.hardware.wifi" android:required="true" /> <uses-feature android:name="android.hardware.microphone" android:required="true" /> </manifest>
Tạo SipManager
Để sử dụng API SIP, ứng dụng của bạn phải tạo một đối tượng SipManager
. SipManager
sẽ xử lý những vấn đề sau trong ứng dụng của bạn:
- Đang khởi động phiên SIP.
- Đang bắt đầu và nhận cuộc gọi.
- Đăng ký và huỷ đăng ký với nhà cung cấp SIP.
- Đang xác minh khả năng kết nối của phiên.
Bạn tạo thực thể cho một SipManager
mới như sau:
Kotlin
val sipManager: SipManager? by lazy(LazyThreadSafetyMode.NONE) { SipManager.newInstance(this) }
Java
public SipManager sipManager = null; ... if (sipManager == null) { sipManager = SipManager.newInstance(this); }
Đăng ký bằng máy chủ SIP
Một ứng dụng Android SIP thông thường bao gồm một hoặc nhiều người dùng, mỗi người đều có một tài khoản SIP. Trong ứng dụng Android SIP, mỗi tài khoản SIP được biểu thị bằng một đối tượng SipProfile
.
SipProfile
xác định hồ sơ SIP, bao gồm tài khoản SIP, thông tin miền và máy chủ. Hồ sơ liên kết với tài khoản SIP trên thiết bị đang chạy ứng dụng được gọi là hồ sơ cục bộ. Hồ sơ mà phiên kết nối được gọi là hồ sơ ngang hàng. Khi ứng dụng SIP của bạn đăng nhập vào máy chủ SIP bằng SipProfile
cục bộ, thao tác này sẽ đăng ký thiết bị làm vị trí để gửi các lệnh gọi SIP đến địa chỉ SIP của bạn một cách hiệu quả.
Phần này cho biết cách tạo SipProfile
, đăng ký với máy chủ SIP và theo dõi các sự kiện đăng ký.
Bạn sẽ tạo đối tượng SipProfile
như sau:
Kotlin
private var sipProfile: SipProfile? = null ... val builder = SipProfile.Builder(username, domain) .setPassword(password) sipProfile = builder.build()
Java
public SipProfile sipProfile = null; ... SipProfile.Builder builder = new SipProfile.Builder(username, domain); builder.setPassword(password); sipProfile = builder.build();
Phần trích dẫn mã sau đây sẽ mở hồ sơ cục bộ để thực hiện cuộc gọi và/hoặc nhận các lệnh gọi SIP chung. Phương thức gọi có thể thực hiện các lệnh gọi tiếp theo thông qua mSipManager.makeAudioCall
. Phần trích dẫn này cũng đặt thao tác android.SipDemo.INCOMING_CALL
. Thao tác này sẽ được bộ lọc ý định sử dụng khi thiết bị nhận được lệnh gọi (xem phần Thiết lập bộ lọc ý định để nhận lệnh gọi). Đây là bước đăng ký:
Kotlin
val intent = Intent("android.SipDemo.INCOMING_CALL") val pendingIntent: PendingIntent = PendingIntent.getBroadcast(this, 0, intent, Intent.FILL_IN_DATA) sipManager?.open(sipProfile, pendingIntent, null)
Java
Intent intent = new Intent(); intent.setAction("android.SipDemo.INCOMING_CALL"); PendingIntent pendingIntent = PendingIntent.getBroadcast(this, 0, intent, Intent.FILL_IN_DATA); sipManager.open(sipProfile, pendingIntent, null);
Cuối cùng, mã này sẽ đặt SipRegistrationListener
trên SipManager
. Thông tin này sẽ theo dõi xem SipProfile
có được đăng ký thành công với nhà cung cấp dịch vụ SIP của bạn hay không:
Kotlin
sipManager?.setRegistrationListener(sipProfile?.uriString, object : SipRegistrationListener { override fun onRegistering(localProfileUri: String) { updateStatus("Registering with SIP Server...") } override fun onRegistrationDone(localProfileUri: String, expiryTime: Long) { updateStatus("Ready") } override fun onRegistrationFailed( localProfileUri: String, errorCode: Int, errorMessage: String ) { updateStatus("Registration failed. Please check settings.") } })
Java
sipManager.setRegistrationListener(sipProfile.getUriString(), new SipRegistrationListener() { public void onRegistering(String localProfileUri) { updateStatus("Registering with SIP Server..."); } public void onRegistrationDone(String localProfileUri, long expiryTime) { updateStatus("Ready"); } public void onRegistrationFailed(String localProfileUri, int errorCode, String errorMessage) { updateStatus("Registration failed. Please check settings."); } }
Khi sử dụng xong một hồ sơ, ứng dụng của bạn sẽ đóng hồ sơ đó để các đối tượng liên kết giải phóng vào bộ nhớ và huỷ đăng ký thiết bị khỏi máy chủ. Ví dụ:
Kotlin
fun closeLocalProfile() { try { sipManager?.close(sipProfile?.uriString) } catch (ee: Exception) { Log.d("WalkieTalkieActivity/onDestroy", "Failed to close local profile.", ee) } }
Java
public void closeLocalProfile() { if (sipManager == null) { return; } try { if (sipProfile != null) { sipManager.close(sipProfile.getUriString()); } } catch (Exception ee) { Log.d("WalkieTalkieActivity/onDestroy", "Failed to close local profile.", ee); } }
Gọi thoại
Để thực hiện cuộc gọi thoại, bạn phải có sẵn những thứ sau:
SipProfile
đang thực hiện cuộc gọi ("hồ sơ cục bộ") và địa chỉ SIP hợp lệ để nhận cuộc gọi ("hồ sơ ngang hàng").- Đối tượng
SipManager
.
Để gọi thoại, bạn nên thiết lập SipAudioCall.Listener
. Phần lớn hoạt động tương tác của máy khách với ngăn xếp SIP xảy ra thông qua trình nghe. Trong đoạn mã này, bạn sẽ thấy cách SipAudioCall.Listener
thiết lập mọi thứ sau khi lệnh gọi được thiết lập:
Kotlin
var listener: SipAudioCall.Listener = object : SipAudioCall.Listener() { override fun onCallEstablished(call: SipAudioCall) { call.apply { startAudio() setSpeakerMode(true) toggleMute() } } override fun onCallEnded(call: SipAudioCall) { // Do something. } }
Java
SipAudioCall.Listener listener = new SipAudioCall.Listener() { @Override public void onCallEstablished(SipAudioCall call) { call.startAudio(); call.setSpeakerMode(true); call.toggleMute(); ... } @Override public void onCallEnded(SipAudioCall call) { // Do something. } };
Sau khi thiết lập SipAudioCall.Listener
, bạn có thể thực hiện lệnh gọi. Phương thức SipManager
makeAudioCall
nhận các thông số sau:
- Một hồ sơ SIP cục bộ (người gọi).
- Hồ sơ SIP ngang hàng (người dùng đang được gọi).
SipAudioCall.Listener
để theo dõi các sự kiện gọi từSipAudioCall
. Giá trị này có thể lànull
, nhưng như đã trình bày ở trên, trình nghe được dùng để thiết lập sau khi lệnh gọi được thiết lập.- Giá trị thời gian chờ, tính bằng giây.
Ví dụ:
Kotlin
val call: SipAudioCall? = sipManager?.makeAudioCall( sipProfile?.uriString, sipAddress, listener, 30 )
Java
call = sipManager.makeAudioCall(sipProfile.getUriString(), sipAddress, listener, 30);
Đang nhận cuộc gọi
Để nhận cuộc gọi, ứng dụng SIP phải bao gồm một lớp con của BroadcastReceiver
có khả năng phản hồi ý định cho biết có cuộc gọi đến. Do đó, bạn phải làm những việc sau trong ứng dụng:
- Trong
AndroidManifest.xml
, hãy khai báo<receiver>
. Trong Sipdemo, đây là<receiver android:name=".IncomingCallReceiver" android:label="Call Receiver" />
. - Triển khai receiver (là lớp con của
BroadcastReceiver
). Trong Sipdemo, đây làIncomingCallReceiver
. - Khởi động hồ sơ cục bộ (
SipProfile
) với ý định đang chờ xử lý sẽ kích hoạt dịch vụ nhận khi có người gọi hồ sơ cục bộ. - Thiết lập bộ lọc ý định lọc theo thao tác đại diện cho lệnh gọi đến. Trong Sipdemo, thao tác này là
android.SipDemo.INCOMING_CALL
.
Phân lớp BroadcastReceiver
Để nhận cuộc gọi, ứng dụng SIP của bạn phải phân lớp con BroadcastReceiver
. Hệ thống Android xử lý các cuộc gọi SIP đến và thông báo ý định "cuộc gọi đến" (do ứng dụng xác định) khi nhận được một cuộc gọi. Dưới đây là mã
BroadcastReceiver
phân lớp con từ mẫu Sipdemo.
Kotlin
/** * Listens for incoming SIP calls, intercepts and hands them off to WalkieTalkieActivity. */ class IncomingCallReceiver : BroadcastReceiver() { /** * Processes the incoming call, answers it, and hands it over to the * WalkieTalkieActivity. * @param context The context under which the receiver is running. * @param intent The intent being received. */ override fun onReceive(context: Context, intent: Intent) { val wtActivity = context as WalkieTalkieActivity var incomingCall: SipAudioCall? = null try { incomingCall = wtActivity.sipManager?.takeAudioCall(intent, listener) incomingCall?.apply { answerCall(30) startAudio() setSpeakerMode(true) if (isMuted) { toggleMute() } wtActivity.call = this wtActivity.updateStatus(this) } } catch (e: Exception) { incomingCall?.close() } } private val listener = object : SipAudioCall.Listener() { override fun onRinging(call: SipAudioCall, caller: SipProfile) { try { call.answerCall(30) } catch (e: Exception) { e.printStackTrace() } } } }
Java
/** * Listens for incoming SIP calls, intercepts and hands them off to WalkieTalkieActivity. */ public class IncomingCallReceiver extends BroadcastReceiver { /** * Processes the incoming call, answers it, and hands it over to the * WalkieTalkieActivity. * @param context The context under which the receiver is running. * @param intent The intent being received. */ @Override public void onReceive(Context context, Intent intent) { SipAudioCall incomingCall = null; try { SipAudioCall.Listener listener = new SipAudioCall.Listener() { @Override public void onRinging(SipAudioCall call, SipProfile caller) { try { call.answerCall(30); } catch (Exception e) { e.printStackTrace(); } } }; WalkieTalkieActivity wtActivity = (WalkieTalkieActivity) context; incomingCall = wtActivity.sipManager.takeAudioCall(intent, listener); incomingCall.answerCall(30); incomingCall.startAudio(); incomingCall.setSpeakerMode(true); if(incomingCall.isMuted()) { incomingCall.toggleMute(); } wtActivity.call = incomingCall; wtActivity.updateStatus(incomingCall); } catch (Exception e) { if (incomingCall != null) { incomingCall.close(); } } } }
Thiết lập bộ lọc ý định để nhận lệnh gọi
Khi nhận được một lệnh gọi mới, dịch vụ SIP sẽ gửi một ý định bằng chuỗi thao tác do ứng dụng cung cấp. Trong Sipdemo, chuỗi hành động này là android.SipDemo.INCOMING_CALL
.
Đoạn mã này được trích từ Sipdemo cho thấy cách đối tượng SipProfile
được tạo bằng ý định đang chờ xử lý dựa trên chuỗi hành động android.SipDemo.INCOMING_CALL
. Đối tượng PendingIntent
sẽ thực hiện một thông báo truyền tin khi SipProfile
nhận được một lệnh gọi:
Kotlin
val sipManager: SipManager? by lazy(LazyThreadSafetyMode.NONE) { SipManager.newInstance(this) } var sipProfile: SipProfile? = null ... val intent = Intent("android.SipDemo.INCOMING_CALL") val pendingIntent: PendingIntent = PendingIntent.getBroadcast(this, 0, intent, Intent.FILL_IN_DATA) sipManager?.open (sipProfile, pendingIntent, null)
Java
public SipManager sipManager = null; public SipProfile sipProfile = null; ... Intent intent = new Intent(); intent.setAction("android.SipDemo.INCOMING_CALL"); PendingIntent pendingIntent = PendingIntent.getBroadcast(this, 0, intent, Intent.FILL_IN_DATA); sipManager.open(sipProfile, pendingIntent, null);
Tin truyền sẽ bị chặn bởi bộ lọc ý định, sau đó sẽ kích hoạt receiver (IncomingCallReceiver
). Bạn có thể chỉ định bộ lọc ý định trong tệp kê khai của ứng dụng hoặc thực hiện việc này trong mã như trong phương thức onCreate()
của ứng dụng mẫu Sipdemo của Activity
của ứng dụng:
Kotlin
class WalkieTalkieActivity : Activity(), View.OnTouchListener { ... lateinit var callReceiver: IncomingCallReceiver ... override fun onCreate(savedInstanceState: Bundle) { val filter = IntentFilter().apply { addAction("android.SipDemo.INCOMING_CALL") } callReceiver = IncomingCallReceiver() this.registerReceiver(callReceiver, filter) ... } ... }
Java
public class WalkieTalkieActivity extends Activity implements View.OnTouchListener { ... public IncomingCallReceiver callReceiver; ... @Override public void onCreate(Bundle savedInstanceState) { IntentFilter filter = new IntentFilter(); filter.addAction("android.SipDemo.INCOMING_CALL"); callReceiver = new IncomingCallReceiver(); this.registerReceiver(callReceiver, filter); ... } ... }
Kiểm thử các ứng dụng SIP
Để kiểm thử các ứng dụng SIP, bạn cần có:
- Thiết bị di động đang chạy Android 2.3 trở lên. SIP chạy qua mạng không dây, vì vậy, bạn phải kiểm thử trên một thiết bị thực. Bạn không thể kiểm thử trên AVD.
- Tài khoản SIP. Có nhiều nhà cung cấp SIP khác nhau cung cấp tài khoản SIP.
- Nếu bạn đang thực hiện cuộc gọi, thì cuộc gọi đó cũng phải đến một tài khoản SIP hợp lệ.
Cách kiểm thử ứng dụng SIP:
- Trên thiết bị của bạn, hãy kết nối với mạng không dây (Cài đặt > Không dây và mạng > Wi-Fi > Cài đặt Wi-Fi).
- Thiết lập thiết bị di động để kiểm thử, như mô tả trong Phát triển trên thiết bị.
- Chạy ứng dụng trên thiết bị di động của bạn, như mô tả trong Phát triển trên thiết bị.
- Nếu sử dụng Android Studio, bạn có thể xem đầu ra nhật ký ứng dụng bằng cách mở bảng điều khiển Nhật ký sự kiện (View > Tool Windows > Event Log (Xem > Cửa sổ công cụ > Nhật ký sự kiện)).
- Đảm bảo ứng dụng của bạn được định cấu hình để tự động chạy Logcat khi ứng dụng chạy:
- Chọn Run > Edit Configurations (Chạy > Chỉnh sửa cấu hình).
- Chọn thẻ Miscellaneous (Khác) trong cửa sổ Run/Debug Configurations (Cấu hình chạy/gỡ lỗi).
- Trong Logcat, hãy chọn Show logcat Automatically (Tự động hiện logcat) rồi chọn OK.